@microsoft/dev-tunnels-ssh
Version:
SSH library for Dev Tunnels
333 lines • 13.4 kB
JavaScript
"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 integer bytes as required by SSH.
const signatureReader = new derData_1.DerReader(signature);
const x = signatureReader.readInteger();
const y = signatureReader.readInteger();
const keySizeInBytes = Math.ceil(this.keyPair.curve.keySize / 8);
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 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