k3s/vendor/github.com/cloudflare/cfssl/ocsp/ocsp.go

216 lines
6.3 KiB
Go
Raw Normal View History

2018-08-06 23:30:17 +00:00
/*
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"
"io/ioutil"
"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 := ioutil.ReadFile(responderFile)
if err != nil {
return nil, err
}
log.Debug("Loading responder key: ", keyFile)
keyBytes, err := ioutil.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.Debug("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)
}
2018-08-09 04:22:01 +00:00
err := req.Certificate.CheckSignatureFrom(s.issuer)
if err != nil {
return nil, cferr.Wrap(cferr.OCSPError, cferr.VerifyFailed, err)
2018-08-06 23:30:17 +00:00
}
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)
}