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") }