DNSSEC09

Status: Final

Purpose

  • Verify that SOA responses are signed and that SOA RRSIG records are time-valid, algorithm-supported, and cryptographically matched by DNSKEY records from the same nameserver.

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 nameserver execution fanout.

Algorithm And Decision Flow

  1. Emit TEST_CASE_START.
  2. Build nameserver set from the union of GlueNameservers and ApexNameservers (deduplicated by ns.String()), then deduplicate by IP.
  3. For each unique nameserver IP (parallelized):
    • If transport is disabled, emit IPV4_DISABLED or IPV6_DISABLED for rrtype DNSKEY and skip.
    • Query DNSKEY at child apex with DNSSEC enabled.
    • Skip nameserver if response is absent, non-NOERROR, non-AA, or has no apex DNSKEY records.
    • Query SOA at child apex with DNSSEC enabled over UDP (UseVC=false).
    • Skip nameserver if response is absent, non-NOERROR, non-AA, or has no apex SOA records.
    • If answer section has no RRSIG, mark nameserver for DS09_MISSING_RRSIG_IN_RESPONSE.
    • Else, for each answer-section RRSIG:
      • If inception is after test time, mark DS09_SOA_RRSIG_NOT_YET_VALID by keytag.
      • Else if expiration is before test time, mark DS09_SOA_RRSIG_EXPIRED by keytag.
      • Else if algorithm is unsupported, mark DS09_ALGO_NOT_SUPPORTED_BY_ZM by keytag+algo.
      • Else find matching DNSKEY records by keytag:
        • If none found, mark DS09_NO_MATCHING_DNSKEY.
        • Else verify signature over SOA RRset; if no candidate validates, mark DS09_RRSIG_NOT_VALID_BY_DNSKEY.
  4. Emit accumulated DS09 findings grouped by keytag/algo as applicable.
  5. Collect nameserver IPs that had RRSIGs and emitted no DS09 failure; if non-empty emit DS09_SOA_RRSIG_VALID.
  6. Emit TEST_CASE_END.

Per-NS SOA RRSIG Verification (steps 2-6)

child set = GlueNameservers ++ ApexNameservers; dedupe 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 / !AA  -> skip
    +- no apex DNSKEY in answer                  -> skip
   query SOA at z.Name, DNSSEC=on, UseVC=false
    +- resp.Msg == nil / RCODE != NOERROR / !AA  -> skip
    +- no apex SOA in answer                     -> skip
    +- no RRSIG records in SOA answer            -> missingRRSIG[ns]
    +- otherwise, for each RRSIG in SOA answer
      (now = packetTime(dnskeyResp); per-sig keyed by sig.KeyTag):
         sig.Inception > now    -> soaRRSIGNotYetValid[keytag][ns]
         sig.Expiration < now   -> soaRRSIGExpired[keytag][ns]
         algorithm unsupported  -> algoNotSupportedByZM[keytag][algo][ns]
         no DNSKEY with that keytag in answer
                                -> noMatchingDNSKEY[keytag][ns]
         verify over SOA rrset against matching DNSKEYs:
            no candidate validates -> rrsigNotValidByDNSKEY[keytag][ns]
            success                -> mark ns as having at least one valid RRSIG

Emit per category:
  missingRRSIG IPs                  -> DS09_MISSING_RRSIG_IN_RESPONSE  (addresses)
  per keytag soaRRSIGNotYetValid    -> DS09_SOA_RRSIG_NOT_YET_VALID    (keytag, addresses)
  per keytag soaRRSIGExpired        -> DS09_SOA_RRSIG_EXPIRED          (keytag, addresses)
  per keytag noMatchingDNSKEY       -> DS09_NO_MATCHING_DNSKEY         (keytag, addresses)
  per keytag rrsigNotValidByDNSKEY  -> DS09_RRSIG_NOT_VALID_BY_DNSKEY  (keytag, addresses)
  per (keytag, algo) algoNotSupportedByZM
                                    -> DS09_ALGO_NOT_SUPPORTED_BY_ZM  (keytag, algo_num,
                                                                       algo_mnemo, addresses)

  IPs that had any RRSIG AND no DS09 failure
                                    -> DS09_SOA_RRSIG_VALID            (addresses)

emit TEST_CASE_END

Emitted Tags (Possible Set)

TagEmitted when
DS09_ALGO_NOT_SUPPORTED_BY_ZMRRSIG verification requires an unsupported algorithm.
DS09_SOA_RRSIG_VALIDAt least one nameserver returned SOA with RRSIG and all RRSIG checks passed.
DS09_MISSING_RRSIG_IN_RESPONSESOA response has no answer-section RRSIG records.
DS09_NO_MATCHING_DNSKEYRRSIG keytag has no matching DNSKEY keytag.
DS09_RRSIG_NOT_VALID_BY_DNSKEYMatching DNSKEY candidates exist but none verify the SOA signature.
DS09_RSA_EXPONENT_UNSUPPORTEDSOA RRSIG could not be checked only because the matching DNSKEY is an RSA key whose public exponent exceeds what the local verifier supports (more than 64 bits).
DS09_SOA_RRSIG_EXPIREDSOA-related RRSIG expiration is before test time.
DS09_SOA_RRSIG_NOT_YET_VALIDSOA-related RRSIG inception is after test time.
IPV4_DISABLEDIPv4 transport is disabled for a queried nameserver (DNSKEY).
IPV6_DISABLEDIPv6 transport is disabled for a queried nameserver (DNSKEY).
TEST_CASE_ENDTestcase completion marker is emitted.
TEST_CASE_STARTTestcase start marker is emitted.

Tag Arguments

TagArgument keyTypeMeaning
DS09_ALGO_NOT_SUPPORTED_BY_ZMkeytagintRRSIG keytag associated with unsupported algorithm.
DS09_ALGO_NOT_SUPPORTED_BY_ZMalgo_numintUnsupported DNSSEC algorithm number.
DS09_ALGO_NOT_SUPPORTED_BY_ZMalgo_mnemostringUnsupported DNSSEC algorithm mnemonic.
DS09_ALGO_NOT_SUPPORTED_BY_ZMaddressesarray<string>Structured child nameserver IP list.
DS09_MISSING_RRSIG_IN_RESPONSEaddressesarray<string>Structured child nameserver IP list lacking SOA RRSIG.
DS09_NO_MATCHING_DNSKEYkeytagintRRSIG keytag with no matching DNSKEY keytag.
DS09_NO_MATCHING_DNSKEYaddressesarray<string>Structured child nameserver IP list.
DS09_RRSIG_NOT_VALID_BY_DNSKEYkeytagintRRSIG keytag that failed DNSKEY verification.
DS09_RRSIG_NOT_VALID_BY_DNSKEYaddressesarray<string>Structured child nameserver IP list.
DS09_RSA_EXPONENT_UNSUPPORTEDkeytagintRRSIG keytag whose matching DNSKEY RSA public exponent the local verifier cannot use.
DS09_RSA_EXPONENT_UNSUPPORTEDaddressesarray<string>Structured child nameserver IP list.
DS09_SOA_RRSIG_EXPIREDkeytagintRRSIG keytag with expired validity window.
DS09_SOA_RRSIG_EXPIREDaddressesarray<string>Structured child nameserver IP list.
DS09_SOA_RRSIG_NOT_YET_VALIDkeytagintRRSIG keytag with not-yet-valid validity window.
DS09_SOA_RRSIG_NOT_YET_VALIDaddressesarray<string>Structured child nameserver IP list.
DS09_SOA_RRSIG_VALIDaddressesarray<string>Structured child nameserver IP list where all SOA RRSIG checks passed.
IPV4_DISABLEDnsstringNameserver identity (ns name only; use address for IP) skipped on IPv4.
IPV4_DISABLEDaddressstringNameserver IP address for the same endpoint.
IPV4_DISABLEDrrtypestringrrtype skipped (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 (DNSKEY).
TEST_CASE_ENDtestcasestringTestcase display name (DNSSEC09).
TEST_CASE_STARTtestcasestringTestcase display name (DNSSEC09).

Severity Levels Per Tag

TagLevelNotes
DS09_ALGO_NOT_SUPPORTED_BY_ZMNOTICEDefault from share/profile.json (test_levels.DNSSEC).
DS09_MISSING_RRSIG_IN_RESPONSEERRORDefault from share/profile.json (test_levels.DNSSEC).
DS09_NO_MATCHING_DNSKEYERRORDefault from share/profile.json (test_levels.DNSSEC).
DS09_RRSIG_NOT_VALID_BY_DNSKEYERRORDefault from share/profile.json (test_levels.DNSSEC).
DS09_RSA_EXPONENT_UNSUPPORTEDNOTICEDefault from share/profile.json (test_levels.DNSSEC); carries zero score penalty (scoring TagPenalties).
DS09_SOA_RRSIG_EXPIREDERRORDefault from share/profile.json (test_levels.DNSSEC).
DS09_SOA_RRSIG_NOT_YET_VALIDERRORDefault from share/profile.json (test_levels.DNSSEC).
DS09_SOA_RRSIG_VALIDINFODefault from share/profile.json (test_levels.DNSSEC).
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: procedure text describes evaluating SOA RRSIG records. Gonemaster: iterates all answer-section RRSIG records and does not explicitly filter by TypeCovered == SOA before DS09 classification.
    • Upstream: describes validity checks against test execution time. Gonemaster: uses the DNSKEY response packet timestamp (packetTime(dnskeyResp)) as the reference time.
    • 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.
  • Potential upstream report:
    • no

Edge Cases And Limitations

  • Nameserver evaluation is deduplicated by IP; repeated names on one IP share one DS09 outcome.
  • Nameservers failing response-shape checks (Msg, NOERROR, AA, apex owner match) are skipped for DS09 findings.
  • If no usable DNSKEY records are found for a nameserver, SOA signing checks are skipped for that nameserver.
  • 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, or a leading zero byte in the exponent or modulus) is verified by gonemaster’s own RSA path instead, so the SOA RRSIG passes or fails like any other. Only when the exponent exceeds 64 bits is the finding reclassified from the ERROR DS09_RRSIG_NOT_VALID_BY_DNSKEY to the NOTICE DS09_RSA_EXPONENT_UNSUPPORTED. Such a nameserver is treated as indeterminate and is not counted as a DS09_SOA_RRSIG_VALID pass.