UNPKG

@codai/cbd

Version:

Codai Better Database - High-Performance Vector Memory System with HPKV-inspired architecture and MCP server

793 lines (679 loc) â€ĸ 28.2 kB
/** * 🔐 CBD Data Encryption Service - Enterprise-Grade Data Protection * * Phase 4.2.3.2 Implementation * Features: AES-256 Encryption, Key Management, Field/Document Encryption, Key Rotation * * Created: August 2, 2025 * Security Level: Enterprise Grade - AES-256-GCM */ const express = require('express'); const crypto = require('crypto'); const fs = require('fs').promises; const path = require('path'); // Initialize Express app for Encryption Service const app = express(); const PORT = 4450; // Encryption Service Port // Encryption Configuration const ENCRYPTION_CONFIG = { algorithm: 'aes-256-gcm', keyLength: 32, // 256 bits ivLength: 16, // 128 bits tagLength: 16, // 128 bits keyDerivationIterations: 100000, keyRotationInterval: 7 * 24 * 60 * 60 * 1000, // 7 days encryptionPolicies: { default: { sensitive_fields: ['password', 'ssn', 'credit_card', 'email', 'phone'], pii_fields: ['name', 'address', 'date_of_birth', 'personal_id'], financial_fields: ['account_number', 'routing_number', 'balance', 'transaction_amount'], health_fields: ['medical_record', 'diagnosis', 'treatment', 'prescription'] } } }; // In-memory stores (replace with secure key management system in production) const encryptionKeys = new Map(); const keyRotationSchedule = new Map(); const encryptionStats = new Map(); const keyUsageLog = new Map(); // Encryption Error Class class CBDEncryptionError extends Error { constructor(message, code, statusCode = 500) { super(message); this.name = 'CBDEncryptionError'; this.code = code; this.statusCode = statusCode; } } // 🔐 Advanced Encryption Service class CBDEncryptionService { constructor(config) { this.config = config; this.masterKey = this.generateMasterKey(); this.initializeEncryptionKeys(); this.setupKeyRotation(); } generateMasterKey() { return crypto.randomBytes(this.config.keyLength); } deriveKey(password, salt) { return crypto.pbkdf2Sync( password, salt, this.config.keyDerivationIterations, this.config.keyLength, 'sha256' ); } initializeEncryptionKeys() { // Generate default encryption keys const keyTypes = ['general', 'pii', 'financial', 'health', 'system']; keyTypes.forEach(keyType => { const key = crypto.randomBytes(this.config.keyLength); const keyId = `key_${keyType}_${Date.now()}`; encryptionKeys.set(keyType, { id: keyId, key: key, algorithm: this.config.algorithm, createdAt: new Date(), lastUsed: new Date(), usageCount: 0, isActive: true }); }); console.log(`🔑 Initialized ${keyTypes.length} encryption keys`); } setupKeyRotation() { // Schedule automatic key rotation setInterval(() => { this.rotateKeys(); }, this.config.keyRotationInterval); console.log(`🔄 Key rotation scheduled every ${this.config.keyRotationInterval / (24 * 60 * 60 * 1000)} days`); } async encryptField(data, options = {}) { try { const { keyType = 'general', algorithm = this.config.algorithm, encoding = 'hex' } = options; const dataString = typeof data === 'string' ? data : JSON.stringify(data); const keyInfo = encryptionKeys.get(keyType); if (!keyInfo || !keyInfo.isActive) { throw new CBDEncryptionError(`Encryption key not found or inactive: ${keyType}`, 'KEY_NOT_FOUND', 404); } const iv = crypto.randomBytes(this.config.ivLength); const cipher = crypto.createCipher(algorithm, keyInfo.key, iv); let encrypted = cipher.update(dataString, 'utf8', encoding); encrypted += cipher.final(encoding); const tag = cipher.getAuthTag(); // Update key usage keyInfo.lastUsed = new Date(); keyInfo.usageCount++; // Log encryption operation await this.logEncryptionOperation(keyType, 'encrypt', { dataSize: dataString.length, algorithm, keyId: keyInfo.id }); return { encrypted: true, data: encrypted, iv: iv.toString(encoding), tag: tag.toString(encoding), algorithm: algorithm, keyType: keyType, keyId: keyInfo.id, timestamp: new Date().toISOString() }; } catch (error) { await this.logEncryptionOperation(keyType || 'unknown', 'encrypt_error', { error: error.message }); if (error instanceof CBDEncryptionError) { throw error; } throw new CBDEncryptionError(`Encryption failed: ${error.message}`, 'ENCRYPTION_ERROR', 500); } } async decryptField(encryptedData, options = {}) { try { const { validateIntegrity = true, encoding = 'hex' } = options; if (!encryptedData.encrypted || !encryptedData.data) { throw new CBDEncryptionError('Invalid encrypted data format', 'INVALID_FORMAT', 400); } const keyInfo = encryptionKeys.get(encryptedData.keyType); if (!keyInfo) { throw new CBDEncryptionError(`Decryption key not found: ${encryptedData.keyType}`, 'KEY_NOT_FOUND', 404); } // Verify key ID if provided if (encryptedData.keyId && keyInfo.id !== encryptedData.keyId) { console.warn(`Key rotation detected: expected ${encryptedData.keyId}, using ${keyInfo.id}`); // In production, implement key versioning to handle rotated keys } const iv = Buffer.from(encryptedData.iv, encoding); const tag = Buffer.from(encryptedData.tag, encoding); const decipher = crypto.createDecipher(encryptedData.algorithm, keyInfo.key, iv); decipher.setAuthTag(tag); let decrypted = decipher.update(encryptedData.data, encoding, 'utf8'); decrypted += decipher.final('utf8'); // Update key usage keyInfo.lastUsed = new Date(); keyInfo.usageCount++; // Log decryption operation await this.logEncryptionOperation(encryptedData.keyType, 'decrypt', { algorithm: encryptedData.algorithm, keyId: keyInfo.id }); // Try to parse as JSON, fallback to string try { return JSON.parse(decrypted); } catch { return decrypted; } } catch (error) { await this.logEncryptionOperation(encryptedData?.keyType || 'unknown', 'decrypt_error', { error: error.message }); if (error instanceof CBDEncryptionError) { throw error; } throw new CBDEncryptionError(`Decryption failed: ${error.message}`, 'DECRYPTION_ERROR', 500); } } async encryptDocument(document, encryptionPolicy = 'default') { try { const policy = this.config.encryptionPolicies[encryptionPolicy] || this.config.encryptionPolicies.default; const encryptedDocument = { ...document }; const encryptionMetadata = { algorithm: this.config.algorithm, encryptedFields: [], encryptionPolicy: encryptionPolicy, timestamp: new Date().toISOString(), keyUsage: {} }; // Identify fields to encrypt const fieldsToEncrypt = new Set(); // Add fields based on policy Object.keys(document).forEach(field => { const fieldLower = field.toLowerCase(); // Check sensitive fields if (policy.sensitive_fields?.some(pattern => fieldLower.includes(pattern))) { fieldsToEncrypt.add(field); } // Check PII fields if (policy.pii_fields?.some(pattern => fieldLower.includes(pattern))) { fieldsToEncrypt.add(field); } // Check financial fields if (policy.financial_fields?.some(pattern => fieldLower.includes(pattern))) { fieldsToEncrypt.add(field); } // Check health fields if (policy.health_fields?.some(pattern => fieldLower.includes(pattern))) { fieldsToEncrypt.add(field); } }); // Encrypt identified fields for (const field of fieldsToEncrypt) { if (document[field] !== undefined && document[field] !== null) { // Determine key type based on field classification let keyType = 'general'; const fieldLower = field.toLowerCase(); if (policy.financial_fields?.some(pattern => fieldLower.includes(pattern))) { keyType = 'financial'; } else if (policy.health_fields?.some(pattern => fieldLower.includes(pattern))) { keyType = 'health'; } else if (policy.pii_fields?.some(pattern => fieldLower.includes(pattern))) { keyType = 'pii'; } const encryptedField = await this.encryptField(document[field], { keyType }); encryptedDocument[field] = encryptedField; encryptionMetadata.encryptedFields.push(field); encryptionMetadata.keyUsage[field] = keyType; } } // Add encryption metadata encryptedDocument._encryption = encryptionMetadata; await this.logEncryptionOperation('document', 'encrypt', { fieldsEncrypted: encryptionMetadata.encryptedFields.length, policy: encryptionPolicy, keyTypes: Object.values(encryptionMetadata.keyUsage) }); return encryptedDocument; } catch (error) { await this.logEncryptionOperation('document', 'encrypt_error', { policy: encryptionPolicy, error: error.message }); throw error; } } async decryptDocument(encryptedDocument) { try { if (!encryptedDocument._encryption) { return encryptedDocument; // Not encrypted } const decryptedDocument = { ...encryptedDocument }; const metadata = encryptedDocument._encryption; // Decrypt each encrypted field for (const field of metadata.encryptedFields) { if (encryptedDocument[field] && encryptedDocument[field].encrypted) { decryptedDocument[field] = await this.decryptField(encryptedDocument[field]); } } // Remove encryption metadata delete decryptedDocument._encryption; await this.logEncryptionOperation('document', 'decrypt', { fieldsDecrypted: metadata.encryptedFields.length, policy: metadata.encryptionPolicy }); return decryptedDocument; } catch (error) { await this.logEncryptionOperation('document', 'decrypt_error', { error: error.message }); throw error; } } async rotateKeys() { try { console.log('🔄 Starting key rotation...'); let rotatedKeys = 0; for (const [keyType, keyInfo] of encryptionKeys.entries()) { const keyAge = Date.now() - keyInfo.createdAt.getTime(); if (keyAge >= this.config.keyRotationInterval) { // Generate new key const newKey = crypto.randomBytes(this.config.keyLength); const newKeyId = `key_${keyType}_${Date.now()}`; // Archive old key (keep for decryption of old data) const archivedKeyId = `archived_${keyInfo.id}`; encryptionKeys.set(archivedKeyId, { ...keyInfo, isActive: false, archivedAt: new Date() }); // Set new active key encryptionKeys.set(keyType, { id: newKeyId, key: newKey, algorithm: this.config.algorithm, createdAt: new Date(), lastUsed: new Date(), usageCount: 0, isActive: true, previousKeyId: keyInfo.id }); rotatedKeys++; console.log(`🔑 Rotated key: ${keyType} (${keyInfo.id} → ${newKeyId})`); } } if (rotatedKeys > 0) { await this.logEncryptionOperation('system', 'key_rotation', { rotatedKeys, timestamp: new Date().toISOString() }); console.log(`✅ Key rotation completed: ${rotatedKeys} keys rotated`); } else { console.log('â„šī¸ No keys required rotation'); } } catch (error) { console.error('❌ Key rotation failed:', error); await this.logEncryptionOperation('system', 'key_rotation_error', { error: error.message }); } } async getEncryptionStats() { const stats = { totalKeys: encryptionKeys.size, activeKeys: Array.from(encryptionKeys.values()).filter(k => k.isActive).length, archivedKeys: Array.from(encryptionKeys.values()).filter(k => !k.isActive).length, keyUsage: {}, operationStats: {}, securityMetrics: { keyRotationCompliance: 0, encryptionCoverage: 0, averageKeyAge: 0 } }; // Calculate key usage stats for (const [keyType, keyInfo] of encryptionKeys.entries()) { if (keyInfo.isActive) { stats.keyUsage[keyType] = { id: keyInfo.id, createdAt: keyInfo.createdAt, lastUsed: keyInfo.lastUsed, usageCount: keyInfo.usageCount, ageInDays: Math.floor((Date.now() - keyInfo.createdAt.getTime()) / (24 * 60 * 60 * 1000)) }; } } // Calculate operation stats const operations = Array.from(keyUsageLog.values()); stats.operationStats = { totalOperations: operations.length, encryptOperations: operations.filter(op => op.operation === 'encrypt').length, decryptOperations: operations.filter(op => op.operation === 'decrypt').length, errorOperations: operations.filter(op => op.operation.includes('error')).length }; // Calculate security metrics const activeKeys = Array.from(encryptionKeys.values()).filter(k => k.isActive); if (activeKeys.length > 0) { const totalAge = activeKeys.reduce((sum, key) => sum + (Date.now() - key.createdAt.getTime()), 0); stats.securityMetrics.averageKeyAge = Math.floor(totalAge / activeKeys.length / (24 * 60 * 60 * 1000)); const compliantKeys = activeKeys.filter(key => (Date.now() - key.createdAt.getTime()) < this.config.keyRotationInterval ); stats.securityMetrics.keyRotationCompliance = Math.round((compliantKeys.length / activeKeys.length) * 100); } return stats; } async logEncryptionOperation(keyType, operation, details) { const logEntry = { id: `enc_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, timestamp: new Date().toISOString(), keyType, operation, ...details }; keyUsageLog.set(logEntry.id, logEntry); // Keep only last 1000 log entries if (keyUsageLog.size > 1000) { const entries = Array.from(keyUsageLog.entries()).sort((a, b) => new Date(b[1].timestamp) - new Date(a[1].timestamp) ); keyUsageLog.clear(); entries.slice(0, 1000).forEach(([id, entry]) => { keyUsageLog.set(id, entry); }); } return logEntry; } // Secure key export for backup/migration async exportKeys(password) { try { const keyData = {}; for (const [keyType, keyInfo] of encryptionKeys.entries()) { if (keyInfo.isActive) { keyData[keyType] = { id: keyInfo.id, key: keyInfo.key.toString('base64'), algorithm: keyInfo.algorithm, createdAt: keyInfo.createdAt.toISOString(), usageCount: keyInfo.usageCount }; } } // Encrypt the key export with password const salt = crypto.randomBytes(16); const exportKey = this.deriveKey(password, salt); const exportData = await this.encryptField(JSON.stringify(keyData), { keyType: 'system', algorithm: this.config.algorithm }); return { encrypted: true, salt: salt.toString('base64'), data: exportData, exportedAt: new Date().toISOString(), keyCount: Object.keys(keyData).length }; } catch (error) { throw new CBDEncryptionError(`Key export failed: ${error.message}`, 'EXPORT_ERROR', 500); } } } // Initialize services const encryptionService = new CBDEncryptionService(ENCRYPTION_CONFIG); // Express middleware app.use(express.json({ limit: '10mb' })); app.use((req, res, next) => { res.set({ 'X-Content-Type-Options': 'nosniff', 'X-Frame-Options': 'DENY', 'X-XSS-Protection': '1; mode=block', 'Strict-Transport-Security': 'max-age=31536000; includeSubDomains' }); next(); }); // Authentication middleware (integrate with Security Gateway) const authenticateEncryption = (req, res, next) => { // Simple API key authentication for demo const apiKey = req.headers['x-api-key']; const validApiKeys = ['enc_key_admin_2025', 'enc_key_service_2025']; if (!apiKey || !validApiKeys.includes(apiKey)) { return res.status(401).json({ success: false, error: 'API key required', code: 'UNAUTHORIZED' }); } next(); }; // 🚀 API Routes // Field encryption endpoint app.post('/encrypt/field', authenticateEncryption, async (req, res) => { try { const { data, keyType, algorithm } = req.body; if (!data) { return res.status(400).json({ success: false, error: 'Data is required', code: 'MISSING_DATA' }); } const result = await encryptionService.encryptField(data, { keyType, algorithm }); res.json({ success: true, result, message: 'Field encrypted successfully' }); } catch (error) { console.error('Field encryption error:', error); res.status(error.statusCode || 500).json({ success: false, error: error.message, code: error.code || 'ENCRYPTION_FAILED' }); } }); // Field decryption endpoint app.post('/decrypt/field', authenticateEncryption, async (req, res) => { try { const { encryptedData } = req.body; if (!encryptedData) { return res.status(400).json({ success: false, error: 'Encrypted data is required', code: 'MISSING_DATA' }); } const result = await encryptionService.decryptField(encryptedData); res.json({ success: true, result, message: 'Field decrypted successfully' }); } catch (error) { console.error('Field decryption error:', error); res.status(error.statusCode || 500).json({ success: false, error: error.message, code: error.code || 'DECRYPTION_FAILED' }); } }); // Document encryption endpoint app.post('/encrypt/document', authenticateEncryption, async (req, res) => { try { const { document, policy } = req.body; if (!document || typeof document !== 'object') { return res.status(400).json({ success: false, error: 'Valid document object is required', code: 'INVALID_DOCUMENT' }); } const result = await encryptionService.encryptDocument(document, policy); res.json({ success: true, result, message: 'Document encrypted successfully', encryptedFields: result._encryption?.encryptedFields || [] }); } catch (error) { console.error('Document encryption error:', error); res.status(error.statusCode || 500).json({ success: false, error: error.message, code: error.code || 'ENCRYPTION_FAILED' }); } }); // Document decryption endpoint app.post('/decrypt/document', authenticateEncryption, async (req, res) => { try { const { encryptedDocument } = req.body; if (!encryptedDocument || typeof encryptedDocument !== 'object') { return res.status(400).json({ success: false, error: 'Valid encrypted document object is required', code: 'INVALID_DOCUMENT' }); } const result = await encryptionService.decryptDocument(encryptedDocument); res.json({ success: true, result, message: 'Document decrypted successfully' }); } catch (error) { console.error('Document decryption error:', error); res.status(error.statusCode || 500).json({ success: false, error: error.message, code: error.code || 'DECRYPTION_FAILED' }); } }); // Key rotation endpoint app.post('/keys/rotate', authenticateEncryption, async (req, res) => { try { await encryptionService.rotateKeys(); res.json({ success: true, message: 'Key rotation completed', timestamp: new Date().toISOString() }); } catch (error) { console.error('Key rotation error:', error); res.status(500).json({ success: false, error: error.message, code: 'ROTATION_FAILED' }); } }); // Encryption statistics endpoint app.get('/stats', authenticateEncryption, async (req, res) => { try { const stats = await encryptionService.getEncryptionStats(); res.json({ success: true, stats, timestamp: new Date().toISOString() }); } catch (error) { console.error('Stats error:', error); res.status(500).json({ success: false, error: error.message, code: 'STATS_FAILED' }); } }); // Key export endpoint (admin only) app.post('/keys/export', authenticateEncryption, async (req, res) => { try { const { password } = req.body; if (!password || password.length < 12) { return res.status(400).json({ success: false, error: 'Strong password (12+ characters) required for key export', code: 'WEAK_PASSWORD' }); } const exportData = await encryptionService.exportKeys(password); res.json({ success: true, exportData, message: 'Keys exported successfully', warning: 'Store exported keys securely - they contain sensitive encryption material' }); } catch (error) { console.error('Key export error:', error); res.status(error.statusCode || 500).json({ success: false, error: error.message, code: error.code || 'EXPORT_FAILED' }); } }); // Health check endpoint app.get('/health', (req, res) => { res.json({ success: true, service: 'CBD Data Encryption Service', version: '4.2.3.2', status: 'healthy', timestamp: new Date().toISOString(), uptime: process.uptime(), features: { fieldEncryption: true, documentEncryption: true, keyManagement: true, keyRotation: true, encryptionPolicies: true, auditLogging: true }, algorithms: [ENCRYPTION_CONFIG.algorithm], keyTypes: Array.from(encryptionKeys.keys()).filter(k => !k.startsWith('archived_')) }); }); // Error handling middleware app.use((error, req, res, next) => { console.error('Encryption Service Error:', error); res.status(error.statusCode || 500).json({ success: false, error: error.message || 'Internal server error', code: error.code || 'INTERNAL_ERROR' }); }); // Start server app.listen(PORT, () => { console.log(`🔐 CBD Data Encryption Service running on port ${PORT}`); console.log(`📍 Health check: http://localhost:${PORT}/health`); console.log(`🔒 Field encryption: POST http://localhost:${PORT}/encrypt/field`); console.log(`📄 Document encryption: POST http://localhost:${PORT}/encrypt/document`); console.log(`📊 Statistics: GET http://localhost:${PORT}/stats`); console.log(''); console.log('🔑 API Authentication:'); console.log(' Header: X-API-Key: enc_key_admin_2025'); console.log(' Header: X-API-Key: enc_key_service_2025'); console.log(''); console.log('đŸ›Ąī¸ Security Features:'); console.log(' â€ĸ AES-256-GCM encryption'); console.log(' â€ĸ Automatic key rotation'); console.log(' â€ĸ Field & document encryption'); console.log(' â€ĸ Encryption policies'); console.log(' â€ĸ Comprehensive audit logging'); console.log(''); console.log('📖 Usage Examples:'); console.log(' Field: {"data": "sensitive info", "keyType": "pii"}'); console.log(' Document: {"document": {"name": "John", "ssn": "123-45-6789"}}'); }); module.exports = app;