@portable/arc
Functions
Arc.seal
Adds an ARC set to a message (RFC 8617 section 5.1) and resolves to its three fields, ending in
CRLF, to put above the message's other fields: ARC-Seal, ARC-Message-Signature and
ARC-Authentication-Results. The new set's instance is one above the highest instance on the
message. The ARC-Message-Signature uses relaxed/relaxed canonicalization, and with cv=fail
the seal covers only the new set (section 5.1.2).
Throws a TypeError or RangeError for invalid options, such as an authResults with a line
break that isn't folding, and Arc.InvalidInputError for a message that must not be sealed.
async function seal(
message: Message,
options: seal.Options,
): Promise<string>;| Option | Type | Default | Description |
|---|---|---|---|
domain | string | The sealing domain (d= of both signatures). U-labels are converted to A-labels. | |
selector | string | The selector (s=): the public key is published at <selector>._domainkey.<domain>. | |
privateKey | string | CryptoKey | JsonWebKey | An RSA key of at least 1024 bits or an Ed25519 key, in any form Dkim.sign takes: a CryptoKey, a PEM PRIVATE KEY or RSA PRIVATE KEY, or a JWK. The key type sets a=. | |
authResults | string | The ARC-Authentication-Results payload: this server's authserv-id and the results it found, such as mx.example.com; spf=pass smtp.mailfrom=example.org. It may be folded with CRLF and whitespace. | |
chainValidation | Result.Code | 'none' for a message without them | The cv= value: the result of Arc.verify for this message. Required when the message already has ARC fields. |
headers | readonly string[] | DKIM-Signature, Delivered-To and the fields Dkim.sign signs | The fields the ARC-Message-Signature signs, as in Dkim.sign. ARC fields and Authentication-Results are never signed (RFC 8617 section 4.1.2). |
now | any | new Date() | The signature time (t=), as a Date or a Temporal.Instant. |
hasher | ((hash: Dkim.Hash) => Dkim.Hasher) | buffers the body for crypto.subtle.digest | Creates the body hash, as in Dkim.sign. |
signal | AbortSignal | Stops reading the message, and rejects with signal.reason. |
import * as Arc from '@portable/arc';
const arc = await Arc.verify(message, { resolver });
const fields = await Arc.seal(message, {
domain: 'lists.example.org',
selector: 'arc',
privateKey: env.ARC_PRIVATE_KEY,
authResults: `lists.example.org; arc=${arc.result}; spf=pass smtp.mailfrom=example.com`,
chainValidation: arc.result,
});
const forwarded = fields + message;Arc.verify
Validates the ARC chain of a message (RFC 8617 section 5.2) and resolves to pass, fail or
none. It checks every set's structure, the newest ARC-Message-Signature and every ARC-Seal,
with the RFC 6376 tag syntax, RFC 8301 algorithms and Ed25519 seals (RFC 8463). The body is
read once, and the keys are looked up while it is read.
The promise rejects only for invalid options, a failing message stream or an aborted signal.
async function verify(
message: Message,
options: verify.Options,
): Promise<Result>;| Option | Type | Default | Description |
|---|---|---|---|
resolver | Dkim.Resolver | Looks up the keys. Pass Dns.create() or node:dns/promises. | |
now | any | new Date() | The time to check the ARC-Message-Signature's x= against, as a Date or a Temporal.Instant. |
minKeyBits | number | 1024 | RSA keys shorter than this fail the chain (RFC 8301 section 3.2). |
hasher | ((hash: Dkim.Hash) => Dkim.Hasher) | buffers the body for crypto.subtle.digest | Creates the body hash, as in Dkim.verify. |
signal | AbortSignal | Stops reading the message and the key lookups, and rejects with signal.reason. |
import * as Arc from '@portable/arc';
import * as Dns from '@portable/dns';
const arc = await Arc.verify(message, { resolver: Dns.create() });
console.log(`arc=${arc.result}${arc.reason ? ` (${arc.reason})` : ''}`);Errors
Arc.InvalidInputError
Thrown by seal for a message it must not seal: one without a From field, one whose newest
ARC-Seal says cv=fail (RFC 8617 section 5.1 step 2), one that already has 50 ARC sets, or a
cv=pass seal over a chain whose sets can't be read.
class InvalidInputError extends Errors.InvalidInputError {
readonly kind: string;
}import * as Arc from '@portable/arc';
try {
headers = await Arc.seal(message, {
domain,
selector,
privateKey,
authResults,
chainValidation,
});
} catch (error) {
if (!(error instanceof Arc.InvalidInputError)) {
throw error;
}
}Types
Arc.Resolver
The DNS lookup ARC needs: a Dns.Resolver from @portable/dns, node:dns/promises, or any
object with resolveTxt. It is the same as Dkim.Resolver.
type Resolver = Dkim.Resolver;Arc.Result
The outcome of validating a message's ARC chain.
type Result = {
result: Result.Code;
instance: number;
reason?: string | undefined;
cause?: unknown;
sets: readonly Result.Set[];
};