Documentation
¶
Overview ¶
Package dkim generates DKIM signing keys and renders their DNS TXT records.
The product can store a domain's DKIM private key (via PUT /api/v1/admin/dkim) and sign with it in the pipeline, but it has no way to *generate* a keypair or render the public key as the DNS record receivers verify against. This package fills that gap so an instance can be given a working DKIM setup: generate a keypair, install the private key via the admin API, and publish the record.
Index ¶
- Constants
- func BuildSignedHeaders(hTag string, allHeaders []Header, sig Header, canon string) string
- func CanonicalizeBody(body, canon string) string
- func CanonicalizeHeader(h Header, canon string) string
- func FetchKey(ctx context.Context, selector, domain string, resolver TXTResolver) (*rsa.PublicKey, string)
- func GenerateKey(bits int) (privatePEM, publicPEM string, err error)
- func HashBytes(h crypto.Hash, data []byte) []byte
- func ParsePrivateKey(pemStr string) (*rsa.PrivateKey, error)
- func ParseTagList(s string) map[string]string
- func RecordFragment(name, value string) string
- func RecordName(selector, domain string) string
- func RecordValue(publicKeyPEM string) (string, error)
- func RemoveBValue(field string) string
- func Sign(rawMessage []byte, opt SignOptions) (string, error)
- func StripWSP(s string) string
- type Header
- type SignOptions
- type TXTResolver
- type VerifyResult
Constants ¶
const ( ResultPass = "pass" ResultFail = "fail" ResultNeutral = "neutral" ResultNone = "none" ResultTempError = "temperror" ResultPermError = "permerror" )
Verification result strings, mirroring RFC 8601 dkim= values.
const DefaultSelector = "default"
DefaultSelector is the selector the API's DNS health check looks up (default._domainkey.<domain>). Keep in sync with internal/api/handlers.
Variables ¶
This section is empty.
Functions ¶
func BuildSignedHeaders ¶
BuildSignedHeaders assembles the canonicalized header block that a signature's b= tag signs: each header named in hTag (a colon-separated list, matched bottom-up per RFC 6376 §5.4.2), followed by the signature header (sig) itself with its b= value emptied and NO trailing CRLF.
It is exported so a signer or a layered scheme (ARC's ARC-Message-Signature) can produce the exact bytes VerifySignature will hash.
func CanonicalizeBody ¶
CanonicalizeBody applies simple or relaxed body canonicalization (RFC 6376 §3.4.3 / §3.4.4). Input is a CRLF-normalized body (as returned by SplitMessage). The result always ends in exactly one CRLF, except that a relaxed canonicalization of an empty body is the empty string.
func CanonicalizeHeader ¶
CanonicalizeHeader canonicalizes a single header field per RFC 6376 §3.4, using either "relaxed" or "simple" canonicalization. The returned string has NO trailing CRLF — the caller appends one between signed headers (and none after the trailing signature header being verified).
func FetchKey ¶
func FetchKey(ctx context.Context, selector, domain string, resolver TXTResolver) (*rsa.PublicKey, string)
FetchKey resolves and parses a signer's RSA public key from its DKIM key record at <selector>._domainkey.<domain>. On success it returns (key, ""); on failure it returns (nil, result) where result is ResultTempError (transient DNS failure) or ResultPermError (missing, revoked or malformed key).
func GenerateKey ¶
GenerateKey creates an RSA DKIM keypair and returns both halves PEM-encoded. The private PEM is what you install via the admin API; the public PEM feeds RecordValue.
func HashBytes ¶
HashBytes hashes data with the given hash (crypto.SHA256 or crypto.SHA1, the two algorithms RFC 6376 defines) and returns the digest.
func ParsePrivateKey ¶
func ParsePrivateKey(pemStr string) (*rsa.PrivateKey, error)
ParsePrivateKey parses a PEM-encoded RSA private key in PKCS#1 or PKCS#8 form.
func ParseTagList ¶
ParseTagList parses a DKIM tag=value list ("k=v; k2=v2") into a map. Keys and the ends of values are trimmed; internal FWS in values is preserved (callers strip it via StripWSP where it is insignificant, e.g. b=, bh=, p=). It parses DKIM-Signature, DKIM key records, and ARC header (i=, cv=, …) tag lists alike.
func RecordFragment ¶
RecordFragment renders a dnsmasq txt-record line for a DKIM record, splitting the value into <=255-char strings (the DNS TXT per-string limit) so that 2048-bit records — whose p= value exceeds 255 chars — stay valid:
txt-record=default._domainkey.d,"chunk1","chunk2"
func RecordName ¶
RecordName returns the DKIM record name <selector>._domainkey.<domain>.
func RecordValue ¶
RecordValue renders the DKIM DNS TXT value (v=DKIM1; k=rsa; p=<base64 DER>) from a PEM-encoded RSA public key (as produced by GenerateKey).
func RemoveBValue ¶
RemoveBValue blanks the value of the b= tag in a signature field (a DKIM-Signature, ARC-Message-Signature, or ARC-Seal value) while preserving every other byte (including the "b=" itself), as required before canonicalizing the signature header for verification.
func Sign ¶
func Sign(rawMessage []byte, opt SignOptions) (string, error)
Sign computes a DKIM-Signature header field VALUE (everything after "DKIM-Signature:") over the given raw RFC 5322 message. The caller prepends it as "DKIM-Signature: " + value + "\r\n".
Signing operates on the message's ACTUAL header/body bytes and shares its canonicalization with Verify (via buildSignedHeaders / canonicalizeBody), so a message signed here verifies here — and, because it signs the real transmitted bytes rather than a reconstruction, at any RFC 6376 verifier. Only headers that are actually present are included in h=.
Types ¶
type Header ¶
type Header struct {
// Name is the field name, e.g. "From" (whitespace-trimmed, original case).
Name string
// Value is everything after the colon, with folding CRLFs preserved and the
// trailing CRLF stripped.
Value string
// Raw is the full field exactly as it appeared (name, colon, value, folds),
// with no trailing CRLF — used by simple header canonicalization.
Raw string
}
Header is one parsed header field of an RFC 5322 message: its name, its value (everything after the colon, folding CRLFs preserved, trailing CRLF stripped) and the full raw field. It is the unit SplitMessage produces and that CanonicalizeHeader / VerifySignature / BuildSignedHeaders consume.
func SplitMessage ¶
SplitMessage normalizes line endings to CRLF and splits a raw RFC 5322 message into ordered header fields and the body. Line-ending normalization (bare LF / lone CR → CRLF) reconstructs the canonical wire form the signer hashed, in case an intermediate stored the message with LF-only endings.
It is exported so that layered signature schemes (e.g. ARC) can parse a raw message into the same Header slice / body that Sign and Verify operate on.
type SignOptions ¶
type SignOptions struct {
Domain string // d= signing domain (required)
Selector string // s= selector (required)
PrivateKey *rsa.PrivateKey // signing key (required)
Headers []string // headers to sign; default from:to:subject:date:message-id
HeaderCanon string // "relaxed" (default) or "simple"
BodyCanon string // "relaxed" (default) or "simple"
Time int64 // t= value; 0 omits the tag
}
SignOptions configures DKIM signing. Zero-value fields fall back to the documented defaults.
type TXTResolver ¶
TXTResolver looks up DNS TXT records for a name. It matches the signature of net.Resolver.LookupTXT so the default resolver can be used directly, and a stub can be injected in tests.
type VerifyResult ¶
type VerifyResult struct {
Domain string // d= signing domain
Selector string // s= selector
Result string // one of the Result* constants
Reason string // human-readable detail
}
VerifyResult is the outcome of verifying a single DKIM-Signature.
func Verify ¶
func Verify(ctx context.Context, rawMessage []byte, resolver TXTResolver) []VerifyResult
Verify performs RFC 6376 DKIM verification against a raw RFC 5322 message.
Verification MUST run over the exact bytes that were signed — the header and body as transmitted — so this operates on the raw message, never on a parsed or reconstructed representation (reconstructing headers/body from structured fields would not reproduce the signer's canonicalization for anything but the simplest messages).
It returns one VerifyResult per DKIM-Signature header found, in header order. An empty slice means the message carried no DKIM-Signature. A nil resolver uses the system DNS resolver.
func VerifySignature ¶
func VerifySignature(ctx context.Context, sig Header, allHeaders []Header, body string, resolver TXTResolver) VerifyResult
VerifySignature verifies a single DKIM-style signature header (sig) against the message it covers: allHeaders is the full ordered header block the signature's h= tag selects from, and body is the CRLF-normalized body (both as returned by SplitMessage). It performs body-hash, header-hash and public key checks and returns a VerifyResult.
It is the per-signature verification primitive underneath Verify. It is exported so that layered schemes (e.g. ARC) can verify their DKIM-shaped signature — an ARC-Message-Signature is structurally a DKIM-Signature — using exactly the same canonicalization and crypto path.