UNPKG

@microsoft/dev-tunnels-ssh

Version:
497 lines 30.9 kB
"use strict"; // // Copyright (c) Microsoft Corporation. All rights reserved. // var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var KeyExchangeService_1; Object.defineProperty(exports, "__esModule", { value: true }); exports.KeyExchangeService = void 0; const buffer_1 = require("buffer"); const sshSession_1 = require("../sshSession"); const sshService_1 = require("./sshService"); const bigInt_1 = require("../io/bigInt"); const kexMessages_1 = require("../messages/kexMessages"); const sshAlgorithms_1 = require("../algorithms/sshAlgorithms"); const sshData_1 = require("../io/sshData"); const errors_1 = require("../errors"); const transportMessages_1 = require("../messages/transportMessages"); const sshSessionAlgorithms_1 = require("../sshSessionAlgorithms"); const vscode_jsonrpc_1 = require("vscode-jsonrpc"); const connectionService_1 = require("./connectionService"); const serviceActivation_1 = require("./serviceActivation"); const trace_1 = require("../trace"); class ExchangeContext { } const serverExtensionInfoSignal = 'ext-info-s'; const clientExtensionInfoSignal = 'ext-info-c'; let KeyExchangeService = KeyExchangeService_1 = class KeyExchangeService extends sshService_1.SshService { constructor(session) { super(session); this.isInitialExchange = false; this.exchangeContext = null; } get exchanging() { return !!this.exchangeContext; } async startKeyExchange(isInitialExchange) { this.isInitialExchange = isInitialExchange; this.exchangeContext = new ExchangeContext(); const kexInitMessage = this.createKeyExchangeInitMessage(); let kexGuessMessage = null; if (this.session.isClientSession) { if (isInitialExchange && this.session.config.enableKeyExchangeGuess) { kexGuessMessage = await this.createKeyExchangeGuessMessage(); kexInitMessage.firstKexPacketFollows = !!kexGuessMessage; } this.exchangeContext.clientKexInitPayload = kexInitMessage.toBuffer(); } else { this.exchangeContext.serverKexInitPayload = kexInitMessage.toBuffer(); } return [kexInitMessage, kexGuessMessage]; } finishKeyExchange() { var _a; if (!this.exchangeContext) { throw new errors_1.SshConnectionError('Key exchange not started.', transportMessages_1.SshDisconnectReason.protocolError); } const newAlgorithms = this.exchangeContext.newAlgorithms; if (!newAlgorithms) { throw new errors_1.SshConnectionError('Key exchange not completed.', transportMessages_1.SshDisconnectReason.protocolError); } newAlgorithms.isExtensionInfoRequested = (_a = this.exchangeContext) === null || _a === void 0 ? void 0 : _a.isExtensionInfoRequested; this.exchangeContext = null; return newAlgorithms; } abortKeyExchange() { this.exchangeContext = null; } createKeyExchangeInitMessage() { // Reference RFC 8308: Signaling of Extension Negotiation in Key Exchange. const extinfo = this.session.isClientSession ? clientExtensionInfoSignal : serverExtensionInfoSignal; const config = this.session.config; const message = new kexMessages_1.KeyExchangeInitMessage(); message.keyExchangeAlgorithms = (0, sshAlgorithms_1.algorithmNames)(config.keyExchangeAlgorithms).concat(extinfo); message.serverHostKeyAlgorithms = this.getPublicKeyAlgorithms(); message.encryptionAlgorithmsClientToServer = message.encryptionAlgorithmsServerToClient = (0, sshAlgorithms_1.algorithmNames)(config.encryptionAlgorithms); message.macAlgorithmsClientToServer = message.macAlgorithmsServerToClient = (0, sshAlgorithms_1.algorithmNames)(config.hmacAlgorithms); message.compressionAlgorithmsClientToServer = message.compressionAlgorithmsServerToClient = (0, sshAlgorithms_1.algorithmNames)(config.compressionAlgorithms); message.languagesClientToServer = ['']; message.languagesServerToClient = ['']; message.firstKexPacketFollows = false; message.reserved = 0; return message; } /** * Gets the list of public key algorithms that the current session can support. * For a server session the list is filtered based on the available private keys. */ getPublicKeyAlgorithms() { var _a, _b; let publicKeyAlgorithms = [...this.session.config.publicKeyAlgorithms]; if (publicKeyAlgorithms.length > 1 && !this.session.isClientSession) { const privateKeyAlgorithms = (_b = (_a = this.session.credentials) === null || _a === void 0 ? void 0 : _a.publicKeys) === null || _b === void 0 ? void 0 : _b.map((k) => k.keyAlgorithmName); if (privateKeyAlgorithms) { publicKeyAlgorithms = publicKeyAlgorithms.filter((a) => a && privateKeyAlgorithms.includes(a.keyAlgorithmName)); } } const publicKeyAlgorithmNames = (0, sshAlgorithms_1.algorithmNames)(publicKeyAlgorithms); return publicKeyAlgorithmNames; } async createKeyExchangeGuessMessage() { if (!this.exchangeContext) { throw new Error('Key exchange was not started.'); } // Select the first key exchange algorithm as the "guess". (They are in preferential order.) const kexAlgorithm = this.session.config.keyExchangeAlgorithms[0]; if (!kexAlgorithm) { return null; } this.exchangeContext.keyExchange = kexAlgorithm.name; this.exchangeContext.exchange = kexAlgorithm.createKeyExchange(); this.exchangeContext.exchangeValue = await this.exchangeContext.exchange.startKeyExchange(); const guess = new kexMessages_1.KeyExchangeDhInitMessage(); guess.e = this.exchangeContext.exchangeValue; return guess; } handleMessage(message, cancellation) { if (message instanceof kexMessages_1.KeyExchangeInitMessage) { return this.handleInitMessage(message, cancellation); } else if (message instanceof kexMessages_1.KeyExchangeDhInitMessage) { return this.handleDhInitMessage(message, cancellation); } else if (message instanceof kexMessages_1.KeyExchangeDhReplyMessage) { return this.handleDhReplyMessage(message, cancellation); } else { throw new Error(`Message not implemented: ${message}`); } } async handleInitMessage(message, cancellation) { var _a, _b, _c, _d; if (!this.exchangeContext) { throw new Error('Key exchange was not started.'); } const config = this.session.config; this.exchangeContext.keyExchange = this.chooseAlgorithm('KeyExchange', (0, sshAlgorithms_1.algorithmNames)(config.keyExchangeAlgorithms), message.keyExchangeAlgorithms); if (this.exchangeContext.keyExchange === 'none') { this.trace(trace_1.TraceLevel.Info, trace_1.SshTraceEventIds.algorithmNegotiation, 'Client and server negotiated no security. Cancelling key-exchange.'); // The connection service is normally activated after authentication. But when there is // no key-exchange there will be no authentication, so connections must be enabled now. this.session.activateService(connectionService_1.ConnectionService); this.exchangeContext.newAlgorithms = new sshSessionAlgorithms_1.SshSessionAlgorithms(); await this.session.handleNewKeysMessage(new kexMessages_1.NewKeysMessage(), cancellation); return; } this.exchangeContext.publicKey = this.chooseAlgorithm('PublicKey', this.getPublicKeyAlgorithms(), message.serverHostKeyAlgorithms); this.exchangeContext.clientEncryption = this.chooseAlgorithm('ClientEncryption', (0, sshAlgorithms_1.algorithmNames)(config.encryptionAlgorithms), message.encryptionAlgorithmsClientToServer); this.exchangeContext.serverEncryption = this.chooseAlgorithm('ServerEncryption', (0, sshAlgorithms_1.algorithmNames)(config.encryptionAlgorithms), message.encryptionAlgorithmsServerToClient); this.exchangeContext.clientHmac = this.chooseAlgorithm('ClientHmac', (0, sshAlgorithms_1.algorithmNames)(config.hmacAlgorithms), message.macAlgorithmsClientToServer); this.exchangeContext.serverHmac = this.chooseAlgorithm('ServerHmac', (0, sshAlgorithms_1.algorithmNames)(config.hmacAlgorithms), message.macAlgorithmsServerToClient); this.exchangeContext.clientCompression = this.chooseAlgorithm('ClientCompression', (0, sshAlgorithms_1.algorithmNames)(config.compressionAlgorithms), message.compressionAlgorithmsClientToServer); this.exchangeContext.serverCompression = this.chooseAlgorithm('ServerCompression', (0, sshAlgorithms_1.algorithmNames)(config.compressionAlgorithms), message.compressionAlgorithmsServerToClient); if (this.session.isClientSession) { this.exchangeContext.serverKexInitPayload = message.toBuffer(); // If the exchange value is already initialized then this side sent a guess. const alreadySentGuess = !!this.exchangeContext.exchangeValue; // Check if the negotiated algorithm is the one preferred by THIS side. // This means if there was a "guess" at kex initialization then it was correct. const negotiatedKexAlgorithmIsPreferred = this.exchangeContext.keyExchange === ((_a = config.keyExchangeAlgorithms[0]) === null || _a === void 0 ? void 0 : _a.name); // If a guess was not sent, or the guess was wrong, send the init message now. if (!alreadySentGuess || !negotiatedKexAlgorithmIsPreferred) { const kexAlgorithm = config.getKeyExchangeAlgorithm(this.exchangeContext.keyExchange); this.exchangeContext.exchange = kexAlgorithm.createKeyExchange(); this.exchangeContext.exchangeValue = await this.exchangeContext.exchange.startKeyExchange(); const reply = new kexMessages_1.KeyExchangeDhInitMessage(); reply.e = this.exchangeContext.exchangeValue; await this.session.sendMessage(reply, cancellation); } else { this.trace(trace_1.TraceLevel.Verbose, trace_1.SshTraceEventIds.algorithmNegotiation, 'Already sent correct guess for key-exchange init.'); } this.exchangeContext.isExtensionInfoRequested = this.isInitialExchange && ((_b = message.keyExchangeAlgorithms) === null || _b === void 0 ? void 0 : _b.includes(serverExtensionInfoSignal)); } else { if (message.firstKexPacketFollows) { // The remote side indicated it is sending a guess immediately following. // Check if the negotiated algorithm is the one preferred by the OTHER side. // If so, the following "guess" will be correct. Otherwise it must be ignored. const negotiatedKexAlgorithmIsPreferred = this.exchangeContext.keyExchange === ((_c = message.keyExchangeAlgorithms) === null || _c === void 0 ? void 0 : _c[0]); const guessResult = negotiatedKexAlgorithmIsPreferred ? 'correct' : 'incorrect'; this.trace(trace_1.TraceLevel.Verbose, trace_1.SshTraceEventIds.algorithmNegotiation, `Client's KeyExchange guess was ${guessResult}.`); this.exchangeContext.discardGuessedInit = !negotiatedKexAlgorithmIsPreferred; } this.exchangeContext.clientKexInitPayload = message.toBuffer(); this.exchangeContext.isExtensionInfoRequested = this.isInitialExchange && ((_d = message.keyExchangeAlgorithms) === null || _d === void 0 ? void 0 : _d.includes(clientExtensionInfoSignal)); } } async handleDhInitMessage(message, cancellation) { var _a, _b, _c, _d, _e; if (this.session.isClientSession) { return; } const serverSession = this.session; if (!this.exchangeContext || !this.exchangeContext.keyExchange || !this.exchangeContext.publicKey) { throw new errors_1.SshConnectionError('Key exchange not started.', transportMessages_1.SshDisconnectReason.protocolError); } if (this.exchangeContext.discardGuessedInit) { // Algorithm negotiation determined that an incorrect guess would be received. this.exchangeContext.discardGuessedInit = false; return; } const kexAlg = this.session.config.getKeyExchangeAlgorithm(this.exchangeContext.keyExchange); if (!kexAlg) { throw new errors_1.SshConnectionError('Key exchange not supported for algorithm: ' + this.exchangeContext.keyExchange, transportMessages_1.SshDisconnectReason.keyExchangeFailed); } const publicKeyAlg = this.session.config.getPublicKeyAlgorithm(this.exchangeContext.publicKey); if (!publicKeyAlg) { throw new errors_1.SshConnectionError('Public key algorithm not supported: ' + this.exchangeContext.publicKey, transportMessages_1.SshDisconnectReason.keyExchangeFailed); } let privateKey = null; if ((_a = serverSession.credentials) === null || _a === void 0 ? void 0 : _a.publicKeys) { const publicKey = serverSession.credentials.publicKeys.find((k) => k.keyAlgorithmName === publicKeyAlg.keyAlgorithmName); privateKey = publicKey !== null && publicKey !== void 0 ? publicKey : null; if ((privateKey === null || privateKey === void 0 ? void 0 : privateKey.hasPrivateKey) === false) { if (!serverSession.credentials.privateKeyProvider) { throw new Error('A private key provider is required.'); } privateKey = await serverSession.credentials.privateKeyProvider(publicKey, cancellation !== null && cancellation !== void 0 ? cancellation : vscode_jsonrpc_1.CancellationToken.None); } } if (privateKey == null) { throw new errors_1.SshConnectionError('Private key not found for algorithm: ' + this.exchangeContext.publicKey, transportMessages_1.SshDisconnectReason.keyExchangeFailed); } const clientEncryption = this.session.config.getEncryptionAlgorithm(this.exchangeContext.clientEncryption); const serverEncryption = this.session.config.getEncryptionAlgorithm(this.exchangeContext.serverEncryption); const serverHmac = this.session.config.getHmacAlgorithm(this.exchangeContext.serverHmac); const clientHmac = this.session.config.getHmacAlgorithm(this.exchangeContext.clientHmac); const keyExchange = kexAlg.createKeyExchange(); const clientExchangeValue = message.e || buffer_1.Buffer.alloc(0); const serverExchangeValue = await keyExchange.startKeyExchange(); const sharedSecret = await keyExchange.decryptKeyExchange(clientExchangeValue); const hostKeyAndCerts = await privateKey.getPublicKeyBytes(publicKeyAlg.name); if (!hostKeyAndCerts) { throw new errors_1.SshConnectionError('Public key not set.', transportMessages_1.SshDisconnectReason.keyExchangeFailed); } const exchangeHash = await this.computeExchangeHash(keyExchange, hostKeyAndCerts, clientExchangeValue, serverExchangeValue, sharedSecret); if (!this.session.sessionId) { this.session.sessionId = exchangeHash; } const [clientCipherIV, serverCipherIV, clientCipherKey, serverCipherKey, clientHmacKey, serverHmacKey,] = await this.computeKeys(keyExchange, sharedSecret, exchangeHash, clientEncryption, serverEncryption, clientHmac, serverHmac); const cipher = (_b = (await (serverEncryption === null || serverEncryption === void 0 ? void 0 : serverEncryption.createCipher(true, serverCipherKey, serverCipherIV)))) !== null && _b !== void 0 ? _b : null; const decipher = (_c = (await (clientEncryption === null || clientEncryption === void 0 ? void 0 : clientEncryption.createCipher(false, clientCipherKey, clientCipherIV)))) !== null && _c !== void 0 ? _c : null; const signer = (_d = (await (serverHmac === null || serverHmac === void 0 ? void 0 : serverHmac.createSigner(serverHmacKey)))) !== null && _d !== void 0 ? _d : null; const verifier = (_e = (await (clientHmac === null || clientHmac === void 0 ? void 0 : clientHmac.createVerifier(clientHmacKey)))) !== null && _e !== void 0 ? _e : null; const algorithms = new sshSessionAlgorithms_1.SshSessionAlgorithms(); algorithms.publicKeyAlgorithmName = this.exchangeContext.publicKey; algorithms.cipher = cipher; algorithms.decipher = decipher; algorithms.signer = signer; algorithms.verifier = verifier; algorithms.messageSigner = (cipher === null || cipher === void 0 ? void 0 : cipher.authenticatedEncryption) ? cipher : signer; algorithms.messageVerifier = (decipher === null || decipher === void 0 ? void 0 : decipher.authenticatedEncryption) ? decipher : verifier; algorithms.compressor = this.session.config.getCompressionAlgorithm(this.exchangeContext.serverCompression); algorithms.decompressor = this.session.config.getCompressionAlgorithm(this.exchangeContext.clientCompression); this.exchangeContext.newAlgorithms = algorithms; // Wipe the keys from memory after they are stored in native key objects. if (clientCipherIV) clientCipherIV.fill(0); if (clientCipherKey) clientCipherKey.fill(0); if (clientHmacKey) clientHmacKey.fill(0); if (serverCipherIV) serverCipherIV.fill(0); if (serverCipherKey) serverCipherKey.fill(0); if (serverHmacKey) serverHmacKey.fill(0); const exchangeSigner = publicKeyAlg.createSigner(privateKey); let signature = await exchangeSigner.sign(exchangeHash); signature = publicKeyAlg.createSignatureData(signature); const reply = new kexMessages_1.KeyExchangeDhReplyMessage(); reply.hostKey = hostKeyAndCerts; reply.f = serverExchangeValue; reply.signature = signature; await this.session.sendMessage(reply, cancellation); await this.session.sendMessage(new kexMessages_1.NewKeysMessage(), cancellation); } async handleDhReplyMessage(message, cancellation) { var _a, _b, _c, _d; if (!this.session.isClientSession) { return; } if (!this.exchangeContext) { throw new errors_1.SshConnectionError('Key exchange was not started.', transportMessages_1.SshDisconnectReason.protocolError); } const config = this.session.config; const keyExchange = this.exchangeContext.exchange; const publicKeyAlgorithmName = this.exchangeContext.publicKey; const publicKeyAlg = config.getPublicKeyAlgorithm(publicKeyAlgorithmName); const clientEncryption = config.getEncryptionAlgorithm(this.exchangeContext.clientEncryption); const serverEncryption = config.getEncryptionAlgorithm(this.exchangeContext.serverEncryption); const serverHmac = config.getHmacAlgorithm(this.exchangeContext.serverHmac); const clientHmac = config.getHmacAlgorithm(this.exchangeContext.clientHmac); const clientExchangeValue = this.exchangeContext.exchangeValue; const serverExchangeValue = message.f; if (!keyExchange || !clientExchangeValue) { throw new errors_1.SshConnectionError('Failed to initialize crypto after key exchange.', transportMessages_1.SshDisconnectReason.keyExchangeFailed); } // Load the server's public key bytes into a key-pair instance. this.hostKey = publicKeyAlg.createKeyPair(); await this.hostKey.setPublicKeyBytes(message.hostKey); const sharedSecret = await keyExchange.decryptKeyExchange(serverExchangeValue); const hostKeyAndCerts = message.hostKey; const exchangeHash = await this.computeExchangeHash(keyExchange, hostKeyAndCerts, clientExchangeValue, serverExchangeValue, sharedSecret); const signature = publicKeyAlg.readSignatureData(message.signature); const exchangeVerifier = publicKeyAlg.createVerifier(this.hostKey); let verified; try { verified = await exchangeVerifier.verify(exchangeHash, signature); } catch (e) { if (!(e instanceof Error)) throw e; this.trace(trace_1.TraceLevel.Error, trace_1.SshTraceEventIds.serverAuthenticationFailed, `Server public key verification error: ${e.message}`, e); throw new errors_1.SshConnectionError(`Server public key verification failed: ${e.message}`, transportMessages_1.SshDisconnectReason.hostKeyNotVerifiable); } if (verified) { this.trace(trace_1.TraceLevel.Verbose, trace_1.SshTraceEventIds.sessionAuthenticated, 'Server public key verification succeeded.'); } else { this.trace(trace_1.TraceLevel.Warning, trace_1.SshTraceEventIds.serverAuthenticationFailed, 'Server public key verification failed.'); throw new errors_1.SshConnectionError('Server public key verification failed.', transportMessages_1.SshDisconnectReason.hostKeyNotVerifiable); } if (this.session.sessionId == null) { this.session.sessionId = exchangeHash; } const [clientCipherIV, serverCipherIV, clientCipherKey, serverCipherKey, clientHmacKey, serverHmacKey,] = await this.computeKeys(keyExchange, sharedSecret, exchangeHash, clientEncryption, serverEncryption, clientHmac, serverHmac); const cipher = (_a = (await (clientEncryption === null || clientEncryption === void 0 ? void 0 : clientEncryption.createCipher(true, clientCipherKey, clientCipherIV)))) !== null && _a !== void 0 ? _a : null; const decipher = (_b = (await (serverEncryption === null || serverEncryption === void 0 ? void 0 : serverEncryption.createCipher(false, serverCipherKey, serverCipherIV)))) !== null && _b !== void 0 ? _b : null; const signer = (_c = (await (clientHmac === null || clientHmac === void 0 ? void 0 : clientHmac.createSigner(clientHmacKey)))) !== null && _c !== void 0 ? _c : null; const verifier = (_d = (await (serverHmac === null || serverHmac === void 0 ? void 0 : serverHmac.createVerifier(serverHmacKey)))) !== null && _d !== void 0 ? _d : null; const algorithms = new sshSessionAlgorithms_1.SshSessionAlgorithms(); algorithms.publicKeyAlgorithmName = publicKeyAlgorithmName; algorithms.cipher = cipher; algorithms.decipher = decipher; algorithms.signer = signer; algorithms.verifier = verifier; algorithms.messageSigner = (cipher === null || cipher === void 0 ? void 0 : cipher.authenticatedEncryption) ? cipher : signer; algorithms.messageVerifier = (decipher === null || decipher === void 0 ? void 0 : decipher.authenticatedEncryption) ? decipher : verifier; algorithms.compressor = config.getCompressionAlgorithm(this.exchangeContext.clientCompression); algorithms.decompressor = config.getCompressionAlgorithm(this.exchangeContext.serverCompression); this.exchangeContext.newAlgorithms = algorithms; // Wipe the keys from memory after they are stored in native key objects. if (clientCipherIV) clientCipherIV.fill(0); if (clientCipherKey) clientCipherKey.fill(0); if (clientHmacKey) clientHmacKey.fill(0); if (serverCipherIV) serverCipherIV.fill(0); if (serverCipherKey) serverCipherKey.fill(0); if (serverHmacKey) serverHmacKey.fill(0); await this.session.sendMessage(new kexMessages_1.NewKeysMessage(), cancellation); } chooseAlgorithm(label, localAlgorithms, remoteAlgorithms) { // Ensure consistent results if the client and server list the same algorithms // in different order of preference. let serverAlgorithms; let clientAlgorithms; if (this.session.isClientSession) { serverAlgorithms = remoteAlgorithms || []; clientAlgorithms = localAlgorithms; } else { serverAlgorithms = localAlgorithms; clientAlgorithms = remoteAlgorithms || []; } const negotiationDetail = `${label} negotiation: ` + `Server (${serverAlgorithms.join(', ')}) ` + `Client (${clientAlgorithms.join(', ')})`; for (const client of clientAlgorithms) { for (const server of serverAlgorithms) { if (server === client) { const result = server; this.trace(trace_1.TraceLevel.Info, trace_1.SshTraceEventIds.algorithmNegotiation, `${negotiationDetail} => ${result}`); return result; } } } throw new Error(`Failed ${negotiationDetail}`); } async computeExchangeHash(kex, hostKeyAndCerts, clientExchangeValue, serverExchangeValue, sharedSecret) { if (!this.session.remoteVersion) { throw new Error('Key exchange not completed.'); } const writer = new sshData_1.SshDataWriter(buffer_1.Buffer.alloc(2048)); if (this.session.isClientSession) { writer.writeString(sshSession_1.SshSession.localVersion.toString(), 'ascii'); writer.writeString(this.session.remoteVersion.toString(), 'ascii'); } else { writer.writeString(this.session.remoteVersion.toString(), 'ascii'); writer.writeString(sshSession_1.SshSession.localVersion.toString(), 'ascii'); } writer.writeBinary(this.exchangeContext.clientKexInitPayload); writer.writeBinary(this.exchangeContext.serverKexInitPayload); writer.writeBinary(hostKeyAndCerts); // These values are formatted as bigints (with leading zeroes if the first bit is high) // even though they might not really be bigints, depending on the key-exchange algorithm. writer.writeBigInt(bigInt_1.BigInt.fromBytes(clientExchangeValue, { unsigned: true })); writer.writeBigInt(bigInt_1.BigInt.fromBytes(serverExchangeValue, { unsigned: true })); writer.writeBigInt(bigInt_1.BigInt.fromBytes(sharedSecret, { unsigned: true })); const hash = await kex.sign(writer.toBuffer()); return hash; } async computeKeys(keyExchange, sharedSecret, exchangeHash, clientEncryption, serverEncryption, clientHmac, serverHmac) { var _a, _b; const writer = new sshData_1.SshDataWriter(buffer_1.Buffer.alloc(4 /* mpint header */ + sharedSecret.length + exchangeHash.length + Math.max(1 /* letter */ + ((_b = (_a = this.session.sessionId) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0), keyExchange.digestLength))); writer.writeBinary(sharedSecret); writer.write(exchangeHash); const offset = writer.position; const clientCipherIV = clientEncryption && (await this.computeKey(keyExchange, writer, offset, clientEncryption.blockLength, 'A')); const serverCipherIV = serverEncryption && (await this.computeKey(keyExchange, writer, offset, serverEncryption.blockLength, 'B')); const clientCipherKey = clientEncryption && (await this.computeKey(keyExchange, writer, offset, clientEncryption.keyLength, 'C')); const serverCipherKey = serverEncryption && (await this.computeKey(keyExchange, writer, offset, serverEncryption.keyLength, 'D')); const clientHmacKey = clientHmac && (await this.computeKey(keyExchange, writer, offset, clientHmac.keyLength, 'E')); const serverHmacKey = serverHmac && (await this.computeKey(keyExchange, writer, offset, serverHmac.keyLength, 'F')); return [ clientCipherIV, serverCipherIV, clientCipherKey, serverCipherKey, clientHmacKey, serverHmacKey, ]; } async computeKey(keyExchange, writer, writerOffset, blockSize, letter) { const keyBuffer = buffer_1.Buffer.alloc(blockSize); let keyBufferIndex = 0; let currentHashLength = 0; let currentHash = null; if (!this.session.sessionId) { throw new Error('Session ID not set.'); } while (keyBufferIndex < blockSize) { writer.position = writerOffset; if (!currentHash) { writer.writeByte(letter.charCodeAt(0)); writer.write(this.session.sessionId); } else { writer.write(currentHash); } currentHash = await keyExchange.sign(writer.toBuffer()); currentHashLength = Math.min(currentHash.length, blockSize - keyBufferIndex); currentHash.copy(keyBuffer, keyBufferIndex); keyBufferIndex += currentHashLength; } if (currentHash) { currentHash.fill(0); } return keyBuffer; } }; KeyExchangeService.serviceName = 'ssh-keyexchange'; KeyExchangeService = KeyExchangeService_1 = __decorate([ (0, serviceActivation_1.serviceActivation)({ serviceRequest: KeyExchangeService_1.serviceName }) ], KeyExchangeService); exports.KeyExchangeService = KeyExchangeService; //# sourceMappingURL=keyExchangeService.js.map