"use strict";
//
//  Copyright (c) Microsoft Corporation. All rights reserved.
//
Object.defineProperty(exports, "__esModule", { value: true });
exports.NodeEncryption = void 0;
const crypto = require("crypto");
const buffer_1 = require("buffer");
const encryptionAlgorithm_1 = require("../encryptionAlgorithm");
class NodeEncryption extends encryptionAlgorithm_1.EncryptionAlgorithm {
    constructor(name, algorithmName, cipherMode, keySizeInBits) {
        super(name);
        this.algorithmName = algorithmName;
        this.cipherMode = cipherMode;
        this.keySizeInBits = keySizeInBits;
        if (algorithmName !== 'AES') {
            throw new Error(`Unsupported encryption algorithm: ${algorithmName}`);
        }
        this.blockSizeInBits = NodeEncryption.getBlockSize(algorithmName);
    }
    get keyLength() {
        return this.keySizeInBits / 8;
    }
    get blockLength() {
        return this.blockSizeInBits / 8;
    }
    async createCipher(isEncryption, key, iv) {
        let cipher;
        if (this.cipherMode === 'CTR' || this.cipherMode === 'CBC') {
            cipher = new NodeAesCipher(isEncryption, this.keySizeInBits, this.blockSizeInBits, key, iv, this.cipherMode);
        }
        else if (this.cipherMode === 'GCM') {
            cipher = new NodeAesGcmCipher(isEncryption, this.keySizeInBits, this.blockSizeInBits, key, iv);
        }
        else {
            throw new Error(`Unsupported cipher mode: ${this.cipherMode}`);
        }
        return cipher;
    }
    static getBlockSize(algorithmName) {
        if (algorithmName === 'AES') {
            return 128;
        }
        else {
            throw new Error(`Unsupported encryption algorithm: ${algorithmName}`);
        }
    }
}
exports.NodeEncryption = NodeEncryption;
class NodeAesCipher {
    constructor(isEncryption, keySizeInBits, blockSizeInBits, key, iv, cipherMode) {
        this.isEncryption = isEncryption;
        this.keySizeInBits = keySizeInBits;
        this.blockSizeInBits = blockSizeInBits;
        const nodeAlgorithm = `AES-${this.keySizeInBits}-${cipherMode}`;
        this.cipher = this.isEncryption
            ? crypto.createCipheriv(nodeAlgorithm, key, iv)
            : crypto.createDecipheriv(nodeAlgorithm, key, iv);
        this.cipher.setAutoPadding(false);
    }
    get blockLength() {
        return this.blockSizeInBits / 8;
    }
    transform(data) {
        const result = this.cipher.update(data);
        if (result.length !== data.length) {
            const message = 'Result from encrypt/decrypt has invalid length ' +
                `${result.length}, expected ${data.length}.`;
            throw new Error(message);
        }
        return Promise.resolve(result);
    }
    dispose() { }
}
class NodeAesGcmCipher {
    constructor(isEncryption, keySizeInBits, blockSizeInBits, key, iv) {
        this.isEncryption = isEncryption;
        this.keySizeInBits = keySizeInBits;
        this.blockSizeInBits = blockSizeInBits;
        this.tag = null;
        this.algorithmName = `aes-${this.keySizeInBits}-gcm`;
        this.key = buffer_1.Buffer.alloc(key.length);
        key.copy(this.key);
        // Ininitialize the nonce to the first 12 bytes of the IV. It will be incremented by each op.
        this.nonce = buffer_1.Buffer.alloc(12);
        iv.copy(this.nonce, 0, 0, 12);
        this.associatedData = buffer_1.Buffer.alloc(4);
    }
    get blockLength() {
        return this.blockSizeInBits / 8;
    }
    get digestLength() {
        return 16;
    }
    get authenticatedEncryption() {
        return true;
    }
    transform(data) {
        if (data.length % this.blockLength !== 0) {
            const message = 'Encrypt/decrypt input has invalid length ' +
                `${data.length}, not a multiple of block size ${this.blockLength}.`;
            throw new Error(message);
        }
        const cipher = this.isEncryption
            ? crypto.createCipheriv(this.algorithmName, this.key, this.nonce)
            : crypto.createDecipheriv(this.algorithmName, this.key, this.nonce);
        // Associated data is the 32-bit packet length.
        const packetLength = data.length;
        this.associatedData[0] = packetLength >>> 24;
        this.associatedData[1] = packetLength >>> 16;
        this.associatedData[2] = packetLength >>> 8;
        this.associatedData[3] = packetLength;
        cipher.setAAD(this.associatedData);
        if (!this.isEncryption) {
            if (!this.tag) {
                throw new Error('AES-GCM tag was not set before decrypting.');
            }
            cipher.setAuthTag(this.tag);
        }
        const result = cipher.update(data);
        if (result.length !== data.length) {
            const message = 'Result from encrypt/decrypt has invalid length ' +
                `${result.length}, expected ${data.length}.`;
            throw new Error(message);
        }
        cipher.final();
        if (this.isEncryption) {
            this.tag = cipher.getAuthTag();
        }
        else {
            this.tag = null;
        }
        // Increment the counter (last 8 bytes of the nonce) as a big-endian integer.
        // First increment the last byte, and if it reaches 0 then increment the
        // next-to-last byte, and so on.
        let k = 12;
        while (--k >= 4) {
            this.nonce[k]++;
            if (this.nonce[k] !== 0) {
                break;
            }
        }
        return Promise.resolve(result);
    }
    async sign(data) {
        if (!this.tag) {
            throw new Error('AES-GCM tag was not obtained by encrypting.');
        }
        return this.tag;
    }
    async verify(data, signature) {
        if (signature.length !== this.digestLength) {
            throw new Error('Incorrect AES-GCM tag length.');
        }
        this.tag = signature;
        return true;
    }
    dispose() { }
}
//# sourceMappingURL=https://github.com/VSCodium/sourcemaps/releases/download/stable-4c0b0c6cc561d2d3636d1ec250935431876ce4dc/node_modules-@microsoft-dev-tunnels-ssh-algorithms-node-nodeEncryption.js.map