package ca import ( "bytes" "context" "crypto" "crypto/rand" "crypto/sha256" "crypto/x509" "crypto/x509/pkix" "encoding/asn1" "encoding/hex" "errors" "fmt" "math/big" mrand "math/rand/v2" "time" ct "github.com/google/certificate-transparency-go" cttls "github.com/google/certificate-transparency-go/tls" "github.com/jmhodges/clock" "github.com/miekg/pkcs11" "github.com/prometheus/client_golang/prometheus" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/trace" "golang.org/x/crypto/cryptobyte" cryptobyte_asn1 "golang.org/x/crypto/cryptobyte/asn1" "golang.org/x/crypto/ocsp" "google.golang.org/protobuf/types/known/timestamppb" capb "github.com/letsencrypt/boulder/ca/proto" "github.com/letsencrypt/boulder/core" csrlib "github.com/letsencrypt/boulder/csr" berrors "github.com/letsencrypt/boulder/errors" "github.com/letsencrypt/boulder/goodkey" "github.com/letsencrypt/boulder/identifier" "github.com/letsencrypt/boulder/issuance" "github.com/letsencrypt/boulder/linter" blog "github.com/letsencrypt/boulder/log" rapb "github.com/letsencrypt/boulder/ra/proto" sapb "github.com/letsencrypt/boulder/sa/proto" ) type certificateType string const ( precertType = certificateType("precertificate") certType = certificateType("certificate") ) // issuanceEvent is logged before and after issuance of precertificates and certificates. // The `omitempty` fields are not always present. // CSR, Precertificate, and Certificate are hex-encoded DER bytes to make it easier to // ad-hoc search for sequences or OIDs in logs. Other data, like public key within CSR, // is logged as base64 because it doesn't have interesting DER structure. type issuanceEvent struct { CSR string `json:",omitempty"` IssuanceRequest *issuance.IssuanceRequest Issuer string OrderID int64 Profile string Requester int64 Result struct { Precertificate string `json:",omitempty"` Certificate string `json:",omitempty"` } } // Two maps of keys to Issuers. Lookup by PublicKeyAlgorithm is useful for // determining the set of issuers which can sign a given (pre)cert, based on its // PublicKeyAlgorithm. Lookup by NameID is useful for looking up a specific // issuer based on the issuer of a given (pre)certificate. type issuerMaps struct { byAlg map[x509.PublicKeyAlgorithm][]*issuance.Issuer byNameID map[issuance.NameID]*issuance.Issuer } type certProfileWithID struct { // name is a human readable name used to refer to the certificate profile. name string profile *issuance.Profile } // caMetrics holds various metrics which are shared between caImpl, ocspImpl, // and crlImpl. type caMetrics struct { signatureCount *prometheus.CounterVec signErrorCount *prometheus.CounterVec lintErrorCount prometheus.Counter certificates *prometheus.CounterVec } func NewCAMetrics(stats prometheus.Registerer) *caMetrics { signatureCount := prometheus.NewCounterVec( prometheus.CounterOpts{ Name: "signatures", Help: "Number of signatures", }, []string{"purpose", "issuer"}) stats.MustRegister(signatureCount) signErrorCount := prometheus.NewCounterVec(prometheus.CounterOpts{ Name: "signature_errors", Help: "A counter of signature errors labelled by error type", }, []string{"type"}) stats.MustRegister(signErrorCount) lintErrorCount := prometheus.NewCounter( prometheus.CounterOpts{ Name: "lint_errors", Help: "Number of issuances that were halted by linting errors", }) stats.MustRegister(lintErrorCount) certificates := prometheus.NewCounterVec( prometheus.CounterOpts{ Name: "certificates", Help: "Number of certificates issued", }, []string{"profile"}) stats.MustRegister(certificates) return &caMetrics{signatureCount, signErrorCount, lintErrorCount, certificates} } func (m *caMetrics) noteSignError(err error) { var pkcs11Error pkcs11.Error if errors.As(err, &pkcs11Error) { m.signErrorCount.WithLabelValues("HSM").Inc() } } // certificateAuthorityImpl represents a CA that signs certificates. // It can sign OCSP responses as well, but only via delegation to an ocspImpl. type certificateAuthorityImpl struct { capb.UnsafeCertificateAuthorityServer sa sapb.StorageAuthorityCertificateClient sctClient rapb.SCTProviderClient pa core.PolicyAuthority issuers issuerMaps certProfiles map[string]*certProfileWithID // The prefix is prepended to the serial number. prefix byte maxNames int keyPolicy goodkey.KeyPolicy clk clock.Clock log blog.Logger metrics *caMetrics tracer trace.Tracer } var _ capb.CertificateAuthorityServer = (*certificateAuthorityImpl)(nil) // makeIssuerMaps processes a list of issuers into a set of maps for easy // lookup either by key algorithm (useful for picking an issuer for a precert) // or by unique ID (useful for final certs, OCSP, and CRLs). If two issuers with // the same unique ID are encountered, an error is returned. func makeIssuerMaps(issuers []*issuance.Issuer) (issuerMaps, error) { issuersByAlg := make(map[x509.PublicKeyAlgorithm][]*issuance.Issuer, 2) issuersByNameID := make(map[issuance.NameID]*issuance.Issuer, len(issuers)) for _, issuer := range issuers { if _, found := issuersByNameID[issuer.NameID()]; found { return issuerMaps{}, fmt.Errorf("two issuers with same NameID %d (%s) configured", issuer.NameID(), issuer.Name()) } issuersByNameID[issuer.NameID()] = issuer if issuer.IsActive() { issuersByAlg[issuer.KeyType()] = append(issuersByAlg[issuer.KeyType()], issuer) } } if i, ok := issuersByAlg[x509.ECDSA]; !ok || len(i) == 0 { return issuerMaps{}, errors.New("no ECDSA issuers configured") } if i, ok := issuersByAlg[x509.RSA]; !ok || len(i) == 0 { return issuerMaps{}, errors.New("no RSA issuers configured") } return issuerMaps{issuersByAlg, issuersByNameID}, nil } // makeCertificateProfilesMap processes a set of named certificate issuance // profile configs into a map from name to profile. func makeCertificateProfilesMap(profiles map[string]*issuance.ProfileConfig) (map[string]*certProfileWithID, error) { if len(profiles) <= 0 { return nil, fmt.Errorf("must pass at least one certificate profile") } profilesByName := make(map[string]*certProfileWithID, len(profiles)) for name, profileConfig := range profiles { profile, err := issuance.NewProfile(profileConfig) if err != nil { return nil, err } profilesByName[name] = &certProfileWithID{ name: name, profile: profile, } } return profilesByName, nil } // NewCertificateAuthorityImpl creates a CA instance that can sign certificates // from any number of issuance.Issuers according to their profiles, and can sign // OCSP (via delegation to an ocspImpl and its issuers). func NewCertificateAuthorityImpl( sa sapb.StorageAuthorityCertificateClient, sctService rapb.SCTProviderClient, pa core.PolicyAuthority, boulderIssuers []*issuance.Issuer, certificateProfiles map[string]*issuance.ProfileConfig, serialPrefix byte, maxNames int, keyPolicy goodkey.KeyPolicy, logger blog.Logger, metrics *caMetrics, clk clock.Clock, ) (*certificateAuthorityImpl, error) { var ca *certificateAuthorityImpl var err error if serialPrefix < 0x01 || serialPrefix > 0x7f { err = errors.New("serial prefix must be between 0x01 (1) and 0x7f (127)") return nil, err } if len(boulderIssuers) == 0 { return nil, errors.New("must have at least one issuer") } certProfiles, err := makeCertificateProfilesMap(certificateProfiles) if err != nil { return nil, err } issuers, err := makeIssuerMaps(boulderIssuers) if err != nil { return nil, err } ca = &certificateAuthorityImpl{ sa: sa, sctClient: sctService, pa: pa, issuers: issuers, certProfiles: certProfiles, prefix: serialPrefix, maxNames: maxNames, keyPolicy: keyPolicy, log: logger, metrics: metrics, tracer: otel.GetTracerProvider().Tracer("github.com/letsencrypt/boulder/ca"), clk: clk, } return ca, nil } var ocspStatusToCode = map[string]int{ "good": ocsp.Good, "revoked": ocsp.Revoked, "unknown": ocsp.Unknown, } // issuePrecertificate is the first step in the [issuance cycle]. It allocates and stores a serial number, // selects a certificate profile, generates and stores a linting certificate, sets the serial's status to // "wait", signs and stores a precertificate, updates the serial's status to "good", then returns the // precertificate. // // Subsequent final issuance based on this precertificate must happen at most once, and must use the same // certificate profile. // // Returns precertificate DER. // // [issuance cycle]: https://github.com/letsencrypt/boulder/blob/main/docs/ISSUANCE-CYCLE.md func (ca *certificateAuthorityImpl) issuePrecertificate(ctx context.Context, certProfile *certProfileWithID, issueReq *capb.IssueCertificateRequest) ([]byte, error) { serialBigInt, err := ca.generateSerialNumber() if err != nil { return nil, err } notBefore, notAfter := certProfile.profile.GenerateValidity(ca.clk.Now()) serialHex := core.SerialToString(serialBigInt) regID := issueReq.RegistrationID _, err = ca.sa.AddSerial(ctx, &sapb.AddSerialRequest{ Serial: serialHex, RegID: regID, Created: timestamppb.New(ca.clk.Now()), Expires: timestamppb.New(notAfter), }) if err != nil { return nil, err } precertDER, _, err := ca.issuePrecertificateInner(ctx, issueReq, certProfile, serialBigInt, notBefore, notAfter) if err != nil { return nil, err } _, err = ca.sa.SetCertificateStatusReady(ctx, &sapb.Serial{Serial: serialHex}) if err != nil { return nil, err } return precertDER, nil } func (ca *certificateAuthorityImpl) IssueCertificate(ctx context.Context, issueReq *capb.IssueCertificateRequest) (*capb.IssueCertificateResponse, error) { if core.IsAnyNilOrZero(issueReq, issueReq.Csr, issueReq.RegistrationID, issueReq.OrderID) { return nil, berrors.InternalServerError("Incomplete issue certificate request") } if ca.sctClient == nil { return nil, errors.New("IssueCertificate called with a nil SCT service") } // All issuance requests must come with a profile name, and the RA handles selecting the default. certProfile, ok := ca.certProfiles[issueReq.CertProfileName] if !ok { return nil, fmt.Errorf("the CA is incapable of using a profile named %s", issueReq.CertProfileName) } precertDER, err := ca.issuePrecertificate(ctx, certProfile, issueReq) if err != nil { return nil, err } scts, err := ca.sctClient.GetSCTs(ctx, &rapb.SCTRequest{PrecertDER: precertDER}) if err != nil { return nil, err } certDER, err := ca.issueCertificateForPrecertificate(ctx, certProfile, precertDER, scts.SctDER, issueReq.RegistrationID, issueReq.OrderID) if err != nil { return nil, err } return &capb.IssueCertificateResponse{DER: certDER}, nil } // issueCertificateForPrecertificate is final step in the [issuance cycle]. // // Given a precertificate and a set of SCTs for that precertificate, it generates // a linting final certificate, then signs a final certificate using a real issuer. // The poison extension is removed from the precertificate and a // SCT list extension is inserted in its place. Except for this and the // signature the final certificate exactly matches the precertificate. // // It's critical not to sign two different final certificates for the same // precertificate. This can happen, for instance, if the caller provides a // different set of SCTs on subsequent calls to issueCertificateForPrecertificate. // We rely on the RA not to call issueCertificateForPrecertificate twice for the // same serial. This is accomplished by the fact that // issueCertificateForPrecertificate is only ever called once per call to `IssueCertificate`. // If there is any error, the whole certificate issuance attempt fails and any subsequent // issuance will use a different serial number. // // We also check that the provided serial number does not already exist as a // final certificate, but this is just a belt-and-suspenders measure, since // there could be race conditions where two goroutines are issuing for the same // serial number at the same time. // // Returns the final certificate's bytes as DER. // // [issuance cycle]: https://github.com/letsencrypt/boulder/blob/main/docs/ISSUANCE-CYCLE.md func (ca *certificateAuthorityImpl) issueCertificateForPrecertificate(ctx context.Context, certProfile *certProfileWithID, precertDER []byte, sctBytes [][]byte, regID int64, orderID int64, ) ([]byte, error) { precert, err := x509.ParseCertificate(precertDER) if err != nil { return nil, err } serialHex := core.SerialToString(precert.SerialNumber) if _, err = ca.sa.GetCertificate(ctx, &sapb.Serial{Serial: serialHex}); err == nil { err = berrors.InternalServerError("issuance of duplicate final certificate requested: %s", serialHex) ca.log.AuditErr(err.Error()) return nil, err } else if !errors.Is(err, berrors.NotFound) { return nil, fmt.Errorf("error checking for duplicate issuance of %s: %s", serialHex, err) } var scts []ct.SignedCertificateTimestamp for _, singleSCTBytes := range sctBytes { var sct ct.SignedCertificateTimestamp _, err = cttls.Unmarshal(singleSCTBytes, &sct) if err != nil { return nil, err } scts = append(scts, sct) } issuer, ok := ca.issuers.byNameID[issuance.IssuerNameID(precert)] if !ok { return nil, berrors.InternalServerError("no issuer found for Issuer Name %s", precert.Issuer) } issuanceReq, err := issuance.RequestFromPrecert(precert, scts) if err != nil { return nil, err } lintCertBytes, issuanceToken, err := issuer.Prepare(certProfile.profile, issuanceReq) if err != nil { ca.log.AuditErrf("Preparing cert failed: serial=[%s] err=[%v]", serialHex, err) return nil, berrors.InternalServerError("failed to prepare certificate signing: %s", err) } logEvent := issuanceEvent{ IssuanceRequest: issuanceReq, Issuer: issuer.Name(), OrderID: orderID, Profile: certProfile.name, Requester: regID, } ca.log.AuditObject("Signing cert", logEvent) var ipStrings []string for _, ip := range issuanceReq.IPAddresses { ipStrings = append(ipStrings, ip.String()) } _, span := ca.tracer.Start(ctx, "signing cert", trace.WithAttributes( attribute.String("serial", serialHex), attribute.String("issuer", issuer.Name()), attribute.String("certProfileName", certProfile.name), attribute.StringSlice("names", issuanceReq.DNSNames), attribute.StringSlice("ipAddresses", ipStrings), )) certDER, err := issuer.Issue(issuanceToken) if err != nil { ca.metrics.noteSignError(err) ca.log.AuditErrf("Signing cert failed: serial=[%s] err=[%v]", serialHex, err) span.SetStatus(codes.Error, err.Error()) span.End() return nil, berrors.InternalServerError("failed to sign certificate: %s", err) } span.End() err = tbsCertIsDeterministic(lintCertBytes, certDER) if err != nil { return nil, err } ca.metrics.signatureCount.With(prometheus.Labels{"purpose": string(certType), "issuer": issuer.Name()}).Inc() ca.metrics.certificates.With(prometheus.Labels{"profile": certProfile.name}).Inc() logEvent.Result.Certificate = hex.EncodeToString(certDER) ca.log.AuditObject("Signing cert success", logEvent) _, err = ca.sa.AddCertificate(ctx, &sapb.AddCertificateRequest{ Der: certDER, RegID: regID, Issued: timestamppb.New(ca.clk.Now()), }) if err != nil { ca.log.AuditErrf("Failed RPC to store at SA: serial=[%s] err=[%v]", serialHex, hex.EncodeToString(certDER)) return nil, err } return certDER, nil } // generateSerialNumber produces a big.Int which has more than 64 bits of // entropy and has the CA's configured one-byte prefix. func (ca *certificateAuthorityImpl) generateSerialNumber() (*big.Int, error) { // We want 136 bits of random number, plus an 8-bit instance id prefix. const randBits = 136 serialBytes := make([]byte, randBits/8+1) serialBytes[0] = ca.prefix _, err := rand.Read(serialBytes[1:]) if err != nil { err = berrors.InternalServerError("failed to generate serial: %s", err) ca.log.AuditErrf("Serial randomness failed, err=[%v]", err) return nil, err } serialBigInt := big.NewInt(0) serialBigInt = serialBigInt.SetBytes(serialBytes) return serialBigInt, nil } // generateSKID computes the Subject Key Identifier using one of the methods in // RFC 7093 Section 2 Additional Methods for Generating Key Identifiers: // The keyIdentifier [may be] composed of the leftmost 160-bits of the // SHA-256 hash of the value of the BIT STRING subjectPublicKey // (excluding the tag, length, and number of unused bits). func generateSKID(pk crypto.PublicKey) ([]byte, error) { pkBytes, err := x509.MarshalPKIXPublicKey(pk) if err != nil { return nil, err } var pkixPublicKey struct { Algo pkix.AlgorithmIdentifier BitString asn1.BitString } if _, err := asn1.Unmarshal(pkBytes, &pkixPublicKey); err != nil { return nil, err } skid := sha256.Sum256(pkixPublicKey.BitString.Bytes) return skid[0:20:20], nil } func (ca *certificateAuthorityImpl) issuePrecertificateInner(ctx context.Context, issueReq *capb.IssueCertificateRequest, certProfile *certProfileWithID, serialBigInt *big.Int, notBefore time.Time, notAfter time.Time) ([]byte, *certProfileWithID, error) { csr, err := x509.ParseCertificateRequest(issueReq.Csr) if err != nil { return nil, nil, err } err = csrlib.VerifyCSR(ctx, csr, ca.maxNames, &ca.keyPolicy, ca.pa) if err != nil { ca.log.AuditErr(err.Error()) // VerifyCSR returns berror instances that can be passed through as-is // without wrapping. return nil, nil, err } // Select which pool of issuers to use, based on the to-be-issued cert's key // type. alg := csr.PublicKeyAlgorithm // Select a random issuer from among the active issuers of this key type. issuerPool, ok := ca.issuers.byAlg[alg] if !ok || len(issuerPool) == 0 { return nil, nil, berrors.InternalServerError("no issuers found for public key algorithm %s", csr.PublicKeyAlgorithm) } issuer := issuerPool[mrand.IntN(len(issuerPool))] if issuer.Cert.NotAfter.Before(notAfter) { err = berrors.InternalServerError("cannot issue a certificate that expires after the issuer certificate") ca.log.AuditErr(err.Error()) return nil, nil, err } subjectKeyId, err := generateSKID(csr.PublicKey) if err != nil { return nil, nil, fmt.Errorf("computing subject key ID: %w", err) } serialHex := core.SerialToString(serialBigInt) dnsNames, ipAddresses, err := identifier.FromCSR(csr).ToValues() if err != nil { return nil, nil, err } req := &issuance.IssuanceRequest{ PublicKey: issuance.MarshalablePublicKey{PublicKey: csr.PublicKey}, SubjectKeyId: subjectKeyId, Serial: serialBigInt.Bytes(), DNSNames: dnsNames, IPAddresses: ipAddresses, CommonName: csrlib.CNFromCSR(csr), IncludeCTPoison: true, NotBefore: notBefore, NotAfter: notAfter, } lintCertBytes, issuanceToken, err := issuer.Prepare(certProfile.profile, req) if err != nil { ca.log.AuditErrf("Preparing precert failed: serial=[%s] err=[%v]", serialHex, err) if errors.Is(err, linter.ErrLinting) { ca.metrics.lintErrorCount.Inc() } return nil, nil, berrors.InternalServerError("failed to prepare precertificate signing: %s", err) } // Note: we write the linting certificate bytes to this table, rather than the precertificate // (which we audit log but do not put in the database). This is to ensure that even if there is // an error immediately after signing the precertificate, we have a record in the DB of what we // intended to sign, and can do revocations based on that. See #6807. // The name of the SA method ("AddPrecertificate") is a historical artifact. _, err = ca.sa.AddPrecertificate(context.Background(), &sapb.AddCertificateRequest{ Der: lintCertBytes, RegID: issueReq.RegistrationID, Issued: timestamppb.New(ca.clk.Now()), IssuerNameID: int64(issuer.NameID()), OcspNotReady: true, }) if err != nil { return nil, nil, err } logEvent := issuanceEvent{ CSR: hex.EncodeToString(csr.Raw), IssuanceRequest: req, Issuer: issuer.Name(), Profile: certProfile.name, Requester: issueReq.RegistrationID, OrderID: issueReq.OrderID, } ca.log.AuditObject("Signing precert", logEvent) var ipStrings []string for _, ip := range csr.IPAddresses { ipStrings = append(ipStrings, ip.String()) } _, span := ca.tracer.Start(ctx, "signing precert", trace.WithAttributes( attribute.String("serial", serialHex), attribute.String("issuer", issuer.Name()), attribute.String("certProfileName", certProfile.name), attribute.StringSlice("names", csr.DNSNames), attribute.StringSlice("ipAddresses", ipStrings), )) certDER, err := issuer.Issue(issuanceToken) if err != nil { ca.metrics.noteSignError(err) ca.log.AuditErrf("Signing precert failed: serial=[%s] err=[%v]", serialHex, err) span.SetStatus(codes.Error, err.Error()) span.End() return nil, nil, berrors.InternalServerError("failed to sign precertificate: %s", err) } span.End() err = tbsCertIsDeterministic(lintCertBytes, certDER) if err != nil { return nil, nil, err } ca.metrics.signatureCount.With(prometheus.Labels{"purpose": string(precertType), "issuer": issuer.Name()}).Inc() logEvent.Result.Precertificate = hex.EncodeToString(certDER) // The CSR is big and not that informative, so don't log it a second time. logEvent.CSR = "" ca.log.AuditObject("Signing precert success", logEvent) return certDER, &certProfileWithID{certProfile.name, nil}, nil } // verifyTBSCertIsDeterministic verifies that x509.CreateCertificate signing // operation is deterministic and produced identical DER bytes between the given // lint certificate and leaf certificate. If the DER byte equality check fails // it's mississuance, but it's better to know about the problem sooner than // later. The caller is responsible for passing the appropriate valid // certificate bytes in the correct position. func tbsCertIsDeterministic(lintCertBytes []byte, leafCertBytes []byte) error { if core.IsAnyNilOrZero(lintCertBytes, leafCertBytes) { return fmt.Errorf("lintCertBytes of leafCertBytes were nil") } // extractTBSCertBytes is a partial copy of //crypto/x509/parser.go to // extract the RawTBSCertificate field from given DER bytes. It the // RawTBSCertificate field bytes or an error if the given bytes cannot be // parsed. This is far more performant than parsing the entire *Certificate // structure with x509.ParseCertificate(). // // RFC 5280, Section 4.1 // Certificate ::= SEQUENCE { // tbsCertificate TBSCertificate, // signatureAlgorithm AlgorithmIdentifier, // signatureValue BIT STRING } // // TBSCertificate ::= SEQUENCE { // .. extractTBSCertBytes := func(inputDERBytes *[]byte) ([]byte, error) { input := cryptobyte.String(*inputDERBytes) // Extract the Certificate bytes if !input.ReadASN1(&input, cryptobyte_asn1.SEQUENCE) { return nil, errors.New("malformed certificate") } var tbs cryptobyte.String // Extract the TBSCertificate bytes from the Certificate bytes if !input.ReadASN1(&tbs, cryptobyte_asn1.SEQUENCE) { return nil, errors.New("malformed tbs certificate") } if tbs.Empty() { return nil, errors.New("parsed RawTBSCertificate field was empty") } return tbs, nil } lintRawTBSCert, err := extractTBSCertBytes(&lintCertBytes) if err != nil { return fmt.Errorf("while extracting lint TBS cert: %w", err) } leafRawTBSCert, err := extractTBSCertBytes(&leafCertBytes) if err != nil { return fmt.Errorf("while extracting leaf TBS cert: %w", err) } if !bytes.Equal(lintRawTBSCert, leafRawTBSCert) { return fmt.Errorf("mismatch between lintCert and leafCert RawTBSCertificate DER bytes: \"%x\" != \"%x\"", lintRawTBSCert, leafRawTBSCert) } return nil }