Add DNSSEC signing, key backups and Porkbun support

Zones get a DNSSEC setting: named signs them with BIND's default (or a
custom) dnssec-policy and inline signing, with keys in a per-view key
directory. Jarl shows the keys, the DS record and when it is safe to
publish it, checks the parent's DS records (check-ds, which also tells
named), and refuses steps that would break a signed domain: switching
straight to unsigned, finishing a turn-off too early, deleting or moving
a signed zone, TTLs above max-zone-ttl. Turning off goes through the
insecure policy; wipe starts over once the parent has no DS.

Backups store the DNSSEC keys and restore puts them back for named; the
backup unit gets CAP_DAC_READ_SEARCH to read them. Jarl raises a zone's
serial above named's whenever it writes an older one (after a restore).
The serials check compares the signed serial.

With a Porkbun API key in jarl.yaml, the registrar's name servers, glue
records and DS records can be shown and changed, each change checked
first (dry run, force to override).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
abrown38andClaude Opus 5.5 committed 2026-10-08 21:19:04 +00:00
1 parent 293e6ddd33
commit 5630e7a848
72 files changed
+5760 -153

No files matched your search

+320
View File
@@ -0,0 +1,320 @@
// Package dnssec reads the DNSSEC keys named keeps for the zones it signs
// (dnssec-policy with inline signing) and works out where a zone is in the
// steps of turning signing on or off.
//
// named writes three files per key into the zone's key-directory:
// K<zone>.+<alg>+<tag>.key (the public DNSKEY, world-readable),
// .private (the private key, readable only by named's user) and .state
// (the key's timing and state, see the dnssec-keymgr states in the BIND
// ARM). Jarl only reads the .key and .state files.
package dnssec
import (
"bufio"
"fmt"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"time"
"github.com/miekg/dns"
)
// Key states, as named writes them in .state files.
const (
Hidden = "hidden" // not in the zone (or the parent)
Rumoured = "rumoured" // published, but caches may not have it yet
Omnipresent = "omnipresent" // published long enough that everyone has it
Unretentive = "unretentive" // removed, but caches may still have it
)
// Status describes how a zone is signed.
type Status struct {
Policy string `json:"policy"`
// KeyDir is the zone's key-directory.
KeyDir string `json:"key_dir"`
Keys []*Key `json:"keys"`
Phase Phase `json:"phase"`
Named *NamedSigning `json:"named,omitempty"`
// NamedError says why named couldn't be asked.
NamedError string `json:"named_error,omitempty"`
// KeyError says why the key directory couldn't be read.
KeyError string `json:"key_error,omitempty"`
// Policies are the policies the zone could use: default and the
// custom dnssec-policy statements.
Policies []string `json:"policies"`
}
// NamedSigning is what named reports about a zone it signs.
type NamedSigning struct {
Secure bool `json:"secure"`
InlineSigning bool `json:"inline_signing"`
Serial uint32 `json:"serial"`
SignedSerial uint32 `json:"signed_serial,omitempty"`
NextKeyEvent string `json:"next_key_event,omitempty"`
}
// DS is a DS record for a key: what the parent zone publishes to vouch
// for it.
type DS struct {
KeyTag uint16 `json:"key_tag"`
Algorithm uint8 `json:"algorithm"`
DigestType uint8 `json:"digest_type"`
Digest string `json:"digest"`
// Record is the DS record in zone file format.
Record string `json:"record"`
}
// Key is one DNSSEC key of a zone.
type Key struct {
Tag uint16 `json:"tag"`
Algorithm uint8 `json:"algorithm"`
AlgorithmName string `json:"algorithm_name"`
// Role is CSK (signs everything: BIND's default policy), KSK (signs
// the DNSKEY set; the parent's DS points at it) or ZSK.
Role string `json:"role"`
// DNSKEY is the public key record, and DS its DS record (KSK and CSK
// only).
DNSKEY string `json:"dnskey,omitempty"`
DS *DS `json:"ds,omitempty"`
// States from the .state file.
Goal string `json:"goal,omitempty"`
DNSKEYState string `json:"dnskey_state,omitempty"`
DSState string `json:"ds_state,omitempty"`
ZoneRRSIG string `json:"zone_rrsig_state,omitempty"`
KeyRRSIG string `json:"key_rrsig_state,omitempty"`
// Times from the .state file.
Created *time.Time `json:"created,omitempty"`
Published *time.Time `json:"published,omitempty"`
Active *time.Time `json:"active,omitempty"`
Retired *time.Time `json:"retired,omitempty"`
Removed *time.Time `json:"removed,omitempty"`
// PublishCDS is when named publishes CDS/CDNSKEY records: from then on
// the DS can safely be added at the parent.
PublishCDS *time.Time `json:"publish_cds,omitempty"`
// DSPublished and DSRemoved are when named was told (or saw) that the
// parent added or removed the DS.
DSPublished *time.Time `json:"ds_published,omitempty"`
DSRemoved *time.Time `json:"ds_removed,omitempty"`
// Files are the key's file names in the key directory.
Files []string `json:"files"`
}
// SigningKey reports whether the parent's DS should point at the key.
func (k *Key) SigningKey() bool { return k.Role == "CSK" || k.Role == "KSK" }
// Gone reports whether named has finished removing the key: every state
// is hidden.
func (k *Key) Gone() bool {
for _, s := range []string{k.Goal, k.DNSKEYState, k.DSState, k.ZoneRRSIG, k.KeyRRSIG} {
if s != "" && s != Hidden {
return false
}
}
return true
}
// FilePrefix is how named's key files for zone (a name with the trailing
// dot) begin.
func FilePrefix(zone string) string { return "K" + strings.ToLower(zone) + "+" }
// Files lists the names of zone's key files in dir (every .key, .private
// and .state file, and any other file named with the zone's key prefix).
// A missing directory has none.
func Files(dir, zone string) ([]string, error) {
ents, err := os.ReadDir(dir)
if os.IsNotExist(err) {
return nil, nil
}
if err != nil {
return nil, err
}
prefix := FilePrefix(zone)
var out []string
for _, e := range ents {
if !e.IsDir() && strings.HasPrefix(strings.ToLower(e.Name()), strings.ToLower(prefix)) {
out = append(out, e.Name())
}
}
sort.Strings(out)
return out, nil
}
// Keys reads zone's keys from dir, ordered with the signing keys first.
func Keys(dir, zone string) ([]*Key, error) {
files, err := Files(dir, zone)
if err != nil {
return nil, err
}
byBase := map[string]*Key{}
var order []string
for _, f := range files {
ext := filepath.Ext(f)
base := strings.TrimSuffix(f, ext)
k := byBase[base]
if k == nil {
k = &Key{}
byBase[base] = k
order = append(order, base)
}
k.Files = append(k.Files, f)
switch ext {
case ".key":
if err := k.readPublic(filepath.Join(dir, f)); err != nil {
return nil, err
}
case ".state":
if err := k.readState(filepath.Join(dir, f)); err != nil {
return nil, err
}
}
}
var out []*Key
for _, b := range order {
k := byBase[b]
if k.DNSKEY == "" && k.Goal == "" {
continue // only a .private file: nothing to show
}
out = append(out, k)
}
sort.SliceStable(out, func(i, j int) bool {
if out[i].SigningKey() != out[j].SigningKey() {
return out[i].SigningKey()
}
return out[i].Tag < out[j].Tag
})
return out, nil
}
func (k *Key) readPublic(path string) error {
b, err := os.ReadFile(path)
if err != nil {
return err
}
zp := dns.NewZoneParser(strings.NewReader(string(b)), "", path)
for rr, ok := zp.Next(); ok; rr, ok = zp.Next() {
key, isKey := rr.(*dns.DNSKEY)
if !isKey {
continue
}
k.Tag = key.KeyTag()
k.Algorithm = key.Algorithm
k.AlgorithmName = dns.AlgorithmToString[key.Algorithm]
k.DNSKEY = key.String()
if k.Role == "" {
switch {
case key.Flags&dns.SEP != 0:
k.Role = "KSK"
default:
k.Role = "ZSK"
}
}
if key.Flags&dns.SEP != 0 {
if ds := key.ToDS(dns.SHA256); ds != nil {
k.DS = &DS{KeyTag: ds.KeyTag, Algorithm: ds.Algorithm, DigestType: ds.DigestType, Digest: strings.ToUpper(ds.Digest), Record: ds.String()}
}
}
return nil
}
if err := zp.Err(); err != nil {
return fmt.Errorf("%s: %w", path, err)
}
return fmt.Errorf("%s: no DNSKEY record", path)
}
func (k *Key) readState(path string) error {
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()
var ksk, zsk bool
sc := bufio.NewScanner(f)
for sc.Scan() {
line := sc.Text()
if strings.HasPrefix(line, ";") {
continue
}
name, v, ok := strings.Cut(line, ":")
if !ok {
continue
}
v = strings.TrimSpace(v)
t := func() *time.Time {
// 20261008190331 (Thu Oct 8 19:03:31 2026)
f := strings.Fields(v)
if len(f) == 0 {
return nil
}
ts, err := time.Parse("20060102150405", f[0])
if err != nil {
return nil
}
return &ts
}
switch name {
case "Algorithm":
if n, err := strconv.Atoi(v); err == nil && k.Algorithm == 0 {
k.Algorithm = uint8(n)
k.AlgorithmName = dns.AlgorithmToString[uint8(n)]
}
case "KSK":
ksk = v == "yes"
case "ZSK":
zsk = v == "yes"
case "Generated":
k.Created = t()
case "Published":
k.Published = t()
case "Active":
k.Active = t()
case "Retired":
k.Retired = t()
case "Removed":
k.Removed = t()
case "PublishCDS":
k.PublishCDS = t()
case "DSPublish":
k.DSPublished = t()
case "DSRemoved":
k.DSRemoved = t()
case "GoalState":
k.Goal = v
case "DNSKEYState":
k.DNSKEYState = v
case "DSState":
k.DSState = v
case "ZRRSIGState":
k.ZoneRRSIG = v
case "KRRSIGState":
k.KeyRRSIG = v
}
}
switch {
case ksk && zsk:
k.Role = "CSK"
case ksk:
k.Role = "KSK"
case zsk:
k.Role = "ZSK"
}
return sc.Err()
}
// ZoneFileSuffixes are the files named keeps next to a zone file: the
// journal of the zone Jarl wrote (.jnl, and .jbk while it rewrites it) and,
// for a signed zone, the signed copy and its journal.
var ZoneFileSuffixes = []string{".jnl", ".jbk", ".signed", ".signed.jnl", ".signed.jbk"}
// DerivedFiles returns the files named made next to zoneFile that exist.
func DerivedFiles(zoneFile string) []string {
var out []string
for _, s := range ZoneFileSuffixes {
if _, err := os.Lstat(zoneFile + s); err == nil {
out = append(out, zoneFile+s)
}
}
return out
}
+117
View File
@@ -0,0 +1,117 @@
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)
}
}
+170
View File
@@ -0,0 +1,170 @@
package dnssec
import (
"fmt"
"strings"
"time"
)
// Phases of a zone, from unsigned to signed and back. Turning signing on:
// off -> starting -> signing -> ready (add the DS at the parent) ->
// ds_pending -> secure. Turning it off: secure -> removing_ds (remove the
// DS at the parent) -> ds_expiring -> removing_keys -> unsigned -> off.
const (
PhaseOff = "off" // not signed, no keys
PhaseLeftover = "leftover_keys" // not signed, but old keys are still in the key directory
PhaseStarting = "starting" // signing is on in Jarl; named hasn't made keys yet
PhaseSigning = "signing" // keys made, zone signed; not yet safe to add the DS
PhaseReady = "ready" // safe to add the DS at the parent
PhaseDSPending = "ds_pending" // the DS is at the parent; named waits for its TTL
PhaseSecure = "secure" // signed, and the parent's DS is in place
PhaseRemovingDS = "removing_ds" // turning off: the DS must go from the parent first
PhaseDSExpiring = "ds_expiring" // the DS is gone; named waits until caches forget it
PhaseRemovingKeys = "removing_keys" // named removes the keys and signatures
PhaseUnsigned = "unsigned" // turning off has finished; switch DNSSEC off
)
// Phase says where a zone is and what comes next.
type Phase struct {
Code string `json:"code"`
Summary string `json:"summary"`
Next string `json:"next,omitempty"`
// DSReadyAt is when the DS can safely be added at the parent (phase
// signing).
DSReadyAt *time.Time `json:"ds_ready_at,omitempty"`
// CanPublishDS: the zone is ready for (or already has) a DS at the
// parent.
CanPublishDS bool `json:"can_publish_ds"`
// CanFinish: the zone is being turned off and the parent has no DS
// any more (as far as named knows), so DNSSEC can be switched off.
CanFinish bool `json:"can_finish"`
// NeedsDS lists the signing keys whose DS should be at the parent,
// and StaleDS those whose DS should be removed from it.
NeedsDS []uint16 `json:"needs_ds,omitempty"`
StaleDS []uint16 `json:"stale_ds,omitempty"`
}
// Assess works out the phase of a zone signed with policy ("" when Jarl
// doesn't sign it, "insecure" while signing is being removed) that has
// keys.
func Assess(policy string, keys []*Key) Phase {
var live []*Key // signing keys named keeps (or is introducing)
for _, k := range keys {
if k.SigningKey() && k.Goal == Omnipresent {
live = append(live, k)
}
}
switch {
case policy == "":
if len(keys) == 0 {
return Phase{Code: PhaseOff, Summary: "DNSSEC is off."}
}
return Phase{Code: PhaseLeftover, Summary: "DNSSEC is off, but keys from when the zone was signed are still in its key directory.",
Next: "Wipe them (jarl zone dnssec wipe) once you're sure the parent zone has no DS record for them, or turn signing on again to reuse them."}
case policy == "insecure":
return assessOff(keys)
}
if len(live) == 0 {
return Phase{Code: PhaseStarting, Summary: "Signing is on; named hasn't created the zone's keys yet.",
Next: "Wait a moment: named makes the keys when it loads the configuration."}
}
p := Phase{}
var notReady []*Key
for _, k := range live {
if k.DNSKEYState != Omnipresent || k.KeyRRSIG != Omnipresent {
notReady = append(notReady, k)
}
}
for _, k := range keys {
if k.SigningKey() && k.Goal == Hidden && k.DSState != Hidden && k.DSRemoved == nil {
p.StaleDS = append(p.StaleDS, k.Tag)
}
}
if len(notReady) > 0 {
p.Code = PhaseSigning
p.Summary = "named is signing the zone. Resolvers still treat it as unsigned, because the parent zone has no DS record for it."
for _, k := range notReady {
if k.PublishCDS != nil && (p.DSReadyAt == nil || k.PublishCDS.After(*p.DSReadyAt)) {
t := *k.PublishCDS
p.DSReadyAt = &t
}
}
if p.DSReadyAt != nil {
p.Next = fmt.Sprintf("Wait until %s before adding the DS at the parent: until then some resolvers may still have unsigned answers cached, and would reject them once the DS appears.", p.DSReadyAt.UTC().Format("Mon 2 Jan 15:04 MST"))
} else {
p.Next = "Wait until named has published the keys long enough for caches to pick them up before adding the DS."
}
return p
}
p.CanPublishDS = true
var pending, secure int
for _, k := range live {
switch {
case k.DSState == Omnipresent:
secure++
case k.DSPublished != nil:
pending++
default:
p.NeedsDS = append(p.NeedsDS, k.Tag)
}
}
switch {
case len(p.NeedsDS) > 0:
p.Code = PhaseReady
p.Summary = "The zone is signed and ready: add the DS record at the parent zone (your registrar) to turn validation on."
p.Next = "Add the DS record (" + tags(p.NeedsDS) + ") at your registrar, then run the DS check so named knows it's there."
case pending > 0:
p.Code = PhaseDSPending
p.Summary = "The parent zone has the DS record. named waits for the parent's TTL before it treats it as in place."
default:
p.Code = PhaseSecure
p.Summary = "The zone is signed and the parent zone's DS record points at its key: validating resolvers check its answers."
}
if len(p.StaleDS) > 0 && p.Next == "" {
p.Next = "Remove the DS record of the retired key " + tags(p.StaleDS) + " at your registrar, then run the DS check."
}
return p
}
func assessOff(keys []*Key) Phase {
var dsUp, dsGoing, keysLeft bool
for _, k := range keys {
if !k.SigningKey() {
if !k.Gone() {
keysLeft = true
}
continue
}
switch {
case k.DSState == Hidden || k.DSState == "":
case k.DSRemoved != nil:
dsGoing = true
default:
dsUp = true
}
if !k.Gone() {
keysLeft = true
}
}
switch {
case dsUp:
return Phase{Code: PhaseRemovingDS, Summary: "Turning DNSSEC off: named keeps the zone signed until the parent zone's DS record is gone.",
Next: "Remove the DS record at your registrar, then run the DS check so named knows. Don't switch DNSSEC off before that: the zone would fail to validate."}
case dsGoing:
return Phase{Code: PhaseDSExpiring, Summary: "Turning DNSSEC off: the parent's DS record is gone; named keeps signing until resolvers' cached copies of it expire (the parent's DS TTL, often a day).",
Next: "Wait: switching DNSSEC off now would make the zone fail to validate for resolvers that still have the DS cached."}
case keysLeft:
return Phase{Code: PhaseRemovingKeys, Summary: "Turning DNSSEC off: the parent has no DS any more, and named is removing the keys and signatures.",
Next: "Wait until named has finished, then switch DNSSEC off.", CanFinish: true}
}
return Phase{Code: PhaseUnsigned, Summary: "Turning DNSSEC off has finished: the zone is no longer signed.",
Next: "Switch DNSSEC off for the zone to finish (jarl zone dnssec disable), and wipe the old keys if you like.", CanFinish: true}
}
func tags(ts []uint16) string {
var s []string
for _, t := range ts {
s = append(s, fmt.Sprintf("key %d", t))
}
return strings.Join(s, ", ")
}
+6
View File
@@ -0,0 +1,6 @@
; This is a key-signing key, keyid 22642, for example.test.
; Created: 20261008190331 (Thu Oct 8 19:03:31 2026)
; Publish: 20261008190331 (Thu Oct 8 19:03:31 2026)
; Activate: 20261008190331 (Thu Oct 8 19:03:31 2026)
; SyncPublish: 20261009190831 (Fri Oct 9 19:08:31 2026)
example.test. 3600 IN DNSKEY 257 3 13 fiSDgJJGHyAp36ZNGOeb45gn+O1pF3hCbTUPHeJ3ztIuisGoywmo7Be3 +vUPyC6e9BmTCIRIeElB4f2o9HoPXg==
@@ -0,0 +1 @@
Private-key-format: v1.3 (test data, not a real key)
+19
View File
@@ -0,0 +1,19 @@
; This is the state of key 22642, for example.test.
Algorithm: 13
Length: 256
Lifetime: 0
KSK: yes
ZSK: yes
Generated: 20261008190331 (Thu Oct 8 19:03:31 2026)
Published: 20261008190331 (Thu Oct 8 19:03:31 2026)
Active: 20261008190331 (Thu Oct 8 19:03:31 2026)
PublishCDS: 20261009190831 (Fri Oct 9 19:08:31 2026)
DNSKEYChange: 20261008190331 (Thu Oct 8 19:03:31 2026)
ZRRSIGChange: 20261008190331 (Thu Oct 8 19:03:31 2026)
KRRSIGChange: 20261008190331 (Thu Oct 8 19:03:31 2026)
DSChange: 20261008190331 (Thu Oct 8 19:03:31 2026)
DNSKEYState: rumoured
ZRRSIGState: rumoured
KRRSIGState: rumoured
DSState: hidden
GoalState: omnipresent
+8
View File
@@ -0,0 +1,8 @@
; This is a key-signing key, keyid 642, for fast.test.
; Created: 20261008203220 (Thu Oct 8 20:32:20 2026)
; Publish: 20261008203220 (Thu Oct 8 20:32:20 2026)
; Activate: 20261008203220 (Thu Oct 8 20:32:20 2026)
; Inactive: 20261008203319 (Thu Oct 8 20:33:19 2026)
; Delete: 20261024213819 (Sat Oct 24 21:38:19 2026)
; SyncPublish: 20261008203234 (Thu Oct 8 20:32:34 2026)
fast.test. 3600 IN DNSKEY 257 3 13 D6O3u6ZWXwKPu/Y4acS2eRm1zKQ8LaN8zhvkr6KNWP+fEPgko6yLib94 J5KcUXpFcG6Y/jdxKM8FgeASRDq/ow==
@@ -0,0 +1 @@
Private-key-format: v1.3 (test data, not a real key)
+23
View File
@@ -0,0 +1,23 @@
; This is the state of key 642, for fast.test.
Algorithm: 13
Length: 256
Lifetime: 0
KSK: yes
ZSK: yes
Generated: 20261008203220 (Thu Oct 8 20:32:20 2026)
Published: 20261008203220 (Thu Oct 8 20:32:20 2026)
Active: 20261008203220 (Thu Oct 8 20:32:20 2026)
Retired: 20261008203319 (Thu Oct 8 20:33:19 2026)
Removed: 20261024213819 (Sat Oct 24 21:38:19 2026)
DSPublish: 20261008203249 (Thu Oct 8 20:32:49 2026)
DSRemoved: 20261008203329 (Thu Oct 8 20:33:29 2026)
PublishCDS: 20261008203234 (Thu Oct 8 20:32:34 2026)
DNSKEYChange: 20261008203234 (Thu Oct 8 20:32:34 2026)
ZRRSIGChange: 20261008203232 (Thu Oct 8 20:32:32 2026)
KRRSIGChange: 20261008203234 (Thu Oct 8 20:32:34 2026)
DSChange: 20261008203319 (Thu Oct 8 20:33:19 2026)
DNSKEYState: omnipresent
ZRRSIGState: omnipresent
KRRSIGState: omnipresent
DSState: unretentive
GoalState: hidden
+8
View File
@@ -0,0 +1,8 @@
; This is a key-signing key, keyid 28643, for fast2.test.
; Created: 20261008203340 (Thu Oct 8 20:33:40 2026)
; Publish: 20261008203340 (Thu Oct 8 20:33:40 2026)
; Activate: 20261008203340 (Thu Oct 8 20:33:40 2026)
; Inactive: 20261008203344 (Thu Oct 8 20:33:44 2026)
; Delete: 20261024213844 (Sat Oct 24 21:38:44 2026)
; SyncPublish: 20261008203354 (Thu Oct 8 20:33:54 2026)
fast2.test. 3600 IN DNSKEY 257 3 13 6WkqPp3kxXxMJtC9clsF4zTnZGPXaFRikSdMLG1eFExa4d0Uo+PtMbqo KHR/Qe601tO7yZoY8itie0aZzPaMiA==
@@ -0,0 +1 @@
Private-key-format: v1.3 (test data, not a real key)
+21
View File
@@ -0,0 +1,21 @@
; This is the state of key 28643, for fast2.test.
Algorithm: 13
Length: 256
Lifetime: 0
KSK: yes
ZSK: yes
Generated: 20261008203340 (Thu Oct 8 20:33:40 2026)
Published: 20261008203340 (Thu Oct 8 20:33:40 2026)
Active: 20261008203340 (Thu Oct 8 20:33:40 2026)
Retired: 20261008203344 (Thu Oct 8 20:33:44 2026)
Removed: 20261024213844 (Sat Oct 24 21:38:44 2026)
PublishCDS: 20261008203354 (Thu Oct 8 20:33:54 2026)
DNSKEYChange: 20261008203344 (Thu Oct 8 20:33:44 2026)
ZRRSIGChange: 20261008203344 (Thu Oct 8 20:33:44 2026)
KRRSIGChange: 20261008203344 (Thu Oct 8 20:33:44 2026)
DSChange: 20261008203340 (Thu Oct 8 20:33:40 2026)
DNSKEYState: unretentive
ZRRSIGState: unretentive
KRRSIGState: unretentive
DSState: hidden
GoalState: hidden