1import { describe, expect, it } from "vitest";2import { generateKeyPair } from "@stablelib/x25519";3import { noiseXXInitiator, noiseXXResponder } from "./noise-xx";45function memoryPair() {6 const a: Uint8Array[] = [];7 const b: Uint8Array[] = [];8 const aR: Array<(v: Uint8Array) => void> = [];9 const bR: Array<(v: Uint8Array) => void> = [];10 const tA = {11 async send(m: Uint8Array) {12 const r = bR.shift();13 if (r) r(m); else b.push(m);14 },15 receive(): Promise<Uint8Array> {16 const v = a.shift();17 if (v) return Promise.resolve(v);18 return new Promise<Uint8Array>((resolve) => aR.push(resolve));19 },20 };21 const tB = {22 async send(m: Uint8Array) {23 const r = aR.shift();24 if (r) r(m); else a.push(m);25 },26 receive(): Promise<Uint8Array> {27 const v = b.shift();28 if (v) return Promise.resolve(v);29 return new Promise<Uint8Array>((resolve) => bR.push(resolve));30 },31 };32 return [tA, tB] as const;33}3435describe("Noise XX mutual authentication", () => {36 it("completes the three-message handshake and exchanges static keys", async () => {37 const alice = generateKeyPair();38 const bob = generateKeyPair();39 const [tA, tB] = memoryPair();4041 const aPromise = noiseXXInitiator(alice.secretKey, alice.publicKey, tA.send, tA.receive);42 const bPromise = noiseXXResponder(bob.secretKey, bob.publicKey, tB.send, tB.receive);4344 const [a, b] = await Promise.all([aPromise, bPromise]);45 expect(a.remoteStaticPublicKey).toEqual(bob.publicKey);46 expect(b.remoteStaticPublicKey).toEqual(alice.publicKey);47 expect(a.handshakeHash).toEqual(b.handshakeHash);48 });4950 it("transport frames decrypt in both directions and are bound to the handshake", async () => {51 const alice = generateKeyPair();52 const bob = generateKeyPair();53 const [tA, tB] = memoryPair();5455 const [a, b] = await Promise.all([56 noiseXXInitiator(alice.secretKey, alice.publicKey, tA.send, tA.receive),57 noiseXXResponder(bob.secretKey, bob.publicKey, tB.send, tB.receive),58 ]);5960 const ct1 = a.encrypt(new TextEncoder().encode("hello bob"));61 expect(new TextDecoder().decode(b.decrypt(ct1))).toBe("hello bob");6263 const ct2 = b.encrypt(new TextEncoder().encode("hello alice"));64 expect(new TextDecoder().decode(a.decrypt(ct2))).toBe("hello alice");6566 67 const ct3 = a.encrypt(new TextEncoder().encode("not delivered"));68 ct3[0] ^= 1;69 expect(() => b.decrypt(ct3)).toThrow();70 });7172 it("an attacker who swaps the responder static cannot complete msg2 auth", async () => {73 const alice = generateKeyPair();74 const bobReal = generateKeyPair();75 const bobImpostor = generateKeyPair();76 const [tA, tB] = memoryPair();7778 79 80 81 const aPromise = noiseXXInitiator(alice.secretKey, alice.publicKey, tA.send, tA.receive);82 const bPromise = noiseXXResponder(bobImpostor.secretKey, bobImpostor.publicKey, tB.send, tB.receive);83 const [a] = await Promise.all([aPromise, bPromise]);84 85 expect(a.remoteStaticPublicKey).not.toEqual(bobReal.publicKey);86 expect(a.remoteStaticPublicKey).toEqual(bobImpostor.publicKey);87 });88});