checker-caa/checker/ccadb_test.go

104 lines
3.4 KiB
Go

package checker
import (
"slices"
"strings"
"testing"
)
// TestCCADBEmbedded asserts the shipped CSV parses cleanly. If this
// fails the build produced a broken binary, so fail loudly.
func TestCCADBEmbedded(t *testing.T) {
idx, err := loadCCADB()
if err != nil {
t.Fatalf("load embedded CCADB: %v", err)
}
if len(idx.bySKI) < 100 {
t.Errorf("expected >=100 SKI entries, got %d", len(idx.bySKI))
}
if len(idx.byDN) < 100 {
t.Errorf("expected >=100 DN entries, got %d", len(idx.byDN))
}
}
// TestLookup_LetsEncryptR10 exercises the AKI path against a well-known,
// currently-active intermediate.
func TestLookup_LetsEncryptR10(t *testing.T) {
domains, ok := Lookup("BBBCC347A5E4BCA9C6C3A4720C108DA235E1C8E8", "")
if !ok {
t.Fatal("expected Let's Encrypt R10 AKI to resolve")
}
if !slices.Contains(domains, "letsencrypt.org") {
t.Errorf("expected letsencrypt.org in domains, got %v", domains)
}
}
// TestLookup_CaseInsensitiveAKI ensures callers don't need to pre-
// uppercase the AKI.
func TestLookup_CaseInsensitiveAKI(t *testing.T) {
upper, ok := Lookup("BBBCC347A5E4BCA9C6C3A4720C108DA235E1C8E8", "")
if !ok {
t.Skip("fixture row missing from embedded CCADB")
}
lower, ok := Lookup("bbbcc347a5e4bca9c6c3a4720c108da235e1c8e8", "")
if !ok {
t.Fatal("lowercase AKI should resolve too")
}
if strings.Join(upper, ",") != strings.Join(lower, ",") {
t.Errorf("upper %v != lower %v", upper, lower)
}
}
// TestLookup_DNFallback asserts the DN path works when AKI is empty.
// We use Go's pkix.Name.String-style comma DN and expect it to match
// CCADB's semicolon DN for the same subject.
func TestLookup_DNFallback(t *testing.T) {
// The ISRG Root X2 row uses SKI 7C4296AEDE4B483BFA92F89E8CCF6D8BA9723795
// and Subject "CN=ISRG Root X2; O=Internet Security Research Group; C=US".
// Go would render the same DN with commas, so normalizeDN should
// collapse both to the same key.
domains, ok := Lookup("", "CN=ISRG Root X2,O=Internet Security Research Group,C=US")
if !ok {
t.Fatal("expected ISRG Root X2 DN to resolve via byDN index")
}
if !slices.Contains(domains, "letsencrypt.org") {
t.Errorf("expected letsencrypt.org, got %v", domains)
}
}
// TestLookup_Unknown ensures false is returned cleanly.
func TestLookup_Unknown(t *testing.T) {
if _, ok := Lookup("0000000000000000000000000000000000000000", ""); ok {
t.Error("unknown AKI must not resolve")
}
if _, ok := Lookup("", "CN=This CA Does Not Exist"); ok {
t.Error("unknown DN must not resolve")
}
if _, ok := Lookup("", ""); ok {
t.Error("empty inputs must not resolve")
}
}
// TestNormalizeDN_SortsAndUppercases exercises the canonicalization
// used by the DN fallback. This is the part most likely to miscompare
// across CSV formatting variations.
func TestNormalizeDN_SortsAndUppercases(t *testing.T) {
a := normalizeDN("CN=Foo,O=Bar,C=US")
b := normalizeDN("c=US; cn=Foo; o=Bar")
if a != b {
t.Errorf("expected canonical equality:\n a=%q\n b=%q", a, b)
}
}
// TestSplitCAADomains handles the comma-separated cell format that
// DigiCert and similar CAs use.
func TestSplitCAADomains(t *testing.T) {
got := splitCAADomains("www.digicert.com, digicert.com, amazon.com")
want := []string{"www.digicert.com", "digicert.com", "amazon.com"}
if !slices.Equal(got, want) {
t.Errorf("splitCAADomains got %v want %v", got, want)
}
if splitCAADomains("") != nil {
t.Error("empty input should yield nil")
}
}