DNSSEC02

Status: Final

Purpose

  • Verify that DS records found at the parent delegation match usable DNSKEYs in the child zone and that matching DNSKEYs can validate DNSKEY RRset signatures.

Preconditions And Inputs

  • Preconditions:
    • A zone.Zone object is available.
  • Required inputs:
  • Profile/config knobs that affect behavior:
    • net.ipv4 and net.ipv6: disabled transports are skipped with transport debug tags.
    • resolver.defaults.parallel: parallel parent and child query execution fanout.

Algorithm And Decision Flow

  1. Emit TEST_CASE_START.
  2. Collect DS records from parent nameservers:
    • Query each unique parent nameserver IP in parallel.
    • If transport is disabled, emit IPV4_DISABLED or IPV6_DISABLED for rrtype DS and skip.
    • Accept response only if DNSSEC response shape passes (NOERROR, OPT, DO, AA) and at least one DS record matches child zone owner name.
    • Add unique DS RDATA values to DS record set.
  3. If DS record set is empty, emit TEST_CASE_END and stop.
  4. Build child nameserver set from the union of GlueNameservers and ApexNameservers (deduplicated by ns.String()), then deduplicate by IP.
  5. For each unique child nameserver IP (parallelized):
    • If transport is disabled, emit IPV4_DISABLED or IPV6_DISABLED for rrtype DNSKEY and skip.
    • Query DNSKEY with DNSSEC enabled.
    • Require NOERROR, OPT, DO, AA, the TC flag clear, and at least one DNSKEY at child apex; otherwise skip. A truncated answer is a partial RRset, whichever transport delivered it.
    • Mark nameserver as responding.
    • For each DS record:
      • Find DNSKEY candidates by keytag and select a matching candidate (digest-checked when digest type is supported).
      • A keytag candidate whose DNSKEY algorithm differs from the DS algorithm field is never a match: add keytag/ns with both algorithm values to DS02_DS_ALGO_DNSKEY_MISMATCH and skip the candidate. This applies on both the supported-digest and unsupported-digest branches (RFC 4034 section 5.2 requires the DS algorithm field to equal the DNSKEY algorithm).
      • If no DNSKEY by keytag exists, add keytag/ns to DS02_NO_DNSKEY_FOR_DS.
      • If DNSKEY exists but DS digest check fails, add keytag/ns to DS02_NO_MATCH_DS_DNSKEY.
      • If DNSKEY has no ZONE flag, add keytag/ns to DS02_DNSKEY_NOT_FOR_ZONE_SIGNING and stop processing that DS.
      • If DNSKEY has no SEP flag, add keytag/ns to DS02_DNSKEY_NOT_SEP.
      • Track DNSKEY as DS-matching candidate for signature checks.
    • For each DS-matching DNSKEY:
      • Find DNSKEY-covering RRSIG by keytag and verify against DNSKEY.
      • Missing/invalid match contributes DS02_NO_MATCHING_DNSKEY_RRSIG.
      • Unsupported verification algorithm contributes DS02_ALGO_NOT_SUPPORTED_BY_ZM.
      • Signature verification failure contributes DS02_RRSIG_NOT_VALID_BY_DNSKEY.
      • Successful verification marks nameserver as having an RRSIG match for DS.
  6. Emit accumulated per-keytag findings (DS02_*) with merged addresses.
  7. Emit per-nameserver summary:
    • DS02_NO_VALID_DNSKEY_FOR_ANY_DS for responding nameservers with no DS-matching DNSKEY.
    • Else DS02_DNSKEY_NOT_SIGNED_BY_ANY_DS for responding nameservers with DS-matching DNSKEY but no validating RRSIG from those keys.
  8. For each DS-linked DNSKEY keytag whose RRSIG validated, emit DS02_MATCH_DS_DNSKEY with that keytag and the IPs where it validated. A keytag that failed on every nameserver is never reported as a match.
  9. Emit TEST_CASE_END.

Parent DS Collection (steps 2-4)

parentNS = ParentNameservers

For each unique parent NS IP (parallel; fan-out = resolver.defaults.parallel):

   transport disabled for DS    -> IPV4_DISABLED / IPV6_DISABLED, skip
   query DS at z.Name, DNSSEC=on
    +- resp.Msg == nil / RCODE != NOERROR / no EDNS / !DO / !AA  -> skip
    +- no DS records for z.Name in answer                        -> skip
    +- otherwise                                                 -> add DS rdata
                                                                    to dsRecords
                                                                    (dedupe by rdata)

After all tasks:
  len(dsRecords) == 0 -> emit TEST_CASE_END and stop
                         (no further DS02_* findings)

Per-Child DNSKEY Match and RRSIG Verify (steps 5-8)

child set = GlueNameservers ++ ApexNameservers; dedupe by ns.String(), then by IP

For each unique child NS IP (parallel; fan-out = resolver.defaults.parallel):

   transport disabled for DNSKEY -> IPV4_DISABLED / IPV6_DISABLED, skip
   query DNSKEY at z.Name, DNSSEC=on
    +- resp.Msg == nil / RCODE != NOERROR / no EDNS / !DO / !AA  -> skip
    +- TC set (over UDP or TCP)                                  -> skip
    +- no DNSKEY at z.Name in answer                             -> skip
    +- no records parse as *dns.DNSKEY                           -> skip
    +- otherwise                                                 -> mark responding

   For each DS in dsRecords:
     matchingKeytagDNSKEYs = DNSKEYs with key.KeyTag() == ds.KeyTag
     scan matchingKeytagDNSKEYs:
        ds.Algorithm != key.Algorithm
           -> dsAlgoMismatch[ds.KeyTag][ds.Algorithm/key.Algorithm], skip candidate
        digest supported AND key.ToDS(ds.DigestType).Digest == ds.Digest
           -> matchingDNSKEY, matchDSDNSKEY = true
        digest unsupported
           -> matchingDNSKEY, matchDSDNSKEY = true
     no match found but matchingKeytagDNSKEYs non-empty
        -> matchingDNSKEY = matchingKeytagDNSKEYs[0]

     matchingDNSKEY == nil                          -> noDNSKEYForDS[ds.KeyTag]
                                                      continue to next DS
     !matchDSDNSKEY                                 -> noMatchDSDNSKEY[ds.KeyTag]
     !FlagZONE                                      -> dnskeyNotForZoneSigning[ds.KeyTag]
                                                      continue to next DS
     !FlagSEP                                       -> dnskeyNotSEP[ds.KeyTag]
     mark hasDNSKEYMatchDS for this NS;
     record dnskey keytag for RRSIG checks

   For each DNSKEY in dnskeyMatchingDS (keytag from key.KeyTag()):
     matchingRRSIG = RRSIGs over DNSKEY rrset with same keytag
     verify each; classify:
       err == ErrAlg  -> algoNotSupportedByZM[keytag][algo]
       other error    -> rrsigNotValidByDNSKEY[keytag]
       success        -> foundMatch
     no matchingRRSIG OR no success
                       -> noMatchingDNSKEYRRSIG[keytag]
     otherwise         -> rrsigMatchDS[keytag] for this NS

Aggregation:
  per keytag per category, emit a tag with merged child NS IP list:
    DS02_NO_DNSKEY_FOR_DS, DS02_NO_MATCH_DS_DNSKEY,
    DS02_DNSKEY_NOT_FOR_ZONE_SIGNING, DS02_DNSKEY_NOT_SEP,
    DS02_NO_MATCHING_DNSKEY_RRSIG, DS02_RRSIG_NOT_VALID_BY_DNSKEY
  per (keytag, algo):
    DS02_ALGO_NOT_SUPPORTED_BY_ZM (algo_num, algo_mnemo)
  per (keytag, ds algo, dnskey algo):
    DS02_DS_ALGO_DNSKEY_MISMATCH (keytag, ds_key_algo_num, ds_key_algo_mnemo,
                                  algo_num, algo_mnemo, addresses)

  nsDNSKEY  = responding child NS without DS-matching DNSKEY
  nsRRSIG   = responding child NS without RRSIG match for any DS-matching DNSKEY
  nsDNSKEY non-empty -> DS02_NO_VALID_DNSKEY_FOR_ANY_DS (addresses)
  else nsRRSIG non-empty -> DS02_DNSKEY_NOT_SIGNED_BY_ANY_DS (addresses)

  per keytag in rrsigMatchDS -> DS02_MATCH_DS_DNSKEY (keytag, addresses)

emit TEST_CASE_END

Emitted Tags (Possible Set)

TagEmitted when
DS02_ALGO_NOT_SUPPORTED_BY_ZMDNSKEY RRSIG verification requires an unsupported algorithm for this build/runtime.
DS02_DS_ALGO_DNSKEY_MISMATCHA DNSKEY matches the DS keytag but the DS algorithm field differs from the DNSKEY algorithm; validating resolvers ignore such a DS record.
DS02_MATCH_DS_DNSKEYA DS-matching DNSKEY validates the DNSKEY RRset; emitted once per such keytag.
DS02_DNSKEY_NOT_FOR_ZONE_SIGNINGDS-matching DNSKEY is found but lacks ZONE flag.
DS02_DNSKEY_NOT_SEPDS-matching DNSKEY is found but lacks SEP flag.
DS02_DNSKEY_NOT_SIGNED_BY_ANY_DSNameserver has DS-matching DNSKEY(s), but no validating DNSKEY RRSIG from those keys.
DS02_NO_DNSKEY_FOR_DSNo DNSKEY with matching keytag exists for DS record.
DS02_NO_MATCHING_DNSKEY_RRSIGNo valid DNSKEY-covering RRSIG could be matched to a DS-matching DNSKEY.
DS02_NO_MATCH_DS_DNSKEYDNSKEY keytag match exists but DS digest/algorithm does not match DNSKEY data.
DS02_NO_VALID_DNSKEY_FOR_ANY_DSResponding child nameserver has no valid DS-matching DNSKEY for any DS.
DS02_RRSIG_NOT_VALID_BY_DNSKEYCandidate DNSKEY RRSIG was present but failed verification with matching DNSKEY.
DS02_RSA_EXPONENT_UNSUPPORTEDCandidate DNSKEY RRSIG could not be checked only because the matching DS-linked DNSKEY is an RSA key whose public exponent exceeds what the local verifier supports (more than 64 bits).
IPV4_DISABLEDIPv4 transport is disabled for a queried nameserver (DS or DNSKEY).
IPV6_DISABLEDIPv6 transport is disabled for a queried nameserver (DS or DNSKEY).
TEST_CASE_ENDTestcase completion marker is emitted.
TEST_CASE_STARTTestcase start marker is emitted.

Tag Arguments

TagArgument keyTypeMeaning
DS02_ALGO_NOT_SUPPORTED_BY_ZMkeytagintDNSKEY keytag associated with unsupported signature algorithm.
DS02_ALGO_NOT_SUPPORTED_BY_ZMalgo_numintDNSSEC algorithm number that verification cannot process.
DS02_ALGO_NOT_SUPPORTED_BY_ZMalgo_mnemostringDNSSEC algorithm mnemonic string.
DS02_ALGO_NOT_SUPPORTED_BY_ZMaddressesarray<string>Structured child nameserver IP list.
DS02_DS_ALGO_DNSKEY_MISMATCHkeytagintDS keytag whose DS algorithm field disagrees with the DNSKEY algorithm.
DS02_DS_ALGO_DNSKEY_MISMATCHds_key_algo_numintAlgorithm field value from the DS RDATA.
DS02_DS_ALGO_DNSKEY_MISMATCHds_key_algo_mnemostringDNSSEC algorithm mnemonic for the DS algorithm field value.
DS02_DS_ALGO_DNSKEY_MISMATCHalgo_numintAlgorithm of the keytag-matching DNSKEY record.
DS02_DS_ALGO_DNSKEY_MISMATCHalgo_mnemostringDNSSEC algorithm mnemonic for the DNSKEY algorithm.
DS02_DS_ALGO_DNSKEY_MISMATCHaddressesarray<string>Structured child nameserver IP list.
DS02_DNSKEY_NOT_FOR_ZONE_SIGNINGkeytagintDS/DNSKEY keytag lacking ZONE bit.
DS02_DNSKEY_NOT_FOR_ZONE_SIGNINGaddressesarray<string>Structured child nameserver IP list.
DS02_DNSKEY_NOT_SEPkeytagintDS/DNSKEY keytag lacking SEP bit.
DS02_DNSKEY_NOT_SEPaddressesarray<string>Structured child nameserver IP list.
DS02_DNSKEY_NOT_SIGNED_BY_ANY_DSaddressesarray<string>Structured child nameserver IP list.
DS02_MATCH_DS_DNSKEYkeytagintDS-matching DNSKEY keytag whose RRSIG over the DNSKEY RRset validates.
DS02_MATCH_DS_DNSKEYaddressesarray<string>Structured child nameserver IP list with DS-matching DNSKEY and valid RRSIG.
DS02_NO_DNSKEY_FOR_DSkeytagintDS keytag for which no DNSKEY was found.
DS02_NO_DNSKEY_FOR_DSaddressesarray<string>Structured child nameserver IP list.
DS02_NO_MATCHING_DNSKEY_RRSIGkeytagintDS-matching DNSKEY keytag lacking a validating DNSKEY RRSIG.
DS02_NO_MATCHING_DNSKEY_RRSIGaddressesarray<string>Structured child nameserver IP list.
DS02_NO_MATCH_DS_DNSKEYkeytagintDS keytag whose DS digest/algorithm did not match DNSKEY data.
DS02_NO_MATCH_DS_DNSKEYaddressesarray<string>Structured child nameserver IP list.
DS02_NO_VALID_DNSKEY_FOR_ANY_DSaddressesarray<string>Structured child nameserver IP list.
DS02_RRSIG_NOT_VALID_BY_DNSKEYkeytagintKeytag from DNSKEY RRSIG verification failure.
DS02_RRSIG_NOT_VALID_BY_DNSKEYaddressesarray<string>Structured child nameserver IP list.
DS02_RSA_EXPONENT_UNSUPPORTEDkeytagintDS-linked DNSKEY keytag whose RSA public exponent the local verifier cannot use.
DS02_RSA_EXPONENT_UNSUPPORTEDaddressesarray<string>Structured child nameserver IP list.
IPV4_DISABLEDnsstringNameserver identity (ns name only; use address for IP) skipped on IPv4.
IPV4_DISABLEDaddressstringNameserver IP address for the same endpoint.
IPV4_DISABLEDrrtypestringrrtype skipped (DS or DNSKEY).
IPV6_DISABLEDnsstringNameserver identity (ns name only; use address for IP) skipped on IPv6.
IPV6_DISABLEDaddressstringNameserver IP address for the same endpoint.
IPV6_DISABLEDrrtypestringrrtype skipped (DS or DNSKEY).
TEST_CASE_ENDtestcasestringTestcase display name (DNSSEC02).
TEST_CASE_STARTtestcasestringTestcase display name (DNSSEC02).

Severity Levels Per Tag

TagLevelNotes
DS02_ALGO_NOT_SUPPORTED_BY_ZMNOTICEDefault from share/profile.json (test_levels.DNSSEC).
DS02_DS_ALGO_DNSKEY_MISMATCHERRORDefault from share/profile.json (test_levels.DNSSEC).
DS02_DNSKEY_NOT_FOR_ZONE_SIGNINGERRORDefault from share/profile.json (test_levels.DNSSEC).
DS02_DNSKEY_NOT_SEPNOTICEDefault from share/profile.json (test_levels.DNSSEC).
DS02_DNSKEY_NOT_SIGNED_BY_ANY_DSERRORDefault from share/profile.json (test_levels.DNSSEC).
DS02_MATCH_DS_DNSKEYINFODefault from share/profile.json (test_levels.DNSSEC).
DS02_NO_DNSKEY_FOR_DSWARNINGDefault from share/profile.json (test_levels.DNSSEC).
DS02_NO_MATCHING_DNSKEY_RRSIGERRORDefault from share/profile.json (test_levels.DNSSEC).
DS02_NO_MATCH_DS_DNSKEYERRORDefault from share/profile.json (test_levels.DNSSEC).
DS02_NO_VALID_DNSKEY_FOR_ANY_DSERRORDefault from share/profile.json (test_levels.DNSSEC).
DS02_RRSIG_NOT_VALID_BY_DNSKEYERRORDefault from share/profile.json (test_levels.DNSSEC).
DS02_RSA_EXPONENT_UNSUPPORTEDNOTICEDefault from share/profile.json (test_levels.DNSSEC); carries zero score penalty (scoring TagPenalties).
IPV4_DISABLEDDEBUG2Default from share/profile.json (test_levels.DNSSEC).
IPV6_DISABLEDDEBUG2Default from share/profile.json (test_levels.DNSSEC).
TEST_CASE_ENDDEBUGDefault from share/profile.json (test_levels.DNSSEC).
TEST_CASE_STARTDEBUGDefault from share/profile.json (test_levels.DNSSEC).

Differences From Upstream

  • Differences (Upstream vs Gonemaster):
    • Upstream: explicitly describes a dedicated undelegated DS input branch in testcase flow. Gonemaster: DNSSEC02 implementation uses parent DS discovery path directly and has no separate testcase-local undelegated DS branch.
    • Upstream: does not explicitly specify testcase boundary and per-query transport debug emissions in this testcase summary. Gonemaster: emits TEST_CASE_START, TEST_CASE_END, IPV4_DISABLED, and IPV6_DISABLED.
    • Upstream: emits DS02_MATCH_DS_DNSKEY once for the zone, without naming a key. Gonemaster: emits it once per DS-linked keytag whose RRSIG validates and names that keytag, so a zone where one DS names a validating key and another names a key that signs nothing does not read as an all-clear.
    • Upstream: emits DS02_NO_MATCHING_DNSKEY_RRSIG at WARNING. Gonemaster: emits it at ERROR, because RFC 4035 section 2.2 requires the apex DNSKEY RRset to be signed by each algorithm in the parent DS RRset, and a validator that implements the referenced algorithm answers SERVFAIL for a zone that omits those signatures. See DIV-DS02-RRSIG-SEVERITY.
  • Potential upstream report:
    • yes

Edge Cases And Limitations

  • If parent DS discovery yields no DS records, testcase stops after boundary tags and emits no DS02 findings.
  • Child nameservers are deduplicated by IP before DNSKEY checks, so repeated names on one IP collapse into one probe context.
  • A DNSKEY answer with the TC flag set is skipped, including one received over TCP after the transport fell back from a truncated UDP answer. The nameserver is not marked responding and contributes to no DS02_* finding, so a missing RRSIG in a partial RRset is never reported as DS02_NO_MATCHING_DNSKEY_RRSIG. Connectivity05 reports the truncated TCP answer as CN05_TCP_ANSWER_TRUNCATED.
  • DS02_NO_VALID_DNSKEY_FOR_ANY_DS and DS02_DNSKEY_NOT_SIGNED_BY_ANY_DS are mutually exclusive by implementation (else if branch).
  • A zone whose DS RRset names one key that signs the DNSKEY RRset and one that does not gets DS02_NO_MATCHING_DNSKEY_RRSIG for the second keytag and DS02_MATCH_DS_DNSKEY for the first. The two findings name different keytags and both hold.
  • DS algorithm field mismatch: a DS whose algorithm field differs from the keytag-matching DNSKEY algorithm never counts as a match. A zone whose only DS has a mismatched algorithm therefore gets both DS02_DS_ALGO_DNSKEY_MISMATCH and the summary DS02_NO_VALID_DNSKEY_FOR_ANY_DS; a zone with an additional correct DS keeps DS02_MATCH_DS_DNSKEY alongside the mismatch tag. The keytag-fallback selection of the candidate for the ZONE/SEP flag checks is unaffected.
  • Large RSA public exponent handling: an RSA DNSKEY the DNS library refuses although RFC 3110 permits it (an exponent of more than 4 bytes or greater than 2^31-1, as with the .lv KSK and its exponent of 2^32+1, or a leading zero byte in the exponent or modulus) is verified by gonemaster’s own RSA path instead, so its RRSIGs pass or fail like any other. Only when the exponent exceeds 64 bits, a ceiling most validators share, is the finding reclassified from the ERROR DS02_RRSIG_NOT_VALID_BY_DNSKEY to the NOTICE DS02_RSA_EXPONENT_UNSUPPORTED. Such a key is treated as indeterminate rather than failed: it does not raise DS02_NO_MATCHING_DNSKEY_RRSIG, and when it is the sole reason a nameserver has no validating DS-linked key, DS02_DNSKEY_NOT_SIGNED_BY_ANY_DS is suppressed.