Skip to content

Add DNSSEC online signing for APP and ANAME/ALIAS records - #2109

Open
Hemsby wants to merge 2 commits into
TechnitiumSoftware:developfrom
Hemsby:feature/dnssec-online-signing-app-aname
Open

Add DNSSEC online signing for APP and ANAME/ALIAS records#2109
Hemsby wants to merge 2 commits into
TechnitiumSoftware:developfrom
Hemsby:feature/dnssec-online-signing-app-aname

Conversation

@Hemsby

@Hemsby Hemsby commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds DNSSEC "online" (dynamic, per-query) signing for APP records
and ANAME/ALIAS records, so both can now exist and work
correctly in a DNSSEC signed primary zone. Today
PrimaryZone.SetRecords/AddRecord/SignRRSet explicitly refuse
both types when the zone is signed, and even setting that aside, the
dynamically resolved answer was never signed.

This follows up on #1928, and discussions #825, #1357, and #1346,
where online signing has been described as a planned feature for
exactly this purpose. #1346 tied it to clustering support for key
distribution across nodes; clustering has since shipped (November
2025) and is stable, so that dependency shouldn't block the scope
covered here.

Scope

Two tiers, both reusing the zone's existing static NSEC/NSEC3 chain
and the existing SignRRSet path (the same one the offline re-sign
cycle already uses) rather than needing any new denial of existence
machinery:

  • Positive answers: the app's or ANAME's synthesized answer gets
    signed at query time with the zone's active ZSK, including the
    correct RFC 4035 Labels field and the RFC 4035 5.3.4 wildcard
    proof of cover for wildcard owned records.
  • Secure NODATA: when the app declines to answer (returns null) or
    ANAME resolution finds nothing, the response now includes a signed
    SOA and the zone's existing NSEC/NSEC3 proof of nonexistence at
    the record's own owner name.

Not in scope: proving nonexistence of an arbitrary name under a
wildcard owned dynamic record (a true NXDOMAIN, for a name the app
or ANAME never had an answer for) needs per query NSEC synthesis,
RFC 4470/4471 "white lies". That needs genuinely new denial of
existence machinery rather than reuse, so I scoped it out as a
follow up PR rather than bundling it here, especially since #1346
suggests you may already have a specific design in mind for it.

One real consequence of leaving white lies out: a resolver doing RFC
8198 aggressive NSEC/NSEC3 caching (the Unbound/BIND default) may
occasionally skip querying for a dynamic name if it already has an
unrelated cached NSEC/NSEC3 covering it. I looked at partially
working around this by declaring A/AAAA in the type bitmap at
APP/ANAME owned names, but that directly contradicts a NODATA
response at that same name (the bitmap can't simultaneously claim a
type exists and deny it). Confirmed with delv that a validator
correctly rejects that as bogus, so I left it out rather than ship
something half right.

Verification

Deployed to a scratch signed zone and validated with delv (BIND's
validating resolver tool) against a manually configured trust
anchor, real cryptographic chain of trust validation rather than
just checking an RRSIG is present. Covered non-wildcard and wildcard
owners, both record types, and both NSEC and NSEC3:

  • Positive answers: fully validated across APP/ANAME x wildcard/non
    wildcard x NSEC/NSEC3
  • NODATA at the record's own name: fully validated across the same
    combinations
  • Confirmed unsigned zone behavior is unchanged

Open to adjusting scope

If you'd rather this be shaped differently, tied to specific
clustering key distribution work, or folded into a broader design
you already have in mind, happy to rework it.

Hemsby added 2 commits August 19, 2026 14:01
Signs the per-query dynamic answer from APP records (Split Horizon,
GeoDistance/Country/Continent, Failover, WeightedRoundRobin, etc.)
and resolved ANAME/ALIAS records, and secures NODATA responses at
the record's own owner name, so both record types can now exist and
work correctly in DNSSEC signed primary zones. Covers non-wildcard
and wildcard owners, and both NSEC and NSEC3.

Previously PrimaryZone.SetRecords/AddRecord/SignRRSet explicitly
refused APP and ANAME records in a signed zone, and the dynamic
answer itself was never signed even apart from that guard.

Verified with delv against a real signed test zone under both NSEC
and NSEC3: positive answers, wildcard positive answers, and NODATA
at the record's own name all validate cleanly for both record types.

Out of scope: proving nonexistence of an arbitrary name under a
wildcard owned dynamic record (true NXDOMAIN) needs per query NSEC
synthesis (RFC 4470/4471 "white lies"), which this does not attempt.
As a result, a resolver doing RFC 8198 aggressive NSEC/NSEC3 caching
may occasionally skip querying for a dynamic name it has unrelated
cached denial records for. That is a known, accepted limitation
until white lies are implemented separately.
A non-wildcard APP record answering for a subdomain beneath its own
owner name (via the closest-ancestor fallback in InternalQuery) that
declines to answer returns NXDOMAIN, but was attaching the NODATA
proof for the record's own owner name instead of a real NXDOMAIN
proof of cover for the queried name. A validator has no way to
accept that as valid, so the response was bogus - confirmed with
delv (only one NSEC3 record was attached, and it didn't even cover
the queried name).

Adds AuthZoneManager.GetNSecProofOfNonExistenceNxDomain, generalized
from the existing GetNSecProofOfWildcardAnswer (now a thin wrapper
around it), and uses it from ProcessAPPAsync's NxDomain branch.
Reuses the same static NSEC/NSEC3 chain machinery as the rest of the
DNSSEC online signing work, no new denial-of-existence logic needed.

Verified with delv against dnssectest.local on 10.0.7.1 under both
NSEC and NSEC3: the fixed NXDOMAIN case now fully validates, and the
existing positive/NODATA test matrix from this branch still
validates cleanly (no regression).
@Hemsby

Hemsby commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

Closing the loop on tier 3 (white lies, RFC 4470/4471) as a follow-up to
this PR's dynamic APP/ANAME signing.

I implemented it against a wildcard-owned APP record that explicitly
returns NXDOMAIN for specific queries (the scenario this PR's write-up
flagged as future work), and it doesn't have a valid implementation.

Both RFC 4470 and RFC 4471 require testing whether a synthesized covering
NSEC actually brackets an existing name before using it in a response,
falling back to a standard non-synthesized proof if it does. For a
wildcard-owned APP record, the wildcard's own RRset always exists in the
zone by construction - that's the mechanism by which the app gets invoked
at all. So the half of the synthesis that would disprove the wildcard
always covers a name that genuinely exists, the mandated check always
fails, and there's no valid fallback either: the wildcard really does
cover the queried name, so no non-synthesized proof of its absence exists.
Confirmed empirically too - a validating resolver (delv) can't build a
consistent trust chain through the synthesized proof, independent of
nesting, TTL, or query shape.

The non-wildcard case (a plain APP record explicitly returning NXDOMAIN,
no wildcard involved) needs no synthesis - it's a static fact provable
with the real NSEC/NSEC3 chain, same machinery this PR already ships. That
part remains valid, just currently unexercised since no shipped app
returns an explicit NXDOMAIN from a non-wildcard APP record.

One genuine, unrelated bug did fall out of building the test setup for
this: #2110, a pre-existing SERVFAIL on DNSSEC-OK queries at NSEC-zone
empty non-terminals, confirmed present on master/develop and unrelated to
either PR.

Marking tier 3 as evaluated and not viable as scoped, rather than leaving
it open.

@ShreyasZare

Copy link
Copy Markdown
Member

Thanks for the PR. This will require studying things in details and it may differ significantly from what I have in my mind. Support for online signing is not immediately planned as there are several other things that are planned to be implemented before it. Thus, I am not sure if/when I can take look at this topic and come back to this PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants