skip to content

Why must a DNSSEC denial of a name also rule out a wildcard, and why can NSEC3 need three records where NSEC needs two?

level: seniorimportance: nice to knowfreq 8%

answer

  1. lookup tries a wildcard next
  2. the longest existing ancestor
  3. hashing flattens the tree
  4. match one, cover two

basics

~20 s

A missing name might still match a wildcard, so a denial must rule one out. NSEC's cleartext names show which ancestor exists; NSEC3's hashes hide it, so NSEC3 must match the closest encloser and cover the next closer name and the wildcard.

solid answer

~40 s

DNS lookup tries an exact match, then a wildcard at the **closest encloser** - the longest existing ancestor of the query name. So a signed `NXDOMAIN` must prove two things: no such name, and no wildcard that would have matched. With `NSEC` that takes at most two records, often one, because a covering record's cleartext owner and next names show which ancestors exist. Hashing flattens the tree, so `NSEC3` must state the closest encloser explicitly: one record **matches** it, one **covers** the **next closer name** (one label longer), and one covers `*.<closest encloser>` - up to three. The reverse case matters too: a wildcard-expanded answer must carry a proof that the exact name does not exist, or an attacker could relabel a signed wildcard answer to hide a real record.

go deeper

for a junior

Recall that a wildcard record can answer for names that do not exist, so DNSSEC must prove no wildcard applied before a denial counts.

for a middle

Explain the closest encloser and next closer name, and walk through which records an NSEC and an NSEC3 name error carry.

for a senior

Explain why the matching closest-encloser record defeats a substituted wildcard denial, why wildcard answers carry proofs, and what this does to response size.

for a principal

Use the proof's size and complexity as one input when weighing NSEC3 against NSEC or compact denial for zones that rely heavily on wildcards.

## Wildcards turn denial into a two-part claim When a name server looks up a query name and finds no exact match, it checks for a **wildcard** - a record owned by `*.<ancestor>` - at one place only: the **closest encloser**, the longest ancestor of the query name that exists in the zone. If `*.<closest encloser>` exists, the server synthesises an answer from it. So "this name does not exist" is really two statements: 1. there is no exact match for the query name, and 2. there is no wildcard at the closest encloser that would have produced one. RFC 3833's threat analysis already noted that authenticated denial must prove the non-existence of applicable wildcards, and RFC 4035 section 5.4 makes it a validator requirement. Without the second part, an attacker could strip a legitimate wildcard answer and replace it with a denial of the exact name. ## With NSEC: up to two records Take `example.net` holding the apex, `app`, `db.internal` and `www`. `internal.example.net` owns no records but has a child, so it is an **empty non-terminal** - it exists in the tree. The chain contains `app.example.net NSEC db.internal.example.net`. For a query for `a.b.internal.example.net`, that single record does everything: the query sorts between `app` and `db.internal`, and so does `*.internal.example.net`, because `*` sorts before `d`. The cleartext next name `db.internal.example.net` also tells the resolver that `internal.example.net` exists, so the closest encloser is implicit. Elsewhere the two facts may need two records - RFC 4035 section 3.1.3.2 requires one covering the name and one ruling out the wildcard, sent once if they coincide. ## With NSEC3: the closest encloser proof `NSEC3` owner names are hashes, so the chain is a flat, hash-ordered list with no visible hierarchy. A covering record proves only "this hash falls in a gap" - it cannot show which ancestor exists. RFC 5155 section 7.2.1 adds an explicit **closest encloser proof**; for the same query: 1. an `NSEC3` that **matches** `internal.example.net` - its owner is the hash of that name, proving it exists; 2. an `NSEC3` that **covers** the **next closer name** `b.internal.example.net` - proving nothing exists one label further down, so neither does the query name; 3. an `NSEC3` that **covers** `*.internal.example.net` - proving no wildcard at the closest encloser. That is the "up to three" (some can coincide). RFC 7129 (Informational) explains why step 1 cannot be dropped: if a resolver accepted any record covering *some* wildcard, an attacker could pick a record covering the hash of `*.b.internal.example.net`, a name where no wildcard could legitimately exist, and use it to deny an expansion that should happen at the real closest encloser. Pinning the closest encloser fixes where the wildcard check must happen. In an opt-out zone the server may only be able to prove the **closest provable encloser** - the closest ancestor it can show exists - and the same pattern applies. ## Wildcard answers need denial too When an answer *is* synthesised, the resolver sees an `RRSIG` whose Labels field is smaller than the owner name's label count, which marks a wildcard expansion (RFC 4035 section 5.3.4; the field's layout belongs to the record-format topic). The response must also prove that no exact or closer match existed: | Response case | NSEC proof | NSEC3 proof | |---|---|---| | Name error | covers the name, covers the wildcard (one record may do both) | matches closest encloser, covers next closer name, covers wildcard | | No data | matches the name; bitmap lacks the type | matches the name; type and `CNAME` bits clear | | Wildcard answer | covers the name | covers the next closer name | | Wildcard no data | covers the name; matches the wildcard, type absent | closest encloser proof; matches the wildcard, type and `CNAME` absent | For a wildcard answer under `NSEC3` no matching record is needed: the expanded wildcard itself shows its parent exists (RFC 5155 section 7.2.6). RFC 7129 section 5.3 shows why the proof is mandatory: without it, an attacker could take a signed wildcard answer, rewrite its owner to a name that really exists with different data, and the resolver would accept the substitute. ## Operational consequences - Up to three `NSEC3` records plus signatures make negative answers larger, which pushes some responses past a small UDP limit. - RFC 8198 lets a validating resolver reuse cached wildcard proofs to synthesise positive answers itself. - RFC 9824's compact denial avoids the multi-record proof by answering a missing name as no-data with a single record.

  • Why does an NSEC3 wildcard answer not need a record matching the closest encloser?
    The expanded wildcard in the answer already proves its parent exists, since `*.internal.example.net` can only have expanded under an existing `internal.example.net`. RFC 5155 section 7.2.6 therefore requires only the `NSEC3` covering the next closer name, proving the query name itself did not exist and the right wildcard was used.
  • What is the closest provable encloser, and when does it differ from the closest encloser?
    It is the longest ancestor of the query name the server can prove exists. It differs only in an opt-out zone: if the real closest encloser is, or sits inside, an insecure delegation covered by an opt-out span, no `NSEC3` matches it, so the proof uses the nearest authoritative ancestor instead (RFC 5155 section 7.2.1).

saying these in an interview costs you the question

  • An NSEC3 NXDOMAIN proof needs only one record covering the hashed query name.
  • A wildcard-expanded answer needs no denial records because its RRSIG already validates.
  • A wildcard can match at any ancestor of the query name, not one fixed place.
  • A validator can read the closest encloser off the NSEC3 hash order.
  • Empty non-terminals have no NSEC3 records, so they cannot be closest enclosers.