pyrtan / tests / conformance / identity.cjs
3,797 bytes · last changed 2026-10-28 in identity.js: the documents a root signs, and encrypted backups, agreeing with Python
| 1 | // Run identity.json through identity.js and compare with what Python said. |
| 2 | // node tests/conformance/identity.cjs (exit 1 on any difference) |
| 3 | const fs = require("fs"); |
| 4 | const path = require("path"); |
| 5 | const ROOT = path.resolve(__dirname, "..", ".."); |
| 6 | const Id = require(path.join(ROOT, "src", "pyrtan", "js", "identity.js")); |
| 7 | const v = JSON.parse(fs.readFileSync(path.join(__dirname, "identity.json"), "utf8")); |
| 8 | const bytes = (h) => Uint8Array.from(h.match(/../g) || [], (x) => parseInt(x, 16)); |
| 9 | |
| 10 | (async () => { |
| 11 | let failed = 0, total = 0; |
| 12 | const check = (label, python, js) => { |
| 13 | total++; |
| 14 | const a = Id.canonical(python === undefined ? null : python), b = Id.canonical(js === undefined ? null : js); |
| 15 | if (a === b) { console.log(`ok ${label}`); return; } |
| 16 | failed++; |
| 17 | console.log(`DIFF ${label}\n python: ${a.slice(0, 400)}\n js: ${b.slice(0, 400)}`); |
| 18 | }; |
| 19 | for (const s of v.sha3_256) check(`sha3-256 of ${s.in.length / 2} bytes`, s.out, Id.hex(Id.sha3_256(bytes(s.in)))); |
| 20 | for (const i of v.ids) { |
| 21 | check(`id of ${i.key.slice(0, 8)}…`, i.id, Id.encode(bytes(i.key))); |
| 22 | check(`key of ${Id.short(i.id)}`, i.key, Id.hex(Id.decode(i.id))); |
| 23 | } |
| 24 | for (const t of v.refuse) check(`refuses ${t.slice(0, 24)}…`, false, Id.valid(t)); |
| 25 | for (const t of v.accept) check(`accepts ${t.slice(0, 24)}…`, true, Id.valid(t)); |
| 26 | for (const c of v.canonical) check(`canonical ${c.canonical.slice(0, 40)}`, c.canonical, Id.canonical(c.value)); |
| 27 | check("a pasted note: good", null, Id.noteProblem(v.note, v.note.device, v.note.issued)); |
| 28 | check("a pasted note: another key's", true, /not this browser's key/.test(Id.noteProblem(v.note, v.ids[2].id, v.note.issued))); |
| 29 | check("a pasted note: expired", "the note has expired", Id.noteProblem(v.note, v.note.device, v.note.expires + 1)); |
| 30 | for (const r of v.requests) { |
| 31 | const label = `${r.method} ${r.path}${r.note ? " (device)" : ""}`; |
| 32 | check(`message ${label}`, r.message, new TextDecoder().decode(Id.requestMessage(r.audience, r.method, r.path, r.date, r.body))); |
| 33 | const key = await Id.fromSeedAndPublic(bytes(r.seed), bytes(r.public)); |
| 34 | check(`headers ${label}`, r.headers, await Id.signRequest(key, r.audience, r.method, r.path, r.body, r.note, r.date)); |
| 35 | } |
| 36 | for (const d of v.documents) { |
| 37 | const key = await Id.fromSeedAndPublic(bytes(d.seed), bytes(d.public)), m = d.make; |
| 38 | let got; |
| 39 | if (d.kind === "identity") got = await Id.makeIdentity(key, m); |
| 40 | else if (d.kind === "device") got = await Id.makeDevice(key, m.device, m.may, m); |
| 41 | else if (d.kind === "name") got = await Id.makeName(key, m.name, m); |
| 42 | else if (d.kind === "handover") got = await Id.makeName(key, m.name, Object.assign({}, m, { handoverFrom: await Id.fromSeedAndPublic(bytes(d.from_seed), bytes(d.from_public)) })); |
| 43 | else if (d.kind === "release") got = await Id.makeRelease(key, m.name, m); |
| 44 | check(`signs a ${d.kind}`, d.expect, got); |
| 45 | } |
| 46 | for (const b of v.backups) { |
| 47 | const seed = await Id.openBackup(b.expect, b.passphrase); |
| 48 | check("opens a backup Python wrote", b.seed, Id.hex(seed)); |
| 49 | check("writes the backup Python writes", b.expect, await Id.makeBackup(bytes(b.seed), b.expect.id, b.passphrase, { salt: bytes(b.salt), iv: bytes(b.iv), iterations: b.iterations, made: b.made, document: b.expect.document })); |
| 50 | let refused = null; try { await Id.openBackup(b.expect, b.passphrase + "x"); } catch (e) { refused = e.message; } |
| 51 | check("refuses a wrong passphrase", true, /does not open/.test(refused || "")); |
| 52 | check("refuses a weakened kdf", true, /at least/.test(Id.backupProblem(Object.assign({}, b.expect, { kdf: Object.assign({}, b.expect.kdf, { iterations: 1000 }) })) || "")); |
| 53 | } |
| 54 | console.log(`${total - failed}/${total} agree`); |
| 55 | process.exit(failed ? 1 : 0); |
| 56 | })(); |