github.com/letsencrypt/boulder@v0.20251208.0/issuance/cert.go (about)

     1  package issuance
     2  
     3  import (
     4  	"bytes"
     5  	"crypto"
     6  	"crypto/ecdsa"
     7  	"crypto/rand"
     8  	"crypto/rsa"
     9  	"crypto/x509"
    10  	"crypto/x509/pkix"
    11  	"encoding/asn1"
    12  	"encoding/json"
    13  	"errors"
    14  	"fmt"
    15  	"math/big"
    16  	"net"
    17  	"sync"
    18  	"time"
    19  
    20  	ct "github.com/google/certificate-transparency-go"
    21  	cttls "github.com/google/certificate-transparency-go/tls"
    22  	ctx509 "github.com/google/certificate-transparency-go/x509"
    23  	"github.com/jmhodges/clock"
    24  	"github.com/zmap/zlint/v3/lint"
    25  
    26  	"github.com/letsencrypt/boulder/cmd"
    27  	"github.com/letsencrypt/boulder/config"
    28  	"github.com/letsencrypt/boulder/linter"
    29  	"github.com/letsencrypt/boulder/precert"
    30  )
    31  
    32  // ProfileConfig describes the certificate issuance constraints for all issuers.
    33  type ProfileConfig struct {
    34  	// OmitCommonName causes the CN field to be excluded from the resulting
    35  	// certificate, regardless of its inclusion in the IssuanceRequest.
    36  	OmitCommonName bool
    37  	// OmitKeyEncipherment causes the keyEncipherment bit to be omitted from the
    38  	// Key Usage field of all certificates (instead of only from ECDSA certs).
    39  	OmitKeyEncipherment bool
    40  	// OmitClientAuth causes the id-kp-clientAuth OID (TLS Client Authentication)
    41  	// to be omitted from the EKU extension.
    42  	OmitClientAuth bool
    43  	// OmitSKID causes the Subject Key Identifier extension to be omitted.
    44  	OmitSKID bool
    45  
    46  	MaxValidityPeriod   config.Duration
    47  	MaxValidityBackdate config.Duration
    48  
    49  	// LintConfig is a path to a zlint config file, which can be used to control
    50  	// the behavior of zlint's "customizable lints".
    51  	LintConfig string
    52  	// IgnoredLints is a list of lint names that we know will fail for this
    53  	// profile, and which we know it is safe to ignore.
    54  	IgnoredLints []string
    55  }
    56  
    57  // Profile is the validated structure created by reading in a ProfileConfig
    58  type Profile struct {
    59  	omitCommonName      bool
    60  	omitKeyEncipherment bool
    61  	omitClientAuth      bool
    62  	omitSKID            bool
    63  
    64  	maxBackdate time.Duration
    65  	maxValidity time.Duration
    66  
    67  	lints lint.Registry
    68  }
    69  
    70  // NewProfile converts the profile config into a usable profile.
    71  func NewProfile(profileConfig ProfileConfig) (*Profile, error) {
    72  	// The Baseline Requirements, Section 7.1.2.7, says that the notBefore time
    73  	// must be "within 48 hours of the time of signing". We can be even stricter.
    74  	if profileConfig.MaxValidityBackdate.Duration >= 24*time.Hour {
    75  		return nil, fmt.Errorf("backdate %q is too large", profileConfig.MaxValidityBackdate.Duration)
    76  	}
    77  
    78  	// Our CP/CPS, Section 7.1, says that our Subscriber Certificates have a
    79  	// validity period of "up to 100 days".
    80  	if profileConfig.MaxValidityPeriod.Duration >= 100*24*time.Hour {
    81  		return nil, fmt.Errorf("validity period %q is too large", profileConfig.MaxValidityPeriod.Duration)
    82  	}
    83  
    84  	lints, err := linter.NewRegistry(profileConfig.IgnoredLints)
    85  	cmd.FailOnError(err, "Failed to create zlint registry")
    86  	if profileConfig.LintConfig != "" {
    87  		lintconfig, err := lint.NewConfigFromFile(profileConfig.LintConfig)
    88  		cmd.FailOnError(err, "Failed to load zlint config file")
    89  		lints.SetConfiguration(lintconfig)
    90  	}
    91  
    92  	sp := &Profile{
    93  		omitCommonName:      profileConfig.OmitCommonName,
    94  		omitKeyEncipherment: profileConfig.OmitKeyEncipherment,
    95  		omitClientAuth:      profileConfig.OmitClientAuth,
    96  		omitSKID:            profileConfig.OmitSKID,
    97  		maxBackdate:         profileConfig.MaxValidityBackdate.Duration,
    98  		maxValidity:         profileConfig.MaxValidityPeriod.Duration,
    99  		lints:               lints,
   100  	}
   101  
   102  	return sp, nil
   103  }
   104  
   105  // GenerateValidity returns a notBefore/notAfter pair bracketing the input time,
   106  // based on the profile's configured backdate and validity.
   107  func (p *Profile) GenerateValidity(now time.Time) (time.Time, time.Time) {
   108  	// Don't use the full maxBackdate, to ensure that the actual backdate remains
   109  	// acceptable throughout the rest of the issuance process.
   110  	backdate := time.Duration(float64(p.maxBackdate.Nanoseconds()) * 0.9)
   111  	notBefore := now.Add(-1 * backdate).Truncate(time.Second)
   112  	// Subtract one second, because certificate validity periods are *inclusive*
   113  	// of their final second (Baseline Requirements, Section 1.6.1).
   114  	notAfter := notBefore.Add(p.maxValidity).Add(-1 * time.Second).Truncate(time.Second)
   115  	return notBefore, notAfter
   116  }
   117  
   118  // requestValid verifies the passed IssuanceRequest against the profile. If the
   119  // request doesn't match the signing profile an error is returned.
   120  func (i *Issuer) requestValid(clk clock.Clock, prof *Profile, req *IssuanceRequest) error {
   121  	switch req.PublicKey.PublicKey.(type) {
   122  	case *rsa.PublicKey, *ecdsa.PublicKey:
   123  	default:
   124  		return errors.New("unsupported public key type")
   125  	}
   126  
   127  	if len(req.precertDER) == 0 && !i.active {
   128  		return errors.New("inactive issuer cannot issue precert")
   129  	}
   130  
   131  	if len(req.SubjectKeyId) != 0 && len(req.SubjectKeyId) != 20 {
   132  		return errors.New("unexpected subject key ID length")
   133  	}
   134  
   135  	if req.IncludeCTPoison && req.sctList != nil {
   136  		return errors.New("cannot include both ct poison and sct list extensions")
   137  	}
   138  
   139  	// The validity period is calculated inclusive of the whole second represented
   140  	// by the notAfter timestamp.
   141  	validity := req.NotAfter.Add(time.Second).Sub(req.NotBefore)
   142  	if validity <= 0 {
   143  		return errors.New("NotAfter must be after NotBefore")
   144  	}
   145  	if validity > prof.maxValidity {
   146  		return fmt.Errorf("validity period is more than the maximum allowed period (%s>%s)", validity, prof.maxValidity)
   147  	}
   148  	backdatedBy := clk.Now().Sub(req.NotBefore)
   149  	if backdatedBy > prof.maxBackdate {
   150  		return fmt.Errorf("NotBefore is backdated more than the maximum allowed period (%s>%s)", backdatedBy, prof.maxBackdate)
   151  	}
   152  	if backdatedBy < 0 {
   153  		return errors.New("NotBefore is in the future")
   154  	}
   155  
   156  	// We use 19 here because a 20-byte serial could produce >20 octets when
   157  	// encoded in ASN.1. That happens when the first byte is >0x80. See
   158  	// https://letsencrypt.org/docs/a-warm-welcome-to-asn1-and-der/#integer-encoding
   159  	if len(req.Serial) > 19 || len(req.Serial) < 9 {
   160  		return errors.New("serial must be between 9 and 19 bytes")
   161  	}
   162  
   163  	return nil
   164  }
   165  
   166  // Baseline Requirements, Section 7.1.6.1: domain-validated
   167  var domainValidatedOID = func() x509.OID {
   168  	x509OID, err := x509.OIDFromInts([]uint64{2, 23, 140, 1, 2, 1})
   169  	if err != nil {
   170  		// This should never happen, as the OID is hardcoded.
   171  		panic(fmt.Errorf("failed to create OID using ints %v: %s", x509OID, err))
   172  	}
   173  	return x509OID
   174  }()
   175  
   176  func (i *Issuer) generateTemplate() *x509.Certificate {
   177  	template := &x509.Certificate{
   178  		SignatureAlgorithm:    i.sigAlg,
   179  		IssuingCertificateURL: []string{i.issuerURL},
   180  		BasicConstraintsValid: true,
   181  		// Baseline Requirements, Section 7.1.6.1: domain-validated
   182  		Policies: []x509.OID{domainValidatedOID},
   183  	}
   184  
   185  	return template
   186  }
   187  
   188  var ctPoisonExt = pkix.Extension{
   189  	// OID for CT poison, RFC 6962 (was never assigned a proper id-pe- name)
   190  	Id:       asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11129, 2, 4, 3},
   191  	Value:    asn1.NullBytes,
   192  	Critical: true,
   193  }
   194  
   195  // OID for SCT list, RFC 6962 (was never assigned a proper id-pe- name)
   196  var sctListOID = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11129, 2, 4, 2}
   197  
   198  func generateSCTListExt(scts []ct.SignedCertificateTimestamp) (pkix.Extension, error) {
   199  	list := ctx509.SignedCertificateTimestampList{}
   200  	for _, sct := range scts {
   201  		sctBytes, err := cttls.Marshal(sct)
   202  		if err != nil {
   203  			return pkix.Extension{}, err
   204  		}
   205  		list.SCTList = append(list.SCTList, ctx509.SerializedSCT{Val: sctBytes})
   206  	}
   207  	listBytes, err := cttls.Marshal(list)
   208  	if err != nil {
   209  		return pkix.Extension{}, err
   210  	}
   211  	extBytes, err := asn1.Marshal(listBytes)
   212  	if err != nil {
   213  		return pkix.Extension{}, err
   214  	}
   215  	return pkix.Extension{
   216  		Id:    sctListOID,
   217  		Value: extBytes,
   218  	}, nil
   219  }
   220  
   221  // MarshalablePublicKey is a wrapper for crypto.PublicKey with a custom JSON
   222  // marshaller that encodes the public key as a DER-encoded SubjectPublicKeyInfo.
   223  type MarshalablePublicKey struct {
   224  	crypto.PublicKey
   225  }
   226  
   227  func (pk MarshalablePublicKey) MarshalJSON() ([]byte, error) {
   228  	keyDER, err := x509.MarshalPKIXPublicKey(pk.PublicKey)
   229  	if err != nil {
   230  		return nil, err
   231  	}
   232  	return json.Marshal(keyDER)
   233  }
   234  
   235  type HexMarshalableBytes []byte
   236  
   237  func (h HexMarshalableBytes) MarshalJSON() ([]byte, error) {
   238  	return json.Marshal(fmt.Sprintf("%x", h))
   239  }
   240  
   241  // IssuanceRequest describes a certificate issuance request
   242  //
   243  // It can be marshaled as JSON for logging purposes, though note that sctList and precertDER
   244  // will be omitted from the marshaled output because they are unexported.
   245  type IssuanceRequest struct {
   246  	// PublicKey is of type MarshalablePublicKey so we can log an IssuanceRequest as a JSON object.
   247  	PublicKey    MarshalablePublicKey
   248  	SubjectKeyId HexMarshalableBytes
   249  
   250  	Serial HexMarshalableBytes
   251  
   252  	NotBefore time.Time
   253  	NotAfter  time.Time
   254  
   255  	CommonName  string
   256  	DNSNames    []string
   257  	IPAddresses []net.IP
   258  
   259  	IncludeCTPoison bool
   260  
   261  	// sctList is a list of SCTs to include in a final certificate.
   262  	// If it is non-empty, PrecertDER must also be non-empty.
   263  	sctList []ct.SignedCertificateTimestamp
   264  	// precertDER is the encoded bytes of the precertificate that a
   265  	// final certificate is expected to correspond to. If it is non-empty,
   266  	// SCTList must also be non-empty.
   267  	precertDER []byte
   268  }
   269  
   270  // An issuanceToken represents an assertion that Issuer.Lint has generated
   271  // a linting certificate for a given input and run the linter over it with no
   272  // errors. The token may be redeemed (at most once) to sign a certificate or
   273  // precertificate with the same Issuer's private key, containing the same
   274  // contents that were linted.
   275  type issuanceToken struct {
   276  	mu       sync.Mutex
   277  	template *x509.Certificate
   278  	pubKey   MarshalablePublicKey
   279  	// A pointer to the issuer that created this token. This token may only
   280  	// be redeemed by the same issuer.
   281  	issuer *Issuer
   282  }
   283  
   284  // Prepare combines the given profile and request with the Issuer's information
   285  // to create a template certificate. It then generates a linting certificate
   286  // from that template and runs the linter over it. If successful, returns both
   287  // the linting certificate (which can be stored) and an issuanceToken. The
   288  // issuanceToken can be used to sign a matching certificate with this Issuer's
   289  // private key.
   290  func (i *Issuer) Prepare(prof *Profile, req *IssuanceRequest) ([]byte, *issuanceToken, error) {
   291  	// check request is valid according to the issuance profile
   292  	err := i.requestValid(i.clk, prof, req)
   293  	if err != nil {
   294  		return nil, nil, err
   295  	}
   296  
   297  	// generate template from the issuer's data
   298  	template := i.generateTemplate()
   299  
   300  	ekus := []x509.ExtKeyUsage{
   301  		x509.ExtKeyUsageServerAuth,
   302  		x509.ExtKeyUsageClientAuth,
   303  	}
   304  	if prof.omitClientAuth {
   305  		ekus = []x509.ExtKeyUsage{
   306  			x509.ExtKeyUsageServerAuth,
   307  		}
   308  	}
   309  	template.ExtKeyUsage = ekus
   310  
   311  	// populate template from the issuance request
   312  	template.NotBefore, template.NotAfter = req.NotBefore, req.NotAfter
   313  	template.SerialNumber = big.NewInt(0).SetBytes(req.Serial)
   314  	if req.CommonName != "" && !prof.omitCommonName {
   315  		template.Subject.CommonName = req.CommonName
   316  	}
   317  	template.DNSNames = req.DNSNames
   318  	template.IPAddresses = req.IPAddresses
   319  
   320  	switch req.PublicKey.PublicKey.(type) {
   321  	case *rsa.PublicKey:
   322  		if prof.omitKeyEncipherment {
   323  			template.KeyUsage = x509.KeyUsageDigitalSignature
   324  		} else {
   325  			template.KeyUsage = x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment
   326  		}
   327  	case *ecdsa.PublicKey:
   328  		template.KeyUsage = x509.KeyUsageDigitalSignature
   329  	}
   330  
   331  	if !prof.omitSKID {
   332  		template.SubjectKeyId = req.SubjectKeyId
   333  	}
   334  
   335  	if req.IncludeCTPoison {
   336  		template.ExtraExtensions = append(template.ExtraExtensions, ctPoisonExt)
   337  	} else if len(req.sctList) > 0 {
   338  		if len(req.precertDER) == 0 {
   339  			return nil, nil, errors.New("inconsistent request contains sctList but no precertDER")
   340  		}
   341  		sctListExt, err := generateSCTListExt(req.sctList)
   342  		if err != nil {
   343  			return nil, nil, err
   344  		}
   345  		template.ExtraExtensions = append(template.ExtraExtensions, sctListExt)
   346  	} else {
   347  		return nil, nil, errors.New("invalid request contains neither sctList nor precertDER")
   348  	}
   349  
   350  	// Pick a CRL shard based on the serial number modulo the number of shards.
   351  	// This gives us random distribution that is nonetheless consistent between
   352  	// precert and cert.
   353  	shardZeroBased := big.NewInt(0).Mod(template.SerialNumber, big.NewInt(int64(i.crlShards)))
   354  	shard := int(shardZeroBased.Int64()) + 1
   355  	template.CRLDistributionPoints = []string{i.crlURL(shard)}
   356  
   357  	// check that the tbsCertificate is properly formed by signing it
   358  	// with a throwaway key and then linting it using zlint
   359  	lintCertBytes, err := i.Linter.Check(template, req.PublicKey.PublicKey, prof.lints)
   360  	if err != nil {
   361  		return nil, nil, fmt.Errorf("tbsCertificate linting failed: %w", err)
   362  	}
   363  
   364  	if len(req.precertDER) > 0 {
   365  		err = precert.Correspond(req.precertDER, lintCertBytes)
   366  		if err != nil {
   367  			return nil, nil, fmt.Errorf("precert does not correspond to linted final cert: %w", err)
   368  		}
   369  	}
   370  
   371  	token := &issuanceToken{sync.Mutex{}, template, req.PublicKey, i}
   372  	return lintCertBytes, token, nil
   373  }
   374  
   375  // Issue performs a real issuance using an issuanceToken resulting from a
   376  // previous call to Prepare(). Call this at most once per token. Calls after
   377  // the first will receive an error.
   378  func (i *Issuer) Issue(token *issuanceToken) ([]byte, error) {
   379  	if token == nil {
   380  		return nil, errors.New("nil issuanceToken")
   381  	}
   382  	token.mu.Lock()
   383  	defer token.mu.Unlock()
   384  	if token.template == nil {
   385  		return nil, errors.New("issuance token already redeemed")
   386  	}
   387  	template := token.template
   388  	token.template = nil
   389  
   390  	if token.issuer != i {
   391  		return nil, errors.New("tried to redeem issuance token with the wrong issuer")
   392  	}
   393  
   394  	return x509.CreateCertificate(rand.Reader, template, i.Cert.Certificate, token.pubKey.PublicKey, i.Signer)
   395  }
   396  
   397  // containsCTPoison returns true if the provided set of extensions includes
   398  // an entry whose OID and value both match the expected values for the CT
   399  // Poison extension.
   400  func containsCTPoison(extensions []pkix.Extension) bool {
   401  	for _, ext := range extensions {
   402  		if ext.Id.Equal(ctPoisonExt.Id) && bytes.Equal(ext.Value, asn1.NullBytes) {
   403  			return true
   404  		}
   405  	}
   406  	return false
   407  }
   408  
   409  // RequestFromPrecert constructs a final certificate IssuanceRequest matching
   410  // the provided precertificate. It returns an error if the precertificate doesn't
   411  // contain the CT poison extension.
   412  func RequestFromPrecert(precert *x509.Certificate, scts []ct.SignedCertificateTimestamp) (*IssuanceRequest, error) {
   413  	if !containsCTPoison(precert.Extensions) {
   414  		return nil, errors.New("provided certificate doesn't contain the CT poison extension")
   415  	}
   416  	return &IssuanceRequest{
   417  		PublicKey:    MarshalablePublicKey{precert.PublicKey},
   418  		SubjectKeyId: precert.SubjectKeyId,
   419  		Serial:       precert.SerialNumber.Bytes(),
   420  		NotBefore:    precert.NotBefore,
   421  		NotAfter:     precert.NotAfter,
   422  		CommonName:   precert.Subject.CommonName,
   423  		DNSNames:     precert.DNSNames,
   424  		IPAddresses:  precert.IPAddresses,
   425  		sctList:      scts,
   426  		precertDER:   precert.Raw,
   427  	}, nil
   428  }