dkim

package
v1.4.1 Latest Latest
Warning

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

Go to latest
Published: Apr 22, 2026 License: MIT Imports: 19 Imported by: 0

README

dkim

Package dkim implements DomainKeys Identified Mail (RFC 6376).

Import

import "github.com/synqronlabs/raven/dkim"

What It Does

  • Signs RFC 5322 messages with RSA or Ed25519 keys.
  • Verifies one or more DKIM signatures on incoming messages.
  • Parses DKIM records and signatures into typed structs.

Key API

  • (*Signer).Sign(message)
  • Verify(ctx, resolver, message)
  • SignMail(...), SignMailMultiple(...), QuickSign(...)

Example

signer := &dkim.Signer{
    Domain:     "example.com",
    Selector:   "selector1",
    PrivateKey: privateKey,
}

sigHeader, err := signer.Sign(rawMessage)
if err != nil {
    panic(err)
}

_ = sigHeader

results, err := dkim.Verify(ctx, resolver, rawMessage)
if err != nil {
    panic(err)
}

for _, r := range results {
    if r.Status == dkim.StatusPass {
        // Verified signature.
    }
}

Documentation

Overview

Package dkim implements DomainKeys Identified Mail (DKIM) signatures per RFC 6376.

DKIM allows a sender to associate a domain name with an email message, thus vouching for its authenticity. A message is signed by adding a DKIM-Signature header, which contains a cryptographic signature of the message headers and body.

This implementation supports:

  • RSA-SHA256 (required by RFC 6376)
  • RSA-SHA1 (deprecated, but supported for compatibility)
  • Ed25519-SHA256 (RFC 8463)

Basic Usage

Signing a message:

signer := dkim.Signer{
    Domain:     "example.com",
    Selector:   "selector1",
    PrivateKey: privateKey,
}
signature, err := signer.Sign(message)

Verifying a message:

results, err := dkim.Verify(ctx, resolver, message)
for _, r := range results {
    if r.Status == dkim.StatusPass {
        // Signature verified
    }
}

Index

Constants

This section is empty.

Variables

View Source
var (
	// DNS lookup errors.
	ErrNoRecord        = errors.New("dkim: no DKIM DNS record found")
	ErrMultipleRecords = errors.New("dkim: multiple DKIM DNS records found")
	ErrDNS             = errors.New("dkim: DNS lookup failed")
	ErrSyntax          = errors.New("dkim: syntax error in DKIM record")

	// Signature verification errors.
	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 email")
	ErrDomainIdentityMismatch  = errors.New("dkim: domain and identity mismatch")
	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 header is malformed")
	ErrFromRequired            = errors.New("dkim: From header is required")
	ErrQueryMethod             = errors.New("dkim: no recognized query method")
	ErrKeyRevoked              = errors.New("dkim: key has been revoked")
	ErrWeakKey                 = errors.New("dkim: key is too weak")
	ErrPolicy                  = errors.New("dkim: signature rejected by policy")
	ErrMissingTag              = errors.New("dkim: missing required tag")
	ErrDuplicateTag            = errors.New("dkim: duplicate tag")
	ErrInvalidVersion          = errors.New("dkim: invalid version")
	ErrTLD                     = errors.New("dkim: signed domain is top-level domain")
	ErrBodyHashLength          = errors.New("dkim: body hash length mismatch")
)

Common errors.

View Source
var DefaultSignedHeaders = []string{
	"From",
	"To",
	"Cc",
	"Subject",
	"Date",
	"Message-ID",
	"In-Reply-To",
	"References",
	"MIME-Version",
	"Content-Type",
	"Content-Transfer-Encoding",
	"Content-Disposition",
	"Reply-To",
}

DefaultSignedHeaders is the default list of headers to sign. These headers are commonly signed for message integrity.

View Source
var MinimumSignedHeaders = []string{
	"From",
	"To",
	"Subject",
	"Date",
}

MinimumSignedHeaders is the minimum set of headers that should be signed.

Functions

func IsTemporaryError

func IsTemporaryError(err error) bool

IsTemporaryError returns true if the error is temporary.

func QuickSign

func QuickSign(mail *ravenmail.Mail, domain, selector string, privateKey crypto.Signer) error

QuickSign is a simplified signing function for common use cases.

func SignMail

func SignMail(mail *ravenmail.Mail, signer *Signer) error

SignMail signs a mail message and adds the DKIM-Signature header. This is a convenience function for signing mail objects.

func SignMailMultiple

func SignMailMultiple(mail *ravenmail.Mail, signers []Signer) error

SignMailMultiple signs a mail message with multiple signers.

func SignMultiple

func SignMultiple(message []byte, signers []Signer) (string, error)

SignMultiple signs the message with multiple selectors. Returns multiple DKIM-Signature headers concatenated. This function caches body hashes to avoid recomputation when multiple signers use the same canonicalization and hash algorithm.

Types

type Algorithm

type Algorithm string

Algorithm represents a DKIM signing algorithm.

const (
	// AlgRSASHA256 is the RSA-SHA256 algorithm (required by RFC 6376).
	AlgRSASHA256 Algorithm = "rsa-sha256"

	// AlgRSASHA1 is the deprecated RSA-SHA1 algorithm.
	AlgRSASHA1 Algorithm = "rsa-sha1"

	// AlgEd25519SHA256 is the Ed25519-SHA256 algorithm (RFC 8463).
	AlgEd25519SHA256 Algorithm = "ed25519-sha256"
)

type Canonicalization

type Canonicalization string

Canonicalization represents header/body canonicalization algorithms.

const (
	// CanonSimple uses the "simple" canonicalization algorithm.
	CanonSimple Canonicalization = "simple"

	// CanonRelaxed uses the "relaxed" canonicalization algorithm.
	CanonRelaxed Canonicalization = "relaxed"
)

type Record

type Record struct {
	// Version is the record version, must be "DKIM1".
	Version string

	// Hashes is the list of acceptable hash algorithms (e.g., "sha256", "sha1").
	// Empty means all algorithms are acceptable.
	Hashes []string

	// Key is the key type: "rsa" (default) or "ed25519".
	Key string

	// Notes contains optional human-readable notes.
	Notes string

	// Pubkey is the raw public key data (base64-decoded).
	// Empty means the key has been revoked.
	Pubkey []byte

	// Services lists acceptable service types.
	// Empty or containing "*" means all services.
	Services []string

	// Flags contains key flags:
	//   "y" - Domain is testing DKIM
	//   "s" - i= domain must exactly match d= domain
	Flags []string

	// PublicKey is the parsed public key.
	// This is *rsa.PublicKey or ed25519.PublicKey.
	PublicKey any
}

Record represents a DKIM DNS TXT record (RFC 6376 Section 3.6.1). The record is retrieved from <selector>._domainkey.<domain>.

func ParseRecord

func ParseRecord(txt string) (*Record, bool, error)

ParseRecord parses a DKIM DNS TXT record. Returns the parsed record and a boolean indicating if it's a DKIM record.

func (*Record) HashAllowed

func (r *Record) HashAllowed(hash string) bool

HashAllowed returns true if the given hash algorithm is allowed.

func (*Record) IsTesting

func (r *Record) IsTesting() bool

IsTesting returns true if the key is marked for testing (t=y).

func (*Record) RequireStrictAlignment

func (r *Record) RequireStrictAlignment() bool

RequireStrictAlignment returns true if strict alignment is required (t=s).

func (*Record) ServiceAllowed

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

ServiceAllowed returns true if the given service is allowed by this key.

func (*Record) ToTXT

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

ToTXT generates a DNS TXT record string from this Record.

type Result

type Result struct {
	// Status is the verification result.
	Status Status

	// Signature is the parsed DKIM-Signature header.
	Signature *Signature

	// Record is the parsed DKIM DNS record.
	Record *Record

	// RecordAuthentic indicates if a trusted validating recursive resolver
	// authenticated the DNS record lookup.
	RecordAuthentic bool

	// Err contains any error that occurred during verification.
	Err error
}

Result represents the result of verifying a single DKIM-Signature.

func Verify

func Verify(ctx context.Context, resolver ravendns.Resolver, message []byte) ([]Result, error)

Verify is a convenience function to verify DKIM signatures.

func VerifyMailContext

func VerifyMailContext(ctx context.Context, mail *ravenmail.Mail, resolver ravendns.Resolver) ([]Result, error)

VerifyMailContext verifies DKIM signatures in a mail message. Returns verification results for each signature found.

func VerifyReader

func VerifyReader(ctx context.Context, resolver ravendns.Resolver, message io.ReaderAt) ([]Result, error)

VerifyReader is a convenience function to verify DKIM signatures from a reader.

type Signature

type Signature struct {
	// Required fields
	Version       int      // v= Version, must be 1
	Algorithm     string   // a= Algorithm (e.g., "rsa-sha256")
	Signature     []byte   // b= Signature data
	BodyHash      []byte   // bh= Body hash
	Domain        string   // d= Signing domain
	SignedHeaders []string // h= Signed header fields
	Selector      string   // s= Selector

	// Optional fields
	Canonicalization string   // c= Canonicalization (e.g., "relaxed/simple")
	Identity         string   // i= Agent or User Identifier (AUID)
	Length           int64    // l= Body length limit (-1 if not set)
	QueryMethods     []string // q= Query methods
	SignTime         int64    // t= Signature timestamp (-1 if not set)
	ExpireTime       int64    // x= Signature expiration (-1 if not set)
	CopiedHeaders    []string // z= Copied header fields
}

Signature represents a parsed DKIM-Signature header (RFC 6376 Section 3.5).

func NewSignature

func NewSignature() *Signature

NewSignature creates a new Signature with default values.

func ParseSignature

func ParseSignature(header string) (*Signature, []byte, error)

ParseSignature parses a DKIM-Signature header value. The input should include the header name (DKIM-Signature:). Returns the parsed signature and the original header with b= value removed (for signature verification).

func (*Signature) AlgorithmHash

func (s *Signature) AlgorithmHash() string

AlgorithmHash returns the hash algorithm part (e.g., "sha256" from "rsa-sha256").

func (*Signature) AlgorithmSign

func (s *Signature) AlgorithmSign() string

AlgorithmSign returns the signing algorithm part (e.g., "rsa" from "rsa-sha256").

func (*Signature) BodyCanon

func (s *Signature) BodyCanon() Canonicalization

BodyCanon returns the body canonicalization algorithm.

func (*Signature) Header

func (s *Signature) Header(includeSignature bool) (string, error)

Header generates the DKIM-Signature header string. If includeSignature is false, the b= value is left empty for signing.

func (*Signature) HeaderCanon

func (s *Signature) HeaderCanon() Canonicalization

HeaderCanon returns the header canonicalization algorithm.

func (*Signature) IsExpired

func (s *Signature) IsExpired() bool

IsExpired returns true if the signature has expired.

type Signer

type Signer struct {
	// Domain is the signing domain (d= tag).
	Domain string

	// Selector is the selector for the signing key (s= tag).
	Selector string

	// PrivateKey is the signing key.
	// Supported types: *rsa.PrivateKey, ed25519.PrivateKey
	PrivateKey crypto.Signer

	// Headers is the list of headers to sign.
	// If empty, DefaultSignedHeaders is used.
	Headers []string

	// HeaderCanonicalization is the header canonicalization algorithm.
	// Default is CanonRelaxed.
	HeaderCanonicalization Canonicalization

	// BodyCanonicalization is the body canonicalization algorithm.
	// Default is CanonRelaxed.
	BodyCanonicalization Canonicalization

	// Hash is the hash algorithm name (e.g., "sha256").
	// Default is "sha256".
	Hash string

	// Identity is the signing identity (i= tag).
	// If empty, defaults to "@" + Domain.
	Identity string

	// Expiration is the signature validity period.
	// If zero, no expiration is set.
	Expiration time.Duration

	// OversignHeaders causes header names to be repeated to prevent header addition.
	// When enabled, each header in Headers is signed one more time than it appears
	// in the message, which prevents additional headers with the same name from
	// being added later.
	OversignHeaders bool
}

Signer provides DKIM message signing.

func (*Signer) Sign

func (s *Signer) Sign(message []byte) (string, error)

Sign signs the message and returns the DKIM-Signature header. The message should be the complete RFC 5322 message (headers + body).

type Status

type Status string

Status represents the result of DKIM verification per RFC 8601.

const (
	// StatusNone indicates the message was not signed.
	StatusNone Status = "none"

	// StatusPass indicates the signature was verified successfully.
	StatusPass Status = "pass"

	// StatusFail indicates the signature verification failed.
	StatusFail Status = "fail"

	// StatusPolicy indicates the signature is not accepted by policy.
	StatusPolicy Status = "policy"

	// StatusNeutral indicates the signature could not be processed.
	StatusNeutral Status = "neutral"

	// StatusTemperror indicates a temporary error (e.g., DNS timeout).
	StatusTemperror Status = "temperror"

	// StatusPermerror indicates a permanent error (e.g., invalid syntax).
	StatusPermerror Status = "permerror"
)

type Verifier

type Verifier struct {
	// Resolver is the DNS resolver to use.
	Resolver ravendns.Resolver

	// IgnoreTestMode ignores the t=y flag in DKIM records.
	// When false (default), signatures from domains in test mode
	// that fail verification return StatusNone instead of StatusFail.
	IgnoreTestMode bool

	// Policy is a function that can reject signatures based on policy.
	// Return an error to reject the signature with StatusPolicy.
	// If nil, all signatures are accepted.
	Policy func(*Signature) error

	// MinRSAKeyBits is the minimum RSA key size to accept.
	// Default is 1024 (per RFC 8301).
	MinRSAKeyBits int
}

Verifier provides DKIM signature verification.

func (*Verifier) Verify

func (v *Verifier) Verify(ctx context.Context, message []byte) ([]Result, error)

Verify verifies all DKIM-Signature headers in the message. Returns a result for each signature found.

func (*Verifier) VerifyReader

func (v *Verifier) VerifyReader(ctx context.Context, message io.ReaderAt) ([]Result, error)

VerifyReader verifies all DKIM-Signature headers from a reader.

Jump to

Keyboard shortcuts

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