UNPKG

claude-flow-novice

Version:

Claude Flow Novice - Advanced orchestration platform for multi-agent AI workflows with CFN Loop architecture Includes CodeSearch (hybrid SQLite + pgvector), mem0/memgraph specialists, and all CFN skills.

322 lines (321 loc) 12.8 kB
/** * Backup Encryption Manager * * Implements AES-256-GCM encryption for backup files with: * - Secure key management via environment variables * - Unique IV generation for each backup * - HMAC-SHA256 integrity verification * - Key rotation support * - Backward compatibility detection * - Encryption metadata tracking * * CVSS Mitigation: Addresses CVSS 7.2 (high severity) vulnerability * by encrypting sensitive backup data at rest. * * Security Requirements: * - BACKUP_ENCRYPTION_KEY: 32-byte hex encoded AES-256 key (env var) * - BACKUP_ENCRYPTION_ENABLED: Set to 'true' to enable encryption (env var) * - IV: Cryptographically random, unique per backup * - Auth Tag: GCM authentication tag for integrity verification * - HMAC: SHA-256 for additional integrity verification * * Usage: * const encryptionManager = new EncryptionManager(); * const encrypted = await encryptionManager.encrypt(buffer, backupId); * const decrypted = await encryptionManager.decrypt(encrypted, backupId); * const verified = encryptionManager.verifyIntegrity(encrypted); */ import * as crypto from 'crypto'; import { createLogger } from './logging.js'; import { createError, ErrorCode } from './errors.js'; const logger = createLogger('encryption-manager'); /** * Backup Encryption Manager * * Implements AES-256-GCM encryption with integrity verification */ export class EncryptionManager { enabled; masterKey = null; keyVersion; ALGORITHM = 'aes-256-gcm'; IV_LENGTH = 16; AUTH_TAG_LENGTH = 16; KEY_LENGTH = 32; HMAC_ALGORITHM = 'sha256'; constructor(config = {}){ // Check if encryption is enabled this.enabled = config.enabled !== undefined ? config.enabled : this.isEncryptionEnabled(); // Load and validate master key if (this.enabled) { const keyHex = config.masterKey || process.env.BACKUP_ENCRYPTION_KEY; if (!keyHex) { throw createError(ErrorCode.VALIDATION_FAILED, 'BACKUP_ENCRYPTION_KEY environment variable is required when encryption is enabled', { enabledVia: 'BACKUP_ENCRYPTION_ENABLED env var' }); } try { this.masterKey = Buffer.from(keyHex, 'hex'); if (this.masterKey.length !== this.KEY_LENGTH) { throw createError(ErrorCode.VALIDATION_FAILED, `Master key must be exactly ${this.KEY_LENGTH} bytes (${this.KEY_LENGTH * 2} hex characters)`, { providedLength: this.masterKey.length, expectedLength: this.KEY_LENGTH }); } logger.info('Encryption manager initialized with AES-256-GCM', { enabled: true, algorithm: this.ALGORITHM, keyLength: this.KEY_LENGTH, ivLength: this.IV_LENGTH, authTagLength: this.AUTH_TAG_LENGTH }); } catch (error) { if (error instanceof Error && 'code' in error) { throw error; } throw createError(ErrorCode.VALIDATION_FAILED, 'Failed to parse BACKUP_ENCRYPTION_KEY. Must be valid hex-encoded 32-byte key.', { error: error instanceof Error ? error.message : String(error) }); } } else { logger.info('Encryption manager initialized with encryption disabled', { enabled: false, enablementRequired: 'Set BACKUP_ENCRYPTION_ENABLED=true and provide BACKUP_ENCRYPTION_KEY to enable encryption' }); } this.keyVersion = config.keyVersion || 'v1'; } /** * Check if encryption is enabled */ isEnabled() { return this.enabled; } /** * Encrypt backup data * * @param data - Data to encrypt * @param backupId - Backup identifier (for logging/tracking) * @returns Encrypted backup with metadata * @throws Error if encryption is disabled or encryption fails */ async encrypt(data, backupId) { if (!this.enabled || !this.masterKey) { throw createError(ErrorCode.VALIDATION_FAILED, 'Encryption is not enabled. Enable BACKUP_ENCRYPTION_ENABLED and provide BACKUP_ENCRYPTION_KEY.', { backupId }); } try { // Generate cryptographically random IV const iv = crypto.randomBytes(this.IV_LENGTH); // Create cipher const cipher = crypto.createCipheriv(this.ALGORITHM, this.masterKey, iv); // Encrypt data let encryptedData = cipher.update(data); encryptedData = Buffer.concat([ encryptedData, cipher.final() ]); // Get authentication tag const authTag = cipher.getAuthTag(); // Calculate HMAC for additional integrity verification const hmac = crypto.createHmac(this.HMAC_ALGORITHM, this.masterKey).update(Buffer.concat([ iv, encryptedData, authTag ])).digest(); // Create metadata const metadata = { algorithm: 'AES-256-GCM', iv: iv.toString('hex'), authTag: authTag.toString('hex'), hmac: hmac.toString('hex'), encryptedAt: new Date().toISOString(), keyVersion: this.keyVersion }; logger.debug('Backup encrypted successfully', { backupId, algorithm: this.ALGORITHM, dataSize: data.length, encryptedSize: encryptedData.length, ivLength: iv.length, authTagLength: authTag.length }); return { data: encryptedData, metadata }; } catch (error) { logger.error('Backup encryption failed', error instanceof Error ? error : undefined, { backupId }); throw createError(ErrorCode.INTERNAL_ERROR, 'Backup encryption failed', { backupId, error: error instanceof Error ? error.message : String(error) }); } } /** * Decrypt backup data * * @param encrypted - Encrypted backup * @param backupId - Backup identifier (for logging/tracking) * @returns Decryption result with data and integrity verification * @throws Error if decryption fails or integrity verification fails */ async decrypt(encrypted, backupId) { if (!this.enabled || !this.masterKey) { throw createError(ErrorCode.VALIDATION_FAILED, 'Encryption is not enabled. Enable BACKUP_ENCRYPTION_ENABLED and provide BACKUP_ENCRYPTION_KEY.', { backupId }); } try { // Verify HMAC first const iv = Buffer.from(encrypted.metadata.iv, 'hex'); const authTag = Buffer.from(encrypted.metadata.authTag, 'hex'); const expectedHmac = encrypted.metadata.hmac; const calculatedHmac = crypto.createHmac(this.HMAC_ALGORITHM, this.masterKey).update(Buffer.concat([ iv, encrypted.data, authTag ])).digest().toString('hex'); const integrityVerified = calculatedHmac === expectedHmac; if (!integrityVerified) { logger.warn('HMAC verification failed during decryption', { backupId, expectedHmac, calculatedHmac }); } // Decrypt data const decipher = crypto.createDecipheriv(this.ALGORITHM, this.masterKey, iv); decipher.setAuthTag(authTag); let decryptedData = decipher.update(encrypted.data); decryptedData = Buffer.concat([ decryptedData, decipher.final() ]); logger.debug('Backup decrypted successfully', { backupId, algorithm: this.ALGORITHM, encryptedSize: encrypted.data.length, decryptedSize: decryptedData.length, integrityVerified }); return { data: decryptedData, integrityVerified, metadata: encrypted.metadata }; } catch (error) { logger.error('Backup decryption failed', error instanceof Error ? error : undefined, { backupId }); throw createError(ErrorCode.INTERNAL_ERROR, 'Backup decryption failed. Backup may be corrupted or key may be incorrect.', { backupId, error: error instanceof Error ? error.message : String(error) }); } } /** * Verify backup integrity without decryption * * @param encrypted - Encrypted backup * @param backupId - Backup identifier (for logging) * @returns Whether integrity verification passed */ verifyIntegrity(encrypted, backupId) { if (!this.masterKey) { logger.error('Cannot verify integrity without master key', { backupId }); return false; } try { const iv = Buffer.from(encrypted.metadata.iv, 'hex'); const authTag = Buffer.from(encrypted.metadata.authTag, 'hex'); const expectedHmac = encrypted.metadata.hmac; const calculatedHmac = crypto.createHmac(this.HMAC_ALGORITHM, this.masterKey).update(Buffer.concat([ iv, encrypted.data, authTag ])).digest().toString('hex'); const verified = calculatedHmac === expectedHmac; if (!verified) { logger.warn('Integrity verification failed', { backupId }); } return verified; } catch (error) { logger.error('Integrity verification error', error instanceof Error ? error : undefined, { backupId }); return false; } } /** * Detect if a backup file is encrypted * * Useful for backward compatibility - detects if backup is encrypted * * @param data - File data * @returns Whether the data appears to be encrypted */ static isEncrypted(data) { // Encrypted data with metadata should have a specific structure // For now, we can check if the data is valid JSON metadata (simple heuristic) try { // Try to detect our encryption format // This is a simple heuristic - encrypted data won't be valid text/JSON if (data.length < 100) { return false; } // Try to parse as JSON at the end for metadata const str = data.toString('utf8', Math.max(0, data.length - 500)); const match = str.match(/"algorithm"\s*:\s*"AES-256-GCM"/); return match !== null; } catch { return false; } } /** * Generate a new encryption key * * Useful for key rotation * * @returns 32-byte hex-encoded AES-256 key */ static generateKey() { return crypto.randomBytes(32).toString('hex'); } /** * Export encryption metadata as JSON * * Useful for storing metadata separately from encrypted data * * @param encrypted - Encrypted backup * @returns JSON stringified metadata */ static exportMetadata(encrypted) { return JSON.stringify(encrypted.metadata, null, 2); } /** * Import encryption metadata from JSON * * @param metadataJson - JSON stringified metadata * @returns Parsed encryption metadata */ static importMetadata(metadataJson) { return JSON.parse(metadataJson); } // ============================================================================ // Private Helper Methods // ============================================================================ isEncryptionEnabled() { const enabled = process.env.BACKUP_ENCRYPTION_ENABLED?.toLowerCase() === 'true'; return enabled; } } /** * Singleton instance */ let defaultManager = null; /** * Get the default encryption manager instance */ export function getEncryptionManager(config) { if (!defaultManager) { defaultManager = new EncryptionManager(config); } return defaultManager; } //# sourceMappingURL=encryption-manager.js.map