UNPKG

@microsoft/dev-tunnels-ssh

Version:
332 lines 13.2 kB
"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=nodeECDsa.js.map