fix comware legacy ssh handshake (#1045)
This commit is contained in:
@@ -1,6 +1,8 @@
|
|||||||
const test = require("node:test");
|
const test = require("node:test");
|
||||||
const assert = require("node:assert/strict");
|
const assert = require("node:assert/strict");
|
||||||
const crypto = require("node:crypto");
|
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 sshBridge = require("./sshBridge.cjs");
|
||||||
const sftpBridge = require("./sftpBridge.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 [
|
for (const [label, buildAlgorithms] of [
|
||||||
["SSH", sshBridge.buildAlgorithms],
|
["SSH", sshBridge.buildAlgorithms],
|
||||||
["SFTP", sftpBridge.buildSftpAlgorithms],
|
["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) {
|
if (names !== undefined) {
|
||||||
sftp._debug && sftp._debug(
|
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