@microsoft/dev-tunnels-ssh-keys
Version:
SSH key import/export library for Dev Tunnels
242 lines • 12.4 kB
JavaScript
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
var _a;
Object.defineProperty(exports, "__esModule", { value: true });
exports.importKeyBytes = exports.importKeyFile = exports.importKey = exports.exportPrivateKeyBytes = exports.exportPrivateKeyFile = exports.exportPrivateKey = exports.exportPublicKeyBytes = exports.exportPublicKeyFile = exports.exportPublicKey = exports.keyFormatters = void 0;
const buffer_1 = require("buffer");
const dev_tunnels_ssh_1 = require("@microsoft/dev-tunnels-ssh");
const defaultKeyFormatter_1 = require("./defaultKeyFormatter");
const publicKeyFormatter_1 = require("./publicKeyFormatter");
const pkcs1KeyFormatter_1 = require("./pkcs1KeyFormatter");
const sec1KeyFormatter_1 = require("./sec1KeyFormatter");
const pkcs8KeyFormatter_1 = require("./pkcs8KeyFormatter");
const jsonWebKeyFormatter_1 = require("./jsonWebKeyFormatter");
const keyData_1 = require("./keyData");
/**
* Mapping to formatters for each supported key format.
*/
exports.keyFormatters = new Map();
exports.keyFormatters.set(0 /* KeyFormat.Default */, new defaultKeyFormatter_1.DefaultKeyFormatter());
exports.keyFormatters.set(1 /* KeyFormat.Ssh */, new publicKeyFormatter_1.PublicKeyFormatter());
exports.keyFormatters.set(3 /* KeyFormat.Pkcs1 */, new pkcs1KeyFormatter_1.Pkcs1KeyFormatter());
exports.keyFormatters.set(4 /* KeyFormat.Sec1 */, new sec1KeyFormatter_1.Sec1KeyFormatter());
exports.keyFormatters.set(5 /* KeyFormat.Pkcs8 */, new pkcs8KeyFormatter_1.Pkcs8KeyFormatter());
exports.keyFormatters.set(7 /* KeyFormat.Jwk */, new jsonWebKeyFormatter_1.JsonWebKeyFormatter());
const enableFileIO = !!((_a = process === null || process === void 0 ? void 0 : process.versions) === null || _a === void 0 ? void 0 : _a.node);
/** Exports the public key from a key pair, as a string. */
function exportPublicKey(keyPair, keyFormat = 0 /* KeyFormat.Default */, keyEncoding = 0 /* KeyEncoding.Default */) {
if (keyEncoding === 1 /* KeyEncoding.Binary */) {
throw new Error('Cannot represent binary-encoded key as a string.');
}
return exportPublicKeyBytes(keyPair, keyFormat, keyEncoding).then((keyBytes) => keyBytes.toString('utf8'));
}
exports.exportPublicKey = exportPublicKey;
/** Exports the public key from a key pair, to a file. */
function exportPublicKeyFile(keyPair, keyFile, keyFormat = 0 /* KeyFormat.Default */, keyEncoding = 0 /* KeyEncoding.Default */) {
if (!enableFileIO)
throw new Error('File I/O is not supported in a browser environment.');
return exportPublicKeyBytes(keyPair, keyFormat, keyEncoding).then((keyBytes) => require('fs').promises.writeFile(keyFile, keyBytes));
}
exports.exportPublicKeyFile = exportPublicKeyFile;
/** Exports the public key from a key pair, to a byte buffer. */
function exportPublicKeyBytes(keyPair, keyFormat = 0 /* KeyFormat.Default */, keyEncoding = 0 /* KeyEncoding.Default */) {
return exportKeyBytes(keyPair, null, keyFormat, keyEncoding, false);
}
exports.exportPublicKeyBytes = exportPublicKeyBytes;
/** Exports the private key from a key pair, as a string. */
function exportPrivateKey(keyPair, passphrase = null, keyFormat = 0 /* KeyFormat.Default */, keyEncoding = 0 /* KeyEncoding.Default */) {
if (keyEncoding === 1 /* KeyEncoding.Binary */) {
throw new Error('Cannot represent binary-encoded key as a string.');
}
return exportPrivateKeyBytes(keyPair, passphrase, keyFormat, keyEncoding).then((keyBytes) => keyBytes.toString('utf8'));
}
exports.exportPrivateKey = exportPrivateKey;
/** Exports the private key from a key pair, to a file. */
function exportPrivateKeyFile(keyPair, passphrase = null, keyFile, keyFormat = 0 /* KeyFormat.Default */, keyEncoding = 0 /* KeyEncoding.Default */) {
if (!enableFileIO)
throw new Error('File I/O is not supported in a browser environment.');
return exportPrivateKeyBytes(keyPair, passphrase, keyFormat, keyEncoding).then((keyBytes) => require('fs').promises.writeFile(keyFile, keyBytes));
}
exports.exportPrivateKeyFile = exportPrivateKeyFile;
/** Exports the private key from a key pair, to a byte buffer. */
function exportPrivateKeyBytes(keyPair, passphrase = null, keyFormat = 0 /* KeyFormat.Default */, keyEncoding = 0 /* KeyEncoding.Default */) {
return exportKeyBytes(keyPair, passphrase, keyFormat, keyEncoding, true);
}
exports.exportPrivateKeyBytes = exportPrivateKeyBytes;
/** Imports a public key or public/private key pair from a string. */
function importKey(keyString, passphrase = null, keyFormat = 0 /* KeyFormat.Default */, keyEncoding = 0 /* KeyEncoding.Default */) {
if (keyEncoding === 1 /* KeyEncoding.Binary */) {
throw new Error('Cannot represent binary-encoded key as a string.');
}
return importKeyBytes(buffer_1.Buffer.from(keyString, 'utf8'), passphrase, keyFormat, keyEncoding);
}
exports.importKey = importKey;
/** Imports a public key or public/private key pair from a file. */
function importKeyFile(keyFile, passphrase = null, keyFormat = 0 /* KeyFormat.Default */, keyEncoding = 0 /* KeyEncoding.Default */) {
if (keyEncoding === 1 /* KeyEncoding.Binary */) {
throw new Error('Cannot represent binary-encoded key as a string.');
}
return require('fs')
.promises.readFile(keyFile)
.then((keyBytes) => importKeyBytes(keyBytes, passphrase, keyFormat, keyEncoding));
}
exports.importKeyFile = importKeyFile;
/** Imports a public key or public/private key pair from a byte array. */
async function importKeyBytes(keyBytes, passphrase = null, keyFormat = 0 /* KeyFormat.Default */, keyEncoding = 0 /* KeyEncoding.Default */) {
if (!(keyBytes instanceof buffer_1.Buffer))
throw new TypeError('Buffer expected.');
let keyData = null;
if (keyEncoding === 0 /* KeyEncoding.Default */ || keyEncoding === 3 /* KeyEncoding.Pem */) {
keyData = keyData_1.KeyData.tryDecodePemBytes(keyBytes);
if (!keyData && keyEncoding === 3 /* KeyEncoding.Pem */) {
throw new Error('Key is not PEM-encoded.');
}
}
let keyType = null;
let comment = null;
if (!keyData && (keyEncoding === 0 /* KeyEncoding.Default */ || keyEncoding === 5 /* KeyEncoding.Json */)) {
try {
JSON.parse(keyBytes.toString('utf8'));
keyData = new keyData_1.KeyData();
keyData.data = keyBytes;
keyEncoding = 5 /* KeyEncoding.Json */;
keyFormat = 7 /* KeyFormat.Jwk */;
}
catch (e) {
if (keyEncoding === 5 /* KeyEncoding.Json */) {
throw new Error('Key is not JSON-formatted.');
}
}
}
if (!keyData &&
(keyFormat === 0 /* KeyFormat.Default */ || keyFormat === 1 /* KeyFormat.Ssh */) &&
(keyEncoding === 0 /* KeyEncoding.Default */ || keyEncoding === 4 /* KeyEncoding.SshBase64 */)) {
try {
let keyString = keyBytes.toString('utf8');
const lines = keyString.split('\n').filter((line) => !!line);
if (lines.length === 1) {
keyString = lines[0];
const parts = keyString.split(' ', 3);
if (parts.length >= 2 && parts[0].length < 40) {
keyType = parts[0];
keyBytes = buffer_1.Buffer.from(parts[1], 'utf8');
comment = parts.length === 3 ? parts[2].trimRight() : null;
keyEncoding = 2 /* KeyEncoding.Base64 */;
keyFormat = 1 /* KeyFormat.Ssh */;
}
}
}
catch (e) { }
if (!keyType && keyEncoding === 4 /* KeyEncoding.SshBase64 */) {
throw new Error('Key does not have SSH algorithm prefix.');
}
}
if (!keyData && (keyEncoding === 0 /* KeyEncoding.Default */ || keyEncoding === 2 /* KeyEncoding.Base64 */)) {
try {
const keyString = keyBytes.toString('utf8');
// Node doesn't throw when parsing invalid base64. To check if the parse was successful,
// compare the resulting decoded bytes to the expected length, which is 3/4 of the input.
if (keyString.length % 4 === 0) {
const encodedLengthWithoutPadding = keyString.length - (keyString.endsWith('==') ? 2 : keyString.endsWith('=') ? 1 : 0);
const decodedLength = Math.floor((encodedLengthWithoutPadding / 4) * 3);
const decoded = buffer_1.Buffer.from(keyString, 'base64');
if (decoded.length === decodedLength) {
keyBytes = decoded;
keyEncoding = 1 /* KeyEncoding.Binary */;
}
}
}
catch (e) {
if (keyEncoding === 2 /* KeyEncoding.Base64 */) {
throw new Error('Key is not base64-encoded.');
}
}
}
if (keyData === null &&
(keyEncoding === 0 /* KeyEncoding.Default */ ||
keyEncoding === 1 /* KeyEncoding.Binary */ ||
keyEncoding === 5 /* KeyEncoding.Json */)) {
keyData = new keyData_1.KeyData();
keyData.data = keyBytes;
if (keyType) {
keyData.keyType = keyType;
}
if (comment) {
keyData.headers.set('Comment', comment);
}
}
if (!keyData) {
throw new Error('Failed to decode key.');
}
if (keyFormat === 0 /* KeyFormat.Default */ && !keyData.keyType) {
throw new Error('Specify a key format when importing binary data.');
}
const formatter = exports.keyFormatters.get(keyFormat);
if (!formatter) {
throw new Error(`Unimplemented or invalid or key format: ${keyFormat}`);
}
keyData = await formatter.decrypt(keyData, passphrase);
if (!keyData) {
throw new Error('Failed to decrypt key.');
}
const keyPair = await formatter.import(keyData);
if (!keyPair) {
throw new Error('Failed to import key.');
}
return keyPair;
}
exports.importKeyBytes = importKeyBytes;
async function exportKeyBytes(keyPair, passphrase, keyFormat, keyEncoding, includePrivate) {
if (typeof keyPair !== 'object')
throw new TypeError('KeyPair object expected.');
if (includePrivate && !keyPair.hasPrivateKey) {
throw new Error('The KeyPair object does not contain a private key.');
}
if (keyFormat === 0 /* KeyFormat.Default */) {
keyFormat = includePrivate ? 5 /* KeyFormat.Pkcs8 */ : 1 /* KeyFormat.Ssh */;
}
if (keyEncoding === 0 /* KeyEncoding.Default */) {
switch (keyFormat) {
case 1 /* KeyFormat.Ssh */:
keyEncoding = 4 /* KeyEncoding.SshBase64 */;
break;
case 7 /* KeyFormat.Jwk */:
keyEncoding = 5 /* KeyEncoding.Json */;
break;
default:
keyEncoding = 3 /* KeyEncoding.Pem */;
break;
}
}
// Automatically switch between PKCS#1/SEC1 based on key algorithm.
if (keyFormat === 3 /* KeyFormat.Pkcs1 */ && keyPair instanceof dev_tunnels_ssh_1.ECDsa.KeyPair) {
keyFormat = 4 /* KeyFormat.Sec1 */;
}
else if (keyFormat === 4 /* KeyFormat.Sec1 */ && keyPair instanceof dev_tunnels_ssh_1.Rsa.KeyPair) {
keyFormat = 3 /* KeyFormat.Pkcs1 */;
}
const formatter = exports.keyFormatters.get(keyFormat);
if (!formatter) {
throw new Error(`Unimplemented or invalid or key format: ${keyFormat}`);
}
let keyData = await formatter.export(keyPair, includePrivate);
if (passphrase) {
keyData = await formatter.encrypt(keyData, passphrase);
}
switch (keyEncoding) {
case 1 /* KeyEncoding.Binary */:
case 5 /* KeyEncoding.Json */:
return keyData.data;
case 2 /* KeyEncoding.Base64 */:
return buffer_1.Buffer.from(keyData.data.toString('base64'), 'utf8');
case 4 /* KeyEncoding.SshBase64 */:
return keyData.encodeSshPublicKeyBytes();
case 3 /* KeyEncoding.Pem */:
return keyData.encodePemBytes();
default:
throw new Error('Invalid key encoding.');
}
}
//# sourceMappingURL=importExport.js.map
;