goocsp/internal/ocspsource/ocspsource.go

198 lines
6.1 KiB
Go
Raw Normal View History

2022-03-06 15:51:09 +00:00
/*
Copyright 2022 CAcert Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package ocspsource
import (
"bytes"
2022-03-06 15:18:04 +00:00
"context"
"crypto"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"errors"
"fmt"
"math/big"
"net/http"
2022-03-06 15:18:04 +00:00
"sync"
"time"
"github.com/sirupsen/logrus"
"golang.org/x/crypto/ocsp"
)
2022-03-06 15:51:09 +00:00
// CertificateIssuer is an abstraction for the OCSP responder that knows certificates of a specific CA
type CertificateIssuer struct {
responderCertificate *x509.Certificate
responderKey crypto.Signer
caCertificate *x509.Certificate
certDb *OpenSSLCertDB
}
2022-03-06 15:51:09 +00:00
// NewIssuer is used to construct a new CertificateIssuer instance
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
}
2022-03-06 15:51:09 +00:00
// publicKeyMatches determines whether a given public key hash matches the public key of the CA certificate of the
// CertificateIssuer.
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)
2022-03-06 15:52:40 +00:00
return bytes.Equal(issuerHash, requestHash), nil
}
2022-03-06 15:51:09 +00:00
// buildUnknownResponse builds the OCSP response template for an unknown certificate
func (i *CertificateIssuer) buildUnknownResponse(number *big.Int) ([]byte, error) {
template := &ocsp.Response{
SerialNumber: number,
Status: ocsp.Unknown,
}
return i.buildResponse(template)
}
2022-03-06 15:51:09 +00:00
// buildResponse fills the timestamp fields of the response template and creates a signed OCSP response
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)
}
2022-03-06 15:51:09 +00:00
// String returns the name of the CertificateIssuer
func (i *CertificateIssuer) String() string {
return i.caCertificate.Subject.String()
}
2022-03-06 15:51:09 +00:00
// LookupResponse looks for status information of a certificate and returns a signed response
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)
}
2022-03-06 15:51:09 +00:00
// The backgroundTasks of the issuer are started
2022-03-06 15:18:04 +00:00
func (i *CertificateIssuer) backgroundTasks(ctx context.Context) {
logrus.Infof("starting background tasks for ceritificate issuer %s", i)
watcherCtx, cancel := context.WithCancel(ctx)
go i.certDb.WatchChanges(watcherCtx)
<-ctx.Done()
cancel()
}
2022-03-06 15:51:09 +00:00
// OcspSource is an implementation of the Source interface used by the Cloudflare OCSP response handler
type OcspSource struct {
issuers []*CertificateIssuer
}
2022-03-06 15:51:09 +00:00
// Option to configure OcspSource
type Option func(*OcspSource) error
2022-03-06 15:51:09 +00:00
// NewSource creates a OcspSource instance configured via the passed Options
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
}
2022-03-06 15:51:09 +00:00
// WithIssuer is an Option to add a CertificateIssuer to an OcspSource
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
}
}
2022-03-06 15:51:09 +00:00
// Response is dispatching incoming requests to the CertificateIssuer instances of the OcspSource
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
}
2022-03-06 15:51:09 +00:00
// The getIssuer method looks for a matching issuer for the issuer key hash of an incoming OCSP request
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")
}
2022-03-06 15:18:04 +00:00
2022-03-06 15:51:09 +00:00
// BackgroundTasks delegates background task handling to the CertificateIssuer instances of the OcspSource
2022-03-06 15:18:04 +00:00
func (o *OcspSource) BackgroundTasks(ctx context.Context) {
wg := sync.WaitGroup{}
wg.Add(len(o.issuers))
logrus.Info("starting OCSP source background tasks")
for _, issuer := range o.issuers {
issuer := issuer
go func() {
issuer.backgroundTasks(ctx)
wg.Done()
}()
}
wg.Wait()
logrus.Info("stopped OCSP source background tasks")
}