diff --git a/lib/protocol/Protocol.js b/lib/protocol/Protocol.js index 85e1249a..73024881 100644 --- a/lib/protocol/Protocol.js +++ b/lib/protocol/Protocol.js @@ -218,11 +218,18 @@ class Protocol { if (typeof offer !== 'object' || offer === null) { offer = (this._server ? DEFAULT_KEXINIT_SERVER : DEFAULT_KEXINIT_CLIENT); } else if (offer.constructor !== KexInit) { - if (!this._server) - offer.kex = offer.kex.concat(['ext-info-c']); + if (this._server) { + offer.kex = offer.kex.concat(['kex-strict-s-v00@openssh.com']); + } else { + offer.kex = offer.kex.concat([ + 'ext-info-c', + 'kex-strict-c-v00@openssh.com', + ]); + } offer = new KexInit(offer); } this._kex = undefined; + this._strictMode = undefined; this._kexinit = undefined; this._offer = offer; this._cipher = new NullCipher(0, this._onWrite); diff --git a/lib/protocol/kex.js b/lib/protocol/kex.js index 1724ff1e..811e631b 100644 --- a/lib/protocol/kex.js +++ b/lib/protocol/kex.js @@ -232,13 +232,37 @@ function handleKexInit(self, payload) { clientList = localKex; remoteExtInfoEnabled = (serverList.indexOf('ext-info-s') !== -1); } + if (self._strictMode === undefined) { + if (self._server) { + self._strictMode = + (clientList.indexOf('kex-strict-c-v00@openssh.com') !== -1); + } else { + self._strictMode = + (serverList.indexOf('kex-strict-s-v00@openssh.com') !== -1); + } + // Note: We check for seqno of 1 instead of 0 since we increment before + // calling the packet handler + if (self._strictMode) { + debug && debug('Handshake: strict KEX mode enabled'); + if (self._decipher.inSeqno !== 1) { + if (debug) + debug('Handshake: KEXINIT not first packet in strict KEX mode'); + return doFatalError( + self, + 'Handshake failed: KEXINIT not first packet in strict KEX mode', + 'handshake', + DISCONNECT_REASON.KEY_EXCHANGE_FAILED + ); + } + } + } // Check for agreeable key exchange algorithm for (i = 0; i < clientList.length && serverList.indexOf(clientList[i]) === -1; ++i); if (i === clientList.length) { // No suitable match found! - debug && debug('Handshake: No matching key exchange algorithm'); + debug && debug('Handshake: no matching key exchange algorithm'); return doFatalError( self, 'Handshake failed: no matching key exchange algorithm', @@ -1236,6 +1260,8 @@ const createKeyExchange = (() => { 'Inbound: NEWKEYS' ); this._receivedNEWKEYS = true; + if (this._protocol._strictMode) + this._protocol._decipher.inSeqno = 0; ++this._step; return this.finish(!this._protocol._server && !this._hostVerified); @@ -1756,11 +1782,20 @@ function onKEXPayload(state, payload) { payload = this._packetRW.read.read(payload); const type = payload[0]; + + if (!this._strictMode) { + switch (type) { + case MESSAGE.IGNORE: + case MESSAGE.UNIMPLEMENTED: + case MESSAGE.DEBUG: + if (!MESSAGE_HANDLERS) + MESSAGE_HANDLERS = require('./handlers.js'); + return MESSAGE_HANDLERS[type](this, payload); + } + } + switch (type) { case MESSAGE.DISCONNECT: - case MESSAGE.IGNORE: - case MESSAGE.UNIMPLEMENTED: - case MESSAGE.DEBUG: if (!MESSAGE_HANDLERS) MESSAGE_HANDLERS = require('./handlers.js'); return MESSAGE_HANDLERS[type](this, payload); @@ -1776,6 +1811,8 @@ function onKEXPayload(state, payload) { state.firstPacket = false; return handleKexInit(this, payload); default: + // Ensure packet is either an algorithm negotiation or KEX + // algorithm-specific packet if (type < 20 || type > 49) { return doFatalError( this, @@ -1824,6 +1861,8 @@ function trySendNEWKEYS(kex) { kex._protocol._packetRW.write.finalize(packet, true) ); kex._sentNEWKEYS = true; + if (kex._protocol._strictMode) + kex._protocol._cipher.outSeqno = 0; } } @@ -1832,7 +1871,7 @@ module.exports = { kexinit, onKEXPayload, DEFAULT_KEXINIT_CLIENT: new KexInit({ - kex: DEFAULT_KEX.concat(['ext-info-c']), + kex: DEFAULT_KEX.concat(['ext-info-c', 'kex-strict-c-v00@openssh.com']), serverHostKey: DEFAULT_SERVER_HOST_KEY, cs: { cipher: DEFAULT_CIPHER, @@ -1848,7 +1887,7 @@ module.exports = { }, }), DEFAULT_KEXINIT_SERVER: new KexInit({ - kex: DEFAULT_KEX, + kex: DEFAULT_KEX.concat(['kex-strict-s-v00@openssh.com']), serverHostKey: DEFAULT_SERVER_HOST_KEY, cs: { cipher: DEFAULT_CIPHER, diff --git a/lib/server.js b/lib/server.js index c10c42ed..306d6584 100644 --- a/lib/server.js +++ b/lib/server.js @@ -294,7 +294,11 @@ class Server extends EventEmitter { } const algorithms = { - kex: generateAlgorithmList(cfgAlgos.kex, DEFAULT_KEX, SUPPORTED_KEX), + kex: generateAlgorithmList( + cfgAlgos.kex, + DEFAULT_KEX, + SUPPORTED_KEX + ).concat(['kex-strict-s-v00@openssh.com']), serverHostKey: hostKeyAlgoOrder, cs: { cipher: generateAlgorithmList(