fix comware legacy ssh handshake (#1045)
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
const test = require("node:test");
|
||||
const assert = require("node:assert/strict");
|
||||
const crypto = require("node:crypto");
|
||||
const { KexInit, HANDLERS: KEX_HANDLERS } = require("../../node_modules/ssh2/lib/protocol/kex.js");
|
||||
const { COMPAT, COMPAT_CHECKS, MESSAGE } = require("../../node_modules/ssh2/lib/protocol/constants.js");
|
||||
|
||||
const sshBridge = require("./sshBridge.cjs");
|
||||
const sftpBridge = require("./sftpBridge.cjs");
|
||||
@@ -45,6 +47,81 @@ function withAlgorithmRuntime({ unsupportedGroups = new Set(), hashes = ["sha1",
|
||||
}
|
||||
}
|
||||
|
||||
function kexPayloadFrom(init) {
|
||||
const payload = Buffer.alloc(1 + 16 + init.totalSize + 1 + 4);
|
||||
payload[0] = MESSAGE.KEXINIT;
|
||||
init.copyAllTo(payload, 17);
|
||||
return payload;
|
||||
}
|
||||
|
||||
function buildKexInit(algorithms) {
|
||||
return new KexInit({
|
||||
kex: algorithms.kex,
|
||||
serverHostKey: algorithms.serverHostKey,
|
||||
cs: {
|
||||
cipher: algorithms.cipher,
|
||||
mac: algorithms.hmac,
|
||||
compress: algorithms.compress,
|
||||
lang: [],
|
||||
},
|
||||
sc: {
|
||||
cipher: algorithms.cipher,
|
||||
mac: algorithms.hmac,
|
||||
compress: algorithms.compress,
|
||||
lang: [],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function readLegacyGexRequestBits(compatFlags) {
|
||||
const algorithms = sshBridge.buildAlgorithms(true);
|
||||
const writtenPackets = [];
|
||||
const protocol = {
|
||||
_server: false,
|
||||
_compatFlags: compatFlags,
|
||||
_offer: buildKexInit(algorithms),
|
||||
_debug: undefined,
|
||||
_strictMode: undefined,
|
||||
_kex: undefined,
|
||||
_kexinit: Buffer.from("local-kexinit"),
|
||||
_identRaw: Buffer.from("SSH-2.0-netcatty-test"),
|
||||
_remoteIdentRaw: Buffer.from("SSH-2.0-Comware-5.20"),
|
||||
_packetRW: {
|
||||
write: {
|
||||
allocStartKEX: 0,
|
||||
alloc(size) {
|
||||
return Buffer.alloc(size);
|
||||
},
|
||||
finalize(packet) {
|
||||
return packet;
|
||||
},
|
||||
},
|
||||
},
|
||||
_cipher: {
|
||||
encrypt(packet) {
|
||||
writtenPackets.push(Buffer.from(packet));
|
||||
},
|
||||
},
|
||||
};
|
||||
const remote = buildKexInit({
|
||||
kex: ["diffie-hellman-group-exchange-sha1"],
|
||||
serverHostKey: ["ecdsa-sha2-nistp256", "ssh-rsa"],
|
||||
cipher: ["aes128-ctr"],
|
||||
hmac: ["hmac-sha2-256"],
|
||||
compress: ["none"],
|
||||
});
|
||||
|
||||
KEX_HANDLERS[MESSAGE.KEXINIT](protocol, kexPayloadFrom(remote));
|
||||
|
||||
const request = writtenPackets.find((packet) => packet[0] === MESSAGE.KEXDH_GEX_REQUEST);
|
||||
assert.ok(request, "expected a DH group-exchange request packet");
|
||||
return {
|
||||
min: request.readUInt32BE(1),
|
||||
preferred: request.readUInt32BE(5),
|
||||
max: request.readUInt32BE(9),
|
||||
};
|
||||
}
|
||||
|
||||
for (const [label, buildAlgorithms] of [
|
||||
["SSH", sshBridge.buildAlgorithms],
|
||||
["SFTP", sftpBridge.buildSftpAlgorithms],
|
||||
@@ -123,3 +200,17 @@ test("legacy HMAC algorithms skip MD5 when the runtime disables it", () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test("Comware legacy group-exchange requests OpenSSH 6.4-sized DH groups", () => {
|
||||
const comwareCompatRule = COMPAT_CHECKS.find(([pattern, flags]) => (
|
||||
pattern instanceof RegExp
|
||||
&& pattern.test("Comware-5.20")
|
||||
&& (flags & COMPAT.COMWARE_DHGEX_1024)
|
||||
));
|
||||
|
||||
assert.ok(comwareCompatRule, "Comware servers should opt into the old DH group-exchange request size");
|
||||
assert.deepEqual(
|
||||
readLegacyGexRequestBits(COMPAT.COMWARE_DHGEX_1024),
|
||||
{ min: 1024, preferred: 1024, max: 8192 },
|
||||
);
|
||||
});
|
||||
|
||||
@@ -738,3 +738,40 @@ index 9f33c02..9751164 100644
|
||||
}
|
||||
if (names !== undefined) {
|
||||
sftp._debug && sftp._debug(
|
||||
diff --git a/node_modules/ssh2/lib/protocol/constants.js b/node_modules/ssh2/lib/protocol/constants.js
|
||||
index ad77592..4b3f71a 100644
|
||||
--- a/node_modules/ssh2/lib/protocol/constants.js
|
||||
+++ b/node_modules/ssh2/lib/protocol/constants.js
|
||||
@@ -160,4 +160,5 @@ const COMPAT = {
|
||||
DYN_RPORT_BUG: 1 << 2,
|
||||
BUG_DHGEX_LARGE: 1 << 3,
|
||||
IMPLY_RSA_SHA2_SIGALGS: 1 << 4,
|
||||
+ COMWARE_DHGEX_1024: 1 << 5,
|
||||
};
|
||||
@@ -330,6 +331,7 @@ module.exports = {
|
||||
COMPAT_CHECKS: [
|
||||
[ 'Cisco-1.25', COMPAT.BAD_DHGEX ],
|
||||
[ /^Cisco-1[.]/, COMPAT.BUG_DHGEX_LARGE ],
|
||||
+ [ /^Comware-/, COMPAT.COMWARE_DHGEX_1024 ],
|
||||
[ /^[0-9.]+$/, COMPAT.OLD_EXIT ], // old SSH.com implementations
|
||||
[ /^OpenSSH_5[.][0-9]+/, COMPAT.DYN_RPORT_BUG ],
|
||||
[ /^OpenSSH_7[.]4/, COMPAT.IMPLY_RSA_SHA2_SIGALGS ],
|
||||
diff --git a/node_modules/ssh2/lib/protocol/kex.js b/node_modules/ssh2/lib/protocol/kex.js
|
||||
index 811e631..4b5f792 100644
|
||||
--- a/node_modules/ssh2/lib/protocol/kex.js
|
||||
+++ b/node_modules/ssh2/lib/protocol/kex.js
|
||||
@@ -1377,8 +1377,13 @@ const createKeyExchange = (() => {
|
||||
this._generator = null;
|
||||
this._minBits = GEX_MIN_BITS;
|
||||
this._prefBits = dhEstimate(this.negotiated);
|
||||
- if (this._protocol._compatFlags & COMPAT.BUG_DHGEX_LARGE)
|
||||
+ if (hashName === 'sha1'
|
||||
+ && (this._protocol._compatFlags & COMPAT.COMWARE_DHGEX_1024)) {
|
||||
+ this._minBits = 1024;
|
||||
+ this._prefBits = 1024;
|
||||
+ } else if (this._protocol._compatFlags & COMPAT.BUG_DHGEX_LARGE) {
|
||||
this._prefBits = Math.min(this._prefBits, 4096);
|
||||
+ }
|
||||
this._maxBits = GEX_MAX_BITS;
|
||||
}
|
||||
start() {
|
||||
|
||||
Reference in New Issue
Block a user