Skip to content

Commit

Permalink
feat: support DNSKEY from anchor
Browse files Browse the repository at this point in the history
  • Loading branch information
developStorm committed Nov 16, 2024
1 parent 288f731 commit 61a71c5
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 12 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@ jobs:
run: go mod tidy

- name: Run tests
run: go test ./...
run: go test -v ./...
30 changes: 27 additions & 3 deletions rootanchors.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ type KeyDigest struct {
Algorithm uint8 `xml:"Algorithm"`
DigestType uint8 `xml:"DigestType"`
Digest string `xml:"Digest"`
PublicKey string `xml:"PublicKey,omitempty"`
Flags uint16 `xml:"Flags,omitempty"`
}

// GetRawAnchors returns the raw XML data parsed into a TrustAnchor struct.
Expand All @@ -73,12 +75,12 @@ func GetValidDSRecords() map[uint16]dns.DS {

dsRecords := make(map[uint16]dns.DS)
for _, kd := range ta.KeyDigests {
if !isKeyDigestValid(kd) {
if !kd.isValid() {
continue
}

dsRecords[kd.KeyTag] = dns.DS{
Hdr: dns.RR_Header{Name: ta.Zone},
Hdr: dns.RR_Header{Name: ta.Zone, Rrtype: dns.TypeDS, Class: dns.ClassINET},
KeyTag: kd.KeyTag,
Algorithm: kd.Algorithm,
DigestType: kd.DigestType,
Expand All @@ -89,7 +91,29 @@ func GetValidDSRecords() map[uint16]dns.DS {
return dsRecords
}

func isKeyDigestValid(kd KeyDigest) bool {
// GetValidDNSKEYRecords returns root anchors as DNSKEY records defined by miekg/dns.
func GetValidDNSKEYRecords() map[uint16]*dns.DNSKEY {
ta := GetRawAnchors()

dnskeyRecords := make(map[uint16]*dns.DNSKEY)
for _, kd := range ta.KeyDigests {
if !kd.isValid() || kd.PublicKey == "" {
continue
}

dnskeyRecords[kd.KeyTag] = &dns.DNSKEY{
Hdr: dns.RR_Header{Name: ta.Zone, Rrtype: dns.TypeDNSKEY, Class: dns.ClassINET},
Flags: kd.Flags,
Protocol: 3, // RFC4034 Section 2.1.2
Algorithm: kd.Algorithm,
PublicKey: kd.PublicKey,
}
}

return dnskeyRecords
}

func (kd KeyDigest) isValid() bool {
validFrom, err := time.Parse(time.RFC3339, kd.ValidFrom)
if err != nil {
return false
Expand Down
108 changes: 100 additions & 8 deletions rootanchors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import (
rootanchors "github.com/zmap/go-dns-root-anchors"
)

// TestRootDNSKEYValidation tests DNSKEY validation against the root DS anchor.
func TestRootDNSKEYValidation(t *testing.T) {
// TestRootDSValidation checks root KSK against DS records from root trust anchor.
func TestRootDSValidation(t *testing.T) {
dnsKeys, err := queryRootKSK()
if err != nil {
t.Fatalf("Failed to query root DNSKEY records: %v", err)
Expand Down Expand Up @@ -38,21 +38,113 @@ func TestRootDNSKEYValidation(t *testing.T) {
}
}

// queryRootKSK queries the KSK records for the root zone (".").
func queryRootKSK() ([]*dns.DNSKEY, error) {
// TestRootDNSKEYValidation checks root DNSKEY records against KSKs from root trust anchor.
func TestRootDNSKEYValidation(t *testing.T) {
queriedDNSKEY, err := queryRoot(dns.TypeDNSKEY)
if err != nil {
t.Fatalf("Failed to query root DNSKEY records: %v", err)
}

KSKs := rootanchors.GetValidDNSKEYRecords()
t.Logf("KSKs: %v", KSKs)

var queriedKeys []dns.RR
rrsigs := make(map[uint16]*dns.RRSIG)
for _, rr := range queriedDNSKEY.Answer {
if ns, ok := rr.(*dns.DNSKEY); ok {
queriedKeys = append(queriedKeys, ns)
} else if rrsig, ok := rr.(*dns.RRSIG); ok {
rrsigs[rrsig.TypeCovered] = rrsig
} else {
t.Fatalf("Unexpected RR type: %T", rr)
}
}

rrsig, ok := rrsigs[dns.TypeDNSKEY]
if !ok {
t.Fatalf("RRSIG not found for DNSKEY records")
}

ksk, ok := KSKs[rrsig.KeyTag]
if !ok {
t.Fatalf("KSK not found for RRSIG with key tag %d", rrsig.KeyTag)
}

t.Logf("Verifying RRSet for RRSIG with key %v", ksk)
if err := rrsig.Verify(ksk, queriedKeys); err != nil {
t.Fatalf("Failed to verify RRSet: %v", err)
}

t.Logf("RRSet verified for RRSIG with key tag %d", rrsig.KeyTag)
}

// TestAnchorDNSKEYMatchesAnchorDSes checks that DNSKEY records from root trust anchor match DS records.
func TestAnchorDNSKEYMatchesAnchorDSes(t *testing.T) {
dnsKeys := rootanchors.GetValidDNSKEYRecords()
dsRecords := rootanchors.GetValidDSRecords()

for keyTag, dnsKey := range dnsKeys {
ds, ok := dsRecords[keyTag]
if !ok {
t.Fatalf("DS record not found for DNSKEY with key tag %d", keyTag)
}

computedDS := dnsKey.ToDS(ds.DigestType)
computedDigest := strings.ToUpper(computedDS.Digest)
t.Logf("Authentic digest: %s, computed digest: %s", ds.Digest, computedDigest)
if computedDigest != ds.Digest {
t.Fatalf("DS record mismatch for DNSKEY with key tag %d", keyTag)
}
}
}

// TestKSKAnchorsIncludesOnlineKSKs checks that the root trust anchor includes all online KSKs.
func TestKSKAnchorsIncludesOnlineKSKs(t *testing.T) {
anchors := rootanchors.GetValidDNSKEYRecords()
dnsKeys, err := queryRootKSK()
if err != nil {
t.Fatalf("Failed to query root DNSKEY records: %v", err)
}

for _, key := range dnsKeys {
anchor, ok := anchors[key.KeyTag()]
if !ok {
t.Fatalf("KSK not found for DNSKEY with key tag %d", key.KeyTag())
}

if key.PublicKey != anchor.PublicKey {
t.Fatalf("Public key mismatch for DNSKEY with key tag %d", key.KeyTag())
}

t.Logf("Public key matches for DNSKEY with key tag %d", key.KeyTag())
}
}

// queryRoot queries the root zone (".") for the specified DNS record type.
func queryRoot(recordType uint16) (*dns.Msg, error) {
c := new(dns.Client)
m := new(dns.Msg)

m.SetQuestion(".", dns.TypeDNSKEY)
m.SetQuestion(".", recordType)
m.SetEdns0(4096, true)

r, _, err := c.Exchange(m, "1.1.1.1:53")
r, _, err := c.Exchange(m, "198.41.0.4:53")
if err != nil {
return nil, fmt.Errorf("failed to query root DNSKEY: %v", err)
return nil, fmt.Errorf("failed to query root: %v", err)
}

if r.Rcode != dns.RcodeSuccess {
return nil, fmt.Errorf("invalid answer for root DNSKEY query: %v", r.Rcode)
return nil, fmt.Errorf("invalid answer for root query: %v", r.Rcode)
}

return r, nil
}

// queryRootKSK queries the KSK records for the root zone (".").
func queryRootKSK() ([]*dns.DNSKEY, error) {
r, err := queryRoot(dns.TypeDNSKEY)
if err != nil {
return nil, err
}

// Collect DNSKEY records from the response.
Expand Down

0 comments on commit 61a71c5

Please sign in to comment.