UNPKG

recoder-security

Version:

Enterprise-grade security and compliance layer for CodeCraft CLI

520 lines 21.4 kB
"use strict"; /** * End-to-End Data Encryption System * Provides comprehensive encryption for user data, API communications, and file storage * with key management, rotation, and compliance features */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.DataEncryption = void 0; const crypto_1 = require("crypto"); const util_1 = require("util"); const node_forge_1 = __importDefault(require("node-forge")); const shared_1 = require("@recoder/shared"); class DataEncryption { constructor(config) { this.logger = new shared_1.Logger('DataEncryption'); this.masterKeys = new Map(); this.keyMetadata = new Map(); this.currentKeyVersion = '1'; this.config = { algorithm: 'aes-256-gcm', keyDerivation: 'pbkdf2', keySize: 256, ivSize: 12, tagSize: 16, iterations: 100000, saltSize: 32, compressionEnabled: true, keyRotationDays: 90, ...config, }; this.initializeMasterKey(); } /** * Encrypt data with context and metadata */ async encryptData(data, context, password) { this.logger.debug(`Encrypting ${context.dataType} data`); try { // Get or derive encryption key const key = password ? await this.deriveKeyFromPassword(password) : await this.getCurrentEncryptionKey(); // Convert data to buffer let dataBuffer = Buffer.isBuffer(data) ? data : Buffer.from(data, 'utf8'); let compressed = false; let originalSize = dataBuffer.length; // Apply compression if enabled and beneficial if (this.config.compressionEnabled && dataBuffer.length > 1024) { const compressedData = await this.compressData(dataBuffer); if (compressedData.length < dataBuffer.length * 0.9) { dataBuffer = compressedData; compressed = true; } } // Generate IV and salt const iv = (0, crypto_1.randomBytes)(this.config.ivSize); const salt = (0, crypto_1.randomBytes)(this.config.saltSize); // Create cipher const cipher = (0, crypto_1.createCipheriv)(this.config.algorithm, key, iv); // Encrypt data const encrypted = Buffer.concat([ cipher.update(dataBuffer), cipher.final(), ]); // Get authentication tag (for GCM mode) const tag = this.config.algorithm.includes('gcm') ? cipher.getAuthTag() : undefined; // Calculate checksum const checksum = (0, crypto_1.createHash)('sha256').update(dataBuffer).digest('hex'); const result = { ciphertext: encrypted.toString('base64'), iv: iv.toString('base64'), tag: tag?.toString('base64'), salt: salt.toString('base64'), algorithm: this.config.algorithm, keyVersion: this.currentKeyVersion, timestamp: Date.now(), metadata: { compressed, originalSize: compressed ? originalSize : undefined, checksum, }, }; // Log encryption event (without sensitive data) await this.logEncryptionEvent('encrypt', context, { keyVersion: this.currentKeyVersion, algorithm: this.config.algorithm, dataSize: dataBuffer.length, compressed, }); return result; } catch (error) { this.logger.error('Encryption failed:', error); throw new Error(`Encryption failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } } /** * Decrypt data with validation and integrity checks */ async decryptData(encryptedData, context, password) { this.logger.debug(`Decrypting ${context.dataType} data`); try { // Get decryption key const key = password ? await this.deriveKeyFromPassword(password, Buffer.from(encryptedData.salt, 'base64')) : await this.getEncryptionKey(encryptedData.keyVersion); // Decode encrypted components const ciphertext = Buffer.from(encryptedData.ciphertext, 'base64'); const iv = Buffer.from(encryptedData.iv, 'base64'); const tag = encryptedData.tag ? Buffer.from(encryptedData.tag, 'base64') : undefined; // Create decipher const decipher = (0, crypto_1.createDecipheriv)(encryptedData.algorithm, key, iv); // Set auth tag for GCM mode if (tag && encryptedData.algorithm.includes('gcm')) { decipher.setAuthTag(tag); } // Decrypt data const decrypted = Buffer.concat([ decipher.update(ciphertext), decipher.final(), ]); // Decompress if needed let finalData = decrypted; if (encryptedData.metadata?.compressed) { finalData = await this.decompressData(decrypted); } // Verify checksum if (encryptedData.metadata?.checksum) { const calculatedChecksum = (0, crypto_1.createHash)('sha256').update(finalData).digest('hex'); if (calculatedChecksum !== encryptedData.metadata.checksum) { throw new Error('Data integrity check failed - checksum mismatch'); } } // Log decryption event await this.logEncryptionEvent('decrypt', context, { keyVersion: encryptedData.keyVersion, algorithm: encryptedData.algorithm, dataSize: finalData.length, compressed: encryptedData.metadata?.compressed || false, }); // Update key usage await this.updateKeyUsage(encryptedData.keyVersion); return finalData; } catch (error) { this.logger.error('Decryption failed:', error); throw new Error(`Decryption failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } } /** * Encrypt file with streaming support for large files */ async encryptFile(inputPath, outputPath, context) { const fs = require('fs-extra'); const { pipeline } = require('stream'); const { createReadStream, createWriteStream } = require('fs'); this.logger.info(`Encrypting file: ${inputPath}`); try { const key = await this.getCurrentEncryptionKey(); const iv = (0, crypto_1.randomBytes)(this.config.ivSize); const salt = (0, crypto_1.randomBytes)(this.config.saltSize); // Get file stats const stats = await fs.stat(inputPath); const originalSize = stats.size; // Create cipher stream const cipher = (0, crypto_1.createCipheriv)(this.config.algorithm, key, iv); // Setup file streams const readStream = createReadStream(inputPath); const writeStream = createWriteStream(outputPath); // Calculate checksum while reading const hashStream = (0, crypto_1.createHash)('sha256'); readStream.on('data', chunk => hashStream.update(chunk)); // Encrypt file await (0, util_1.promisify)(pipeline)(readStream, cipher, writeStream); // Get authentication tag const tag = this.config.algorithm.includes('gcm') ? cipher.getAuthTag() : undefined; const checksum = hashStream.digest('hex'); const result = { ciphertext: outputPath, // File path instead of base64 iv: iv.toString('base64'), tag: tag?.toString('base64'), salt: salt.toString('base64'), algorithm: this.config.algorithm, keyVersion: this.currentKeyVersion, timestamp: Date.now(), metadata: { compressed: false, originalSize, checksum, }, }; await this.logEncryptionEvent('encrypt_file', context, { keyVersion: this.currentKeyVersion, algorithm: this.config.algorithm, dataSize: originalSize, filePath: inputPath, }); return result; } catch (error) { this.logger.error('File encryption failed:', error); throw new Error(`File encryption failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } } /** * Decrypt file with streaming support */ async decryptFile(encryptedData, outputPath, context) { const { pipeline } = require('stream'); const { createReadStream, createWriteStream } = require('fs'); this.logger.info(`Decrypting file to: ${outputPath}`); try { const key = await this.getEncryptionKey(encryptedData.keyVersion); const iv = Buffer.from(encryptedData.iv, 'base64'); const tag = encryptedData.tag ? Buffer.from(encryptedData.tag, 'base64') : undefined; // Create decipher stream const decipher = (0, crypto_1.createDecipheriv)(encryptedData.algorithm, key, iv); if (tag && encryptedData.algorithm.includes('gcm')) { decipher.setAuthTag(tag); } // Setup file streams const readStream = createReadStream(encryptedData.ciphertext); // File path const writeStream = createWriteStream(outputPath); // Calculate checksum while writing const hashStream = (0, crypto_1.createHash)('sha256'); writeStream.on('pipe', () => { writeStream.on('data', chunk => hashStream.update(chunk)); }); // Decrypt file await (0, util_1.promisify)(pipeline)(readStream, decipher, writeStream); // Verify checksum if (encryptedData.metadata?.checksum) { const calculatedChecksum = hashStream.digest('hex'); if (calculatedChecksum !== encryptedData.metadata.checksum) { throw new Error('File integrity check failed - checksum mismatch'); } } await this.logEncryptionEvent('decrypt_file', context, { keyVersion: encryptedData.keyVersion, algorithm: encryptedData.algorithm, filePath: outputPath, }); } catch (error) { this.logger.error('File decryption failed:', error); throw new Error(`File decryption failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } } /** * Generate RSA key pair for asymmetric encryption */ async generateRSAKeyPair(keySize = 2048) { this.logger.info(`Generating RSA key pair (${keySize} bits)`); const keypair = node_forge_1.default.pki.rsa.generateKeyPair(keySize); const publicKeyPem = node_forge_1.default.pki.publicKeyToPem(keypair.publicKey); const privateKeyPem = node_forge_1.default.pki.privateKeyToPem(keypair.privateKey); const keyId = this.generateKeyId(); // Store key metadata const metadata = { id: keyId, version: this.currentKeyVersion, algorithm: `rsa-${keySize}`, created: Date.now(), lastUsed: Date.now(), rotationDue: Date.now() + (this.config.keyRotationDays * 24 * 60 * 60 * 1000), purpose: 'encryption', status: 'active', }; this.keyMetadata.set(keyId, metadata); return { publicKey: publicKeyPem, privateKey: privateKeyPem, keyId, }; } /** * Encrypt with RSA public key */ async encryptWithRSA(data, publicKeyPem) { try { const publicKey = node_forge_1.default.pki.publicKeyFromPem(publicKeyPem); const encrypted = publicKey.encrypt(data, 'RSA-OAEP', { md: node_forge_1.default.md.sha256.create(), mgf1: node_forge_1.default.mgf.mgf1.create(node_forge_1.default.md.sha256.create()), }); return node_forge_1.default.util.encode64(encrypted); } catch (error) { this.logger.error('RSA encryption failed:', error); throw new Error(`RSA encryption failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } } /** * Decrypt with RSA private key */ async decryptWithRSA(encryptedData, privateKeyPem) { try { const privateKey = node_forge_1.default.pki.privateKeyFromPem(privateKeyPem); const encrypted = node_forge_1.default.util.decode64(encryptedData); const decrypted = privateKey.decrypt(encrypted, 'RSA-OAEP', { md: node_forge_1.default.md.sha256.create(), mgf1: node_forge_1.default.mgf.mgf1.create(node_forge_1.default.md.sha256.create()), }); return decrypted; } catch (error) { this.logger.error('RSA decryption failed:', error); throw new Error(`RSA decryption failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } } /** * Sign data with RSA private key */ async signData(data, privateKeyPem) { try { const privateKey = node_forge_1.default.pki.privateKeyFromPem(privateKeyPem); const md = node_forge_1.default.md.sha256.create(); md.update(data, 'utf8'); const signature = privateKey.sign(md); return node_forge_1.default.util.encode64(signature); } catch (error) { this.logger.error('Data signing failed:', error); throw new Error(`Data signing failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } } /** * Verify signature with RSA public key */ async verifySignature(data, signature, publicKeyPem) { try { const publicKey = node_forge_1.default.pki.publicKeyFromPem(publicKeyPem); const md = node_forge_1.default.md.sha256.create(); md.update(data, 'utf8'); const signatureBytes = node_forge_1.default.util.decode64(signature); return publicKey.verify(md.digest().bytes(), signatureBytes); } catch (error) { this.logger.error('Signature verification failed:', error); return false; } } /** * Rotate encryption keys */ async rotateKeys() { this.logger.info('Starting key rotation'); try { // Create new key version const newVersion = (parseInt(this.currentKeyVersion) + 1).toString(); // Generate new master key const newMasterKey = (0, crypto_1.randomBytes)(this.config.keySize / 8); this.masterKeys.set(newVersion, newMasterKey); // Update metadata for old key const oldMetadata = this.keyMetadata.get(this.currentKeyVersion); if (oldMetadata) { oldMetadata.status = 'deprecated'; this.keyMetadata.set(this.currentKeyVersion, oldMetadata); } // Create metadata for new key const newMetadata = { id: this.generateKeyId(), version: newVersion, algorithm: this.config.algorithm, created: Date.now(), lastUsed: Date.now(), rotationDue: Date.now() + (this.config.keyRotationDays * 24 * 60 * 60 * 1000), purpose: 'encryption', status: 'active', }; this.keyMetadata.set(newVersion, newMetadata); this.currentKeyVersion = newVersion; this.logger.info(`Key rotation completed. New version: ${newVersion}`); // Schedule cleanup of old keys (after grace period) setTimeout(() => { this.cleanupOldKeys(); }, 30 * 24 * 60 * 60 * 1000); // 30 days } catch (error) { this.logger.error('Key rotation failed:', error); throw error; } } /** * Get encryption statistics */ getEncryptionStats() { const activeKeys = Array.from(this.keyMetadata.values()).filter(k => k.status === 'active').length; const deprecatedKeys = Array.from(this.keyMetadata.values()).filter(k => k.status === 'deprecated').length; const currentMetadata = this.keyMetadata.get(this.currentKeyVersion); const rotationDue = currentMetadata?.rotationDue || 0; return { currentKeyVersion: this.currentKeyVersion, totalKeys: this.masterKeys.size, algorithm: this.config.algorithm, keyRotationDue: rotationDue, activeKeys, deprecatedKeys, }; } /** * Initialize master encryption key */ initializeMasterKey() { // In production, this would load from secure key management service const masterKey = process.env.ENCRYPTION_MASTER_KEY ? Buffer.from(process.env.ENCRYPTION_MASTER_KEY, 'hex') : (0, crypto_1.randomBytes)(this.config.keySize / 8); this.masterKeys.set(this.currentKeyVersion, masterKey); // Create initial metadata const metadata = { id: this.generateKeyId(), version: this.currentKeyVersion, algorithm: this.config.algorithm, created: Date.now(), lastUsed: Date.now(), rotationDue: Date.now() + (this.config.keyRotationDays * 24 * 60 * 60 * 1000), purpose: 'encryption', status: 'active', }; this.keyMetadata.set(this.currentKeyVersion, metadata); } /** * Get current encryption key */ async getCurrentEncryptionKey() { const key = this.masterKeys.get(this.currentKeyVersion); if (!key) { throw new Error('Master encryption key not found'); } return key; } /** * Get encryption key by version */ async getEncryptionKey(version) { const key = this.masterKeys.get(version); if (!key) { throw new Error(`Encryption key version ${version} not found`); } return key; } /** * Derive key from password using PBKDF2 */ async deriveKeyFromPassword(password, salt) { const derivationSalt = salt || (0, crypto_1.randomBytes)(this.config.saltSize); return (0, crypto_1.pbkdf2Sync)(password, derivationSalt, this.config.iterations, this.config.keySize / 8, 'sha256'); } /** * Compress data using gzip */ async compressData(data) { const zlib = require('zlib'); return (0, util_1.promisify)(zlib.gzip)(data); } /** * Decompress data using gzip */ async decompressData(data) { const zlib = require('zlib'); return (0, util_1.promisify)(zlib.gunzip)(data); } /** * Generate unique key ID */ generateKeyId() { return (0, crypto_1.createHash)('sha256') .update((0, crypto_1.randomBytes)(32)) .digest('hex') .substring(0, 16); } /** * Log encryption events for audit trail */ async logEncryptionEvent(operation, context, metadata) { const event = { timestamp: new Date().toISOString(), operation, userId: context.userId, dataType: context.dataType, purpose: context.purpose, complianceLevel: context.complianceLevel, ...metadata, }; this.logger.info('Encryption event', event); } /** * Update key usage timestamp */ async updateKeyUsage(version) { const metadata = this.keyMetadata.get(version); if (metadata) { metadata.lastUsed = Date.now(); this.keyMetadata.set(version, metadata); } } /** * Cleanup old deprecated keys */ cleanupOldKeys() { const cutoffTime = Date.now() - (90 * 24 * 60 * 60 * 1000); // 90 days ago for (const [version, metadata] of this.keyMetadata) { if (metadata.status === 'deprecated' && metadata.lastUsed < cutoffTime) { this.masterKeys.delete(version); this.keyMetadata.delete(version); this.logger.info(`Cleaned up old key version: ${version}`); } } } } exports.DataEncryption = DataEncryption; //# sourceMappingURL=data-encryption.js.map