UNPKG

@microsoft/dev-tunnels-ssh

Version:
270 lines 10.8 kB
"use strict"; // // Copyright (c) Microsoft Corporation. All rights reserved. // Object.defineProperty(exports, "__esModule", { value: true }); exports.NodeRsa = void 0; const crypto = require("crypto"); const buffer_1 = require("buffer"); const publicKeyAlgorithm_1 = require("../publicKeyAlgorithm"); const sshData_1 = require("../../io/sshData"); const nodeHmac_1 = require("./nodeHmac"); 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); // Note this is exposed as an inner-class property below: `NodeRsa.KeyPair`. // TypeScript requires that the class definition comes first. class NodeRsaKeyPair { /* @internal */ constructor() { this.comment = null; } get hasPublicKey() { return !!this.publicKey; } get hasPrivateKey() { return !!this.privateKey; } get keyAlgorithmName() { return NodeRsa.keyAlgorithmName; } generate(keySizeInBits) { keySizeInBits = keySizeInBits !== null && keySizeInBits !== void 0 ? keySizeInBits : NodeRsaKeyPair.defaultKeySize; if (nodeGenerateKeyPairSupport && nodeKeyObjectSupport) { return this.generateNodeKeyPairObjects(keySizeInBits); } else if (nodeGenerateKeyPairSupport) { return this.generateNodeKeyPairBuffers(keySizeInBits); } else { return this.generateExternalKeyPair(keySizeInBits); } } async generateNodeKeyPairObjects(keySizeInBits) { [this.publicKey, this.privateKey] = await new Promise((resolve, reject) => { const keyGenParams = { modulusLength: keySizeInBits, }; try { crypto.generateKeyPair('rsa', keyGenParams, (err, publicKey, privateKey) => { if (err) { reject(err); } else { resolve([publicKey, privateKey]); } }); } catch (err) { reject(err); } }); } async generateNodeKeyPairBuffers(keySizeInBits) { [this.publicKey, this.privateKey] = await new Promise((resolve, reject) => { const keyGenParams = { modulusLength: keySizeInBits, publicKeyEncoding: { type: 'pkcs1', format: 'pem' }, privateKeyEncoding: { type: 'pkcs1', format: 'pem', cipher: undefined, passphrase: undefined, }, }; try { crypto.generateKeyPair('rsa', keyGenParams, (err, publicKey, privateKey) => { if (err) { reject(err); } else { resolve([publicKey, privateKey]); } }); } catch (err) { reject(err); } }); } async generateExternalKeyPair(keySizeInBits) { // When running in a version of node that doesn't have a built-in API // for RSA key-gen, use an external library. Note this implementation // is SLOW because it's pure JS. It may take 1-5 seconds to generate // a 2048 bit key. const externRsa = await Promise.resolve().then(() => require('node-rsa')); const keyPair = new externRsa({ b: keySizeInBits }); this.publicKey = keyPair.exportKey('pkcs1-public-pem'); this.privateKey = keyPair.exportKey('pkcs1-private-pem'); // Ensure the PEM format ends in a newline, just for consistency. if (!this.publicKey.endsWith('\n')) this.publicKey += '\n'; if (!this.privateKey.endsWith('\n')) this.privateKey += '\n'; } async setPublicKeyBytes(keyBytes) { if (!keyBytes) { throw new TypeError('Buffer is required.'); } // Read public key in SSH format. const reader = new sshData_1.SshDataReader(keyBytes); const algorithmName = reader.readString('ascii'); if (algorithmName !== this.keyAlgorithmName && algorithmName !== NodeRsa.rsaWithSha256 && algorithmName !== NodeRsa.rsaWithSha512) { throw new Error(`Invalid RSA key algorithm: ${algorithmName}`); } const exponent = reader.readBigInt(); const modulus = reader.readBigInt(); // Write public key in PKCS#1 format. keyBytes = keyFormatters_1.Pkcs1KeyFormatter.formatRsaPublic({ modulus, exponent }); if (nodeKeyObjectSupport) { this.publicKey = crypto.createPublicKey({ key: keyBytes, type: 'pkcs1', format: 'der', }); } else { this.publicKey = (0, keyFormatters_1.formatPem)(keyBytes, 'RSA PUBLIC KEY'); } } async getPublicKeyBytes(algorithmName) { if (!this.publicKey) { return null; } if (!algorithmName) { algorithmName = this.keyAlgorithmName; } let keyBytes; if (typeof this.publicKey === 'string') { keyBytes = (0, keyFormatters_1.parsePem)(this.publicKey); } else { keyBytes = this.publicKey.export({ type: 'pkcs1', format: 'der', }); } const parameters = keyFormatters_1.Pkcs1KeyFormatter.parseRsaPublic(keyBytes); // Write public key in SSH format. const keyBuffer = buffer_1.Buffer.alloc(512); const keyWriter = new sshData_1.SshDataWriter(keyBuffer); keyWriter.writeString(algorithmName, 'ascii'); keyWriter.writeBigInt(parameters.exponent); keyWriter.writeBigInt(parameters.modulus); keyBytes = keyWriter.toBuffer(); return keyBytes; } async importParameters(parameters) { if (nodeKeyObjectSupport) { this.publicKey = crypto.createPublicKey({ key: keyFormatters_1.Pkcs1KeyFormatter.formatRsaPublic(parameters), format: 'der', type: 'pkcs1', }); if (parameters.d) { this.privateKey = crypto.createPrivateKey({ key: keyFormatters_1.Pkcs1KeyFormatter.formatRsaPrivate(parameters), format: 'der', type: 'pkcs1', }); } else { this.privateKey = undefined; } } else { const publicKeyBytes = keyFormatters_1.Pkcs1KeyFormatter.formatRsaPublic(parameters); this.publicKey = (0, keyFormatters_1.formatPem)(publicKeyBytes, 'RSA PUBLIC KEY'); if (parameters.d) { const privateKeyBytes = keyFormatters_1.Pkcs1KeyFormatter.formatRsaPrivate(parameters); this.privateKey = (0, keyFormatters_1.formatPem)(privateKeyBytes, 'RSA PRIVATE KEY'); } } } async exportParameters() { var _a, _b; if (!this.publicKey) throw new Error('Public key not set.'); let keyBytes; if (nodeKeyObjectSupport) { keyBytes = ((_a = this.privateKey) !== null && _a !== void 0 ? _a : this.publicKey).export({ format: 'der', type: 'pkcs1', }); } else { keyBytes = (0, keyFormatters_1.parsePem)((_b = this.privateKey) !== null && _b !== void 0 ? _b : this.publicKey); } return this.privateKey ? keyFormatters_1.Pkcs1KeyFormatter.parseRsaPrivate(keyBytes) : keyFormatters_1.Pkcs1KeyFormatter.parseRsaPublic(keyBytes); } dispose() { this.publicKey = undefined; this.privateKey = undefined; } } NodeRsaKeyPair.defaultKeySize = 2048; class NodeRsa extends publicKeyAlgorithm_1.PublicKeyAlgorithm { constructor(name, hashAlgorithmName) { super(name, NodeRsa.keyAlgorithmName, hashAlgorithmName); } createKeyPair() { return new NodeRsaKeyPair(); } async generateKeyPair(keySizeInBits) { const rsaKey = new NodeRsaKeyPair(); await rsaKey.generate(keySizeInBits); return rsaKey; } createSigner(keyPair) { if (!(keyPair instanceof NodeRsaKeyPair)) { throw new TypeError('RSA key pair object expected.'); } return new NodeRsaSignerVerifier(keyPair, NodeRsa.convertHashAlgorithmName(this.hashAlgorithmName), nodeHmac_1.NodeHmac.getHashDigestLength(this.hashAlgorithmName)); } createVerifier(keyPair) { if (!(keyPair instanceof NodeRsaKeyPair)) { throw new TypeError('RSA key pair object expected.'); } return new NodeRsaSignerVerifier(keyPair, NodeRsa.convertHashAlgorithmName(this.hashAlgorithmName), nodeHmac_1.NodeHmac.getHashDigestLength(this.hashAlgorithmName)); } static convertHashAlgorithmName(hashAlgorithmName) { return hashAlgorithmName.replace('SHA2-', 'SHA'); } } exports.NodeRsa = NodeRsa; NodeRsa.keyAlgorithmName = 'ssh-rsa'; NodeRsa.rsaWithSha256 = 'rsa-sha2-256'; NodeRsa.rsaWithSha512 = 'rsa-sha2-512'; NodeRsa.KeyPair = NodeRsaKeyPair; class NodeRsaSignerVerifier { constructor(keyPair, hashAlgorithmName, digestLength) { this.keyPair = keyPair; this.hashAlgorithmName = hashAlgorithmName; this.digestLength = digestLength; } async sign(data) { if (!this.keyPair.privateKey) { throw new Error('Private key not set.'); } const signer = crypto.createSign(this.hashAlgorithmName); signer.update(data); const signature = signer.sign(this.keyPair.privateKey); return signature; } async verify(data, signature) { if (!this.keyPair.publicKey) { throw new Error('Public key not set.'); } const verifier = crypto.createVerify(this.hashAlgorithmName); verifier.update(data); const result = verifier.verify(this.keyPair.publicKey, signature); return result; } dispose() { } } //# sourceMappingURL=nodeRsa.js.map