12345678910import { describe, expect, it } from "vitest";11import { generateKeyPair } from "@stablelib/x25519";12import { noiseNKInitiator, noiseNKResponder } from "./noise-js";13import { noiseXXInitiator, noiseXXResponder } from "./noise-xx";14151617const ZERO_POINT = new Uint8Array(32);1819function queuePair() {20 const makeQueue = () => {21 const items: Uint8Array[] = [];22 const waiters: Array<(m: Uint8Array) => void> = [];23 return {24 push(m: Uint8Array) {25 const w = waiters.shift();26 if (w) w(m);27 else items.push(m);28 },29 pull(): Promise<Uint8Array> {30 const m = items.shift();31 return m ? Promise.resolve(m) : new Promise((r) => waiters.push(r));32 },33 };34 };35 const aToB = makeQueue();36 const bToA = makeQueue();37 return {38 a: { send: async (m: Uint8Array) => aToB.push(m), receive: () => bToA.pull() },39 b: { send: async (m: Uint8Array) => bToA.push(m), receive: () => aToB.pull() },40 };41}4243const deadTransport = {44 send: async () => undefined,45 receive: () => new Promise<Uint8Array>(() => undefined), 46};4748describe("PCH-02-009 — reject zero X25519 DH outputs", () => {49 it("NK initiator aborts against an all-zero responder static key", async () => {50 51 52 await expect(53 noiseNKInitiator(ZERO_POINT, deadTransport.send, deadTransport.receive),54 ).rejects.toThrow(/invalid shared key/);55 });5657 it("NK responder aborts on an all-zero msg-1 ephemeral", async () => {58 const ourStatic = generateKeyPair();59 60 const msg1 = new Uint8Array(48);61 await expect(62 noiseNKResponder(63 ourStatic.secretKey,64 ourStatic.publicKey,65 deadTransport.send,66 async () => msg1,67 ),68 ).rejects.toThrow(/invalid shared key/);69 });7071 it("XX responder aborts on an all-zero msg-1 ephemeral", async () => {72 const ourStatic = generateKeyPair();73 await expect(74 noiseXXResponder(75 ourStatic.secretKey,76 ourStatic.publicKey,77 deadTransport.send,78 async () => ZERO_POINT.slice(),79 ),80 ).rejects.toThrow(/invalid shared key/);81 });8283 it("honest NK handshake still completes and transports a frame (regression)", async () => {84 const responderStatic = generateKeyPair();85 const { a, b } = queuePair();8687 const [initiator, responder] = await Promise.all([88 noiseNKInitiator(responderStatic.publicKey, a.send, a.receive),89 noiseNKResponder(responderStatic.secretKey, responderStatic.publicKey, b.send, b.receive),90 ]);9192 const frame = new TextEncoder().encode("peach-nk-frame");93 expect(responder.decrypt(initiator.encrypt(frame))).toEqual(frame);94 expect(initiator.handshakeHash).toEqual(responder.handshakeHash);95 });9697 it("honest XX handshake still completes and transports a frame (regression)", async () => {98 const initiatorStatic = generateKeyPair();99 const responderStatic = generateKeyPair();100 const { a, b } = queuePair();101102 const [initiator, responder] = await Promise.all([103 noiseXXInitiator(initiatorStatic.secretKey, initiatorStatic.publicKey, a.send, a.receive),104 noiseXXResponder(responderStatic.secretKey, responderStatic.publicKey, b.send, b.receive),105 ]);106107 expect(initiator.remoteStaticPublicKey).toEqual(responderStatic.publicKey);108 expect(responder.remoteStaticPublicKey).toEqual(initiatorStatic.publicKey);109110 const frame = new TextEncoder().encode("peach-xx-frame");111 expect(responder.decrypt(initiator.encrypt(frame))).toEqual(frame);112 });113});