feat(eudi): optional DNSSEC check for did:web verifier domains - #621
feat(eudi): optional DNSSEC check for did:web verifier domains#621dobby-coder[bot] wants to merge 5 commits into
Conversation
Validates the DNSSEC chain of trust for a host's address records, from the IANA root trust anchors down to the leaf zone, using a recursive resolver only as transport (DO+CD; the AD bit is not trusted). Outcomes distinguish a failed validation (bogus) from an unsigned domain (insecure) and an unavailable check (indeterminate), so callers can warn without blocking. Part of #619 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The did:web document resolver and DID verifier validator accept an optional dnssec.Verifier. When configured, a bogus or missing DNSSEC chain for the verifier's domain surfaces as a typed warning on the requestor handed to the app (did_web_dnssec_invalid / did_web_dnssec_missing); it never blocks the session. Off by default, opt-in via client.SetDidWebDnssecCheck(true). Part of #619 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
VERDICT: approve
Rules Dobby 2 sign-off, merged with the code review.
Code review (Review Dobby 2): production code is correct. Built and ran the dnssec tests (12/12) and didweb tests green; gofmt and go vet clean. The openid4vp package was skipped locally because SQLCipher cgo is unavailable in the review container, but the CI test job (which installs libsqlcipher-dev) passes. One non-blocking nit, left inline: a test-only data race in the fake resolver, visible only under go test -race.
Rule check: no violations. Reviewer assigned, PR body closes #619, conventional commit title, changelog entry present, 19 new test functions across 4 test files, prose clean. All 18 CI checks pass.
Non-blocking; safe to merge once a maintainer agrees.
Closes #619
Adds an optional DNSSEC check to did:web validation in the OpenID4VP flow. Off by default; when off or unavailable, existing did:web validation is unchanged.
How it works
The new
eudi/dnssecpackage validates the DNSSEC chain of trust for a host's address records. It queries a recursive resolver with the DO and CD bits set and verifies every RRSIG, DS and DNSKEY itself, anchored at the IANA root trust anchors. The resolver's AD bit is deliberately not trusted: on a hostile network the path to the resolver is exactly what the attacker controls, and with local validation a spoofed resolver can cause a warning but can never forge a "secure" verdict.Four outcomes:
secure,insecure(domain is not signed),bogus(validation failed, the spoofing signal),indeterminate(check unavailable, e.g. no resolver or a resolver that strips DNSSEC records).didweb.DocumentResolverandDidVerifierValidatortake an optionaldnssec.Verifier. In the OpenID4VP flow the outcome maps to typed warnings on the requestor (TrustedParty.Warnings) that reach the app through the session state:bogusbecomesdid_web_dnssec_invalidinsecurebecomesdid_web_dnssec_missingsecureandindeterminateproduce no warningValidation itself is never blocked by the check. Enable it with
client.SetDidWebDnssecCheck(true).The warning codes are for irmamobile to translate into user-friendly text (the wallet owns the wording, so no DNSSEC jargon needs to reach users). Note that
did_web_dnssec_missingwill fire for most domains on the internet; showing it prominently is a product decision.Limitations
securetoinsecure, but cannot forgesecure. Documented in the package.ChainVerifier.Serversexplicitly.Testing
eudi/dnssecis tested against an in-process fake resolver serving a fully signed root, TLD and leaf zone hierarchy: tampered records, stripped signatures, expired signatures, forged DS, self-signed DS (a zone forging its own chain), CNAME into an unsigned zone, completely unsigned zones and IPv6-only hosts.eudi/didwebandeudi/openid4vptests cover the plumbing: the check runs against the DID's domain, each status maps to the right warning, and a nil verifier changes nothing.secure, dnssec-failed.org asbogus, google.com asinsecure.go test ./...passes except for pre-existing failures that need postgres/mail services (keyshare and sessiontest keyshare subtests, identical on master).