"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 AuthenticationService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.AuthenticationService = void 0;
const sshService_1 = require("./sshService");
const authenticationMessages_1 = require("../messages/authenticationMessages");
const vscode_jsonrpc_1 = require("vscode-jsonrpc");
const sshData_1 = require("../io/sshData");
const transportMessages_1 = require("../messages/transportMessages");
const sshAuthenticatingEventArgs_1 = require("../events/sshAuthenticatingEventArgs");
const connectionService_1 = require("./connectionService");
const serviceActivation_1 = require("./serviceActivation");
const queue_1 = require("../util/queue");
const trace_1 = require("../trace");
const errors_1 = require("../errors");
/**
 * Handles SSH protocol messages related to client authentication.
 */
let AuthenticationService = AuthenticationService_1 = class AuthenticationService extends sshService_1.SshService {
    constructor(session) {
        var _a;
        super(session);
        this.currentRequestMessage = null;
        this.authenticationFailureCount = 0;
        this.disposeCancellationSource = new vscode_jsonrpc_1.CancellationTokenSource();
        const algorithmName = (_a = session.algorithms) === null || _a === void 0 ? void 0 : _a.publicKeyAlgorithmName;
        if (!algorithmName) {
            throw new Error('Algorithms not initialized.');
        }
        this.publicKeyAlgorithmName = algorithmName;
    }
    handleMessage(message, cancellation) {
        if (message instanceof authenticationMessages_1.AuthenticationSuccessMessage) {
            return this.handleSuccessMessage(message);
        }
        else if (message instanceof authenticationMessages_1.AuthenticationFailureMessage) {
            return this.handleFailureMessage(message);
        }
        else if (message instanceof authenticationMessages_1.AuthenticationRequestMessage) {
            return this.handleAuthenticationRequestMessage(message, cancellation);
        }
        else if (message instanceof authenticationMessages_1.AuthenticationInfoRequestMessage) {
            return this.handleInfoRequestMessage(message, cancellation);
        }
        else if (message instanceof authenticationMessages_1.AuthenticationInfoResponseMessage) {
            return this.handleInfoResponseMessage(message, cancellation);
        }
        else if (message instanceof authenticationMessages_1.PublicKeyOKMessage) {
            // Not handled.
        }
        else {
            // Ignore unrecognized authentication messages.
        }
    }
    async handleAuthenticationRequestMessage(message, cancellation) {
        this.trace(trace_1.TraceLevel.Info, trace_1.SshTraceEventIds.sessionAuthenticating, `Authentication request: ${message.methodName}`);
        let methodName = message.methodName;
        if (!this.session.config.authenticationMethods.includes(methodName)) {
            methodName = null;
        }
        if (methodName === "publickey" /* AuthenticationMethod.publicKey */ ||
            methodName === "hostbased" /* AuthenticationMethod.hostBased */) {
            const publicKeymessage = message.convertTo(new authenticationMessages_1.PublicKeyRequestMessage());
            this.setCurrentRequest(publicKeymessage);
            return this.handlePublicKeyRequestMessage(publicKeymessage, cancellation);
        }
        else if (methodName === "password" /* AuthenticationMethod.password */) {
            const passwordMessage = message.convertTo(new authenticationMessages_1.PasswordRequestMessage());
            this.setCurrentRequest(passwordMessage);
            return this.handlePasswordRequestMessage(passwordMessage, cancellation);
        }
        else if (methodName === "keyboard-interactive" /* AuthenticationMethod.keyboardInteractive */) {
            this.setCurrentRequest(message);
            return this.beginInteractiveAuthentication(message, cancellation);
        }
        else if (methodName === "none" /* AuthenticationMethod.none */) {
            this.setCurrentRequest(message);
            return this.handleAuthenticating(new sshAuthenticatingEventArgs_1.SshAuthenticatingEventArgs(sshAuthenticatingEventArgs_1.SshAuthenticationType.clientNone, {
                username: message.username,
            }), cancellation);
        }
        else {
            this.setCurrentRequest(null);
            const failureMessage = new authenticationMessages_1.AuthenticationFailureMessage();
            failureMessage.methodNames = [
                "publickey" /* AuthenticationMethod.publicKey */,
                "password" /* AuthenticationMethod.password */,
                "hostbased" /* AuthenticationMethod.hostBased */,
            ];
            await this.session.sendMessage(failureMessage, cancellation);
        }
    }
    setCurrentRequest(message) {
        var _a;
        this.currentRequestMessage = message;
        const protocol = this.session.protocol;
        if (protocol) {
            protocol.messageContext = (_a = message === null || message === void 0 ? void 0 : message.methodName) !== null && _a !== void 0 ? _a : null;
        }
    }
    async handlePublicKeyRequestMessage(message, cancellation) {
        var _a, _b, _c;
        const publicKeyAlg = this.session.config.getPublicKeyAlgorithm(message.keyAlgorithmName);
        if (!publicKeyAlg) {
            const failureMessage = new authenticationMessages_1.AuthenticationFailureMessage();
            failureMessage.methodNames = [
                "publickey" /* AuthenticationMethod.publicKey */,
                "password" /* AuthenticationMethod.password */,
            ];
            await this.session.sendMessage(failureMessage, cancellation);
            return;
        }
        const publicKey = publicKeyAlg.createKeyPair();
        await publicKey.setPublicKeyBytes(message.publicKey);
        let args;
        if (message.methodName === "hostbased" /* AuthenticationMethod.hostBased */) {
            args = new sshAuthenticatingEventArgs_1.SshAuthenticatingEventArgs(sshAuthenticatingEventArgs_1.SshAuthenticationType.clientHostBased, {
                username: (_a = message.username) !== null && _a !== void 0 ? _a : '',
                publicKey: publicKey,
                clientHostname: message.clientHostname,
                clientUsername: message.clientUsername,
            });
        }
        else if (!message.hasSignature) {
            args = new sshAuthenticatingEventArgs_1.SshAuthenticatingEventArgs(sshAuthenticatingEventArgs_1.SshAuthenticationType.clientPublicKeyQuery, {
                username: (_b = message.username) !== null && _b !== void 0 ? _b : '',
                publicKey: publicKey,
            });
        }
        else {
            // Verify that the signature matches the public key.
            const signature = publicKeyAlg.readSignatureData(message.signature);
            const sessionId = this.session.sessionId;
            if (sessionId == null) {
                throw new Error('Session ID not initialized.');
            }
            const writer = new sshData_1.SshDataWriter(Buffer.alloc(sessionId.length + message.payloadWithoutSignature.length + 20));
            writer.writeBinary(sessionId);
            writer.write(message.payloadWithoutSignature);
            const signedData = writer.toBuffer();
            const verifier = publicKeyAlg.createVerifier(publicKey);
            const verified = await verifier.verify(signedData, signature);
            if (!verified) {
                await this.handleAuthenticationFailure('Public key authentication failed: invalid signature.', cancellation);
            }
            args = new sshAuthenticatingEventArgs_1.SshAuthenticatingEventArgs(sshAuthenticatingEventArgs_1.SshAuthenticationType.clientPublicKey, {
                username: (_c = message.username) !== null && _c !== void 0 ? _c : '',
                publicKey: publicKey,
            });
        }
        // Raise an Authenticating event that allows handlers to do additional verification
        // of the client's username and public key.
        await this.handleAuthenticating(args, cancellation);
    }
    async handlePasswordRequestMessage(message, cancellation) {
        var _a, _b;
        // Raise an Authenticating event that allows handlers to do verification
        // of the client's username and password.
        const args = new sshAuthenticatingEventArgs_1.SshAuthenticatingEventArgs(sshAuthenticatingEventArgs_1.SshAuthenticationType.clientPassword, {
            username: (_a = message.username) !== null && _a !== void 0 ? _a : '',
            password: (_b = message.password) !== null && _b !== void 0 ? _b : '',
        });
        await this.handleAuthenticating(args, cancellation);
    }
    async beginInteractiveAuthentication(message, cancellation) {
        // Raise an Authenticating event that allows the server to interactively prompt for
        // information from the client.
        const args = new sshAuthenticatingEventArgs_1.SshAuthenticatingEventArgs(sshAuthenticatingEventArgs_1.SshAuthenticationType.clientInteractive, {
            username: message.username,
        });
        await this.handleAuthenticating(args, cancellation);
    }
    async handleInfoRequestMessage(message, cancellation) {
        // Raise an Authenticating event that allows the client to respond to interactive prompts
        // and provide requested information to the server.
        const args = new sshAuthenticatingEventArgs_1.SshAuthenticatingEventArgs(sshAuthenticatingEventArgs_1.SshAuthenticationType.clientInteractive, {
            infoRequest: message,
        });
        await this.handleAuthenticating(args, cancellation);
    }
    async handleInfoResponseMessage(message, cancellation) {
        var _a;
        // Raise an Authenticating event that allows the server to process the client's responses
        // to interactive prompts, and request further info if necessary.
        const args = new sshAuthenticatingEventArgs_1.SshAuthenticatingEventArgs(sshAuthenticatingEventArgs_1.SshAuthenticationType.clientInteractive, {
            username: (_a = this.currentRequestMessage) === null || _a === void 0 ? void 0 : _a.username,
            infoResponse: message,
        });
        await this.handleAuthenticating(args, cancellation);
    }
    async handleAuthenticating(args, cancellation) {
        var _a;
        if (!this.currentRequestMessage) {
            throw new errors_1.SshConnectionError('No current authentication request.', transportMessages_1.SshDisconnectReason.protocolError);
        }
        args.cancellation = this.disposeCancellationSource.token;
        let authenticatedPrincipal = null;
        try {
            authenticatedPrincipal = await this.session.raiseAuthenticatingEvent(args);
        }
        catch (e) {
            if (!(e instanceof Error))
                throw e;
            this.trace(trace_1.TraceLevel.Error, trace_1.SshTraceEventIds.authenticationError, `Error while authenticating client: ${e.message}`, e);
            authenticatedPrincipal = null;
        }
        if (authenticatedPrincipal) {
            if (args.authenticationType === sshAuthenticatingEventArgs_1.SshAuthenticationType.clientPublicKeyQuery) {
                const publicKeyRequest = this.currentRequestMessage;
                const okMessage = new authenticationMessages_1.PublicKeyOKMessage();
                okMessage.keyAlgorithmName = publicKeyRequest.keyAlgorithmName;
                okMessage.publicKey = publicKeyRequest.publicKey;
                this.setCurrentRequest(null);
                await this.session.sendMessage(okMessage, cancellation);
            }
            else {
                this.session.principal = authenticatedPrincipal;
                const serviceName = this.currentRequestMessage.serviceName;
                if (serviceName) {
                    this.session.activateService(serviceName);
                }
                this.trace(trace_1.TraceLevel.Info, trace_1.SshTraceEventIds.sessionAuthenticated, `${sshAuthenticatingEventArgs_1.SshAuthenticationType[args.authenticationType]} authentication succeeded.`);
                this.setCurrentRequest(null);
                await this.session.sendMessage(new authenticationMessages_1.AuthenticationSuccessMessage(), cancellation);
                (_a = this.session) === null || _a === void 0 ? void 0 : _a.handleClientAuthenticated();
            }
        }
        else if (args.authenticationType === sshAuthenticatingEventArgs_1.SshAuthenticationType.clientInteractive &&
            !this.session.isClientSession &&
            args.infoRequest) {
            // Server authenticating event-handler supplied an info request.
            await this.sendMessage(args.infoRequest, cancellation);
        }
        else if (args.authenticationType === sshAuthenticatingEventArgs_1.SshAuthenticationType.clientInteractive &&
            this.session.isClientSession &&
            args.infoResponse) {
            // Client authenticating event-handler supplied an info response.
            await this.sendMessage(args.infoResponse, cancellation);
        }
        else {
            this.setCurrentRequest(null);
            await this.handleAuthenticationFailure(`${sshAuthenticatingEventArgs_1.SshAuthenticationType[args.authenticationType]} authentication failed.`);
        }
    }
    async handleAuthenticationFailure(message, cancellation) {
        this.authenticationFailureCount++;
        this.trace(trace_1.TraceLevel.Warning, trace_1.SshTraceEventIds.clientAuthenticationFailed, message);
        const failureMessage = new authenticationMessages_1.AuthenticationFailureMessage();
        failureMessage.methodNames = this.session.config.authenticationMethods;
        await this.session.sendMessage(failureMessage, cancellation);
        // Allow trying again with another authentication method. But prevent unlimited tries.
        if (this.authenticationFailureCount >= this.session.config.maxClientAuthenticationAttempts) {
            await this.session.close(transportMessages_1.SshDisconnectReason.noMoreAuthMethodsAvailable, 'Authentication failed.');
        }
    }
    async authenticateClient(credentials, cancellation) {
        var _a, _b, _c, _d;
        this.clientAuthenticationMethods = new queue_1.Queue();
        const configuredMethods = this.session.config.authenticationMethods;
        if (configuredMethods.includes("publickey" /* AuthenticationMethod.publicKey */)) {
            for (const publicKey of (_a = credentials.publicKeys) !== null && _a !== void 0 ? _a : []) {
                if (!publicKey)
                    continue;
                const username = (_b = credentials.username) !== null && _b !== void 0 ? _b : '';
                let privateKey = publicKey;
                const privateKeyProvider = credentials.privateKeyProvider;
                this.clientAuthenticationMethods.enqueue({
                    method: "publickey" /* AuthenticationMethod.publicKey */,
                    handler: async (cancellation2) => {
                        if (!privateKey.hasPrivateKey) {
                            if (privateKeyProvider == null) {
                                throw new Error('A private key provider is required.');
                            }
                            privateKey = await privateKeyProvider(publicKey, cancellation2 !== null && cancellation2 !== void 0 ? cancellation2 : vscode_jsonrpc_1.CancellationToken.None);
                        }
                        if (privateKey) {
                            await this.requestPublicKeyAuthentication(username, privateKey, cancellation2);
                        }
                        else {
                            await this.session.close(transportMessages_1.SshDisconnectReason.authCancelledByUser);
                        }
                    },
                });
            }
        }
        if (configuredMethods.includes("password" /* AuthenticationMethod.password */)) {
            const passwordCredentialProvider = credentials.passwordProvider;
            if (passwordCredentialProvider) {
                this.clientAuthenticationMethods.enqueue({
                    method: "password" /* AuthenticationMethod.password */,
                    handler: async (cancellation2) => {
                        var _a;
                        const passwordCredentialPromise = passwordCredentialProvider(cancellation2 !== null && cancellation2 !== void 0 ? cancellation2 : vscode_jsonrpc_1.CancellationToken.None);
                        const passwordCredential = passwordCredentialPromise
                            ? await passwordCredentialPromise
                            : null;
                        if (passwordCredential) {
                            await this.requestPasswordAuthentication((_a = passwordCredential[0]) !== null && _a !== void 0 ? _a : '', passwordCredential[1], cancellation2);
                        }
                        else {
                            await this.session.close(transportMessages_1.SshDisconnectReason.authCancelledByUser);
                        }
                    },
                });
            }
            else if (credentials.password) {
                const username = (_c = credentials.username) !== null && _c !== void 0 ? _c : '';
                const password = credentials.password;
                this.clientAuthenticationMethods.enqueue({
                    method: "password" /* AuthenticationMethod.password */,
                    handler: async (cancellation2) => {
                        await this.requestPasswordAuthentication(username, password, cancellation2);
                    },
                });
            }
        }
        // Only add None or Interactive methods if no client credentials were supplied.
        if (this.clientAuthenticationMethods.size === 0) {
            const username = (_d = credentials.username) !== null && _d !== void 0 ? _d : '';
            if (configuredMethods.includes("none" /* AuthenticationMethod.none */)) {
                this.clientAuthenticationMethods.enqueue({
                    method: "none" /* AuthenticationMethod.none */,
                    handler: async (cancellation2) => {
                        await this.requestUsernameAuthentication(username, cancellation2);
                    },
                });
            }
            if (configuredMethods.includes("keyboard-interactive" /* AuthenticationMethod.keyboardInteractive */)) {
                this.clientAuthenticationMethods.enqueue({
                    method: "keyboard-interactive" /* AuthenticationMethod.keyboardInteractive */,
                    handler: async (cancellation2) => {
                        await this.requestInteractiveAuthentication(username, cancellation2);
                    },
                });
            }
            if (this.clientAuthenticationMethods.size === 0) {
                throw new Error('Could not prepare request for authentication method(s): ' +
                    configuredMethods.join(', ') +
                    '. Supply client credentials or enable none or interactive authentication methods.');
            }
        }
        // Auth request messages all include a request the for the server to activate the connection
        // service . Go ahead and activate it on the client side too; if authentication fails then
        // a following channel open request will fail anyway.
        this.session.activateService(connectionService_1.ConnectionService);
        const firstAuthMethod = this.clientAuthenticationMethods.dequeue();
        await firstAuthMethod.handler(cancellation);
    }
    async requestUsernameAuthentication(username, cancellation) {
        const authMessage = new authenticationMessages_1.AuthenticationRequestMessage();
        authMessage.serviceName = connectionService_1.ConnectionService.serviceName;
        authMessage.methodName = "none" /* AuthenticationMethod.none */;
        authMessage.username = username;
        this.setCurrentRequest(authMessage);
        await this.session.sendMessage(authMessage, cancellation);
    }
    async requestPublicKeyAuthentication(username, key, cancellation) {
        const algorithm = this.session.config.publicKeyAlgorithms.find((a) => (a === null || a === void 0 ? void 0 : a.keyAlgorithmName) === key.keyAlgorithmName);
        if (!algorithm) {
            throw new Error(`Public key algorithm '${key.keyAlgorithmName}' is not in session config.`);
        }
        const authMessage = new authenticationMessages_1.PublicKeyRequestMessage();
        authMessage.serviceName = connectionService_1.ConnectionService.serviceName;
        authMessage.username = username;
        authMessage.keyAlgorithmName = algorithm.name;
        authMessage.publicKey = (await key.getPublicKeyBytes(algorithm.name));
        authMessage.signature = await this.createAuthenticationSignature(authMessage, algorithm, key);
        this.setCurrentRequest(authMessage);
        await this.session.sendMessage(authMessage, cancellation);
    }
    async requestPasswordAuthentication(username, password, cancellation) {
        const authMessage = new authenticationMessages_1.PasswordRequestMessage();
        authMessage.serviceName = connectionService_1.ConnectionService.serviceName;
        authMessage.username = username;
        authMessage.password = password;
        this.setCurrentRequest(authMessage);
        await this.session.sendMessage(authMessage, cancellation);
    }
    async requestInteractiveAuthentication(username, cancellation) {
        const authMessage = new authenticationMessages_1.AuthenticationRequestMessage();
        authMessage.serviceName = connectionService_1.ConnectionService.serviceName;
        authMessage.methodName = "keyboard-interactive" /* AuthenticationMethod.keyboardInteractive */;
        authMessage.username = username;
        this.setCurrentRequest(authMessage);
        await this.session.sendMessage(authMessage, cancellation);
    }
    async handleFailureMessage(message) {
        var _a, _b;
        this.setCurrentRequest(null);
        while ((_a = this.clientAuthenticationMethods) === null || _a === void 0 ? void 0 : _a.size) {
            const nextAuthMethod = this.clientAuthenticationMethods.dequeue();
            // Skip client auth methods that the server did not suggest.
            if ((_b = message.methodNames) === null || _b === void 0 ? void 0 : _b.includes(nextAuthMethod.method)) {
                await nextAuthMethod.handler(this.disposeCancellationSource.token);
                return;
            }
        }
        this.session.onAuthenticationComplete(false);
    }
    handleSuccessMessage(message) {
        this.setCurrentRequest(null);
        this.session.onAuthenticationComplete(true);
    }
    async createAuthenticationSignature(requestMessage, algorithm, key) {
        const sessionId = this.session.sessionId;
        if (sessionId == null) {
            throw new Error('Session ID not initialized.');
        }
        const writer = new sshData_1.SshDataWriter(Buffer.alloc(requestMessage.publicKey.length + (requestMessage.username || '').length + 400));
        writer.writeBinary(sessionId);
        writer.writeByte(requestMessage.messageType);
        writer.writeString(requestMessage.username || '', 'utf8');
        writer.writeString(requestMessage.serviceName || '', 'ascii');
        writer.writeString("publickey" /* AuthenticationMethod.publicKey */, 'ascii');
        writer.writeBoolean(true);
        writer.writeString(requestMessage.keyAlgorithmName, 'ascii');
        writer.writeBinary(requestMessage.publicKey);
        const signer = algorithm.createSigner(key);
        const signature = await signer.sign(writer.toBuffer());
        return algorithm.createSignatureData(signature);
    }
    dispose() {
        try {
            this.disposeCancellationSource.cancel();
            this.disposeCancellationSource.dispose();
        }
        catch (_a) { }
        super.dispose();
    }
};
AuthenticationService.serviceName = 'ssh-userauth';
AuthenticationService = AuthenticationService_1 = __decorate([
    (0, serviceActivation_1.serviceActivation)({ serviceRequest: AuthenticationService_1.serviceName })
], AuthenticationService);
exports.AuthenticationService = AuthenticationService;
//# sourceMappingURL=https://github.com/VSCodium/sourcemaps/releases/download/stable-824c4c46a288b839f13b24022655329c2aeb9f81/node_modules-@microsoft-dev-tunnels-ssh-services-authenticationService.js.map