UNPKG

@microsoft/dev-tunnels-ssh-keys

Version:

SSH key import/export library for Dev Tunnels

242 lines 12.4 kB
"use strict"; // // 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