noise-kk.test.ts

3.5 KB · raw

1import { describe, expect, it } from "vitest";2import { generateKeyPair, generateKeyPairFromSeed } from "@stablelib/x25519";3import {4  bytesToHex,5  noiseKKInitiator,6  noiseKKResponder,7} from "./noise-js";89function channels() {10  const initiatorToResponder: Uint8Array[] = [];11  const responderToInitiator: Uint8Array[] = [];12  const drain = async (queue: Uint8Array[]) => {13    while (queue.length === 0) await new Promise<void>((resolve) => setTimeout(resolve, 0));14    return queue.shift()!;15  };16  return {17    initiatorSend: async (frame: Uint8Array) => { initiatorToResponder.push(frame); },18    initiatorReceive: () => drain(responderToInitiator),19    responderSend: async (frame: Uint8Array) => { responderToInitiator.push(frame); },20    responderReceive: () => drain(initiatorToResponder),21  };22}2324describe("Noise KK reconnect", () => {25  it("mutually authenticates known static keys and carries transport frames", async () => {26    const initiator = generateKeyPair();27    const responder = generateKeyPair();28    const wire = channels();29    const [a, b] = await Promise.all([30      noiseKKInitiator(31        initiator.secretKey,32        initiator.publicKey,33        responder.publicKey,34        wire.initiatorSend,35        wire.initiatorReceive,36      ),37      noiseKKResponder(38        responder.secretKey,39        responder.publicKey,40        initiator.publicKey,41        wire.responderSend,42        wire.responderReceive,43      ),44    ]);4546    expect(bytesToHex(a.handshakeHash)).toBe(bytesToHex(b.handshakeHash));47    expect(new TextDecoder().decode(b.decrypt(a.encrypt(new TextEncoder().encode("one"))))).toBe("one");48    expect(new TextDecoder().decode(a.decrypt(b.encrypt(new TextEncoder().encode("two"))))).toBe("two");49  });5051  it("pins the cross-platform deterministic handshake hash", async () => {52    const initiator = generateKeyPairFromSeed(new Uint8Array(32).fill(0x01));53    const responder = generateKeyPairFromSeed(new Uint8Array(32).fill(0x02));54    const initiatorEphemeral = generateKeyPairFromSeed(new Uint8Array(32).fill(0x03));55    const responderEphemeral = generateKeyPairFromSeed(new Uint8Array(32).fill(0x04));56    const wire = channels();57    const [a, b] = await Promise.all([58      noiseKKInitiator(59        initiator.secretKey,60        initiator.publicKey,61        responder.publicKey,62        wire.initiatorSend,63        wire.initiatorReceive,64        initiatorEphemeral,65      ),66      noiseKKResponder(67        responder.secretKey,68        responder.publicKey,69        initiator.publicKey,70        wire.responderSend,71        wire.responderReceive,72        responderEphemeral,73      ),74    ]);7576    expect(bytesToHex(a.handshakeHash)).toBe(77      "e92392c530684df3eed06a76bd18da5c7a1887e88f5fdee758f9870dadd28182",78    );79    expect(bytesToHex(b.handshakeHash)).toBe(bytesToHex(a.handshakeHash));80  });8182  it("hard-fails a mismatched expected initiator key without downgrade", async () => {83    const initiator = generateKeyPair();84    const attacker = generateKeyPair();85    const responder = generateKeyPair();86    const wire = channels();8788    const initiatorHandshake = noiseKKInitiator(89      initiator.secretKey,90      initiator.publicKey,91      responder.publicKey,92      wire.initiatorSend,93      wire.initiatorReceive,94    );95    const responderHandshake = noiseKKResponder(96      responder.secretKey,97      responder.publicKey,98      attacker.publicKey,99      wire.responderSend,100      wire.responderReceive,101    );102103    await expect(Promise.all([initiatorHandshake, responderHandshake])).rejects.toThrow(104      /Noise KK: msg1 auth failed/,105    );106  });107});