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

     1  package issuance
     2  
     3  import (
     4  	"crypto"
     5  	"crypto/ecdsa"
     6  	"crypto/elliptic"
     7  	"crypto/rsa"
     8  	"crypto/x509"
     9  	"encoding/json"
    10  	"errors"
    11  	"fmt"
    12  	"math/big"
    13  	"os"
    14  	"slices"
    15  	"strings"
    16  
    17  	"github.com/jmhodges/clock"
    18  
    19  	"github.com/letsencrypt/boulder/core"
    20  	"github.com/letsencrypt/boulder/linter"
    21  	"github.com/letsencrypt/boulder/privatekey"
    22  	"github.com/letsencrypt/pkcs11key/v4"
    23  )
    24  
    25  // ----- Name ID -----
    26  
    27  // NameID is a statistically-unique small ID which can be computed from
    28  // both CA and end-entity certs to link them together into a validation chain.
    29  // It is computed as a truncated hash over the issuer Subject Name bytes, or
    30  // over the end-entity's Issuer Name bytes, which are required to be equal.
    31  type NameID int64
    32  
    33  // SubjectNameID returns the NameID (a truncated hash over the raw bytes of a
    34  // Distinguished Name) of this issuer certificate's Subject. Useful for storing
    35  // as a lookup key in contexts that don't expect hash collisions.
    36  func SubjectNameID(ic *Certificate) NameID {
    37  	return truncatedHash(ic.RawSubject)
    38  }
    39  
    40  // IssuerNameID returns the IssuerNameID (a truncated hash over the raw bytes
    41  // of the Issuer Distinguished Name) of the given end-entity certificate.
    42  // Useful for performing lookups in contexts that don't expect hash collisions.
    43  func IssuerNameID(ee *x509.Certificate) NameID {
    44  	return truncatedHash(ee.RawIssuer)
    45  }
    46  
    47  // truncatedHash computes a truncated SHA1 hash across arbitrary bytes. Uses
    48  // SHA1 because that is the algorithm most commonly used in OCSP requests.
    49  // PURPOSEFULLY NOT EXPORTED. Exists only to ensure that the implementations of
    50  // SubjectNameID() and IssuerNameID() never diverge. Use those instead.
    51  func truncatedHash(name []byte) NameID {
    52  	h := crypto.SHA1.New()
    53  	h.Write(name)
    54  	s := h.Sum(nil)
    55  	return NameID(big.NewInt(0).SetBytes(s[:7]).Int64())
    56  }
    57  
    58  // ----- Issuer Certificates -----
    59  
    60  // Certificate embeds an *x509.Certificate and represents the added semantics
    61  // that this certificate is a CA certificate.
    62  type Certificate struct {
    63  	*x509.Certificate
    64  	// nameID is stored here simply for the sake of precomputation.
    65  	nameID NameID
    66  }
    67  
    68  // NameID is equivalent to SubjectNameID(ic), but faster because it is
    69  // precomputed.
    70  func (ic *Certificate) NameID() NameID {
    71  	return ic.nameID
    72  }
    73  
    74  // NewCertificate wraps an in-memory cert in an issuance.Certificate, marking it
    75  // as an issuer cert. It may fail if the certificate does not contain the
    76  // attributes expected of an issuer certificate.
    77  func NewCertificate(ic *x509.Certificate) (*Certificate, error) {
    78  	if !ic.IsCA {
    79  		return nil, errors.New("certificate is not a CA certificate")
    80  	}
    81  
    82  	res := Certificate{ic, 0}
    83  	res.nameID = SubjectNameID(&res)
    84  	return &res, nil
    85  }
    86  
    87  func LoadCertificate(path string) (*Certificate, error) {
    88  	cert, err := core.LoadCert(path)
    89  	if err != nil {
    90  		return nil, fmt.Errorf("loading issuer certificate: %w", err)
    91  	}
    92  	return NewCertificate(cert)
    93  }
    94  
    95  // LoadChain takes a list of filenames containing pem-formatted certificates,
    96  // and returns a chain representing all of those certificates in order. It
    97  // ensures that the resulting chain is valid. The final file is expected to be
    98  // a root certificate, which the chain will be verified against, but which will
    99  // not be included in the resulting chain.
   100  func LoadChain(certFiles []string) ([]*Certificate, error) {
   101  	if len(certFiles) < 2 {
   102  		return nil, errors.New(
   103  			"each chain must have at least two certificates: an intermediate and a root")
   104  	}
   105  
   106  	// Pre-load all the certificates to make validation easier.
   107  	certs := make([]*Certificate, len(certFiles))
   108  	var err error
   109  	for i := range len(certFiles) {
   110  		certs[i], err = LoadCertificate(certFiles[i])
   111  		if err != nil {
   112  			return nil, fmt.Errorf("failed to load certificate %q: %w", certFiles[i], err)
   113  		}
   114  	}
   115  
   116  	// Iterate over all certs except for the last, checking that their signature
   117  	// comes from the next cert in the list.
   118  	chain := make([]*Certificate, len(certFiles)-1)
   119  	for i := range len(certs) - 1 {
   120  		err = certs[i].CheckSignatureFrom(certs[i+1].Certificate)
   121  		if err != nil {
   122  			return nil, fmt.Errorf("failed to verify signature from %q to %q (%q to %q): %w",
   123  				certs[i+1].Subject, certs[i].Subject, certFiles[i+1], certFiles[i], err)
   124  		}
   125  		chain[i] = certs[i]
   126  	}
   127  
   128  	// Verify that the last cert is self-signed.
   129  	lastCert := certs[len(certs)-1]
   130  	err = lastCert.CheckSignatureFrom(lastCert.Certificate)
   131  	if err != nil {
   132  		return nil, fmt.Errorf(
   133  			"final cert in chain (%q; %q) must be self-signed (used only for validation): %w",
   134  			lastCert.Subject, certFiles[len(certFiles)-1], err)
   135  	}
   136  
   137  	return chain, nil
   138  }
   139  
   140  // ----- Issuers with Signers -----
   141  
   142  // IssuerConfig describes the constraints on and URLs used by a single issuer.
   143  type IssuerConfig struct {
   144  	// Active determines if the issuer can be used to sign precertificates. All
   145  	// issuers, regardless of this field, can be used to sign final certificates
   146  	// (for which an issuance token is presented) and CRLs.
   147  	// All Active issuers of a given key type (RSA or ECDSA) are part of a pool
   148  	// and each precertificate will be issued randomly from a selected pool.
   149  	// The selection of which pool depends on the precertificate's key algorithm.
   150  	Active bool
   151  
   152  	// Profiles is the list of profiles for which this issuer is willing to issue.
   153  	// The names listed here must match the names of configured profiles (see
   154  	// cmd/ca/main.go's Config.Issuance.CertProfiles and issuance/cert.go's
   155  	// ProfileConfig). Can be empty if the issuer is not Active.
   156  	Profiles []string `validate:"required_if=Active true,dive,alphanum,min=1,max=32"`
   157  
   158  	IssuerURL  string `validate:"required,url"`
   159  	CRLURLBase string `validate:"required,url,startswith=http://,endswith=/"`
   160  
   161  	// Number of CRL shards. Must be positive, but can be 1 for no sharding.
   162  	CRLShards int `validate:"required,min=1"`
   163  
   164  	Location IssuerLoc
   165  }
   166  
   167  // IssuerLoc describes the on-disk location and parameters that an issuer
   168  // should use to retrieve its certificate and private key.
   169  // Only one of File, ConfigFile, or PKCS11 should be set.
   170  type IssuerLoc struct {
   171  	// A file from which a private key will be read and parsed.
   172  	File string `validate:"required_without_all=ConfigFile PKCS11"`
   173  	// A file from which a pkcs11key.Config will be read and parsed, if File is not set.
   174  	ConfigFile string `validate:"required_without_all=PKCS11 File"`
   175  	// An in-memory pkcs11key.Config, which will be used if ConfigFile is not set.
   176  	PKCS11 *pkcs11key.Config `validate:"required_without_all=ConfigFile File"`
   177  	// A file from which a certificate will be read and parsed.
   178  	CertFile string `validate:"required"`
   179  	// Number of sessions to open with the HSM. For maximum performance,
   180  	// this should be equal to the number of cores in the HSM. Defaults to 1.
   181  	NumSessions int
   182  }
   183  
   184  // Issuer is capable of issuing new certificates.
   185  type Issuer struct {
   186  	// TODO(#7159): make Cert, Signer, and Linter private when all signing ops
   187  	// are handled through this package (e.g. the CA doesn't need direct access
   188  	// while signing CRLs anymore).
   189  	Cert   *Certificate
   190  	Signer crypto.Signer
   191  	Linter *linter.Linter
   192  
   193  	keyAlg x509.PublicKeyAlgorithm
   194  	sigAlg x509.SignatureAlgorithm
   195  	active bool
   196  
   197  	// Used to set the Authority Information Access caIssuers URL in issued
   198  	// certificates.
   199  	issuerURL string
   200  	// Used to set the Issuing Distribution Point extension in issued CRLs
   201  	// and the CRL Distribution Point extension in issued certs.
   202  	crlURLBase string
   203  
   204  	crlShards int
   205  
   206  	// profiles is a list of the names of profiles that this issuer is willing to
   207  	// issue for.
   208  	profiles []string
   209  
   210  	clk clock.Clock
   211  }
   212  
   213  // newIssuer constructs a new Issuer from the in-memory certificate and signer.
   214  // It exists as a helper for LoadIssuer to make testing simpler.
   215  func newIssuer(config IssuerConfig, cert *Certificate, signer crypto.Signer, clk clock.Clock) (*Issuer, error) {
   216  	var keyAlg x509.PublicKeyAlgorithm
   217  	var sigAlg x509.SignatureAlgorithm
   218  	switch k := cert.PublicKey.(type) {
   219  	case *rsa.PublicKey:
   220  		keyAlg = x509.RSA
   221  		sigAlg = x509.SHA256WithRSA
   222  	case *ecdsa.PublicKey:
   223  		keyAlg = x509.ECDSA
   224  		switch k.Curve {
   225  		case elliptic.P256():
   226  			sigAlg = x509.ECDSAWithSHA256
   227  		case elliptic.P384():
   228  			sigAlg = x509.ECDSAWithSHA384
   229  		default:
   230  			return nil, fmt.Errorf("unsupported ECDSA curve: %q", k.Curve.Params().Name)
   231  		}
   232  	default:
   233  		return nil, errors.New("unsupported issuer key type")
   234  	}
   235  
   236  	if config.IssuerURL == "" {
   237  		return nil, errors.New("issuer URL is required")
   238  	}
   239  	if config.CRLURLBase == "" {
   240  		return nil, errors.New("crlURLBase is required")
   241  	}
   242  	if !strings.HasPrefix(config.CRLURLBase, "http://") {
   243  		return nil, fmt.Errorf("crlURLBase must use HTTP scheme, got %q", config.CRLURLBase)
   244  	}
   245  	if !strings.HasSuffix(config.CRLURLBase, "/") {
   246  		return nil, fmt.Errorf("crlURLBase must end with exactly one forward slash, got %q", config.CRLURLBase)
   247  	}
   248  	if config.CRLShards <= 0 {
   249  		return nil, errors.New("number of CRL shards is required")
   250  	}
   251  
   252  	// We require that all of our issuers be capable of both issuing certs and
   253  	// providing revocation information.
   254  	if cert.KeyUsage&x509.KeyUsageCertSign == 0 {
   255  		return nil, errors.New("end-entity signing cert does not have keyUsage certSign")
   256  	}
   257  	if cert.KeyUsage&x509.KeyUsageCRLSign == 0 {
   258  		return nil, errors.New("end-entity signing cert does not have keyUsage crlSign")
   259  	}
   260  	if cert.KeyUsage&x509.KeyUsageDigitalSignature == 0 {
   261  		return nil, errors.New("end-entity signing cert does not have keyUsage digitalSignature")
   262  	}
   263  
   264  	// If the issuer is active, it must have at least one profile configured.
   265  	if config.Active && len(config.Profiles) == 0 {
   266  		return nil, errors.New("active issuers must have at least one profile")
   267  	}
   268  
   269  	lintSigner, err := linter.New(cert.Certificate, signer)
   270  	if err != nil {
   271  		return nil, fmt.Errorf("creating fake lint signer: %w", err)
   272  	}
   273  
   274  	i := &Issuer{
   275  		Cert:       cert,
   276  		Signer:     signer,
   277  		Linter:     lintSigner,
   278  		keyAlg:     keyAlg,
   279  		sigAlg:     sigAlg,
   280  		active:     config.Active,
   281  		issuerURL:  config.IssuerURL,
   282  		crlURLBase: config.CRLURLBase,
   283  		crlShards:  config.CRLShards,
   284  		profiles:   config.Profiles,
   285  		clk:        clk,
   286  	}
   287  	return i, nil
   288  }
   289  
   290  // KeyType returns either x509.RSA or x509.ECDSA, depending on whether the
   291  // issuer has an RSA or ECDSA keypair. This is useful for determining which
   292  // issuance requests should be routed to this issuer.
   293  func (i *Issuer) KeyType() x509.PublicKeyAlgorithm {
   294  	return i.keyAlg
   295  }
   296  
   297  // IsActive is true if the issuer is willing to issue precertificates, and false
   298  // if the issuer is only willing to issue final certificates and CRLs.
   299  func (i *Issuer) IsActive() bool {
   300  	return i.active
   301  }
   302  
   303  // Name provides the Common Name specified in the issuer's certificate.
   304  func (i *Issuer) Name() string {
   305  	return i.Cert.Subject.CommonName
   306  }
   307  
   308  // NameID provides the NameID of the issuer's certificate.
   309  func (i *Issuer) NameID() NameID {
   310  	return i.Cert.NameID()
   311  }
   312  
   313  // Profiles returns the set of profiles that this issuer can issue for.
   314  func (i *Issuer) Profiles() []string {
   315  	return slices.Clone(i.profiles)
   316  }
   317  
   318  // LoadIssuer constructs a new Issuer, loading its certificate from disk and its
   319  // private key material from the indicated location. It also verifies that the
   320  // issuer metadata (such as AIA URLs) is well-formed.
   321  func LoadIssuer(config IssuerConfig, clk clock.Clock) (*Issuer, error) {
   322  	issuerCert, err := LoadCertificate(config.Location.CertFile)
   323  	if err != nil {
   324  		return nil, err
   325  	}
   326  
   327  	signer, err := loadSigner(config.Location, issuerCert.PublicKey)
   328  	if err != nil {
   329  		return nil, err
   330  	}
   331  
   332  	if !core.KeyDigestEquals(signer.Public(), issuerCert.PublicKey) {
   333  		return nil, fmt.Errorf("issuer key did not match issuer cert %q", config.Location.CertFile)
   334  	}
   335  
   336  	return newIssuer(config, issuerCert, signer, clk)
   337  }
   338  
   339  func loadSigner(location IssuerLoc, pubkey crypto.PublicKey) (crypto.Signer, error) {
   340  	if location.File == "" && location.ConfigFile == "" && location.PKCS11 == nil {
   341  		return nil, errors.New("must supply File, ConfigFile, or PKCS11")
   342  	}
   343  
   344  	if location.File != "" {
   345  		signer, _, err := privatekey.Load(location.File)
   346  		if err != nil {
   347  			return nil, err
   348  		}
   349  		return signer, nil
   350  	}
   351  
   352  	var pkcs11Config *pkcs11key.Config
   353  	if location.ConfigFile != "" {
   354  		contents, err := os.ReadFile(location.ConfigFile)
   355  		if err != nil {
   356  			return nil, err
   357  		}
   358  		pkcs11Config = new(pkcs11key.Config)
   359  		err = json.Unmarshal(contents, pkcs11Config)
   360  		if err != nil {
   361  			return nil, err
   362  		}
   363  	} else {
   364  		pkcs11Config = location.PKCS11
   365  	}
   366  
   367  	if pkcs11Config.Module == "" ||
   368  		pkcs11Config.TokenLabel == "" ||
   369  		pkcs11Config.PIN == "" {
   370  		return nil, fmt.Errorf("missing a field in pkcs11Config %#v", pkcs11Config)
   371  	}
   372  
   373  	numSessions := location.NumSessions
   374  	if numSessions <= 0 {
   375  		numSessions = 1
   376  	}
   377  
   378  	return pkcs11key.NewPool(numSessions, pkcs11Config.Module,
   379  		pkcs11Config.TokenLabel, pkcs11Config.PIN, pubkey)
   380  }