208 lines
6.5 KiB
TypeScript
208 lines
6.5 KiB
TypeScript
/**
|
|
* 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<Record<SSHAlgorithmCategory, readonly string[]>> = {
|
|
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<Record<SSHAlgorithmCategory, readonly string[]>> = {
|
|
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<Record<SSHAlgorithmCategory, readonly string[]>> = {
|
|
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<SSHAlgorithmCategory, readonly string[]> {
|
|
const result: Record<SSHAlgorithmCategory, string[]> = {
|
|
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;
|
|
}
|