arc

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

arc

Package arc implements Authenticated Received Chain (ARC, RFC 8617).

Import

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

What It Does

  • Verifies existing ARC sets on a message.
  • Seals a message with a new ARC set at intermediaries.
  • Exposes structured verification results for policy decisions.

Key API

  • Verifier.Verify(ctx, message)
  • Sealer.Seal(message, authServID, authResults, chainValidation)
  • SignMail(...), QuickSeal(...)
  • EvaluateARCForDMARC(...)

Example

verifier := &arc.Verifier{Resolver: resolver}
result, err := verifier.Verify(ctx, rawMessage)
if err != nil {
    panic(err)
}

if result.Status == arc.StatusPass {
    // ARC chain validated.
}

sealer := &arc.Sealer{
    Domain:     "example.com",
    Selector:   "arc1",
    PrivateKey: privateKey,
}

sealResult, err := sealer.Seal(
    rawMessage,
    "mx.example.com",
    "dkim=pass header.d=example.com",
    arc.ChainValidationNone,
)
if err != nil {
    panic(err)
}

_ = sealResult.AuthenticationResults
_ = sealResult.MessageSignature
_ = sealResult.Seal

Documentation

Overview

Package arc implements Authenticated Received Chain (ARC) per RFC 8617.

ARC provides a way to preserve email authentication results across intermediaries that may modify messages (such as mailing lists). When a message passes through an intermediary, ARC creates a chain of custody that allows receivers to validate that the message was authenticated when received by each intermediary.

ARC consists of three header types:

  • ARC-Authentication-Results: Contains authentication results from the intermediary
  • ARC-Message-Signature: A DKIM-like signature over the message
  • ARC-Seal: A signature that seals the entire ARC chain

Basic Usage

Verifying an ARC chain:

verifier := arc.Verifier{
    Resolver: resolver,
}
result, err := verifier.Verify(ctx, message)
if result.Status == arc.StatusPass {
    // ARC chain is valid
}

Sealing a message (as an intermediary):

sealer := arc.Sealer{
    Domain:     "example.com",
    Selector:   "arc1",
    PrivateKey: privateKey,
}
headers, err := sealer.Seal(message, "mx.example.com", authResults, arc.ChainValidationNone)

Chain Validation States

RFC 8617 defines the following chain validation states:

  • none: No ARC headers present
  • pass: All ARC sets validated successfully
  • fail: ARC validation failed

Integration with DMARC

ARC results can be used by DMARC to override authentication failures when a trusted ARC chain indicates the message was originally authenticated. This is particularly useful for mailing lists that modify messages.

References

  • RFC 8617: The Authenticated Received Chain (ARC) Protocol
  • RFC 6376: DomainKeys Identified Mail (DKIM) Signatures
  • RFC 8601: Message Header Field for Indicating Message Authentication Status

Index

Constants

View Source
const MaxInstance = 50

MaxInstance is the maximum allowed ARC instance number per RFC 8617.

Variables

View Source
var (
	// ErrNoARCHeaders indicates no ARC headers were found in the message.
	ErrNoARCHeaders = errors.New("arc: no ARC headers found")

	// ErrInvalidChain indicates the ARC chain is structurally invalid.
	ErrInvalidChain = errors.New("arc: invalid ARC chain structure")

	// ErrMissingSet indicates a required ARC set is missing.
	ErrMissingSet = errors.New("arc: missing ARC set")

	// ErrDuplicateSet indicates duplicate ARC sets with the same instance number.
	ErrDuplicateSet = errors.New("arc: duplicate ARC set instance")

	// ErrGapInChain indicates a gap in the ARC chain instance numbers.
	ErrGapInChain = errors.New("arc: gap in ARC chain instance numbers")

	// ErrInvalidInstance indicates an invalid instance number.
	ErrInvalidInstance = errors.New("arc: invalid instance number")

	// ErrInstanceTooHigh indicates the instance number exceeds the limit.
	ErrInstanceTooHigh = errors.New("arc: instance number exceeds limit (50)")

	// ErrSealFailed indicates the ARC-Seal verification failed.
	ErrSealFailed = errors.New("arc: seal verification failed")

	// ErrMessageSignatureFailed indicates the ARC-Message-Signature verification failed.
	ErrMessageSignatureFailed = errors.New("arc: message signature verification failed")

	// ErrChainValidationMismatch indicates the cv= tag doesn't match the actual chain state.
	ErrChainValidationMismatch = errors.New("arc: chain validation status mismatch")

	// ErrDNS indicates a DNS lookup error occurred.
	ErrDNS = errors.New("arc: DNS lookup error")

	// ErrNoRecord indicates no DNS record was found.
	ErrNoRecord = errors.New("arc: no DNS record found")

	// ErrSyntax indicates a syntax error in an ARC header.
	ErrSyntax = errors.New("arc: syntax error")

	// ErrMissingTag indicates a required tag is missing.
	ErrMissingTag = errors.New("arc: missing required tag")

	// ErrInvalidVersion indicates an invalid version tag.
	ErrInvalidVersion = errors.New("arc: invalid version")

	// ErrAlgorithmUnknown indicates an unknown signing algorithm.
	ErrAlgorithmUnknown = errors.New("arc: unknown algorithm")

	// ErrHashUnknown indicates an unknown hash algorithm.
	ErrHashUnknown = errors.New("arc: unknown hash algorithm")

	// ErrCanonicalizationUnknown indicates an unknown canonicalization algorithm.
	ErrCanonicalizationUnknown = errors.New("arc: unknown canonicalization")

	// ErrKeyRevoked indicates the signing key has been revoked.
	ErrKeyRevoked = errors.New("arc: key has been revoked")

	// ErrWeakKey indicates the key is too weak.
	ErrWeakKey = errors.New("arc: key is too weak")

	// ErrExpired indicates the signature has expired.
	ErrExpired = errors.New("arc: signature expired")

	// ErrBodyHashMismatch indicates the body hash doesn't match.
	ErrBodyHashMismatch = errors.New("arc: body hash mismatch")

	// ErrSignatureFailed indicates signature verification failed.
	ErrSignatureFailed = errors.New("arc: signature verification failed")

	// ErrInvalidAuthResults indicates invalid Authentication-Results header.
	ErrInvalidAuthResults = errors.New("arc: invalid Authentication-Results")

	// ErrTLD indicates the domain is a top-level domain.
	ErrTLD = errors.New("arc: domain is a top-level domain")

	// ErrFromRequired indicates the From header must be signed.
	ErrFromRequired = errors.New("arc: From header must be signed")
)

Common errors for ARC processing.

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",
	"Received",
	"DKIM-Signature",
}

DefaultSignedHeaders is the default list of headers to sign in ARC-Message-Signature.

Functions

func EvaluateARCForDMARC

func EvaluateARCForDMARC(result *Result, trustedDomains []string) (trusted bool, oldestTrustedPass int)

EvaluateARCForDMARC helps DMARC evaluation by providing ARC chain information. This can be used to override DMARC failures when a trusted ARC chain exists.

Parameters:

  • result: The ARC verification result
  • trustedDomains: List of domains whose ARC seals are trusted

Returns:

  • trusted: Whether the ARC chain was sealed by a trusted domain
  • oldestTrustedPass: The oldest instance where a trusted domain passed ARC

func QuickSeal

func QuickSeal(mail *ravenmail.Mail, domain, selector string, privateKey crypto.Signer, authServID, authResults string, chainValidation ChainValidationStatus) error

QuickSeal is a simplified sealing function for common use cases.

func SignMail

func SignMail(mail *ravenmail.Mail, sealer *Sealer, authServID, authResults string, chainValidation ChainValidationStatus) error

SignMail adds ARC headers to a mail message (sealing it). This is a convenience function for sealing mail objects.

Parameters:

  • mail: The mail message to seal
  • sealer: The sealer configuration
  • authServID: The authentication service identifier (your domain)
  • authResults: The authentication results string (from SPF, DKIM, DMARC)
  • chainValidation: The status of any existing ARC chain

The function will prepend the three ARC headers to the message: ARC-Seal, ARC-Message-Signature, and ARC-Authentication-Results.

Types

type Algorithm

type Algorithm string

Algorithm represents an ARC signing algorithm. ARC uses the same algorithms as DKIM.

const (
	// AlgRSASHA256 is the RSA-SHA256 algorithm (required by RFC 8617).
	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 AuthenticationResults

type AuthenticationResults struct {
	// Instance is the ARC set instance number (i= tag).
	Instance int

	// AuthServID is the authentication service identifier (required).
	AuthServID string

	// Results contains the authentication results string.
	// This is the raw results string as it appears in the header.
	Results string

	// Raw is the complete raw header value.
	Raw string
}

AuthenticationResults represents a parsed ARC-Authentication-Results header. Per RFC 8617, this header preserves the authentication results observed by an intermediary.

func ParseAuthenticationResults

func ParseAuthenticationResults(value string) (*AuthenticationResults, error)

ParseAuthenticationResults parses an ARC-Authentication-Results header value.

func (*AuthenticationResults) Header

func (aar *AuthenticationResults) Header() string

Header generates the ARC-Authentication-Results header string.

type Canonicalization

type Canonicalization string

Canonicalization represents header/body canonicalization algorithms. ARC uses the same canonicalization as DKIM.

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

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

type ChainValidationStatus

type ChainValidationStatus string

ChainValidationStatus represents the chain validation status (cv= tag).

const (
	// ChainValidationNone indicates no prior ARC chain.
	ChainValidationNone ChainValidationStatus = "none"

	// ChainValidationPass indicates the prior ARC chain validated.
	ChainValidationPass ChainValidationStatus = "pass"

	// ChainValidationFail indicates the prior ARC chain failed validation.
	ChainValidationFail ChainValidationStatus = "fail"
)

func GetARCChainStatus

func GetARCChainStatus(result *Result) ChainValidationStatus

GetARCChainStatus determines the validation status for sealing based on verification results. This is useful when an intermediary needs to add its own ARC set.

Usage:

result, err := VerifyMailContext(ctx, mail, resolver)
chainStatus := GetARCChainStatus(result)
err = SignMail(mail, sealer, authServID, authResults, chainStatus)

type DKIMRecord

type DKIMRecord struct {
	Version   string
	Key       string
	Hashes    []string
	Pubkey    []byte
	PublicKey any
}

DKIMRecord represents a DKIM public key record.

type MessageSignature

type MessageSignature struct {
	// Instance is the ARC set instance number (i= tag). Required.
	Instance int

	// Version is the signature version (v= tag). Must be 1.
	Version int

	// Algorithm is the signing algorithm (a= tag). Required.
	Algorithm string

	// Signature is the message signature (b= tag). Required.
	Signature []byte

	// BodyHash is the body hash (bh= tag). Required.
	BodyHash []byte

	// Domain is the signing domain (d= tag). Required.
	Domain string

	// SignedHeaders is the list of signed headers (h= tag). Required.
	SignedHeaders []string

	// Selector is the selector (s= tag). Required.
	Selector string

	// Canonicalization is the canonicalization (c= tag).
	// Format: "header/body" (e.g., "relaxed/relaxed").
	Canonicalization string

	// Length is the body length limit (l= tag). -1 if not set.
	Length int64

	// Timestamp is the signature timestamp (t= tag). -1 if not set.
	Timestamp int64

	// Expiration is the signature expiration (x= tag). -1 if not set.
	Expiration int64

	// Raw is the complete raw header value.
	Raw string
}

MessageSignature represents a parsed ARC-Message-Signature header. This is similar to a DKIM-Signature but with an instance number.

func ParseMessageSignature

func ParseMessageSignature(value string) (*MessageSignature, []byte, error)

ParseMessageSignature parses an ARC-Message-Signature header value.

func (*MessageSignature) AlgorithmHash

func (ms *MessageSignature) AlgorithmHash() string

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

func (*MessageSignature) AlgorithmSign

func (ms *MessageSignature) AlgorithmSign() string

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

func (*MessageSignature) BodyCanon

func (ms *MessageSignature) BodyCanon() Canonicalization

BodyCanon returns the body canonicalization algorithm.

func (*MessageSignature) Header

func (ms *MessageSignature) Header(includeSignature bool) string

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

func (*MessageSignature) HeaderCanon

func (ms *MessageSignature) HeaderCanon() Canonicalization

HeaderCanon returns the header canonicalization algorithm.

type Result

type Result struct {
	// Status is the overall chain validation status.
	Status Status

	// OldestPass is the instance number of the oldest passing ARC set.
	// This is useful for policy decisions about trusted intermediaries.
	// Zero if no sets passed.
	OldestPass int

	// Sets contains the parsed ARC sets, ordered by instance number.
	Sets []*Set

	// Err contains any error that occurred during validation.
	Err error

	// FailedInstance is the instance number where validation failed.
	// Zero if validation passed or no sets were present.
	FailedInstance int

	// FailedReason provides details about why validation failed.
	FailedReason string
}

Result represents the result of ARC chain validation.

func VerifyMailContext

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

VerifyMailContext verifies ARC chain in a mail message. Returns the verification result.

type Seal

type Seal struct {
	// Instance is the ARC set instance number (i= tag). Required.
	Instance int

	// Version is the seal version (v= tag). Must be 1.
	Version int

	// Algorithm is the signing algorithm (a= tag). Required.
	Algorithm string

	// Signature is the seal signature (b= tag). Required.
	Signature []byte

	// Domain is the signing domain (d= tag). Required.
	Domain string

	// Selector is the selector (s= tag). Required.
	Selector string

	// ChainValidation is the chain validation status (cv= tag). Required.
	ChainValidation ChainValidationStatus

	// Timestamp is the signature timestamp (t= tag). -1 if not set.
	Timestamp int64

	// Raw is the complete raw header value.
	Raw string
}

Seal represents a parsed ARC-Seal header. The seal signs the ARC chain to prevent tampering.

func ParseSeal

func ParseSeal(value string) (*Seal, []byte, error)

ParseSeal parses an ARC-Seal header value.

func (*Seal) AlgorithmHash

func (s *Seal) AlgorithmHash() string

AlgorithmHash returns the hash algorithm part for the seal.

func (*Seal) AlgorithmSign

func (s *Seal) AlgorithmSign() string

AlgorithmSign returns the signing algorithm part for the seal.

func (*Seal) Header

func (s *Seal) Header(includeSignature bool) string

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

type SealResult

type SealResult struct {
	// AuthenticationResults is the ARC-Authentication-Results header.
	AuthenticationResults string

	// MessageSignature is the ARC-Message-Signature header.
	MessageSignature string

	// Seal is the ARC-Seal header.
	Seal string

	// Instance is the ARC set instance number.
	Instance int
}

SealResult contains the headers generated by sealing.

type Sealer

type Sealer 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 in ARC-Message-Signature.
	// 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

	// Clock is used for timestamp generation.
	// If nil, time.Now is used.
	Clock func() time.Time
}

Sealer provides ARC message sealing.

func (*Sealer) Seal

func (s *Sealer) Seal(message []byte, authServID, authResults string, chainValidation ChainValidationStatus) (*SealResult, error)

Seal creates a new ARC set for the message. The authResults parameter should contain the authentication results observed by this intermediary. The chainValidation parameter indicates the validation status of any existing ARC chain.

Returns the three ARC headers that should be prepended to the message.

type Set

type Set struct {
	// Instance is the ARC set instance number (i= tag).
	// Must be between 1 and MaxInstance (50).
	Instance int

	// AuthenticationResults is the parsed ARC-Authentication-Results header.
	AuthenticationResults *AuthenticationResults

	// MessageSignature is the parsed ARC-Message-Signature header.
	MessageSignature *MessageSignature

	// Seal is the parsed ARC-Seal header.
	Seal *Seal
}

Set represents a complete ARC set for a single instance. Each set contains exactly one of each header type with matching instance numbers.

type Status

type Status string

Status represents the result of ARC chain validation per RFC 8617.

const (
	// StatusNone indicates no ARC headers are present.
	StatusNone Status = "none"

	// StatusPass indicates all ARC sets validated successfully.
	StatusPass Status = "pass"

	// StatusFail indicates ARC validation failed.
	StatusFail Status = "fail"
)

type Verifier

type Verifier struct {
	// Resolver is the DNS resolver for key lookups.
	Resolver ravendns.Resolver

	// MinRSAKeyBits is the minimum RSA key size to accept.
	// Default is 1024.
	MinRSAKeyBits int

	// IgnoreExpired allows verification of expired signatures.
	// Default is false.
	IgnoreExpired bool

	// Clock is used for timestamp verification.
	// If nil, time.Now is used.
	Clock func() time.Time
}

Verifier provides ARC chain verification.

func (*Verifier) Verify

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

Verify verifies the ARC chain in a message.

func (*Verifier) VerifyReader

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

VerifyReader verifies the ARC chain from a reader.

Jump to

Keyboard shortcuts

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