@microsoft/dev-tunnels-ssh
Version:
SSH library for Dev Tunnels
497 lines • 30.9 kB
JavaScript
//
// 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
;