package ca

import (
	"context"
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/asn1"
	"errors"
	"fmt"
	"math/big"
	"os"
	"strings"
	"testing"
	"time"

	ct "github.com/google/certificate-transparency-go"
	cttls "github.com/google/certificate-transparency-go/tls"
	ctx509 "github.com/google/certificate-transparency-go/x509"
	"github.com/jmhodges/clock"
	"github.com/miekg/pkcs11"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/zmap/zlint/v3/lint"
	"google.golang.org/grpc"
	"google.golang.org/protobuf/types/known/emptypb"

	capb "github.com/letsencrypt/boulder/ca/proto"
	"github.com/letsencrypt/boulder/config"
	"github.com/letsencrypt/boulder/core"
	corepb "github.com/letsencrypt/boulder/core/proto"
	berrors "github.com/letsencrypt/boulder/errors"
	"github.com/letsencrypt/boulder/features"
	"github.com/letsencrypt/boulder/goodkey"
	"github.com/letsencrypt/boulder/issuance"
	"github.com/letsencrypt/boulder/linter"
	blog "github.com/letsencrypt/boulder/log"
	"github.com/letsencrypt/boulder/metrics"
	"github.com/letsencrypt/boulder/must"
	"github.com/letsencrypt/boulder/policy"
	sapb "github.com/letsencrypt/boulder/sa/proto"
	"github.com/letsencrypt/boulder/test"
)

var (
	// * Random public key
	// * CN = not-example.com
	// * DNSNames = not-example.com, www.not-example.com
	CNandSANCSR = mustRead("./testdata/cn_and_san.der.csr")

	// CSR generated by Go:
	// * Random public key
	// * CN = not-example.com
	// * Includes an extensionRequest attribute for a well-formed TLS Feature extension
	MustStapleCSR = mustRead("./testdata/must_staple.der.csr")

	// CSR generated by Go:
	// * Random public key
	// * CN = not-example.com
	// * Includes an extensionRequest attribute for an unknown extension with an
	//   empty value. That extension's OID, 2.25.123456789, is on the UUID arc.
	//   It isn't a real randomly-generated UUID because Go represents the
	//   components of the OID as 32-bit integers, which aren't large enough to
	//   hold a real 128-bit UUID; this doesn't matter as far as what we're
	//   testing here is concerned.
	UnsupportedExtensionCSR = mustRead("./testdata/unsupported_extension.der.csr")

	// CSR generated by Go:
	// * Random public key
	// * CN = not-example.com
	// * Includes an extensionRequest attribute for the CT poison extension
	//   with a valid NULL value.
	CTPoisonExtensionCSR = mustRead("./testdata/ct_poison_extension.der.csr")

	// CSR generated by Go:
	// * Random public key
	// * CN = not-example.com
	// * Includes an extensionRequest attribute for the CT poison extension
	//   with an invalid empty value.
	CTPoisonExtensionEmptyCSR = mustRead("./testdata/ct_poison_extension_empty.der.csr")

	// CSR generated by Go:
	// * Random ECDSA public key.
	// * CN = [none]
	// * DNSNames = example.com, example2.com
	ECDSACSR = mustRead("./testdata/ecdsa.der.csr")

	// OIDExtensionCTPoison is defined in RFC 6962 s3.1.
	OIDExtensionCTPoison = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11129, 2, 4, 3}

	// OIDExtensionSCTList is defined in RFC 6962 s3.3.
	OIDExtensionSCTList = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11129, 2, 4, 2}
)

const arbitraryRegID int64 = 1001

func mustRead(path string) []byte {
	return must.Do(os.ReadFile(path))
}

type testCtx struct {
	pa                     core.PolicyAuthority
	ocsp                   *ocspImpl
	crl                    *crlImpl
	defaultCertProfileName string
	lints                  lint.Registry
	certProfiles           map[string]issuance.ProfileConfig
	certExpiry             time.Duration
	certBackdate           time.Duration
	serialPrefix           int
	maxNames               int
	boulderIssuers         []*issuance.Issuer
	keyPolicy              goodkey.KeyPolicy
	fc                     clock.FakeClock
	metrics                *caMetrics
	logger                 *blog.Mock
}

type mockSA struct {
	certificate core.Certificate
}

func (m *mockSA) AddCertificate(ctx context.Context, req *sapb.AddCertificateRequest, _ ...grpc.CallOption) (*emptypb.Empty, error) {
	m.certificate.DER = req.Der
	return nil, nil
}

func (m *mockSA) AddPrecertificate(ctx context.Context, req *sapb.AddCertificateRequest, _ ...grpc.CallOption) (*emptypb.Empty, error) {
	return &emptypb.Empty{}, nil
}

func (m *mockSA) AddSerial(ctx context.Context, req *sapb.AddSerialRequest, _ ...grpc.CallOption) (*emptypb.Empty, error) {
	return &emptypb.Empty{}, nil
}

func (m *mockSA) GetCertificate(ctx context.Context, req *sapb.Serial, _ ...grpc.CallOption) (*corepb.Certificate, error) {
	return nil, berrors.NotFoundError("cannot find the cert")
}

func (m *mockSA) GetLintPrecertificate(ctx context.Context, req *sapb.Serial, _ ...grpc.CallOption) (*corepb.Certificate, error) {
	return nil, berrors.NotFoundError("cannot find the precert")
}

func (m *mockSA) SetCertificateStatusReady(ctx context.Context, req *sapb.Serial, _ ...grpc.CallOption) (*emptypb.Empty, error) {
	return &emptypb.Empty{}, nil
}

var ctx = context.Background()

func setup(t *testing.T) *testCtx {
	features.Reset()
	fc := clock.NewFake()
	fc.Add(1 * time.Hour)

	pa, err := policy.New(nil, blog.NewMock())
	test.AssertNotError(t, err, "Couldn't create PA")
	err = pa.LoadHostnamePolicyFile("../test/hostname-policy.yaml")
	test.AssertNotError(t, err, "Couldn't set hostname policy")

	certProfiles := make(map[string]issuance.ProfileConfig, 0)
	certProfiles["defaultBoulderCertificateProfile"] = issuance.ProfileConfig{
		AllowMustStaple: true,
		AllowCTPoison:   true,
		AllowSCTList:    true,
		AllowCommonName: true,
		Policies: []issuance.PolicyConfig{
			{OID: "2.23.140.1.2.1"},
		},
		MaxValidityPeriod:   config.Duration{Duration: time.Hour * 8760},
		MaxValidityBackdate: config.Duration{Duration: time.Hour},
	}
	certProfiles["longerLived"] = issuance.ProfileConfig{
		AllowMustStaple: true,
		AllowCTPoison:   true,
		AllowSCTList:    true,
		AllowCommonName: true,
		Policies: []issuance.PolicyConfig{
			{OID: "2.23.140.1.2.1"},
		},
		MaxValidityPeriod:   config.Duration{Duration: time.Hour * 8761},
		MaxValidityBackdate: config.Duration{Duration: time.Hour},
	}
	test.AssertEquals(t, len(certProfiles), 2)

	boulderIssuers := make([]*issuance.Issuer, 4)
	for i, name := range []string{"int-r3", "int-r4", "int-e1", "int-e2"} {
		boulderIssuers[i], err = issuance.LoadIssuer(issuance.IssuerConfig{
			Active:     true,
			IssuerURL:  fmt.Sprintf("http://not-example.com/i/%s", name),
			OCSPURL:    "http://not-example.com/o",
			CRLURLBase: fmt.Sprintf("http://not-example.com/c/%s/", name),
			Location: issuance.IssuerLoc{
				File:     fmt.Sprintf("../test/hierarchy/%s.key.pem", name),
				CertFile: fmt.Sprintf("../test/hierarchy/%s.cert.pem", name),
			},
		}, fc)
		test.AssertNotError(t, err, "Couldn't load test issuer")
	}

	keyPolicy, err := goodkey.NewPolicy(nil, nil)
	test.AssertNotError(t, err, "Failed to create test keypolicy")

	signatureCount := prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Name: "signatures",
			Help: "Number of signatures",
		},
		[]string{"purpose", "issuer"})
	signErrorCount := prometheus.NewCounterVec(prometheus.CounterOpts{
		Name: "signature_errors",
		Help: "A counter of signature errors labelled by error type",
	}, []string{"type"})
	lintErrorCount := prometheus.NewCounter(
		prometheus.CounterOpts{
			Name: "lint_errors",
			Help: "Number of issuances that were halted by linting errors",
		})
	cametrics := &caMetrics{signatureCount, signErrorCount, lintErrorCount}

	lints, err := linter.NewRegistry([]string{"w_subject_common_name_included"})
	test.AssertNotError(t, err, "Failed to create zlint registry")

	ocsp, err := NewOCSPImpl(
		boulderIssuers,
		24*time.Hour,
		0,
		time.Second,
		blog.NewMock(),
		metrics.NoopRegisterer,
		cametrics,
		fc,
	)
	test.AssertNotError(t, err, "Failed to create ocsp impl")

	crl, err := NewCRLImpl(
		boulderIssuers,
		issuance.CRLProfileConfig{
			ValidityInterval: config.Duration{Duration: 216 * time.Hour},
			MaxBackdate:      config.Duration{Duration: time.Hour},
		},
		100,
		blog.NewMock(),
		cametrics,
	)
	test.AssertNotError(t, err, "Failed to create crl impl")

	return &testCtx{
		pa:                     pa,
		ocsp:                   ocsp,
		crl:                    crl,
		defaultCertProfileName: "defaultBoulderCertificateProfile",
		lints:                  lints,
		certProfiles:           certProfiles,
		certExpiry:             8760 * time.Hour,
		certBackdate:           time.Hour,
		serialPrefix:           17,
		maxNames:               2,
		boulderIssuers:         boulderIssuers,
		keyPolicy:              keyPolicy,
		fc:                     fc,
		metrics:                cametrics,
		logger:                 blog.NewMock(),
	}
}

func TestSerialPrefix(t *testing.T) {
	t.Parallel()
	testCtx := setup(t)

	_, err := NewCertificateAuthorityImpl(
		nil,
		nil,
		nil,
		"",
		nil,
		nil,
		nil,
		testCtx.certExpiry,
		testCtx.certBackdate,
		0,
		testCtx.maxNames,
		testCtx.keyPolicy,
		testCtx.logger,
		nil,
		testCtx.fc)
	test.AssertError(t, err, "CA should have failed with no SerialPrefix")

	_, err = NewCertificateAuthorityImpl(
		nil,
		nil,
		nil,
		"",
		nil,
		nil,
		nil,
		testCtx.certExpiry,
		testCtx.certBackdate,
		128,
		testCtx.maxNames,
		testCtx.keyPolicy,
		testCtx.logger,
		nil,
		testCtx.fc)
	test.AssertError(t, err, "CA should have failed with too-large SerialPrefix")
}

func TestNoteSignError(t *testing.T) {
	testCtx := setup(t)
	metrics := testCtx.metrics

	err := fmt.Errorf("wrapped non-signing error: %w", errors.New("oops"))
	metrics.noteSignError(err)
	test.AssertMetricWithLabelsEquals(t, metrics.signErrorCount, prometheus.Labels{"type": "HSM"}, 0)

	err = fmt.Errorf("wrapped signing error: %w", pkcs11.Error(5))
	metrics.noteSignError(err)
	test.AssertMetricWithLabelsEquals(t, metrics.signErrorCount, prometheus.Labels{"type": "HSM"}, 1)
}

type TestCertificateIssuance struct {
	ca      *certificateAuthorityImpl
	sa      *mockSA
	req     *x509.CertificateRequest
	certDER []byte
	cert    *x509.Certificate
}

func TestIssuePrecertificate(t *testing.T) {
	t.Parallel()
	testCases := []struct {
		name    string
		csr     []byte
		subTest func(t *testing.T, i *TestCertificateIssuance)
	}{
		{"IssuePrecertificate", CNandSANCSR, issueCertificateSubTestIssuePrecertificate},
		{"ValidityUsesCAClock", CNandSANCSR, issueCertificateSubTestValidityUsesCAClock},
		{"ProfileSelectionRSA", CNandSANCSR, issueCertificateSubTestProfileSelectionRSA},
		{"ProfileSelectionECDSA", ECDSACSR, issueCertificateSubTestProfileSelectionECDSA},
		{"MustStaple", MustStapleCSR, issueCertificateSubTestMustStaple},
		{"UnknownExtension", UnsupportedExtensionCSR, issueCertificateSubTestUnknownExtension},
		{"CTPoisonExtension", CTPoisonExtensionCSR, issueCertificateSubTestCTPoisonExtension},
		{"CTPoisonExtensionEmpty", CTPoisonExtensionEmptyCSR, issueCertificateSubTestCTPoisonExtension},
	}

	for _, testCase := range testCases {
		// TODO(#7454) Remove this rebinding
		testCase := testCase

		// The loop through the issuance modes must be inside the loop through
		// |testCases| because the "certificate-for-precertificate" tests use
		// the precertificates previously generated from the preceding
		// "precertificate" test.
		for _, mode := range []string{"precertificate", "certificate-for-precertificate"} {
			ca, sa := issueCertificateSubTestSetup(t, nil)
			t.Run(fmt.Sprintf("%s - %s", mode, testCase.name), func(t *testing.T) {
				t.Parallel()
				req, err := x509.ParseCertificateRequest(testCase.csr)
				test.AssertNotError(t, err, "Certificate request failed to parse")
				issueReq := &capb.IssueCertificateRequest{Csr: testCase.csr, RegistrationID: arbitraryRegID}

				var certDER []byte
				response, err := ca.IssuePrecertificate(ctx, issueReq)

				test.AssertNotError(t, err, "Failed to issue precertificate")
				certDER = response.DER

				cert, err := x509.ParseCertificate(certDER)
				test.AssertNotError(t, err, "Certificate failed to parse")
				poisonExtension := findExtension(cert.Extensions, OIDExtensionCTPoison)
				test.AssertNotNil(t, poisonExtension, "Precert doesn't contain poison extension")
				if poisonExtension != nil {
					test.AssertEquals(t, poisonExtension.Critical, true)
					test.AssertDeepEquals(t, poisonExtension.Value, []byte{0x05, 0x00}) // ASN.1 DER NULL
				}

				i := TestCertificateIssuance{
					ca:      ca,
					sa:      sa,
					req:     req,
					certDER: certDER,
					cert:    cert,
				}

				testCase.subTest(t, &i)
			})
		}
	}
}

func issueCertificateSubTestSetup(t *testing.T, e *ECDSAAllowList) (*certificateAuthorityImpl, *mockSA) {
	testCtx := setup(t)
	ecdsaAllowList := &ECDSAAllowList{}
	if e == nil {
		e = ecdsaAllowList
	}
	sa := &mockSA{}
	ca, err := NewCertificateAuthorityImpl(
		sa,
		testCtx.pa,
		testCtx.boulderIssuers,
		testCtx.defaultCertProfileName,
		testCtx.certProfiles,
		testCtx.lints,
		e,
		testCtx.certExpiry,
		testCtx.certBackdate,
		testCtx.serialPrefix,
		testCtx.maxNames,
		testCtx.keyPolicy,
		testCtx.logger,
		testCtx.metrics,
		testCtx.fc)
	test.AssertNotError(t, err, "Failed to create CA")

	return ca, sa
}

func issueCertificateSubTestIssuePrecertificate(t *testing.T, i *TestCertificateIssuance) {
	cert := i.cert

	test.AssertEquals(t, cert.Subject.CommonName, "not-example.com")

	if len(cert.DNSNames) == 1 {
		if cert.DNSNames[0] != "not-example.com" {
			t.Errorf("Improper list of domain names %v", cert.DNSNames)
		}
		t.Errorf("Improper list of domain names %v", cert.DNSNames)
	}

	if len(cert.Subject.Country) > 0 {
		t.Errorf("Subject contained unauthorized values: %v", cert.Subject)
	}
}

func issueCertificateSubTestValidityUsesCAClock(t *testing.T, i *TestCertificateIssuance) {
	test.AssertEquals(t, i.cert.NotBefore, i.ca.clk.Now().Add(-1*i.ca.backdate))
	test.AssertEquals(t, i.cert.NotAfter.Add(time.Second).Sub(i.cert.NotBefore), i.ca.validityPeriod)
}

// Test failure mode when no issuers are present.
func TestNoIssuers(t *testing.T) {
	t.Parallel()
	testCtx := setup(t)
	sa := &mockSA{}
	_, err := NewCertificateAuthorityImpl(
		sa,
		testCtx.pa,
		nil, // No issuers
		testCtx.defaultCertProfileName,
		testCtx.certProfiles,
		testCtx.lints,
		nil,
		testCtx.certExpiry,
		testCtx.certBackdate,
		testCtx.serialPrefix,
		testCtx.maxNames,
		testCtx.keyPolicy,
		testCtx.logger,
		testCtx.metrics,
		testCtx.fc)
	test.AssertError(t, err, "No issuers found during CA construction.")
	test.AssertEquals(t, err.Error(), "must have at least one issuer")
}

// Test issuing when multiple issuers are present.
func TestMultipleIssuers(t *testing.T) {
	t.Parallel()
	testCtx := setup(t)
	sa := &mockSA{}
	ca, err := NewCertificateAuthorityImpl(
		sa,
		testCtx.pa,
		testCtx.boulderIssuers,
		testCtx.defaultCertProfileName,
		testCtx.certProfiles,
		testCtx.lints,
		nil,
		testCtx.certExpiry,
		testCtx.certBackdate,
		testCtx.serialPrefix,
		testCtx.maxNames,
		testCtx.keyPolicy,
		testCtx.logger,
		testCtx.metrics,
		testCtx.fc)
	test.AssertNotError(t, err, "Failed to remake CA")

	selectedProfile := ca.certProfiles.defaultName
	_, ok := ca.certProfiles.profileByName[selectedProfile]
	test.Assert(t, ok, "Certificate profile was expected to exist")

	// Test that an RSA CSR gets issuance from an RSA issuer.
	issuedCert, err := ca.IssuePrecertificate(ctx, &capb.IssueCertificateRequest{Csr: CNandSANCSR, RegistrationID: arbitraryRegID, CertProfileName: selectedProfile})
	test.AssertNotError(t, err, "Failed to issue certificate")
	cert, err := x509.ParseCertificate(issuedCert.DER)
	test.AssertNotError(t, err, "Certificate failed to parse")
	validated := false
	for _, issuer := range ca.issuers.byAlg[x509.RSA] {
		err = cert.CheckSignatureFrom(issuer.Cert.Certificate)
		if err == nil {
			validated = true
			break
		}
	}
	test.Assert(t, validated, "Certificate failed signature validation")
	test.AssertMetricWithLabelsEquals(t, ca.metrics.signatureCount, prometheus.Labels{"purpose": "precertificate", "status": "success"}, 1)

	// Test that an ECDSA CSR gets issuance from an ECDSA issuer.
	issuedCert, err = ca.IssuePrecertificate(ctx, &capb.IssueCertificateRequest{Csr: ECDSACSR, RegistrationID: arbitraryRegID, CertProfileName: selectedProfile})
	test.AssertNotError(t, err, "Failed to issue certificate")
	cert, err = x509.ParseCertificate(issuedCert.DER)
	test.AssertNotError(t, err, "Certificate failed to parse")
	validated = false
	for _, issuer := range ca.issuers.byAlg[x509.ECDSA] {
		err = cert.CheckSignatureFrom(issuer.Cert.Certificate)
		if err == nil {
			validated = true
			break
		}
	}
	test.Assert(t, validated, "Certificate failed signature validation")
	test.AssertMetricWithLabelsEquals(t, ca.metrics.signatureCount, prometheus.Labels{"purpose": "precertificate", "status": "success"}, 2)
}

func TestUnpredictableIssuance(t *testing.T) {
	testCtx := setup(t)
	sa := &mockSA{}

	// Load our own set of issuer configs, specifically with:
	// - 3 issuers,
	// - 2 of which are active
	boulderIssuers := make([]*issuance.Issuer, 3)
	var err error
	for i, name := range []string{"int-e1", "int-e2", "int-r3"} {
		boulderIssuers[i], err = issuance.LoadIssuer(issuance.IssuerConfig{
			Active:     i != 0, // Make one of the ECDSA issuers inactive.
			IssuerURL:  fmt.Sprintf("http://not-example.com/i/%s", name),
			OCSPURL:    "http://not-example.com/o",
			CRLURLBase: fmt.Sprintf("http://not-example.com/c/%s/", name),
			Location: issuance.IssuerLoc{
				File:     fmt.Sprintf("../test/hierarchy/%s.key.pem", name),
				CertFile: fmt.Sprintf("../test/hierarchy/%s.cert.pem", name),
			},
		}, testCtx.fc)
		test.AssertNotError(t, err, "Couldn't load test issuer")
	}

	ca, err := NewCertificateAuthorityImpl(
		sa,
		testCtx.pa,
		boulderIssuers,
		testCtx.defaultCertProfileName,
		testCtx.certProfiles,
		testCtx.lints,
		nil,
		testCtx.certExpiry,
		testCtx.certBackdate,
		testCtx.serialPrefix,
		testCtx.maxNames,
		testCtx.keyPolicy,
		testCtx.logger,
		testCtx.metrics,
		testCtx.fc)
	test.AssertNotError(t, err, "Failed to remake CA")

	// Then, modify the resulting issuer maps so that the RSA issuer appears to
	// be an ECDSA issuer. This would be easier if we had three ECDSA issuers to
	// use here, but that doesn't exist in //test/hierarchy (yet).
	ca.issuers.byAlg[x509.ECDSA] = append(ca.issuers.byAlg[x509.ECDSA], ca.issuers.byAlg[x509.RSA]...)
	ca.issuers.byAlg[x509.RSA] = []*issuance.Issuer{}

	// Issue the same (ECDSA-keyed) certificate 20 times. None of the issuances
	// should come from the inactive issuer (int-e1). At least one issuance should
	// come from each of the two active issuers (int-e2 and int-r3). With 20
	// trials, the probability that all 20 issuances come from the same issuer is
	// 0.5 ^ 20 = 9.5e-7 ~= 1e-6 = 1 in a million, so we do not consider this test
	// to be flaky.
	req := &capb.IssueCertificateRequest{Csr: ECDSACSR, RegistrationID: arbitraryRegID}
	seenE2 := false
	seenR3 := false
	for i := 0; i < 20; i++ {
		result, err := ca.IssuePrecertificate(ctx, req)
		test.AssertNotError(t, err, "Failed to issue test certificate")
		cert, err := x509.ParseCertificate(result.DER)
		test.AssertNotError(t, err, "Failed to parse test certificate")
		if strings.Contains(cert.Issuer.CommonName, "E1") {
			t.Fatal("Issued certificate from inactive issuer")
		} else if strings.Contains(cert.Issuer.CommonName, "E2") {
			seenE2 = true
		} else if strings.Contains(cert.Issuer.CommonName, "R3") {
			seenR3 = true
		}
	}
	test.Assert(t, seenE2, "Expected at least one issuance from active issuer")
	test.Assert(t, seenR3, "Expected at least one issuance from active issuer")
}

func TestProfiles(t *testing.T) {
	t.Parallel()
	testCtx := setup(t)
	test.AssertEquals(t, len(testCtx.certProfiles), 2)

	sa := &mockSA{}

	duplicateProfiles := make(map[string]issuance.ProfileConfig, 0)
	// These profiles contain the same data which will produce an identical
	// hash, even though the names are different.
	duplicateProfiles["defaultBoulderCertificateProfile"] = issuance.ProfileConfig{
		AllowMustStaple: false,
		AllowCTPoison:   false,
		AllowSCTList:    false,
		AllowCommonName: false,
		Policies: []issuance.PolicyConfig{
			{OID: "2.23.140.1.2.1"},
		},
		MaxValidityPeriod:   config.Duration{Duration: time.Hour * 8760},
		MaxValidityBackdate: config.Duration{Duration: time.Hour},
	}
	duplicateProfiles["uhoh_ohno"] = issuance.ProfileConfig{
		AllowMustStaple: false,
		AllowCTPoison:   false,
		AllowSCTList:    false,
		AllowCommonName: false,
		Policies: []issuance.PolicyConfig{
			{OID: "2.23.140.1.2.1"},
		},
		MaxValidityPeriod:   config.Duration{Duration: time.Hour * 8760},
		MaxValidityBackdate: config.Duration{Duration: time.Hour},
	}
	test.AssertEquals(t, len(duplicateProfiles), 2)

	jackedProfiles := make(map[string]issuance.ProfileConfig, 0)
	jackedProfiles["ruhroh"] = issuance.ProfileConfig{
		AllowMustStaple: false,
		AllowCTPoison:   false,
		AllowSCTList:    false,
		AllowCommonName: false,
		Policies: []issuance.PolicyConfig{
			{OID: "2.23.140.1.2.1"},
		},
		MaxValidityPeriod:   config.Duration{Duration: time.Hour * 9000},
		MaxValidityBackdate: config.Duration{Duration: time.Hour},
	}
	test.AssertEquals(t, len(jackedProfiles), 1)

	type nameToHash struct {
		name string
		hash [32]byte
	}

	emptyMap := make(map[string]issuance.ProfileConfig, 0)
	testCases := []struct {
		name              string
		profileConfigs    map[string]issuance.ProfileConfig
		defaultName       string
		expectedErrSubstr string
		expectedProfiles  []nameToHash
	}{
		{
			name:              "no profiles",
			profileConfigs:    emptyMap,
			expectedErrSubstr: "at least one certificate profile",
		},
		{
			name:              "nil profile map",
			profileConfigs:    nil,
			expectedErrSubstr: "at least one certificate profile",
		},
		{
			name:              "duplicate hash",
			profileConfigs:    duplicateProfiles,
			expectedErrSubstr: "duplicate certificate profile hash",
		},
		{
			name:           "default profiles from setup func",
			profileConfigs: testCtx.certProfiles,
			expectedProfiles: []nameToHash{
				{
					name: testCtx.defaultCertProfileName,
					hash: [32]byte{205, 182, 88, 236, 32, 18, 154, 120, 148, 194, 42, 215, 117, 140, 13, 169, 127, 196, 219, 67, 82, 36, 147, 67, 254, 117, 65, 112, 202, 60, 185, 9},
				},
				{
					name: "longerLived",
					hash: [32]byte{80, 228, 198, 83, 7, 184, 187, 236, 113, 17, 103, 213, 226, 245, 172, 212, 135, 241, 125, 92, 122, 200, 34, 159, 139, 72, 191, 41, 1, 244, 86, 62},
				},
			},
		},
		{
			name:              "no profile matching default name",
			profileConfigs:    jackedProfiles,
			expectedErrSubstr: "profile object was not found for that name",
		},
		{
			name:           "certificate profile hash changed mid-issuance",
			profileConfigs: jackedProfiles,
			defaultName:    "ruhroh",
			expectedProfiles: []nameToHash{
				{
					// We'll change the mapped hash key under the hood during
					// the test.
					name: "ruhroh",
					hash: [32]byte{84, 131, 8, 59, 3, 244, 7, 36, 151, 161, 118, 68, 117, 183, 197, 177, 179, 232, 215, 10, 188, 48, 159, 195, 195, 140, 19, 204, 201, 182, 239, 235},
				},
			},
		},
	}

	for _, tc := range testCases {
		// TODO(#7454) Remove this rebinding
		tc := tc
		// This is handled by boulder-ca, not the CA package.
		if tc.defaultName == "" {
			tc.defaultName = testCtx.defaultCertProfileName
		}
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			tCA, err := NewCertificateAuthorityImpl(
				sa,
				testCtx.pa,
				testCtx.boulderIssuers,
				tc.defaultName,
				tc.profileConfigs,
				testCtx.lints,
				nil,
				testCtx.certExpiry,
				testCtx.certBackdate,
				testCtx.serialPrefix,
				testCtx.maxNames,
				testCtx.keyPolicy,
				testCtx.logger,
				testCtx.metrics,
				testCtx.fc,
			)

			if tc.expectedErrSubstr != "" {
				test.AssertContains(t, err.Error(), tc.expectedErrSubstr)
				test.AssertError(t, err, "No profile found during CA construction.")
			} else {
				test.AssertNotError(t, err, "Profiles should exist, but were not found")
			}

			if tc.expectedProfiles != nil {
				test.AssertEquals(t, len(tc.expectedProfiles), len(tCA.certProfiles.profileByName))
			}

			for _, expected := range tc.expectedProfiles {
				cpwid, ok := tCA.certProfiles.profileByName[expected.name]
				test.Assert(t, ok, "Profile name was not found, but should have been")
				test.AssertEquals(t, expected.hash, cpwid.hash)

				if tc.name == "certificate profile hash changed mid-issuance" {
					// This is an attempt to simulate the hash changing, but the
					// name remaining the same on a CA node in the duration
					// between CA1 sending capb.IssuePrecerticateResponse and
					// before the RA calls
					// capb.IssueCertificateForPrecertificate. We expect the
					// receiving CA2 to error that the hash we expect could not
					// be found in the map.
					originalHash := cpwid.hash
					cpwid.hash = [32]byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6, 6, 6}
					test.AssertNotEquals(t, originalHash, cpwid.hash)
				}
			}
		})
	}
}

func TestECDSAAllowList(t *testing.T) {
	t.Parallel()
	req := &capb.IssueCertificateRequest{Csr: ECDSACSR, RegistrationID: arbitraryRegID}

	// With allowlist containing arbitraryRegID, issuance should come from ECDSA issuer.
	regIDMap := makeRegIDsMap([]int64{arbitraryRegID})
	ca, _ := issueCertificateSubTestSetup(t, &ECDSAAllowList{regIDMap})
	result, err := ca.IssuePrecertificate(ctx, req)
	test.AssertNotError(t, err, "Failed to issue certificate")
	cert, err := x509.ParseCertificate(result.DER)
	test.AssertNotError(t, err, "Certificate failed to parse")
	test.AssertEquals(t, cert.SignatureAlgorithm, x509.ECDSAWithSHA384)

	// With allowlist not containing arbitraryRegID, issuance should fall back to RSA issuer.
	regIDMap = makeRegIDsMap([]int64{2002})
	ca, _ = issueCertificateSubTestSetup(t, &ECDSAAllowList{regIDMap})
	result, err = ca.IssuePrecertificate(ctx, req)
	test.AssertNotError(t, err, "Failed to issue certificate")
	cert, err = x509.ParseCertificate(result.DER)
	test.AssertNotError(t, err, "Certificate failed to parse")
	test.AssertEquals(t, cert.SignatureAlgorithm, x509.SHA256WithRSA)

	// With empty allowlist but ECDSAForAll enabled, issuance should come from ECDSA issuer.
	ca, _ = issueCertificateSubTestSetup(t, nil)
	features.Set(features.Config{ECDSAForAll: true})
	defer features.Reset()
	result, err = ca.IssuePrecertificate(ctx, req)
	test.AssertNotError(t, err, "Failed to issue certificate")
	cert, err = x509.ParseCertificate(result.DER)
	test.AssertNotError(t, err, "Certificate failed to parse")
	test.AssertEquals(t, cert.SignatureAlgorithm, x509.ECDSAWithSHA384)
}

func TestInvalidCSRs(t *testing.T) {
	t.Parallel()
	testCases := []struct {
		name         string
		csrPath      string
		check        func(t *testing.T, ca *certificateAuthorityImpl, sa *mockSA)
		errorMessage string
		errorType    berrors.ErrorType
	}{
		// Test that the CA rejects CSRs that have no names.
		//
		// CSR generated by Go:
		// * Random RSA public key.
		// * CN = [none]
		// * DNSNames = [none]
		{"RejectNoHostnames", "./testdata/no_names.der.csr", nil, "Issued certificate with no names", berrors.BadCSR},

		// Test that the CA rejects CSRs that have too many names.
		//
		// CSR generated by Go:
		// * Random public key
		// * CN = [none]
		// * DNSNames = not-example.com, www.not-example.com, mail.example.com
		{"RejectTooManyHostnames", "./testdata/too_many_names.der.csr", nil, "Issued certificate with too many names", berrors.BadCSR},

		// Test that the CA rejects CSRs that have public keys that are too short.
		//
		// CSR generated by Go:
		// * Random public key -- 512 bits long
		// * CN = (none)
		// * DNSNames = not-example.com, www.not-example.com, mail.not-example.com
		{"RejectShortKey", "./testdata/short_key.der.csr", nil, "Issued a certificate with too short a key.", berrors.BadCSR},

		// Test that the CA rejects CSRs that have bad signature algorithms.
		//
		// CSR generated by Go:
		// * Random public key -- 2048 bits long
		// * CN = (none)
		// * DNSNames = not-example.com, www.not-example.com, mail.not-example.com
		// * Signature Algorithm: sha1WithRSAEncryption
		{"RejectBadAlgorithm", "./testdata/bad_algorithm.der.csr", nil, "Issued a certificate based on a CSR with a bad signature algorithm.", berrors.BadCSR},

		// CSR generated by Go:
		// * Random RSA public key.
		// * CN = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.com
		// * DNSNames = [none]
		{"RejectLongCommonName", "./testdata/long_cn.der.csr", nil, "Issued a certificate with a CN over 64 bytes.", berrors.BadCSR},

		// CSR generated by OpenSSL:
		// Edited signature to become invalid.
		{"RejectWrongSignature", "./testdata/invalid_signature.der.csr", nil, "Issued a certificate based on a CSR with an invalid signature.", berrors.BadCSR},
	}

	for _, testCase := range testCases {
		// TODO(#7454) Remove this rebinding
		testCase := testCase
		testCtx := setup(t)
		sa := &mockSA{}
		ca, err := NewCertificateAuthorityImpl(
			sa,
			testCtx.pa,
			testCtx.boulderIssuers,
			testCtx.defaultCertProfileName,
			testCtx.certProfiles,
			testCtx.lints,
			nil,
			testCtx.certExpiry,
			testCtx.certBackdate,
			testCtx.serialPrefix,
			testCtx.maxNames,
			testCtx.keyPolicy,
			testCtx.logger,
			testCtx.metrics,
			testCtx.fc)
		test.AssertNotError(t, err, "Failed to create CA")

		t.Run(testCase.name, func(t *testing.T) {
			t.Parallel()
			serializedCSR := mustRead(testCase.csrPath)
			issueReq := &capb.IssueCertificateRequest{Csr: serializedCSR, RegistrationID: arbitraryRegID}
			_, err = ca.IssuePrecertificate(ctx, issueReq)

			test.AssertErrorIs(t, err, testCase.errorType)
			test.AssertMetricWithLabelsEquals(t, ca.metrics.signatureCount, prometheus.Labels{"purpose": "cert"}, 0)

			test.AssertError(t, err, testCase.errorMessage)
			if testCase.check != nil {
				testCase.check(t, ca, sa)
			}
		})
	}
}

func TestRejectValidityTooLong(t *testing.T) {
	t.Parallel()
	testCtx := setup(t)
	sa := &mockSA{}
	ca, err := NewCertificateAuthorityImpl(
		sa,
		testCtx.pa,
		testCtx.boulderIssuers,
		testCtx.defaultCertProfileName,
		testCtx.certProfiles,
		testCtx.lints,
		nil,
		testCtx.certExpiry,
		testCtx.certBackdate,
		testCtx.serialPrefix,
		testCtx.maxNames,
		testCtx.keyPolicy,
		testCtx.logger,
		testCtx.metrics,
		testCtx.fc)
	test.AssertNotError(t, err, "Failed to create CA")

	future, err := time.Parse(time.RFC3339, "2025-02-10T00:30:00Z")

	test.AssertNotError(t, err, "Failed to parse time")
	testCtx.fc.Set(future)
	// Test that the CA rejects CSRs that would expire after the intermediate cert
	_, err = ca.IssuePrecertificate(ctx, &capb.IssueCertificateRequest{Csr: CNandSANCSR, RegistrationID: arbitraryRegID})
	test.AssertError(t, err, "Cannot issue a certificate that expires after the intermediate certificate")
	test.AssertErrorIs(t, err, berrors.InternalServer)
}

func issueCertificateSubTestProfileSelectionRSA(t *testing.T, i *TestCertificateIssuance) {
	// Certificates for RSA keys should be marked as usable for signatures and encryption.
	expectedKeyUsage := x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment
	t.Logf("expected key usage %v, got %v", expectedKeyUsage, i.cert.KeyUsage)
	test.AssertEquals(t, i.cert.KeyUsage, expectedKeyUsage)
}

func issueCertificateSubTestProfileSelectionECDSA(t *testing.T, i *TestCertificateIssuance) {
	// Certificates for ECDSA keys should be marked as usable for only signatures.
	expectedKeyUsage := x509.KeyUsageDigitalSignature
	t.Logf("expected key usage %v, got %v", expectedKeyUsage, i.cert.KeyUsage)
	test.AssertEquals(t, i.cert.KeyUsage, expectedKeyUsage)
}

func countMustStaple(t *testing.T, cert *x509.Certificate) (count int) {
	oidTLSFeature := asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 24}
	mustStapleFeatureValue := []byte{0x30, 0x03, 0x02, 0x01, 0x05}
	for _, ext := range cert.Extensions {
		if ext.Id.Equal(oidTLSFeature) {
			test.Assert(t, !ext.Critical, "Extension was marked critical")
			test.AssertByteEquals(t, ext.Value, mustStapleFeatureValue)
			count++
		}
	}
	return count
}

func issueCertificateSubTestMustStaple(t *testing.T, i *TestCertificateIssuance) {
	test.AssertMetricWithLabelsEquals(t, i.ca.metrics.signatureCount, prometheus.Labels{"purpose": "precertificate"}, 1)
	test.AssertEquals(t, countMustStaple(t, i.cert), 1)
}

func issueCertificateSubTestUnknownExtension(t *testing.T, i *TestCertificateIssuance) {
	test.AssertMetricWithLabelsEquals(t, i.ca.metrics.signatureCount, prometheus.Labels{"purpose": "precertificate"}, 1)

	// NOTE: The hard-coded value here will have to change over time as Boulder
	// adds or removes (unrequested/default) extensions in certificates.
	expectedExtensionCount := 9
	test.AssertEquals(t, len(i.cert.Extensions), expectedExtensionCount)
}

func issueCertificateSubTestCTPoisonExtension(t *testing.T, i *TestCertificateIssuance) {
	test.AssertMetricWithLabelsEquals(t, i.ca.metrics.signatureCount, prometheus.Labels{"purpose": "precertificate"}, 1)
}

func findExtension(extensions []pkix.Extension, id asn1.ObjectIdentifier) *pkix.Extension {
	for _, ext := range extensions {
		if ext.Id.Equal(id) {
			return &ext
		}
	}
	return nil
}

func makeSCTs() ([][]byte, error) {
	sct := ct.SignedCertificateTimestamp{
		SCTVersion: 0,
		Timestamp:  2020,
		Signature: ct.DigitallySigned{
			Signature: []byte{0},
		},
	}
	sctBytes, err := cttls.Marshal(sct)
	if err != nil {
		return nil, err
	}
	return [][]byte{sctBytes}, err
}

func TestIssueCertificateForPrecertificate(t *testing.T) {
	t.Parallel()
	testCtx := setup(t)
	sa := &mockSA{}
	ca, err := NewCertificateAuthorityImpl(
		sa,
		testCtx.pa,
		testCtx.boulderIssuers,
		testCtx.defaultCertProfileName,
		testCtx.certProfiles,
		testCtx.lints,
		nil,
		testCtx.certExpiry,
		testCtx.certBackdate,
		testCtx.serialPrefix,
		testCtx.maxNames,
		testCtx.keyPolicy,
		testCtx.logger,
		testCtx.metrics,
		testCtx.fc)
	test.AssertNotError(t, err, "Failed to create CA")

	_, ok := ca.certProfiles.profileByName[ca.certProfiles.defaultName]
	test.Assert(t, ok, "Certificate profile was expected to exist")

	issueReq := capb.IssueCertificateRequest{Csr: CNandSANCSR, RegistrationID: arbitraryRegID, OrderID: 0}
	precert, err := ca.IssuePrecertificate(ctx, &issueReq)
	test.AssertNotError(t, err, "Failed to issue precert")
	parsedPrecert, err := x509.ParseCertificate(precert.DER)
	test.AssertNotError(t, err, "Failed to parse precert")
	test.AssertMetricWithLabelsEquals(t, ca.metrics.signatureCount, prometheus.Labels{"purpose": "precertificate", "status": "success"}, 1)
	test.AssertMetricWithLabelsEquals(t, ca.metrics.signatureCount, prometheus.Labels{"purpose": "certificate", "status": "success"}, 0)

	// Check for poison extension
	poisonExtension := findExtension(parsedPrecert.Extensions, OIDExtensionCTPoison)
	test.AssertNotNil(t, poisonExtension, "Couldn't find CTPoison extension")
	test.AssertEquals(t, poisonExtension.Critical, true)
	test.AssertDeepEquals(t, poisonExtension.Value, []byte{0x05, 0x00}) // ASN.1 DER NULL

	sctBytes, err := makeSCTs()
	if err != nil {
		t.Fatal(err)
	}

	test.AssertNotError(t, err, "Failed to marshal SCT")
	cert, err := ca.IssueCertificateForPrecertificate(ctx, &capb.IssueCertificateForPrecertificateRequest{
		DER:             precert.DER,
		SCTs:            sctBytes,
		RegistrationID:  arbitraryRegID,
		OrderID:         0,
		CertProfileHash: precert.CertProfileHash,
	})
	test.AssertNotError(t, err, "Failed to issue cert from precert")
	parsedCert, err := x509.ParseCertificate(cert.Der)
	test.AssertNotError(t, err, "Failed to parse cert")
	test.AssertMetricWithLabelsEquals(t, ca.metrics.signatureCount, prometheus.Labels{"purpose": "certificate", "status": "success"}, 1)

	// Check for SCT list extension
	sctListExtension := findExtension(parsedCert.Extensions, OIDExtensionSCTList)
	test.AssertNotNil(t, sctListExtension, "Couldn't find SCTList extension")
	test.AssertEquals(t, sctListExtension.Critical, false)
	var rawValue []byte
	_, err = asn1.Unmarshal(sctListExtension.Value, &rawValue)
	test.AssertNotError(t, err, "Failed to unmarshal extension value")
	sctList, err := deserializeSCTList(rawValue)
	test.AssertNotError(t, err, "Failed to deserialize SCT list")
	test.Assert(t, len(sctList) == 1, fmt.Sprintf("Wrong number of SCTs, wanted: 1, got: %d", len(sctList)))
}

func TestIssueCertificateForPrecertificateWithSpecificCertificateProfile(t *testing.T) {
	t.Parallel()
	testCtx := setup(t)
	sa := &mockSA{}
	ca, err := NewCertificateAuthorityImpl(
		sa,
		testCtx.pa,
		testCtx.boulderIssuers,
		testCtx.defaultCertProfileName,
		testCtx.certProfiles,
		testCtx.lints,
		nil,
		testCtx.certExpiry,
		testCtx.certBackdate,
		testCtx.serialPrefix,
		testCtx.maxNames,
		testCtx.keyPolicy,
		testCtx.logger,
		testCtx.metrics,
		testCtx.fc)
	test.AssertNotError(t, err, "Failed to create CA")

	selectedProfile := "longerLived"
	certProfile, ok := ca.certProfiles.profileByName[selectedProfile]
	test.Assert(t, ok, "Certificate profile was expected to exist")

	issueReq := capb.IssueCertificateRequest{
		Csr:             CNandSANCSR,
		RegistrationID:  arbitraryRegID,
		OrderID:         0,
		CertProfileName: selectedProfile,
	}
	precert, err := ca.IssuePrecertificate(ctx, &issueReq)
	test.AssertNotError(t, err, "Failed to issue precert")
	parsedPrecert, err := x509.ParseCertificate(precert.DER)
	test.AssertNotError(t, err, "Failed to parse precert")
	test.AssertMetricWithLabelsEquals(t, ca.metrics.signatureCount, prometheus.Labels{"purpose": "precertificate", "status": "success"}, 1)
	test.AssertMetricWithLabelsEquals(t, ca.metrics.signatureCount, prometheus.Labels{"purpose": "certificate", "status": "success"}, 0)

	// Check for poison extension
	poisonExtension := findExtension(parsedPrecert.Extensions, OIDExtensionCTPoison)
	test.AssertNotNil(t, poisonExtension, "Couldn't find CTPoison extension")
	test.AssertEquals(t, poisonExtension.Critical, true)
	test.AssertDeepEquals(t, poisonExtension.Value, []byte{0x05, 0x00}) // ASN.1 DER NULL

	sctBytes, err := makeSCTs()
	if err != nil {
		t.Fatal(err)
	}

	test.AssertNotError(t, err, "Failed to marshal SCT")
	cert, err := ca.IssueCertificateForPrecertificate(ctx, &capb.IssueCertificateForPrecertificateRequest{
		DER:             precert.DER,
		SCTs:            sctBytes,
		RegistrationID:  arbitraryRegID,
		OrderID:         0,
		CertProfileHash: certProfile.hash[:],
	})
	test.AssertNotError(t, err, "Failed to issue cert from precert")
	parsedCert, err := x509.ParseCertificate(cert.Der)
	test.AssertNotError(t, err, "Failed to parse cert")
	test.AssertMetricWithLabelsEquals(t, ca.metrics.signatureCount, prometheus.Labels{"purpose": "certificate", "status": "success"}, 1)

	// Check for SCT list extension
	sctListExtension := findExtension(parsedCert.Extensions, OIDExtensionSCTList)
	test.AssertNotNil(t, sctListExtension, "Couldn't find SCTList extension")
	test.AssertEquals(t, sctListExtension.Critical, false)
	var rawValue []byte
	_, err = asn1.Unmarshal(sctListExtension.Value, &rawValue)
	test.AssertNotError(t, err, "Failed to unmarshal extension value")
	sctList, err := deserializeSCTList(rawValue)
	test.AssertNotError(t, err, "Failed to deserialize SCT list")
	test.Assert(t, len(sctList) == 1, fmt.Sprintf("Wrong number of SCTs, wanted: 1, got: %d", len(sctList)))
}

// deserializeSCTList deserializes a list of SCTs.
// Forked from github.com/cloudflare/cfssl/helpers
func deserializeSCTList(serializedSCTList []byte) ([]ct.SignedCertificateTimestamp, error) {
	var sctList ctx509.SignedCertificateTimestampList
	rest, err := cttls.Unmarshal(serializedSCTList, &sctList)
	if err != nil {
		return nil, err
	}
	if len(rest) != 0 {
		return nil, errors.New("serialized SCT list contained trailing garbage")
	}
	list := make([]ct.SignedCertificateTimestamp, len(sctList.SCTList))
	for i, serializedSCT := range sctList.SCTList {
		var sct ct.SignedCertificateTimestamp
		rest, err := cttls.Unmarshal(serializedSCT.Val, &sct)
		if err != nil {
			return nil, err
		}
		if len(rest) != 0 {
			return nil, errors.New("serialized SCT contained trailing garbage")
		}
		list[i] = sct
	}
	return list, nil
}

// dupeSA returns a non-error to GetCertificate in order to simulate a request
// to issue a final certificate with a duplicate serial.
type dupeSA struct {
	mockSA
}

func (m *dupeSA) GetCertificate(ctx context.Context, req *sapb.Serial, _ ...grpc.CallOption) (*corepb.Certificate, error) {
	return nil, nil
}

// getCertErrorSA always returns an error for GetCertificate
type getCertErrorSA struct {
	mockSA
}

func (m *getCertErrorSA) GetCertificate(ctx context.Context, req *sapb.Serial, _ ...grpc.CallOption) (*corepb.Certificate, error) {
	return nil, fmt.Errorf("i don't like it")
}

func TestIssueCertificateForPrecertificateDuplicateSerial(t *testing.T) {
	t.Parallel()
	testCtx := setup(t)
	sa := &dupeSA{}
	ca, err := NewCertificateAuthorityImpl(
		sa,
		testCtx.pa,
		testCtx.boulderIssuers,
		testCtx.defaultCertProfileName,
		testCtx.certProfiles,
		testCtx.lints,
		nil,
		testCtx.certExpiry,
		testCtx.certBackdate,
		testCtx.serialPrefix,
		testCtx.maxNames,
		testCtx.keyPolicy,
		testCtx.logger,
		testCtx.metrics,
		testCtx.fc)
	test.AssertNotError(t, err, "Failed to create CA")

	sctBytes, err := makeSCTs()
	if err != nil {
		t.Fatal(err)
	}

	selectedProfile := ca.certProfiles.defaultName
	certProfile, ok := ca.certProfiles.profileByName[selectedProfile]
	test.Assert(t, ok, "Certificate profile was expected to exist")

	issueReq := capb.IssueCertificateRequest{Csr: CNandSANCSR, RegistrationID: arbitraryRegID, OrderID: 0}
	precert, err := ca.IssuePrecertificate(ctx, &issueReq)
	test.AssertNotError(t, err, "Failed to issue precert")
	test.AssertMetricWithLabelsEquals(t, ca.metrics.signatureCount, prometheus.Labels{"purpose": "precertificate", "status": "success"}, 1)
	_, err = ca.IssueCertificateForPrecertificate(ctx, &capb.IssueCertificateForPrecertificateRequest{
		DER:             precert.DER,
		SCTs:            sctBytes,
		RegistrationID:  arbitraryRegID,
		OrderID:         0,
		CertProfileHash: certProfile.hash[:],
	})
	if err == nil {
		t.Error("Expected error issuing duplicate serial but got none.")
	}
	if !strings.Contains(err.Error(), "issuance of duplicate final certificate requested") {
		t.Errorf("Wrong type of error issuing duplicate serial. Expected 'issuance of duplicate', got '%s'", err)
	}
	// The success metric doesn't increase when a duplicate certificate issuance
	// is attempted.
	test.AssertMetricWithLabelsEquals(t, ca.metrics.signatureCount, prometheus.Labels{"purpose": "certificate", "status": "success"}, 0)

	// Now check what happens if there is an error (e.g. timeout) while checking
	// for the duplicate.
	errorsa := &getCertErrorSA{}
	errorca, err := NewCertificateAuthorityImpl(
		errorsa,
		testCtx.pa,
		testCtx.boulderIssuers,
		testCtx.defaultCertProfileName,
		testCtx.certProfiles,
		testCtx.lints,
		nil,
		testCtx.certExpiry,
		testCtx.certBackdate,
		testCtx.serialPrefix,
		testCtx.maxNames,
		testCtx.keyPolicy,
		testCtx.logger,
		testCtx.metrics,
		testCtx.fc)
	test.AssertNotError(t, err, "Failed to create CA")

	_, err = errorca.IssueCertificateForPrecertificate(ctx, &capb.IssueCertificateForPrecertificateRequest{
		DER:             precert.DER,
		SCTs:            sctBytes,
		RegistrationID:  arbitraryRegID,
		OrderID:         0,
		CertProfileHash: certProfile.hash[:],
	})
	if err == nil {
		t.Fatal("Expected error issuing duplicate serial but got none.")
	}
	if !strings.Contains(err.Error(), "error checking for duplicate") {
		t.Fatalf("Wrong type of error issuing duplicate serial. Expected 'error checking for duplicate', got '%s'", err)
	}
	// The success metric doesn't increase when a duplicate certificate issuance
	// is attempted.
	test.AssertMetricWithLabelsEquals(t, ca.metrics.signatureCount, prometheus.Labels{"purpose": "certificate", "status": "success"}, 0)
}

func TestGenerateSKID(t *testing.T) {
	t.Parallel()
	key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	test.AssertNotError(t, err, "Error generating key")

	sha256skid, err := generateSKID(key.Public())
	test.AssertNotError(t, err, "Error generating SKID")
	test.AssertEquals(t, len(sha256skid), 20)
	test.AssertEquals(t, cap(sha256skid), 20)
	features.Reset()
}

func TestVerifyTBSCertIsDeterministic(t *testing.T) {
	t.Parallel()

	// Create first keypair and cert
	testKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	test.AssertNotError(t, err, "unable to generate ECDSA private key")
	template := &x509.Certificate{
		NotAfter:     time.Now().Add(1 * time.Hour),
		DNSNames:     []string{"example.com"},
		SerialNumber: big.NewInt(1),
	}
	certDer1, err := x509.CreateCertificate(rand.Reader, template, template, &testKey.PublicKey, testKey)
	test.AssertNotError(t, err, "unable to create certificate")

	// Create second keypair and cert
	testKey2, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	test.AssertNotError(t, err, "unable to generate ECDSA private key")
	template2 := &x509.Certificate{
		NotAfter:     time.Now().Add(2 * time.Hour),
		DNSNames:     []string{"example.net"},
		SerialNumber: big.NewInt(2),
	}
	certDer2, err := x509.CreateCertificate(rand.Reader, template2, template2, &testKey2.PublicKey, testKey2)
	test.AssertNotError(t, err, "unable to create certificate")

	testCases := []struct {
		name          string
		lintCertBytes []byte
		leafCertBytes []byte
		errorSubstr   string
	}{
		{
			name:          "Both nil",
			lintCertBytes: nil,
			leafCertBytes: nil,
			errorSubstr:   "were nil",
		},
		{
			name:          "Missing a value, invalid input",
			lintCertBytes: nil,
			leafCertBytes: []byte{0x6, 0x6, 0x6},
			errorSubstr:   "were nil",
		},
		{
			name:          "Missing a value, valid input",
			lintCertBytes: nil,
			leafCertBytes: certDer1,
			errorSubstr:   "were nil",
		},
		{
			name:          "Mismatched bytes, invalid input",
			lintCertBytes: []byte{0x6, 0x6, 0x6},
			leafCertBytes: []byte{0x1, 0x2, 0x3},
			errorSubstr:   "malformed certificate",
		},
		{
			name:          "Mismatched bytes, invalider input",
			lintCertBytes: certDer1,
			leafCertBytes: []byte{0x1, 0x2, 0x3},
			errorSubstr:   "malformed certificate",
		},
		{
			// This case is an example of when a linting cert's DER bytes are
			// mismatched compared to then precert or final cert created from
			// that linting cert's DER bytes.
			name:          "Mismatched bytes, valid input",
			lintCertBytes: certDer1,
			leafCertBytes: certDer2,
			errorSubstr:   "mismatch between",
		},
		{
			// Take this with a grain of salt since this test is not actually
			// creating a linting certificate and performing two
			// x509.CreateCertificate() calls like
			// ca.IssueCertificateForPrecertificate and
			// ca.issuePrecertificateInner do. However, we're still going to
			// verify the equality.
			name:          "Valid",
			lintCertBytes: certDer1,
			leafCertBytes: certDer1,
		},
	}

	for _, testCase := range testCases {
		// TODO(#7454) Remove this rebinding
		testCase := testCase
		t.Run(testCase.name, func(t *testing.T) {
			t.Parallel()
			err := tbsCertIsDeterministic(testCase.lintCertBytes, testCase.leafCertBytes)
			if testCase.errorSubstr != "" {
				test.AssertError(t, err, "your lack of errors is disturbing")
				test.AssertContains(t, err.Error(), testCase.errorSubstr)
			} else {
				test.AssertNotError(t, err, "unexpected error")
			}
		})
	}
}
