dkim

package
v0.0.9 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 9, 2024 License: MIT Imports: 24 Imported by: 1

Documentation

Overview

Package dkim (DomainKeys Identified Mail signatures, RFC 6376) signs and verifies DKIM signatures.

Signatures are added to email messages in DKIM-Signature headers. By signing a message, a domain takes responsibility for the message. A message can have signatures for multiple domains, and the domain does not necessarily have to match a domain in a From header. Receiving mail servers can build a spaminess reputation based on domains that signed the message, along with other mechanisms.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrNoRecord        = errors.New("dkim: no dkim dns record for selector and domain")
	ErrMultipleRecords = errors.New("dkim: multiple dkim dns record for selector and domain")
	ErrDNS             = errors.New("dkim: lookup of dkim dns record")
	ErrSyntax          = errors.New("dkim: syntax error in dkim dns record")
)

Lookup errors.

View Source
var (
	ErrSigAlgMismatch          = errors.New("dkim: signature algorithm mismatch with dns record")
	ErrHashAlgNotAllowed       = errors.New("dkim: hash algorithm not allowed by dns record")
	ErrKeyNotForEmail          = errors.New("dkim: dns record not allowed for use with email")
	ErrDomainIdentityMismatch  = errors.New("dkim: dns record disallows mismatch of domain (d=) and identity (i=)")
	ErrSigExpired              = errors.New("dkim: signature has expired")
	ErrHashAlgorithmUnknown    = errors.New("dkim: unknown hash algorithm")
	ErrBodyhashMismatch        = errors.New("dkim: body hash does not match")
	ErrSigVerify               = errors.New("dkim: signature verification failed")
	ErrSigAlgorithmUnknown     = errors.New("dkim: unknown signature algorithm")
	ErrCanonicalizationUnknown = errors.New("dkim: unknown canonicalization")
	ErrHeaderMalformed         = errors.New("dkim: mail message header is malformed")
	ErrFrom                    = errors.New("dkim: bad from headers")
	ErrQueryMethod             = errors.New("dkim: no recognized query method")
	ErrKeyRevoked              = errors.New("dkim: key has been revoked")
	ErrTLD                     = errors.New("dkim: signed domain is top-level domain, above organizational domain")
	ErrPolicy                  = errors.New("dkim: signature rejected by policy")
	ErrWeakKey                 = errors.New("dkim: key is too weak, need at least 1024 bits for rsa")
)

Signature verification errors.

View Source
var Pedantic bool

Pedantic enables stricter parsing.

Functions

func DefaultPolicy

func DefaultPolicy(sig *Sig) error

DefaultPolicy is the default DKIM policy.

Signatures with a length restriction are rejected because it is hard to decide how many signed bytes should be required (none? at least half? all except max N bytes?). Also, it isn't likely email applications (MUAs) will be displaying the signed vs unsigned (partial) content differently, mostly because the encoded data is signed. E.g. half a base64 image could be signed, and the rest unsigned.

Signatures without Subject field are rejected. The From header field is always required and does not need to be checked in the policy. Other signatures are accepted.

func Lookup

func Lookup(ctx context.Context, elog *slog.Logger, resolver dns.Resolver, selector, domain dns.Domain) (rstatus Status, rrecord *Record, rtxt string, authentic bool, rerr error)

Lookup looks up the DKIM TXT record and parses it.

A requested record is <selector>._domainkey.<domain>. Exactly one valid DKIM record should be present.

authentic indicates if DNS results were DNSSEC-verified.

func Sign

func Sign(ctx context.Context, elog *slog.Logger, localpart smtp.Localpart, domain dns.Domain, selectors []Selector, smtputf8 bool, msg io.ReaderAt) (headers string, rerr error)

Sign returns line(s) with DKIM-Signature headers, generated according to the configuration.

Types

type Identity

type Identity struct {
	Localpart *smtp.Localpart // Optional.
	Domain    dns.Domain
}

Identity is used for the optional i= field in a DKIM-Signature header. It uses the syntax of an email address, but does not necessarily represent one.

func (Identity) String

func (i Identity) String() string

String returns a value for use in the i= DKIM-Signature field.

type Record

type Record struct {
	Version  string   // Version, fixed "DKIM1" (case sensitive). Field "v".
	Hashes   []string // Acceptable hash algorithms, e.g. "sha1", "sha256". Optional, defaults to all algorithms. Field "h".
	Key      string   // Key type, "rsa" or "ed25519". Optional, default "rsa". Field "k".
	Notes    string   // Debug notes. Field "n".
	Pubkey   []byte   // Public key, as base64 in record. If empty, the key has been revoked. Field "p".
	Services []string // Service types. Optional, default "*" for all services. Other values: "email". Field "s".
	Flags    []string // Flags, colon-separated. Optional, default is no flags. Other values: "y" for testing DKIM, "s" for "i=" must have same domain as "d" in signatures. Field "t".

	PublicKey any `json:"-"` // Parsed form of public key, an *rsa.PublicKey or ed25519.PublicKey.
}

Record is a DKIM DNS record, served on <selector>._domainkey.<domain> for a given selector and domain (s= and d= in the DKIM-Signature).

The record is a semicolon-separated list of "="-separated field value pairs. Strings should be compared case-insensitively, e.g. k=ed25519 is equivalent to k=ED25519.

Example:

v=DKIM1;h=sha256;k=ed25519;p=ln5zd/JEX4Jy60WAhUOv33IYm2YZMyTQAdr9stML504=

func ParseRecord

func ParseRecord(s string) (record *Record, isdkim bool, err error)

ParseRecord parses a DKIM DNS TXT record.

If the record is a dkim record, but an error occurred, isdkim will be true and err will be the error. Such errors must be treated differently from parse errors where the record does not appear to be DKIM, which can happen with misconfigured DNS (e.g. wildcard records).

func (*Record) Record

func (r *Record) Record() (string, error)

Record returns a DNS TXT record that should be served at <selector>._domainkey.<domain>.

Only values that are not the default values are included.

func (*Record) ServiceAllowed

func (r *Record) ServiceAllowed(s string) bool

ServiceAllowed returns whether service s is allowed by this key.

The optional field "s" can specify purposes for which the key can be used. If value was specified, both "*" and "email" are enough for use with DKIM.

type Result

type Result struct {
	Status          Status
	Sig             *Sig    // Parsed form of DKIM-Signature header. Can be nil for invalid DKIM-Signature header.
	Record          *Record // Parsed form of DKIM DNS record for selector and domain in Sig. Optional.
	RecordAuthentic bool    // Whether DKIM DNS record was DNSSEC-protected. Only valid if Sig is non-nil.
	Err             error   // If Status is not StatusPass, this error holds the details and can be checked using errors.Is.
}

Result is the conclusion of verifying one DKIM-Signature header. An email can have multiple signatures, each with different parameters.

To decide what to do with a message, both the signature parameters and the DNS TXT record have to be consulted.

func Verify

func Verify(ctx context.Context, elog *slog.Logger, resolver dns.Resolver, smtputf8 bool, policy func(*Sig) error, r io.ReaderAt, ignoreTestMode bool) (results []Result, rerr error)

Verify parses the DKIM-Signature headers in a message and verifies each of them.

If the headers of the message cannot be found, an error is returned. Otherwise, each DKIM-Signature header is reflected in the returned results.

NOTE: Verify does not check if the domain (d=) that signed the message is the domain of the sender. The caller, e.g. through DMARC, should do this.

If ignoreTestMode is true and the DKIM record is in test mode (t=y), a verification failure is treated as actual failure. With ignoreTestMode false, such verification failures are treated as if there is no signature by returning StatusNone.

type Selector added in v0.0.9

type Selector struct {
	Hash          string   // "sha256" or the older "sha1".
	HeaderRelaxed bool     // If the header is canonicalized in relaxed instead of simple mode.
	BodyRelaxed   bool     // If the body is canonicalized in relaxed instead of simple mode.
	Headers       []string // Headers to include in signature.

	// Whether to "oversign" headers, ensuring additional/new values of existing
	// headers cannot be added.
	SealHeaders bool

	// If > 0, period a signature is valid after signing, as duration, e.g. 72h. The
	// period should be enough for delivery at the final destination, potentially with
	// several hops/relays. In the order of days at least.
	Expiration time.Duration

	PrivateKey crypto.Signer // Either an *rsa.PrivateKey or ed25519.PrivateKey.
	Domain     dns.Domain    // Of selector only, not FQDN.
}

Selector holds selectors and key material to generate DKIM signatures.

type Sig

type Sig struct {
	// Required fields.
	Version       int        // Version, 1. Field "v". Always the first field.
	AlgorithmSign string     // "rsa" or "ed25519". Field "a".
	AlgorithmHash string     // "sha256" or the deprecated "sha1" (deprecated). Field "a".
	Signature     []byte     // Field "b".
	BodyHash      []byte     // Field "bh".
	Domain        dns.Domain // Field "d".
	SignedHeaders []string   // Duplicates are meaningful. Field "h".
	Selector      dns.Domain // Selector, for looking DNS TXT record at <s>._domainkey.<domain>. Field "s".

	// Optional fields.
	// Canonicalization is the transformation of header and/or body before hashing. The
	// value is in original case, but must be compared case-insensitively. Normally two
	// slash-separated values: header canonicalization and body canonicalization. But
	// the "simple" means "simple/simple" and "relaxed" means "relaxed/simple". Field
	// "c".
	Canonicalization string
	Length           int64     // Body length to verify, default -1 for whole body. Field "l".
	Identity         *Identity // AUID (agent/user id). If nil and an identity is needed, should be treated as an Identity without localpart and Domain from d= field. Field "i".
	QueryMethods     []string  // For public key, currently known value is "dns/txt" (should be compared case-insensitively). If empty, dns/txt must be assumed. Field "q".
	SignTime         int64     // Unix epoch. -1 if unset. Field "t".
	ExpireTime       int64     // Unix epoch. -1 if unset. Field "x".
	CopiedHeaders    []string  // Copied header fields. Field "z".
}

Sig is a DKIM-Signature header.

String values must be compared case insensitively.

func (Sig) Algorithm

func (s Sig) Algorithm() string

Algorithm returns an algorithm string for use in the "a" field. E.g. "ed25519-sha256".

func (*Sig) Header

func (s *Sig) Header() (string, error)

Header returns the DKIM-Signature header in string form, to be prepended to a message, including DKIM-Signature field name and trailing \r\n.

type Status

type Status string

Status is the result of verifying a DKIM-Signature as described by RFC 8601, "Message Header Field for Indicating Message Authentication Status".

const (
	StatusNone      Status = "none"      // Message was not signed.
	StatusPass      Status = "pass"      // Message was signed and signature was verified.
	StatusFail      Status = "fail"      // Message was signed, but signature was invalid.
	StatusPolicy    Status = "policy"    // Message was signed, but signature is not accepted by policy.
	StatusNeutral   Status = "neutral"   // Message was signed, but the signature contains an error or could not be processed. This status is also used for errors not covered by other statuses.
	StatusTemperror Status = "temperror" // Message could not be verified. E.g. because of DNS resolve error. A later attempt may succeed. A missing DNS record is treated as temporary error, a new key may not have propagated through DNS shortly after it was taken into use.
	StatusPermerror Status = "permerror" // Message cannot be verified. E.g. when a required header field is absent or for invalid (combination of) parameters. Typically set if a DNS record does not allow the signature, e.g. due to algorithm mismatch or expiry.
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL