"use strict";
//
//  Copyright (c) Microsoft Corporation. All rights reserved.
//
Object.defineProperty(exports, "__esModule", { value: true });
exports.NodeECDsa = void 0;
const crypto = require("crypto");
const buffer_1 = require("buffer");
const publicKeyAlgorithm_1 = require("../publicKeyAlgorithm");
const ecdsaCurves_1 = require("../ecdsaCurves");
const bigInt_1 = require("../../io/bigInt");
const derData_1 = require("../../io/derData");
const sshData_1 = require("../../io/sshData");
const keyFormatters_1 = require("./keyFormatters");
const nodeVersionParts = process.versions.node.split('.').map((v) => parseInt(v, 10));
const nodeGenerateKeyPairSupport = nodeVersionParts[0] > 10 || (nodeVersionParts[0] === 10 && nodeVersionParts[1] >= 12);
const nodeKeyObjectSupport = nodeVersionParts[0] > 11 || (nodeVersionParts[0] === 11 && nodeVersionParts[1] >= 6);
class NodeECDsaKeyPair {
    /**
     * Constructs a new ECDSA key pair object.
     *
     * @param algorithmName Key pair algorithm name. If unspecified, the key pair object must be
     * initialized before use via `importParameters()`.
     */
    constructor(algorithmName) {
        this.comment = null;
        if (algorithmName) {
            this.algorithmName = algorithmName;
        }
    }
    get hasPublicKey() {
        return !!this.publicKey;
    }
    get hasPrivateKey() {
        return !!this.privateKey;
    }
    get keyAlgorithmName() {
        return this.algorithmName;
    }
    get algorithmName() {
        return this.algorithm;
    }
    set algorithmName(value) {
        const curveName = value.split('-')[2];
        this.curve = ecdsaCurves_1.curves.find((c) => c.name === curveName);
        if (!this.curve) {
            throw new Error('Invalid or unsupported ECDSA algorithm: ' + value);
        }
        this.algorithm = value;
    }
    generate() {
        if (nodeGenerateKeyPairSupport && nodeKeyObjectSupport) {
            return this.generateNodeKeyPairObjects();
        }
        else if (nodeGenerateKeyPairSupport) {
            return this.generateNodeKeyPairBuffers();
        }
        else {
            return this.generateExternalKeyPair();
        }
    }
    async generateNodeKeyPairObjects() {
        [this.publicKey, this.privateKey] = await new Promise((resolve, reject) => {
            const keyGenParams = {
                namedCurve: this.curve.shortName,
            };
            try {
                crypto.generateKeyPair('ec', keyGenParams, (err, publicKey, privateKey) => {
                    if (err) {
                        reject(err);
                    }
                    else {
                        resolve([publicKey, privateKey]);
                    }
                });
            }
            catch (err) {
                reject(err);
            }
        });
    }
    async generateNodeKeyPairBuffers() {
        [this.publicKey, this.privateKey] = await new Promise((resolve, reject) => {
            const keyGenParams = {
                namedCurve: this.curve.shortName,
                publicKeyEncoding: { type: 'spki', format: 'pem' },
                privateKeyEncoding: {
                    type: 'sec1',
                    format: 'pem',
                    cipher: undefined,
                    passphrase: undefined,
                },
            };
            try {
                crypto.generateKeyPair('ec', keyGenParams, (err, publicKey, privateKey) => {
                    if (err) {
                        reject(err);
                    }
                    else {
                        resolve([publicKey, privateKey]);
                    }
                });
            }
            catch (err) {
                reject(err);
            }
        });
    }
    async generateExternalKeyPair() {
        throw new Error('This version of node does not support generating key pairs. Use node >= 10.12.');
    }
    async setPublicKeyBytes(keyBytes, algorithmName) {
        if (!keyBytes) {
            throw new TypeError('Buffer is required.');
        }
        // Read public key in SSH format.
        const reader = new sshData_1.SshDataReader(keyBytes);
        const readAlgorithmName = reader.readString('ascii');
        this.algorithmName = algorithmName || readAlgorithmName;
        const curveName = reader.readString('ascii');
        this.algorithmName = `ecdsa-sha2-${curveName}`;
        const xy = reader.readBinary();
        const x = bigInt_1.BigInt.fromBytes(xy.slice(1, 1 + (xy.length - 1) / 2), { unsigned: true });
        const y = bigInt_1.BigInt.fromBytes(xy.slice(1 + (xy.length - 1) / 2), { unsigned: true });
        const derKeyBytes = keyFormatters_1.Sec1KeyFormatter.formatECPublic({
            curve: { name: this.curve.name, oid: this.curve.oid },
            x,
            y,
        });
        if (nodeKeyObjectSupport) {
            this.publicKey = crypto.createPublicKey({
                key: derKeyBytes,
                type: 'spki',
                format: 'der',
            });
        }
        else {
            this.publicKey = (0, keyFormatters_1.formatPem)(derKeyBytes, 'PUBLIC KEY');
        }
    }
    async getPublicKeyBytes(algorithmName) {
        if (!this.publicKey) {
            return null;
        }
        let derKeyBytes;
        if (typeof this.publicKey === 'string') {
            derKeyBytes = (0, keyFormatters_1.parsePem)(this.publicKey);
        }
        else {
            derKeyBytes = this.publicKey.export({
                type: 'spki',
                format: 'der',
            });
        }
        const ec = keyFormatters_1.Sec1KeyFormatter.parseECPublic(derKeyBytes);
        // Write public key in SSH format.
        algorithmName = algorithmName || this.algorithmName || this.keyAlgorithmName;
        const keyWriter = new sshData_1.SshDataWriter(buffer_1.Buffer.alloc(512));
        keyWriter.writeString(algorithmName, 'ascii');
        keyWriter.writeString(this.curve.name, 'ascii');
        const keySizeInBytes = Math.ceil(this.curve.keySize / 8);
        const xBytes = ec.x.toBytes({ unsigned: true, length: keySizeInBytes });
        const yBytes = ec.y.toBytes({ unsigned: true, length: keySizeInBytes });
        keyWriter.writeUInt32(1 + xBytes.length + yBytes.length);
        keyWriter.writeByte(4); // Indicates uncompressed curve format
        keyWriter.write(xBytes);
        keyWriter.write(yBytes);
        const keyBytes = keyWriter.toBuffer();
        return keyBytes;
    }
    async importParameters(parameters) {
        if (!parameters.curve)
            throw new TypeError('A curve is required.');
        let curve;
        if (parameters.curve.oid) {
            curve = ecdsaCurves_1.curves.find((c) => c.oid === parameters.curve.oid);
            if (!curve) {
                throw new Error(`Unsupported curve OID: ${parameters.curve.oid}`);
            }
        }
        else if (parameters.curve.name) {
            curve = ecdsaCurves_1.curves.find((c) => c.name === parameters.curve.name);
            if (!curve) {
                throw new Error(`Unsupported curve: ${parameters.curve.name}`);
            }
        }
        else {
            throw new TypeError('A curve OID or name is required.');
        }
        this.algorithmName = 'ecdsa-sha2-' + curve.name;
        const publicKeyBytes = keyFormatters_1.Sec1KeyFormatter.formatECPublic(parameters);
        if (nodeKeyObjectSupport) {
            this.publicKey = crypto.createPublicKey({
                key: publicKeyBytes,
                type: 'spki',
                format: 'der',
            });
        }
        else {
            this.publicKey = (0, keyFormatters_1.formatPem)(publicKeyBytes, 'EC PUBLIC KEY');
        }
        if (parameters.d) {
            const privateKeyBytes = keyFormatters_1.Sec1KeyFormatter.formatECPrivate(parameters);
            if (nodeKeyObjectSupport) {
                this.privateKey = crypto.createPrivateKey({
                    key: privateKeyBytes,
                    type: 'sec1',
                    format: 'der',
                });
            }
            else {
                this.privateKey = (0, keyFormatters_1.formatPem)(privateKeyBytes, 'EC PRIVATE KEY');
            }
        }
        else {
            this.privateKey = undefined;
        }
    }
    async exportParameters() {
        var _a, _b;
        if (!this.publicKey) {
            throw new Error('Key is not present.');
        }
        let derKeyBytes;
        if (typeof this.publicKey === 'string') {
            derKeyBytes = (0, keyFormatters_1.parsePem)((_a = this.privateKey) !== null && _a !== void 0 ? _a : this.publicKey);
        }
        else {
            derKeyBytes = ((_b = this.privateKey) !== null && _b !== void 0 ? _b : this.publicKey).export({
                type: this.privateKey ? 'sec1' : 'spki',
                format: 'der',
            });
        }
        return this.privateKey
            ? keyFormatters_1.Sec1KeyFormatter.parseECPrivate(derKeyBytes)
            : keyFormatters_1.Sec1KeyFormatter.parseECPublic(derKeyBytes);
    }
    dispose() { }
}
class NodeECDsa extends publicKeyAlgorithm_1.PublicKeyAlgorithm {
    constructor(name, hashAlgorithmName) {
        super(name, name, // The key algorithm name is the same (unlike RSA).
        hashAlgorithmName);
    }
    createKeyPair() {
        return new NodeECDsaKeyPair(this.name);
    }
    async generateKeyPair() {
        const ecdsaKey = new NodeECDsaKeyPair(this.name);
        await ecdsaKey.generate();
        return ecdsaKey;
    }
    createSigner(keyPair) {
        if (!(keyPair instanceof NodeECDsaKeyPair)) {
            throw new TypeError('ECDSA key pair object expected.');
        }
        return new NodeECDsaSignerVerifier(keyPair, NodeECDsa.convertHashAlgorithmName(this.hashAlgorithmName));
    }
    createVerifier(keyPair) {
        if (!(keyPair instanceof NodeECDsaKeyPair)) {
            throw new TypeError('ECDSA key pair object expected.');
        }
        return new NodeECDsaSignerVerifier(keyPair, NodeECDsa.convertHashAlgorithmName(this.hashAlgorithmName));
    }
    static convertHashAlgorithmName(hashAlgorithmName) {
        return hashAlgorithmName.replace('SHA2-', 'SHA');
    }
    /* @internal */
    static getSignatureLength(keySizeInBits) {
        // The signature is double the key size, but formatted as 2 bigints.
        // To each bigint add 4 for the length and 1 for a leading zero.
        const keySizeInBytes = Math.ceil(keySizeInBits / 8);
        return (4 + 1 + keySizeInBytes) * 2;
    }
}
exports.NodeECDsa = NodeECDsa;
NodeECDsa.ecdsaSha2Nistp256 = 'ecdsa-sha2-nistp256';
NodeECDsa.ecdsaSha2Nistp384 = 'ecdsa-sha2-nistp384';
NodeECDsa.ecdsaSha2Nistp521 = 'ecdsa-sha2-nistp521';
NodeECDsa.curves = ecdsaCurves_1.curves;
NodeECDsa.KeyPair = NodeECDsaKeyPair;
class NodeECDsaSignerVerifier {
    constructor(keyPair, hashAlgorithmName) {
        this.keyPair = keyPair;
        this.hashAlgorithmName = hashAlgorithmName;
    }
    get digestLength() {
        const curve = this.keyPair.curve;
        if (!curve) {
            return 0;
        }
        else {
            return NodeECDsa.getSignatureLength(curve.keySize);
        }
    }
    async sign(data) {
        if (!this.keyPair.privateKey) {
            throw new Error('Private key not set.');
        }
        const signer = crypto.createSign(this.hashAlgorithmName);
        signer.update(data);
        let signature = signer.sign(this.keyPair.privateKey);
        // Reformat the signature as two mpint big-ints per RFC 5656 / RFC 4251.
        const signatureReader = new derData_1.DerReader(signature);
        const x = signatureReader.readInteger();
        const y = signatureReader.readInteger();
        const signatureWriter = new sshData_1.SshDataWriter(buffer_1.Buffer.alloc(this.digestLength));
        signatureWriter.writeBigInt(x);
        signatureWriter.writeBigInt(y);
        signature = signatureWriter.toBuffer();
        return signature;
    }
    async verify(data, signature) {
        if (!this.keyPair.publicKey) {
            throw new Error('Public key not set.');
        }
        // Reformat the signature integer bytes as required by node.
        const signatureReader = new sshData_1.SshDataReader(signature);
        const x = signatureReader.readBigInt();
        const y = signatureReader.readBigInt();
        const signatureWriter = new derData_1.DerWriter(buffer_1.Buffer.alloc(signature.length));
        signatureWriter.writeInteger(x);
        signatureWriter.writeInteger(y);
        signature = signatureWriter.toBuffer();
        const verifier = crypto.createVerify(this.hashAlgorithmName);
        verifier.update(data);
        const result = verifier.verify(this.keyPair.publicKey, signature);
        return result;
    }
    dispose() { }
}
//# sourceMappingURL=https://github.com/VSCodium/sourcemaps/releases/download/stable-4c0b0c6cc561d2d3636d1ec250935431876ce4dc/node_modules-@microsoft-dev-tunnels-ssh-algorithms-node-nodeECDsa.js.map