UNPKG

@microsoft/dev-tunnels-ssh-keys

Version:

SSH key import/export library for Dev Tunnels

197 lines 8.76 kB
"use strict"; // // Copyright (c) Microsoft Corporation. All rights reserved. // Object.defineProperty(exports, "__esModule", { value: true }); exports.Pkcs1KeyFormatter = void 0; const dev_tunnels_ssh_1 = require("@microsoft/dev-tunnels-ssh"); const keyFormatter_1 = require("./keyFormatter"); const keyData_1 = require("./keyData"); /** Provides import/export of the PKCS#1 key format. */ class Pkcs1KeyFormatter { async import(keyData) { if (!keyData) throw new TypeError('KeyData object expected.'); if (!keyData.keyType) { // Automatically determine public or private by reading the first few bytes. try { const reader = new dev_tunnels_ssh_1.DerReader(keyData.data); reader.readInteger(); reader.readInteger(); keyData.keyType = reader.available > 0 ? Pkcs1KeyFormatter.privateKeyType : Pkcs1KeyFormatter.publicKeyType; } catch (e) { return null; } } let parameters = null; if (keyData.keyType === Pkcs1KeyFormatter.publicKeyType) { parameters = Pkcs1KeyFormatter.parseRsaPublic(keyData.data); } else if (keyData.keyType === Pkcs1KeyFormatter.privateKeyType) { parameters = Pkcs1KeyFormatter.parseRsaPrivate(keyData.data); } if (parameters) { const keyPair = dev_tunnels_ssh_1.SshAlgorithms.publicKey.rsaWithSha512.createKeyPair(); await keyPair.importParameters(parameters); return keyPair; } return null; } async export(keyPair, includePrivate) { if (!keyPair) throw new TypeError('KeyPair object expected.'); if (keyPair instanceof dev_tunnels_ssh_1.Rsa.KeyPair) { if (!keyPair.hasPublicKey) { throw new Error('KeyPair object does not have a public key.'); } else if (includePrivate && !keyPair.hasPrivateKey) { throw new Error('KeyPair object does not have a private key.'); } const keyData = new keyData_1.KeyData(); const parameters = await keyPair.exportParameters(); if (includePrivate) { keyData.keyType = Pkcs1KeyFormatter.privateKeyType; keyData.data = Pkcs1KeyFormatter.formatRsaPrivate(parameters); } else { keyData.keyType = Pkcs1KeyFormatter.publicKeyType; keyData.data = Pkcs1KeyFormatter.formatRsaPublic(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 === Pkcs1KeyFormatter.publicKeyType || (!keyData.keyType && !passphrase)) { return keyData; } else if (keyData.keyType === Pkcs1KeyFormatter.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.decryptPrivate(keyData, passphrase); } else { return keyData; } } return null; } async encrypt(keyData, passphrase) { if (!keyData) throw new TypeError('KeyData object expected.'); if (keyData.keyType === Pkcs1KeyFormatter.publicKeyType) { throw new Error('Public key cannot be encrypted.'); } else if (keyData.keyType === Pkcs1KeyFormatter.privateKeyType) { throw new Error('PKCS#1 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 formatRsaPublic(rsa) { const writer = new dev_tunnels_ssh_1.DerWriter(Buffer.alloc(1024)); writer.writeInteger(rsa.modulus); writer.writeInteger(rsa.exponent); return writer.toBuffer(); } static formatRsaPrivate(rsa) { if (!(rsa.d && rsa.p && rsa.q && rsa.dp && rsa.dq && rsa.qi)) { throw new Error('Missing private key parameters.'); } const writer = new dev_tunnels_ssh_1.DerWriter(Buffer.alloc(2048)); writer.writeInteger(dev_tunnels_ssh_1.BigInt.fromInt32(0)); writer.writeInteger(rsa.modulus); writer.writeInteger(rsa.exponent); writer.writeInteger(rsa.d); writer.writeInteger(rsa.p); writer.writeInteger(rsa.q); writer.writeInteger(rsa.dp); writer.writeInteger(rsa.dq); writer.writeInteger(rsa.qi); return writer.toBuffer(); } static parseRsaPublic(keyBytes) { const reader = new dev_tunnels_ssh_1.DerReader(keyBytes); const modulus = reader.readInteger(); const exponent = reader.readInteger(); return { modulus, exponent }; } static parseRsaPrivate(keyBytes) { const reader = new dev_tunnels_ssh_1.DerReader(keyBytes); const version = reader.readInteger(); const modulus = reader.readInteger(); const exponent = reader.readInteger(); const d = reader.readInteger(); const p = reader.readInteger(); const q = reader.readInteger(); const dp = reader.readInteger(); const dq = reader.readInteger(); const qi = reader.readInteger(); return { modulus, exponent, d, p, q, dp, dq, qi }; } /* @internal */ static async decryptPrivate(keyData, passphrase) { const decryptionInfo = keyData.headers.get('DEK-Info'); if (!decryptionInfo) { throw new Error('PKCS#1 decryption parameters not found.'); } const decryptionInfoParts = decryptionInfo.split(','); const cipherName = decryptionInfoParts[0]; const ivHex = decryptionInfoParts[1]; const iv = Buffer.from(ivHex, 'hex'); const encryption = (0, keyFormatter_1.getKeyEncryptionAlgorithm)(cipherName); const key = Pkcs1KeyFormatter.deriveDecryptionKey(Buffer.from(passphrase, 'utf8'), iv, encryption.keyLength); const decryptedKeyData = new keyData_1.KeyData(keyData.keyType); decryptedKeyData.headers = new Map(keyData.headers); decryptedKeyData.headers.delete('Proc-Type'); decryptedKeyData.headers.delete('DEK-Info'); const decipher = await encryption.createCipher(false, key, iv); decryptedKeyData.data = await decipher.transform(keyData.data); // The first part of the key should be a DER sequence header. if (decryptedKeyData.data[0] !== (32 /* DerType.Constructed */ | 16 /* DerType.Sequence */)) { throw new Error('Key decryption failed - incorrect passphrase.'); } return decryptedKeyData; } static deriveDecryptionKey(passphraseBytes, iv, keyLength) { if ((0, keyFormatter_1.useWebCrypto)()) { // Web crypto does not provide an MD5 implementation. An external lib could be used here, // but it's not worth it to support an insecure encryption format. Use PKCS#8 instead. throw new Error('PKCS#1 decryption not implemented.'); } const nodeCrypto = require('crypto'); const PKCS5_SALT_LEN = 8; const salt = iv.slice(0, PKCS5_SALT_LEN); let key = Buffer.alloc(0); while (key.length < keyLength) { // MD5 is an unsafe hash algorithm, but it is used only for decrypting (importing) keys, // not encrypting. const md5 = nodeCrypto.createHash('md5'); md5.update(key); md5.update(passphraseBytes); md5.update(salt); key = Buffer.concat([key, md5.digest()]); } key = key.slice(0, keyLength); return key; } } exports.Pkcs1KeyFormatter = Pkcs1KeyFormatter; Pkcs1KeyFormatter.publicKeyType = 'RSA PUBLIC KEY'; Pkcs1KeyFormatter.privateKeyType = 'RSA PRIVATE KEY'; //# sourceMappingURL=pkcs1KeyFormatter.js.map