/** * User-selectable SSH algorithm lists for the host-level "advanced * algorithm overrides" UI. These lists must remain a subset of the * algorithms ssh2 actually supports (see `ssh2/lib/protocol/constants.js`); * passing an algorithm outside that set causes ssh2 to throw * "Unsupported algorithm" before the SSH handshake even starts. * * Order in each array is the suggested display / default-priority order * (modern + secure first). When the user picks a subset, that subset * fully replaces the negotiated list for the category. */ export type SSHAlgorithmCategory = | "kex" | "cipher" | "hmac" | "serverHostKey" | "compress"; // IMPORTANT: every algorithm in these lists must also appear in ssh2's // `SUPPORTED_*` constant (see `node_modules/ssh2/lib/protocol/constants.js`). // ssh2 throws `Unsupported algorithm` synchronously from `Client.connect()` // when it sees an algorithm outside its supported set, so exposing a dead // choice in the UI would make a host unreachable the moment the user // saved it. // // In particular, OpenSSL 3 disabled `blowfish`, `cast128`, and the // `arcfour` family — ssh2's `canUseCipher` filter then drops them from // `SUPPORTED_CIPHER` at startup. They are intentionally absent below. // `sshAlgorithmList.test.ts` enforces the subset invariant. export const SUPPORTED_KEX_ALGORITHMS: readonly string[] = [ "curve25519-sha256", "curve25519-sha256@libssh.org", "ecdh-sha2-nistp256", "ecdh-sha2-nistp384", "ecdh-sha2-nistp521", "diffie-hellman-group-exchange-sha256", "diffie-hellman-group14-sha256", "diffie-hellman-group15-sha512", "diffie-hellman-group16-sha512", "diffie-hellman-group17-sha512", "diffie-hellman-group18-sha512", "diffie-hellman-group-exchange-sha1", "diffie-hellman-group14-sha1", "diffie-hellman-group1-sha1", ]; export const SUPPORTED_CIPHER_ALGORITHMS: readonly string[] = [ "chacha20-poly1305@openssh.com", "aes128-gcm@openssh.com", "aes256-gcm@openssh.com", "aes128-gcm", "aes256-gcm", "aes128-ctr", "aes192-ctr", "aes256-ctr", "aes128-cbc", "aes192-cbc", "aes256-cbc", "3des-cbc", ]; export const SUPPORTED_HMAC_ALGORITHMS: readonly string[] = [ "hmac-sha2-256-etm@openssh.com", "hmac-sha2-512-etm@openssh.com", "hmac-sha1-etm@openssh.com", "hmac-sha2-256", "hmac-sha2-512", "hmac-sha1", "hmac-sha2-256-96", "hmac-sha2-512-96", "hmac-sha1-96", "hmac-md5", "hmac-md5-96", "hmac-ripemd160", ]; export const SUPPORTED_SERVER_HOST_KEY_ALGORITHMS: readonly string[] = [ "ssh-ed25519", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384", "ecdsa-sha2-nistp521", "rsa-sha2-512", "rsa-sha2-256", "ssh-rsa", "ssh-dss", ]; export const SUPPORTED_COMPRESS_ALGORITHMS: readonly string[] = [ "none", "zlib@openssh.com", "zlib", ]; export const SUPPORTED_ALGORITHMS_BY_CATEGORY: Readonly> = { kex: SUPPORTED_KEX_ALGORITHMS, cipher: SUPPORTED_CIPHER_ALGORITHMS, hmac: SUPPORTED_HMAC_ALGORITHMS, serverHostKey: SUPPORTED_SERVER_HOST_KEY_ALGORITHMS, compress: SUPPORTED_COMPRESS_ALGORITHMS, }; export const SSH_ALGORITHM_CATEGORIES: readonly SSHAlgorithmCategory[] = [ "kex", "cipher", "hmac", "serverHostKey", "compress", ]; // Mirror of what `electron/bridges/sshAlgorithms.cjs#buildAlgorithms(false)` // actually emits in non-legacy mode. Used by the UI to seed a category's // first customization with the *current effective default* rather than the // full SUPPORTED list — otherwise unchecking a single modern algorithm // would inadvertently introduce CBC / arcfour / MD5 into the offer list. // // `hmac` and `serverHostKey` here mirror ssh2's `DEFAULT_MAC` and // `DEFAULT_SERVER_HOST_KEY` because non-legacy mode leaves both fields // unset, letting ssh2 fall back to those defaults. Keep them in sync if // the ssh2 dependency bumps. const MODERN_DEFAULT_ALGORITHMS: Readonly> = { kex: [ "curve25519-sha256", "curve25519-sha256@libssh.org", "ecdh-sha2-nistp256", "ecdh-sha2-nistp384", "ecdh-sha2-nistp521", "diffie-hellman-group14-sha256", "diffie-hellman-group16-sha512", "diffie-hellman-group18-sha512", "diffie-hellman-group-exchange-sha256", ], cipher: [ "aes128-gcm@openssh.com", "aes256-gcm@openssh.com", "aes128-ctr", "aes192-ctr", "aes256-ctr", "chacha20-poly1305@openssh.com", ], hmac: [ "hmac-sha2-256-etm@openssh.com", "hmac-sha2-512-etm@openssh.com", "hmac-sha1-etm@openssh.com", "hmac-sha2-256", "hmac-sha2-512", "hmac-sha1", ], serverHostKey: [ "ssh-ed25519", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384", "ecdsa-sha2-nistp521", "rsa-sha2-512", "rsa-sha2-256", "ssh-rsa", ], compress: ["none"], }; // Additions appended when legacy mode is on — matches // `applyLegacyAlgorithms` + `applyLegacyHmacAlgorithms` in // `electron/bridges/sshAlgorithms.cjs`. `hmac-md5` is conditional on // runtime support there but we seed it unconditionally; ssh2 will surface // "Unsupported algorithm" at connect time on FIPS Node builds, which is // acceptable as a UI hint that the user picked something the runtime // rejects. const LEGACY_DEFAULT_ADDITIONS: Partial> = { kex: [ "diffie-hellman-group14-sha1", "diffie-hellman-group1-sha1", "diffie-hellman-group-exchange-sha1", ], cipher: ["aes128-cbc", "aes256-cbc", "3des-cbc"], hmac: ["hmac-md5"], serverHostKey: ["ssh-dss"], }; /** * Return the algorithm list that NetCatty would actually offer for each * category at connect time given the current legacy toggle. The advanced * override UI seeds an untouched category from this list so a partial * customization can't accidentally re-enable algorithms the connection * wouldn't otherwise advertise. */ export function effectiveDefaultAlgorithms( legacyEnabled: boolean, ): Record { const result: Record = { kex: [...MODERN_DEFAULT_ALGORITHMS.kex], cipher: [...MODERN_DEFAULT_ALGORITHMS.cipher], hmac: [...MODERN_DEFAULT_ALGORITHMS.hmac], serverHostKey: [...MODERN_DEFAULT_ALGORITHMS.serverHostKey], compress: [...MODERN_DEFAULT_ALGORITHMS.compress], }; if (legacyEnabled) { for (const category of SSH_ALGORITHM_CATEGORIES) { const additions = LEGACY_DEFAULT_ADDITIONS[category]; if (!additions) continue; for (const algo of additions) { if (!result[category].includes(algo)) result[category].push(algo); } } } return result; }