package main import ( "path/filepath" "strings" "testing" ) // The lab's first root, and the one a re-initialised CA generates in its place. const ( firstRoot = "-----BEGIN CERTIFICATE-----\nMIIBeFIRST\n-----END CERTIFICATE-----\n" secondRoot = "-----BEGIN CERTIFICATE-----\nMIIBeSECOND\n-----END CERTIFICATE-----\n" ) // A re-initialised CA does not need somebody to delete the cache by hand. // // autocert keeps its account key at one fixed name and reuses it for ever. When an internal CA is // re-initialised it has never heard of that account, rejects every use of it, and autocert has no // path back: nothing is re-registered, no order reaches the CA, and issuance stops with nothing // saying why. Naming the cache after the authority means the account is only ever found where it is // still valid — the new root lands in a directory with no account in it, and autocert registers. func TestANewCARootMeansANewAccountCache(t *testing.T) { const directory = "https://anchor.internal/acme/acme/directory" before := forThisAuthority("/var/lib/route-proxy/acme", directory, []byte(firstRoot)) after := forThisAuthority("/var/lib/route-proxy/acme", directory, []byte(secondRoot)) if before == after { t.Fatalf("a re-initialised CA reuses the account it was rejected for: %s", before) } } // And the SAME authority keeps the account it registered, restart after restart. // // This is the whole reason ACME_CACHE is required in the first place: an account and its // certificates that did not persist would be re-ordered on every restart, which works silently until // a rate limit says it does not. Whitespace around the delivered root is not a new authority — the // mesh writes that file, and a trailing newline coming or going must not throw away an account. func TestTheSameAuthorityKeepsItsAccount(t *testing.T) { const directory = "https://anchor.internal/acme/acme/directory" first := forThisAuthority("/var/lib/route-proxy/acme", directory, []byte(firstRoot)) again := forThisAuthority("/var/lib/route-proxy/acme", directory, []byte("\n"+firstRoot+"\n\n")) if first != again { t.Errorf("the same authority was given two caches, so every restart orders again:\n%s\n%s", first, again) } } // Staging and production are different authorities, and were sharing one account. // // The latent fault of the same shape: pointing ACME_DIRECTORY at production after testing against // staging reused the staging account, because the cache had no idea they were different. func TestStagingAndProductionDoNotShareAnAccount(t *testing.T) { staging := forThisAuthority("/acme", stagingDirectory, nil) production := forThisAuthority("/acme", "https://acme-v02.api.letsencrypt.org/directory", nil) if staging == production { t.Errorf("two issuers share one account: %s", staging) } } // It stays inside the directory the mesh gave it, and is a plain name. // // The mesh owns ACME_CACHE and mounts it; a name derived from a certificate that escaped it — or // that carried a separator out of the PEM — would put an account somewhere nothing persists. func TestTheAccountCacheStaysWhereTheMeshPutIt(t *testing.T) { const cache = "/var/lib/route-proxy/acme" got := forThisAuthority(cache, "https://anchor.internal/acme/acme/directory", []byte(firstRoot)) if !strings.HasPrefix(got, cache+"/") { t.Fatalf("the account cache is not under %s: %s", cache, got) } name := strings.TrimPrefix(got, cache+"/") if name != filepath.Base(got) || strings.ContainsAny(name, "/.") { t.Errorf("the account cache is not a plain name: %q", name) } }