Internet Protocols · DNS

DNSSEC Validation and the Chain of Trust

How DNSKEY, DS, and RRSIG records form a verifiable chain from the root to a leaf answer, and what a validator does when a link is missing or a signature has expired.

DNSSEC does not encrypt anything and does not hide names. It adds a digital signature to each RRset and a mechanism for a parent zone to vouch for a child’s public key. The result is that a resolver can distinguish an answer that came from the zone that owns the name from an answer that was injected somewhere along the path, but only if it can build an unbroken chain from a trust anchor it configured to the RRset it received.

The chain is the hard part. Signatures are cheap; deciding which public key to trust is the entire security model.

What a signed zone contains

Four record types carry the mechanism. DNSKEY holds a zone’s public keys. RRSIG holds a signature over one RRset, naming the key that made it. DS, which lives in the parent zone, holds a hash of a child’s DNSKEY. NSEC and NSEC3 prove absence. The first three are what build the chain; the fourth is covered in Authenticated Denial of Existence with NSEC and NSEC3.

A DNSKEY record carries flags, a protocol field that must be 3, an algorithm number, and the key material. Two flag bits matter here. The zone-key flag (0x0100) says the key may sign the zone’s data. The secure-entry-point flag (0x0001) marks the key an operator intends the parent’s DS to point at. The conventional split is operational rather than structural: a key-signing key has both bits set (flags 257) and signs the DNSKEY RRset itself, while a zone-signing key has only the zone bit (flags 256) and signs the rest of the zone. The separation exists so that the key which is used constantly, the zone-signing key, can be kept online and rotated frequently, while the key that the parent’s DS record points at, the key-signing key, can be kept offline and changed rarely. A validator only needs to trust the key-signing key; every other signature in the zone hangs off the DNSKEY RRset that key signed.

An RRSIG carries the type it covers, the algorithm, the key tag, the signer’s name, the original TTL, the signature’s inception and expiration times, a label count, and the signature bytes. The label count is how a validator knows whether the signature was made over the exact owner name or over a wildcard. A signature over *.example.org has a label count one lower than the number of labels in the expanded owner name, which is the signal that the answer was synthesised. Without that field, a wildcard-expanded answer would be indistinguishable from a real record at the queried name, and the security difference is large: a wildcard signature is valid for any name the wildcard covers, so an attacker who obtains one can answer for names the zone never published.

The original TTL field exists because a cached RRset’s TTL has counted down. The signature covers the RRset with the TTL set to the original value, and the validator substitutes that value before verifying, then checks that the received TTL does not exceed it. This is what stops a cache from extending a signature’s validity window by inflating a TTL, and it is why the TTL in a signed response is effectively bounded by the signature.

Building the chain, one DS at a time

The validator starts from a trust anchor: a DNSKEY or DS record for the root, configured out of band. It fetches the root’s DNSKEY RRset, verifies it against the anchor, and now trusts every key in that set that the anchor vouches for.

To move down, it needs the parent’s DS RRset for the child. Following example.org from the root, the org servers answer with a referral that carries the NS RRset for example.org and, if the child is signed, its DS RRset. The DS record contains a key tag, an algorithm, a digest type, and a digest of the child’s DNSKEY. The validator computes the same digest over each candidate DNSKEY from the child and looks for a match. A match means the child’s key is vouched for by the parent, and the validator can now verify the child’s DNSKEY RRset signature and, through it, every other RRset in the child zone.

Two properties of the DS design are worth naming. First, the DS RRset lives in the parent, so it is signed by the parent’s zone-signing key, and the parent’s authority is what is being delegated. Second, the digest covers the child’s owner name and DNSKEY RDATA, not the whole record, so the DS is independent of the child’s TTLs and of how the child serves the key. That independence means a DS stays valid while the child re-signs or changes TTLs, and it is what allows the child to publish a new key before the parent’s DS is changed to point at it.

def validate_chain(qname, qtype, resp, anchors):
    """Each step consumes a DS RRset for the next zone down."""
    zone, trusted_keys = ".", keys_from(anchors)
    while True:
        dnskey_rrset = fetch(zone, "DNSKEY")
        if not verify_rrsig(dnskey_rrset, trusted_keys):
            return "BOGUS"                      # key set not vouched for
        keys = [k for k in dnskey_rrset if k.flags & 0x0100]   # zone keys: ZSK and KSK
        child = next_cut(qname, zone)
        if child is None:
            return "SECURE" if verify_rrsig(resp.answer, keys) else "BOGUS"
        # The DS lives in the parent, so it comes from a referral, not the leaf answer.
        referral_msg = referral(zone, child)
        ds_rrset = referral_msg.authority_ds(child)
        if ds_rrset is None:
            return "INSECURE" if referral_msg.proves_no_ds(child) else "BOGUS"
        child_keys = fetch(child, "DNSKEY")
        matched = [k for k in child_keys if ds_matches(ds_rrset, k)]
        if not matched:
            return "BOGUS"                      # DS does not describe this key
        zone, trusted_keys = child, matched

The three outcomes are the whole vocabulary. SECURE means every link verified. INSECURE means a delegation had no DS, so the zone is unsigned and its answers carry no proof either way. BOGUS means a link existed and failed to verify, which is the case that must be treated as an attack or as a broken zone rather than as a missing feature.

The unsigned-delegation hole

The INSECURE outcome is the design’s largest concession. A parent can delegate to a child without publishing a DS record, and the validator has no way to tell whether that is because the child is unsigned or because an attacker stripped the DS from the referral. Both look identical.

The mitigation is that the parent’s referral must itself be authenticated. If the validator has verified the parent’s NS RRset and its signature, and the response provably contains no DS for the child, then the parent genuinely has no DS to publish, assuming the parent is not compromised. Proving the absence of a DS requires a signed denial, which is why NSEC and NSEC3 records appear even in referrals for unsigned children. A referral that simply omits the DS without a denial is not sufficient evidence, and a validator that accepts the omission is vulnerable to a downgrade attack that strips DS records in transit.

This is the reason the chain has to be walked from the top for every new zone, and why a validator cannot accept a cached “this zone is unsigned” conclusion without having verified the denial that established it. The cache entry for an insecure delegation is as security-sensitive as the cache entry for a key.

Clock skew, expiry, and what a validator does about it

Signatures have inception and expiration times, expressed as 32-bit unsigned seconds since the epoch. The comparison is against the validator’s clock, which means a validator with a wrong clock rejects every valid signature. RFC 4034 requires serial-number arithmetic for the comparison, so values near the 32-bit wrap in 2106 remain ordered correctly, but the format cannot express dates more than about 68 years in the past or future. That horizon is a limitation of the record format rather than of the algorithm.

A signature must stay valid across the zone’s own publication cycle and any caching, so validity windows are usually measured in days or weeks rather than hours. A zone that issues signatures valid for a day but re-signs only once a week will have expired signatures in caches between runs. The trade is between a short validity window, which limits the damage from a compromised key, and a long one, which tolerates operational delay. A zone that signs further ahead than its re-signing interval survives a missed run; the re-signing job is the component whose failure stays invisible until signatures start expiring.

A validator that encounters an expired signature returns BOGUS, not INSECURE. This distinction is deliberate: an expired signature is evidence that something is wrong, whereas a missing DS is evidence that nothing was promised. The user-visible consequence is that an expired signature makes a name unresolvable through a validating resolver while the same name resolves fine through a non-validating one, which is the classic symptom that sends operators looking at the wrong layer.

Key rollover and the windows it creates

Rolling a key-signing key requires the parent’s DS record to change. The procedure has two orderings, and both have a window in which something is inconsistent.

In the double-DS rollover, the parent publishes DS records for both the old and the new key, the child publishes both keys, and the old DS is removed only after the new one has propagated. At every point, a validator that has cached any version of the DS set can find a matching key. The cost is that two DS records exist, which doubles the size of the DS RRset in the referral and requires two parent updates with a propagation delay between them.

In the double-KSK rollover, the child publishes both keys and signs the DNSKEY RRset with both, then the parent swaps the DS in one step. This requires only one parent update, but there is an interval during which a validator holding the old DS and a child serving only the new key cannot verify. The interval is bounded by the DS TTL, and the risk is that a cache somewhere holds the old DS for longer than the operator expects.

The parent-side constraint is the reason rollovers take days. The DS TTL in the parent zone sets how long a stale DS can persist, and the child cannot shorten it. An operator who wants fast rollovers must negotiate a short DS TTL with the parent, which is not under the child operator’s control.

RFC 5011 adds a mechanism for trust anchors to update themselves. A resolver that holds a root key can watch the root’s DNSKEY RRset, and if a new key-signing key has been continuously present and valid for a hold-down period, it can add that key to its anchor set. The hold-down is the defence against an attacker who can forge the root’s DNSKEY set: the attack must be sustained for the whole period, which gives the operator time to notice. The mechanism only works for anchors the resolver already trusts, and it does not help with a key that was never published in a set the resolver could verify.

What validation does not give

It does not provide confidentiality. An observer sees the same queries and responses, and the signatures prove origin and integrity but reveal the zone’s contents just as plainly. It does not prevent a zone from publishing false data about itself: a signed lie is still a valid signature. It does not protect the delegation if the parent is compromised, because the parent’s signature on the DS is exactly what the attacker needs to replace.

It also does not remove the need for the record-level rules described in Record Semantics: CNAME Exclusivity and RRset Atomicity. A signature covers an RRset, and an RRset that violates the exclusivity rules is invalid regardless of whether its signature verifies. Validation is layered on top of the data model, not a substitute for it, and a validator that skips the structural checks can be made to accept a set the zone should never have served.

References

  1. RFC 4033: DNS Security Introduction and Requirements — IETF
  2. RFC 4034: Resource Records for the DNS Security Extensions — IETF
  3. RFC 4035: Protocol Modifications for the DNS Security Extensions — IETF
  4. RFC 5011: Automated Updates of DNS Security (DNSSEC) Trust Anchors — IETF