Files
bare-operating-system/packages/bare-os-openssh/vendor/bare-ssh2/lib/protocol/kex.js
T

1789 lines
56 KiB
JavaScript

'use strict'
const {
createDiffieHellman,
createDiffieHellmanGroup,
createECDH,
createHash,
createPublicKey,
diffieHellman,
generateKeyPairSync,
randomFillSync
} = require('crypto')
const { Ber } = require('asn1')
const {
COMPAT,
curve25519Supported,
DEFAULT_KEX,
DEFAULT_SERVER_HOST_KEY,
DEFAULT_CIPHER,
DEFAULT_MAC,
DEFAULT_COMPRESSION,
DISCONNECT_REASON,
MESSAGE
} = require('./constants.js')
const { CIPHER_INFO, createCipher, createDecipher, MAC_INFO } = require('./crypto.js')
const { parseDERKey } = require('./keyParser.js')
const {
bufferFill,
bufferParser,
convertSignature,
doFatalError,
FastBuffer,
sigSSHToASN1,
writeUInt32BE
} = require('./utils.js')
const { PacketReader, PacketWriter, ZlibPacketReader, ZlibPacketWriter } = require('./zlib.js')
let MESSAGE_HANDLERS
const GEX_MIN_BITS = 2048 // RFC 8270
const GEX_MAX_BITS = 8192 // RFC 8270
const EMPTY_BUFFER = Buffer.alloc(0)
// Client/Server
function kexinit(self) {
/*
byte SSH_MSG_KEXINIT
byte[16] cookie (random bytes)
name-list kex_algorithms
name-list server_host_key_algorithms
name-list encryption_algorithms_client_to_server
name-list encryption_algorithms_server_to_client
name-list mac_algorithms_client_to_server
name-list mac_algorithms_server_to_client
name-list compression_algorithms_client_to_server
name-list compression_algorithms_server_to_client
name-list languages_client_to_server
name-list languages_server_to_client
boolean first_kex_packet_follows
uint32 0 (reserved for future extension)
*/
let payload
if (self._compatFlags & COMPAT.BAD_DHGEX) {
const entry = self._offer.lists.kex
let kex = entry.array
let found = false
for (let i = 0; i < kex.length; ++i) {
if (kex[i].includes('group-exchange')) {
if (!found) {
found = true
// Copy array lazily
kex = kex.slice()
}
kex.splice(i--, 1)
}
}
if (found) {
let len = 1 + 16 + self._offer.totalSize + 1 + 4
const newKexBuf = Buffer.from(kex.join(','))
len -= entry.buffer.length - newKexBuf.length
const all = self._offer.lists.all
const rest = new Uint8Array(
all.buffer,
all.byteOffset + 4 + entry.buffer.length,
all.length - (4 + entry.buffer.length)
)
payload = Buffer.allocUnsafe(len)
writeUInt32BE(payload, newKexBuf.length, 17)
payload.set(newKexBuf, 17 + 4)
payload.set(rest, 17 + 4 + newKexBuf.length)
}
}
if (payload === undefined) {
payload = Buffer.allocUnsafe(1 + 16 + self._offer.totalSize + 1 + 4)
self._offer.copyAllTo(payload, 17)
}
self._debug && self._debug('Outbound: Sending KEXINIT')
payload[0] = MESSAGE.KEXINIT
randomFillSync(payload, 1, 16)
// Zero-fill first_kex_packet_follows and reserved bytes
bufferFill(payload, 0, payload.length - 5)
self._kexinit = payload
// Needed to correct the starting position in allocated "packets" when packets
// will be buffered due to active key exchange
self._packetRW.write.allocStart = 0
// TODO: only create single buffer and set _kexinit as slice of packet instead
{
const p = self._packetRW.write.allocStartKEX
const packet = self._packetRW.write.alloc(payload.length, true)
packet.set(payload, p)
self._cipher.encrypt(self._packetRW.write.finalize(packet, true))
}
}
function handleKexInit(self, payload) {
/*
byte SSH_MSG_KEXINIT
byte[16] cookie (random bytes)
name-list kex_algorithms
name-list server_host_key_algorithms
name-list encryption_algorithms_client_to_server
name-list encryption_algorithms_server_to_client
name-list mac_algorithms_client_to_server
name-list mac_algorithms_server_to_client
name-list compression_algorithms_client_to_server
name-list compression_algorithms_server_to_client
name-list languages_client_to_server
name-list languages_server_to_client
boolean first_kex_packet_follows
uint32 0 (reserved for future extension)
*/
const init = {
kex: undefined,
serverHostKey: undefined,
cs: {
cipher: undefined,
mac: undefined,
compress: undefined,
lang: undefined
},
sc: {
cipher: undefined,
mac: undefined,
compress: undefined,
lang: undefined
}
}
bufferParser.init(payload, 17)
if (
(init.kex = bufferParser.readList()) === undefined ||
(init.serverHostKey = bufferParser.readList()) === undefined ||
(init.cs.cipher = bufferParser.readList()) === undefined ||
(init.sc.cipher = bufferParser.readList()) === undefined ||
(init.cs.mac = bufferParser.readList()) === undefined ||
(init.sc.mac = bufferParser.readList()) === undefined ||
(init.cs.compress = bufferParser.readList()) === undefined ||
(init.sc.compress = bufferParser.readList()) === undefined ||
(init.cs.lang = bufferParser.readList()) === undefined ||
(init.sc.lang = bufferParser.readList()) === undefined
) {
bufferParser.clear()
return doFatalError(
self,
'Received malformed KEXINIT',
'handshake',
DISCONNECT_REASON.KEY_EXCHANGE_FAILED
)
}
const pos = bufferParser.pos()
const firstFollows = pos < payload.length && payload[pos] === 1
bufferParser.clear()
const local = self._offer
const remote = init
let localKex = local.lists.kex.array
if (self._compatFlags & COMPAT.BAD_DHGEX) {
let found = false
for (let i = 0; i < localKex.length; ++i) {
if (localKex[i].indexOf('group-exchange') !== -1) {
if (!found) {
found = true
// Copy array lazily
localKex = localKex.slice()
}
localKex.splice(i--, 1)
}
}
}
let clientList
let serverList
let i
const debug = self._debug
debug && debug('Inbound: Handshake in progress')
// Key exchange method =======================================================
debug && debug(`Handshake: (local) KEX method: ${localKex}`)
debug && debug(`Handshake: (remote) KEX method: ${remote.kex}`)
let remoteExtInfoEnabled
if (self._server) {
serverList = localKex
clientList = remote.kex
remoteExtInfoEnabled = clientList.indexOf('ext-info-c') !== -1
} else {
serverList = remote.kex
clientList = localKex
remoteExtInfoEnabled = serverList.indexOf('ext-info-s') !== -1
}
if (self._strictMode === undefined) {
if (self._server) {
self._strictMode = clientList.indexOf('[email protected]') !== -1
} else {
self._strictMode = serverList.indexOf('[email protected]') !== -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')
return doFatalError(
self,
'Handshake failed: no matching key exchange algorithm',
'handshake',
DISCONNECT_REASON.KEY_EXCHANGE_FAILED
)
}
init.kex = clientList[i]
debug && debug(`Handshake: KEX algorithm: ${clientList[i]}`)
if (firstFollows && (!remote.kex.length || clientList[i] !== remote.kex[0])) {
// Ignore next inbound packet, it was a wrong first guess at KEX algorithm
self._skipNextInboundPacket = true
}
// Server host key format ====================================================
const localSrvHostKey = local.lists.serverHostKey.array
debug && debug(`Handshake: (local) Host key format: ${localSrvHostKey}`)
debug && debug(`Handshake: (remote) Host key format: ${remote.serverHostKey}`)
if (self._server) {
serverList = localSrvHostKey
clientList = remote.serverHostKey
} else {
serverList = remote.serverHostKey
clientList = localSrvHostKey
}
// Check for agreeable server host key format
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 host key format')
return doFatalError(
self,
'Handshake failed: no matching host key format',
'handshake',
DISCONNECT_REASON.KEY_EXCHANGE_FAILED
)
}
init.serverHostKey = clientList[i]
debug && debug(`Handshake: Host key format: ${clientList[i]}`)
// Client->Server cipher =====================================================
const localCSCipher = local.lists.cs.cipher.array
debug && debug(`Handshake: (local) C->S cipher: ${localCSCipher}`)
debug && debug(`Handshake: (remote) C->S cipher: ${remote.cs.cipher}`)
if (self._server) {
serverList = localCSCipher
clientList = remote.cs.cipher
} else {
serverList = remote.cs.cipher
clientList = localCSCipher
}
// Check for agreeable client->server cipher
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 C->S cipher')
return doFatalError(
self,
'Handshake failed: no matching C->S cipher',
'handshake',
DISCONNECT_REASON.KEY_EXCHANGE_FAILED
)
}
init.cs.cipher = clientList[i]
debug && debug(`Handshake: C->S Cipher: ${clientList[i]}`)
// Server->Client cipher =====================================================
const localSCCipher = local.lists.sc.cipher.array
debug && debug(`Handshake: (local) S->C cipher: ${localSCCipher}`)
debug && debug(`Handshake: (remote) S->C cipher: ${remote.sc.cipher}`)
if (self._server) {
serverList = localSCCipher
clientList = remote.sc.cipher
} else {
serverList = remote.sc.cipher
clientList = localSCCipher
}
// Check for agreeable server->client cipher
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 S->C cipher')
return doFatalError(
self,
'Handshake failed: no matching S->C cipher',
'handshake',
DISCONNECT_REASON.KEY_EXCHANGE_FAILED
)
}
init.sc.cipher = clientList[i]
debug && debug(`Handshake: S->C cipher: ${clientList[i]}`)
// Client->Server MAC ========================================================
const localCSMAC = local.lists.cs.mac.array
debug && debug(`Handshake: (local) C->S MAC: ${localCSMAC}`)
debug && debug(`Handshake: (remote) C->S MAC: ${remote.cs.mac}`)
if (CIPHER_INFO[init.cs.cipher].authLen > 0) {
init.cs.mac = ''
debug && debug('Handshake: C->S MAC: <implicit>')
} else {
if (self._server) {
serverList = localCSMAC
clientList = remote.cs.mac
} else {
serverList = remote.cs.mac
clientList = localCSMAC
}
// Check for agreeable client->server hmac 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 C->S MAC')
return doFatalError(
self,
'Handshake failed: no matching C->S MAC',
'handshake',
DISCONNECT_REASON.KEY_EXCHANGE_FAILED
)
}
init.cs.mac = clientList[i]
debug && debug(`Handshake: C->S MAC: ${clientList[i]}`)
}
// Server->Client MAC ========================================================
const localSCMAC = local.lists.sc.mac.array
debug && debug(`Handshake: (local) S->C MAC: ${localSCMAC}`)
debug && debug(`Handshake: (remote) S->C MAC: ${remote.sc.mac}`)
if (CIPHER_INFO[init.sc.cipher].authLen > 0) {
init.sc.mac = ''
debug && debug('Handshake: S->C MAC: <implicit>')
} else {
if (self._server) {
serverList = localSCMAC
clientList = remote.sc.mac
} else {
serverList = remote.sc.mac
clientList = localSCMAC
}
// Check for agreeable server->client hmac 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 S->C MAC')
return doFatalError(
self,
'Handshake failed: no matching S->C MAC',
'handshake',
DISCONNECT_REASON.KEY_EXCHANGE_FAILED
)
}
init.sc.mac = clientList[i]
debug && debug(`Handshake: S->C MAC: ${clientList[i]}`)
}
// Client->Server compression ================================================
const localCSCompress = local.lists.cs.compress.array
debug && debug(`Handshake: (local) C->S compression: ${localCSCompress}`)
debug && debug(`Handshake: (remote) C->S compression: ${remote.cs.compress}`)
if (self._server) {
serverList = localCSCompress
clientList = remote.cs.compress
} else {
serverList = remote.cs.compress
clientList = localCSCompress
}
// Check for agreeable client->server compression 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 C->S compression')
return doFatalError(
self,
'Handshake failed: no matching C->S compression',
'handshake',
DISCONNECT_REASON.KEY_EXCHANGE_FAILED
)
}
init.cs.compress = clientList[i]
debug && debug(`Handshake: C->S compression: ${clientList[i]}`)
// Server->Client compression ================================================
const localSCCompress = local.lists.sc.compress.array
debug && debug(`Handshake: (local) S->C compression: ${localSCCompress}`)
debug && debug(`Handshake: (remote) S->C compression: ${remote.sc.compress}`)
if (self._server) {
serverList = localSCCompress
clientList = remote.sc.compress
} else {
serverList = remote.sc.compress
clientList = localSCCompress
}
// Check for agreeable server->client compression 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 S->C compression')
return doFatalError(
self,
'Handshake failed: no matching S->C compression',
'handshake',
DISCONNECT_REASON.KEY_EXCHANGE_FAILED
)
}
init.sc.compress = clientList[i]
debug && debug(`Handshake: S->C compression: ${clientList[i]}`)
init.cs.lang = ''
init.sc.lang = ''
// XXX: hack -- find a better way to do this
if (self._kex) {
if (!self._kexinit) {
// We received a rekey request, but we haven't sent a KEXINIT in response
// yet
kexinit(self)
}
self._decipher._onPayload = onKEXPayload.bind(self, { firstPacket: false })
}
self._kex = createKeyExchange(init, self, payload)
self._kex.remoteExtInfoEnabled = remoteExtInfoEnabled
self._kex.start()
}
const createKeyExchange = (() => {
/** Avoid Buffer#copy across safer-buffer vs bare-node-buffer (throws "argument should be a Buffer"). */
function copyBytes(src, dst, dstOff, srcStart, srcEnd) {
for (let i = srcStart, j = dstOff; i < srcEnd; ++i, ++j) dst[j] = src[i]
}
function convertToMpint(buf) {
let idx = 0
let length = buf.length
while (buf[idx] === 0x00) {
++idx
--length
}
let newBuf
if (buf[idx] & 0x80) {
newBuf = Buffer.allocUnsafe(1 + length)
newBuf[0] = 0
copyBytes(buf, newBuf, 1, idx, idx + length)
buf = newBuf
} else if (length !== buf.length) {
newBuf = Buffer.allocUnsafe(length)
copyBytes(buf, newBuf, 0, idx, buf.length)
buf = newBuf
}
return buf
}
class KeyExchange {
constructor(negotiated, protocol, remoteKexinit) {
this._protocol = protocol
this.sessionID = protocol._kex ? protocol._kex.sessionID : undefined
this.negotiated = negotiated
this.remoteExtInfoEnabled = false
this._step = 1
this._public = null
this._dh = null
this._sentNEWKEYS = false
this._receivedNEWKEYS = false
this._finished = false
this._hostVerified = false
// Data needed for initializing cipher/decipher/etc.
this._kexinit = protocol._kexinit
this._remoteKexinit = remoteKexinit
this._identRaw = protocol._identRaw
this._remoteIdentRaw = protocol._remoteIdentRaw
this._hostKey = undefined
this._dhData = undefined
this._sig = undefined
}
finish(scOnly) {
if (this._finished) return false
this._finished = true
const isServer = this._protocol._server
const negotiated = this.negotiated
const pubKey = this.convertPublicKey(this._dhData)
let secret = this.computeSecret(this._dhData)
if (secret instanceof Error) {
secret.message = `Error while computing DH secret (${this.type}): ${secret.message}`
secret.level = 'handshake'
return doFatalError(this._protocol, secret, DISCONNECT_REASON.KEY_EXCHANGE_FAILED)
}
const hash = createHash(this.hashName)
// V_C
hashString(hash, isServer ? this._remoteIdentRaw : this._identRaw)
// "V_S"
hashString(hash, isServer ? this._identRaw : this._remoteIdentRaw)
// "I_C"
hashString(hash, isServer ? this._remoteKexinit : this._kexinit)
// "I_S"
hashString(hash, isServer ? this._kexinit : this._remoteKexinit)
// "K_S"
const serverPublicHostKey = isServer ? this._hostKey.getPublicSSH() : this._hostKey
hashString(hash, serverPublicHostKey)
if (this.type === 'groupex') {
// Group exchange-specific
const params = this.getDHParams()
const num = Buffer.allocUnsafe(4)
// min (uint32)
writeUInt32BE(num, this._minBits, 0)
hash.update(num)
// preferred (uint32)
writeUInt32BE(num, this._prefBits, 0)
hash.update(num)
// max (uint32)
writeUInt32BE(num, this._maxBits, 0)
hash.update(num)
// prime
hashString(hash, params.prime)
// generator
hashString(hash, params.generator)
}
// method-specific data sent by client
hashString(hash, isServer ? pubKey : this.getPublicKey())
// method-specific data sent by server
const serverPublicKey = isServer ? this.getPublicKey() : pubKey
hashString(hash, serverPublicKey)
// shared secret ("K")
hashString(hash, secret)
// "H"
const exchangeHash = hash.digest()
if (!isServer) {
bufferParser.init(this._sig, 0)
const sigType = bufferParser.readString(true)
if (!sigType) {
return doFatalError(
this._protocol,
'Malformed packet while reading signature',
'handshake',
DISCONNECT_REASON.KEY_EXCHANGE_FAILED
)
}
if (sigType !== negotiated.serverHostKey) {
return doFatalError(
this._protocol,
`Wrong signature type: ${sigType}, ` + `expected: ${negotiated.serverHostKey}`,
'handshake',
DISCONNECT_REASON.KEY_EXCHANGE_FAILED
)
}
// "s"
let sigValue = bufferParser.readString()
bufferParser.clear()
if (sigValue === undefined) {
return doFatalError(
this._protocol,
'Malformed packet while reading signature',
'handshake',
DISCONNECT_REASON.KEY_EXCHANGE_FAILED
)
}
if (!(sigValue = sigSSHToASN1(sigValue, sigType))) {
return doFatalError(
this._protocol,
'Malformed signature',
'handshake',
DISCONNECT_REASON.KEY_EXCHANGE_FAILED
)
}
let parsedHostKey
{
bufferParser.init(this._hostKey, 0)
const name = bufferParser.readString(true)
const hostKey = this._hostKey.slice(bufferParser.pos())
bufferParser.clear()
parsedHostKey = parseDERKey(hostKey, name)
if (parsedHostKey instanceof Error) {
parsedHostKey.level = 'handshake'
return doFatalError(
this._protocol,
parsedHostKey,
DISCONNECT_REASON.KEY_EXCHANGE_FAILED
)
}
}
let hashAlgo
// Check if we need to override the default hash algorithm
switch (this.negotiated.serverHostKey) {
case 'rsa-sha2-256':
hashAlgo = 'sha256'
break
case 'rsa-sha2-512':
hashAlgo = 'sha512'
break
}
this._protocol._debug && this._protocol._debug('Verifying signature ...')
const verified = parsedHostKey.verify(exchangeHash, sigValue, hashAlgo)
if (verified !== true) {
if (verified instanceof Error) {
this._protocol._debug &&
this._protocol._debug(`Signature verification failed: ${verified.stack}`)
} else {
this._protocol._debug && this._protocol._debug('Signature verification failed')
}
return doFatalError(
this._protocol,
'Handshake failed: signature verification failed',
'handshake',
DISCONNECT_REASON.KEY_EXCHANGE_FAILED
)
}
this._protocol._debug && this._protocol._debug('Verified signature')
} else {
// Server
let hashAlgo
// Check if we need to override the default hash algorithm
switch (this.negotiated.serverHostKey) {
case 'rsa-sha2-256':
hashAlgo = 'sha256'
break
case 'rsa-sha2-512':
hashAlgo = 'sha512'
break
}
this._protocol._debug && this._protocol._debug('Generating signature ...')
let signature = this._hostKey.sign(exchangeHash, hashAlgo)
if (signature instanceof Error) {
return doFatalError(
this._protocol,
'Handshake failed: signature generation failed for ' +
`${this._hostKey.type} host key: ${signature.message}`,
'handshake',
DISCONNECT_REASON.KEY_EXCHANGE_FAILED
)
}
signature = convertSignature(signature, this._hostKey.type)
if (signature === false) {
return doFatalError(
this._protocol,
'Handshake failed: signature conversion failed for ' + `${this._hostKey.type} host key`,
'handshake',
DISCONNECT_REASON.KEY_EXCHANGE_FAILED
)
}
// Send KEX reply
/*
byte SSH_MSG_KEXDH_REPLY
/ SSH_MSG_KEX_DH_GEX_REPLY
/ SSH_MSG_KEX_ECDH_REPLY
string server public host key and certificates (K_S)
string <method-specific data>
string signature of H
*/
const sigType = this.negotiated.serverHostKey
const sigTypeLen = Buffer.byteLength(sigType)
const sigLen = 4 + sigTypeLen + 4 + signature.length
let p = this._protocol._packetRW.write.allocStartKEX
const packet = this._protocol._packetRW.write.alloc(
1 + 4 + serverPublicHostKey.length + 4 + serverPublicKey.length + 4 + sigLen,
true
)
packet[p] = MESSAGE.KEXDH_REPLY
writeUInt32BE(packet, serverPublicHostKey.length, ++p)
packet.set(serverPublicHostKey, (p += 4))
writeUInt32BE(packet, serverPublicKey.length, (p += serverPublicHostKey.length))
packet.set(serverPublicKey, (p += 4))
writeUInt32BE(packet, sigLen, (p += serverPublicKey.length))
writeUInt32BE(packet, sigTypeLen, (p += 4))
packet.utf8Write(sigType, (p += 4), sigTypeLen)
writeUInt32BE(packet, signature.length, (p += sigTypeLen))
packet.set(signature, (p += 4))
if (this._protocol._debug) {
let type
switch (this.type) {
case 'group':
type = 'KEXDH_REPLY'
break
case 'groupex':
type = 'KEXDH_GEX_REPLY'
break
default:
type = 'KEXECDH_REPLY'
}
this._protocol._debug(`Outbound: Sending ${type}`)
}
this._protocol._cipher.encrypt(this._protocol._packetRW.write.finalize(packet, true))
}
if (isServer || !scOnly) trySendNEWKEYS(this)
let hsCipherConfig
let hsWrite
const completeHandshake = (partial) => {
if (hsCipherConfig) {
trySendNEWKEYS(this)
hsCipherConfig.outbound.seqno = this._protocol._cipher.outSeqno
this._protocol._cipher.free()
this._protocol._cipher = createCipher(hsCipherConfig)
this._protocol._packetRW.write = hsWrite
hsCipherConfig = undefined
hsWrite = undefined
this._protocol._onHandshakeComplete(negotiated)
return false
}
if (!this.sessionID) this.sessionID = exchangeHash
{
const newSecret = Buffer.allocUnsafe(4 + secret.length)
writeUInt32BE(newSecret, secret.length, 0)
newSecret.set(secret, 4)
secret = newSecret
}
// Initialize new ciphers, deciphers, etc.
const csCipherInfo = CIPHER_INFO[negotiated.cs.cipher]
const scCipherInfo = CIPHER_INFO[negotiated.sc.cipher]
const csIV = generateKEXVal(
csCipherInfo.ivLen,
this.hashName,
secret,
exchangeHash,
this.sessionID,
'A'
)
const scIV = generateKEXVal(
scCipherInfo.ivLen,
this.hashName,
secret,
exchangeHash,
this.sessionID,
'B'
)
const csKey = generateKEXVal(
csCipherInfo.keyLen,
this.hashName,
secret,
exchangeHash,
this.sessionID,
'C'
)
const scKey = generateKEXVal(
scCipherInfo.keyLen,
this.hashName,
secret,
exchangeHash,
this.sessionID,
'D'
)
let csMacInfo
let csMacKey
if (!csCipherInfo.authLen) {
csMacInfo = MAC_INFO[negotiated.cs.mac]
csMacKey = generateKEXVal(
csMacInfo.len,
this.hashName,
secret,
exchangeHash,
this.sessionID,
'E'
)
}
let scMacInfo
let scMacKey
if (!scCipherInfo.authLen) {
scMacInfo = MAC_INFO[negotiated.sc.mac]
scMacKey = generateKEXVal(
scMacInfo.len,
this.hashName,
secret,
exchangeHash,
this.sessionID,
'F'
)
}
const config = {
inbound: {
onPayload: this._protocol._onPayload,
seqno: this._protocol._decipher.inSeqno,
decipherInfo: !isServer ? scCipherInfo : csCipherInfo,
decipherIV: !isServer ? scIV : csIV,
decipherKey: !isServer ? scKey : csKey,
macInfo: !isServer ? scMacInfo : csMacInfo,
macKey: !isServer ? scMacKey : csMacKey
},
outbound: {
onWrite: this._protocol._onWrite,
seqno: this._protocol._cipher.outSeqno,
cipherInfo: isServer ? scCipherInfo : csCipherInfo,
cipherIV: isServer ? scIV : csIV,
cipherKey: isServer ? scKey : csKey,
macInfo: isServer ? scMacInfo : csMacInfo,
macKey: isServer ? scMacKey : csMacKey
}
}
this._protocol._decipher.free()
hsCipherConfig = config
this._protocol._decipher = createDecipher(config)
const rw = {
read: undefined,
write: undefined
}
switch (negotiated.cs.compress) {
case 'zlib': // starts immediately
if (isServer) rw.read = new ZlibPacketReader()
else rw.write = new ZlibPacketWriter(this._protocol)
break
case '[email protected]':
// Starts after successful user authentication
if (this._protocol._authenticated) {
// If a rekey happens and this compression method is selected and
// we already authenticated successfully, we need to start
// immediately instead
if (isServer) rw.read = new ZlibPacketReader()
else rw.write = new ZlibPacketWriter(this._protocol)
break
}
// FALLTHROUGH
default:
// none -- never any compression/decompression
if (isServer) rw.read = new PacketReader()
else rw.write = new PacketWriter(this._protocol)
}
switch (negotiated.sc.compress) {
case 'zlib': // starts immediately
if (isServer) rw.write = new ZlibPacketWriter(this._protocol)
else rw.read = new ZlibPacketReader()
break
case '[email protected]':
// Starts after successful user authentication
if (this._protocol._authenticated) {
// If a rekey happens and this compression method is selected and
// we already authenticated successfully, we need to start
// immediately instead
if (isServer) rw.write = new ZlibPacketWriter(this._protocol)
else rw.read = new ZlibPacketReader()
break
}
// FALLTHROUGH
default:
// none -- never any compression/decompression
if (isServer) rw.write = new PacketWriter(this._protocol)
else rw.read = new PacketReader()
}
this._protocol._packetRW.read.cleanup()
this._protocol._packetRW.write.cleanup()
this._protocol._packetRW.read = rw.read
hsWrite = rw.write
// Cleanup/reset various state
this._public = null
this._dh = null
this._kexinit = this._protocol._kexinit = undefined
this._remoteKexinit = undefined
this._identRaw = undefined
this._remoteIdentRaw = undefined
this._hostKey = undefined
this._dhData = undefined
this._sig = undefined
if (!partial) return completeHandshake()
return false
}
if (isServer || scOnly) this.finish = completeHandshake
if (!isServer) return completeHandshake(scOnly)
}
start() {
if (!this._protocol._server) {
if (this._protocol._debug) {
let type
switch (this.type) {
case 'group':
type = 'KEXDH_INIT'
break
default:
type = 'KEXECDH_INIT'
}
this._protocol._debug(`Outbound: Sending ${type}`)
}
const pubKey = this.getPublicKey()
let p = this._protocol._packetRW.write.allocStartKEX
const packet = this._protocol._packetRW.write.alloc(1 + 4 + pubKey.length, true)
packet[p] = MESSAGE.KEXDH_INIT
writeUInt32BE(packet, pubKey.length, ++p)
packet.set(pubKey, (p += 4))
this._protocol._cipher.encrypt(this._protocol._packetRW.write.finalize(packet, true))
}
}
getPublicKey() {
this.generateKeys()
const key = this._public
if (key) return this.convertPublicKey(key)
}
convertPublicKey(key) {
let newKey
let idx = 0
let len = key.length
while (key[idx] === 0x00) {
++idx
--len
}
if (key[idx] & 0x80) {
newKey = Buffer.allocUnsafe(1 + len)
newKey[0] = 0
copyBytes(key, newKey, 1, idx, key.length)
return newKey
}
if (len !== key.length) {
newKey = Buffer.allocUnsafe(len)
copyBytes(key, newKey, 0, idx, key.length)
key = newKey
}
return key
}
computeSecret(otherPublicKey) {
this.generateKeys()
try {
return convertToMpint(this._dh.computeSecret(otherPublicKey))
} catch (ex) {
return ex
}
}
parse(payload) {
const type = payload[0]
switch (this._step) {
case 1:
if (this._protocol._server) {
// Server
if (type !== MESSAGE.KEXDH_INIT) {
return doFatalError(
this._protocol,
`Received packet ${type} instead of ${MESSAGE.KEXDH_INIT}`,
'handshake',
DISCONNECT_REASON.KEY_EXCHANGE_FAILED
)
}
this._protocol._debug && this._protocol._debug('Received DH Init')
/*
byte SSH_MSG_KEXDH_INIT
/ SSH_MSG_KEX_ECDH_INIT
string <method-specific data>
*/
bufferParser.init(payload, 1)
const dhData = bufferParser.readString()
bufferParser.clear()
if (dhData === undefined) {
return doFatalError(
this._protocol,
'Received malformed KEX*_INIT',
'handshake',
DISCONNECT_REASON.KEY_EXCHANGE_FAILED
)
}
// Client public key
this._dhData = dhData
let hostKey = this._protocol._hostKeys[this.negotiated.serverHostKey]
if (Array.isArray(hostKey)) hostKey = hostKey[0]
this._hostKey = hostKey
this.finish()
} else {
// Client
if (type !== MESSAGE.KEXDH_REPLY) {
return doFatalError(
this._protocol,
`Received packet ${type} instead of ${MESSAGE.KEXDH_REPLY}`,
'handshake',
DISCONNECT_REASON.KEY_EXCHANGE_FAILED
)
}
this._protocol._debug && this._protocol._debug('Received DH Reply')
/*
byte SSH_MSG_KEXDH_REPLY
/ SSH_MSG_KEX_DH_GEX_REPLY
/ SSH_MSG_KEX_ECDH_REPLY
string server public host key and certificates (K_S)
string <method-specific data>
string signature of H
*/
bufferParser.init(payload, 1)
let hostPubKey
let dhData
let sig
if (
(hostPubKey = bufferParser.readString()) === undefined ||
(dhData = bufferParser.readString()) === undefined ||
(sig = bufferParser.readString()) === undefined
) {
bufferParser.clear()
return doFatalError(
this._protocol,
'Received malformed KEX*_REPLY',
'handshake',
DISCONNECT_REASON.KEY_EXCHANGE_FAILED
)
}
bufferParser.clear()
// Check that the host public key type matches what was negotiated
// during KEXINIT swap
bufferParser.init(hostPubKey, 0)
const hostPubKeyType = bufferParser.readString(true)
bufferParser.clear()
if (hostPubKeyType === undefined) {
return doFatalError(
this._protocol,
'Received malformed host public key',
'handshake',
DISCONNECT_REASON.KEY_EXCHANGE_FAILED
)
}
if (hostPubKeyType !== this.negotiated.serverHostKey) {
// Check if we need to make an exception
switch (this.negotiated.serverHostKey) {
case 'rsa-sha2-256':
case 'rsa-sha2-512':
if (hostPubKeyType === 'ssh-rsa') break
// FALLTHROUGH
default:
return doFatalError(
this._protocol,
'Host key does not match negotiated type',
'handshake',
DISCONNECT_REASON.KEY_EXCHANGE_FAILED
)
}
}
this._hostKey = hostPubKey
this._dhData = dhData
this._sig = sig
let checked = false
let ret
if (this._protocol._hostVerifier === undefined) {
ret = true
this._protocol._debug &&
this._protocol._debug('Host accepted by default (no verification)')
} else {
ret = this._protocol._hostVerifier(hostPubKey, (permitted) => {
if (checked) return
checked = true
if (permitted === false) {
this._protocol._debug &&
this._protocol._debug('Host denied (verification failed)')
return doFatalError(
this._protocol,
'Host denied (verification failed)',
'handshake',
DISCONNECT_REASON.KEY_EXCHANGE_FAILED
)
}
this._protocol._debug && this._protocol._debug('Host accepted (verified)')
this._hostVerified = true
if (this._receivedNEWKEYS) this.finish()
else trySendNEWKEYS(this)
})
}
if (ret === undefined) {
// Async host verification
++this._step
return
}
checked = true
if (ret === false) {
this._protocol._debug && this._protocol._debug('Host denied (verification failed)')
return doFatalError(
this._protocol,
'Host denied (verification failed)',
'handshake',
DISCONNECT_REASON.KEY_EXCHANGE_FAILED
)
}
this._protocol._debug && this._protocol._debug('Host accepted (verified)')
this._hostVerified = true
trySendNEWKEYS(this)
}
++this._step
break
case 2:
if (type !== MESSAGE.NEWKEYS) {
return doFatalError(
this._protocol,
`Received packet ${type} instead of ${MESSAGE.NEWKEYS}`,
'handshake',
DISCONNECT_REASON.KEY_EXCHANGE_FAILED
)
}
this._protocol._debug && this._protocol._debug('Inbound: NEWKEYS')
this._receivedNEWKEYS = true
if (this._protocol._strictMode) this._protocol._decipher.inSeqno = 0
++this._step
return this.finish(!this._protocol._server && !this._hostVerified)
default:
return doFatalError(
this._protocol,
`Received unexpected packet ${type} after NEWKEYS`,
'handshake',
DISCONNECT_REASON.KEY_EXCHANGE_FAILED
)
}
}
}
class Curve25519Exchange extends KeyExchange {
constructor(hashName, ...args) {
super(...args)
this.type = '25519'
this.hashName = hashName
this._keys = null
}
generateKeys() {
if (!this._keys) this._keys = generateKeyPairSync('x25519')
}
getPublicKey() {
this.generateKeys()
const key = this._keys.publicKey.export({ type: 'spki', format: 'der' })
return key.slice(-32) // HACK: avoids parsing DER/BER header
}
convertPublicKey(key) {
let newKey
let idx = 0
let len = key.length
while (key[idx] === 0x00) {
++idx
--len
}
if (key.length === 32) return key
if (len !== key.length) {
newKey = Buffer.allocUnsafe(len)
copyBytes(key, newKey, 0, idx, key.length)
key = newKey
}
return key
}
computeSecret(otherPublicKey) {
this.generateKeys()
try {
const asnWriter = new Ber.Writer()
asnWriter.startSequence()
// algorithm
asnWriter.startSequence()
asnWriter.writeOID('1.3.101.110') // id-X25519
asnWriter.endSequence()
// PublicKey
asnWriter.startSequence(Ber.BitString)
asnWriter.writeByte(0x00)
// XXX: hack to write a raw buffer without a tag -- yuck
asnWriter._ensure(otherPublicKey.length)
{
const _ab = asnWriter._buf
const _ao = asnWriter._offset
for (let i = 0; i < otherPublicKey.length; ++i) {
_ab[_ao + i] = otherPublicKey[i]
}
}
asnWriter._offset += otherPublicKey.length
asnWriter.endSequence()
asnWriter.endSequence()
return convertToMpint(
diffieHellman({
privateKey: this._keys.privateKey,
publicKey: createPublicKey({
key: asnWriter.buffer,
type: 'spki',
format: 'der'
})
})
)
} catch (ex) {
return ex
}
}
}
class ECDHExchange extends KeyExchange {
constructor(curveName, hashName, ...args) {
super(...args)
this.type = 'ecdh'
this.curveName = curveName
this.hashName = hashName
}
generateKeys() {
if (!this._dh) {
this._dh = createECDH(this.curveName)
this._public = this._dh.generateKeys()
}
}
}
class DHGroupExchange extends KeyExchange {
constructor(hashName, ...args) {
super(...args)
this.type = 'groupex'
this.hashName = hashName
this._prime = null
this._generator = null
this._minBits = GEX_MIN_BITS
this._prefBits = dhEstimate(this.negotiated)
if (this._protocol._compatFlags & COMPAT.BUG_DHGEX_LARGE)
this._prefBits = Math.min(this._prefBits, 4096)
this._maxBits = GEX_MAX_BITS
}
start() {
if (this._protocol._server) return
this._protocol._debug && this._protocol._debug('Outbound: Sending KEXDH_GEX_REQUEST')
let p = this._protocol._packetRW.write.allocStartKEX
const packet = this._protocol._packetRW.write.alloc(1 + 4 + 4 + 4, true)
packet[p] = MESSAGE.KEXDH_GEX_REQUEST
writeUInt32BE(packet, this._minBits, ++p)
writeUInt32BE(packet, this._prefBits, (p += 4))
writeUInt32BE(packet, this._maxBits, (p += 4))
this._protocol._cipher.encrypt(this._protocol._packetRW.write.finalize(packet, true))
}
generateKeys() {
if (!this._dh && this._prime && this._generator) {
this._dh = createDiffieHellman(this._prime, this._generator)
this._public = this._dh.generateKeys()
}
}
setDHParams(prime, generator) {
if (!Buffer.isBuffer(prime)) throw new Error('Invalid prime value')
if (!Buffer.isBuffer(generator)) throw new Error('Invalid generator value')
this._prime = prime
this._generator = generator
}
getDHParams() {
if (this._dh) {
return {
prime: convertToMpint(this._dh.getPrime()),
generator: convertToMpint(this._dh.getGenerator())
}
}
}
parse(payload) {
const type = payload[0]
switch (this._step) {
case 1: {
if (this._protocol._server) {
if (type !== MESSAGE.KEXDH_GEX_REQUEST) {
return doFatalError(
this._protocol,
`Received packet ${type} instead of ` + MESSAGE.KEXDH_GEX_REQUEST,
'handshake',
DISCONNECT_REASON.KEY_EXCHANGE_FAILED
)
}
// TODO: allow user implementation to provide safe prime and
// generator on demand to support group exchange on server side
return doFatalError(
this._protocol,
'Group exchange not implemented for server',
'handshake',
DISCONNECT_REASON.KEY_EXCHANGE_FAILED
)
}
if (type !== MESSAGE.KEXDH_GEX_GROUP) {
return doFatalError(
this._protocol,
`Received packet ${type} instead of ${MESSAGE.KEXDH_GEX_GROUP}`,
'handshake',
DISCONNECT_REASON.KEY_EXCHANGE_FAILED
)
}
this._protocol._debug && this._protocol._debug('Received DH GEX Group')
/*
byte SSH_MSG_KEX_DH_GEX_GROUP
mpint p, safe prime
mpint g, generator for subgroup in GF(p)
*/
bufferParser.init(payload, 1)
let prime
let gen
if (
(prime = bufferParser.readString()) === undefined ||
(gen = bufferParser.readString()) === undefined
) {
bufferParser.clear()
return doFatalError(
this._protocol,
'Received malformed KEXDH_GEX_GROUP',
'handshake',
DISCONNECT_REASON.KEY_EXCHANGE_FAILED
)
}
bufferParser.clear()
// TODO: validate prime
this.setDHParams(prime, gen)
this.generateKeys()
const pubkey = this.getPublicKey()
this._protocol._debug && this._protocol._debug('Outbound: Sending KEXDH_GEX_INIT')
let p = this._protocol._packetRW.write.allocStartKEX
const packet = this._protocol._packetRW.write.alloc(1 + 4 + pubkey.length, true)
packet[p] = MESSAGE.KEXDH_GEX_INIT
writeUInt32BE(packet, pubkey.length, ++p)
packet.set(pubkey, (p += 4))
this._protocol._cipher.encrypt(this._protocol._packetRW.write.finalize(packet, true))
++this._step
break
}
case 2:
if (this._protocol._server) {
if (type !== MESSAGE.KEXDH_GEX_INIT) {
return doFatalError(
this._protocol,
`Received packet ${type} instead of ${MESSAGE.KEXDH_GEX_INIT}`,
'handshake',
DISCONNECT_REASON.KEY_EXCHANGE_FAILED
)
}
this._protocol._debug && this._protocol._debug('Received DH GEX Init')
return doFatalError(
this._protocol,
'Group exchange not implemented for server',
'handshake',
DISCONNECT_REASON.KEY_EXCHANGE_FAILED
)
} else if (type !== MESSAGE.KEXDH_GEX_REPLY) {
return doFatalError(
this._protocol,
`Received packet ${type} instead of ${MESSAGE.KEXDH_GEX_REPLY}`,
'handshake',
DISCONNECT_REASON.KEY_EXCHANGE_FAILED
)
}
this._protocol._debug && this._protocol._debug('Received DH GEX Reply')
this._step = 1
payload[0] = MESSAGE.KEXDH_REPLY
this.parse = KeyExchange.prototype.parse
this.parse(payload)
}
}
}
class DHExchange extends KeyExchange {
constructor(groupName, hashName, ...args) {
super(...args)
this.type = 'group'
this.groupName = groupName
this.hashName = hashName
}
start() {
if (!this._protocol._server) {
this._protocol._debug && this._protocol._debug('Outbound: Sending KEXDH_INIT')
const pubKey = this.getPublicKey()
let p = this._protocol._packetRW.write.allocStartKEX
const packet = this._protocol._packetRW.write.alloc(1 + 4 + pubKey.length, true)
packet[p] = MESSAGE.KEXDH_INIT
writeUInt32BE(packet, pubKey.length, ++p)
packet.set(pubKey, (p += 4))
this._protocol._cipher.encrypt(this._protocol._packetRW.write.finalize(packet, true))
}
}
generateKeys() {
if (!this._dh) {
this._dh = createDiffieHellmanGroup(this.groupName)
this._public = this._dh.generateKeys()
}
}
getDHParams() {
if (this._dh) {
return {
prime: convertToMpint(this._dh.getPrime()),
generator: convertToMpint(this._dh.getGenerator())
}
}
}
}
return (negotiated, ...args) => {
if (typeof negotiated !== 'object' || negotiated === null)
throw new Error('Invalid negotiated argument')
const kexType = negotiated.kex
if (typeof kexType === 'string') {
args = [negotiated, ...args]
switch (kexType) {
case 'curve25519-sha256':
case '[email protected]':
if (!curve25519Supported) break
return new Curve25519Exchange('sha256', ...args)
case 'ecdh-sha2-nistp256':
return new ECDHExchange('prime256v1', 'sha256', ...args)
case 'ecdh-sha2-nistp384':
return new ECDHExchange('secp384r1', 'sha384', ...args)
case 'ecdh-sha2-nistp521':
return new ECDHExchange('secp521r1', 'sha512', ...args)
case 'diffie-hellman-group1-sha1':
return new DHExchange('modp2', 'sha1', ...args)
case 'diffie-hellman-group14-sha1':
return new DHExchange('modp14', 'sha1', ...args)
case 'diffie-hellman-group14-sha256':
return new DHExchange('modp14', 'sha256', ...args)
case 'diffie-hellman-group15-sha512':
return new DHExchange('modp15', 'sha512', ...args)
case 'diffie-hellman-group16-sha512':
return new DHExchange('modp16', 'sha512', ...args)
case 'diffie-hellman-group17-sha512':
return new DHExchange('modp17', 'sha512', ...args)
case 'diffie-hellman-group18-sha512':
return new DHExchange('modp18', 'sha512', ...args)
case 'diffie-hellman-group-exchange-sha1':
return new DHGroupExchange('sha1', ...args)
case 'diffie-hellman-group-exchange-sha256':
return new DHGroupExchange('sha256', ...args)
}
throw new Error(`Unsupported key exchange algorithm: ${kexType}`)
}
throw new Error(`Invalid key exchange type: ${kexType}`)
}
})()
const KexInit = (() => {
const KEX_PROPERTY_NAMES = [
'kex',
'serverHostKey',
['cs', 'cipher'],
['sc', 'cipher'],
['cs', 'mac'],
['sc', 'mac'],
['cs', 'compress'],
['sc', 'compress'],
['cs', 'lang'],
['sc', 'lang']
]
return class KexInit {
constructor(obj) {
if (typeof obj !== 'object' || obj === null) throw new TypeError('Argument must be an object')
const lists = {
kex: undefined,
serverHostKey: undefined,
cs: {
cipher: undefined,
mac: undefined,
compress: undefined,
lang: undefined
},
sc: {
cipher: undefined,
mac: undefined,
compress: undefined,
lang: undefined
},
all: undefined
}
let totalSize = 0
for (const prop of KEX_PROPERTY_NAMES) {
let base
let val
let desc
let key
if (typeof prop === 'string') {
base = lists
val = obj[prop]
desc = key = prop
} else {
const parent = prop[0]
base = lists[parent]
key = prop[1]
val = obj[parent][key]
desc = `${parent}.${key}`
}
const entry = { array: undefined, buffer: undefined }
if (Buffer.isBuffer(val)) {
entry.array = ('' + val).split(',')
entry.buffer = val
totalSize += 4 + val.length
} else {
if (typeof val === 'string') val = val.split(',')
if (Array.isArray(val)) {
entry.array = val
entry.buffer = Buffer.from(val.join(','))
} else {
throw new TypeError(`Invalid \`${desc}\` type: ${typeof val}`)
}
totalSize += 4 + entry.buffer.length
}
base[key] = entry
}
const all = Buffer.allocUnsafe(totalSize)
lists.all = all
let allPos = 0
for (const prop of KEX_PROPERTY_NAMES) {
let data
if (typeof prop === 'string') data = lists[prop].buffer
else data = lists[prop[0]][prop[1]].buffer
allPos = writeUInt32BE(all, data.length, allPos)
all.set(data, allPos)
allPos += data.length
}
this.totalSize = totalSize
this.lists = lists
}
copyAllTo(buf, offset) {
const src = this.lists.all
if (typeof offset !== 'number') throw new TypeError(`Invalid offset value: ${typeof offset}`)
if (buf.length - offset < src.length) throw new Error('Insufficient space to copy list')
buf.set(src, offset)
return src.length
}
}
})()
const hashString = (() => {
const LEN = Buffer.allocUnsafe(4)
return (hash, buf) => {
writeUInt32BE(LEN, buf.length, 0)
hash.update(LEN)
hash.update(buf)
}
})()
function generateKEXVal(len, hashName, secret, exchangeHash, sessionID, char) {
let ret
if (len) {
let digest = createHash(hashName)
.update(secret)
.update(exchangeHash)
.update(char)
.update(sessionID)
.digest()
while (digest.length < len) {
const chunk = createHash(hashName).update(secret).update(exchangeHash).update(digest).digest()
const extended = Buffer.allocUnsafe(digest.length + chunk.length)
extended.set(digest, 0)
extended.set(chunk, digest.length)
digest = extended
}
if (digest.length === len) ret = digest
else ret = new FastBuffer(digest.buffer, digest.byteOffset, len)
} else {
ret = EMPTY_BUFFER
}
return ret
}
function onKEXPayload(state, payload) {
// XXX: move this to the Decipher implementations?
if (payload.length === 0) {
this._debug && this._debug('Inbound: Skipping empty packet payload')
return
}
if (this._skipNextInboundPacket) {
this._skipNextInboundPacket = false
return
}
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:
if (!MESSAGE_HANDLERS) MESSAGE_HANDLERS = require('./handlers.js')
return MESSAGE_HANDLERS[type](this, payload)
case MESSAGE.KEXINIT:
if (!state.firstPacket) {
return doFatalError(
this,
'Received extra KEXINIT during handshake',
'handshake',
DISCONNECT_REASON.KEY_EXCHANGE_FAILED
)
}
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,
`Received unexpected packet type ${type}`,
'handshake',
DISCONNECT_REASON.KEY_EXCHANGE_FAILED
)
}
}
return this._kex.parse(payload)
}
function dhEstimate(neg) {
const csCipher = CIPHER_INFO[neg.cs.cipher]
const scCipher = CIPHER_INFO[neg.sc.cipher]
// XXX: if OpenSSH's `umac-*` MACs are ever supported, their key lengths will
// also need to be considered when calculating `bits`
const bits =
Math.max(
0,
csCipher.sslName === 'des-ede3-cbc' ? 14 : csCipher.keyLen,
csCipher.blockLen,
csCipher.ivLen,
scCipher.sslName === 'des-ede3-cbc' ? 14 : scCipher.keyLen,
scCipher.blockLen,
scCipher.ivLen
) * 8
if (bits <= 112) return 2048
if (bits <= 128) return 3072
if (bits <= 192) return 7680
return 8192
}
function trySendNEWKEYS(kex) {
if (!kex._sentNEWKEYS) {
kex._protocol._debug && kex._protocol._debug('Outbound: Sending NEWKEYS')
const p = kex._protocol._packetRW.write.allocStartKEX
const packet = kex._protocol._packetRW.write.alloc(1, true)
packet[p] = MESSAGE.NEWKEYS
kex._protocol._cipher.encrypt(kex._protocol._packetRW.write.finalize(packet, true))
kex._sentNEWKEYS = true
if (kex._protocol._strictMode) kex._protocol._cipher.outSeqno = 0
}
}
module.exports = {
KexInit,
kexinit,
onKEXPayload,
DEFAULT_KEXINIT_CLIENT: new KexInit({
kex: DEFAULT_KEX.concat(['ext-info-c', '[email protected]']),
serverHostKey: DEFAULT_SERVER_HOST_KEY,
cs: {
cipher: DEFAULT_CIPHER,
mac: DEFAULT_MAC,
compress: DEFAULT_COMPRESSION,
lang: []
},
sc: {
cipher: DEFAULT_CIPHER,
mac: DEFAULT_MAC,
compress: DEFAULT_COMPRESSION,
lang: []
}
}),
DEFAULT_KEXINIT_SERVER: new KexInit({
kex: DEFAULT_KEX.concat(['[email protected]']),
serverHostKey: DEFAULT_SERVER_HOST_KEY,
cs: {
cipher: DEFAULT_CIPHER,
mac: DEFAULT_MAC,
compress: DEFAULT_COMPRESSION,
lang: []
},
sc: {
cipher: DEFAULT_CIPHER,
mac: DEFAULT_MAC,
compress: DEFAULT_COMPRESSION,
lang: []
}
}),
HANDLERS: {
[MESSAGE.KEXINIT]: handleKexInit
}
}