// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package pki

import (
	"bytes"
	"context"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/hex"
	"encoding/pem"
	"fmt"
	"strconv"
	"strings"
	"testing"
	"time"

	"github.com/hashicorp/vault/sdk/logical"
)

// For speed, all keys are ECDSA.
type CBGenerateKey struct {
	Name string
}

func (c CBGenerateKey) Run(t testing.TB, b *backend, s logical.Storage, knownKeys map[string]string, knownCerts map[string]string) {
	resp, err := CBWrite(b, s, "keys/generate/exported", map[string]interface{}{
		"name": c.Name,
		"algo": "ec",
		"bits": 256,
	})
	if err != nil {
		t.Fatalf("failed to provision key (%v): %v", c.Name, err)
	}
	knownKeys[c.Name] = resp.Data["private"].(string)
}

// Generate a root.
type CBGenerateRoot struct {
	Key          string
	Existing     bool
	Name         string
	CommonName   string
	ErrorMessage string
}

func (c CBGenerateRoot) Run(t testing.TB, b *backend, s logical.Storage, knownKeys map[string]string, knownCerts map[string]string) {
	url := "issuers/generate/root/"
	data := make(map[string]interface{})

	if c.Existing {
		url += "existing"
		data["key_ref"] = c.Key
	} else {
		url += "exported"
		data["key_type"] = "ec"
		data["key_bits"] = 256
		data["key_name"] = c.Key
	}

	data["issuer_name"] = c.Name
	data["common_name"] = c.Name
	if len(c.CommonName) > 0 {
		data["common_name"] = c.CommonName
	}

	resp, err := CBWrite(b, s, url, data)
	if err != nil {
		if len(c.ErrorMessage) > 0 {
			if !strings.Contains(err.Error(), c.ErrorMessage) {
				t.Fatalf("failed to generate root cert for issuer (%v): expected (%v) in error message but got %v", c.Name, c.ErrorMessage, err)
			}
			return
		}
		t.Fatalf("failed to provision issuer (%v): %v / body: %v", c.Name, err, data)
	} else if len(c.ErrorMessage) > 0 {
		t.Fatalf("expected to fail generation of issuer (%v) with error message containing (%v)", c.Name, c.ErrorMessage)
	}

	if !c.Existing {
		knownKeys[c.Key] = resp.Data["private_key"].(string)
	}

	knownCerts[c.Name] = resp.Data["certificate"].(string)

	// Validate key_id matches.
	url = "key/" + c.Key
	resp, err = CBRead(b, s, url)
	if err != nil {
		t.Fatalf("failed to fetch key for name %v: %v", c.Key, err)
	}
	if resp == nil {
		t.Fatalf("failed to fetch key for name %v: nil response", c.Key)
	}

	expectedKeyId := resp.Data["key_id"]

	url = "issuer/" + c.Name
	resp, err = CBRead(b, s, url)
	if err != nil {
		t.Fatalf("failed to fetch issuer for name %v: %v", c.Name, err)
	}
	if resp == nil {
		t.Fatalf("failed to fetch issuer for name %v: nil response", c.Name)
	}

	actualKeyId := resp.Data["key_id"]
	if expectedKeyId != actualKeyId {
		t.Fatalf("expected issuer %v to have key matching %v but got mismatch: %v vs %v", c.Name, c.Key, actualKeyId, expectedKeyId)
	}
}

// Generate an intermediate. Might not really be an intermediate; might be
// a cross-signed cert.
type CBGenerateIntermediate struct {
	Key                string
	Existing           bool
	Name               string
	CommonName         string
	SKID               string
	Parent             string
	ImportErrorMessage string
}

func (c CBGenerateIntermediate) Run(t testing.TB, b *backend, s logical.Storage, knownKeys map[string]string, knownCerts map[string]string) {
	// Build CSR
	url := "issuers/generate/intermediate/"
	data := make(map[string]interface{})

	if c.Existing {
		url += "existing"
		data["key_ref"] = c.Key
	} else {
		url += "exported"
		data["key_type"] = "ec"
		data["key_bits"] = 256
		data["key_name"] = c.Key
	}

	resp, err := CBWrite(b, s, url, data)
	if err != nil {
		t.Fatalf("failed to generate CSR for issuer (%v): %v / body: %v", c.Name, err, data)
	}

	if !c.Existing {
		knownKeys[c.Key] = resp.Data["private_key"].(string)
	}

	csr := resp.Data["csr"].(string)

	// Sign CSR
	url = fmt.Sprintf("issuer/%s/sign-intermediate", c.Parent)
	data = make(map[string]interface{})
	data["csr"] = csr
	data["common_name"] = c.Name
	if len(c.CommonName) > 0 {
		data["common_name"] = c.CommonName
	}
	if len(c.SKID) > 0 {
		// Copy the SKID from an existing, already-issued cert.
		otherPEM := knownCerts[c.SKID]
		otherCert := ToCertificate(t, otherPEM)

		data["skid"] = hex.EncodeToString(otherCert.SubjectKeyId)
	}

	resp, err = CBWrite(b, s, url, data)
	if err != nil {
		t.Fatalf("failed to sign CSR for issuer (%v): %v / body: %v", c.Name, err, data)
	}

	knownCerts[c.Name] = strings.TrimSpace(resp.Data["certificate"].(string))

	// Verify SKID if one was requested.
	if len(c.SKID) > 0 {
		otherPEM := knownCerts[c.SKID]
		otherCert := ToCertificate(t, otherPEM)
		ourCert := ToCertificate(t, knownCerts[c.Name])

		if !bytes.Equal(otherCert.SubjectKeyId, ourCert.SubjectKeyId) {
			t.Fatalf("Expected two certs to have equal SKIDs but differed: them: %v vs us: %v", otherCert.SubjectKeyId, ourCert.SubjectKeyId)
		}
	}

	// Set the signed intermediate
	url = "intermediate/set-signed"
	data = make(map[string]interface{})
	data["certificate"] = knownCerts[c.Name]
	data["issuer_name"] = c.Name

	resp, err = CBWrite(b, s, url, data)
	if err != nil {
		if len(c.ImportErrorMessage) > 0 {
			if !strings.Contains(err.Error(), c.ImportErrorMessage) {
				t.Fatalf("failed to import signed cert for issuer (%v): expected (%v) in error message but got %v", c.Name, c.ImportErrorMessage, err)
			}
			return
		}

		t.Fatalf("failed to import signed cert for issuer (%v): %v / body: %v", c.Name, err, data)
	} else if len(c.ImportErrorMessage) > 0 {
		t.Fatalf("expected to fail import (with error %v) of cert for issuer (%v) but was success: response: %v", c.ImportErrorMessage, c.Name, resp)
	}

	// Update the name since set-signed doesn't actually take an issuer name
	// parameter.
	rawNewCerts := resp.Data["imported_issuers"].([]string)
	if len(rawNewCerts) != 1 {
		t.Fatalf("Expected a single new certificate during import of signed cert for %v: got %v\nresp: %v", c.Name, len(rawNewCerts), resp)
	}

	newCertId := rawNewCerts[0]
	_, err = CBWrite(b, s, "issuer/"+newCertId, map[string]interface{}{
		"issuer_name": c.Name,
	})
	if err != nil {
		t.Fatalf("failed to update name for issuer (%v/%v): %v", c.Name, newCertId, err)
	}

	// Validate key_id matches.
	url = "key/" + c.Key
	resp, err = CBRead(b, s, url)
	if err != nil {
		t.Fatalf("failed to fetch key for name %v: %v", c.Key, err)
	}
	if resp == nil {
		t.Fatalf("failed to fetch key for name %v: nil response", c.Key)
	}

	expectedKeyId := resp.Data["key_id"]

	url = "issuer/" + c.Name
	resp, err = CBRead(b, s, url)
	if err != nil {
		t.Fatalf("failed to fetch issuer for name %v: %v", c.Name, err)
	}
	if resp == nil {
		t.Fatalf("failed to fetch issuer for name %v: nil response", c.Name)
	}

	actualKeyId := resp.Data["key_id"]
	if expectedKeyId != actualKeyId {
		t.Fatalf("expected issuer %v to have key matching %v but got mismatch: %v vs %v", c.Name, c.Key, actualKeyId, expectedKeyId)
	}
}

// Delete an issuer; breaks chains.
type CBDeleteIssuer struct {
	Issuer string
}

func (c CBDeleteIssuer) Run(t testing.TB, b *backend, s logical.Storage, knownKeys map[string]string, knownCerts map[string]string) {
	url := fmt.Sprintf("issuer/%v", c.Issuer)
	_, err := CBDelete(b, s, url)
	if err != nil {
		t.Fatalf("failed to delete issuer (%v): %v", c.Issuer, err)
	}

	delete(knownCerts, c.Issuer)
}

// Validate the specified chain exists, by name.
type CBValidateChain struct {
	Chains  map[string][]string
	Aliases map[string]string
}

func (c CBValidateChain) ChainToPEMs(t testing.TB, parent string, chain []string, knownCerts map[string]string) []string {
	var result []string
	for entryIndex, entry := range chain {
		var chainEntry string
		modifiedEntry := entry
		if entryIndex == 0 && entry == "self" {
			modifiedEntry = parent
		}
		for pattern, replacement := range c.Aliases {
			modifiedEntry = strings.ReplaceAll(modifiedEntry, pattern, replacement)
		}
		for _, issuer := range strings.Split(modifiedEntry, ",") {
			cert, ok := knownCerts[issuer]
			if !ok {
				t.Fatalf("Unknown issuer %v in chain for %v: %v", issuer, parent, chain)
			}

			chainEntry += cert
		}
		result = append(result, chainEntry)
	}

	return result
}

func (c CBValidateChain) FindNameForCert(t testing.TB, cert string, knownCerts map[string]string) string {
	for issuer, known := range knownCerts {
		if strings.TrimSpace(known) == strings.TrimSpace(cert) {
			return issuer
		}
	}

	t.Fatalf("Unable to find cert:\n[%v]\nin known map:\n%v\n", cert, knownCerts)
	return ""
}

func (c CBValidateChain) PrettyChain(t testing.TB, chain []string, knownCerts map[string]string) []string {
	var prettyChain []string
	for _, cert := range chain {
		prettyChain = append(prettyChain, c.FindNameForCert(t, cert, knownCerts))
	}

	return prettyChain
}

func ToCertificate(t testing.TB, cert string) *x509.Certificate {
	t.Helper()

	block, _ := pem.Decode([]byte(cert))
	if block == nil {
		t.Fatalf("Unable to parse certificate: nil PEM block\n[%v]\n", cert)
	}

	ret, err := x509.ParseCertificate(block.Bytes)
	if err != nil {
		t.Fatalf("Unable to parse certificate: %v\n[%v]\n", err, cert)
	}

	return ret
}

func ToCRL(t testing.TB, crl string, issuer *x509.Certificate) *pkix.CertificateList {
	t.Helper()

	block, _ := pem.Decode([]byte(crl))
	if block == nil {
		t.Fatalf("Unable to parse CRL: nil PEM block\n[%v]\n", crl)
	}

	ret, err := x509.ParseCRL(block.Bytes)
	if err != nil {
		t.Fatalf("Unable to parse CRL: %v\n[%v]\n", err, crl)
	}

	if issuer != nil {
		if err := issuer.CheckCRLSignature(ret); err != nil {
			t.Fatalf("Unable to check CRL signature: %v\n[%v]\n[%v]\n", err, crl, issuer)
		}
	}

	return ret
}

func (c CBValidateChain) Run(t testing.TB, b *backend, s logical.Storage, knownKeys map[string]string, knownCerts map[string]string) {
	for issuer, chain := range c.Chains {
		resp, err := CBRead(b, s, "issuer/"+issuer)
		if err != nil {
			t.Fatalf("failed to get chain for issuer (%v): %v", issuer, err)
		}

		rawCurrentChain := resp.Data["ca_chain"].([]string)
		var currentChain []string
		for _, entry := range rawCurrentChain {
			currentChain = append(currentChain, strings.TrimSpace(entry))
		}

		// Ensure the issuer cert is always first.
		if currentChain[0] != knownCerts[issuer] {
			pretty := c.FindNameForCert(t, currentChain[0], knownCerts)
			t.Fatalf("expected certificate at index 0 to be self:\n[%v]\n[pretty: %v]\nis not the issuer's cert:\n[%v]\n[pretty: %v]", currentChain[0], pretty, knownCerts[issuer], issuer)
		}

		// Validate it against the expected chain.
		expectedChain := c.ChainToPEMs(t, issuer, chain, knownCerts)
		if len(currentChain) != len(expectedChain) {
			prettyCurrentChain := c.PrettyChain(t, currentChain, knownCerts)
			t.Fatalf("Lengths of chains for issuer %v mismatched: got %v vs expected %v:\n[%v]\n[pretty: %v]\n[%v]\n[pretty: %v]", issuer, len(currentChain), len(expectedChain), currentChain, prettyCurrentChain, expectedChain, chain)
		}

		for currentIndex, currentCert := range currentChain {
			// Chains might be forked so we may not be able to strictly validate
			// the chain against a single value. Instead, use strings.Contains
			// to validate the current cert is in the list of allowed
			// possibilities.
			if !strings.Contains(expectedChain[currentIndex], currentCert) {
				pretty := c.FindNameForCert(t, currentCert, knownCerts)
				t.Fatalf("chain mismatch at index %v for issuer %v: got cert:\n[%v]\n[pretty: %v]\nbut expected one of\n[%v]\n[pretty: %v]\n", currentIndex, issuer, currentCert, pretty, expectedChain[currentIndex], chain[currentIndex])
			}
		}

		// Due to alternate paths, the above doesn't ensure ensure each cert
		// in the chain is only used once. Validate that now.
		for thisIndex, thisCert := range currentChain {
			for otherIndex, otherCert := range currentChain[thisIndex+1:] {
				if thisCert == otherCert {
					thisPretty := c.FindNameForCert(t, thisCert, knownCerts)
					otherPretty := c.FindNameForCert(t, otherCert, knownCerts)
					otherIndex += thisIndex + 1
					t.Fatalf("cert reused in chain for %v:\n[%v]\n[pretty: %v / index: %v]\n[%v]\n[pretty: %v / index: %v]\n", issuer, thisCert, thisPretty, thisIndex, otherCert, otherPretty, otherIndex)
				}
			}
		}

		// Finally, validate that all certs verify something that came before
		// it. In the linear chain sense, this should strictly mean that the
		// parent comes before the child.
		for thisIndex, thisCertPem := range currentChain[1:] {
			thisIndex += 1 // Absolute index.
			parentCert := ToCertificate(t, thisCertPem)

			// Iterate backwards; prefer the most recent cert to the older
			// certs.
			foundCert := false
			for otherIndex := thisIndex - 1; otherIndex >= 0; otherIndex-- {
				otherCertPem := currentChain[otherIndex]
				childCert := ToCertificate(t, otherCertPem)

				if err := childCert.CheckSignatureFrom(parentCert); err == nil {
					foundCert = true
				}
			}

			if !foundCert {
				pretty := c.FindNameForCert(t, thisCertPem, knownCerts)
				t.Fatalf("malformed test scenario: certificate at chain index %v when validating %v does not validate any previous certificates:\n[%v]\n[pretty: %v]\n", thisIndex, issuer, thisCertPem, pretty)
			}
		}
	}
}

// Update an issuer
type CBUpdateIssuer struct {
	Name    string
	CAChain []string
	Usage   string
}

func (c CBUpdateIssuer) Run(t testing.TB, b *backend, s logical.Storage, knownKeys map[string]string, knownCerts map[string]string) {
	url := "issuer/" + c.Name
	data := make(map[string]interface{})
	data["issuer_name"] = c.Name

	resp, err := CBRead(b, s, url)
	if err != nil {
		t.Fatalf("failed to read issuer (%v): %v", c.Name, err)
	}

	if len(c.CAChain) == 1 && c.CAChain[0] == "existing" {
		data["manual_chain"] = resp.Data["manual_chain"]
	} else {
		data["manual_chain"] = c.CAChain
	}

	if c.Usage == "existing" {
		data["usage"] = resp.Data["usage"]
	} else if len(c.Usage) == 0 {
		data["usage"] = "read-only,issuing-certificates,crl-signing"
	} else {
		data["usage"] = c.Usage
	}

	_, err = CBWrite(b, s, url, data)
	if err != nil {
		t.Fatalf("failed to update issuer (%v): %v / body: %v", c.Name, err, data)
	}
}

// Issue a leaf, revoke it, and then validate it appears on the CRL.
type CBIssueLeaf struct {
	Issuer string
	Role   string
}

func (c CBIssueLeaf) IssueLeaf(t testing.TB, b *backend, s logical.Storage, knownKeys map[string]string, knownCerts map[string]string, errorMessage string) *logical.Response {
	// Write a role
	url := "roles/" + c.Role
	data := make(map[string]interface{})
	data["allow_localhost"] = true
	data["ttl"] = "200s"
	data["key_type"] = "ec"

	_, err := CBWrite(b, s, url, data)
	if err != nil {
		t.Fatalf("failed to update role (%v): %v / body: %v", c.Role, err, data)
	}

	// Issue the certificate.
	url = "issuer/" + c.Issuer + "/issue/" + c.Role
	data = make(map[string]interface{})
	data["common_name"] = "localhost"

	resp, err := CBWrite(b, s, url, data)
	if err != nil {
		if len(errorMessage) >= 0 {
			if !strings.Contains(err.Error(), errorMessage) {
				t.Fatalf("failed to issue cert (%v via %v): %v / body: %v\nExpected error message: %v", c.Issuer, c.Role, err, data, errorMessage)
			}

			return nil
		}

		t.Fatalf("failed to issue cert (%v via %v): %v / body: %v", c.Issuer, c.Role, err, data)
	}
	if resp == nil {
		t.Fatalf("failed to issue cert (%v via %v): nil response / body: %v", c.Issuer, c.Role, data)
	}

	raw_cert := resp.Data["certificate"].(string)
	cert := ToCertificate(t, raw_cert)
	raw_issuer := resp.Data["issuing_ca"].(string)
	issuer := ToCertificate(t, raw_issuer)

	// Validate issuer and signatures are good.
	if strings.TrimSpace(raw_issuer) != strings.TrimSpace(knownCerts[c.Issuer]) {
		t.Fatalf("signing certificate ended with wrong certificate for issuer %v:\n[%v]\n\nvs\n\n[%v]\n", c.Issuer, raw_issuer, knownCerts[c.Issuer])
	}

	if err := cert.CheckSignatureFrom(issuer); err != nil {
		t.Fatalf("failed to verify signature on issued certificate from %v: %v\n[%v]\n[%v]\n", c.Issuer, err, raw_cert, raw_issuer)
	}

	return resp
}

func (c CBIssueLeaf) RevokeLeaf(t testing.TB, b *backend, s logical.Storage, knownKeys map[string]string, knownCerts map[string]string, issueResponse *logical.Response, hasCRL bool, isDefault bool) {
	api_serial := issueResponse.Data["serial_number"].(string)
	raw_cert := issueResponse.Data["certificate"].(string)
	cert := ToCertificate(t, raw_cert)
	raw_issuer := issueResponse.Data["issuing_ca"].(string)
	issuer := ToCertificate(t, raw_issuer)

	// Revoke the certificate.
	url := "revoke"
	data := make(map[string]interface{})
	data["serial_number"] = api_serial
	resp, err := CBWrite(b, s, url, data)
	if err != nil {
		t.Fatalf("failed to revoke issued certificate (%v) under role %v / issuer %v: %v", api_serial, c.Role, c.Issuer, err)
	}
	if resp == nil {
		t.Fatalf("failed to revoke issued certificate (%v) under role %v / issuer %v: nil response", api_serial, c.Role, c.Issuer)
	}
	if _, ok := resp.Data["revocation_time"]; !ok {
		t.Fatalf("failed to revoke issued certificate (%v) under role %v / issuer %v: expected response parameter revocation_time was missing from response:\n%v", api_serial, c.Role, c.Issuer, resp.Data)
	}

	if !hasCRL {
		// Nothing further we can test here. We could re-enable CRL building
		// and check that it works, but that seems like a stretch. Other
		// issuers might be functionally the same as this issuer (and thus
		// this CRL will still be issued), but that requires more work to
		// check and verify.
		return
	}

	// Verify it is on this issuer's CRL.
	url = "issuer/" + c.Issuer + "/crl"
	resp, err = CBRead(b, s, url)
	if err != nil {
		t.Fatalf("failed to fetch CRL for issuer %v: %v", c.Issuer, err)
	}
	if resp == nil {
		t.Fatalf("failed to fetch CRL for issuer %v: nil response", c.Issuer)
	}

	raw_crl := resp.Data["crl"].(string)
	crl := ToCRL(t, raw_crl, issuer)

	foundCert := requireSerialNumberInCRL(nil, crl.TBSCertList, api_serial)
	if !foundCert {
		if !hasCRL && !isDefault {
			// Update the issuer we expect to find this on.
			resp, err := CBRead(b, s, "config/issuers")
			if err != nil {
				t.Fatalf("failed to read default issuer config: %v", err)
			}
			if resp == nil {
				t.Fatalf("failed to read default issuer config: nil response")
			}
			defaultID := resp.Data["default"].(issuerID).String()
			c.Issuer = defaultID
			issuer = nil
		}

		// Verify it is on the default issuer's CRL.
		url = "issuer/" + c.Issuer + "/crl"
		resp, err = CBRead(b, s, url)
		if err != nil {
			t.Fatalf("failed to fetch CRL for issuer %v: %v", c.Issuer, err)
		}
		if resp == nil {
			t.Fatalf("failed to fetch CRL for issuer %v: nil response", c.Issuer)
		}

		raw_crl = resp.Data["crl"].(string)
		crl = ToCRL(t, raw_crl, issuer)

		foundCert = requireSerialNumberInCRL(nil, crl.TBSCertList, api_serial)
	}

	if !foundCert {
		// If CRL building is broken, this is useful for finding which issuer's
		// CRL the revoked cert actually appears on.
		for issuerName := range knownCerts {
			url = "issuer/" + issuerName + "/crl"
			resp, err = CBRead(b, s, url)
			if err != nil {
				t.Fatalf("failed to fetch CRL for issuer %v: %v", issuerName, err)
			}
			if resp == nil {
				t.Fatalf("failed to fetch CRL for issuer %v: nil response", issuerName)
			}

			raw_crl := resp.Data["crl"].(string)
			crl := ToCRL(t, raw_crl, nil)

			for index, revoked := range crl.TBSCertList.RevokedCertificates {
				// t.Logf("[%v] revoked serial number: %v -- vs -- %v", index, revoked.SerialNumber, cert.SerialNumber)
				if revoked.SerialNumber.Cmp(cert.SerialNumber) == 0 {
					t.Logf("found revoked cert at index: %v for unexpected issuer: %v", index, issuerName)
					break
				}
			}
		}

		t.Fatalf("expected to find certificate with serial [%v] on issuer %v's CRL but was missing: %v revoked certs\n\nCRL:\n[%v]\n\nLeaf:\n[%v]\n\nIssuer (hasCRL: %v):\n[%v]\n", api_serial, c.Issuer, len(crl.TBSCertList.RevokedCertificates), raw_crl, raw_cert, hasCRL, raw_issuer)
	}
}

func (c CBIssueLeaf) Run(t testing.TB, b *backend, s logical.Storage, knownKeys map[string]string, knownCerts map[string]string) {
	if len(c.Role) == 0 {
		c.Role = "testing"
	}

	resp, err := CBRead(b, s, "config/issuers")
	if err != nil {
		t.Fatalf("failed to read default issuer config: %v", err)
	}
	if resp == nil {
		t.Fatalf("failed to read default issuer config: nil response")
	}
	defaultID := resp.Data["default"].(issuerID).String()

	resp, err = CBRead(b, s, "issuer/"+c.Issuer)
	if err != nil {
		t.Fatalf("failed to read issuer %v: %v", c.Issuer, err)
	}
	if resp == nil {
		t.Fatalf("failed to read issuer %v: nil response", c.Issuer)
	}
	ourID := resp.Data["issuer_id"].(issuerID).String()
	areDefault := ourID == defaultID

	for _, usage := range []string{"read-only", "crl-signing", "issuing-certificates", "issuing-certificates,crl-signing"} {
		ui := CBUpdateIssuer{
			Name:    c.Issuer,
			CAChain: []string{"existing"},
			Usage:   usage,
		}
		ui.Run(t, b, s, knownKeys, knownCerts)

		ilError := "requested usage issuing-certificates for issuer"
		hasIssuing := strings.Contains(usage, "issuing-certificates")
		if hasIssuing {
			ilError = ""
		}

		hasCRL := strings.Contains(usage, "crl-signing")

		resp := c.IssueLeaf(t, b, s, knownKeys, knownCerts, ilError)
		if resp == nil && !hasIssuing {
			continue
		}

		c.RevokeLeaf(t, b, s, knownKeys, knownCerts, resp, hasCRL, areDefault)
	}
}

// Stable ordering
func ensureStableOrderingOfChains(t testing.TB, b *backend, s logical.Storage, knownKeys map[string]string, knownCerts map[string]string) {
	// Start by fetching all chains
	certChains := make(map[string][]string)
	for issuer := range knownCerts {
		resp, err := CBRead(b, s, "issuer/"+issuer)
		if err != nil {
			t.Fatalf("failed to get chain for issuer (%v): %v", issuer, err)
		}

		rawCurrentChain := resp.Data["ca_chain"].([]string)
		var currentChain []string
		for _, entry := range rawCurrentChain {
			currentChain = append(currentChain, strings.TrimSpace(entry))
		}

		certChains[issuer] = currentChain
	}

	// Now, generate a bunch of arbitrary roots and validate the chain is
	// consistent.
	var runs []time.Duration
	for i := 0; i < 10; i++ {
		name := "stable-order-root-" + strconv.Itoa(i)
		step := CBGenerateRoot{
			Key:  name,
			Name: name,
		}
		step.Run(t, b, s, make(map[string]string), make(map[string]string))

		before := time.Now()
		_, err := CBDelete(b, s, "issuer/"+name)
		if err != nil {
			t.Fatalf("failed to delete temporary testing issuer %v: %v", name, err)
		}
		after := time.Now()
		elapsed := after.Sub(before)
		runs = append(runs, elapsed)

		for issuer := range knownCerts {
			resp, err := CBRead(b, s, "issuer/"+issuer)
			if err != nil {
				t.Fatalf("failed to get chain for issuer (%v): %v", issuer, err)
			}

			rawCurrentChain := resp.Data["ca_chain"].([]string)
			for index, entry := range rawCurrentChain {
				if strings.TrimSpace(entry) != certChains[issuer][index] {
					t.Fatalf("iteration %d - chain for issuer %v differed at index %d\n%v\nvs\n%v", i, issuer, index, entry, certChains[issuer][index])
				}
			}
		}
	}

	min := runs[0]
	max := runs[0]
	var avg time.Duration
	for _, run := range runs {
		if run < min {
			min = run
		}

		if run > max {
			max = run
		}

		avg += run
	}
	avg = avg / time.Duration(len(runs))

	t.Logf("Chain building run time (deletion) - min: %v / avg: %v / max: %v - entries: %v", min, avg, max, runs)
}

type CBTestStep interface {
	Run(t testing.TB, b *backend, s logical.Storage, knownKeys map[string]string, knownCerts map[string]string)
}

type CBTestScenario struct {
	Steps []CBTestStep
}

var chainBuildingTestCases = []CBTestScenario{
	{
		// This test builds up two cliques lined by a cycle, dropping into
		// a single intermediate.
		Steps: []CBTestStep{
			// Create a reissued certificate using the same key. These
			// should validate themselves.
			CBGenerateRoot{
				Key:        "key-root-old",
				Name:       "root-old-a",
				CommonName: "root-old",
			},
			CBValidateChain{
				Chains: map[string][]string{
					"root-old-a": {"self"},
				},
			},
			// After adding the second root using the same key and common
			// name, there should now be two certs in each chain.
			CBGenerateRoot{
				Key:        "key-root-old",
				Existing:   true,
				Name:       "root-old-b",
				CommonName: "root-old",
			},
			CBValidateChain{
				Chains: map[string][]string{
					"root-old-a": {"self", "root-old-b"},
					"root-old-b": {"self", "root-old-a"},
				},
			},
			// After adding a third root, there are now two possibilities for
			// each later chain entry.
			CBGenerateRoot{
				Key:        "key-root-old",
				Existing:   true,
				Name:       "root-old-c",
				CommonName: "root-old",
			},
			CBValidateChain{
				Chains: map[string][]string{
					"root-old-a": {"self", "root-old-bc", "root-old-bc"},
					"root-old-b": {"self", "root-old-ac", "root-old-ac"},
					"root-old-c": {"self", "root-old-ab", "root-old-ab"},
				},
				Aliases: map[string]string{
					"root-old-ac": "root-old-a,root-old-c",
					"root-old-ab": "root-old-a,root-old-b",
					"root-old-bc": "root-old-b,root-old-c",
				},
			},
			// If we generate an unrelated issuer, it shouldn't affect either
			// chain.
			CBGenerateRoot{
				Key:        "key-root-new",
				Name:       "root-new-a",
				CommonName: "root-new",
			},
			CBValidateChain{
				Chains: map[string][]string{
					"root-old-a": {"self", "root-old-bc", "root-old-bc"},
					"root-old-b": {"self", "root-old-ac", "root-old-ac"},
					"root-old-c": {"self", "root-old-ab", "root-old-ab"},
					"root-new-a": {"self"},
				},
				Aliases: map[string]string{
					"root-old-ac": "root-old-a,root-old-c",
					"root-old-ab": "root-old-a,root-old-b",
					"root-old-bc": "root-old-b,root-old-c",
				},
			},
			// Reissuing this new root should form another clique.
			CBGenerateRoot{
				Key:        "key-root-new",
				Existing:   true,
				Name:       "root-new-b",
				CommonName: "root-new",
			},
			CBValidateChain{
				Chains: map[string][]string{
					"root-old-a": {"self", "root-old-bc", "root-old-bc"},
					"root-old-b": {"self", "root-old-ac", "root-old-ac"},
					"root-old-c": {"self", "root-old-ab", "root-old-ab"},
					"root-new-a": {"self", "root-new-b"},
					"root-new-b": {"self", "root-new-a"},
				},
				Aliases: map[string]string{
					"root-old-ac": "root-old-a,root-old-c",
					"root-old-ab": "root-old-a,root-old-b",
					"root-old-bc": "root-old-b,root-old-c",
				},
			},
			// Generating a cross-signed cert from old->new should result
			// in all old clique certs showing up in the new root's paths.
			// This does not form a cycle.
			CBGenerateIntermediate{
				// In order to validate the existing root-new clique, we
				// have to reuse the key and common name here for
				// cross-signing.
				Key:        "key-root-new",
				Existing:   true,
				Name:       "cross-old-new",
				CommonName: "root-new",
				SKID:       "root-new-a",
				// Which old issuer is used here doesn't matter as they have
				// the same CN and key.
				Parent: "root-old-a",
			},
			CBValidateChain{
				Chains: map[string][]string{
					"root-old-a":    {"self", "root-old-bc", "root-old-bc"},
					"root-old-b":    {"self", "root-old-ac", "root-old-ac"},
					"root-old-c":    {"self", "root-old-ab", "root-old-ab"},
					"cross-old-new": {"self", "root-old-abc", "root-old-abc", "root-old-abc"},
					"root-new-a":    {"self", "root-new-b", "cross-old-new", "root-old-abc", "root-old-abc", "root-old-abc"},
					"root-new-b":    {"self", "root-new-a", "cross-old-new", "root-old-abc", "root-old-abc", "root-old-abc"},
				},
				Aliases: map[string]string{
					"root-old-ac":  "root-old-a,root-old-c",
					"root-old-ab":  "root-old-a,root-old-b",
					"root-old-bc":  "root-old-b,root-old-c",
					"root-old-abc": "root-old-a,root-old-b,root-old-c",
				},
			},
			// If we create a new intermediate off of the root-new, we should
			// simply add to the existing chain.
			CBGenerateIntermediate{
				Key:    "key-inter-a-root-new",
				Name:   "inter-a-root-new",
				Parent: "root-new-a",
			},
			CBValidateChain{
				Chains: map[string][]string{
					"root-old-a":    {"self", "root-old-bc", "root-old-bc"},
					"root-old-b":    {"self", "root-old-ac", "root-old-ac"},
					"root-old-c":    {"self", "root-old-ab", "root-old-ab"},
					"cross-old-new": {"self", "root-old-abc", "root-old-abc", "root-old-abc"},
					"root-new-a":    {"self", "root-new-b", "cross-old-new", "root-old-abc", "root-old-abc", "root-old-abc"},
					"root-new-b":    {"self", "root-new-a", "cross-old-new", "root-old-abc", "root-old-abc", "root-old-abc"},
					// If we find cross-old-new first, the old clique will be ahead
					// of the new clique; otherwise the new clique will appear first.
					"inter-a-root-new": {"self", "full-cycle", "full-cycle", "full-cycle", "full-cycle", "full-cycle", "full-cycle"},
				},
				Aliases: map[string]string{
					"root-old-ac":  "root-old-a,root-old-c",
					"root-old-ab":  "root-old-a,root-old-b",
					"root-old-bc":  "root-old-b,root-old-c",
					"root-old-abc": "root-old-a,root-old-b,root-old-c",
					"full-cycle":   "root-old-a,root-old-b,root-old-c,cross-old-new,root-new-a,root-new-b",
				},
			},
			// Now, if we cross-sign back from new to old, we should
			// form cycle with multiple reissued cliques. This means
			// all nodes will have the same chain.
			CBGenerateIntermediate{
				// In order to validate the existing root-old clique, we
				// have to reuse the key and common name here for
				// cross-signing.
				Key:        "key-root-old",
				Existing:   true,
				Name:       "cross-new-old",
				CommonName: "root-old",
				SKID:       "root-old-a",
				// Which new issuer is used here doesn't matter as they have
				// the same CN and key.
				Parent: "root-new-a",
			},
			CBValidateChain{
				Chains: map[string][]string{
					"root-old-a":       {"self", "root-old-bc", "root-old-bc", "both-cross-old-new", "both-cross-old-new", "root-new-ab", "root-new-ab"},
					"root-old-b":       {"self", "root-old-ac", "root-old-ac", "both-cross-old-new", "both-cross-old-new", "root-new-ab", "root-new-ab"},
					"root-old-c":       {"self", "root-old-ab", "root-old-ab", "both-cross-old-new", "both-cross-old-new", "root-new-ab", "root-new-ab"},
					"cross-old-new":    {"self", "cross-new-old", "both-cliques", "both-cliques", "both-cliques", "both-cliques", "both-cliques"},
					"cross-new-old":    {"self", "cross-old-new", "both-cliques", "both-cliques", "both-cliques", "both-cliques", "both-cliques"},
					"root-new-a":       {"self", "root-new-b", "both-cross-old-new", "both-cross-old-new", "root-old-abc", "root-old-abc", "root-old-abc"},
					"root-new-b":       {"self", "root-new-a", "both-cross-old-new", "both-cross-old-new", "root-old-abc", "root-old-abc", "root-old-abc"},
					"inter-a-root-new": {"self", "full-cycle", "full-cycle", "full-cycle", "full-cycle", "full-cycle", "full-cycle", "full-cycle"},
				},
				Aliases: map[string]string{
					"root-old-ac":        "root-old-a,root-old-c",
					"root-old-ab":        "root-old-a,root-old-b",
					"root-old-bc":        "root-old-b,root-old-c",
					"root-old-abc":       "root-old-a,root-old-b,root-old-c",
					"root-new-ab":        "root-new-a,root-new-b",
					"both-cross-old-new": "cross-old-new,cross-new-old",
					"both-cliques":       "root-old-a,root-old-b,root-old-c,root-new-a,root-new-b",
					"full-cycle":         "root-old-a,root-old-b,root-old-c,cross-old-new,cross-new-old,root-new-a,root-new-b",
				},
			},
			// Update each old root to only include itself.
			CBUpdateIssuer{
				Name:    "root-old-a",
				CAChain: []string{"root-old-a"},
			},
			CBUpdateIssuer{
				Name:    "root-old-b",
				CAChain: []string{"root-old-b"},
			},
			CBUpdateIssuer{
				Name:    "root-old-c",
				CAChain: []string{"root-old-c"},
			},
			// Step 19
			CBValidateChain{
				Chains: map[string][]string{
					"root-old-a":       {"self"},
					"root-old-b":       {"self"},
					"root-old-c":       {"self"},
					"cross-old-new":    {"self", "cross-new-old", "both-cliques", "both-cliques", "both-cliques", "both-cliques", "both-cliques"},
					"cross-new-old":    {"self", "cross-old-new", "both-cliques", "both-cliques", "both-cliques", "both-cliques", "both-cliques"},
					"root-new-a":       {"self", "root-new-b", "both-cross-old-new", "both-cross-old-new", "root-old-abc", "root-old-abc", "root-old-abc"},
					"root-new-b":       {"self", "root-new-a", "both-cross-old-new", "both-cross-old-new", "root-old-abc", "root-old-abc", "root-old-abc"},
					"inter-a-root-new": {"self", "full-cycle", "full-cycle", "full-cycle", "full-cycle", "full-cycle", "full-cycle", "full-cycle"},
				},
				Aliases: map[string]string{
					"root-old-ac":        "root-old-a,root-old-c",
					"root-old-ab":        "root-old-a,root-old-b",
					"root-old-bc":        "root-old-b,root-old-c",
					"root-old-abc":       "root-old-a,root-old-b,root-old-c",
					"root-new-ab":        "root-new-a,root-new-b",
					"both-cross-old-new": "cross-old-new,cross-new-old",
					"both-cliques":       "root-old-a,root-old-b,root-old-c,root-new-a,root-new-b",
					"full-cycle":         "root-old-a,root-old-b,root-old-c,cross-old-new,cross-new-old,root-new-a,root-new-b",
				},
			},
			// Reset the old roots; should get the original chains back.
			CBUpdateIssuer{
				Name: "root-old-a",
			},
			CBUpdateIssuer{
				Name: "root-old-b",
			},
			CBUpdateIssuer{
				Name: "root-old-c",
			},
			CBValidateChain{
				Chains: map[string][]string{
					"root-old-a":       {"self", "root-old-bc", "root-old-bc", "both-cross-old-new", "both-cross-old-new", "root-new-ab", "root-new-ab"},
					"root-old-b":       {"self", "root-old-ac", "root-old-ac", "both-cross-old-new", "both-cross-old-new", "root-new-ab", "root-new-ab"},
					"root-old-c":       {"self", "root-old-ab", "root-old-ab", "both-cross-old-new", "both-cross-old-new", "root-new-ab", "root-new-ab"},
					"cross-old-new":    {"self", "cross-new-old", "both-cliques", "both-cliques", "both-cliques", "both-cliques", "both-cliques"},
					"cross-new-old":    {"self", "cross-old-new", "both-cliques", "both-cliques", "both-cliques", "both-cliques", "both-cliques"},
					"root-new-a":       {"self", "root-new-b", "both-cross-old-new", "both-cross-old-new", "root-old-abc", "root-old-abc", "root-old-abc"},
					"root-new-b":       {"self", "root-new-a", "both-cross-old-new", "both-cross-old-new", "root-old-abc", "root-old-abc", "root-old-abc"},
					"inter-a-root-new": {"self", "full-cycle", "full-cycle", "full-cycle", "full-cycle", "full-cycle", "full-cycle", "full-cycle"},
				},
				Aliases: map[string]string{
					"root-old-ac":        "root-old-a,root-old-c",
					"root-old-ab":        "root-old-a,root-old-b",
					"root-old-bc":        "root-old-b,root-old-c",
					"root-old-abc":       "root-old-a,root-old-b,root-old-c",
					"root-new-ab":        "root-new-a,root-new-b",
					"both-cross-old-new": "cross-old-new,cross-new-old",
					"both-cliques":       "root-old-a,root-old-b,root-old-c,root-new-a,root-new-b",
					"full-cycle":         "root-old-a,root-old-b,root-old-c,cross-old-new,cross-new-old,root-new-a,root-new-b",
				},
			},
			CBIssueLeaf{Issuer: "root-old-a"},
			CBIssueLeaf{Issuer: "root-old-b"},
			CBIssueLeaf{Issuer: "root-old-c"},
			CBIssueLeaf{Issuer: "cross-old-new"},
			CBIssueLeaf{Issuer: "cross-new-old"},
			CBIssueLeaf{Issuer: "root-new-a"},
			CBIssueLeaf{Issuer: "root-new-b"},
			CBIssueLeaf{Issuer: "inter-a-root-new"},
		},
	},
	{
		// Here we're testing our chain capacity. First we'll create a
		// bunch of unique roots to form a cycle of length 10.
		Steps: []CBTestStep{
			CBGenerateRoot{
				Key:        "key-root-a",
				Name:       "root-a",
				CommonName: "root-a",
			},
			CBGenerateRoot{
				Key:        "key-root-b",
				Name:       "root-b",
				CommonName: "root-b",
			},
			CBGenerateRoot{
				Key:        "key-root-c",
				Name:       "root-c",
				CommonName: "root-c",
			},
			CBGenerateRoot{
				Key:        "key-root-d",
				Name:       "root-d",
				CommonName: "root-d",
			},
			CBGenerateRoot{
				Key:        "key-root-e",
				Name:       "root-e",
				CommonName: "root-e",
			},
			// They should all be disjoint to start.
			CBValidateChain{
				Chains: map[string][]string{
					"root-a": {"self"},
					"root-b": {"self"},
					"root-c": {"self"},
					"root-d": {"self"},
					"root-e": {"self"},
				},
			},
			// Start the cross-signing chains. These are all linear, so there's
			// no error expected; they're just long.
			CBGenerateIntermediate{
				Key:        "key-root-b",
				Existing:   true,
				Name:       "cross-a-b",
				CommonName: "root-b",
				Parent:     "root-a",
			},
			CBValidateChain{
				Chains: map[string][]string{
					"root-a":    {"self"},
					"cross-a-b": {"self", "root-a"},
					"root-b":    {"self", "cross-a-b", "root-a"},
					"root-c":    {"self"},
					"root-d":    {"self"},
					"root-e":    {"self"},
				},
			},
			CBGenerateIntermediate{
				Key:        "key-root-c",
				Existing:   true,
				Name:       "cross-b-c",
				CommonName: "root-c",
				Parent:     "root-b",
			},
			CBValidateChain{
				Chains: map[string][]string{
					"root-a":    {"self"},
					"cross-a-b": {"self", "root-a"},
					"root-b":    {"self", "cross-a-b", "root-a"},
					"cross-b-c": {"self", "b-or-cross", "b-chained-cross", "b-chained-cross"},
					"root-c":    {"self", "cross-b-c", "b-or-cross", "b-chained-cross", "b-chained-cross"},
					"root-d":    {"self"},
					"root-e":    {"self"},
				},
				Aliases: map[string]string{
					"b-or-cross":      "root-b,cross-a-b",
					"b-chained-cross": "root-b,cross-a-b,root-a",
				},
			},
			CBGenerateIntermediate{
				Key:        "key-root-d",
				Existing:   true,
				Name:       "cross-c-d",
				CommonName: "root-d",
				Parent:     "root-c",
			},
			CBValidateChain{
				Chains: map[string][]string{
					"root-a":    {"self"},
					"cross-a-b": {"self", "root-a"},
					"root-b":    {"self", "cross-a-b", "root-a"},
					"cross-b-c": {"self", "b-or-cross", "b-chained-cross", "b-chained-cross"},
					"root-c":    {"self", "cross-b-c", "b-or-cross", "b-chained-cross", "b-chained-cross"},
					"cross-c-d": {"self", "c-or-cross", "c-chained-cross", "c-chained-cross", "c-chained-cross", "c-chained-cross"},
					"root-d":    {"self", "cross-c-d", "c-or-cross", "c-chained-cross", "c-chained-cross", "c-chained-cross", "c-chained-cross"},
					"root-e":    {"self"},
				},
				Aliases: map[string]string{
					"b-or-cross":      "root-b,cross-a-b",
					"b-chained-cross": "root-b,cross-a-b,root-a",
					"c-or-cross":      "root-c,cross-b-c",
					"c-chained-cross": "root-c,cross-b-c,root-b,cross-a-b,root-a",
				},
			},
			CBGenerateIntermediate{
				Key:        "key-root-e",
				Existing:   true,
				Name:       "cross-d-e",
				CommonName: "root-e",
				Parent:     "root-d",
			},
			CBValidateChain{
				Chains: map[string][]string{
					"root-a":    {"self"},
					"cross-a-b": {"self", "root-a"},
					"root-b":    {"self", "cross-a-b", "root-a"},
					"cross-b-c": {"self", "b-or-cross", "b-chained-cross", "b-chained-cross"},
					"root-c":    {"self", "cross-b-c", "b-or-cross", "b-chained-cross", "b-chained-cross"},
					"cross-c-d": {"self", "c-or-cross", "c-chained-cross", "c-chained-cross", "c-chained-cross", "c-chained-cross"},
					"root-d":    {"self", "cross-c-d", "c-or-cross", "c-chained-cross", "c-chained-cross", "c-chained-cross", "c-chained-cross"},
					"cross-d-e": {"self", "d-or-cross", "d-chained-cross", "d-chained-cross", "d-chained-cross", "d-chained-cross", "d-chained-cross", "d-chained-cross"},
					"root-e":    {"self", "cross-d-e", "d-or-cross", "d-chained-cross", "d-chained-cross", "d-chained-cross", "d-chained-cross", "d-chained-cross", "d-chained-cross"},
				},
				Aliases: map[string]string{
					"b-or-cross":      "root-b,cross-a-b",
					"b-chained-cross": "root-b,cross-a-b,root-a",
					"c-or-cross":      "root-c,cross-b-c",
					"c-chained-cross": "root-c,cross-b-c,root-b,cross-a-b,root-a",
					"d-or-cross":      "root-d,cross-c-d",
					"d-chained-cross": "root-d,cross-c-d,root-c,cross-b-c,root-b,cross-a-b,root-a",
				},
			},
			CBIssueLeaf{Issuer: "root-a"},
			CBIssueLeaf{Issuer: "cross-a-b"},
			CBIssueLeaf{Issuer: "root-b"},
			CBIssueLeaf{Issuer: "cross-b-c"},
			CBIssueLeaf{Issuer: "root-c"},
			CBIssueLeaf{Issuer: "cross-c-d"},
			CBIssueLeaf{Issuer: "root-d"},
			CBIssueLeaf{Issuer: "cross-d-e"},
			CBIssueLeaf{Issuer: "root-e"},
			// Importing the new e->a cross fails because the cycle
			// it builds is too long.
			CBGenerateIntermediate{
				Key:                "key-root-a",
				Existing:           true,
				Name:               "cross-e-a",
				CommonName:         "root-a",
				Parent:             "root-e",
				ImportErrorMessage: "exceeds max size",
			},
			// Deleting any root and one of its crosses (either a->b or b->c)
			// should fix this.
			CBDeleteIssuer{"root-b"},
			CBDeleteIssuer{"cross-b-c"},
			// Importing the new e->a cross fails because the cycle
			// it builds is too long.
			CBGenerateIntermediate{
				Key:        "key-root-a",
				Existing:   true,
				Name:       "cross-e-a",
				CommonName: "root-a",
				Parent:     "root-e",
			},
			CBIssueLeaf{Issuer: "root-a"},
			CBIssueLeaf{Issuer: "cross-a-b"},
			CBIssueLeaf{Issuer: "root-c"},
			CBIssueLeaf{Issuer: "cross-c-d"},
			CBIssueLeaf{Issuer: "root-d"},
			CBIssueLeaf{Issuer: "cross-d-e"},
			CBIssueLeaf{Issuer: "root-e"},
			CBIssueLeaf{Issuer: "cross-e-a"},
		},
	},
	{
		// Here we're testing our clique capacity. First we'll create a
		// bunch of unique roots to form a cycle of length 10.
		Steps: []CBTestStep{
			CBGenerateRoot{
				Key:        "key-root",
				Name:       "root-a",
				CommonName: "root",
			},
			CBGenerateRoot{
				Key:        "key-root",
				Existing:   true,
				Name:       "root-b",
				CommonName: "root",
			},
			CBGenerateRoot{
				Key:        "key-root",
				Existing:   true,
				Name:       "root-c",
				CommonName: "root",
			},
			CBGenerateRoot{
				Key:        "key-root",
				Existing:   true,
				Name:       "root-d",
				CommonName: "root",
			},
			CBGenerateRoot{
				Key:        "key-root",
				Existing:   true,
				Name:       "root-e",
				CommonName: "root",
			},
			CBGenerateRoot{
				Key:        "key-root",
				Existing:   true,
				Name:       "root-f",
				CommonName: "root",
			},
			CBIssueLeaf{Issuer: "root-a"},
			CBIssueLeaf{Issuer: "root-b"},
			CBIssueLeaf{Issuer: "root-c"},
			CBIssueLeaf{Issuer: "root-d"},
			CBIssueLeaf{Issuer: "root-e"},
			CBIssueLeaf{Issuer: "root-f"},
			// Seventh reissuance fails.
			CBGenerateRoot{
				Key:          "key-root",
				Existing:     true,
				Name:         "root-g",
				CommonName:   "root",
				ErrorMessage: "excessively reissued certificate",
			},
			// Deleting one and trying again should succeed.
			CBDeleteIssuer{"root-a"},
			CBGenerateRoot{
				Key:        "key-root",
				Existing:   true,
				Name:       "root-g",
				CommonName: "root",
			},
			CBIssueLeaf{Issuer: "root-b"},
			CBIssueLeaf{Issuer: "root-c"},
			CBIssueLeaf{Issuer: "root-d"},
			CBIssueLeaf{Issuer: "root-e"},
			CBIssueLeaf{Issuer: "root-f"},
			CBIssueLeaf{Issuer: "root-g"},
		},
	},
	{
		// There's one more pathological case here: we have a cycle
		// which validates a clique/cycle via cross-signing. We call
		// the parent cycle new roots and the child cycle/clique the
		// old roots.
		Steps: []CBTestStep{
			// New Cycle
			CBGenerateRoot{
				Key:  "key-root-new-a",
				Name: "root-new-a",
			},
			CBGenerateRoot{
				Key:  "key-root-new-b",
				Name: "root-new-b",
			},
			CBGenerateIntermediate{
				Key:        "key-root-new-b",
				Existing:   true,
				Name:       "cross-root-new-b-sig-a",
				CommonName: "root-new-b",
				Parent:     "root-new-a",
			},
			CBGenerateIntermediate{
				Key:        "key-root-new-a",
				Existing:   true,
				Name:       "cross-root-new-a-sig-b",
				CommonName: "root-new-a",
				Parent:     "root-new-b",
			},
			// Old Cycle + Clique
			CBGenerateRoot{
				Key:  "key-root-old-a",
				Name: "root-old-a",
			},
			CBGenerateRoot{
				Key:        "key-root-old-a",
				Existing:   true,
				Name:       "root-old-a-reissued",
				CommonName: "root-old-a",
			},
			CBGenerateRoot{
				Key:  "key-root-old-b",
				Name: "root-old-b",
			},
			CBGenerateRoot{
				Key:        "key-root-old-b",
				Existing:   true,
				Name:       "root-old-b-reissued",
				CommonName: "root-old-b",
			},
			CBGenerateIntermediate{
				Key:        "key-root-old-b",
				Existing:   true,
				Name:       "cross-root-old-b-sig-a",
				CommonName: "root-old-b",
				Parent:     "root-old-a",
			},
			CBGenerateIntermediate{
				Key:        "key-root-old-a",
				Existing:   true,
				Name:       "cross-root-old-a-sig-b",
				CommonName: "root-old-a",
				Parent:     "root-old-b",
			},
			// Validate the chains are separate before linking them.
			CBValidateChain{
				Chains: map[string][]string{
					// New stuff
					"root-new-a":             {"self", "cross-root-new-a-sig-b", "root-new-b-or-cross", "root-new-b-or-cross"},
					"root-new-b":             {"self", "cross-root-new-b-sig-a", "root-new-a-or-cross", "root-new-a-or-cross"},
					"cross-root-new-b-sig-a": {"self", "any-root-new", "any-root-new", "any-root-new"},
					"cross-root-new-a-sig-b": {"self", "any-root-new", "any-root-new", "any-root-new"},

					// Old stuff
					"root-old-a":             {"self", "root-old-a-reissued", "cross-root-old-a-sig-b", "cross-root-old-b-sig-a", "both-root-old-b", "both-root-old-b"},
					"root-old-a-reissued":    {"self", "root-old-a", "cross-root-old-a-sig-b", "cross-root-old-b-sig-a", "both-root-old-b", "both-root-old-b"},
					"root-old-b":             {"self", "root-old-b-reissued", "cross-root-old-b-sig-a", "cross-root-old-a-sig-b", "both-root-old-a", "both-root-old-a"},
					"root-old-b-reissued":    {"self", "root-old-b", "cross-root-old-b-sig-a", "cross-root-old-a-sig-b", "both-root-old-a", "both-root-old-a"},
					"cross-root-old-b-sig-a": {"self", "all-root-old", "all-root-old", "all-root-old", "all-root-old", "all-root-old"},
					"cross-root-old-a-sig-b": {"self", "all-root-old", "all-root-old", "all-root-old", "all-root-old", "all-root-old"},
				},
				Aliases: map[string]string{
					"root-new-a-or-cross": "root-new-a,cross-root-new-a-sig-b",
					"root-new-b-or-cross": "root-new-b,cross-root-new-b-sig-a",
					"both-root-new":       "root-new-a,root-new-b",
					"any-root-new":        "root-new-a,cross-root-new-a-sig-b,root-new-b,cross-root-new-b-sig-a",
					"both-root-old-a":     "root-old-a,root-old-a-reissued",
					"both-root-old-b":     "root-old-b,root-old-b-reissued",
					"all-root-old":        "root-old-a,root-old-a-reissued,root-old-b,root-old-b-reissued,cross-root-old-b-sig-a,cross-root-old-a-sig-b",
				},
			},
			// Finally, generate an intermediate to link new->old. We
			// link root-new-a into root-old-a.
			CBGenerateIntermediate{
				Key:        "key-root-old-a",
				Existing:   true,
				Name:       "cross-root-old-a-sig-root-new-a",
				CommonName: "root-old-a",
				Parent:     "root-new-a",
			},
			CBValidateChain{
				Chains: map[string][]string{
					// New stuff should be unchanged.
					"root-new-a":             {"self", "cross-root-new-a-sig-b", "root-new-b-or-cross", "root-new-b-or-cross"},
					"root-new-b":             {"self", "cross-root-new-b-sig-a", "root-new-a-or-cross", "root-new-a-or-cross"},
					"cross-root-new-b-sig-a": {"self", "any-root-new", "any-root-new", "any-root-new"},
					"cross-root-new-a-sig-b": {"self", "any-root-new", "any-root-new", "any-root-new"},

					// Old stuff
					"root-old-a":             {"self", "root-old-a-reissued", "cross-root-old-a-sig-b", "cross-root-old-b-sig-a", "both-root-old-b", "both-root-old-b", "cross-root-old-a-sig-root-new-a", "any-root-new", "any-root-new", "any-root-new", "any-root-new"},
					"root-old-a-reissued":    {"self", "root-old-a", "cross-root-old-a-sig-b", "cross-root-old-b-sig-a", "both-root-old-b", "both-root-old-b", "cross-root-old-a-sig-root-new-a", "any-root-new", "any-root-new", "any-root-new", "any-root-new"},
					"root-old-b":             {"self", "root-old-b-reissued", "cross-root-old-b-sig-a", "cross-root-old-a-sig-b", "both-root-old-a", "both-root-old-a", "cross-root-old-a-sig-root-new-a", "any-root-new", "any-root-new", "any-root-new", "any-root-new"},
					"root-old-b-reissued":    {"self", "root-old-b", "cross-root-old-b-sig-a", "cross-root-old-a-sig-b", "both-root-old-a", "both-root-old-a", "cross-root-old-a-sig-root-new-a", "any-root-new", "any-root-new", "any-root-new", "any-root-new"},
					"cross-root-old-b-sig-a": {"self", "all-root-old", "all-root-old", "all-root-old", "all-root-old", "all-root-old", "cross-root-old-a-sig-root-new-a", "any-root-new", "any-root-new", "any-root-new", "any-root-new"},
					"cross-root-old-a-sig-b": {"self", "all-root-old", "all-root-old", "all-root-old", "all-root-old", "all-root-old", "cross-root-old-a-sig-root-new-a", "any-root-new", "any-root-new", "any-root-new", "any-root-new"},

					// Link
					"cross-root-old-a-sig-root-new-a": {"self", "root-new-a-or-cross", "any-root-new", "any-root-new", "any-root-new"},
				},
				Aliases: map[string]string{
					"root-new-a-or-cross": "root-new-a,cross-root-new-a-sig-b",
					"root-new-b-or-cross": "root-new-b,cross-root-new-b-sig-a",
					"both-root-new":       "root-new-a,root-new-b",
					"any-root-new":        "root-new-a,cross-root-new-a-sig-b,root-new-b,cross-root-new-b-sig-a",
					"both-root-old-a":     "root-old-a,root-old-a-reissued",
					"both-root-old-b":     "root-old-b,root-old-b-reissued",
					"all-root-old":        "root-old-a,root-old-a-reissued,root-old-b,root-old-b-reissued,cross-root-old-b-sig-a,cross-root-old-a-sig-b",
				},
			},
			CBIssueLeaf{Issuer: "root-new-a"},
			CBIssueLeaf{Issuer: "root-new-b"},
			CBIssueLeaf{Issuer: "cross-root-new-b-sig-a"},
			CBIssueLeaf{Issuer: "cross-root-new-a-sig-b"},
			CBIssueLeaf{Issuer: "root-old-a"},
			CBIssueLeaf{Issuer: "root-old-a-reissued"},
			CBIssueLeaf{Issuer: "root-old-b"},
			CBIssueLeaf{Issuer: "root-old-b-reissued"},
			CBIssueLeaf{Issuer: "cross-root-old-b-sig-a"},
			CBIssueLeaf{Issuer: "cross-root-old-a-sig-b"},
			CBIssueLeaf{Issuer: "cross-root-old-a-sig-root-new-a"},
		},
	},
	{
		// Test a dual-root of trust chaining example with different
		// lengths of chains.
		Steps: []CBTestStep{
			CBGenerateRoot{
				Key:  "key-root-new",
				Name: "root-new",
			},
			CBGenerateIntermediate{
				Key:    "key-inter-new",
				Name:   "inter-new",
				Parent: "root-new",
			},
			CBGenerateRoot{
				Key:  "key-root-old",
				Name: "root-old",
			},
			CBGenerateIntermediate{
				Key:    "key-inter-old-a",
				Name:   "inter-old-a",
				Parent: "root-old",
			},
			CBGenerateIntermediate{
				Key:    "key-inter-old-b",
				Name:   "inter-old-b",
				Parent: "inter-old-a",
			},
			// Now generate a cross-signed intermediate to merge these
			// two chains.
			CBGenerateIntermediate{
				Key:        "key-cross-old-new",
				Name:       "cross-old-new-signed-new",
				CommonName: "cross-old-new",
				Parent:     "inter-new",
			},
			CBGenerateIntermediate{
				Key:        "key-cross-old-new",
				Existing:   true,
				Name:       "cross-old-new-signed-old",
				CommonName: "cross-old-new",
				Parent:     "inter-old-b",
			},
			CBGenerateIntermediate{
				Key:    "key-leaf-inter",
				Name:   "leaf-inter",
				Parent: "cross-old-new-signed-new",
			},
			CBValidateChain{
				Chains: map[string][]string{
					"root-new":                 {"self"},
					"inter-new":                {"self", "root-new"},
					"cross-old-new-signed-new": {"self", "inter-new", "root-new"},
					"root-old":                 {"self"},
					"inter-old-a":              {"self", "root-old"},
					"inter-old-b":              {"self", "inter-old-a", "root-old"},
					"cross-old-new-signed-old": {"self", "inter-old-b", "inter-old-a", "root-old"},
					"leaf-inter":               {"self", "either-cross", "one-intermediate", "other-inter-or-root", "everything-else", "everything-else", "everything-else", "everything-else"},
				},
				Aliases: map[string]string{
					"either-cross":        "cross-old-new-signed-new,cross-old-new-signed-old",
					"one-intermediate":    "inter-new,inter-old-b",
					"other-inter-or-root": "root-new,inter-old-a",
					"everything-else":     "cross-old-new-signed-new,cross-old-new-signed-old,inter-new,inter-old-b,root-new,inter-old-a,root-old",
				},
			},
			CBIssueLeaf{Issuer: "root-new"},
			CBIssueLeaf{Issuer: "inter-new"},
			CBIssueLeaf{Issuer: "root-old"},
			CBIssueLeaf{Issuer: "inter-old-a"},
			CBIssueLeaf{Issuer: "inter-old-b"},
			CBIssueLeaf{Issuer: "cross-old-new-signed-new"},
			CBIssueLeaf{Issuer: "cross-old-new-signed-old"},
			CBIssueLeaf{Issuer: "leaf-inter"},
		},
	},
	{
		// Test just a single root.
		Steps: []CBTestStep{
			CBGenerateRoot{
				Key:  "key-root",
				Name: "root",
			},
			CBValidateChain{
				Chains: map[string][]string{
					"root": {"self"},
				},
			},
			CBIssueLeaf{Issuer: "root"},
		},
	},
	{
		// Test root + intermediate.
		Steps: []CBTestStep{
			CBGenerateRoot{
				Key:  "key-root",
				Name: "root",
			},
			CBGenerateIntermediate{
				Key:    "key-inter",
				Name:   "inter",
				Parent: "root",
			},
			CBValidateChain{
				Chains: map[string][]string{
					"root":  {"self"},
					"inter": {"self", "root"},
				},
			},
			CBIssueLeaf{Issuer: "root"},
			CBIssueLeaf{Issuer: "inter"},
		},
	},
	{
		// Test root + intermediate, twice (simulating rotation without
		// chaining).
		Steps: []CBTestStep{
			CBGenerateRoot{
				Key:  "key-root-a",
				Name: "root-a",
			},
			CBGenerateIntermediate{
				Key:    "key-inter-a",
				Name:   "inter-a",
				Parent: "root-a",
			},
			CBGenerateRoot{
				Key:  "key-root-b",
				Name: "root-b",
			},
			CBGenerateIntermediate{
				Key:    "key-inter-b",
				Name:   "inter-b",
				Parent: "root-b",
			},
			CBValidateChain{
				Chains: map[string][]string{
					"root-a":  {"self"},
					"inter-a": {"self", "root-a"},
					"root-b":  {"self"},
					"inter-b": {"self", "root-b"},
				},
			},
			CBIssueLeaf{Issuer: "root-a"},
			CBIssueLeaf{Issuer: "inter-a"},
			CBIssueLeaf{Issuer: "root-b"},
			CBIssueLeaf{Issuer: "inter-b"},
		},
	},
	{
		// Test root + intermediate, twice, chained a->b.
		Steps: []CBTestStep{
			CBGenerateRoot{
				Key:  "key-root-a",
				Name: "root-a",
			},
			CBGenerateIntermediate{
				Key:    "key-inter-a",
				Name:   "inter-a",
				Parent: "root-a",
			},
			CBGenerateRoot{
				Key:  "key-root-b",
				Name: "root-b",
			},
			CBGenerateIntermediate{
				Key:    "key-inter-b",
				Name:   "inter-b",
				Parent: "root-b",
			},
			CBGenerateIntermediate{
				Key:        "key-root-b",
				Existing:   true,
				Name:       "cross-a-b",
				CommonName: "root-b",
				Parent:     "root-a",
			},
			CBValidateChain{
				Chains: map[string][]string{
					"root-a":    {"self"},
					"inter-a":   {"self", "root-a"},
					"root-b":    {"self", "cross-a-b", "root-a"},
					"inter-b":   {"self", "root-b", "cross-a-b", "root-a"},
					"cross-a-b": {"self", "root-a"},
				},
			},
			CBIssueLeaf{Issuer: "root-a"},
			CBIssueLeaf{Issuer: "inter-a"},
			CBIssueLeaf{Issuer: "root-b"},
			CBIssueLeaf{Issuer: "inter-b"},
			CBIssueLeaf{Issuer: "cross-a-b"},
		},
	},
}

func Test_CAChainBuilding(t *testing.T) {
	t.Parallel()
	for testIndex, testCase := range chainBuildingTestCases {
		b, s := CreateBackendWithStorage(t)

		knownKeys := make(map[string]string)
		knownCerts := make(map[string]string)
		for stepIndex, testStep := range testCase.Steps {
			t.Logf("Running %v / %v", testIndex, stepIndex)
			testStep.Run(t, b, s, knownKeys, knownCerts)
		}

		t.Logf("Checking stable ordering of chains...")
		ensureStableOrderingOfChains(t, b, s, knownKeys, knownCerts)
	}
}

func BenchmarkChainBuilding(benchies *testing.B) {
	for testIndex, testCase := range chainBuildingTestCases {
		name := "test-case-" + strconv.Itoa(testIndex)
		benchies.Run(name, func(bench *testing.B) {
			// Stop the timer as we setup the infra and certs.
			bench.StopTimer()
			bench.ResetTimer()

			b, s := CreateBackendWithStorage(bench)

			knownKeys := make(map[string]string)
			knownCerts := make(map[string]string)
			for _, testStep := range testCase.Steps {
				testStep.Run(bench, b, s, knownKeys, knownCerts)
			}

			// Run the benchmark.
			ctx := context.Background()
			sc := b.makeStorageContext(ctx, s)
			bench.StartTimer()
			for n := 0; n < bench.N; n++ {
				sc.rebuildIssuersChains(nil)
			}
		})
	}
}
