import test from "node:test"; import assert from "node:assert/strict"; import { createRequire } from "node:module"; import { effectiveDefaultAlgorithms, SUPPORTED_ALGORITHMS_BY_CATEGORY, } from "./sshAlgorithmList.ts"; const requireSsh2 = createRequire(import.meta.url); // Anchor the UI editor's supported lists to what ssh2 will actually // accept at connect time. If ssh2 drops a cipher / KEX / MAC at any // point (OpenSSL 3 already removed blowfish / arcfour / cast128, for // example) the editor must not offer it — picking it would throw // "Unsupported algorithm" synchronously before negotiation. const ssh2Constants = requireSsh2("ssh2/lib/protocol/constants.js"); const SSH2_SUPPORTED_BY_CATEGORY: Record = { kex: ssh2Constants.SUPPORTED_KEX, cipher: ssh2Constants.SUPPORTED_CIPHER, hmac: ssh2Constants.SUPPORTED_MAC, serverHostKey: ssh2Constants.SUPPORTED_SERVER_HOST_KEY, compress: ssh2Constants.SUPPORTED_COMPRESSION, }; test("effectiveDefaultAlgorithms (modern) never seeds legacy SHA-1 KEX", () => { const result = effectiveDefaultAlgorithms(false); assert.ok(!result.kex.includes("diffie-hellman-group1-sha1")); assert.ok(!result.kex.includes("diffie-hellman-group14-sha1")); assert.ok(!result.kex.includes("diffie-hellman-group-exchange-sha1")); // Modern KEX still present. assert.ok(result.kex.includes("curve25519-sha256")); assert.ok(result.kex.includes("diffie-hellman-group14-sha256")); }); test("effectiveDefaultAlgorithms (modern) never seeds CBC / arcfour / MD5", () => { const result = effectiveDefaultAlgorithms(false); for (const algo of result.cipher) { assert.ok(!algo.endsWith("-cbc"), `${algo} is a CBC cipher and should not be in modern defaults`); assert.ok(!algo.startsWith("arcfour"), `${algo} (arcfour) should not be in modern defaults`); assert.ok(algo !== "3des-cbc", "3des-cbc is legacy"); } for (const algo of result.hmac) { assert.ok(!algo.includes("md5"), `${algo} should not be in modern defaults`); } }); test("effectiveDefaultAlgorithms (modern) includes chacha20-poly1305", () => { const result = effectiveDefaultAlgorithms(false); assert.ok(result.cipher.includes("chacha20-poly1305@openssh.com")); }); test("effectiveDefaultAlgorithms (legacy) appends sha1 KEX, CBC, and ssh-dss", () => { const modern = effectiveDefaultAlgorithms(false); const legacy = effectiveDefaultAlgorithms(true); // Every modern algorithm is still present. for (const category of Object.keys(modern) as (keyof typeof modern)[]) { for (const algo of modern[category]) { assert.ok(legacy[category].includes(algo), `${algo} missing from legacy ${category}`); } } assert.ok(legacy.kex.includes("diffie-hellman-group14-sha1")); assert.ok(legacy.kex.includes("diffie-hellman-group1-sha1")); assert.ok(legacy.cipher.includes("aes128-cbc")); assert.ok(legacy.cipher.includes("3des-cbc")); assert.ok(legacy.serverHostKey.includes("ssh-dss")); }); test("SUPPORTED_ALGORITHMS_BY_CATEGORY only lists algorithms ssh2 will actually accept", () => { for (const category of Object.keys(SUPPORTED_ALGORITHMS_BY_CATEGORY) as (keyof typeof SUPPORTED_ALGORITHMS_BY_CATEGORY)[]) { const ssh2Supported = SSH2_SUPPORTED_BY_CATEGORY[category]; assert.ok(ssh2Supported, `unexpected category ${category}`); for (const algo of SUPPORTED_ALGORITHMS_BY_CATEGORY[category]) { assert.ok( ssh2Supported.includes(algo), `${algo} (${category}) is in the UI list but ssh2 would reject it`, ); } } }); test("effectiveDefaultAlgorithms output is a subset of SUPPORTED_ALGORITHMS_BY_CATEGORY", () => { for (const enabled of [false, true]) { const result = effectiveDefaultAlgorithms(enabled); for (const category of Object.keys(result) as (keyof typeof result)[]) { const supported = SUPPORTED_ALGORITHMS_BY_CATEGORY[category]; for (const algo of result[category]) { assert.ok(supported.includes(algo), `${algo} (${category}) not in supported list`); } } } });