package ca

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

	"github.com/beeker1121/goque"
	cfsslConfig "github.com/cloudflare/cfssl/config"
	"github.com/cloudflare/cfssl/helpers"
	"github.com/cloudflare/cfssl/signer"
	"github.com/cloudflare/cfssl/signer/local"
	ct "github.com/google/certificate-transparency-go"
	cttls "github.com/google/certificate-transparency-go/tls"
	"github.com/jmhodges/clock"
	"github.com/prometheus/client_golang/prometheus"
	zlint "github.com/zmap/zlint/v2/lint"
	"golang.org/x/crypto/ocsp"

	capb "github.com/letsencrypt/boulder/ca/proto"
	"github.com/letsencrypt/boulder/cmd"
	"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/lint"
	blog "github.com/letsencrypt/boulder/log"
	"github.com/letsencrypt/boulder/metrics"
	"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 extensionRequest attributes for *two* must-staple extensions
	DuplicateMustStapleCSR = mustRead("./testdata/duplicate_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}

	// The "certificate-for-precertificate" tests use the precertificate from a
	// previous "precertificate" test, in order to verify that the CA is
	// stateless with respect to these two operations, since a separate CA
	// object instance will be used for generating each. Consequently, the
	// "precertificate" tests must be before the "certificate-for-precertificate"
	// tests in this list, and we cannot run these sub-tests concurrently.
	//
	// In order to test the case where the same CA object is used for issuing
	// both the precertificate and the certificate, we'd need to contort
	// |TestIssueCertificate| quite a bit, and since it isn't clear that that
	// would be useful, we've avoided adding that case, at least for now.
	issuanceModes = []IssuanceMode{
		{name: "precertificate", issueCertificateForPrecertificate: false},
		{name: "certificate-for-precertificate", issueCertificateForPrecertificate: true},
	}
)

const arbitraryRegID int64 = 1001

// CFSSL config
const rsaProfileName = "rsaEE"
const ecdsaProfileName = "ecdsaEE"
const caKeyFile = "../test/test-ca.key"
const caCertFile = "../test/test-ca.pem"
const caCertFile2 = "../test/test-ca2.pem"

func mustRead(path string) []byte {
	b, err := ioutil.ReadFile(path)
	if err != nil {
		panic(fmt.Sprintf("unable to read %#v: %s", path, err))
	}
	return b
}

type testCtx struct {
	pa                core.PolicyAuthority
	certExpiry        time.Duration
	certBackdate      time.Duration
	serialPrefix      int
	maxNames          int
	ocspLifetime      time.Duration
	cfsslProfiles     cfsslConfig.Config
	cfsslRSAProfile   string
	cfsslECDSAProfile string
	cfsslIssuers      []Issuer
	boulderIssuers    []*issuance.Issuer
	keyPolicy         goodkey.KeyPolicy
	fc                clock.FakeClock
	stats             prometheus.Registerer
	logger            *blog.Mock
}

type mockSA struct {
	certificate core.Certificate
}

func (m *mockSA) AddCertificate(ctx context.Context, der []byte, _ int64, _ []byte, _ *time.Time) (string, error) {
	m.certificate.DER = der
	return "", nil
}

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

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

func (m *mockSA) GetCertificate(ctx context.Context, serial string) (core.Certificate, error) {
	return core.Certificate{}, berrors.NotFoundError("cannot find the cert")
}

var caKey crypto.Signer
var caCert *issuance.Certificate
var caCert2 *issuance.Certificate
var ctx = context.Background()

func init() {
	var err error
	caKey, err = helpers.ParsePrivateKeyPEM(mustRead(caKeyFile))
	if err != nil {
		panic(fmt.Sprintf("Unable to parse %s: %s", caKeyFile, err))
	}
	caCert, err = issuance.LoadCertificate(caCertFile)
	if err != nil {
		panic(fmt.Sprintf("Unable to parse %s: %s", caCertFile, err))
	}
	caCert2, err = issuance.LoadCertificate(caCertFile2)
	if err != nil {
		panic(fmt.Sprintf("Unable to parse %s: %s", caCertFile2, err))
	}
}

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

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

	allowedExtensions := []cfsslConfig.OID{
		cfsslConfig.OID(oidTLSFeature),
		cfsslConfig.OID(OIDExtensionCTPoison),
	}

	cfsslProfiles := cfsslConfig.Config{
		Signing: &cfsslConfig.Signing{
			Profiles: map[string]*cfsslConfig.SigningProfile{
				rsaProfileName: {
					Usage:     []string{"digital signature", "key encipherment", "server auth"},
					IssuerURL: []string{"http://not-example.com/issuer-url"},
					OCSP:      "http://not-example.com/ocsp",
					CRL:       "http://not-example.com/crl",

					Policies: []cfsslConfig.CertificatePolicy{
						{
							ID: cfsslConfig.OID(asn1.ObjectIdentifier{2, 23, 140, 1, 2, 1}),
						},
					},
					ExpiryString: "8760h",
					Backdate:     time.Hour,
					CSRWhitelist: &cfsslConfig.CSRWhitelist{
						PublicKeyAlgorithm: true,
						PublicKey:          true,
						SignatureAlgorithm: true,
					},
					ClientProvidesSerialNumbers: true,
					AllowedExtensions:           allowedExtensions,
				},
				ecdsaProfileName: {
					Usage:     []string{"digital signature", "server auth"},
					IssuerURL: []string{"http://not-example.com/issuer-url"},
					OCSP:      "http://not-example.com/ocsp",
					CRL:       "http://not-example.com/crl",

					Policies: []cfsslConfig.CertificatePolicy{
						{
							ID: cfsslConfig.OID(asn1.ObjectIdentifier{2, 23, 140, 1, 2, 1}),
						},
					},
					ExpiryString: "8760h",
					Backdate:     time.Hour,
					CSRWhitelist: &cfsslConfig.CSRWhitelist{
						PublicKeyAlgorithm: true,
						PublicKey:          true,
						SignatureAlgorithm: true,
					},
					ClientProvidesSerialNumbers: true,
					AllowedExtensions:           allowedExtensions,
				},
			},
			Default: &cfsslConfig.SigningProfile{
				ExpiryString: "8760h",
			},
		},
	}
	cfsslIssuers := []Issuer{{caKey, caCert}}

	boulderProfile := func(rsa, ecdsa bool) *issuance.Profile {
		res, _ := issuance.NewProfile(
			issuance.ProfileConfig{
				AllowMustStaple: true,
				AllowCTPoison:   true,
				AllowSCTList:    true,
				AllowCommonName: true,
				Policies: []issuance.PolicyInformation{
					{OID: "2.23.140.1.2.1"},
				},
				MaxValidityPeriod:   cmd.ConfigDuration{Duration: time.Hour * 8760},
				MaxValidityBackdate: cmd.ConfigDuration{Duration: time.Hour},
			},
			issuance.IssuerConfig{
				UseForECDSALeaves: ecdsa,
				UseForRSALeaves:   rsa,
				IssuerURL:         "http://not-example.com/issuer-url",
				OCSPURL:           "http://not-example.com/ocsp",
				CRLURL:            "http://not-example.com/crl",
			},
		)
		return res
	}
	boulderLinter, _ := lint.NewLinter(caKey, nil)
	boulderIssuers := []*issuance.Issuer{
		// Must list ECDSA-only issuer first, so it is the default for ECDSA.
		{
			Cert:    caCert2,
			Signer:  caKey,
			Profile: boulderProfile(false, true),
			Linter:  boulderLinter,
			Clk:     fc,
		},
		{
			Cert:    caCert,
			Signer:  caKey,
			Profile: boulderProfile(true, true),
			Linter:  boulderLinter,
			Clk:     fc,
		},
	}

	keyPolicy := goodkey.KeyPolicy{
		AllowRSA:           true,
		AllowECDSANISTP256: true,
		AllowECDSANISTP384: true,
	}

	return &testCtx{
		pa:                pa,
		certExpiry:        8760 * time.Hour,
		certBackdate:      time.Hour,
		serialPrefix:      17,
		maxNames:          2,
		ocspLifetime:      time.Hour,
		cfsslProfiles:     cfsslProfiles,
		cfsslRSAProfile:   rsaProfileName,
		cfsslECDSAProfile: ecdsaProfileName,
		cfsslIssuers:      cfsslIssuers,
		boulderIssuers:    boulderIssuers,
		keyPolicy:         keyPolicy,
		fc:                fc,
		stats:             metrics.NoopRegisterer,
		logger:            blog.NewMock(),
	}
}

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

	_, err := NewCertificateAuthorityImpl(
		nil,
		nil,
		testCtx.cfsslProfiles,
		testCtx.cfsslRSAProfile,
		testCtx.cfsslECDSAProfile,
		testCtx.cfsslIssuers,
		nil,
		nil,
		testCtx.certExpiry,
		testCtx.certBackdate,
		0,
		testCtx.maxNames,
		testCtx.ocspLifetime,
		testCtx.keyPolicy,
		nil,
		0,
		time.Second,
		testCtx.logger,
		testCtx.stats,
		testCtx.fc)
	test.AssertError(t, err, "CA should have failed with no SerialPrefix")
}

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

type IssuanceMode struct {
	name                              string
	issueCertificateForPrecertificate bool
}

func TestIssuePrecertificate(t *testing.T) {
	for _, nonCFSSL := range []bool{true, false} {
		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},
			{"MustStapleDuplicate", DuplicateMustStapleCSR, issueCertificateSubTestMustStaple},
			{"UnknownExtension", UnsupportedExtensionCSR, issueCertificateSubTestUnknownExtension},
			{"CTPoisonExtension", CTPoisonExtensionCSR, issueCertificateSubTestCTPoisonExtension},
			{"CTPoisonExtensionEmpty", CTPoisonExtensionEmptyCSR, issueCertificateSubTestCTPoisonExtension},
		}

		for _, testCase := range testCases {
			// The loop through |issuanceModes| must be inside the loop through
			// |testCases| because the "certificate-for-precertificate" tests use
			// the precertificates previously generated from the preceding
			// "precertificate" test. See also the comment above |issuanceModes|.
			for _, mode := range issuanceModes {
				ca, sa := issueCertificateSubTestSetup(t, nonCFSSL)

				t.Run(fmt.Sprintf("%s - %s (using boulder signer: %t)", mode.name, testCase.name, nonCFSSL), func(t *testing.T) {
					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.AssertEquals(t, true, poisonExtension != nil)
					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,
						mode:    mode,
						certDER: certDER,
						cert:    cert,
					}

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

func issueCertificateSubTestSetup(t *testing.T, boulderIssuer bool) (*CertificateAuthorityImpl, *mockSA) {
	testCtx := setup(t)
	sa := &mockSA{}
	var cfsslIssuers []Issuer
	var boulderIssuers []*issuance.Issuer
	if boulderIssuer {
		boulderIssuers = testCtx.boulderIssuers
		_ = features.Set(map[string]bool{"NonCFSSLSigner": true})
	} else {
		cfsslIssuers = testCtx.cfsslIssuers
	}
	ca, err := NewCertificateAuthorityImpl(
		sa,
		testCtx.pa,
		testCtx.cfsslProfiles,
		testCtx.cfsslRSAProfile,
		testCtx.cfsslECDSAProfile,
		cfsslIssuers,
		boulderIssuers,
		nil,
		testCtx.certExpiry,
		testCtx.certBackdate,
		testCtx.serialPrefix,
		testCtx.maxNames,
		testCtx.ocspLifetime,
		testCtx.keyPolicy,
		nil,
		0,
		time.Second,
		testCtx.logger,
		testCtx.stats,
		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, i.cert.NotBefore.Add(i.ca.validityPeriod))
}

// Test issuing when multiple issuers are present.
func TestMultipleIssuers(t *testing.T) {
	testCtx := setup(t)
	// Load multiple issuers, and ensure the first one in the list is used.
	newIssuerCert, err := issuance.LoadCertificate("../test/test-ca2.pem")
	test.AssertNotError(t, err, "Failed to load new cert")
	newIssuers := []Issuer{
		{
			Signer: caKey,
			// newIssuerCert is first, so it will be the default.
			Cert: newIssuerCert,
		}, {
			Signer: caKey,
			Cert:   caCert,
		},
	}
	sa := &mockSA{}
	ca, err := NewCertificateAuthorityImpl(
		sa,
		testCtx.pa,
		testCtx.cfsslProfiles,
		testCtx.cfsslRSAProfile,
		testCtx.cfsslECDSAProfile,
		newIssuers,
		nil,
		nil,
		testCtx.certExpiry,
		testCtx.certBackdate,
		testCtx.serialPrefix,
		testCtx.maxNames,
		testCtx.ocspLifetime,
		testCtx.keyPolicy,
		nil,
		0,
		time.Second,
		testCtx.logger,
		testCtx.stats,
		testCtx.fc)
	test.AssertNotError(t, err, "Failed to remake CA")

	issuedCert, err := ca.IssuePrecertificate(ctx, &capb.IssueCertificateRequest{Csr: CNandSANCSR, RegistrationID: arbitraryRegID})
	test.AssertNotError(t, err, "Failed to issue certificate")

	cert, err := x509.ParseCertificate(issuedCert.DER)
	test.AssertNotError(t, err, "Certificate failed to parse")
	// Verify cert was signed by newIssuerCert, not caCert.
	err = cert.CheckSignatureFrom(newIssuerCert.Certificate)
	test.AssertNotError(t, err, "Certificate failed signature validation")
}

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

	// With allowlist containing arbitraryRegID, issuance should come from ECDSA issuer.
	ca, _ := issueCertificateSubTestSetup(t, true)
	ca.ecdsaAllowedRegIDs[arbitraryRegID] = true
	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.AssertByteEquals(t, cert.RawIssuer, caCert2.RawSubject)

	// With allowlist not containing arbitraryRegID, issuance should fall back to RSA issuer.
	ca, _ = issueCertificateSubTestSetup(t, true)
	ca.ecdsaAllowedRegIDs[2002] = true
	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.AssertByteEquals(t, cert.RawIssuer, caCert.RawSubject)

	// With empty allowlist but ECDSAForAll enabled, issuance should come from ECDSA issuer.
	ca, _ = issueCertificateSubTestSetup(t, true)
	_ = features.Set(map[string]bool{"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.AssertByteEquals(t, cert.RawIssuer, caCert2.RawSubject)
}

func TestOCSP(t *testing.T) {
	testCtx := setup(t)
	sa := &mockSA{}
	ca, err := NewCertificateAuthorityImpl(
		sa,
		testCtx.pa,
		testCtx.cfsslProfiles,
		testCtx.cfsslRSAProfile,
		testCtx.cfsslECDSAProfile,
		testCtx.cfsslIssuers,
		nil,
		nil,
		testCtx.certExpiry,
		testCtx.certBackdate,
		testCtx.serialPrefix,
		testCtx.maxNames,
		testCtx.ocspLifetime,
		testCtx.keyPolicy,
		nil,
		0,
		time.Second,
		testCtx.logger,
		testCtx.stats,
		testCtx.fc)
	test.AssertNotError(t, err, "Failed to create CA")

	issueReq := capb.IssueCertificateRequest{Csr: CNandSANCSR, RegistrationID: arbitraryRegID}

	cert, err := ca.IssuePrecertificate(ctx, &issueReq)
	test.AssertNotError(t, err, "Failed to issue")
	parsedCert, err := x509.ParseCertificate(cert.DER)
	test.AssertNotError(t, err, "Failed to parse cert")
	status := string(core.OCSPStatusGood)
	ocspResp, err := ca.GenerateOCSP(ctx, &capb.GenerateOCSPRequest{
		CertDER: cert.DER,
		Status:  status,
	})
	test.AssertNotError(t, err, "Failed to generate OCSP")
	parsed, err := ocsp.ParseResponse(ocspResp.Response, caCert.Certificate)
	test.AssertNotError(t, err, "Failed to parse validate OCSP")
	test.AssertEquals(t, parsed.Status, 0)
	test.AssertEquals(t, parsed.RevocationReason, 0)
	test.AssertEquals(t, parsed.SerialNumber.Cmp(parsedCert.SerialNumber), 0)

	// Test that signatures are checked.
	_, err = ca.GenerateOCSP(ctx, &capb.GenerateOCSPRequest{
		CertDER: append(cert.DER, byte(0)),
		Status:  status,
	})
	test.AssertError(t, err, "Generated OCSP for cert with bad signature")

	// Load multiple issuers, including the old issuer, and ensure OCSP is still
	// signed correctly.
	newIssuerCert, err := issuance.LoadCertificate("../test/test-ca2.pem")
	test.AssertNotError(t, err, "Failed to load new cert")
	newIssuers := []Issuer{
		{
			Signer: caKey,
			// newIssuerCert is first, so it will be the default.
			Cert: newIssuerCert,
		}, {
			Signer: caKey,
			Cert:   caCert,
		},
	}
	ca, err = NewCertificateAuthorityImpl(
		sa,
		testCtx.pa,
		testCtx.cfsslProfiles,
		testCtx.cfsslRSAProfile,
		testCtx.cfsslECDSAProfile,
		newIssuers,
		nil,
		nil,
		testCtx.certExpiry,
		testCtx.certBackdate,
		testCtx.serialPrefix,
		testCtx.maxNames,
		testCtx.ocspLifetime,
		testCtx.keyPolicy,
		nil,
		0,
		time.Second,
		testCtx.logger,
		testCtx.stats,
		testCtx.fc)
	test.AssertNotError(t, err, "Failed to remake CA")

	// Now issue a new precert, signed by newIssuerCert
	newCert, err := ca.IssuePrecertificate(ctx, &issueReq)
	test.AssertNotError(t, err, "Failed to issue newCert")
	parsedNewCert, err := x509.ParseCertificate(newCert.DER)
	test.AssertNotError(t, err, "Failed to parse newCert")

	err = parsedNewCert.CheckSignatureFrom(newIssuerCert.Certificate)
	t.Logf("check sig: %s", err)

	// ocspResp2 is a second OCSP response for `cert` (issued by caCert), and
	// should be signed by caCert.
	ocspResp2, err := ca.GenerateOCSP(ctx, &capb.GenerateOCSPRequest{
		CertDER: append([]byte(nil), cert.DER...),
		Status:  status,
	})
	test.AssertNotError(t, err, "Failed to sign second OCSP response")
	_, err = ocsp.ParseResponse(ocspResp2.Response, caCert.Certificate)
	test.AssertNotError(t, err, "Failed to parse / validate second OCSP response")

	// newCertOcspResp is an OCSP response for `newCert` (issued by newIssuer),
	// and should be signed by newIssuer.
	newCertOcspResp, err := ca.GenerateOCSP(ctx, &capb.GenerateOCSPRequest{
		CertDER: newCert.DER,
		Status:  status,
	})
	test.AssertNotError(t, err, "Failed to generate OCSP")
	parsedNewCertOcspResp, err := ocsp.ParseResponse(newCertOcspResp.Response, newIssuerCert.Certificate)
	test.AssertNotError(t, err, "Failed to parse / validate OCSP for newCert")
	test.AssertEquals(t, parsedNewCertOcspResp.Status, 0)
	test.AssertEquals(t, parsedNewCertOcspResp.RevocationReason, 0)
	test.AssertEquals(t, parsedNewCertOcspResp.SerialNumber.Cmp(parsedNewCert.SerialNumber), 0)
}

func TestInvalidCSRs(t *testing.T) {
	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},

		// 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},

		// CSR generated by Go:
		// * Random public key
		// * CN = not-example.com
		// * Includes an extensionRequest attribute for an empty TLS Feature extension
		{"TLSFeatureUnknown", "./testdata/tls_feature_unknown.der.csr", issueCertificateSubTestTLSFeatureUnknown, "Issued a certificate based on a CSR with an empty TLS feature extension.", berrors.Malformed},
	}

	for _, testCase := range testCases {
		testCtx := setup(t)
		sa := &mockSA{}
		ca, err := NewCertificateAuthorityImpl(
			sa,
			testCtx.pa,
			testCtx.cfsslProfiles,
			testCtx.cfsslRSAProfile,
			testCtx.cfsslECDSAProfile,
			testCtx.cfsslIssuers,
			nil,
			nil,
			testCtx.certExpiry,
			testCtx.certBackdate,
			testCtx.serialPrefix,
			testCtx.maxNames,
			testCtx.ocspLifetime,
			testCtx.keyPolicy,
			nil,
			0,
			time.Second,
			testCtx.logger,
			testCtx.stats,
			testCtx.fc)
		test.AssertNotError(t, err, "Failed to create CA")

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

			test.AssertErrorIs(t, err, testCase.errorType)
			test.AssertEquals(t, signatureCountByPurpose("cert", ca.signatureCount), 0)

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

func TestRejectValidityTooLong(t *testing.T) {
	testCtx := setup(t)
	sa := &mockSA{}
	ca, err := NewCertificateAuthorityImpl(
		sa,
		testCtx.pa,
		testCtx.cfsslProfiles,
		testCtx.cfsslRSAProfile,
		testCtx.cfsslECDSAProfile,
		testCtx.cfsslIssuers,
		nil,
		nil,
		testCtx.certExpiry,
		testCtx.certBackdate,
		testCtx.serialPrefix,
		testCtx.maxNames,
		testCtx.ocspLifetime,
		testCtx.keyPolicy,
		nil,
		0,
		time.Second,
		testCtx.logger,
		testCtx.stats,
		testCtx.fc)
	test.AssertNotError(t, err, "Failed to create CA")

	// This time is a few minutes before the notAfter in testdata/ca_cert.pem
	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 TestSingleAIAEnforcement(t *testing.T) {
	pa, err := policy.New(nil)
	test.AssertNotError(t, err, "Couldn't create PA")

	_, err = NewCertificateAuthorityImpl(
		&mockSA{},
		pa,
		cfsslConfig.Config{
			Signing: &cfsslConfig.Signing{
				Profiles: map[string]*cfsslConfig.SigningProfile{
					rsaProfileName: {
						IssuerURL: []string{"http://not-example.com/issuer-url", "bad"},
						Usage:     []string{"digital signature", "key encipherment", "server auth"},
						OCSP:      "http://not-example.com/ocsp",
						CRL:       "http://not-example.com/crl",
						Policies: []cfsslConfig.CertificatePolicy{
							{
								ID: cfsslConfig.OID(asn1.ObjectIdentifier{2, 23, 140, 1, 2, 1}),
							},
						},
						ExpiryString: "8760h",
						Backdate:     time.Hour,
						CSRWhitelist: &cfsslConfig.CSRWhitelist{
							PublicKeyAlgorithm: true,
							PublicKey:          true,
							SignatureAlgorithm: true,
						},
						ClientProvidesSerialNumbers: true,
					},
				},
			},
		},
		rsaProfileName,
		ecdsaProfileName,
		nil,
		nil,
		nil,
		8760*time.Hour,
		time.Hour,
		1,
		1,
		time.Second,
		goodkey.KeyPolicy{},
		nil,
		0,
		time.Second,
		&blog.Mock{},
		metrics.NoopRegisterer,
		clock.New(),
	)
	test.AssertError(t, err, "NewCertificateAuthorityImpl allowed a profile with multiple issuer_urls")
	test.AssertEquals(t, err.Error(), "only one issuer_url supported")
}

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}
	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) {
	// a TLS feature extension should put a must-staple extension into the cert. Even
	// if there are multiple TLS Feature extensions, only one extension should be included.
	test.AssertEquals(t, test.CountCounterVec(csrExtensionCategory, csrExtensionTLSFeature, i.ca.csrExtensionCount), 1)
	test.AssertEquals(t, test.CountCounterVec(csrExtensionCategory, csrExtensionTLSFeatureInvalid, i.ca.csrExtensionCount), 0)
	test.AssertEquals(t, signatureCountByPurpose("precertificate", i.ca.signatureCount), 1)
	test.AssertEquals(t, countMustStaple(t, i.cert), 1)
}

func issueCertificateSubTestTLSFeatureUnknown(t *testing.T, ca *CertificateAuthorityImpl, _ *mockSA) {
	test.AssertEquals(t, test.CountCounterVec(csrExtensionCategory, csrExtensionTLSFeature, ca.csrExtensionCount), 1)
	test.AssertEquals(t, test.CountCounterVec(csrExtensionCategory, csrExtensionTLSFeatureInvalid, ca.csrExtensionCount), 1)
}

func issueCertificateSubTestUnknownExtension(t *testing.T, i *TestCertificateIssuance) {
	// Unsupported extensions in the CSR should be silently ignored.
	test.AssertEquals(t, test.CountCounterVec(csrExtensionCategory, csrExtensionOther, i.ca.csrExtensionCount), 1)
	test.AssertEquals(t, signatureCountByPurpose("precertificate", i.ca.signatureCount), 1)

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

func issueCertificateSubTestCTPoisonExtension(t *testing.T, i *TestCertificateIssuance) {
	// The CT poison extension in the CSR should be silently ignored like an
	// unknown extension, whether it has a valid or invalid value. The check
	// for whether or not the poison extension is present in the issued
	// certificate/precertificate is done in the caller.
	test.AssertEquals(t, test.CountCounterVec(csrExtensionCategory, csrExtensionOther, i.ca.csrExtensionCount), 1)
	test.AssertEquals(t, signatureCountByPurpose("precertificate", i.ca.signatureCount), 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 signatureCountByPurpose(signatureType string, signatureCount *prometheus.CounterVec) int {
	return test.CountCounterVec("purpose", signatureType, signatureCount)
}

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) {
	testCtx := setup(t)
	sa := &mockSA{}
	for _, nonCFSSL := range []bool{true, false} {
		_ = features.Set(map[string]bool{"NonCFSSLSigner": nonCFSSL})
		ca, err := NewCertificateAuthorityImpl(
			sa,
			testCtx.pa,
			testCtx.cfsslProfiles,
			testCtx.cfsslRSAProfile,
			testCtx.cfsslECDSAProfile,
			testCtx.cfsslIssuers,
			testCtx.boulderIssuers,
			nil,
			testCtx.certExpiry,
			testCtx.certBackdate,
			testCtx.serialPrefix,
			testCtx.maxNames,
			testCtx.ocspLifetime,
			testCtx.keyPolicy,
			nil,
			0,
			time.Second,
			testCtx.logger,
			testCtx.stats,
			testCtx.fc)
		test.AssertNotError(t, err, "Failed to create CA")

		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")

		// Check for poison extension
		poisoned := false
		for _, ext := range parsedPrecert.Extensions {
			if ext.Id.Equal(signer.CTPoisonOID) && ext.Critical {
				poisoned = true
			}
		}
		test.Assert(t, poisoned, "returned precert not poisoned")

		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,
		})
		test.AssertNotError(t, err, "Failed to issue cert from precert")
		parsedCert, err := x509.ParseCertificate(cert.Der)
		test.AssertNotError(t, err, "Failed to parse cert")

		// Check for SCT list extension
		list := false
		for _, ext := range parsedCert.Extensions {
			if ext.Id.Equal(signer.SCTListOID) && !ext.Critical {
				list = true
				var rawValue []byte
				_, err = asn1.Unmarshal(ext.Value, &rawValue)
				test.AssertNotError(t, err, "Failed to unmarshal extension value")
				sctList, err := helpers.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)))
			}
		}
		test.Assert(t, list, "returned cert doesn't contain SCT list")
	}
}

// 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, serial string) (core.Certificate, error) {
	return core.Certificate{}, nil
}

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

func (m *getCertErrorSA) GetCertificate(ctx context.Context, serial string) (core.Certificate, error) {
	return core.Certificate{}, fmt.Errorf("i don't like it")
}

func TestIssueCertificateForPrecertificateDuplicateSerial(t *testing.T) {
	testCtx := setup(t)
	sa := &dupeSA{}
	ca, err := NewCertificateAuthorityImpl(
		sa,
		testCtx.pa,
		testCtx.cfsslProfiles,
		testCtx.cfsslRSAProfile,
		testCtx.cfsslECDSAProfile,
		testCtx.cfsslIssuers,
		nil,
		nil,
		testCtx.certExpiry,
		testCtx.certBackdate,
		testCtx.serialPrefix,
		testCtx.maxNames,
		testCtx.ocspLifetime,
		testCtx.keyPolicy,
		nil,
		0,
		time.Second,
		testCtx.logger,
		testCtx.stats,
		testCtx.fc)
	test.AssertNotError(t, err, "Failed to create CA")

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

	issueReq := capb.IssueCertificateRequest{Csr: CNandSANCSR, RegistrationID: arbitraryRegID, OrderID: 0}
	precert, err := ca.IssuePrecertificate(ctx, &issueReq)
	test.AssertNotError(t, err, "Failed to issue precert")
	_, err = ca.IssueCertificateForPrecertificate(ctx, &capb.IssueCertificateForPrecertificateRequest{
		DER:            precert.DER,
		SCTs:           sctBytes,
		RegistrationID: arbitraryRegID,
		OrderID:        0,
	})
	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)
	}

	// 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.cfsslProfiles,
		testCtx.cfsslRSAProfile,
		testCtx.cfsslECDSAProfile,
		testCtx.cfsslIssuers,
		nil,
		nil,
		testCtx.certExpiry,
		testCtx.certBackdate,
		testCtx.serialPrefix,
		testCtx.maxNames,
		testCtx.ocspLifetime,
		testCtx.keyPolicy,
		nil,
		0,
		time.Second,
		testCtx.logger,
		testCtx.stats,
		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,
	})
	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)
	}
}

type queueSA struct {
	mockSA

	fail      bool
	duplicate bool

	issued        *time.Time
	issuedPrecert *time.Time
}

func (qsa *queueSA) AddCertificate(_ context.Context, _ []byte, _ int64, _ []byte, issued *time.Time) (string, error) {
	if qsa.fail {
		return "", errors.New("bad")
	} else if qsa.duplicate {
		return "", berrors.DuplicateError("is a dupe")
	}
	qsa.issued = issued
	return "", nil
}

func (qsa *queueSA) AddPrecertificate(ctx context.Context, req *sapb.AddCertificateRequest) (*corepb.Empty, error) {
	if qsa.fail {
		return nil, errors.New("bad")
	} else if qsa.duplicate {
		return nil, berrors.DuplicateError("is a dupe")
	}
	issued := time.Unix(0, req.Issued)
	qsa.issuedPrecert = &issued
	return nil, nil
}

// TestPrecertOrphanQueue tests that IssuePrecertificate writes precertificates
// to the orphan queue if storage fails, and that `integrateOrphan` later
// successfully writes those precertificates to the database. To do this, it
// uses the `queueSA` mock, which allows us to flip on and off a "fail" bit that
// decides whether it errors in response to storage requests.
func TestPrecertOrphanQueue(t *testing.T) {
	tmpDir, err := ioutil.TempDir("", "orphan-queue-tmp")
	defer os.Remove(tmpDir)
	test.AssertNotError(t, err, "Failed to create temp directory")
	orphanQueue, err := goque.OpenQueue(tmpDir)
	test.AssertNotError(t, err, "Failed to open orphaned certificate queue")

	qsa := &queueSA{fail: true}
	testCtx := setup(t)
	fakeNow := time.Date(2019, 9, 20, 0, 0, 0, 0, time.UTC)
	testCtx.fc.Set(fakeNow)
	ca, err := NewCertificateAuthorityImpl(
		qsa,
		testCtx.pa,
		testCtx.cfsslProfiles,
		testCtx.cfsslRSAProfile,
		testCtx.cfsslECDSAProfile,
		testCtx.cfsslIssuers,
		nil,
		nil,
		testCtx.certExpiry,
		testCtx.certBackdate,
		testCtx.serialPrefix,
		testCtx.maxNames,
		testCtx.ocspLifetime,
		testCtx.keyPolicy,
		orphanQueue,
		0,
		time.Second,
		testCtx.logger,
		testCtx.stats,
		testCtx.fc)
	test.AssertNotError(t, err, "Failed to create CA")

	err = ca.integrateOrphan()
	if err != goque.ErrEmpty {
		t.Fatalf("Unexpected error, wanted %q, got %q", goque.ErrEmpty, err)
	}

	_, err = ca.IssuePrecertificate(context.Background(), &capb.IssueCertificateRequest{
		RegistrationID: 1,
		OrderID:        1,
		Csr:            CNandSANCSR,
	})
	test.AssertError(t, err, "Expected IssuePrecertificate to fail with `failSA`")

	matches := testCtx.logger.GetAllMatching(`orphaning precertificate.* regID=\[1\], orderID=\[1\]`)
	if len(matches) != 1 {
		t.Errorf("no log line, or incorrect log line for orphaned precertificate:\n%s",
			strings.Join(testCtx.logger.GetAllMatching(".*"), "\n"))
	}

	orphanCount := test.CountCounterVec("type", "precert", ca.orphanCount)
	test.AssertEquals(t, orphanCount, 1)

	qsa.fail = false
	err = ca.integrateOrphan()
	test.AssertNotError(t, err, "integrateOrphan failed")
	if !qsa.issuedPrecert.Equal(fakeNow) {
		t.Errorf("expected issued time to be %s, got %s", fakeNow, *qsa.issued)
	}
	err = ca.integrateOrphan()
	if err != goque.ErrEmpty {
		t.Fatalf("Unexpected error, wanted %q, got %q", goque.ErrEmpty, err)
	}

	adoptedCount := test.CountCounterVec("type", "precert", ca.adoptedOrphanCount)
	test.AssertEquals(t, adoptedCount, 1)
}

func TestOrphanQueue(t *testing.T) {
	tmpDir, err := ioutil.TempDir("", "orphan-queue-tmp")
	defer os.Remove(tmpDir)
	test.AssertNotError(t, err, "Failed to create temp directory")
	orphanQueue, err := goque.OpenQueue(tmpDir)
	test.AssertNotError(t, err, "Failed to open orphaned certificate queue")

	qsa := &queueSA{fail: true}
	testCtx := setup(t)
	fakeNow, err := time.Parse("Mon Jan 2 15:04:05 2006", "Mon Jan 2 15:04:05 2006")
	if err != nil {
		t.Fatal(err)
	}
	testCtx.fc.Set(fakeNow)
	ca, err := NewCertificateAuthorityImpl(
		qsa,
		testCtx.pa,
		testCtx.cfsslProfiles,
		testCtx.cfsslRSAProfile,
		testCtx.cfsslECDSAProfile,
		testCtx.cfsslIssuers,
		nil,
		nil,
		testCtx.certExpiry,
		testCtx.certBackdate,
		testCtx.serialPrefix,
		testCtx.maxNames,
		testCtx.ocspLifetime,
		testCtx.keyPolicy,
		orphanQueue,
		0,
		time.Second,
		testCtx.logger,
		testCtx.stats,
		testCtx.fc)
	test.AssertNotError(t, err, "Failed to create CA")

	err = ca.integrateOrphan()
	if err != goque.ErrEmpty {
		t.Fatalf("Unexpected error, wanted %q, got %q", goque.ErrEmpty, err)
	}

	// generate basic test cert
	k, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	test.AssertNotError(t, err, "Failed to generate test key")
	tmpl := &x509.Certificate{
		SerialNumber: big.NewInt(1),
		DNSNames:     []string{"test.invalid"},
		NotBefore:    fakeNow.Add(-time.Hour),
	}
	certDER, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, k.Public(), k)
	test.AssertNotError(t, err, "Failed to generate test cert")
	err = ca.storeCertificate(
		context.Background(),
		1,
		1,
		tmpl.SerialNumber,
		certDER,
		1,
	)
	test.AssertError(t, err, "storeCertificate didn't fail when AddCertificate failed")

	qsa.fail = false
	err = ca.integrateOrphan()
	test.AssertNotError(t, err, "integrateOrphan failed")
	if !qsa.issued.Equal(fakeNow) {
		t.Errorf("expected issued time to be %s, got %s", fakeNow, *qsa.issued)
	}
	err = ca.integrateOrphan()
	if err != goque.ErrEmpty {
		t.Fatalf("Unexpected error, wanted %q, got %q", goque.ErrEmpty, err)
	}

	// test with a duplicate cert
	ca.queueOrphan(&orphanedCert{
		DER:      certDER,
		OCSPResp: []byte{},
		RegID:    1,
	})

	qsa.duplicate = true
	err = ca.integrateOrphan()
	test.AssertNotError(t, err, "integrateOrphan failed with duplicate cert")
	if !qsa.issued.Equal(fakeNow) {
		t.Errorf("expected issued time to be %s, got %s", fakeNow, *qsa.issued)
	}
	err = ca.integrateOrphan()
	if err != goque.ErrEmpty {
		t.Fatalf("Unexpected error, wanted %q, got %q", goque.ErrEmpty, err)
	}

	// add cert to queue, and recreate queue to make sure it still has the cert
	qsa.fail = true
	qsa.duplicate = false
	err = ca.storeCertificate(
		context.Background(),
		1,
		1,
		tmpl.SerialNumber,
		certDER,
		1,
	)
	test.AssertError(t, err, "storeCertificate didn't fail when AddCertificate failed")
	err = orphanQueue.Close()
	test.AssertNotError(t, err, "Failed to close the queue cleanly")
	orphanQueue, err = goque.OpenQueue(tmpDir)
	test.AssertNotError(t, err, "Failed to open orphaned certificate queue")
	defer func() { _ = orphanQueue.Close() }()
	ca.orphanQueue = orphanQueue

	qsa.fail = false
	err = ca.integrateOrphan()
	test.AssertNotError(t, err, "integrateOrphan failed")
	if !qsa.issued.Equal(fakeNow) {
		t.Errorf("expected issued time to be %s, got %s", fakeNow, *qsa.issued)
	}
	err = ca.integrateOrphan()
	if err != goque.ErrEmpty {
		t.Fatalf("Unexpected error, wanted %q, got %q", goque.ErrEmpty, err)
	}
}

type linttrapSigner struct {
	lintErr error
}

func (s *linttrapSigner) Sign(signer.SignRequest) ([]byte, error) {
	return nil, s.lintErr
}

func (s *linttrapSigner) SignFromPrecert(*x509.Certificate, []ct.SignedCertificateTimestamp) ([]byte, error) {
	return nil, errors.New("SignFromPrecert not implemented for linttrapSigner")
}

func TestIssuePrecertificateLinting(t *testing.T) {
	testCtx := setup(t)
	sa := &mockSA{}
	ca, err := NewCertificateAuthorityImpl(
		sa,
		testCtx.pa,
		testCtx.cfsslProfiles,
		testCtx.cfsslRSAProfile,
		testCtx.cfsslECDSAProfile,
		testCtx.cfsslIssuers,
		nil,
		nil,
		testCtx.certExpiry,
		testCtx.certBackdate,
		testCtx.serialPrefix,
		testCtx.maxNames,
		testCtx.ocspLifetime,
		testCtx.keyPolicy,
		nil,
		0,
		time.Second,
		testCtx.logger,
		testCtx.stats,
		testCtx.fc)
	test.AssertNotError(t, err, "Failed to create CA")

	// Reconfigure the CA's cfsslSigner to be a linttrapSigner that always returns
	// two LintResults.
	rsaIssuer := ca.issuers.byAlg[x509.RSA]
	rsaIssuer.cfsslSigner = &linttrapSigner{
		lintErr: &local.LintError{
			ErrorResults: map[string]zlint.LintResult{
				"foobar": {
					Status:  zlint.Error,
					Details: "foobar is error",
				},
				"foobar2": {
					Status:  zlint.Warn,
					Details: "foobar2 is warning",
				},
			},
		},
	}

	// Clear the mock logger
	testCtx.logger.Clear()

	// Attempt to issue a pre-certificate
	_, err = ca.IssuePrecertificate(ctx, &capb.IssueCertificateRequest{
		Csr:            CNandSANCSR,
		RegistrationID: arbitraryRegID,
	})
	// It should error
	test.AssertError(t, err, "expected err from IssuePrecertificate with linttrapSigner")
	// The local.LintError should have been converted to an internal server error
	// berror with the correct message.
	test.AssertErrorIs(t, err, berrors.InternalServer)
	test.AssertEquals(t, err.Error(), "failed to sign certificate: pre-issuance linting found 2 error results")

	// We also expect that an AUDIT level error is logged that includes the expect
	// serialized JSON lintErrors
	regex := `ERR: \[AUDIT\] Signing failed: serial=\[.*\] err=\[pre-issuance linting found 2 error results\] lintErrors=\{"foobar":\{"result":"error","details":"foobar is error"\},"foobar2":\{"result":"warn","details":"foobar2 is warning"\}\}`
	matches := testCtx.logger.GetAllMatching(regex)
	test.AssertEquals(t, len(matches), 1)
}

func TestGenerateOCSPWithIssuerID(t *testing.T) {
	testCtx := setup(t)
	sa := &mockSA{}
	_ = features.Set(map[string]bool{"StoreIssuerInfo": true})
	ca, err := NewCertificateAuthorityImpl(
		sa,
		testCtx.pa,
		testCtx.cfsslProfiles,
		testCtx.cfsslRSAProfile,
		testCtx.cfsslECDSAProfile,
		testCtx.cfsslIssuers,
		nil,
		nil,
		testCtx.certExpiry,
		testCtx.certBackdate,
		testCtx.serialPrefix,
		testCtx.maxNames,
		testCtx.ocspLifetime,
		testCtx.keyPolicy,
		nil,
		0,
		time.Second,
		testCtx.logger,
		testCtx.stats,
		testCtx.fc)
	test.AssertNotError(t, err, "Failed to create CA")

	// GenerateOCSP with feature enabled + req contains bad IssuerID
	_, err = ca.GenerateOCSP(context.Background(), &capb.GenerateOCSPRequest{
		IssuerID: int64(666),
		Serial:   "DEADDEADDEADDEADDEADDEADDEADDEADDEAD",
		Status:   string(core.OCSPStatusGood),
	})
	test.AssertError(t, err, "GenerateOCSP didn't fail with invalid IssuerID")

	// GenerateOCSP with feature enabled + req contains good IssuerID
	rsaIssuer := ca.issuers.byAlg[x509.RSA]
	_, err = ca.GenerateOCSP(context.Background(), &capb.GenerateOCSPRequest{
		IssuerID: int64(rsaIssuer.cert.ID()),
		Serial:   "DEADDEADDEADDEADDEADDEADDEADDEADDEAD",
		Status:   string(core.OCSPStatusGood),
	})
	test.AssertNotError(t, err, "GenerateOCSP failed")

	// GenerateOCSP with feature enabled + req doesn't contain IssuerID
	issueReq := capb.IssueCertificateRequest{Csr: CNandSANCSR, RegistrationID: arbitraryRegID}
	cert, err := ca.IssuePrecertificate(ctx, &issueReq)
	test.AssertNotError(t, err, "Failed to issue")
	_, err = ca.GenerateOCSP(context.Background(), &capb.GenerateOCSPRequest{
		CertDER: cert.DER,
		Status:  string(core.OCSPStatusGood),
	})
	test.AssertNotError(t, err, "GenerateOCSP failed")
}
