"use strict";
//
//  Copyright (c) Microsoft Corporation. All rights reserved.
//
Object.defineProperty(exports, "__esModule", { value: true });
exports.WebECDsa = void 0;
const buffer_1 = require("buffer");
const publicKeyAlgorithm_1 = require("../publicKeyAlgorithm");
const sshData_1 = require("../../io/sshData");
const ecdsaCurves_1 = require("../ecdsaCurves");
const bigInt_1 = require("../../io/bigInt");
const jsonWebKeyFormatter_1 = require("./jsonWebKeyFormatter");
class WebECDsaKeyPair {
    /**
     * 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;
    }
    async generate() {
        try {
            const keyGenParams = {
                name: 'ECDSA',
                namedCurve: this.curve.shortName,
            };
            const keyPair = (await crypto.subtle.generateKey(keyGenParams, true, ['sign', 'verify']));
            this.publicKey = keyPair.publicKey;
            this.privateKey = keyPair.privateKey;
        }
        catch (e) {
            throw new Error('Failed to generate RSA key pair: ' + e);
        }
    }
    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');
        if (curveName !== this.curve.name) {
            throw new Error('EC curve name does not match.');
        }
        // X and Y parameters are equal length, after a one-byte header.
        const key = reader.readBinary();
        const n = Math.ceil(this.curve.keySize / 8);
        const x = bigInt_1.BigInt.fromBytes(key.slice(1, 1 + n), { unsigned: true });
        const y = bigInt_1.BigInt.fromBytes(key.slice(1 + n, key.length), { unsigned: true });
        const jwk = jsonWebKeyFormatter_1.JsonWebKeyFormatter.formatEC({
            curve: { name: this.curve.shortName, oid: this.curve.oid },
            x,
            y,
        });
        try {
            const importParams = {
                name: 'ECDSA',
                namedCurve: this.curve.shortName,
            };
            this.publicKey = await crypto.subtle.importKey('jwk', jwk, importParams, true, ['verify']);
        }
        catch (e) {
            throw new Error('Failed to import EC public key: ' + e);
        }
    }
    async getPublicKeyBytes(algorithmName) {
        if (!this.publicKey) {
            return null;
        }
        // Export public key in JWK format.
        let jwk;
        try {
            jwk = await crypto.subtle.exportKey('jwk', this.publicKey);
        }
        catch (e) {
            throw new Error('Failed to export ECDSA public key: ' + e);
        }
        const x = buffer_1.Buffer.from(jwk.x, 'base64');
        const y = buffer_1.Buffer.from(jwk.y, 'base64');
        const n = Math.ceil(this.curve.keySize / 8);
        if (x.length !== n || y.length !== n) {
            throw new Error('Unexpected key length.');
        }
        // Write public key in SSH format.
        algorithmName = algorithmName || this.algorithmName || this.keyAlgorithmName;
        const keyBuffer = buffer_1.Buffer.alloc(algorithmName.length + x.length + y.length + 10);
        const keyWriter = new sshData_1.SshDataWriter(keyBuffer);
        keyWriter.writeString(algorithmName, 'ascii');
        keyWriter.writeString(this.curve.name, 'ascii');
        keyWriter.writeUInt32(1 + x.length + y.length);
        keyWriter.writeByte(4); // Indicates uncompressed curve format
        keyWriter.write(x);
        keyWriter.write(y);
        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 importParams = {
            name: 'ECDSA',
            namedCurve: this.curve.shortName,
        };
        const jwk = jsonWebKeyFormatter_1.JsonWebKeyFormatter.formatEC(parameters);
        jwk.crv = this.curve.shortName;
        try {
            if (jwk.d) {
                this.privateKey = await crypto.subtle.importKey('jwk', jwk, importParams, true, [
                    'sign',
                ]);
                jwk.d = undefined;
            }
            else {
                this.privateKey = undefined;
            }
            this.publicKey = await crypto.subtle.importKey('jwk', jwk, importParams, true, ['verify']);
        }
        catch (e) {
            throw new Error('Failed to import ECDSA key pair: ' + e);
        }
    }
    async exportParameters() {
        var _a;
        const exportKey = (_a = this.privateKey) !== null && _a !== void 0 ? _a : this.publicKey;
        if (!exportKey) {
            throw new Error('Key not present.');
        }
        let jwk;
        try {
            jwk = await crypto.subtle.exportKey('jwk', exportKey);
        }
        catch (e) {
            throw new Error('Failed to export ECDSA key pair: ' + e);
        }
        const parameters = jsonWebKeyFormatter_1.JsonWebKeyFormatter.parseEC(jwk);
        parameters.curve = { name: this.curve.name, oid: this.curve.oid };
        return parameters;
    }
    dispose() { }
}
class WebECDsa extends publicKeyAlgorithm_1.PublicKeyAlgorithm {
    constructor(name, hashAlgorithmName) {
        super(name, name, hashAlgorithmName);
    }
    createKeyPair() {
        return new WebECDsaKeyPair(this.name);
    }
    async generateKeyPair() {
        const ecdsaKey = new WebECDsaKeyPair(this.name);
        await ecdsaKey.generate();
        return ecdsaKey;
    }
    createSigner(keyPair) {
        if (!(keyPair instanceof WebECDsaKeyPair)) {
            throw new TypeError('ECDSA key pair object expected.');
        }
        const hashAlgorithm = WebECDsa.convertHashAlgorithmName(this.hashAlgorithmName);
        return new WebECDsaSignerVerifier(keyPair, hashAlgorithm);
    }
    createVerifier(keyPair) {
        if (!(keyPair instanceof WebECDsaKeyPair)) {
            throw new TypeError('ECDSA key pair object expected.');
        }
        const hashAlgorithm = WebECDsa.convertHashAlgorithmName(this.hashAlgorithmName);
        return new WebECDsaSignerVerifier(keyPair, hashAlgorithm);
    }
    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.WebECDsa = WebECDsa;
WebECDsa.ecdsaSha2Nistp256 = 'ecdsa-sha2-nistp256';
WebECDsa.ecdsaSha2Nistp384 = 'ecdsa-sha2-nistp384';
WebECDsa.ecdsaSha2Nistp521 = 'ecdsa-sha2-nistp521';
WebECDsa.curves = ecdsaCurves_1.curves;
WebECDsa.KeyPair = WebECDsaKeyPair;
class WebECDsaSignerVerifier {
    constructor(keyPair, hashAlgorithm) {
        this.keyPair = keyPair;
        this.hashAlgorithm = hashAlgorithm;
    }
    get digestLength() {
        const curve = this.keyPair.curve;
        if (!curve) {
            return 0;
        }
        else {
            return WebECDsa.getSignatureLength(curve.keySize);
        }
    }
    async sign(data) {
        if (!this.keyPair.privateKey) {
            throw new Error('Private key not set.');
        }
        let signature = buffer_1.Buffer.from(await crypto.subtle.sign({ name: 'ECDSA', hash: { name: this.hashAlgorithm } }, this.keyPair.privateKey, data));
        const keySizeInBytes = Math.ceil(this.keyPair.curve.keySize / 8);
        if (signature.length !== 2 * keySizeInBytes) {
            throw new Error(`Unexpected signature length: ${signature.length}`);
        }
        // Reformat the signature as two mpint big-ints per RFC 5656 / RFC 4251.
        const x = bigInt_1.BigInt.fromBytes(signature.slice(0, keySizeInBytes), { unsigned: true });
        const y = bigInt_1.BigInt.fromBytes(signature.slice(keySizeInBytes, signature.length), {
            unsigned: true,
        });
        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 the web crypto API.
        const signatureReader = new sshData_1.SshDataReader(signature);
        const x = signatureReader.readBigInt();
        const y = signatureReader.readBigInt();
        const keySizeInBytes = Math.ceil(this.keyPair.curve.keySize / 8);
        signature = buffer_1.Buffer.alloc(2 * keySizeInBytes);
        x.toBytes({ unsigned: true, length: keySizeInBytes }).copy(signature, 0);
        y.toBytes({ unsigned: true, length: keySizeInBytes }).copy(signature, keySizeInBytes);
        const result = await crypto.subtle.verify({ name: 'ECDSA', hash: { name: this.hashAlgorithm } }, this.keyPair.publicKey, signature, data);
        return result;
    }
    dispose() { }
}
//# sourceMappingURL=https://main.vscode-cdn.net/sourcemaps/6a44c352bd24569c417e530095901b649960f9f8/node_modules/@microsoft/dev-tunnels-ssh/algorithms/web/webECDsa.js.map