@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
JavaScript
/**
* đ 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;