@microsoft/dev-tunnels-ssh
Version:
SSH library for Dev Tunnels
279 lines • 11.3 kB
JavaScript
"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 as two mpint big-ints per RFC 5656 / RFC 4251.
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.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 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