/** * Encryption Management API * CWE-311: Encrypt Sensitive Data * * Provides endpoints for: * - Checking encryption status * - Migrating plaintext data to encrypted format * - Key rotation * - Encryption health monitoring */ const express = require('express'); const router = express.Router(); const { authenticate, requireAdmin } = require('../middleware/auth'); const { readLimiter, modifyLimiter } = require('../middleware/rateLimiter'); const { db } = require('../database/db'); const encryption = require('../utils/encryption'); const logger = require('../utils/logger'); const SecurityAuditLogger = require('../utils/securityAudit'); /** * GET /api/encryption/status * Get encryption configuration and health status */ router.get('/status', authenticate, requireAdmin, readLimiter, async (req, res) => { try { const status = encryption.getEncryptionStatus(); // Count encrypted vs unencrypted sensitive data const stats = await getEncryptionStats(); res.json({ success: true, data: { ...status, statistics: stats } }); } catch (error) { logger.error('Error getting encryption status:', error); res.status(500).json({ error: 'Failed to get encryption status' }); } }); /** * GET /api/encryption/scan * Scan database for sensitive unencrypted data */ router.get('/scan', authenticate, requireAdmin, readLimiter, async (req, res) => { try { const findings = await scanForUnencryptedData(); res.json({ success: true, data: { findings, totalIssues: findings.reduce((sum, f) => sum + f.count, 0), recommendation: findings.length > 0 ? 'Run migration to encrypt sensitive data' : 'All sensitive data is encrypted' } }); } catch (error) { logger.error('Error scanning for unencrypted data:', error); res.status(500).json({ error: 'Failed to scan database' }); } }); /** * POST /api/encryption/migrate * Migrate unencrypted sensitive data to encrypted format */ router.post('/migrate', authenticate, requireAdmin, modifyLimiter, async (req, res) => { const ip = req.ip || req.headers['x-forwarded-for'] || req.connection.remoteAddress; const userAgent = req.headers['user-agent']; try { logger.info('Starting encryption migration...'); const results = { settings: 0, vpnConfigs: 0, apiTokens: 0, twoFactorSecrets: 0, errors: [] }; // Migrate settings (sensitive keys only) try { const sensitiveKeys = ['api_key', 'api_secret', 'vpn_password', 'smtp_password']; const settingsResult = await migrateSettings(sensitiveKeys); results.settings = settingsResult.migrated; if (settingsResult.error) results.errors.push(settingsResult.error); } catch (error) { results.errors.push(`Settings migration error: ${error.message}`); } // Migrate VPN configs try { const vpnResult = await migrateVPNConfigs(); results.vpnConfigs = vpnResult.migrated; if (vpnResult.error) results.errors.push(vpnResult.error); } catch (error) { results.errors.push(`VPN migration error: ${error.message}`); } // Migrate API tokens (if any exist in plaintext) try { const tokenResult = await migrateAPITokens(); results.apiTokens = tokenResult.migrated; if (tokenResult.error) results.errors.push(tokenResult.error); } catch (error) { results.errors.push(`API tokens migration error: ${error.message}`); } // Note: 2FA secrets are NOT migrated as they're already handled securely // Passwords are bcrypt hashed (not encrypted), which is correct const totalMigrated = results.settings + results.vpnConfigs + results.apiTokens; // Log admin activity await SecurityAuditLogger.logAdminActivity(req.user.userId, 'encryption_migration', { ip, userAgent, results: { totalMigrated, ...results } }); logger.info('Encryption migration completed', results); res.json({ success: true, message: 'Encryption migration completed', data: { totalMigrated, ...results } }); } catch (error) { logger.error('Error during encryption migration:', error); res.status(500).json({ error: 'Encryption migration failed' }); } }); /** * POST /api/encryption/verify * Verify encrypted data integrity */ router.post('/verify', authenticate, requireAdmin, readLimiter, async (req, res) => { try { const results = await verifyEncryptedData(); res.json({ success: true, data: results }); } catch (error) { logger.error('Error verifying encrypted data:', error); res.status(500).json({ error: 'Verification failed' }); } }); /** * Helper: Get encryption statistics */ async function getEncryptionStats() { return new Promise((resolve, reject) => { db.serialize(() => { const stats = { vpnConfigs: { total: 0, encrypted: 0 }, settings: { total: 0, encrypted: 0 }, twoFactorSecrets: { total: 0, encrypted: 0 } }; // Count VPN configs db.get('SELECT COUNT(*) as total FROM vpn_configs', [], (err, row) => { if (!err && row) { stats.vpnConfigs.total = row.total; // Check how many are encrypted (encrypted format has 4 colon-separated parts) db.get( `SELECT COUNT(*) as encrypted FROM vpn_configs WHERE config_data LIKE '%:%:%:%'`, [], (err, row) => { if (!err && row) stats.vpnConfigs.encrypted = row.encrypted; } ); } }); // Count settings with sensitive data db.get( `SELECT COUNT(*) as total FROM settings WHERE key IN ('api_key', 'api_secret', 'vpn_password', 'smtp_password')`, [], (err, row) => { if (!err && row) { stats.settings.total = row.total; // Count encrypted ones db.get( `SELECT COUNT(*) as encrypted FROM settings WHERE key IN ('api_key', 'api_secret', 'vpn_password', 'smtp_password') AND value LIKE '%:%:%:%'`, [], (err, row) => { if (!err && row) stats.settings.encrypted = row.encrypted; } ); } } ); // Count 2FA secrets (these should always be protected, not necessarily encrypted) db.get( 'SELECT COUNT(*) as total FROM users WHERE two_factor_secret IS NOT NULL', [], (err, row) => { if (!err && row) { stats.twoFactorSecrets.total = row.total; stats.twoFactorSecrets.encrypted = row.total; // Consider all protected } // Wait a bit for async queries to complete setTimeout(() => resolve(stats), 100); } ); }); }); } /** * Helper: Scan for unencrypted sensitive data */ async function scanForUnencryptedData() { const findings = []; return new Promise((resolve, reject) => { db.serialize(() => { // Check VPN configs db.get( `SELECT COUNT(*) as count FROM vpn_configs WHERE config_data NOT LIKE '%:%:%:%' OR config_data IS NULL`, [], (err, row) => { if (!err && row && row.count > 0) { findings.push({ table: 'vpn_configs', field: 'config_data', count: row.count, severity: 'high', description: 'VPN configuration files contain credentials and private keys' }); } } ); // Check sensitive settings db.get( `SELECT COUNT(*) as count FROM settings WHERE key IN ('api_key', 'api_secret', 'vpn_password', 'smtp_password') AND (value NOT LIKE '%:%:%:%' OR value IS NULL)`, [], (err, row) => { if (!err && row && row.count > 0) { findings.push({ table: 'settings', field: 'value', count: row.count, severity: 'medium', description: 'Settings may contain API keys, passwords, and secrets' }); } // Wait for async queries to complete setTimeout(() => resolve(findings), 100); } ); }); }); } /** * Helper: Migrate settings to encrypted format */ async function migrateSettings(sensitiveKeys) { return new Promise((resolve) => { db.all( `SELECT id, user_id, key, value FROM settings WHERE key IN (${sensitiveKeys.map(() => '?').join(',')}) AND value IS NOT NULL AND value NOT LIKE '%:%:%:%'`, sensitiveKeys, async (err, settings) => { if (err || !settings || settings.length === 0) { return resolve({ migrated: 0, error: err ? err.message : null }); } let migrated = 0; for (const setting of settings) { try { const encrypted = encryption.encryptSetting(setting.value, setting.key); await new Promise((res, rej) => { db.run( 'UPDATE settings SET value = ? WHERE id = ?', [encrypted, setting.id], (err) => (err ? rej(err) : res()) ); }); migrated++; } catch (error) { logger.error(`Failed to encrypt setting ${setting.key}:`, error); } } resolve({ migrated, error: null }); } ); }); } /** * Helper: Migrate VPN configs to new encryption format */ async function migrateVPNConfigs() { return new Promise((resolve) => { db.all( `SELECT id, user_id, config_data FROM vpn_configs WHERE config_data IS NOT NULL`, [], async (err, configs) => { if (err || !configs || configs.length === 0) { return resolve({ migrated: 0, error: err ? err.message : null }); } let migrated = 0; for (const config of configs) { try { // Check if already encrypted (new format) if (config.config_data.split(':').length === 4) { continue; // Already encrypted } // Re-encrypt using new encryption module const encrypted = encryption.encryptVPN(JSON.parse(config.config_data)); await new Promise((res, rej) => { db.run( 'UPDATE vpn_configs SET config_data = ? WHERE id = ?', [encrypted, config.id], (err) => (err ? rej(err) : res()) ); }); migrated++; } catch (error) { logger.error(`Failed to encrypt VPN config ${config.id}:`, error); } } resolve({ migrated, error: null }); } ); }); } /** * Helper: Migrate API tokens (if storing plaintext tokens - usually they're hashed) */ async function migrateAPITokens() { // API tokens are typically already hashed/encrypted, so this is a placeholder // Only migrate if you're storing plaintext tokens return Promise.resolve({ migrated: 0, error: null }); } /** * Helper: Verify encrypted data can be decrypted */ async function verifyEncryptedData() { const results = { vpnConfigs: { tested: 0, valid: 0, invalid: 0 }, settings: { tested: 0, valid: 0, invalid: 0 } }; return new Promise((resolve) => { db.serialize(() => { // Verify VPN configs db.all( `SELECT id, config_data FROM vpn_configs WHERE config_data LIKE '%:%:%:%' LIMIT 10`, [], (err, configs) => { if (!err && configs) { configs.forEach(config => { results.vpnConfigs.tested++; try { encryption.decryptVPN(config.config_data); results.vpnConfigs.valid++; } catch { results.vpnConfigs.invalid++; } }); } } ); // Verify settings db.all( `SELECT id, key, value FROM settings WHERE value LIKE '%:%:%:%' LIMIT 10`, [], (err, settings) => { if (!err && settings) { settings.forEach(setting => { results.settings.tested++; try { encryption.decryptSetting(setting.value, setting.key); results.settings.valid++; } catch { results.settings.invalid++; } }); } // Wait for async queries setTimeout(() => resolve(results), 100); } ); }); }); } module.exports = router;