2022-03-06 15:51:09 +00:00
|
|
|
/*
|
2022-03-21 17:46:04 +00:00
|
|
|
Copyright 2022 CAcert Inc.
|
|
|
|
SPDX-License-Identifier: Apache-2.0
|
2022-03-06 15:51:09 +00:00
|
|
|
|
2022-03-21 17:46:04 +00:00
|
|
|
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
|
2022-03-06 15:51:09 +00:00
|
|
|
|
2022-03-21 17:46:04 +00:00
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
2022-03-06 15:51:09 +00:00
|
|
|
|
2022-03-21 17:46:04 +00:00
|
|
|
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.
|
2022-03-06 15:51:09 +00:00
|
|
|
*/
|
|
|
|
|
2022-03-06 13:40:46 +00:00
|
|
|
package ocspsource
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"crypto"
|
|
|
|
"crypto/x509"
|
|
|
|
"crypto/x509/pkix"
|
|
|
|
"encoding/asn1"
|
2022-03-14 10:21:40 +00:00
|
|
|
"encoding/hex"
|
2022-03-06 13:40:46 +00:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"math/big"
|
|
|
|
"net/http"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/sirupsen/logrus"
|
2022-03-28 19:28:41 +00:00
|
|
|
|
|
|
|
"git.cacert.org/cacert-goocsp/pkg/ocsp"
|
2022-03-06 13:40:46 +00:00
|
|
|
)
|
|
|
|
|
2022-03-28 19:16:14 +00:00
|
|
|
type CertificateUpdate struct {
|
|
|
|
Serial *big.Int
|
|
|
|
Status int
|
|
|
|
NotAfter time.Time
|
|
|
|
RevokedAt time.Time
|
|
|
|
RevocationReason int
|
|
|
|
}
|
|
|
|
|
|
|
|
var idPKIXOCSPExtendedRevoke = asn1.ObjectIdentifier([]int{1, 3, 6, 1, 5, 5, 7, 48, 1, 9})
|
|
|
|
|
|
|
|
type CertificateDatabase interface {
|
|
|
|
LookupResponseTemplate(*big.Int) *ocsp.Response
|
|
|
|
UpdateCertificate(*CertificateUpdate)
|
|
|
|
}
|
|
|
|
|
2022-03-21 17:46:04 +00:00
|
|
|
var errUnknownIssuer = errors.New("issuer key hash does not match any of the known issuers")
|
2022-03-14 10:21:40 +00:00
|
|
|
|
2022-03-06 15:51:09 +00:00
|
|
|
// CertificateIssuer is an abstraction for the OCSP responder that knows certificates of a specific CA
|
2022-03-06 13:40:46 +00:00
|
|
|
type CertificateIssuer struct {
|
|
|
|
responderCertificate *x509.Certificate
|
|
|
|
responderKey crypto.Signer
|
|
|
|
caCertificate *x509.Certificate
|
2022-03-28 19:16:14 +00:00
|
|
|
certDb CertificateDatabase
|
2022-03-06 13:40:46 +00:00
|
|
|
}
|
|
|
|
|
2022-03-06 15:51:09 +00:00
|
|
|
// NewIssuer is used to construct a new CertificateIssuer instance
|
2022-03-21 17:46:04 +00:00
|
|
|
func NewIssuer(
|
|
|
|
caCertificate, responderCertificate *x509.Certificate,
|
|
|
|
responderKey crypto.Signer,
|
2022-03-28 19:16:14 +00:00
|
|
|
certDb CertificateDatabase,
|
|
|
|
) *CertificateIssuer {
|
2022-03-06 13:40:46 +00:00
|
|
|
return &CertificateIssuer{
|
2022-03-28 19:16:14 +00:00
|
|
|
caCertificate: caCertificate,
|
|
|
|
responderCertificate: responderCertificate,
|
|
|
|
responderKey: responderKey,
|
|
|
|
certDb: certDb,
|
|
|
|
}
|
2022-03-06 13:40:46 +00:00
|
|
|
}
|
|
|
|
|
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.
|
2022-03-06 13:40:46 +00:00
|
|
|
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)
|
|
|
|
}
|
2022-03-21 17:46:04 +00:00
|
|
|
|
2022-03-06 13:40:46 +00:00
|
|
|
issuerHash := h.Sum(nil)
|
|
|
|
|
2022-03-06 15:52:40 +00:00
|
|
|
return bytes.Equal(issuerHash, requestHash), nil
|
2022-03-06 13:40:46 +00:00
|
|
|
}
|
|
|
|
|
2022-03-06 15:51:09 +00:00
|
|
|
// buildUnknownResponse builds the OCSP response template for an unknown certificate
|
2022-03-06 13:40:46 +00:00
|
|
|
func (i *CertificateIssuer) buildUnknownResponse(number *big.Int) ([]byte, error) {
|
|
|
|
template := &ocsp.Response{
|
|
|
|
SerialNumber: number,
|
|
|
|
Status: ocsp.Unknown,
|
|
|
|
}
|
2022-03-21 17:46:04 +00:00
|
|
|
|
2022-03-06 13:40:46 +00:00
|
|
|
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
|
2022-03-06 13:40:46 +00:00
|
|
|
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
|
2022-03-21 17:46:04 +00:00
|
|
|
|
2022-03-28 19:16:14 +00:00
|
|
|
response, err := ocsp.CreateResponse(
|
|
|
|
i.caCertificate,
|
|
|
|
i.responderCertificate,
|
|
|
|
*template,
|
|
|
|
i.responderKey,
|
|
|
|
[]pkix.Extension{{Id: idPKIXOCSPExtendedRevoke, Value: nil, Critical: false}},
|
|
|
|
)
|
2022-03-21 17:46:04 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("could not create final OCSP response: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return response, nil
|
2022-03-06 13:40:46 +00:00
|
|
|
}
|
|
|
|
|
2022-03-06 15:51:09 +00:00
|
|
|
// String returns the name of the CertificateIssuer
|
2022-03-06 13:40:46 +00:00
|
|
|
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
|
2022-03-28 19:16:14 +00:00
|
|
|
func (i *CertificateIssuer) LookupResponse(serial *big.Int) ([]byte, error) {
|
|
|
|
response := i.certDb.LookupResponseTemplate(serial)
|
2022-03-06 13:40:46 +00:00
|
|
|
if response == nil {
|
2022-03-28 19:16:14 +00:00
|
|
|
return i.buildUnknownResponse(serial)
|
2022-03-06 13:40:46 +00:00
|
|
|
}
|
2022-03-21 17:46:04 +00:00
|
|
|
|
2022-03-06 13:40:46 +00:00
|
|
|
return i.buildResponse(response)
|
|
|
|
}
|
|
|
|
|
2022-03-28 19:16:14 +00:00
|
|
|
func (i *CertificateIssuer) UpdateCert(u *CertificateUpdate) {
|
|
|
|
logrus.Infof("got certificate update: %+v", u)
|
|
|
|
i.certDb.UpdateCertificate(u)
|
2022-03-06 15:18:04 +00:00
|
|
|
}
|
|
|
|
|
2022-03-06 15:51:09 +00:00
|
|
|
// OcspSource is an implementation of the Source interface used by the Cloudflare OCSP response handler
|
2022-03-06 13:40:46 +00:00
|
|
|
type OcspSource struct {
|
|
|
|
issuers []*CertificateIssuer
|
|
|
|
}
|
|
|
|
|
2022-03-06 15:51:09 +00:00
|
|
|
// Option to configure OcspSource
|
2022-03-06 13:40:46 +00:00
|
|
|
type Option func(*OcspSource) error
|
|
|
|
|
2022-03-06 15:51:09 +00:00
|
|
|
// NewSource creates a OcspSource instance configured via the passed Options
|
2022-03-06 13:40:46 +00:00
|
|
|
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
|
2022-03-06 13:40:46 +00:00
|
|
|
func WithIssuer(issuer *CertificateIssuer) Option {
|
|
|
|
return func(o *OcspSource) error {
|
|
|
|
o.issuers = append(o.issuers, issuer)
|
2022-03-21 17:46:04 +00:00
|
|
|
|
2022-03-06 13:40:46 +00:00
|
|
|
logrus.Infof("add issuer %s as known issuer", issuer)
|
2022-03-21 17:46:04 +00:00
|
|
|
|
2022-03-06 13:40:46 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-06 15:51:09 +00:00
|
|
|
// Response is dispatching incoming requests to the CertificateIssuer instances of the OcspSource
|
2022-03-06 13:40:46 +00:00
|
|
|
func (o *OcspSource) Response(r *ocsp.Request) ([]byte, http.Header, error) {
|
|
|
|
issuer, err := o.getIssuer(r.IssuerKeyHash, r.HashAlgorithm)
|
|
|
|
if err != nil {
|
2022-03-21 17:46:04 +00:00
|
|
|
if errors.Is(err, errUnknownIssuer) {
|
2022-03-14 10:21:40 +00:00
|
|
|
logrus.Infof("received request for unknown issuer key hash %s", hex.EncodeToString(r.IssuerKeyHash))
|
2022-03-21 17:46:04 +00:00
|
|
|
|
2022-03-14 10:21:40 +00:00
|
|
|
issuer = o.getDefaultIssuer()
|
2022-03-21 17:46:04 +00:00
|
|
|
|
2022-03-14 10:21:40 +00:00
|
|
|
response, err := issuer.buildUnknownResponse(r.SerialNumber)
|
|
|
|
if err != nil {
|
2022-03-21 17:46:04 +00:00
|
|
|
return nil, nil, fmt.Errorf("could not build OCSP response: %w", err)
|
2022-03-14 10:21:40 +00:00
|
|
|
}
|
2022-03-21 17:46:04 +00:00
|
|
|
|
2022-03-14 10:21:40 +00:00
|
|
|
return response, nil, nil
|
|
|
|
}
|
2022-03-21 17:46:04 +00:00
|
|
|
|
2022-03-14 10:21:40 +00:00
|
|
|
return nil, nil, fmt.Errorf("cannot answer request: %w", err)
|
2022-03-06 13:40:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
response, err := issuer.LookupResponse(r.SerialNumber)
|
|
|
|
if err != nil {
|
2022-03-21 17:46:04 +00:00
|
|
|
return nil, nil, fmt.Errorf("could not build OCSP response: %w", err)
|
2022-03-06 13:40:46 +00:00
|
|
|
}
|
2022-03-21 17:46:04 +00:00
|
|
|
|
2022-03-06 13:40:46 +00:00
|
|
|
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
|
2022-03-06 13:40:46 +00:00
|
|
|
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)
|
|
|
|
}
|
2022-03-21 17:46:04 +00:00
|
|
|
|
2022-03-06 13:40:46 +00:00
|
|
|
if matched {
|
|
|
|
return issuer, nil
|
|
|
|
}
|
|
|
|
}
|
2022-03-21 17:46:04 +00:00
|
|
|
|
|
|
|
return nil, errUnknownIssuer
|
2022-03-06 13:40:46 +00:00
|
|
|
}
|
2022-03-06 15:18:04 +00:00
|
|
|
|
2022-03-14 10:21:40 +00:00
|
|
|
func (o *OcspSource) getDefaultIssuer() *CertificateIssuer {
|
|
|
|
return o.issuers[0]
|
|
|
|
}
|