@microsoft/dev-tunnels-ssh
Version:
SSH library for Dev Tunnels
270 lines • 10.8 kB
JavaScript
"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