Release Mail Tools 0.2.0
Verify / verify (push) Canceled after 0s

This commit is contained in:
2026-09-02 10:47:30 +02:00
parent 9bc0870404
commit 2ab972a9f3
35 changed files with 2061 additions and 150 deletions
+110
View File
@@ -0,0 +1,110 @@
import { bytesToBase64 } from "@add-ideas/toolbox-helpers";
import { describe, expect, it } from "vitest";
import {
inspectDkimSignatures,
prepareDkimVerification,
verifyDkimSignature,
} from "../../src/core/dkim";
import { parseMessage } from "../../src/core/mime";
async function signedMessage() {
const keyPair = await crypto.subtle.generateKey(
{
name: "RSASSA-PKCS1-v1_5",
modulusLength: 1024,
publicExponent: new Uint8Array([1, 0, 1]),
hash: "SHA-256",
},
true,
["sign", "verify"],
);
const body = "Hello DKIM!\r\n";
const bodyHash = bytesToBase64(
new Uint8Array(
await crypto.subtle.digest("SHA-256", new TextEncoder().encode(body)),
),
);
const unsigned = [
"From: Ada <ada@example.test>",
"Subject: Local verification",
`DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=example.test; s=mail; h=from:subject; bh=${bodyHash}; b=`,
"",
body,
].join("\r\n");
const prepared = await prepareDkimVerification(parseMessage(unsigned));
const signature = bytesToBase64(
new Uint8Array(
await crypto.subtle.sign(
"RSASSA-PKCS1-v1_5",
keyPair.privateKey,
prepared.headerBytes,
),
),
);
const publicKey = bytesToBase64(
new Uint8Array(await crypto.subtle.exportKey("spki", keyPair.publicKey)),
);
return {
source: unsigned.replace(/; b=(?=\r\n)/u, `; b=${signature}`),
keyRecord: `v=DKIM1; k=rsa; h=sha256; s=email; p=${publicKey}`,
};
}
describe("DKIM laboratory", () => {
it("inspects signatures without network access", () => {
const message = parseMessage(
"From: a@example.test\r\nDKIM-Signature: v=1; a=rsa-sha256; d=example.test; s=mail; c=relaxed/relaxed; h=from; bh=YQ==; b=Yg==\r\n\r\na",
);
expect(inspectDkimSignatures(message)[0]).toMatchObject({
queryName: "mail._domainkey.example.test",
algorithm: "rsa-sha256",
headerCanonicalization: "relaxed",
bodyCanonicalization: "relaxed",
supported: true,
});
});
it("verifies body and header signatures against a pasted key record", async () => {
const fixture = await signedMessage();
const result = await verifyDkimSignature(
parseMessage(fixture.source),
0,
fixture.keyRecord,
);
expect(result).toMatchObject({
bodyHash: "pass",
signature: "pass",
status: "pass",
});
});
it("rejects a changed body before checking the signature", async () => {
const fixture = await signedMessage();
const changed = fixture.source.replace("Hello DKIM!", "Hello altered!");
const result = await verifyDkimSignature(
parseMessage(changed),
0,
fixture.keyRecord,
);
expect(result).toMatchObject({
bodyHash: "fail",
signature: "not-checked",
status: "fail",
});
});
it("fails closed for malformed key records and unsupported signatures", async () => {
const fixture = await signedMessage();
expect(
await verifyDkimSignature(parseMessage(fixture.source), 0, "v=DKIM1; p="),
).toMatchObject({ signature: "error", status: "permerror" });
const obsolete = parseMessage(
fixture.source.replace("a=rsa-sha256", "a=rsa-sha1"),
);
expect(
await verifyDkimSignature(obsolete, 0, fixture.keyRecord),
).toMatchObject({
status: "permerror",
});
});
});