197 lines
6.1 KiB
Go
197 lines
6.1 KiB
Go
/*
|
|
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"
|
|
"context"
|
|
"crypto"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/asn1"
|
|
"errors"
|
|
"fmt"
|
|
"math/big"
|
|
"net/http"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
"golang.org/x/crypto/ocsp"
|
|
)
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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)
|
|
|
|
return bytes.Equal(issuerHash, requestHash), nil
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
// String returns the name of the CertificateIssuer
|
|
func (i *CertificateIssuer) String() string {
|
|
return i.caCertificate.Subject.String()
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
// The backgroundTasks of the issuer are started
|
|
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()
|
|
}
|
|
|
|
// OcspSource is an implementation of the Source interface used by the Cloudflare OCSP response handler
|
|
type OcspSource struct {
|
|
issuers []*CertificateIssuer
|
|
}
|
|
|
|
// Option to configure OcspSource
|
|
type Option func(*OcspSource) error
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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
|
|
}
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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")
|
|
}
|
|
|
|
// BackgroundTasks delegates background task handling to the CertificateIssuer instances of the OcspSource
|
|
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")
|
|
}
|