"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.keyAlgorithmName);
        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=https://main.vscode-cdn.net/sourcemaps/6a44c352bd24569c417e530095901b649960f9f8/node_modules/@microsoft/dev-tunnels-ssh/services/keyExchangeService.js.map