feat: adopt smolmail protocol 1.1 with accept tokens and leave-mail-on-server
This commit is contained in:
parent
eef7bf9b1d
commit
d6fa097626
11 changed files with 911 additions and 232 deletions
|
|
@ -71,6 +71,18 @@ for (const v of vectors.ed_to_x25519) {
|
|||
eq(`x_pub[${v.name}]`, hex(crypto.ed25519ToX25519(crypto.ed25519PublicKey(unhex(v.seed)))), v.x_pub);
|
||||
}
|
||||
|
||||
// --- §2/§5.8: master-derived rotation seeds and accept tokens -----------------
|
||||
|
||||
{
|
||||
const t = vectors.tokens;
|
||||
const master = unhex(t.master);
|
||||
eq("identity-seed-0", hex(proto.identitySeed(master, 0)), t.identity_seed_0);
|
||||
eq("identity-seed-1", hex(proto.identitySeed(master, 1)), t.identity_seed_1);
|
||||
eq("accept-key", hex(proto.acceptKeyFor(master)), t.accept_key);
|
||||
eq("token-for", hex(proto.tokenFor(master, unhex(t.correspondent))), t.token);
|
||||
eq("accept-mac", hex(proto.acceptMac(unhex(t.token), unhex(t.message_id))), t.accept_mac);
|
||||
}
|
||||
|
||||
// --- §5 envelope --------------------------------------------------------------
|
||||
|
||||
{
|
||||
|
|
@ -125,19 +137,22 @@ for (const v of vectors.ed_to_x25519) {
|
|||
// --- frontmatter (§5.5) -------------------------------------------------------
|
||||
|
||||
{
|
||||
const spec = "---\nSubject: Re: the thing\nIn-Reply-To: 4f2a1c9e8b7d6a5f3e2d1c0b9a8f7e6d\nX-Mood: cautiously optimistic\n---\nBody text starts here.";
|
||||
const rid = "4f2a1c9e8b7d6a5f3e2d1c0b9a8f7e6d5c4b3a291807f6e5d4c3b2a1908f7e6d5"; // §5.4: 64 hex chars
|
||||
const spec = `---\nSubject: Re: the thing\nIn-Reply-To: ${rid}\nX-Mood: cautiously optimistic\n---\nBody text starts here.`;
|
||||
const { fields, body } = proto.parseFrontmatter(spec);
|
||||
eq("fm-subject", fields.Subject, "Re: the thing");
|
||||
eq("fm-reply", fields["In-Reply-To"], "4f2a1c9e8b7d6a5f3e2d1c0b9a8f7e6d");
|
||||
// §5.5: keys are compared case-insensitively, so they come back lowercased.
|
||||
eq("fm-subject", fields.subject, "Re: the thing");
|
||||
eq("fm-reply", fields["in-reply-to"], rid);
|
||||
eq("fm-case-insensitive", proto.parseFrontmatter("---\nSUBJECT: hi\n---\nx").fields.subject, "hi");
|
||||
eq("fm-body", body, "Body text starts here.");
|
||||
// a malformed line invalidates the whole block, which fails closed toward display
|
||||
eq("fm-malformed", proto.parseFrontmatter("---\nno colon here\n---\nrest").body, "---\nno colon here\n---\nrest");
|
||||
eq("fm-unterminated", proto.parseFrontmatter("---\nSubject: x\nno end").body, "---\nSubject: x\nno end");
|
||||
eq("fm-first-wins", proto.parseFrontmatter("---\nA: 1\nA: 2\n---\ntext").fields.A, "1");
|
||||
eq("fm-first-wins", proto.parseFrontmatter("---\nA: 1\nA: 2\n---\ntext").fields.a, "1");
|
||||
eq("fm-escape", proto.buildFrontmatter([], "---\nactual body"), "---\n---\n---\nactual body");
|
||||
eq("fm-build", proto.buildFrontmatter([["Subject", "hi"]], "there"), "---\nSubject: hi\n---\nthere");
|
||||
eq("fm-no-block", proto.buildFrontmatter([], "plain"), "plain");
|
||||
eq("fm-too-many-keys", proto.parseFrontmatter("---\n" + "X: y\n".repeat(65) + "---\nbody").fields.Subject ?? "none", "none");
|
||||
eq("fm-too-many-keys", proto.parseFrontmatter("---\n" + "X: y\n".repeat(65) + "---\nbody").fields.subject ?? "none", "none");
|
||||
}
|
||||
|
||||
// --- addresses (§3) -----------------------------------------------------------
|
||||
|
|
@ -167,21 +182,23 @@ for (const v of vectors.ed_to_x25519) {
|
|||
// --- rotation chains (§7) ------------------------------------------------------
|
||||
|
||||
{
|
||||
const user = "alice";
|
||||
const old = proto.identityFromSeed(crypto.randomBytes(32));
|
||||
const mid = crypto.randomBytes(32);
|
||||
const midPub = crypto.ed25519PublicKey(mid);
|
||||
const fresh = crypto.randomBytes(32);
|
||||
const freshPub = crypto.ed25519PublicKey(fresh);
|
||||
const when = proto.nowSeconds();
|
||||
const chain = [proto.makeCert(old, mid, when), proto.makeCert(proto.identityFromSeed(mid), fresh, when)];
|
||||
ok("chain-valid", proto.walkChain(old.publicKey, freshPub, chain));
|
||||
ok("chain-unchanged", proto.walkChain(old.publicKey, old.publicKey, []));
|
||||
ok("chain-missing-link", !proto.walkChain(old.publicKey, freshPub, chain.slice(1)));
|
||||
const chain = [proto.makeCert(user, old, mid, when),
|
||||
proto.makeCert(user, proto.identityFromSeed(mid), fresh, when)];
|
||||
ok("chain-valid", proto.walkChain(user, old.publicKey, freshPub, chain));
|
||||
ok("chain-unchanged", proto.walkChain(user, old.publicKey, old.publicKey, []));
|
||||
ok("chain-missing-link", !proto.walkChain(user, old.publicKey, freshPub, chain.slice(1)));
|
||||
ok("chain-wrong-username", !proto.walkChain("bob", old.publicKey, freshPub, chain));
|
||||
const forged = chain.slice();
|
||||
forged[1] = proto.makeCert(proto.identityFromSeed(mid), crypto.randomBytes(32), when);
|
||||
ok("chain-broken", !proto.walkChain(old.publicKey, freshPub, forged));
|
||||
ok("chain-oversize", !proto.walkChain(old.publicKey, freshPub, Array(17).fill(chain[0])));
|
||||
eq("cert-len", String(chain[0].length), "136");
|
||||
forged[1] = proto.makeCert(user, proto.identityFromSeed(mid), crypto.randomBytes(32), when);
|
||||
ok("chain-broken", !proto.walkChain(user, old.publicKey, freshPub, forged));
|
||||
ok("chain-oversize", !proto.walkChain(user, old.publicKey, freshPub, Array(17).fill(chain[0])));
|
||||
eq("cert-len", String(chain[0].length), "200");
|
||||
}
|
||||
|
||||
// --- response framing guards (§6.1) -------------------------------------------
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue