aph-resolver
Ready-made I/O adapters for APH notary-key discovery (spec §8.4).
aph-core parses and never fetches: it declares three narrow one-method ports
(discovery::ports::TxtRecordLookup, discovery::ports::DidDocumentFetch,
discovery::ports::StatusCredentialFetch) and keeps no HTTP client, no
resolver and no async runtime. This crate is one implementation of the first
two, carrying hickory-resolver and reqwest so aph-core does not have to;
nothing here implements StatusCredentialFetch, so a host that checks
revocation status supplies that one itself.
[]
= "0.1"
// Both values come from the envelope being verified: the DID URL from
// `proof.verificationMethod`, the instant from `decisionTimestamp` — not the
// wall clock, so an envelope signed before a rotation keeps verifying.
let key = resolve
.await?;
Who this is for
Adopters with no adapter layer of their own — a CLI, a service, a test harness with no opinion about how HTTP and DNS should be performed.
It is deliberately not for a host that already has a transport stack. An
OS or platform with its own HTTP client, DNS policy, timeout budget and audit
log should implement aph_core::discovery::ports over that stack — a few
dozen lines — and keep one place where outbound requests are made. Two HTTP
clients in one process means two timeout policies, two proxy configurations,
and two answers to "what did this machine connect to".
What it decides, and what it does not
Every parsing, selection and ordering rule stays in aph-core. This crate
contributes bytes plus exactly one judgement, the ABSENCE/FAILURE split, which
only an adapter can see: NXDOMAIN, and NOERROR with no TXT records, are
absence (Ok(aph_core::discovery::DiscoveryOutcome::Absent), and §8.4.6
advances to the next mechanism), while timeout, SERVFAIL and REFUSED are
APH_E008 and stop the sequence. Reporting absence as failure would make
every did:web-only notary unverifiable; reporting failure as absence would
let whoever can block DNS choose which anchor a verifier trusts.
Security posture
The host being fetched is named by the envelope, so it is chosen by an
untrusted party. ReqwestDocumentFetch applies five controls:
- A deny table over every resolved address before any connect (loopback, RFC 1918, link-local including cloud metadata, CGNAT, multicast, reserved, the IPv6 equivalents, and the IPv4 addresses embedded in mapped, compatible, NAT64 and 6to4 forms) — then the validated set is pinned so the connect cannot re-resolve.
- HTTPS only, with no certificate-validation escape hatch anywhere.
- Redirects refused, plus an origin-equality re-check on the response.
- A 5 s connect and total budget, and a 64 KiB body cap enforced while streaming rather than after reading.
- One opaque
APH_E008outward for every failure — no status, no address, no timing — so a verifier cannot be steered into acting as a network probe.
Full protocol text: spec/aph-0.1.md §8.4.