123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214 |
- /*
- Package ocsp exposes OCSP signing functionality, much like the signer
- package does for certificate signing. It also provies a basic OCSP
- responder stack for serving pre-signed OCSP responses.
- */
- package ocsp
- import (
- "bytes"
- "crypto"
- "crypto/x509"
- "crypto/x509/pkix"
- "os"
- "strconv"
- "strings"
- "time"
- cferr "github.com/cloudflare/cfssl/errors"
- "github.com/cloudflare/cfssl/helpers"
- "github.com/cloudflare/cfssl/log"
- "golang.org/x/crypto/ocsp"
- )
- // revocationReasonCodes is a map between string reason codes
- // to integers as defined in RFC 5280
- var revocationReasonCodes = map[string]int{
- "unspecified": ocsp.Unspecified,
- "keycompromise": ocsp.KeyCompromise,
- "cacompromise": ocsp.CACompromise,
- "affiliationchanged": ocsp.AffiliationChanged,
- "superseded": ocsp.Superseded,
- "cessationofoperation": ocsp.CessationOfOperation,
- "certificatehold": ocsp.CertificateHold,
- "removefromcrl": ocsp.RemoveFromCRL,
- "privilegewithdrawn": ocsp.PrivilegeWithdrawn,
- "aacompromise": ocsp.AACompromise,
- }
- // StatusCode is a map between string statuses sent by cli/api
- // to ocsp int statuses
- var StatusCode = map[string]int{
- "good": ocsp.Good,
- "revoked": ocsp.Revoked,
- "unknown": ocsp.Unknown,
- }
- // SignRequest represents the desired contents of a
- // specific OCSP response.
- type SignRequest struct {
- Certificate *x509.Certificate
- Status string
- Reason int
- RevokedAt time.Time
- Extensions []pkix.Extension
- // IssuerHash is the hashing function used to hash the issuer subject and public key
- // in the OCSP response. Valid values are crypto.SHA1, crypto.SHA256, crypto.SHA384,
- // and crypto.SHA512. If zero, the default is crypto.SHA1.
- IssuerHash crypto.Hash
- // If provided ThisUpdate will override the default usage of time.Now().Truncate(time.Hour)
- ThisUpdate *time.Time
- // If provided NextUpdate will override the default usage of ThisUpdate.Add(signerInterval)
- NextUpdate *time.Time
- }
- // Signer represents a general signer of OCSP responses. It is
- // responsible for populating all fields in the OCSP response that
- // are not reflected in the SignRequest.
- type Signer interface {
- Sign(req SignRequest) ([]byte, error)
- }
- // StandardSigner is the default concrete type of OCSP signer.
- // It represents a single responder (represented by a key and certificate)
- // speaking for a single issuer (certificate). It is assumed that OCSP
- // responses are issued at a regular interval, which is used to compute
- // the nextUpdate value based on the current time.
- type StandardSigner struct {
- issuer *x509.Certificate
- responder *x509.Certificate
- key crypto.Signer
- interval time.Duration
- }
- // ReasonStringToCode tries to convert a reason string to an integer code
- func ReasonStringToCode(reason string) (reasonCode int, err error) {
- // default to 0
- if reason == "" {
- return 0, nil
- }
- reasonCode, present := revocationReasonCodes[strings.ToLower(reason)]
- if !present {
- reasonCode, err = strconv.Atoi(reason)
- if err != nil {
- return
- }
- if reasonCode >= ocsp.AACompromise || reasonCode <= ocsp.Unspecified {
- return 0, cferr.New(cferr.OCSPError, cferr.InvalidStatus)
- }
- }
- return
- }
- // NewSignerFromFile reads the issuer cert, the responder cert and the responder key
- // from PEM files, and takes an interval in seconds
- func NewSignerFromFile(issuerFile, responderFile, keyFile string, interval time.Duration) (Signer, error) {
- log.Debug("Loading issuer cert: ", issuerFile)
- issuerBytes, err := helpers.ReadBytes(issuerFile)
- if err != nil {
- return nil, err
- }
- log.Debug("Loading responder cert: ", responderFile)
- responderBytes, err := os.ReadFile(responderFile)
- if err != nil {
- return nil, err
- }
- log.Debug("Loading responder key: ", keyFile)
- keyBytes, err := os.ReadFile(keyFile)
- if err != nil {
- return nil, cferr.Wrap(cferr.CertificateError, cferr.ReadFailed, err)
- }
- issuerCert, err := helpers.ParseCertificatePEM(issuerBytes)
- if err != nil {
- return nil, err
- }
- responderCert, err := helpers.ParseCertificatePEM(responderBytes)
- if err != nil {
- return nil, err
- }
- key, err := helpers.ParsePrivateKeyPEM(keyBytes)
- if err != nil {
- log.Debugf("Malformed private key %v", err)
- return nil, err
- }
- return NewSigner(issuerCert, responderCert, key, interval)
- }
- // NewSigner simply constructs a new StandardSigner object from the inputs,
- // taking the interval in seconds
- func NewSigner(issuer, responder *x509.Certificate, key crypto.Signer, interval time.Duration) (Signer, error) {
- return &StandardSigner{
- issuer: issuer,
- responder: responder,
- key: key,
- interval: interval,
- }, nil
- }
- // Sign is used with an OCSP signer to request the issuance of
- // an OCSP response.
- func (s StandardSigner) Sign(req SignRequest) ([]byte, error) {
- if req.Certificate == nil {
- return nil, cferr.New(cferr.OCSPError, cferr.ReadFailed)
- }
- // Verify that req.Certificate is issued under s.issuer
- if bytes.Compare(req.Certificate.RawIssuer, s.issuer.RawSubject) != 0 {
- return nil, cferr.New(cferr.OCSPError, cferr.IssuerMismatch)
- }
- err := req.Certificate.CheckSignatureFrom(s.issuer)
- if err != nil {
- return nil, cferr.Wrap(cferr.OCSPError, cferr.VerifyFailed, err)
- }
- var thisUpdate, nextUpdate time.Time
- if req.ThisUpdate != nil {
- thisUpdate = *req.ThisUpdate
- } else {
- // Round thisUpdate times down to the nearest hour
- thisUpdate = time.Now().Truncate(time.Hour)
- }
- if req.NextUpdate != nil {
- nextUpdate = *req.NextUpdate
- } else {
- nextUpdate = thisUpdate.Add(s.interval)
- }
- status, ok := StatusCode[req.Status]
- if !ok {
- return nil, cferr.New(cferr.OCSPError, cferr.InvalidStatus)
- }
- // If the OCSP responder is the same as the issuer, there is no need to
- // include any certificate in the OCSP response, which decreases the byte size
- // of OCSP responses dramatically.
- certificate := s.responder
- if s.issuer == s.responder || bytes.Equal(s.issuer.Raw, s.responder.Raw) {
- certificate = nil
- }
- template := ocsp.Response{
- Status: status,
- SerialNumber: req.Certificate.SerialNumber,
- ThisUpdate: thisUpdate,
- NextUpdate: nextUpdate,
- Certificate: certificate,
- ExtraExtensions: req.Extensions,
- IssuerHash: req.IssuerHash,
- }
- if status == ocsp.Revoked {
- template.RevokedAt = req.RevokedAt
- template.RevocationReason = req.Reason
- }
- return ocsp.CreateResponse(s.issuer, s.responder, template, s.key)
- }
|