@microsoft/dev-tunnels-ssh-keys
Version:
SSH key import/export library for Dev Tunnels
130 lines • 6.02 kB
JavaScript
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
Object.defineProperty(exports, "__esModule", { value: true });
exports.Sec1KeyFormatter = void 0;
const dev_tunnels_ssh_1 = require("@microsoft/dev-tunnels-ssh");
const keyData_1 = require("./keyData");
const pkcs1KeyFormatter_1 = require("./pkcs1KeyFormatter");
/** Provides import/export of the SEC1 key format. */
class Sec1KeyFormatter {
async import(keyData) {
if (!keyData)
throw new TypeError('KeyData object expected.');
if (keyData.keyType === Sec1KeyFormatter.privateKeyType || !keyData.keyType) {
const parameters = Sec1KeyFormatter.importECPrivate(keyData.data);
const keyPair = new dev_tunnels_ssh_1.ECDsa.KeyPair();
await keyPair.importParameters(parameters);
return keyPair;
}
return null;
}
async export(keyPair, includePrivate) {
if (!keyPair)
throw new TypeError('KeyPair object expected.');
if (!includePrivate) {
throw new Error('Public-only export is not supported by this format.');
}
if (keyPair instanceof dev_tunnels_ssh_1.ECDsa.KeyPair) {
if (!keyPair.hasPublicKey) {
throw new Error('KeyPair object does not have a public key.');
}
else if (!keyPair.hasPrivateKey) {
throw new Error('KeyPair object does not have a private key.');
}
const keyData = new keyData_1.KeyData();
const parameters = await keyPair.exportParameters();
keyData.keyType = Sec1KeyFormatter.privateKeyType;
keyData.data = Sec1KeyFormatter.exportECPrivate(parameters);
return keyData;
}
else {
throw new Error('KeyPair class not supported.');
}
}
async decrypt(keyData, passphrase) {
if (!keyData)
throw new TypeError('KeyData object expected.');
if (keyData.keyType === Sec1KeyFormatter.privateKeyType || !keyData.keyType) {
const procTypeHeader = keyData.headers.get('Proc-Type');
if (procTypeHeader === '4,ENCRYPTED') {
if (!passphrase) {
throw new Error('A passphrase is required to decrypt the key.');
}
return pkcs1KeyFormatter_1.Pkcs1KeyFormatter.decryptPrivate(keyData, passphrase);
}
else {
return keyData;
}
}
return null;
}
async encrypt(keyData, passphrase) {
if (!keyData)
throw new TypeError('KeyData object expected.');
if (keyData.keyType === Sec1KeyFormatter.privateKeyType) {
throw new Error('SEC1 export with passphrase is not supported because the format uses ' +
'a weak key derivation algorithm. Use PKCS#8 to export a ' +
'passphrase-protected private key.');
}
else {
throw new Error(`Unsupported key type: ${keyData.keyType}`);
}
}
static exportECPrivate(ec) {
const curve = dev_tunnels_ssh_1.ECDsa.curves.find((c) => c.oid === ec.curve.oid);
const keySizeInBytes = Math.ceil(curve.keySize / 8);
const writer = new dev_tunnels_ssh_1.DerWriter(Buffer.alloc(1024));
writer.writeInteger(dev_tunnels_ssh_1.BigInt.fromInt32(1)); // version
writer.writeOctetString(ec.d.toBytes({ unsigned: true, length: keySizeInBytes }));
const curveWriter = new dev_tunnels_ssh_1.DerWriter(Buffer.alloc(100));
curveWriter.writeObjectIdentifier(ec.curve.oid);
writer.writeTagged(0, curveWriter);
const publicKeyWriter = new dev_tunnels_ssh_1.DerWriter(Buffer.alloc(512));
const x = ec.x.toBytes({ unsigned: true, length: keySizeInBytes });
const y = ec.y.toBytes({ unsigned: true, length: keySizeInBytes });
const publicKeyData = Buffer.alloc(1 + x.length + y.length);
publicKeyData[0] = 4; // Indicates uncompressed curve format
x.copy(publicKeyData, 1);
y.copy(publicKeyData, 1 + x.length);
publicKeyWriter.writeBitString(publicKeyData);
writer.writeTagged(1, publicKeyWriter);
return writer.toBuffer();
}
static importECPrivate(keyBytes) {
var _a;
const reader = new dev_tunnels_ssh_1.DerReader(keyBytes);
const version = reader.readInteger().toInt32();
if (version !== 1) {
throw new Error(`Unsupported SEC1 format version: ${version}`);
}
const d = dev_tunnels_ssh_1.BigInt.fromBytes(reader.readOctetString(), { unsigned: true });
const curveReader = reader.tryReadTagged(0);
if (!curveReader) {
throw new Error('SEC1 curve info not found.');
}
const curveOid = curveReader.readObjectIdentifier();
const curveName = (_a = dev_tunnels_ssh_1.ECDsa.curves.find((c) => c.oid === curveOid)) === null || _a === void 0 ? void 0 : _a.name;
const publicKeyReader = reader.tryReadTagged(1);
if (!publicKeyReader) {
throw new Error('SEC1 public key data not found.');
}
const xy = publicKeyReader.readBitString();
if (xy.length % 2 !== 1) {
throw new Error(`Unexpected key data length: ${xy.length}`);
}
const x = dev_tunnels_ssh_1.BigInt.fromBytes(xy.slice(1, 1 + (xy.length - 1) / 2), { unsigned: true });
const y = dev_tunnels_ssh_1.BigInt.fromBytes(xy.slice(1 + (xy.length - 1) / 2), { unsigned: true });
const ec = {
curve: { name: curveName, oid: curveOid },
x,
y,
d,
};
return ec;
}
}
exports.Sec1KeyFormatter = Sec1KeyFormatter;
Sec1KeyFormatter.privateKeyType = 'EC PRIVATE KEY';
//# sourceMappingURL=sec1KeyFormatter.js.map
;