fix comware legacy ssh handshake (#1045)

This commit is contained in:
陈大猫
2026-05-22 00:13:59 +08:00
committed by GitHub
parent f4aa6ddb46
commit 308fb45985
2 changed files with 128 additions and 0 deletions

View File

@@ -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 },
);
});

View File

@@ -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() {