UNPKG

e2ee-adapter

Version:

Plug-and-play End-to-End Encryption middleware for Express.js and NestJS using hybrid AES-CBC + RSA encryption with secure key exchange

179 lines 7.4 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.E2EEClient = void 0; const crypto_1 = require("../utils/crypto"); class E2EEClient { constructor(config) { // Validate configuration if (!config.serverKeys || Object.keys(config.serverKeys).length === 0) { throw new Error('serverKeys must be provided with at least one key'); } // Build server keys map this.serverKeys = { ...config.serverKeys }; } /** * Get server public key for a specific keyId */ getServerPublicKey(keyId) { const publicKey = this.serverKeys[keyId]; if (!publicKey) { throw new Error(`Server public key not found for keyId: ${keyId}`); } return publicKey; } /** * Encrypt request data using hybrid encryption (AES-CBC + RSA) * @param data - Data to encrypt * @param keyId - Key ID to use for encryption * @returns Promise<{ encryptedData: string, encryptedKey: string, iv: string, originalAesKey: Buffer, originalIv: Buffer }> */ async encryptRequest(data, keyId) { try { const dataString = JSON.stringify(data); const serverPublicKey = this.getServerPublicKey(keyId); // Encrypt the data using hybrid encryption const encryptionResult = await (0, crypto_1.encrypt)(dataString, serverPublicKey); return { encryptedData: encryptionResult.encryptedData, encryptedKey: encryptionResult.aesKey.toString('base64'), iv: encryptionResult.iv.toString('base64'), originalAesKey: encryptionResult.originalAesKey, // Use the original AES key for response decryption originalIv: encryptionResult.iv, // Store the original IV for response decryption }; } catch (error) { throw new Error(`Request encryption failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } } /** * Decrypt response data using AES-CBC * @param encryptedData - Encrypted data (base64) * @param aesKey - AES key (Buffer) * @param iv - Initialization vector (Buffer) * @returns Promise<any> */ async decryptResponse(encryptedData, aesKey, iv) { try { const decryptedData = (0, crypto_1.decryptAES)(encryptedData, aesKey, iv); return JSON.parse(decryptedData); } catch (error) { throw new Error(`Response decryption failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } } /** * Make an encrypted HTTP request * @param requestConfig - Request configuration * @returns Promise<E2EEClientResponse> */ async request(requestConfig) { try { const { url, method, data, headers = {}, keyId } = requestConfig; // Validate keyId is provided if (!keyId) { throw new Error('keyId is required for encrypted requests'); } // Prepare request headers const requestHeaders = { 'Content-Type': 'application/json', ...headers, }; let requestBody = ''; let aesKey; let iv; // Always generate encryption headers for E2EE support // This allows the server to encrypt responses even for GET requests or requests without body const { encryptedData, encryptedKey, iv: ivString, originalAesKey, originalIv, } = await this.encryptRequest(data || {}, keyId); // Set encryption headers requestHeaders['x-custom-key'] = encryptedKey; requestHeaders['x-custom-iv'] = ivString; requestHeaders['x-key-id'] = keyId; // Store AES key and IV for response decryption aesKey = originalAesKey; iv = originalIv; // Set encrypted data as request body only if data was provided AND method is not GET if (data && method.toUpperCase() !== 'GET') { requestBody = encryptedData; } // Make the HTTP request const fetchOptions = { method, headers: requestHeaders, }; if (data) { fetchOptions.body = requestBody; } const response = await fetch(url, fetchOptions); // Parse response headers const responseHeaders = {}; response.headers.forEach((value, key) => { responseHeaders[key] = value; }); // Get response data const responseData = await response.text(); // Decrypt response if we have the AES key and IV let decryptedData = responseData; if (aesKey && iv && responseData) { try { decryptedData = await this.decryptResponse(responseData, aesKey, iv); } catch (error) { console.warn('Failed to decrypt response, returning raw data:', error); decryptedData = responseData; } } return { data: decryptedData, headers: responseHeaders, status: response.status, statusText: response.statusText, }; } catch (error) { throw new Error(`Request failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } } /** * Generate a new key pair (for testing purposes only) * Note: In production, clients don't generate their own keys */ static async generateKeyPair(keySize = 2048) { const { generateKeyPair } = await Promise.resolve().then(() => __importStar(require('../utils/crypto'))); return generateKeyPair(keySize); } } exports.E2EEClient = E2EEClient; //# sourceMappingURL=e2ee-client.js.map