Skip to content

About

Pure-Zig DNS library: strict RFC 1035 wire-format codec, DNSSEC validation of responses (RFC 4033-4035, RFC 5155) with chain-of-trust from a configured trust anchor, and a validating stub client over DNS-over-HTTPS (RFC 8484); experimental, not yet independently reviewed.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

12 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

zig-dnssec

Code licence: MPL-2.0 Docs licence: CC-BY-SA-4.0 Status: experimental

A pure-Zig DNS library: a strict wire-format codec (RFC 1035), DNSSEC validation of responses (RFC 4033–4035, RFC 5155) with chain-of-trust from a configured trust anchor, and a validating stub client that queries over DNS-over-HTTPS (RFC 8484).

Warning

Experimental. Not yet independently reviewed. Do not rely on this validator for any security decision until it has had an independent review by someone who did not write it. What has and has not been verified is set out in the verification report; the known gaps and the trust-anchor assumptions are in the threat model.

It is not a recursive resolver: it asks an upstream resolver (with CD=1) and authenticates what comes back itself, fetching the DS and DNSKEY records it needs through the same transport.

What it does

  • Wire codec — bounds-checked decoding that rejects malformed messages (compression pointers must point strictly backwards; names in RDATA are decompressed only where RFC 3597 permits; trailing data, misplaced OPT and meta-types are rejected), and an encoder with name compression.

  • Signatures — RSASHA1, RSASHA1-NSEC3-SHA1, RSASHA256, RSASHA512, ECDSAP256SHA256, ECDSAP384SHA384 and ED25519, all through Zig’s standard library; DS digests SHA-1, SHA-256 and SHA-384. No cryptographic primitive is implemented here.

  • Chain of trust — DS → DNSKEY → RRSIG from the signer up to the closest trust anchor, RRSIG validity windows in RFC 1982 serial arithmetic, CNAME and DNAME chains, wildcard expansion.

  • Denial of existence — NSEC and NSEC3 proofs for NXDOMAIN, NODATA, wildcards and insecure delegations, including Opt-Out and the RFC 6840 restrictions.

  • Verdicts that cannot be downgraded silently — the result is secure | insecure | bogus | indeterminate. Answer data is reachable only from the secure arm, or from the insecure arm together with the reason and the zone at which insecurity was proven. Every failure is bogus or indeterminate.

  • Stub client — DoH over TLS 1.2/1.3 with certificate verification, optional HTTP CONNECT proxy (TLS end to end), RFC 8467 query padding.

Not implemented

DNS-over-QUIC (no QUIC in Zig’s standard library), Ed448 and GOST/SM2 signatures, HTTP/2, RFC 5011 trust-anchor rollover, negative trust anchors, caching, QTYPE ANY. The full list, and what each gap means for a caller, is in the threat model.

Using it

Requires Zig 0.17.0.

const dnssec = @import("dnssec");

var anchors = try dnssec.sec.anchors.TrustAnchors.ianaRoot(gpa);
defer anchors.deinit();

var bundle: std.crypto.Certificate.Bundle = .empty;
try bundle.rescan(gpa, io, std.Io.Clock.real.now(io));
var doh = try dnssec.stub.doh.DohTransport.init(io, "https://dns.example/dns-query", &bundle, null);
var stub: dnssec.stub.client.StubClient = .{ .transport = doh.transport(), .anchors = &anchors };

var result = try stub.resolve(gpa, try dnssec.wire.Name.fromText("example.org."), .aaaa, now);
defer result.deinit();
switch (result.verdict) {
    .secure => |answer| use(answer.rrsets),
    .insecure => |i| decideWhetherUnsignedIsAcceptable(i.reason, i.zone, i.answer),
    .bogus, .indeterminate => |failure| reject(failure.reason),
}

StubClient.resolveSecure returns an error for anything that is not secure.

The command-line tool prints the verdict:

$ zig build -Doptimize=ReleaseSafe
$ ./zig-out/bin/dnssec-stub --doh https://cloudflare-dns.com/dns-query example.com. A
verdict=secure reason=- at=- rcode=noerror
example.com. 260 IN A 104.20.23.154
...
$ ./zig-out/bin/dnssec-stub --doh https://cloudflare-dns.com/dns-query dnssec-failed.org. A
verdict=bogus reason=dnskey_not_authenticated at=dnssec-failed.org. type=dnskey

From C, Idris2 and other languages

zig build ffi builds libzig_dnssec (static and shared, pure Zig, no libc) and installs zig_dnssec.h. The header is the contract: a verdict whose security is never SECURE unless the call succeeded, and answer text only for SECURE and INSECURE. The caller supplies the transport as a callback. An Idris2 model of the interface in src/interface/Abi/ states the same rule as a type. See src/interface/README.adoc.

AI-Assisted Installation (Recommended)

Just say it

Say this to any AI assistant that can read a URL and run commands (or give you commands to paste):

Set up zig-dnssec from https://github.com/hyperpolymath/zig-dnssec

The URL leads the assistant to docs/AI_INSTALLATION_GUIDE.adoc. It clones the repository, installs the toolchain pinned in mise.lock (Zig 0.17.0) with just setup, checks the result with just doctor, and runs just test. ./launcher.sh --ai-setup (or just ai-setup) prints the same line.

Important

Privacy: setting up clones this repository and installs developer tools per user; nothing is sent anywhere. Running the tests needs no network. Only the live examples above, and dnssec-stub without --replay, contact a DNS-over-HTTPS server.

Building and testing

zig build test                         # unit, RFC vector, denial, replay, fuzz smoke and C ABI tests
idris2 --typecheck src/interface/abi.ipkg   # proofs of the Idris2 ABI model
just abi-e2e                           # Idris2 -> C ABI -> Zig end to end
scripts/theory-receipts.sh             # chain-of-trust theory: Agda, Rocq and Lean, with receipts
zig build fuzz --fuzz=500K -Doptimize=ReleaseSafe      # coverage-guided fuzzing
zig build fuzz-tool -Doptimize=ReleaseSafe && ./zig-out/bin/dnssec-fuzz all 60
tests/crosscheck/make-zones.sh && tests/crosscheck/servers.sh start && tests/crosscheck/run.sh

What is proved, tested or only argued, and how strongly, is the proof ledger in EXPLAINME.adoc: 8 proven of 18 rows; 6 tested-only, 4 argued, 0 open. The theory needs Agda 2.6.3, Coq 8.18 and Lean 4.15; versions and receipts are in docs/status/PROOF-STATUS.adoc.

The cross-check needs NSD, Unbound, ldns tools and haproxy; build/container/Containerfile provides them. Its latest output is tests/crosscheck/REPORT.adoc.

Licence

Code: MPL-2.0. Documentation: CC-BY-SA-4.0. See LICENSES/.

About

Pure-Zig DNS library: strict RFC 1035 wire-format codec, DNSSEC validation of responses (RFC 4033-4035, RFC 5155) with chain-of-trust from a configured trust anchor, and a validating stub client over DNS-over-HTTPS (RFC 8484); experimental, not yet independently reviewed.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages