UNPKG

@microsoft/dev-tunnels-ssh

Version:

SSH library for Dev Tunnels

279 lines 11.4 kB
"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 integer bytes as required by SSH. 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.writeBinary(x.toBytes({ unsigned: true, length: keySizeInBytes + 1 })); signatureWriter.writeBinary(y.toBytes({ unsigned: true, length: keySizeInBytes + 1 })); 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=webECDsa.js.map