/** * HVAC Testing Framework - Secure Credential Management * * Provides secure credential management with encryption, environment variable support, * and WordPress role-based authentication for the HVAC testing framework. * * Security Features: * - AES-256-GCM encryption for credential storage * - Environment variable-based configuration * - Session integrity verification * - Automatic credential rotation support * - Secure memory handling * * @author Claude Code - Emergency Security Response * @version 1.0.0 * @security CRITICAL - Handles production authentication credentials */ const fs = require('fs').promises; const path = require('path'); const crypto = require('crypto'); require('dotenv').config(); class SecureCredentialManager { constructor() { this.encryptionKey = this.getEncryptionKey(); this.credentials = new Map(); this.sessionData = new Map(); this.auditLog = []; // Initialize audit logging this.auditLogger = this.createAuditLogger(); // Validate environment configuration this.validateEnvironment(); } /** * Get or generate encryption key for credential storage * @returns {Buffer} 256-bit encryption key */ getEncryptionKey() { const keyHex = process.env.SESSION_ENCRYPTION_KEY; if (!keyHex) { throw new Error('SESSION_ENCRYPTION_KEY environment variable not set. Generate with: openssl rand -hex 32'); } if (keyHex.length !== 64) { // 32 bytes = 64 hex characters throw new Error('SESSION_ENCRYPTION_KEY must be 64 hex characters (32 bytes)'); } return Buffer.from(keyHex, 'hex'); } /** * Validate required environment variables are present */ validateEnvironment() { const required = [ 'STAGING_BASE_URL', 'MASTER_TRAINER_USERNAME', 'MASTER_TRAINER_PASSWORD', 'REGULAR_TRAINER_USERNAME', 'REGULAR_TRAINER_PASSWORD', 'SESSION_ENCRYPTION_KEY', 'JWT_SECRET' ]; const missing = required.filter(key => !process.env[key]); if (missing.length > 0) { throw new Error(`Missing required environment variables: ${missing.join(', ')}`); } this.auditLog.push({ timestamp: new Date().toISOString(), event: 'ENVIRONMENT_VALIDATED', details: 'All required environment variables present' }); } /** * Get credentials for a specific user role * @param {string} role - User role (master_trainer, master_trainer_alt, regular_trainer, admin) * @returns {Object} Encrypted credential object */ getCredentials(role) { const credentialMap = { master_trainer: { username: process.env.MASTER_TRAINER_USERNAME, password: process.env.MASTER_TRAINER_PASSWORD, email: process.env.MASTER_TRAINER_EMAIL, role: 'hvac_master_trainer' }, master_trainer_alt: { username: process.env.MASTER_TRAINER_ALT_USERNAME, password: process.env.MASTER_TRAINER_ALT_PASSWORD, email: process.env.MASTER_TRAINER_ALT_EMAIL, role: 'hvac_master_trainer' }, regular_trainer: { username: process.env.REGULAR_TRAINER_USERNAME, password: process.env.REGULAR_TRAINER_PASSWORD, email: process.env.REGULAR_TRAINER_EMAIL, role: 'hvac_trainer' }, admin: { username: process.env.ADMIN_USERNAME, password: process.env.ADMIN_PASSWORD, email: process.env.ADMIN_EMAIL, role: 'administrator' } }; const credentials = credentialMap[role]; if (!credentials) { throw new Error(`Invalid role: ${role}`); } if (!credentials.username || !credentials.password) { throw new Error(`Incomplete credentials for role: ${role}`); } this.auditLog.push({ timestamp: new Date().toISOString(), event: 'CREDENTIALS_ACCESSED', role: role, username: credentials.username }); return this.encryptCredentials(credentials); } /** * Encrypt credentials using AES-256-GCM * @param {Object} credentials - Plain text credentials * @returns {Object} Encrypted credentials with metadata */ encryptCredentials(credentials) { const iv = crypto.randomBytes(16); const cipher = crypto.createCipher('aes-256-gcm', this.encryptionKey, iv); const plaintext = JSON.stringify(credentials); let encrypted = cipher.update(plaintext, 'utf8', 'hex'); encrypted += cipher.final('hex'); const authTag = cipher.getAuthTag(); return { encrypted, iv: iv.toString('hex'), authTag: authTag.toString('hex'), algorithm: 'aes-256-gcm', created: new Date().toISOString() }; } /** * Decrypt credentials * @param {Object} encryptedData - Encrypted credential object * @returns {Object} Plain text credentials */ decryptCredentials(encryptedData) { try { const decipher = crypto.createDecipher( 'aes-256-gcm', this.encryptionKey, Buffer.from(encryptedData.iv, 'hex') ); decipher.setAuthTag(Buffer.from(encryptedData.authTag, 'hex')); let decrypted = decipher.update(encryptedData.encrypted, 'hex', 'utf8'); decrypted += decipher.final('utf8'); return JSON.parse(decrypted); } catch (error) { this.auditLog.push({ timestamp: new Date().toISOString(), event: 'DECRYPTION_FAILED', error: error.message }); throw new Error('Failed to decrypt credentials'); } } /** * Create authenticated session with integrity verification * @param {string} role - User role * @returns {Object} Session object with encrypted credentials and metadata */ createSecureSession(role) { const sessionId = crypto.randomUUID(); const credentials = this.getCredentials(role); const session = { sessionId, role, credentials, created: new Date().toISOString(), expires: new Date(Date.now() + (24 * 60 * 60 * 1000)).toISOString(), // 24 hours integrity: this.generateSessionIntegrityHash(sessionId, role, credentials) }; this.sessionData.set(sessionId, session); this.auditLog.push({ timestamp: new Date().toISOString(), event: 'SESSION_CREATED', sessionId, role, expires: session.expires }); return session; } /** * Verify session integrity and return decrypted credentials * @param {string} sessionId - Session identifier * @returns {Object} Decrypted credentials if session is valid */ getSessionCredentials(sessionId) { const session = this.sessionData.get(sessionId); if (!session) { throw new Error('Session not found'); } // Check expiration if (new Date() > new Date(session.expires)) { this.sessionData.delete(sessionId); throw new Error('Session expired'); } // Verify integrity const expectedHash = this.generateSessionIntegrityHash( sessionId, session.role, session.credentials ); if (session.integrity !== expectedHash) { this.sessionData.delete(sessionId); this.auditLog.push({ timestamp: new Date().toISOString(), event: 'SESSION_INTEGRITY_VIOLATION', sessionId }); throw new Error('Session integrity violation detected'); } return this.decryptCredentials(session.credentials); } /** * Generate session integrity hash * @param {string} sessionId * @param {string} role * @param {Object} encryptedCredentials * @returns {string} HMAC-SHA256 hash */ generateSessionIntegrityHash(sessionId, role, encryptedCredentials) { const data = JSON.stringify({ sessionId, role, encryptedCredentials }); return crypto.createHmac('sha256', this.encryptionKey) .update(data) .digest('hex'); } /** * Destroy session securely * @param {string} sessionId */ destroySession(sessionId) { if (this.sessionData.delete(sessionId)) { this.auditLog.push({ timestamp: new Date().toISOString(), event: 'SESSION_DESTROYED', sessionId }); } } /** * Get base URL with SSL/TLS validation * @returns {string} Validated base URL */ getBaseUrl() { const baseUrl = process.env.STAGING_BASE_URL; if (!baseUrl) { throw new Error('STAGING_BASE_URL environment variable not set'); } // Ensure HTTPS for production environments if (!baseUrl.startsWith('https://')) { throw new Error('Base URL must use HTTPS for security'); } return baseUrl; } /** * Get TLS validation mode * @returns {string} Validation mode (strict|permissive) */ getTLSValidationMode() { return process.env.TLS_VALIDATION_MODE || 'strict'; } /** * Create audit logger for security events * @returns {Function} Audit logging function */ createAuditLogger() { return async (event, details = {}) => { const auditEntry = { timestamp: new Date().toISOString(), event, ...details }; this.auditLog.push(auditEntry); // Write to file if enabled if (process.env.ENABLE_SECURITY_AUDIT === 'true') { try { const logFile = process.env.AUDIT_LOG_FILE || './security-audit.log'; await fs.appendFile(logFile, JSON.stringify(auditEntry) + '\n'); } catch (error) { console.warn('Failed to write security audit log:', error.message); } } }; } /** * Export audit log for security review * @returns {Array} Complete audit log */ getAuditLog() { return [...this.auditLog]; } /** * Clear sensitive data from memory (call on shutdown) */ secureCleanup() { this.credentials.clear(); this.sessionData.clear(); // Overwrite sensitive data if (this.encryptionKey) { this.encryptionKey.fill(0); } this.auditLogger('SECURE_CLEANUP_COMPLETED'); } } // Singleton instance for global use let credentialManagerInstance = null; /** * Get singleton instance of SecureCredentialManager * @returns {SecureCredentialManager} */ function getCredentialManager() { if (!credentialManagerInstance) { credentialManagerInstance = new SecureCredentialManager(); } return credentialManagerInstance; } module.exports = { SecureCredentialManager, getCredentialManager };