package issuance

import (
	"crypto"
	"crypto/dsa"
	"crypto/ecdsa"
	"crypto/ed25519"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/asn1"
	"io/ioutil"
	"math/big"
	"os"
	"testing"
	"time"

	ct "github.com/google/certificate-transparency-go"
	"github.com/jmhodges/clock"
	"github.com/letsencrypt/boulder/cmd"
	"github.com/letsencrypt/boulder/core"
	"github.com/letsencrypt/boulder/lint"
	"github.com/letsencrypt/boulder/policyasn1"
	"github.com/letsencrypt/boulder/test"
)

func defaultProfileConfig() ProfileConfig {
	return ProfileConfig{
		AllowCommonName: true,
		AllowCTPoison:   true,
		AllowSCTList:    true,
		AllowMustStaple: true,
		Policies: []PolicyInformation{
			{OID: "1.2.3"},
		},
		MaxValidityPeriod:   cmd.ConfigDuration{Duration: time.Hour},
		MaxValidityBackdate: cmd.ConfigDuration{Duration: time.Hour},
	}
}

func defaultIssuerConfig() IssuerConfig {
	return IssuerConfig{
		UseForECDSALeaves: true,
		UseForRSALeaves:   true,
		IssuerURL:         "http://issuer-url",
		OCSPURL:           "http://ocsp-url",
	}
}

func defaultProfile() *Profile {
	p, _ := NewProfile(defaultProfileConfig(), defaultIssuerConfig())
	return p
}

var issuerCert *Certificate
var issuerSigner *ecdsa.PrivateKey

func TestMain(m *testing.M) {
	tk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	cmd.FailOnError(err, "failed to generate test key")
	issuerSigner = tk
	template := &x509.Certificate{
		SerialNumber:          big.NewInt(123),
		PublicKey:             tk.Public(),
		BasicConstraintsValid: true,
		IsCA:                  true,
		Subject: pkix.Name{
			CommonName: "big ca",
		},
		KeyUsage:     x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature,
		SubjectKeyId: []byte{1, 2, 3, 4, 5, 6, 7, 8},
	}
	issuer, err := x509.CreateCertificate(rand.Reader, template, template, tk.Public(), tk)
	cmd.FailOnError(err, "failed to generate test issuer")
	cert, err := x509.ParseCertificate(issuer)
	cmd.FailOnError(err, "failed to parse test issuer")
	issuerCert = &Certificate{cert}
	os.Exit(m.Run())
}

func TestNewProfilePolicies(t *testing.T) {
	config := defaultProfileConfig()
	config.Policies = append(config.Policies, PolicyInformation{
		OID: "1.2.3.4",
		Qualifiers: []PolicyQualifier{
			{
				Type:  "id-qt-cps",
				Value: "cps-url",
			},
		},
	})
	profile, err := NewProfile(config, defaultIssuerConfig())
	test.AssertNotError(t, err, "NewProfile failed")
	test.AssertDeepEquals(t, *profile, Profile{
		useForRSALeaves:   true,
		useForECDSALeaves: true,
		allowMustStaple:   true,
		allowCTPoison:     true,
		allowSCTList:      true,
		allowCommonName:   true,
		issuerURL:         "http://issuer-url",
		ocspURL:           "http://ocsp-url",
		policies: &pkix.Extension{
			Id:    asn1.ObjectIdentifier{2, 5, 29, 32},
			Value: []byte{48, 36, 48, 4, 6, 2, 42, 3, 48, 28, 6, 3, 42, 3, 4, 48, 21, 48, 19, 6, 8, 43, 6, 1, 5, 5, 7, 2, 1, 22, 7, 99, 112, 115, 45, 117, 114, 108},
		},
		maxBackdate: time.Hour,
		maxValidity: time.Hour,
	})
	var policies []policyasn1.PolicyInformation
	_, err = asn1.Unmarshal(profile.policies.Value, &policies)
	test.AssertNotError(t, err, "failed to parse policies extension")
	test.AssertEquals(t, len(policies), 2)
	test.AssertDeepEquals(t, policies[0], policyasn1.PolicyInformation{
		Policy: asn1.ObjectIdentifier{1, 2, 3},
	})
	test.AssertDeepEquals(t, policies[1], policyasn1.PolicyInformation{
		Policy: asn1.ObjectIdentifier{1, 2, 3, 4},
		Qualifiers: []policyasn1.PolicyQualifier{{
			OID:   asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 2, 1},
			Value: "cps-url",
		}},
	})
}

func TestNewProfileNoIssuerURL(t *testing.T) {
	_, err := NewProfile(ProfileConfig{}, IssuerConfig{})
	test.AssertError(t, err, "NewProfile didn't fail with no issuer URL")
	test.AssertEquals(t, err.Error(), "Issuer URL is required")
}

func TestNewProfileNoOCSPURL(t *testing.T) {
	_, err := NewProfile(ProfileConfig{}, IssuerConfig{IssuerURL: "issuer-url"})
	test.AssertError(t, err, "NewProfile didn't fail with no OCSP URL")
	test.AssertEquals(t, err.Error(), "OCSP URL is required")
}

func TestNewProfileInvalidOID(t *testing.T) {
	_, err := NewProfile(ProfileConfig{
		Policies: []PolicyInformation{{
			OID: "a.b.c",
		}},
	}, defaultIssuerConfig())
	test.AssertError(t, err, "NewProfile didn't fail with unknown policy qualifier type")
	test.AssertEquals(t, err.Error(), "failed parsing policy OID \"a.b.c\": strconv.Atoi: parsing \"a\": invalid syntax")
}

func TestNewProfileUnknownQualifierType(t *testing.T) {
	_, err := NewProfile(ProfileConfig{
		Policies: []PolicyInformation{{
			OID: "1.2.3",
			Qualifiers: []PolicyQualifier{{
				Type:  "asd",
				Value: "bad",
			}},
		}},
	}, defaultIssuerConfig())
	test.AssertError(t, err, "NewProfile didn't fail with unknown policy qualifier type")
	test.AssertEquals(t, err.Error(), "unknown qualifier type: asd")
}

func TestRequestValid(t *testing.T) {
	fc := clock.NewFake()
	fc.Add(time.Hour * 24)
	tests := []struct {
		name          string
		profile       *Profile
		request       *IssuanceRequest
		expectedError string
	}{
		{
			name:          "unsupported key type",
			profile:       &Profile{},
			request:       &IssuanceRequest{PublicKey: &dsa.PublicKey{}},
			expectedError: "unsupported public key type",
		},
		{
			name:          "cannot sign rsa",
			profile:       &Profile{},
			request:       &IssuanceRequest{PublicKey: &rsa.PublicKey{}},
			expectedError: "cannot sign RSA public keys",
		},
		{
			name:          "cannot sign ecdsa",
			profile:       &Profile{},
			request:       &IssuanceRequest{PublicKey: &ecdsa.PublicKey{}},
			expectedError: "cannot sign ECDSA public keys",
		},
		{
			name: "must staple not allowed",
			profile: &Profile{
				useForECDSALeaves: true,
			},
			request: &IssuanceRequest{
				PublicKey:         &ecdsa.PublicKey{},
				IncludeMustStaple: true,
			},
			expectedError: "must-staple extension cannot be included",
		},
		{
			name: "ct poison not allowed",
			profile: &Profile{
				useForECDSALeaves: true,
			},
			request: &IssuanceRequest{
				PublicKey:       &ecdsa.PublicKey{},
				IncludeCTPoison: true,
			},
			expectedError: "ct poison extension cannot be included",
		},
		{
			name: "sct list not allowed",
			profile: &Profile{
				useForECDSALeaves: true,
			},
			request: &IssuanceRequest{
				PublicKey: &ecdsa.PublicKey{},
				SCTList:   []ct.SignedCertificateTimestamp{},
			},
			expectedError: "sct list extension cannot be included",
		},
		{
			name: "sct list and ct poison not allowed",
			profile: &Profile{
				useForECDSALeaves: true,
				allowCTPoison:     true,
				allowSCTList:      true,
			},
			request: &IssuanceRequest{
				PublicKey:       &ecdsa.PublicKey{},
				IncludeCTPoison: true,
				SCTList:         []ct.SignedCertificateTimestamp{},
			},
			expectedError: "cannot include both ct poison and sct list extensions",
		},
		{
			name: "common name not allowed",
			profile: &Profile{
				useForECDSALeaves: true,
			},
			request: &IssuanceRequest{
				PublicKey:  &ecdsa.PublicKey{},
				CommonName: "cn",
			},
			expectedError: "common name cannot be included",
		},
		{
			name: "negative validity",
			profile: &Profile{
				useForECDSALeaves: true,
			},
			request: &IssuanceRequest{
				PublicKey: &ecdsa.PublicKey{},
				NotBefore: fc.Now().Add(time.Hour),
				NotAfter:  fc.Now(),
			},
			expectedError: "NotAfter must be after NotBefore",
		},
		{
			name: "validity larger than max",
			profile: &Profile{
				useForECDSALeaves: true,
				maxValidity:       time.Minute,
			},
			request: &IssuanceRequest{
				PublicKey: &ecdsa.PublicKey{},
				NotBefore: fc.Now(),
				NotAfter:  fc.Now().Add(time.Hour),
			},
			expectedError: "validity period is more than the maximum allowed period (1h0m0s>1m0s)",
		},
		{
			name: "validity backdated more than max",
			profile: &Profile{
				useForECDSALeaves: true,
				maxValidity:       time.Hour * 2,
				maxBackdate:       time.Hour,
			},
			request: &IssuanceRequest{
				PublicKey: &ecdsa.PublicKey{},
				NotBefore: fc.Now().Add(-time.Hour * 2),
				NotAfter:  fc.Now().Add(-time.Hour),
			},
			expectedError: "NotBefore is backdated more than the maximum allowed period (2h0m0s>1h0m0s)",
		},
		{
			name: "validity is forward dated",
			profile: &Profile{
				useForECDSALeaves: true,
				maxValidity:       time.Hour * 2,
				maxBackdate:       time.Hour,
			},
			request: &IssuanceRequest{
				PublicKey: &ecdsa.PublicKey{},
				NotBefore: fc.Now().Add(time.Hour),
				NotAfter:  fc.Now().Add(time.Hour * 2),
			},
			expectedError: "NotBefore is in the future",
		},
		{
			name: "serial too short",
			profile: &Profile{
				useForECDSALeaves: true,
				maxValidity:       time.Hour * 2,
			},
			request: &IssuanceRequest{
				PublicKey: &ecdsa.PublicKey{},
				NotBefore: fc.Now(),
				NotAfter:  fc.Now().Add(time.Hour),
			},
			expectedError: "serial must be between 8 and 20 bytes",
		},
		{
			name: "serial too long",
			profile: &Profile{
				useForECDSALeaves: true,
				maxValidity:       time.Hour * 2,
			},
			request: &IssuanceRequest{
				PublicKey: &ecdsa.PublicKey{},
				NotBefore: fc.Now(),
				NotAfter:  fc.Now().Add(time.Hour),
				Serial:    []byte{1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 0},
			},
			expectedError: "serial must be between 8 and 20 bytes",
		},
		{
			name: "good",
			profile: &Profile{
				useForECDSALeaves: true,
				maxValidity:       time.Hour * 2,
			},
			request: &IssuanceRequest{
				PublicKey: &ecdsa.PublicKey{},
				NotBefore: fc.Now(),
				NotAfter:  fc.Now().Add(time.Hour),
				Serial:    []byte{1, 2, 3, 4, 5, 6, 7, 8},
			},
		},
	}
	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			err := tc.profile.requestValid(fc, tc.request)
			if err != nil {
				if tc.expectedError == "" {
					t.Errorf("failed with unexpected error: %s", err)
				} else if tc.expectedError != err.Error() {
					t.Errorf("failed with unexpected error, wanted: %q, got: %q", tc.expectedError, err.Error())
				}
				return
			} else if tc.expectedError != "" {
				t.Errorf("didn't fail, expected %q", tc.expectedError)
			}
		})
	}
}

func TestGenerateTemplate(t *testing.T) {
	tests := []struct {
		name             string
		profile          *Profile
		expectedTemplate *x509.Certificate
	}{
		{
			name: "crl url",
			profile: &Profile{
				crlURL: "crl-url",
				sigAlg: x509.SHA256WithRSA,
			},
			expectedTemplate: &x509.Certificate{
				BasicConstraintsValid: true,
				SignatureAlgorithm:    x509.SHA256WithRSA,
				ExtKeyUsage:           defaultEKU,
				IssuingCertificateURL: []string{""},
				OCSPServer:            []string{""},
				CRLDistributionPoints: []string{"crl-url"},
			},
		},
		{
			name: "include policies",
			profile: &Profile{
				sigAlg: x509.SHA256WithRSA,
				policies: &pkix.Extension{
					Id:    asn1.ObjectIdentifier{1, 2, 3},
					Value: []byte{4, 5, 6},
				},
			},
			expectedTemplate: &x509.Certificate{
				BasicConstraintsValid: true,
				SignatureAlgorithm:    x509.SHA256WithRSA,
				ExtKeyUsage:           defaultEKU,
				IssuingCertificateURL: []string{""},
				OCSPServer:            []string{""},
				ExtraExtensions: []pkix.Extension{
					{
						Id:    asn1.ObjectIdentifier{1, 2, 3},
						Value: []byte{4, 5, 6},
					},
				},
			},
		},
	}
	fc := clock.NewFake()
	fc.Set(time.Time{}.Add(time.Hour))
	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			template := tc.profile.generateTemplate(fc)
			test.AssertDeepEquals(t, *template, *tc.expectedTemplate)
		})
	}
}

func TestNewIssuer(t *testing.T) {
	_, err := NewIssuer(
		issuerCert,
		issuerSigner,
		defaultProfile(),
		&lint.Linter{},
		clock.NewFake(),
	)
	test.AssertNotError(t, err, "NewIssuer failed")
}

func TestNewIssuerUnsupportedKeyType(t *testing.T) {
	_, err := NewIssuer(
		&Certificate{
			&x509.Certificate{
				PublicKey: &ed25519.PublicKey{},
			},
		},
		&ed25519.PrivateKey{},
		defaultProfile(),
		&lint.Linter{},
		clock.NewFake(),
	)
	test.AssertError(t, err, "NewIssuer didn't fail")
	test.AssertEquals(t, err.Error(), "unsupported issuer key type")
}

func TestNewIssuerNoCertSign(t *testing.T) {
	_, err := NewIssuer(
		&Certificate{
			&x509.Certificate{
				PublicKey: &ecdsa.PublicKey{
					Curve: elliptic.P256(),
				},
				KeyUsage: 0,
			},
		},
		issuerSigner,
		defaultProfile(),
		&lint.Linter{},
		clock.NewFake(),
	)
	test.AssertError(t, err, "NewIssuer didn't fail")
	test.AssertEquals(t, err.Error(), "end-entity signing cert does not have keyUsage certSign")
}

func TestNewIssuerNoDigitalSignature(t *testing.T) {
	_, err := NewIssuer(
		&Certificate{
			&x509.Certificate{
				PublicKey: &ecdsa.PublicKey{
					Curve: elliptic.P256(),
				},
				KeyUsage: x509.KeyUsageCertSign,
			},
		},
		issuerSigner,
		defaultProfile(),
		&lint.Linter{},
		clock.NewFake(),
	)
	test.AssertError(t, err, "NewIssuer didn't fail")
	test.AssertEquals(t, err.Error(), "end-entity ocsp signing cert does not have keyUsage digitalSignature")
}

func TestNewIssuerOCSPOnly(t *testing.T) {
	p := defaultProfile()
	p.useForRSALeaves = false
	p.useForECDSALeaves = false
	_, err := NewIssuer(
		&Certificate{
			&x509.Certificate{
				PublicKey: &ecdsa.PublicKey{
					Curve: elliptic.P256(),
				},
				KeyUsage: x509.KeyUsageDigitalSignature,
			},
		},
		issuerSigner,
		p,
		&lint.Linter{},
		clock.NewFake(),
	)
	test.AssertNotError(t, err, "NewIssuer failed")
}

func TestIssue(t *testing.T) {
	for _, tc := range []struct {
		name         string
		generateFunc func() (crypto.Signer, error)
		ku           x509.KeyUsage
	}{
		{
			name: "RSA",
			generateFunc: func() (crypto.Signer, error) {
				return rsa.GenerateKey(rand.Reader, 2048)
			},
			ku: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
		},
		{
			name: "ECDSA",
			generateFunc: func() (crypto.Signer, error) {
				return ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
			},
			ku: x509.KeyUsageDigitalSignature,
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			fc := clock.NewFake()
			fc.Set(time.Now())
			linter, _ := lint.NewLinter(
				issuerSigner,
				[]string{"w_ct_sct_policy_count_unsatisfied", "n_subject_common_name_included"},
			)
			signer, err := NewIssuer(issuerCert, issuerSigner, defaultProfile(), linter, fc)
			test.AssertNotError(t, err, "NewIssuer failed")
			pk, err := tc.generateFunc()
			test.AssertNotError(t, err, "failed to generate test key")
			certBytes, err := signer.Issue(&IssuanceRequest{
				PublicKey:  pk.Public(),
				Serial:     []byte{1, 2, 3, 4, 5, 6, 7, 8},
				CommonName: "example.com",
				DNSNames:   []string{"example.com"},
				NotBefore:  fc.Now(),
				NotAfter:   fc.Now().Add(time.Hour),
			})
			test.AssertNotError(t, err, "Issue failed")
			cert, err := x509.ParseCertificate(certBytes)
			test.AssertNotError(t, err, "failed to parse certificate")
			err = cert.CheckSignatureFrom(issuerCert.Certificate)
			test.AssertNotError(t, err, "signature validation failed")
			test.AssertDeepEquals(t, cert.DNSNames, []string{"example.com"})
			test.AssertEquals(t, cert.Subject.CommonName, "example.com")
			test.AssertByteEquals(t, cert.SerialNumber.Bytes(), []byte{1, 2, 3, 4, 5, 6, 7, 8})
			test.AssertDeepEquals(t, cert.PublicKey, pk.Public())
			test.AssertEquals(t, len(cert.Extensions), 8) // Constraints, KU, EKU, SKID, AKID, AIA, SAN, Policies
			test.AssertEquals(t, cert.KeyUsage, tc.ku)
		})
	}
}

func TestIssueRSA(t *testing.T) {
	fc := clock.NewFake()
	fc.Set(time.Now())
	linter, _ := lint.NewLinter(
		issuerSigner,
		[]string{"w_ct_sct_policy_count_unsatisfied"},
	)
	signer, err := NewIssuer(issuerCert, issuerSigner, defaultProfile(), linter, fc)
	test.AssertNotError(t, err, "NewIssuer failed")
	pk, err := rsa.GenerateKey(rand.Reader, 2048)
	test.AssertNotError(t, err, "failed to generate test key")
	certBytes, err := signer.Issue(&IssuanceRequest{
		PublicKey: pk.Public(),
		Serial:    []byte{1, 2, 3, 4, 5, 6, 7, 8},
		DNSNames:  []string{"example.com"},
		NotBefore: fc.Now(),
		NotAfter:  fc.Now().Add(time.Hour),
	})
	test.AssertNotError(t, err, "Issue failed")
	cert, err := x509.ParseCertificate(certBytes)
	test.AssertNotError(t, err, "failed to parse certificate")
	err = cert.CheckSignatureFrom(issuerCert.Certificate)
	test.AssertNotError(t, err, "signature validation failed")
	test.AssertByteEquals(t, cert.SerialNumber.Bytes(), []byte{1, 2, 3, 4, 5, 6, 7, 8})
	test.AssertDeepEquals(t, cert.PublicKey, pk.Public())
	test.AssertEquals(t, len(cert.Extensions), 8) // Constraints, KU, EKU, SKID, AKID, AIA, SAN, Policies
	test.AssertEquals(t, cert.KeyUsage, x509.KeyUsageDigitalSignature|x509.KeyUsageKeyEncipherment)
}

func TestIssueCTPoison(t *testing.T) {
	fc := clock.NewFake()
	fc.Set(time.Now())
	linter, _ := lint.NewLinter(
		issuerSigner,
		[]string{"w_ct_sct_policy_count_unsatisfied"},
	)
	signer, err := NewIssuer(issuerCert, issuerSigner, defaultProfile(), linter, fc)
	test.AssertNotError(t, err, "NewIssuer failed")
	pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	test.AssertNotError(t, err, "failed to generate test key")
	certBytes, err := signer.Issue(&IssuanceRequest{
		PublicKey:       pk.Public(),
		Serial:          []byte{1, 2, 3, 4, 5, 6, 7, 8},
		DNSNames:        []string{"example.com"},
		IncludeCTPoison: true,
		NotBefore:       fc.Now(),
		NotAfter:        fc.Now().Add(time.Hour),
	})
	test.AssertNotError(t, err, "Issue failed")
	cert, err := x509.ParseCertificate(certBytes)
	test.AssertNotError(t, err, "failed to parse certificate")
	err = cert.CheckSignatureFrom(issuerCert.Certificate)
	test.AssertNotError(t, err, "signature validation failed")
	test.AssertByteEquals(t, cert.SerialNumber.Bytes(), []byte{1, 2, 3, 4, 5, 6, 7, 8})
	test.AssertDeepEquals(t, cert.PublicKey, pk.Public())
	test.AssertEquals(t, len(cert.Extensions), 9) // Constraints, KU, EKU, SKID, AKID, AIA, SAN, Policies, CT Poison
	test.AssertDeepEquals(t, cert.Extensions[8], ctPoisonExt)
}

func TestIssueSCTList(t *testing.T) {
	fc := clock.NewFake()
	fc.Set(time.Now())
	linter, _ := lint.NewLinter(
		issuerSigner,
		[]string{"w_ct_sct_policy_count_unsatisfied"},
	)
	signer, err := NewIssuer(issuerCert, issuerSigner, defaultProfile(), linter, fc)
	test.AssertNotError(t, err, "NewIssuer failed")
	pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	test.AssertNotError(t, err, "failed to generate test key")
	certBytes, err := signer.Issue(&IssuanceRequest{
		PublicKey: pk.Public(),
		Serial:    []byte{1, 2, 3, 4, 5, 6, 7, 8},
		DNSNames:  []string{"example.com"},
		SCTList: []ct.SignedCertificateTimestamp{
			{},
		},
		NotBefore: fc.Now(),
		NotAfter:  fc.Now().Add(time.Hour),
	})
	test.AssertNotError(t, err, "Issue failed")
	cert, err := x509.ParseCertificate(certBytes)
	test.AssertNotError(t, err, "failed to parse certificate")
	err = cert.CheckSignatureFrom(issuerCert.Certificate)
	test.AssertNotError(t, err, "signature validation failed")
	test.AssertByteEquals(t, cert.SerialNumber.Bytes(), []byte{1, 2, 3, 4, 5, 6, 7, 8})
	test.AssertDeepEquals(t, cert.PublicKey, pk.Public())
	test.AssertEquals(t, len(cert.Extensions), 9) // Constraints, KU, EKU, SKID, AKID, AIA, SAN, Policies, SCT list
	test.AssertDeepEquals(t, cert.Extensions[8], pkix.Extension{
		Id:    sctListOID,
		Value: []byte{4, 51, 0, 49, 0, 47, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
	})
}

func TestIssueMustStaple(t *testing.T) {
	fc := clock.NewFake()
	fc.Set(time.Now())
	linter, _ := lint.NewLinter(
		issuerSigner,
		[]string{"w_ct_sct_policy_count_unsatisfied"},
	)
	signer, err := NewIssuer(issuerCert, issuerSigner, defaultProfile(), linter, fc)
	test.AssertNotError(t, err, "NewIssuer failed")
	pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	test.AssertNotError(t, err, "failed to generate test key")
	certBytes, err := signer.Issue(&IssuanceRequest{
		PublicKey:         pk.Public(),
		Serial:            []byte{1, 2, 3, 4, 5, 6, 7, 8},
		DNSNames:          []string{"example.com"},
		IncludeMustStaple: true,
		NotBefore:         fc.Now(),
		NotAfter:          fc.Now().Add(time.Hour),
	})
	test.AssertNotError(t, err, "Issue failed")
	cert, err := x509.ParseCertificate(certBytes)
	test.AssertNotError(t, err, "failed to parse certificate")
	err = cert.CheckSignatureFrom(issuerCert.Certificate)
	test.AssertNotError(t, err, "signature validation failed")
	test.AssertByteEquals(t, cert.SerialNumber.Bytes(), []byte{1, 2, 3, 4, 5, 6, 7, 8})
	test.AssertDeepEquals(t, cert.PublicKey, pk.Public())
	test.AssertEquals(t, len(cert.Extensions), 9) // Constraints, KU, EKU, SKID, AKID, AIA, SAN, Policies, Must-Staple
	test.AssertDeepEquals(t, cert.Extensions[8], mustStapleExt)
}

func TestIssueBadLint(t *testing.T) {
	fc := clock.NewFake()
	fc.Set(time.Now())
	linter, _ := lint.NewLinter(issuerSigner, []string{})
	signer, err := NewIssuer(issuerCert, issuerSigner, defaultProfile(), linter, fc)
	test.AssertNotError(t, err, "NewIssuer failed")
	pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	test.AssertNotError(t, err, "failed to generate test key")
	_, err = signer.Issue(&IssuanceRequest{
		PublicKey: pk.Public(),
		Serial:    []byte{1, 2, 3, 4, 5, 6, 7, 8},
		DNSNames:  []string{"example.com"},
		NotBefore: fc.Now(),
		NotAfter:  fc.Now().Add(time.Hour),
	})
	test.AssertError(t, err, "Issue didn't fail")
	test.AssertEquals(t, err.Error(), "tbsCertificate linting failed: failed lints: w_ct_sct_policy_count_unsatisfied")
}

func TestLoadChain_Valid(t *testing.T) {
	chain, err := LoadChain([]string{
		"../test/test-ca-cross.pem",
		"../test/test-root2.pem",
	})
	test.AssertNotError(t, err, "Should load valid chain")

	expectedIssuer, err := core.LoadCert("../test/test-ca-cross.pem")
	test.AssertNotError(t, err, "Failed to load test issuer")

	chainIssuer := chain[0]
	test.AssertNotNil(t, chainIssuer, "Failed to decode chain PEM")

	test.AssertByteEquals(t, chainIssuer.Raw, expectedIssuer.Raw)
}

func TestLoadChain_TooShort(t *testing.T) {
	_, err := LoadChain([]string{"/path/to/one/cert.pem"})
	test.AssertError(t, err, "Should reject too-short chain")
}

func TestLoadChain_Unloadable(t *testing.T) {
	_, err := LoadChain([]string{
		"does-not-exist.pem",
		"../test/test-root2.pem",
	})
	test.AssertError(t, err, "Should reject unloadable chain")

	_, err = LoadChain([]string{
		"../test/test-ca-cross.pem",
		"does-not-exist.pem",
	})
	test.AssertError(t, err, "Should reject unloadable chain")

	invalidPEMFile, _ := ioutil.TempFile("", "invalid.pem")
	err = ioutil.WriteFile(invalidPEMFile.Name(), []byte(""), 0640)
	test.AssertNotError(t, err, "Error writing invalid PEM tmp file")
	_, err = LoadChain([]string{
		invalidPEMFile.Name(),
		"../test/test-root2.pem",
	})
	test.AssertError(t, err, "Should reject unloadable chain")
}

func TestLoadChain_InvalidSig(t *testing.T) {
	_, err := LoadChain([]string{
		"../test/test-root2.pem",
		"../test/test-ca-cross.pem",
	})
	test.AssertError(t, err, "Should reject invalid signature")
}
