// SPDX-FileCopyrightText: © 2026 Jeffrey C. Ollie // SPDX-License-Identifier: MIT //! DKIM — DomainKeys Identified Mail, RFC 6376. //! //! DKIM answers exactly one question: **which domain takes responsibility for //! this message?** Not who wrote it, not whether it is honest, not whether the //! `From` address is real. A domain signs a message with a key it publishes in //! DNS, and a verifier that checks the signature learns that *that domain* put //! its name to *these bytes*. Everything else — whether the domain is one you //! trust, whether it matches the `From` header, what to do when it does not — //! is DMARC's business, and DMARC is a different specification. //! //! ```zig //! const dkim = @import("dkim"); //! //! // Signing: hand back the header field to put at the top of the message. //! var buf: [dkim.signing.max_field_len]u8 = undefined; //! const field = try dkim.signing.sign(&buf, raw_message, .{ //! .domain = "example.com", //! .selector = "mail", //! .key = .{ .rsa = secret_key }, //! .timestamp = now, //! }); //! //! // Verifying: two steps, because the middle one is a DNS lookup. //! var it = dkim.signatures(raw_message); //! while (it.next()) |result| { //! const signature = try result; //! var name_buf: [253]u8 = undefined; //! const name = try signature.recordName(&name_buf); // what to look up //! const txt = try yourResolver.txt(name); // your resolver //! const record = try dkim.Record.parse(txt); //! const verified = try dkim.verify(raw_message, signature, record, .{}); //! std.log.info("signed by {s}", .{verified.domain}); //! } //! ``` //! //! ## Two things this library will not do //! //! **It does no I/O and it allocates nothing.** Not a socket, not a file, not //! an allocator anywhere in the signing or verification path — a message is a //! `[]const u8` that the caller already has, and everything else fits in fixed //! buffers whose sizes are public constants. That is not asceticism: it means //! the whole of DKIM's logic is reachable from a unit test, that a milter can //! run it without deciding what to do about allocation failure mid-message, //! and that it cannot stall on a slow network while an SMTP transaction waits. //! //! **It does not resolve DNS.** Verification is split in two around the //! lookup, as above. A library that resolved names would need an `Io`, a //! timeout policy, a cache and an opinion about DNSSEC, and would be //! untestable without a network; a caller running a mail server already has a //! resolver it would rather use. The consequence is that `TEMPFAIL` is not a //! result this library can return — a DNS failure happens before anything here //! runs — so every failure below is permanent. //! //! **It does not read the clock.** A signature's `t=` and a verifier's idea of //! "now" are both passed in. A signer that read the clock could not be made to //! produce the same bytes twice and so could not be tested against a fixed //! vector, and a verifier that read it could not be tested against a signature //! that expired in 2019. //! //! ## What is checked, and what is left to the caller //! //! `verify` returns an error unless the signature is cryptographically sound //! *and* the key record permits it: the algorithm is one RFC 8301 still allows, //! the key is large enough, the record has not revoked it, its `h=` and `s=` //! permit this use, and `t=s` is respected. What it does not do is decide what //! a pass *means*. A `Verified` says which domain signed, whether the key is //! flagged as testing, and how much of the body `l=` actually covered — and //! all three change what a sensible caller does next. A pass over the first //! 200 bytes of a 40 KB message, from a key whose owner has said it is still //! testing, is not the same thing as a pass. const std = @import("std"); const testing = std.testing; /// The `tag=value` syntax both the signature and the key record are written /// in, RFC 6376 §3.2. pub const tag_list = @import("tag_list.zig"); /// Taking a message apart: where the headers stop, and where each field /// begins and ends. pub const message = @import("message.zig"); /// The two canonicalization algorithms, `simple` and `relaxed`, which are /// what lets a signature survive being relayed. pub const canonical = @import("canonical.zig"); /// The `DKIM-Signature` header field. pub const Signature = @import("Signature.zig"); /// The public key record, which arrives as the text of a DNS TXT record. pub const Record = @import("Record.zig"); /// What actually gets hashed, RFC 6376 §3.7. Shared by the signer and the /// verifier so that the two cannot drift apart. pub const digest = @import("digest.zig"); /// Verifying a signature. pub const verification = @import("verify.zig"); /// Making one. pub const signing = @import("sign.zig"); /// Verify one signature against the key record it names. See /// `verification.verify`. pub const verify = verification.verify; /// The last step of verifying anything signed the way DKIM signs: a /// digest, a signature and a key. Exposed for ARC, whose `ARC-Seal` /// hashes a different thing and then checks it exactly this way. pub const verifyDigest = verification.verifyDigest; /// Walk the signatures on a message. See `verification.signatures`. pub const signatures = verification.signatures; /// Sign a message, returning the header field to prepend. See `signing.sign`. pub const sign = signing.sign; /// The last step of producing anything signed the way DKIM signs: a /// digest, a key, and a base64 signature. The mirror of `verifyDigest`, /// exposed for ARC. pub const signDigest = signing.signDigest; test { testing.refAllDecls(@This()); // Without these the tests in each file would never be analysed, let alone // run: a test executable covers the module it is given and reaches a file // only through a reference to it. _ = tag_list; _ = message; _ = canonical; _ = Signature; _ = Record; _ = digest; _ = verification; _ = signing; }