noise.reject-zero-dh.test.ts

3.6 KB · raw

1// PCH-02-009 regression tests — every Noise XX/NK DH must reject an all-zero2// shared secret (low-order / identity peer point) instead of feeding it into3// the key schedule.45import { describe, expect, it } from "vitest";6import { generateKeyPair } from "@stablelib/x25519";7import { noiseNKInitiator, noiseNKResponder } from "./noise-js";8import { noiseXXInitiator, noiseXXResponder } from "./noise-xx";910const ZERO_POINT = new Uint8Array(32);1112function queuePair() {13  const makeQueue = () => {14    const items: Uint8Array[] = [];15    const waiters: Array<(m: Uint8Array) => void> = [];16    return {17      push(m: Uint8Array) {18        const w = waiters.shift();19        if (w) w(m);20        else items.push(m);21      },22      pull(): Promise<Uint8Array> {23        const m = items.shift();24        return m ? Promise.resolve(m) : new Promise((r) => waiters.push(r));25      },26    };27  };28  const aToB = makeQueue();29  const bToA = makeQueue();30  return {31    a: { send: async (m: Uint8Array) => aToB.push(m), receive: () => bToA.pull() },32    b: { send: async (m: Uint8Array) => bToA.push(m), receive: () => aToB.pull() },33  };34}3536const deadTransport = {37  send: async () => undefined,38  receive: () => new Promise<Uint8Array>(() => undefined),39};4041describe("PCH-02-009 — reject zero X25519 DH outputs", () => {42  it("NK initiator aborts against an all-zero responder static key", async () => {43    await expect(44      noiseNKInitiator(ZERO_POINT, deadTransport.send, deadTransport.receive),45    ).rejects.toThrow(/invalid shared key/);46  });4748  it("NK responder aborts on an all-zero msg-1 ephemeral", async () => {49    const ourStatic = generateKeyPair();50    const msg1 = new Uint8Array(48);51    await expect(52      noiseNKResponder(53        ourStatic.secretKey,54        ourStatic.publicKey,55        deadTransport.send,56        async () => msg1,57      ),58    ).rejects.toThrow(/invalid shared key/);59  });6061  it("XX responder aborts on an all-zero msg-1 ephemeral", async () => {62    const ourStatic = generateKeyPair();63    await expect(64      noiseXXResponder(65        ourStatic.secretKey,66        ourStatic.publicKey,67        deadTransport.send,68        async () => ZERO_POINT.slice(),69      ),70    ).rejects.toThrow(/invalid shared key/);71  });7273  it("honest NK handshake still completes and transports a frame (regression)", async () => {74    const responderStatic = generateKeyPair();75    const { a, b } = queuePair();7677    const [initiator, responder] = await Promise.all([78      noiseNKInitiator(responderStatic.publicKey, a.send, a.receive),79      noiseNKResponder(responderStatic.secretKey, responderStatic.publicKey, b.send, b.receive),80    ]);8182    const frame = new TextEncoder().encode("peach-nk-frame");83    expect(responder.decrypt(initiator.encrypt(frame))).toEqual(frame);84    expect(initiator.handshakeHash).toEqual(responder.handshakeHash);85  });8687  it("honest XX handshake still completes and transports a frame (regression)", async () => {88    const initiatorStatic = generateKeyPair();89    const responderStatic = generateKeyPair();90    const { a, b } = queuePair();9192    const [initiator, responder] = await Promise.all([93      noiseXXInitiator(initiatorStatic.secretKey, initiatorStatic.publicKey, a.send, a.receive),94      noiseXXResponder(responderStatic.secretKey, responderStatic.publicKey, b.send, b.receive),95    ]);9697    expect(initiator.remoteStaticPublicKey).toEqual(responderStatic.publicKey);98    expect(responder.remoteStaticPublicKey).toEqual(initiatorStatic.publicKey);99100    const frame = new TextEncoder().encode("peach-xx-frame");101    expect(responder.decrypt(initiator.encrypt(frame))).toEqual(frame);102  });103});