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