You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
goocsp/internal/ocspsource/ocspsource.go

141 lines
3.7 KiB
Go

package ocspsource
import (
"bytes"
"crypto"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"errors"
"fmt"
"math/big"
"net/http"
"time"
"github.com/sirupsen/logrus"
"golang.org/x/crypto/ocsp"
)
type CertificateIssuer struct {
responderCertificate *x509.Certificate
responderKey crypto.Signer
caCertificate *x509.Certificate
certDb *OpenSSLCertDB
certificateList []*x509.Certificate
}
func NewIssuer(caCertificate, responderCertificate *x509.Certificate, responderKey crypto.Signer, certificateFile string) (*CertificateIssuer, error) {
certDb, err := NewCertDB(certificateFile)
if err != nil {
return nil, fmt.Errorf("could not initialize certificate database: %w", err)
}
return &CertificateIssuer{
caCertificate: caCertificate, responderCertificate: responderCertificate, responderKey: responderKey, certDb: certDb,
}, nil
}
func (i *CertificateIssuer) publicKeyMatches(requestHash []byte, algorithm crypto.Hash) (bool, error) {
var publicKeyInfo struct {
Algorithm pkix.AlgorithmIdentifier
PublicKey asn1.BitString
}
if _, err := asn1.Unmarshal(i.caCertificate.RawSubjectPublicKeyInfo, &publicKeyInfo); err != nil {
return false, fmt.Errorf("could not parse CA certificate public key info: %w", err)
}
h := algorithm.New()
if _, err := h.Write(publicKeyInfo.PublicKey.RightAlign()); err != nil {
return false, fmt.Errorf("could not update digest instance: %w", err)
}
issuerHash := h.Sum(nil)
return bytes.Compare(issuerHash, requestHash) == 0, nil
}
func (i *CertificateIssuer) buildUnknownResponse(number *big.Int) ([]byte, error) {
template := &ocsp.Response{
SerialNumber: number,
Status: ocsp.Unknown,
}
return i.buildResponse(template)
}
func (i *CertificateIssuer) buildResponse(template *ocsp.Response) ([]byte, error) {
template.ProducedAt = time.Now()
template.ThisUpdate = time.Now()
template.NextUpdate = time.Now().Add(time.Hour)
template.Certificate = i.responderCertificate
return ocsp.CreateResponse(i.caCertificate, i.responderCertificate, *template, i.responderKey)
}
func (i *CertificateIssuer) String() string {
return i.caCertificate.Subject.String()
}
func (i *CertificateIssuer) LookupResponse(serialNumber *big.Int) ([]byte, error) {
response := i.certDb.LookupResponseTemplate(serialNumber)
if response == nil {
return i.buildUnknownResponse(serialNumber)
}
return i.buildResponse(response)
}
type OcspSource struct {
issuers []*CertificateIssuer
}
type Option func(*OcspSource) error
func NewSource(options ...Option) (*OcspSource, error) {
source := &OcspSource{}
for _, option := range options {
err := option(source)
if err != nil {
return nil, err
}
}
if len(source.issuers) == 0 {
return nil, errors.New("no issuers configured")
}
return source, nil
}
func WithIssuer(issuer *CertificateIssuer) Option {
return func(o *OcspSource) error {
o.issuers = append(o.issuers, issuer)
logrus.Infof("add issuer %s as known issuer", issuer)
return nil
}
}
func (o *OcspSource) Response(r *ocsp.Request) ([]byte, http.Header, error) {
issuer, err := o.getIssuer(r.IssuerKeyHash, r.HashAlgorithm)
if err != nil {
return nil, nil, fmt.Errorf("cannot answer request: %v", err)
}
response, err := issuer.LookupResponse(r.SerialNumber)
if err != nil {
return nil, nil, fmt.Errorf("could not build OCSP response: %v", err)
}
return response, nil, nil
}
func (o *OcspSource) getIssuer(keyHash []byte, algorithm crypto.Hash) (*CertificateIssuer, error) {
for _, issuer := range o.issuers {
matched, err := issuer.publicKeyMatches(keyHash, algorithm)
if err != nil {
logrus.Errorf("error for issuer: %v", err)
}
if matched {
return issuer, nil
}
}
return nil, errors.New("issuer key hash does not match any of the known issuers")
}