package dnssec import ( "os" "path/filepath" "strings" "testing" ) // testdata holds key files written by named 9.20 with dnssec-policy: // example.test just signed (default policy), fast.test turned insecure // after its DS was removed, fast2.test turned insecure before any DS. func TestKeys(t *testing.T) { keys, err := Keys("testdata", "example.test.") if err != nil { t.Fatal(err) } if len(keys) != 1 { t.Fatalf("%d keys", len(keys)) } k := keys[0] if k.Tag != 22642 || k.Role != "CSK" || k.Algorithm != 13 || k.AlgorithmName != "ECDSAP256SHA256" || len(k.Files) != 3 { t.Errorf("key: %+v", k) } if k.DS == nil || k.DS.KeyTag != 22642 || k.DS.DigestType != 2 || len(k.DS.Digest) != 64 || !strings.HasPrefix(k.DS.Record, "example.test.\t3600\tIN\tDS\t22642 13 2 ") { t.Errorf("DS: %+v", k.DS) } if k.DNSKEYState != Rumoured || k.Goal != Omnipresent || k.PublishCDS == nil || k.PublishCDS.Format("2006-01-02 15:04") != "2026-10-09 19:08" { t.Errorf("state: %+v", k) } // "fast.test." mustn't pick up fast2.test's keys, nor "test." anyone's. if keys, _ := Keys("testdata", "fast.test."); len(keys) != 1 || keys[0].Tag != 642 { t.Errorf("fast.test: %+v", keys) } if files, _ := Files("testdata", "test."); len(files) != 0 { t.Errorf("test.: %v", files) } if files, _ := Files("no-such-dir", "test."); files != nil { t.Errorf("missing dir: %v", files) } } func TestAssess(t *testing.T) { load := func(zone string) []*Key { keys, err := Keys("testdata", zone) if err != nil { t.Fatal(err) } return keys } check := func(name string, p Phase, code string) { t.Helper() if p.Code != code { t.Errorf("%s: %s (%s), want %s", name, p.Code, p.Summary, code) } } check("unsigned", Assess("", nil), PhaseOff) check("left over", Assess("", load("example.test.")), PhaseLeftover) check("no keys yet", Assess("default", nil), PhaseStarting) p := Assess("default", load("example.test.")) check("new", p, PhaseSigning) if p.CanPublishDS || p.DSReadyAt == nil { t.Errorf("new: %+v", p) } check("DS removed", Assess("insecure", load("fast.test.")), PhaseDSExpiring) if Assess("insecure", load("fast.test.")).CanFinish { t.Error("DNSSEC mustn't be switched off while resolvers may still have the DS cached") } p = Assess("insecure", load("fast2.test.")) check("no DS ever", p, PhaseRemovingKeys) if !p.CanFinish { t.Error("no DS: switching off is safe") } check("insecure, no keys", Assess("insecure", nil), PhaseUnsigned) // The rest of the life cycle, from edited state files. edit := func(zone string, repl ...string) []*Key { dir := t.TempDir() files, _ := Files("testdata", zone) for _, f := range files { b, _ := os.ReadFile(filepath.Join("testdata", f)) s := strings.NewReplacer(repl...).Replace(string(b)) os.WriteFile(filepath.Join(dir, f), []byte(s), 0o644) } keys, err := Keys(dir, zone) if err != nil { t.Fatal(err) } return keys } ready := edit("example.test.", "DNSKEYState: rumoured", "DNSKEYState: omnipresent", "KRRSIGState: rumoured", "KRRSIGState: omnipresent", "DSState: hidden", "DSState: rumoured") p = Assess("default", ready) check("ready", p, PhaseReady) if !p.CanPublishDS || len(p.NeedsDS) != 1 || p.NeedsDS[0] != 22642 { t.Errorf("ready: %+v", p) } check("DS added", Assess("default", edit("example.test.", "DNSKEYState: rumoured", "DNSKEYState: omnipresent", "KRRSIGState: rumoured", "KRRSIGState: omnipresent", "DSChange:", "DSPublish: 20261010000000\nDSChange:")), PhaseDSPending) check("secure", Assess("default", edit("example.test.", "DNSKEYState: rumoured", "DNSKEYState: omnipresent", "KRRSIGState: rumoured", "KRRSIGState: omnipresent", "DSState: hidden", "DSState: omnipresent")), PhaseSecure) p = Assess("insecure", edit("fast.test.", "DSRemoved:", "XDSRemoved:")) check("turning off with the DS still there", p, PhaseRemovingDS) if p.CanFinish { t.Error("the DS is still at the parent") } } func TestDerivedFiles(t *testing.T) { dir := t.TempDir() zf := filepath.Join(dir, "example.com.zone") for _, f := range []string{zf, zf + ".jnl", zf + ".signed", zf + ".signed.jnl", zf + ".other"} { os.WriteFile(f, nil, 0o644) } got := DerivedFiles(zf) if len(got) != 3 { t.Errorf("%v", got) } }