eleven / ocho Public

Clone
git clone https://amadan.net/eleven/ocho

Plain git — no account needed to clone.

Download

Download this file

1// Tests for the vault lock layer's crypto (web/static/vaultcrypto.js), run
2// in Node (Node 22 has Web Crypto). node test/vaultcrypto.test.mjs
3//
4// The WebAuthn ceremonies need an authenticator, so — like home's crypto tests —
5// PRF outputs here are synthetic 32-byte secrets: kekFromPRF only ever sees the
6// bytes, so the pipeline from PRF output to unsealed value is exercised end to
7// end, with only the authenticator itself stubbed out.
8import assert from "node:assert/strict";
9import {
10 prfEvalInput,
11 kekFromPRF,
12 importKEK,
13 generateVaultKeypair,
14 wrapVaultPriv,
15 unwrapVaultPriv,
16 sealValue,
17 unsealValue,
18 sealVaultKeyTo,
19 openVaultHandoff,
20} from "../web/static/vaultcrypto.js";
21
22let failures = 0;
23async function test(name, fn) {
24 try {
25 await fn();
26 console.log("PASS " + name);
27 } catch (e) {
28 console.log("FAIL " + name + " — " + (e && e.message));
29 failures++;
30 }
31}
32
33const prf = () => crypto.getRandomValues(new Uint8Array(32));
34
35await test("prfEvalInput is the fixed versioned constant", () => {
36 assert.equal(new TextDecoder().decode(prfEvalInput()), "eleven-vault-kek-v1");
37});
38
39await test("kekFromPRF is deterministic per PRF output", async () => {
40 const out = prf();
41 const kek1 = await kekFromPRF(out);
42 const kek2 = await kekFromPRF(out);
43 const kp = await generateVaultKeypair();
44 // Same PRF bytes → interchangeable KEKs (wrap with one, unwrap with the other).
45 const wrapped = await wrapVaultPriv(kp.privateKey, kek1);
46 assert.equal(await unwrapVaultPriv(wrapped, kek2), kp.privateKey);
47});
48
49await test("seal to the vault public key → unseal with the private key (incl. unicode + '=' values)", async () => {
50 const kp = await generateVaultKeypair();
51 for (const v of ["hunter2", "pässwörd 🌍", "BASE64==", "KEY=VALUE\nOTHER=x", " leading and trailing "]) {
52 const sealed = await sealValue(kp.publicKey, v);
53 assert.equal(await unsealValue(sealed, kp.privateKey), v);
54 }
55});
56
57await test("unseal with the wrong private key throws (never a wrong value)", async () => {
58 const kp = await generateVaultKeypair();
59 const other = await generateVaultKeypair();
60 const sealed = await sealValue(kp.publicKey, "secret");
61 await assert.rejects(() => unsealValue(sealed, other.privateKey));
62});
63
64await test("wrapVaultPriv under one KEK never opens under another", async () => {
65 const kp = await generateVaultKeypair();
66 const wrapped = await wrapVaultPriv(kp.privateKey, await kekFromPRF(prf()));
67 await assert.rejects(async () => unwrapVaultPriv(wrapped, await kekFromPRF(prf())));
68});
69
70await test("importKEK: raw 32 bytes (the CLI keychain shape) drive the same wrap format", async () => {
71 const kp = await generateVaultKeypair();
72 const raw = crypto.getRandomValues(new Uint8Array(32));
73 const wrapped = await wrapVaultPriv(kp.privateKey, await importKEK(raw));
74 assert.equal(await unwrapVaultPriv(wrapped, await importKEK(raw)), kp.privateKey);
75});
76
77await test("the membership handoff: lock hands the key to a member's roster key → they self-enrol → both unseal", async () => {
78 // Paul locks: vault keypair, his wrap under his (synthetic) PRF KEK.
79 const paulKek = await kekFromPRF(prf());
80 const vault = await generateVaultKeypair();
81 const paulWrap = await wrapVaultPriv(vault.privateKey, paulKek);
82 const sealed = await sealValue(vault.publicKey, "LCHAT_ADMIN_TOKEN=abc123");
83
84 // The handoff: Paul seals the vault PRIVATE key to James's ROSTER public key
85 // (this is the `member` wrap's `wrapped`, ridden by vault.lock's handoffs / a
86 // vault.give). James holds a normal user keypair — his identity, not a vault key.
87 const james = await generateVaultKeypair();
88 const handoff = await sealVaultKeyTo(james.publicKey, vault.privateKey);
89
90 // James self-enrols: open the handoff with HIS session private key (no
91 // biometric — it's sealed to his identity), then wrap the vault key under his
92 // OWN passkey KEK. From here on he needs his passkey.
93 const vaultPrivForJames = await openVaultHandoff(handoff, james.privateKey);
94 const jamesKek = await kekFromPRF(prf());
95 const jamesWrap = await wrapVaultPriv(vaultPrivForJames, jamesKek);
96
97 // Both members now unseal the same value through their own biometric-gated wrap.
98 assert.equal(await unsealValue(sealed, await unwrapVaultPriv(paulWrap, paulKek)), "LCHAT_ADMIN_TOKEN=abc123");
99 assert.equal(await unsealValue(sealed, await unwrapVaultPriv(jamesWrap, jamesKek)), "LCHAT_ADMIN_TOKEN=abc123");
100 // …and a third party (not the handoff's recipient) can't open it.
101 const stranger = await generateVaultKeypair();
102 await assert.rejects(() => openVaultHandoff(handoff, stranger.privateKey));
103});
104
105console.log(failures === 0 ? "\nALL PASSED" : `\n${failures} FAILED`);
106process.exit(failures === 0 ? 0 : 1);
107