Files
securebit-chat/src/crypto/EnhancedSecureCryptoUtils.js
lockbitchat e2316f6557 **What Changed:**
- **Removed:** All libsodium dependencies and PAKE-based authentication
- **Replaced With:** ECDH + DTLS + SAS triple-layer security system
- **Impact:** Eliminates complex PAKE implementation in favor of standardized protocols

**Security Benefits:**
-  **Simplified Architecture** - Reduced attack surface
-  **Standards Compliance** - RFC-compliant protocols
-  **Better Maintenance** - Native Web Crypto API usage
-  **Enhanced Security** - Triple-layer defense system

**New Features:**
- **Elliptic Curve Diffie-Hellman** using P-384 (secp384r1)
- **Cryptographically secure** key pair generation
- **Perfect Forward Secrecy** with session-specific keys
- **MITM resistance** requiring knowledge of both private keys
2025-09-04 17:25:01 -04:00

2558 lines
103 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
class EnhancedSecureCryptoUtils {
static _keyMetadata = new WeakMap();
// Initialize secure logging system after class definition
// Utility to sort object keys for deterministic serialization
static sortObjectKeys(obj) {
if (typeof obj !== 'object' || obj === null) {
return obj;
}
if (Array.isArray(obj)) {
return obj.map(EnhancedSecureCryptoUtils.sortObjectKeys);
}
const sortedObj = {};
Object.keys(obj).sort().forEach(key => {
sortedObj[key] = EnhancedSecureCryptoUtils.sortObjectKeys(obj[key]);
});
return sortedObj;
}
// Utility to assert CryptoKey type and properties
static assertCryptoKey(key, expectedName = null, expectedUsages = []) {
if (!(key instanceof CryptoKey)) throw new Error('Expected CryptoKey');
if (expectedName && key.algorithm?.name !== expectedName) {
throw new Error(`Expected algorithm ${expectedName}, got ${key.algorithm?.name}`);
}
for (const u of expectedUsages) {
if (!key.usages || !key.usages.includes(u)) {
throw new Error(`Missing required key usage: ${u}`);
}
}
}
// Helper function to convert ArrayBuffer to Base64
static arrayBufferToBase64(buffer) {
let binary = '';
const bytes = new Uint8Array(buffer);
const len = bytes.byteLength;
for (let i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary);
}
// Helper function to convert Base64 to ArrayBuffer
static base64ToArrayBuffer(base64) {
try {
// Validate input
if (typeof base64 !== 'string' || !base64) {
throw new Error('Invalid base64 input: must be a non-empty string');
}
// Remove any whitespace and validate base64 format
const cleanBase64 = base64.trim();
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(cleanBase64)) {
throw new Error('Invalid base64 format');
}
// Handle empty string case
if (cleanBase64 === '') {
return new ArrayBuffer(0);
}
const binaryString = atob(cleanBase64);
const len = binaryString.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes.buffer;
} catch (error) {
console.error('Base64 to ArrayBuffer conversion failed:', error.message);
throw new Error(`Base64 conversion error: ${error.message}`);
}
}
// Helper function to convert hex string to Uint8Array
static hexToUint8Array(hexString) {
try {
if (!hexString || typeof hexString !== 'string') {
throw new Error('Invalid hex string input: must be a non-empty string');
}
// Remove colons and spaces from hex string (e.g., "aa:bb:cc" -> "aabbcc")
const cleanHex = hexString.replace(/:/g, '').replace(/\s/g, '');
// Validate hex format
if (!/^[0-9a-fA-F]*$/.test(cleanHex)) {
throw new Error('Invalid hex format: contains non-hex characters');
}
// Ensure even length
if (cleanHex.length % 2 !== 0) {
throw new Error('Invalid hex format: odd length');
}
// Convert hex string to bytes
const bytes = new Uint8Array(cleanHex.length / 2);
for (let i = 0; i < cleanHex.length; i += 2) {
bytes[i / 2] = parseInt(cleanHex.substr(i, 2), 16);
}
return bytes;
} catch (error) {
console.error('Hex to Uint8Array conversion failed:', error.message);
throw new Error(`Hex conversion error: ${error.message}`);
}
}
static async encryptData(data, password) {
try {
const dataString = typeof data === 'string' ? data : JSON.stringify(data);
const salt = crypto.getRandomValues(new Uint8Array(16));
const encoder = new TextEncoder();
const passwordBuffer = encoder.encode(password);
const keyMaterial = await crypto.subtle.importKey(
'raw',
passwordBuffer,
{ name: 'PBKDF2' },
false,
['deriveKey']
);
const key = await crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt: salt,
iterations: 100000,
hash: 'SHA-256',
},
keyMaterial,
{ name: 'AES-GCM', length: 256 },
false,
['encrypt']
);
const iv = crypto.getRandomValues(new Uint8Array(12));
const dataBuffer = encoder.encode(dataString);
const encrypted = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv: iv },
key,
dataBuffer
);
const encryptedPackage = {
version: '1.0',
salt: Array.from(salt),
iv: Array.from(iv),
data: Array.from(new Uint8Array(encrypted)),
timestamp: Date.now(),
};
const packageString = JSON.stringify(encryptedPackage);
return EnhancedSecureCryptoUtils.arrayBufferToBase64(new TextEncoder().encode(packageString).buffer);
} catch (error) {
console.error('Encryption failed:', error.message);
throw new Error(`Encryption error: ${error.message}`);
}
}
static async decryptData(encryptedData, password) {
try {
const packageBuffer = EnhancedSecureCryptoUtils.base64ToArrayBuffer(encryptedData);
const packageString = new TextDecoder().decode(packageBuffer);
const encryptedPackage = JSON.parse(packageString);
if (!encryptedPackage.version || !encryptedPackage.salt || !encryptedPackage.iv || !encryptedPackage.data) {
throw new Error('Invalid encrypted data format');
}
const salt = new Uint8Array(encryptedPackage.salt);
const iv = new Uint8Array(encryptedPackage.iv);
const encrypted = new Uint8Array(encryptedPackage.data);
const encoder = new TextEncoder();
const passwordBuffer = encoder.encode(password);
const keyMaterial = await crypto.subtle.importKey(
'raw',
passwordBuffer,
{ name: 'PBKDF2' },
false,
['deriveKey']
);
const key = await crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt: salt,
iterations: 100000,
hash: 'SHA-256'
},
keyMaterial,
{ name: 'AES-GCM', length: 256 },
false,
['decrypt']
);
const decrypted = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv },
key,
encrypted
);
const decryptedString = new TextDecoder().decode(decrypted);
try {
return JSON.parse(decryptedString);
} catch {
return decryptedString;
}
} catch (error) {
console.error('Decryption failed:', error.message);
throw new Error(`Decryption error: ${error.message}`);
}
}
// Generate secure password for data exchange
static generateSecurePassword() {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+-=[]{}|;:,.<>?';
const length = 32;
const randomValues = new Uint32Array(length);
crypto.getRandomValues(randomValues);
let password = '';
for (let i = 0; i < length; i++) {
password += chars[randomValues[i] % chars.length];
}
return password;
}
// Real security level calculation with actual verification
static async calculateSecurityLevel(securityManager) {
let score = 0;
const maxScore = 100; // Fixed: Changed from 110 to 100 for cleaner percentage
const verificationResults = {};
try {
// Fallback to basic calculation if securityManager is not fully initialized
if (!securityManager || !securityManager.securityFeatures) {
console.warn('Security manager not fully initialized, using fallback calculation');
return {
level: 'INITIALIZING',
score: 0,
color: 'gray',
verificationResults: {},
timestamp: Date.now(),
details: 'Security system initializing...',
isRealData: false
};
}
// Check session type to determine available features
const sessionType = securityManager.currentSessionType || 'demo';
const isDemoSession = sessionType === 'demo';
// 1. Base encryption verification (20 points) - Available in demo
try {
if (await EnhancedSecureCryptoUtils.verifyEncryption(securityManager)) {
score += 20;
verificationResults.encryption = { passed: true, details: 'AES-GCM encryption verified', points: 20 };
} else {
verificationResults.encryption = { passed: false, details: 'Encryption not working', points: 0 };
}
} catch (error) {
verificationResults.encryption = { passed: false, details: `Encryption check failed: ${error.message}`, points: 0 };
}
// 2. Simple key exchange verification (15 points) - Available in demo
try {
if (await EnhancedSecureCryptoUtils.verifyECDHKeyExchange(securityManager)) {
score += 15;
verificationResults.keyExchange = { passed: true, details: 'Simple key exchange verified', points: 15 };
} else {
verificationResults.keyExchange = { passed: false, details: 'Key exchange failed', points: 0 };
}
} catch (error) {
verificationResults.keyExchange = { passed: false, details: `Key exchange check failed: ${error.message}`, points: 0 };
}
// 3. Message integrity verification (10 points) - Available in demo
if (await EnhancedSecureCryptoUtils.verifyMessageIntegrity(securityManager)) {
score += 10;
verificationResults.messageIntegrity = { passed: true, details: 'Message integrity verified', points: 10 };
} else {
verificationResults.messageIntegrity = { passed: false, details: 'Message integrity failed', points: 0 };
}
// 4. Rate limiting verification (5 points) - Available in demo
if (await EnhancedSecureCryptoUtils.verifyRateLimiting(securityManager)) {
score += 5;
verificationResults.rateLimiting = { passed: true, details: 'Rate limiting active', points: 5 };
} else {
verificationResults.rateLimiting = { passed: false, details: 'Rate limiting not working', points: 0 };
}
// 5. ECDSA signatures verification (15 points) - Only for enhanced sessions
if (!isDemoSession && await EnhancedSecureCryptoUtils.verifyECDSASignatures(securityManager)) {
score += 15;
verificationResults.ecdsa = { passed: true, details: 'ECDSA signatures verified', points: 15 };
} else {
const reason = isDemoSession ? 'Enhanced session required - feature not available' : 'ECDSA signatures failed';
verificationResults.ecdsa = { passed: false, details: reason, points: 0 };
}
// 6. Metadata protection verification (10 points) - Only for enhanced sessions
if (!isDemoSession && await EnhancedSecureCryptoUtils.verifyMetadataProtection(securityManager)) {
score += 10;
verificationResults.metadataProtection = { passed: true, details: 'Metadata protection verified', points: 10 };
} else {
const reason = isDemoSession ? 'Enhanced session required - feature not available' : 'Metadata protection failed';
verificationResults.metadataProtection = { passed: false, details: reason, points: 0 };
}
// 7. Perfect Forward Secrecy verification (10 points) - Only for enhanced sessions
if (!isDemoSession && await EnhancedSecureCryptoUtils.verifyPFS(securityManager)) {
score += 10;
verificationResults.pfs = { passed: true, details: 'Perfect Forward Secrecy active', points: 10 };
} else {
const reason = isDemoSession ? 'Enhanced session required - feature not available' : 'PFS not active';
verificationResults.pfs = { passed: false, details: reason, points: 0 };
}
// 8. Nested encryption verification (5 points) - Only for enhanced sessions
if (!isDemoSession && await EnhancedSecureCryptoUtils.verifyNestedEncryption(securityManager)) {
score += 5;
verificationResults.nestedEncryption = { passed: true, details: 'Nested encryption active', points: 5 };
} else {
const reason = isDemoSession ? 'Enhanced session required - feature not available' : 'Nested encryption failed';
verificationResults.nestedEncryption = { passed: false, details: reason, points: 0 };
}
// 9. Packet padding verification (5 points) - Only for enhanced sessions
if (!isDemoSession && await EnhancedSecureCryptoUtils.verifyPacketPadding(securityManager)) {
score += 5;
verificationResults.packetPadding = { passed: true, details: 'Packet padding active', points: 5 };
} else {
const reason = isDemoSession ? 'Enhanced session required - feature not available' : 'Packet padding failed';
verificationResults.packetPadding = { passed: false, details: reason, points: 0 };
}
// 10. Advanced features verification (10 points) - Only for premium sessions
if (sessionType === 'premium' && await EnhancedSecureCryptoUtils.verifyAdvancedFeatures(securityManager)) {
score += 10;
verificationResults.advancedFeatures = { passed: true, details: 'Advanced features active', points: 10 };
} else {
const reason = sessionType === 'demo' ? 'Premium session required - feature not available' :
sessionType === 'basic' ? 'Premium session required - feature not available' : 'Advanced features failed';
verificationResults.advancedFeatures = { passed: false, details: reason, points: 0 };
}
const percentage = Math.round((score / maxScore) * 100);
// Calculate available checks based on session type
const availableChecks = isDemoSession ? 4 : 10; // Demo: encryption(20) + key exchange(15) + message integrity(10) + rate limiting(5) = 50 points
const passedChecks = Object.values(verificationResults).filter(r => r.passed).length;
const result = {
level: percentage >= 85 ? 'HIGH' : percentage >= 65 ? 'MEDIUM' : percentage >= 35 ? 'LOW' : 'CRITICAL',
score: percentage,
color: percentage >= 85 ? 'green' : percentage >= 65 ? 'orange' : percentage >= 35 ? 'yellow' : 'red',
verificationResults,
timestamp: Date.now(),
details: `Real verification: ${score}/${maxScore} security checks passed (${passedChecks}/${availableChecks} available)`,
isRealData: true,
passedChecks: passedChecks,
totalChecks: availableChecks,
sessionType: sessionType,
maxPossibleScore: isDemoSession ? 50 : 100 // Demo sessions can only get max 50 points (4 checks)
};
console.log('Real security level calculated:', {
score: percentage,
level: result.level,
passedChecks: passedChecks,
totalChecks: availableChecks,
sessionType: sessionType,
maxPossibleScore: result.maxPossibleScore
});
return result;
} catch (error) {
console.error('Security level calculation failed:', error.message);
return {
level: 'UNKNOWN',
score: 0,
color: 'red',
verificationResults: {},
timestamp: Date.now(),
details: `Verification failed: ${error.message}`,
isRealData: false
};
}
}
// Real verification functions
static async verifyEncryption(securityManager) {
try {
if (!securityManager.encryptionKey) return false;
// Test actual encryption/decryption
const testData = 'Test encryption verification';
const encoder = new TextEncoder();
const testBuffer = encoder.encode(testData);
const iv = crypto.getRandomValues(new Uint8Array(12));
const encrypted = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
securityManager.encryptionKey,
testBuffer
);
const decrypted = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv },
securityManager.encryptionKey,
encrypted
);
const decryptedText = new TextDecoder().decode(decrypted);
return decryptedText === testData;
} catch (error) {
console.error('Encryption verification failed:', error.message);
return false;
}
}
static async verifyECDHKeyExchange(securityManager) {
try {
if (!securityManager.ecdhKeyPair || !securityManager.ecdhKeyPair.privateKey || !securityManager.ecdhKeyPair.publicKey) {
return false;
}
// Test that keys are actually ECDH keys
const keyType = securityManager.ecdhKeyPair.privateKey.algorithm.name;
const curve = securityManager.ecdhKeyPair.privateKey.algorithm.namedCurve;
return keyType === 'ECDH' && (curve === 'P-384' || curve === 'P-256');
} catch (error) {
console.error('ECDH verification failed:', error.message);
return false;
}
}
static async verifyECDSASignatures(securityManager) {
try {
if (!securityManager.ecdsaKeyPair || !securityManager.ecdsaKeyPair.privateKey || !securityManager.ecdsaKeyPair.publicKey) {
return false;
}
// Test actual signing and verification
const testData = 'Test ECDSA signature verification';
const encoder = new TextEncoder();
const testBuffer = encoder.encode(testData);
const signature = await crypto.subtle.sign(
{ name: 'ECDSA', hash: 'SHA-256' },
securityManager.ecdsaKeyPair.privateKey,
testBuffer
);
const isValid = await crypto.subtle.verify(
{ name: 'ECDSA', hash: 'SHA-256' },
securityManager.ecdsaKeyPair.publicKey,
signature,
testBuffer
);
return isValid;
} catch (error) {
console.error('ECDSA verification failed:', error.message);
return false;
}
}
static async verifyMessageIntegrity(securityManager) {
try {
// Check if macKey exists and is a valid CryptoKey
if (!securityManager.macKey || !(securityManager.macKey instanceof CryptoKey)) {
console.warn('MAC key not available or invalid for message integrity verification');
return false;
}
// Test message integrity with HMAC
const testData = 'Test message integrity verification';
const encoder = new TextEncoder();
const testBuffer = encoder.encode(testData);
const hmac = await crypto.subtle.sign(
{ name: 'HMAC', hash: 'SHA-256' },
securityManager.macKey,
testBuffer
);
const isValid = await crypto.subtle.verify(
{ name: 'HMAC', hash: 'SHA-256' },
securityManager.macKey,
hmac,
testBuffer
);
return isValid;
} catch (error) {
console.error('Message integrity verification failed:', error.message);
return false;
}
}
static async verifyNestedEncryption(securityManager) {
try {
// Check if nestedEncryptionKey exists and is a valid CryptoKey
if (!securityManager.nestedEncryptionKey || !(securityManager.nestedEncryptionKey instanceof CryptoKey)) {
console.warn('Nested encryption key not available or invalid');
return false;
}
// Test nested encryption
const testData = 'Test nested encryption verification';
const encoder = new TextEncoder();
const testBuffer = encoder.encode(testData);
// Simulate nested encryption
const encrypted = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv: crypto.getRandomValues(new Uint8Array(12)) },
securityManager.nestedEncryptionKey,
testBuffer
);
return encrypted && encrypted.byteLength > 0;
} catch (error) {
console.error('Nested encryption verification failed:', error.message);
return false;
}
}
static async verifyPacketPadding(securityManager) {
try {
if (!securityManager.paddingConfig || !securityManager.paddingConfig.enabled) return false;
// Test packet padding functionality
const testData = 'Test packet padding verification';
const encoder = new TextEncoder();
const testBuffer = encoder.encode(testData);
// Simulate packet padding
const paddingSize = Math.floor(Math.random() * (securityManager.paddingConfig.maxPadding - securityManager.paddingConfig.minPadding)) + securityManager.paddingConfig.minPadding;
const paddedData = new Uint8Array(testBuffer.byteLength + paddingSize);
paddedData.set(new Uint8Array(testBuffer), 0);
return paddedData.byteLength >= testBuffer.byteLength + securityManager.paddingConfig.minPadding;
} catch (error) {
EnhancedSecureCryptoUtils.secureLog.log('error', 'Packet padding verification failed', { error: error.message });
return false;
}
}
static async verifyAdvancedFeatures(securityManager) {
try {
// Test advanced features like traffic obfuscation, fake traffic, etc.
const hasFakeTraffic = securityManager.fakeTrafficConfig && securityManager.fakeTrafficConfig.enabled;
const hasDecoyChannels = securityManager.decoyChannelsConfig && securityManager.decoyChannelsConfig.enabled;
const hasAntiFingerprinting = securityManager.antiFingerprintingConfig && securityManager.antiFingerprintingConfig.enabled;
return hasFakeTraffic || hasDecoyChannels || hasAntiFingerprinting;
} catch (error) {
EnhancedSecureCryptoUtils.secureLog.log('error', 'Advanced features verification failed', { error: error.message });
return false;
}
}
static async verifyMutualAuth(securityManager) {
try {
if (!securityManager.isVerified || !securityManager.verificationCode) return false;
// Test mutual authentication
return securityManager.isVerified && securityManager.verificationCode.length > 0;
} catch (error) {
EnhancedSecureCryptoUtils.secureLog.log('error', 'Mutual auth verification failed', { error: error.message });
return false;
}
}
static async verifyMetadataProtection(securityManager) {
try {
if (!securityManager.metadataKey) return false;
// Test metadata protection
const testData = 'Test metadata protection verification';
const encoder = new TextEncoder();
const testBuffer = encoder.encode(testData);
const encrypted = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv: crypto.getRandomValues(new Uint8Array(12)) },
securityManager.metadataKey,
testBuffer
);
return encrypted && encrypted.byteLength > 0;
} catch (error) {
EnhancedSecureCryptoUtils.secureLog.log('error', 'Metadata protection verification failed', { error: error.message });
return false;
}
}
static async verifyReplayProtection(securityManager) {
try {
if (!securityManager.processedMessageIds || !securityManager.sequenceNumber) return false;
// Test replay protection
const testId = Date.now().toString();
if (securityManager.processedMessageIds.has(testId)) return false;
securityManager.processedMessageIds.add(testId);
return true;
} catch (error) {
EnhancedSecureCryptoUtils.secureLog.log('error', 'Replay protection verification failed', { error: error.message });
return false;
}
}
static async verifyNonExtractableKeys(securityManager) {
try {
if (!securityManager.encryptionKey) return false;
// Test if keys are non-extractable
const keyData = await crypto.subtle.exportKey('raw', securityManager.encryptionKey);
return keyData && keyData.byteLength > 0;
} catch (error) {
// If export fails, keys are non-extractable (which is good)
return true;
}
}
static async verifyEnhancedValidation(securityManager) {
try {
if (!securityManager.securityFeatures) return false;
// Test enhanced validation features
const hasValidation = securityManager.securityFeatures.hasEnhancedValidation ||
securityManager.securityFeatures.hasEnhancedReplayProtection;
return hasValidation;
} catch (error) {
EnhancedSecureCryptoUtils.secureLog.log('error', 'Enhanced validation verification failed', { error: error.message });
return false;
}
}
static async verifyRateLimiting(securityManager) {
try {
const testId = 'test_' + Date.now();
const canProceed = await EnhancedSecureCryptoUtils.rateLimiter.checkMessageRate(testId, 1, 60000);
return securityManager.rateLimiterId &&
EnhancedSecureCryptoUtils.rateLimiter &&
typeof EnhancedSecureCryptoUtils.rateLimiter.checkMessageRate === 'function' &&
canProceed === true;
} catch (error) {
EnhancedSecureCryptoUtils.secureLog.log('error', 'Rate limiting verification failed', { error: error.message });
return false;
}
}
static async verifyPFS(securityManager) {
try {
// Check if PFS is active
return securityManager.securityFeatures &&
securityManager.securityFeatures.hasPFS === true &&
securityManager.keyRotationInterval &&
securityManager.currentKeyVersion !== undefined &&
securityManager.keyVersions &&
securityManager.keyVersions instanceof Map;
} catch (error) {
EnhancedSecureCryptoUtils.secureLog.log('error', 'PFS verification failed', { error: error.message });
return false;
}
}
// Rate limiting implementation
static rateLimiter = {
messages: new Map(),
connections: new Map(),
locks: new Map(),
async checkMessageRate(identifier, limit = 60, windowMs = 60000) {
if (typeof identifier !== 'string' || identifier.length > 256) {
return false;
}
const key = `msg_${identifier}`;
if (this.locks.has(key)) {
await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 10) + 5));
return this.checkMessageRate(identifier, limit, windowMs);
}
this.locks.set(key, true);
try {
const now = Date.now();
if (!this.messages.has(key)) {
this.messages.set(key, []);
}
const timestamps = this.messages.get(key);
const validTimestamps = timestamps.filter(ts => now - ts < windowMs);
if (validTimestamps.length >= limit) {
return false;
}
validTimestamps.push(now);
this.messages.set(key, validTimestamps);
return true;
} finally {
this.locks.delete(key);
}
},
async checkConnectionRate(identifier, limit = 5, windowMs = 300000) {
if (typeof identifier !== 'string' || identifier.length > 256) {
return false;
}
const key = `conn_${identifier}`;
if (this.locks.has(key)) {
await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 10) + 5));
return this.checkConnectionRate(identifier, limit, windowMs);
}
this.locks.set(key, true);
try {
const now = Date.now();
if (!this.connections.has(key)) {
this.connections.set(key, []);
}
const timestamps = this.connections.get(key);
const validTimestamps = timestamps.filter(ts => now - ts < windowMs);
if (validTimestamps.length >= limit) {
return false;
}
validTimestamps.push(now);
this.connections.set(key, validTimestamps);
return true;
} finally {
this.locks.delete(key);
}
},
cleanup() {
const now = Date.now();
const maxAge = 3600000;
for (const [key, timestamps] of this.messages.entries()) {
if (this.locks.has(key)) continue;
const valid = timestamps.filter(ts => now - ts < maxAge);
if (valid.length === 0) {
this.messages.delete(key);
} else {
this.messages.set(key, valid);
}
}
for (const [key, timestamps] of this.connections.entries()) {
if (this.locks.has(key)) continue;
const valid = timestamps.filter(ts => now - ts < maxAge);
if (valid.length === 0) {
this.connections.delete(key);
} else {
this.connections.set(key, valid);
}
}
for (const lockKey of this.locks.keys()) {
const keyTimestamp = parseInt(lockKey.split('_').pop()) || 0;
if (now - keyTimestamp > 30000) {
this.locks.delete(lockKey);
}
}
}
};
static validateSalt(salt) {
if (!salt || salt.length !== 64) {
throw new Error('Salt must be exactly 64 bytes');
}
const uniqueBytes = new Set(salt);
if (uniqueBytes.size < 16) {
throw new Error('Salt has insufficient entropy');
}
return true;
}
// Secure logging without data leaks
static secureLog = {
logs: [],
maxLogs: 100,
isProductionMode: false,
// Initialize production mode detection
init() {
this.isProductionMode = this._detectProductionMode();
if (this.isProductionMode) {
console.log('[SecureChat] Production mode detected - sensitive logging disabled');
}
},
_detectProductionMode() {
return (
(typeof process !== 'undefined' && process.env?.NODE_ENV === 'production') ||
(!window.DEBUG_MODE && !window.DEVELOPMENT_MODE) ||
(window.location.hostname && !window.location.hostname.includes('localhost') &&
!window.location.hostname.includes('127.0.0.1') &&
!window.location.hostname.includes('.local')) ||
(typeof window.webpackHotUpdate === 'undefined' && !window.location.search.includes('debug'))
);
},
log(level, message, context = {}) {
const sanitizedContext = this.sanitizeContext(context);
const logEntry = {
timestamp: Date.now(),
level,
message,
context: sanitizedContext,
id: crypto.getRandomValues(new Uint32Array(1))[0]
};
this.logs.push(logEntry);
// Keep only recent logs
if (this.logs.length > this.maxLogs) {
this.logs = this.logs.slice(-this.maxLogs);
}
// Production-safe console output
if (this.isProductionMode) {
if (level === 'error') {
// В production показываем только код ошибки без деталей
console.error(`❌ [SecureChat] ${message} [ERROR_CODE: ${this._generateErrorCode(message)}]`);
} else if (level === 'warn') {
// В production показываем только предупреждение без контекста
console.warn(`⚠️ [SecureChat] ${message}`);
} else {
// В production не показываем info/debug логи
return;
}
} else {
// Development mode - показываем все
if (level === 'error') {
console.error(`❌ [SecureChat] ${message}`, { errorType: sanitizedContext?.constructor?.name || 'Unknown' });
} else if (level === 'warn') {
console.warn(`⚠️ [SecureChat] ${message}`, { details: sanitizedContext });
} else {
console.log(`[SecureChat] ${message}`, sanitizedContext);
}
}
},
// Генерирует безопасный код ошибки для production
_generateErrorCode(message) {
const hash = message.split('').reduce((a, b) => {
a = ((a << 5) - a) + b.charCodeAt(0);
return a & a;
}, 0);
return Math.abs(hash).toString(36).substring(0, 6).toUpperCase();
},
sanitizeContext(context) {
if (!context || typeof context !== 'object') {
return context;
}
const sensitivePatterns = [
/key/i, /secret/i, /password/i, /token/i, /signature/i,
/challenge/i, /proof/i, /salt/i, /iv/i, /nonce/i, /hash/i,
/fingerprint/i, /mac/i, /private/i, /encryption/i, /decryption/i
];
const sanitized = {};
for (const [key, value] of Object.entries(context)) {
const isSensitive = sensitivePatterns.some(pattern =>
pattern.test(key) || (typeof value === 'string' && pattern.test(value))
);
if (isSensitive) {
sanitized[key] = '[REDACTED]';
} else if (typeof value === 'string' && value.length > 100) {
sanitized[key] = value.substring(0, 100) + '...[TRUNCATED]';
} else if (value instanceof ArrayBuffer || value instanceof Uint8Array) {
sanitized[key] = `[${value.constructor.name}(${value.byteLength || value.length} bytes)]`;
} else if (value && typeof value === 'object' && !Array.isArray(value)) {
// Рекурсивная санитизация для объектов
sanitized[key] = this.sanitizeContext(value);
} else {
sanitized[key] = value;
}
}
return sanitized;
},
getLogs(level = null) {
if (level) {
return this.logs.filter(log => log.level === level);
}
return [...this.logs];
},
clearLogs() {
this.logs = [];
},
// Метод для отправки ошибок на сервер в production
async sendErrorToServer(errorCode, message, context = {}) {
if (!this.isProductionMode) {
return; // В development не отправляем
}
try {
// Отправляем только безопасную информацию
const safeErrorData = {
errorCode,
timestamp: Date.now(),
userAgent: navigator.userAgent.substring(0, 100),
url: window.location.href.substring(0, 100)
};
// Здесь можно добавить отправку на сервер
// await fetch('/api/error-log', { method: 'POST', body: JSON.stringify(safeErrorData) });
if (window.DEBUG_MODE) {
console.log('[SecureChat] Error logged to server:', safeErrorData);
}
} catch (e) {
// Не логируем ошибки логирования
}
}
};
// Generate ECDH key pair for secure key exchange (non-extractable) with fallback
static async generateECDHKeyPair() {
try {
// Try P-384 first
try {
const keyPair = await crypto.subtle.generateKey(
{
name: 'ECDH',
namedCurve: 'P-384'
},
false, // Non-extractable for enhanced security
['deriveKey']
);
EnhancedSecureCryptoUtils.secureLog.log('info', 'ECDH key pair generated successfully (P-384)', {
curve: 'P-384',
extractable: false
});
return keyPair;
} catch (p384Error) {
EnhancedSecureCryptoUtils.secureLog.log('warn', 'P-384 generation failed, trying P-256', { error: p384Error.message });
// Fallback to P-256
const keyPair = await crypto.subtle.generateKey(
{
name: 'ECDH',
namedCurve: 'P-256'
},
false, // Non-extractable for enhanced security
['deriveKey']
);
EnhancedSecureCryptoUtils.secureLog.log('info', 'ECDH key pair generated successfully (P-256 fallback)', {
curve: 'P-256',
extractable: false
});
return keyPair;
}
} catch (error) {
EnhancedSecureCryptoUtils.secureLog.log('error', 'ECDH key generation failed', { error: error.message });
throw new Error('Failed to create keys for secure exchange');
}
}
// Generate ECDSA key pair for digital signatures with fallback
static async generateECDSAKeyPair() {
try {
// Try P-384 first
try {
const keyPair = await crypto.subtle.generateKey(
{
name: 'ECDSA',
namedCurve: 'P-384'
},
false, // Non-extractable for enhanced security
['sign', 'verify']
);
EnhancedSecureCryptoUtils.secureLog.log('info', 'ECDSA key pair generated successfully (P-384)', {
curve: 'P-384',
extractable: false
});
return keyPair;
} catch (p384Error) {
EnhancedSecureCryptoUtils.secureLog.log('warn', 'P-384 generation failed, trying P-256', { error: p384Error.message });
// Fallback to P-256
const keyPair = await crypto.subtle.generateKey(
{
name: 'ECDSA',
namedCurve: 'P-256'
},
false, // Non-extractable for enhanced security
['sign', 'verify']
);
EnhancedSecureCryptoUtils.secureLog.log('info', 'ECDSA key pair generated successfully (P-256 fallback)', {
curve: 'P-256',
extractable: false
});
return keyPair;
}
} catch (error) {
EnhancedSecureCryptoUtils.secureLog.log('error', 'ECDSA key generation failed', { error: error.message });
throw new Error('Failed to generate keys for digital signatures');
}
}
// Sign data with ECDSA (P-384 or P-256)
static async signData(privateKey, data) {
try {
const encoder = new TextEncoder();
const dataBuffer = typeof data === 'string' ? encoder.encode(data) : data;
// Try SHA-384 first, fallback to SHA-256
try {
const signature = await crypto.subtle.sign(
{
name: 'ECDSA',
hash: 'SHA-384'
},
privateKey,
dataBuffer
);
return Array.from(new Uint8Array(signature));
} catch (sha384Error) {
EnhancedSecureCryptoUtils.secureLog.log('warn', 'SHA-384 signing failed, trying SHA-256', { error: sha384Error.message });
const signature = await crypto.subtle.sign(
{
name: 'ECDSA',
hash: 'SHA-256'
},
privateKey,
dataBuffer
);
return Array.from(new Uint8Array(signature));
}
} catch (error) {
EnhancedSecureCryptoUtils.secureLog.log('error', 'Data signing failed', { error: error.message });
throw new Error('Failed to sign data');
}
}
// Verify ECDSA signature (P-384 or P-256)
static async verifySignature(publicKey, signature, data) {
try {
const encoder = new TextEncoder();
const dataBuffer = typeof data === 'string' ? encoder.encode(data) : data;
const signatureBuffer = new Uint8Array(signature);
// Try SHA-384 first, fallback to SHA-256
try {
const isValid = await crypto.subtle.verify(
{
name: 'ECDSA',
hash: 'SHA-384'
},
publicKey,
signatureBuffer,
dataBuffer
);
EnhancedSecureCryptoUtils.secureLog.log('info', 'Signature verification completed (SHA-384)', {
isValid,
dataSize: dataBuffer.length
});
return isValid;
} catch (sha384Error) {
EnhancedSecureCryptoUtils.secureLog.log('warn', 'SHA-384 verification failed, trying SHA-256', { error: sha384Error.message });
const isValid = await crypto.subtle.verify(
{
name: 'ECDSA',
hash: 'SHA-256'
},
publicKey,
signatureBuffer,
dataBuffer
);
EnhancedSecureCryptoUtils.secureLog.log('info', 'Signature verification completed (SHA-256 fallback)', {
isValid,
dataSize: dataBuffer.length
});
return isValid;
}
} catch (error) {
EnhancedSecureCryptoUtils.secureLog.log('error', 'Signature verification failed', { error: error.message });
throw new Error('Failed to verify digital signature');
}
}
// Enhanced DER/SPKI validation with full ASN.1 parsing
static async validateKeyStructure(keyData, expectedAlgorithm = 'ECDH') {
try {
if (!Array.isArray(keyData) || keyData.length === 0) {
throw new Error('Invalid key data format');
}
const keyBytes = new Uint8Array(keyData);
// Size limits to prevent DoS
if (keyBytes.length < 50) {
throw new Error('Key data too short - invalid SPKI structure');
}
if (keyBytes.length > 2000) {
throw new Error('Key data too long - possible attack');
}
// Parse ASN.1 DER structure
const asn1 = EnhancedSecureCryptoUtils.parseASN1(keyBytes);
// Validate SPKI structure
if (!asn1 || asn1.tag !== 0x30) {
throw new Error('Invalid SPKI structure - missing SEQUENCE tag');
}
// SPKI should have exactly 2 elements: AlgorithmIdentifier and BIT STRING
if (asn1.children.length !== 2) {
throw new Error(`Invalid SPKI structure - expected 2 elements, got ${asn1.children.length}`);
}
// Validate AlgorithmIdentifier
const algIdentifier = asn1.children[0];
if (algIdentifier.tag !== 0x30) {
throw new Error('Invalid AlgorithmIdentifier - not a SEQUENCE');
}
// Parse algorithm OID
const algOid = algIdentifier.children[0];
if (algOid.tag !== 0x06) {
throw new Error('Invalid algorithm OID - not an OBJECT IDENTIFIER');
}
// Validate algorithm OID based on expected algorithm
const oidBytes = algOid.value;
const oidString = EnhancedSecureCryptoUtils.oidToString(oidBytes);
// Check for expected algorithms
const validAlgorithms = {
'ECDH': ['1.2.840.10045.2.1'], // id-ecPublicKey
'ECDSA': ['1.2.840.10045.2.1'], // id-ecPublicKey (same as ECDH)
'RSA': ['1.2.840.113549.1.1.1'], // rsaEncryption
'AES-GCM': ['2.16.840.1.101.3.4.1.6', '2.16.840.1.101.3.4.1.46'] // AES-128-GCM, AES-256-GCM
};
const expectedOids = validAlgorithms[expectedAlgorithm];
if (!expectedOids) {
throw new Error(`Unknown algorithm: ${expectedAlgorithm}`);
}
if (!expectedOids.includes(oidString)) {
throw new Error(`Invalid algorithm OID: expected ${expectedOids.join(' or ')}, got ${oidString}`);
}
// For EC algorithms, validate curve parameters
if (expectedAlgorithm === 'ECDH' || expectedAlgorithm === 'ECDSA') {
if (algIdentifier.children.length < 2) {
throw new Error('Missing curve parameters for EC key');
}
const curveOid = algIdentifier.children[1];
if (curveOid.tag !== 0x06) {
throw new Error('Invalid curve OID - not an OBJECT IDENTIFIER');
}
const curveOidString = EnhancedSecureCryptoUtils.oidToString(curveOid.value);
// Only allow P-256 and P-384 curves
const validCurves = {
'1.2.840.10045.3.1.7': 'P-256', // secp256r1
'1.3.132.0.34': 'P-384' // secp384r1
};
if (!validCurves[curveOidString]) {
throw new Error(`Invalid or unsupported curve OID: ${curveOidString}`);
}
EnhancedSecureCryptoUtils.secureLog.log('info', 'EC key curve validated', {
curve: validCurves[curveOidString],
oid: curveOidString
});
}
// Validate public key BIT STRING
const publicKeyBitString = asn1.children[1];
if (publicKeyBitString.tag !== 0x03) {
throw new Error('Invalid public key - not a BIT STRING');
}
// Check for unused bits (should be 0 for public keys)
if (publicKeyBitString.value[0] !== 0x00) {
throw new Error(`Invalid BIT STRING - unexpected unused bits: ${publicKeyBitString.value[0]}`);
}
// For EC keys, validate point format
if (expectedAlgorithm === 'ECDH' || expectedAlgorithm === 'ECDSA') {
const pointData = publicKeyBitString.value.slice(1); // Skip unused bits byte
// Check for uncompressed point format (0x04)
if (pointData[0] !== 0x04) {
throw new Error(`Invalid EC point format: expected uncompressed (0x04), got 0x${pointData[0].toString(16)}`);
}
// Validate point size based on curve
const expectedSizes = {
'P-256': 65, // 1 + 32 + 32
'P-384': 97 // 1 + 48 + 48
};
// We already validated the curve above, so we can determine expected size
const curveOidString = EnhancedSecureCryptoUtils.oidToString(algIdentifier.children[1].value);
const curveName = curveOidString === '1.2.840.10045.3.1.7' ? 'P-256' : 'P-384';
const expectedSize = expectedSizes[curveName];
if (pointData.length !== expectedSize) {
throw new Error(`Invalid EC point size for ${curveName}: expected ${expectedSize}, got ${pointData.length}`);
}
}
// Additional validation: try to import the key
try {
const algorithm = expectedAlgorithm === 'ECDSA' || expectedAlgorithm === 'ECDH'
? { name: expectedAlgorithm, namedCurve: 'P-384' }
: { name: expectedAlgorithm };
const usages = expectedAlgorithm === 'ECDSA' ? ['verify'] : [];
await crypto.subtle.importKey('spki', keyBytes.buffer, algorithm, false, usages);
} catch (importError) {
// Try P-256 as fallback for EC keys
if (expectedAlgorithm === 'ECDSA' || expectedAlgorithm === 'ECDH') {
try {
const algorithm = { name: expectedAlgorithm, namedCurve: 'P-256' };
const usages = expectedAlgorithm === 'ECDSA' ? ['verify'] : [];
await crypto.subtle.importKey('spki', keyBytes.buffer, algorithm, false, usages);
} catch (fallbackError) {
throw new Error(`Key import validation failed: ${fallbackError.message}`);
}
} else {
throw new Error(`Key import validation failed: ${importError.message}`);
}
}
EnhancedSecureCryptoUtils.secureLog.log('info', 'Key structure validation passed', {
keyLen: keyBytes.length,
algorithm: expectedAlgorithm,
asn1Valid: true,
oidValid: true,
importValid: true
});
return true;
} catch (err) {
EnhancedSecureCryptoUtils.secureLog.log('error', 'Key structure validation failed', {
error: err.message,
algorithm: expectedAlgorithm
});
throw new Error(`Invalid key structure: ${err.message}`);
}
}
// ASN.1 DER parser helper
static parseASN1(bytes, offset = 0) {
if (offset >= bytes.length) {
return null;
}
const tag = bytes[offset];
let lengthOffset = offset + 1;
if (lengthOffset >= bytes.length) {
throw new Error('Truncated ASN.1 structure');
}
let length = bytes[lengthOffset];
let valueOffset = lengthOffset + 1;
// Handle long form length
if (length & 0x80) {
const numLengthBytes = length & 0x7f;
if (numLengthBytes > 4) {
throw new Error('ASN.1 length too large');
}
length = 0;
for (let i = 0; i < numLengthBytes; i++) {
if (valueOffset + i >= bytes.length) {
throw new Error('Truncated ASN.1 length');
}
length = (length << 8) | bytes[valueOffset + i];
}
valueOffset += numLengthBytes;
}
if (valueOffset + length > bytes.length) {
throw new Error('ASN.1 structure extends beyond data');
}
const value = bytes.slice(valueOffset, valueOffset + length);
const node = {
tag: tag,
length: length,
value: value,
children: []
};
// Parse children for SEQUENCE and SET
if (tag === 0x30 || tag === 0x31) {
let childOffset = 0;
while (childOffset < value.length) {
const child = EnhancedSecureCryptoUtils.parseASN1(value, childOffset);
if (!child) break;
node.children.push(child);
childOffset = childOffset + 1 + child.lengthBytes + child.length;
}
}
// Calculate how many bytes were used for length encoding
node.lengthBytes = valueOffset - lengthOffset;
return node;
}
// OID decoder helper
static oidToString(bytes) {
if (!bytes || bytes.length === 0) {
throw new Error('Empty OID');
}
const parts = [];
// First byte encodes first two components
const first = Math.floor(bytes[0] / 40);
const second = bytes[0] % 40;
parts.push(first);
parts.push(second);
// Decode remaining components
let value = 0;
for (let i = 1; i < bytes.length; i++) {
value = (value << 7) | (bytes[i] & 0x7f);
if (!(bytes[i] & 0x80)) {
parts.push(value);
value = 0;
}
}
return parts.join('.');
}
// Helper to validate and sanitize OID string
static validateOidString(oidString) {
// OID format: digits separated by dots
const oidRegex = /^[0-9]+(\.[0-9]+)*$/;
if (!oidRegex.test(oidString)) {
throw new Error(`Invalid OID format: ${oidString}`);
}
const parts = oidString.split('.').map(Number);
// First component must be 0, 1, or 2
if (parts[0] > 2) {
throw new Error(`Invalid OID first component: ${parts[0]}`);
}
// If first component is 0 or 1, second must be <= 39
if ((parts[0] === 0 || parts[0] === 1) && parts[1] > 39) {
throw new Error(`Invalid OID second component: ${parts[1]} (must be <= 39 for first component ${parts[0]})`);
}
return true;
}
// Export public key for transmission with signature
static async exportPublicKeyWithSignature(publicKey, signingKey, keyType = 'ECDH') {
try {
// Validate key type
if (!['ECDH', 'ECDSA'].includes(keyType)) {
throw new Error('Invalid key type');
}
const exported = await crypto.subtle.exportKey('spki', publicKey);
const keyData = Array.from(new Uint8Array(exported));
await EnhancedSecureCryptoUtils.validateKeyStructure(keyData, keyType);
// Create signed key package
const keyPackage = {
keyType,
keyData,
timestamp: Date.now(),
version: '4.0'
};
// Sign the key package
const packageString = JSON.stringify(keyPackage);
const signature = await EnhancedSecureCryptoUtils.signData(signingKey, packageString);
const signedPackage = {
...keyPackage,
signature
};
EnhancedSecureCryptoUtils.secureLog.log('info', 'Public key exported with signature', {
keyType,
keySize: keyData.length,
signed: true
});
return signedPackage;
} catch (error) {
EnhancedSecureCryptoUtils.secureLog.log('error', 'Public key export failed', {
error: error.message,
keyType
});
throw new Error(`Failed to export ${keyType} key: ${error.message}`);
}
}
// Import and verify signed public key
static async importSignedPublicKey(signedPackage, verifyingKey, expectedKeyType = 'ECDH') {
try {
// Validate package structure
if (!signedPackage || typeof signedPackage !== 'object') {
throw new Error('Invalid signed package format');
}
const { keyType, keyData, timestamp, version, signature } = signedPackage;
if (!keyType || !keyData || !timestamp || !signature) {
throw new Error('Missing required fields in signed package');
}
if (!EnhancedSecureCryptoUtils.constantTimeCompare(keyType, expectedKeyType)) {
throw new Error(`Key type mismatch: expected ${expectedKeyType}, got ${keyType}`);
}
// Check timestamp (reject keys older than 1 hour)
const keyAge = Date.now() - timestamp;
if (keyAge > 3600000) {
throw new Error('Signed key package is too old');
}
await EnhancedSecureCryptoUtils.validateKeyStructure(keyData, keyType);
// Verify signature
const packageCopy = { keyType, keyData, timestamp, version };
const packageString = JSON.stringify(packageCopy);
const isValidSignature = await EnhancedSecureCryptoUtils.verifySignature(verifyingKey, signature, packageString);
if (!isValidSignature) {
throw new Error('Invalid signature on key package - possible MITM attack');
}
// Import the key with fallback support
const keyBytes = new Uint8Array(keyData);
// Try P-384 first
try {
const algorithm = keyType === 'ECDH' ?
{ name: 'ECDH', namedCurve: 'P-384' }
: { name: 'ECDSA', namedCurve: 'P-384' };
const keyUsages = keyType === 'ECDH' ? [] : ['verify'];
const publicKey = await crypto.subtle.importKey(
'spki',
keyBytes,
algorithm,
false, // Non-extractable
keyUsages
);
EnhancedSecureCryptoUtils.secureLog.log('info', 'Signed public key imported successfully (P-384)', {
keyType,
signatureValid: true,
keyAge: Math.round(keyAge / 1000) + 's'
});
return publicKey;
} catch (p384Error) {
// Fallback to P-256
EnhancedSecureCryptoUtils.secureLog.log('warn', 'P-384 import failed, trying P-256', {
error: p384Error.message
});
const algorithm = keyType === 'ECDH' ?
{ name: 'ECDH', namedCurve: 'P-256' }
: { name: 'ECDSA', namedCurve: 'P-256' };
const keyUsages = keyType === 'ECDH' ? [] : ['verify'];
const publicKey = await crypto.subtle.importKey(
'spki',
keyBytes,
algorithm,
false, // Non-extractable
keyUsages
);
EnhancedSecureCryptoUtils.secureLog.log('info', 'Signed public key imported successfully (P-256 fallback)', {
keyType,
signatureValid: true,
keyAge: Math.round(keyAge / 1000) + 's'
});
return publicKey;
}
} catch (error) {
EnhancedSecureCryptoUtils.secureLog.log('error', 'Signed public key import failed', {
error: error.message,
expectedKeyType
});
throw new Error(`Failed to import the signed key: ${error.message}`);
}
}
// Legacy export for backward compatibility
static async exportPublicKey(publicKey) {
try {
const exported = await crypto.subtle.exportKey('spki', publicKey);
const keyData = Array.from(new Uint8Array(exported));
await EnhancedSecureCryptoUtils.validateKeyStructure(keyData, 'ECDH');
EnhancedSecureCryptoUtils.secureLog.log('info', 'Legacy public key exported', { keySize: keyData.length });
return keyData;
} catch (error) {
EnhancedSecureCryptoUtils.secureLog.log('error', 'Legacy public key export failed', { error: error.message });
throw new Error('Failed to export the public key');
}
}
// Legacy import for backward compatibility with fallback
static async importPublicKey(keyData) {
try {
await EnhancedSecureCryptoUtils.validateKeyStructure(keyData, 'ECDH');
const keyBytes = new Uint8Array(keyData);
// Try P-384 first
try {
const publicKey = await crypto.subtle.importKey(
'spki',
keyBytes,
{
name: 'ECDH',
namedCurve: 'P-384'
},
false, // Non-extractable
[]
);
EnhancedSecureCryptoUtils.secureLog.log('info', 'Legacy public key imported (P-384)', { keySize: keyData.length });
return publicKey;
} catch (p384Error) {
EnhancedSecureCryptoUtils.secureLog.log('warn', 'P-384 import failed, trying P-256', { error: p384Error.message });
// Fallback to P-256
const publicKey = await crypto.subtle.importKey(
'spki',
keyBytes,
{
name: 'ECDH',
namedCurve: 'P-256'
},
false, // Non-extractable
[]
);
EnhancedSecureCryptoUtils.secureLog.log('info', 'Legacy public key imported (P-256 fallback)', { keySize: keyData.length });
return publicKey;
}
} catch (error) {
EnhancedSecureCryptoUtils.secureLog.log('error', 'Legacy public key import failed', { error: error.message });
throw new Error('Failed to import the public key');
}
}
// Method to check if a key is trusted
static isKeyTrusted(keyOrFingerprint) {
if (keyOrFingerprint instanceof CryptoKey) {
const meta = EnhancedSecureCryptoUtils._keyMetadata.get(keyOrFingerprint);
return meta ? meta.trusted === true : false;
} else if (keyOrFingerprint && keyOrFingerprint._securityMetadata) {
// Check by key metadata
return keyOrFingerprint._securityMetadata.trusted === true;
}
return false;
}
static async importPublicKeyFromSignedPackage(signedPackage, verifyingKey = null, options = {}) {
try {
if (!signedPackage || !signedPackage.keyData || !signedPackage.signature) {
throw new Error('Invalid signed key package format');
}
// Validate all required fields are present
const requiredFields = ['keyData', 'signature', 'keyType', 'timestamp', 'version'];
const missingFields = requiredFields.filter(field => !signedPackage[field]);
if (missingFields.length > 0) {
EnhancedSecureCryptoUtils.secureLog.log('error', 'Missing required fields in signed package', {
missingFields: missingFields,
availableFields: Object.keys(signedPackage)
});
throw new Error(`Required fields are missing in the signed package: ${missingFields.join(', ')}`);
}
// SECURITY ENHANCEMENT: MANDATORY signature verification for signed packages
if (!verifyingKey) {
EnhancedSecureCryptoUtils.secureLog.log('error', 'SECURITY VIOLATION: Signed package received without verifying key', {
keyType: signedPackage.keyType,
keySize: signedPackage.keyData.length,
timestamp: signedPackage.timestamp,
version: signedPackage.version,
securityRisk: 'HIGH - Potential MITM attack vector'
});
// REJECT the signed package if no verifying key provided
throw new Error('CRITICAL SECURITY ERROR: Signed key package received without a verification key. ' +
'This may indicate a possible MITM attack attempt. Import rejected for security reasons.');
}
// ОБНОВЛЕНО: Используем улучшенную валидацию
await EnhancedSecureCryptoUtils.validateKeyStructure(signedPackage.keyData, signedPackage.keyType || 'ECDH');
// MANDATORY signature verification when verifyingKey is provided
const packageCopy = { ...signedPackage };
delete packageCopy.signature;
const packageString = JSON.stringify(packageCopy);
const isValidSignature = await EnhancedSecureCryptoUtils.verifySignature(verifyingKey, signedPackage.signature, packageString);
if (!isValidSignature) {
EnhancedSecureCryptoUtils.secureLog.log('error', 'SECURITY BREACH: Invalid signature detected - MITM attack prevented', {
keyType: signedPackage.keyType,
keySize: signedPackage.keyData.length,
timestamp: signedPackage.timestamp,
version: signedPackage.version,
attackPrevented: true
});
throw new Error('CRITICAL SECURITY ERROR: Invalid key signature detected. ' +
'This indicates a possible MITM attack attempt. Key import rejected.');
}
// Additional MITM protection: Check for key reuse and suspicious patterns
const keyFingerprint = await EnhancedSecureCryptoUtils.calculateKeyFingerprint(signedPackage.keyData);
// Log successful verification with security details
EnhancedSecureCryptoUtils.secureLog.log('info', 'SECURE: Signature verification passed for signed package', {
keyType: signedPackage.keyType,
keySize: signedPackage.keyData.length,
timestamp: signedPackage.timestamp,
version: signedPackage.version,
signatureVerified: true,
securityLevel: 'HIGH',
keyFingerprint: keyFingerprint.substring(0, 8) // Only log first 8 chars for security
});
// Import the public key with fallback
const keyBytes = new Uint8Array(signedPackage.keyData);
const keyType = signedPackage.keyType || 'ECDH';
// Try P-384 first
try {
const publicKey = await crypto.subtle.importKey(
'spki',
keyBytes,
{
name: keyType,
namedCurve: 'P-384'
},
false, // Non-extractable
keyType === 'ECDSA' ? ['verify'] : []
);
// Use WeakMap to store metadata
EnhancedSecureCryptoUtils._keyMetadata.set(publicKey, {
trusted: true,
verificationStatus: 'VERIFIED_SECURE',
verificationTimestamp: Date.now()
});
return publicKey;
} catch (p384Error) {
EnhancedSecureCryptoUtils.secureLog.log('warn', 'P-384 import failed, trying P-256', { error: p384Error.message });
// Fallback to P-256
const publicKey = await crypto.subtle.importKey(
'spki',
keyBytes,
{
name: keyType,
namedCurve: 'P-256'
},
false, // Non-extractable
keyType === 'ECDSA' ? ['verify'] : []
);
// Use WeakMap to store metadata
EnhancedSecureCryptoUtils._keyMetadata.set(publicKey, {
trusted: true,
verificationStatus: 'VERIFIED_SECURE',
verificationTimestamp: Date.now()
});
return publicKey;
}
} catch (error) {
EnhancedSecureCryptoUtils.secureLog.log('error', 'Signed package key import failed', {
error: error.message,
securityImplications: 'Potential security breach prevented'
});
throw new Error(`Failed to import the public key from the signed package: ${error.message}`);
}
}
// Enhanced key derivation with metadata protection and 64-byte salt
static async deriveSharedKeys(privateKey, publicKey, salt) {
try {
// Validate input parameters are CryptoKey instances
if (!(privateKey instanceof CryptoKey)) {
EnhancedSecureCryptoUtils.secureLog.log('error', 'Private key is not a CryptoKey', {
privateKeyType: typeof privateKey,
privateKeyAlgorithm: privateKey?.algorithm?.name
});
throw new Error('The private key is not a valid CryptoKey.');
}
if (!(publicKey instanceof CryptoKey)) {
EnhancedSecureCryptoUtils.secureLog.log('error', 'Public key is not a CryptoKey', {
publicKeyType: typeof publicKey,
publicKeyAlgorithm: publicKey?.algorithm?.name
});
throw new Error('The private key is not a valid CryptoKey.');
}
// Validate salt size (should be 64 bytes for enhanced security)
if (!salt || salt.length !== 64) {
throw new Error('Salt must be exactly 64 bytes for enhanced security');
}
const saltBytes = new Uint8Array(salt);
const encoder = new TextEncoder();
// Enhanced context info with version and additional entropy
const contextInfo = encoder.encode('SecureBit.chat v4.0 Enhanced Security Edition');
// Derive master shared secret with enhanced parameters
// Try SHA-384 first, fallback to SHA-256
let sharedSecret;
try {
sharedSecret = await crypto.subtle.deriveKey(
{
name: 'ECDH',
public: publicKey
},
privateKey,
{
name: 'HKDF',
hash: 'SHA-384',
salt: saltBytes,
info: contextInfo
},
false, // Non-extractable
['deriveKey']
);
} catch (sha384Error) {
EnhancedSecureCryptoUtils.secureLog.log('warn', 'SHA-384 key derivation failed, trying SHA-256', {
error: sha384Error.message,
privateKeyType: typeof privateKey,
publicKeyType: typeof publicKey,
privateKeyAlgorithm: privateKey?.algorithm?.name,
publicKeyAlgorithm: publicKey?.algorithm?.name
});
sharedSecret = await crypto.subtle.deriveKey(
{
name: 'ECDH',
public: publicKey
},
privateKey,
{
name: 'HKDF',
hash: 'SHA-256',
salt: saltBytes,
info: contextInfo
},
false, // Non-extractable
['deriveKey']
);
}
// Derive message encryption key with fallback
let encryptionKey;
try {
encryptionKey = await crypto.subtle.deriveKey(
{
name: 'HKDF',
hash: 'SHA-384',
salt: saltBytes,
info: encoder.encode('message-encryption-v4')
},
sharedSecret,
{
name: 'AES-GCM',
length: 256
},
false, // Non-extractable for enhanced security
['encrypt', 'decrypt']
);
} catch (sha384Error) {
encryptionKey = await crypto.subtle.deriveKey(
{
name: 'HKDF',
hash: 'SHA-256',
salt: saltBytes,
info: encoder.encode('message-encryption-v4')
},
sharedSecret,
{
name: 'AES-GCM',
length: 256
},
false, // Non-extractable for enhanced security
['encrypt', 'decrypt']
);
}
// Derive MAC key for message authentication with fallback
let macKey;
try {
macKey = await crypto.subtle.deriveKey(
{
name: 'HKDF',
hash: 'SHA-384',
salt: saltBytes,
info: encoder.encode('message-authentication-v4')
},
sharedSecret,
{
name: 'HMAC',
hash: 'SHA-384'
},
false, // Non-extractable
['sign', 'verify']
);
} catch (sha384Error) {
macKey = await crypto.subtle.deriveKey(
{
name: 'HKDF',
hash: 'SHA-256',
salt: saltBytes,
info: encoder.encode('message-authentication-v4')
},
sharedSecret,
{
name: 'HMAC',
hash: 'SHA-256'
},
false, // Non-extractable
['sign', 'verify']
);
}
// Derive separate metadata encryption key with fallback
let metadataKey;
try {
metadataKey = await crypto.subtle.deriveKey(
{
name: 'HKDF',
hash: 'SHA-384',
salt: saltBytes,
info: encoder.encode('metadata-protection-v4')
},
sharedSecret,
{
name: 'AES-GCM',
length: 256
},
false, // Non-extractable
['encrypt', 'decrypt']
);
} catch (sha384Error) {
metadataKey = await crypto.subtle.deriveKey(
{
name: 'HKDF',
hash: 'SHA-256',
salt: saltBytes,
info: encoder.encode('metadata-protection-v4')
},
sharedSecret,
{
name: 'AES-GCM',
length: 256
},
false, // Non-extractable
['encrypt', 'decrypt']
);
}
// Generate temporary extractable key for fingerprint calculation with fallback
let fingerprintKey;
try {
fingerprintKey = await crypto.subtle.deriveKey(
{
name: 'HKDF',
hash: 'SHA-384',
salt: saltBytes,
info: encoder.encode('fingerprint-generation-v4')
},
sharedSecret,
{
name: 'AES-GCM',
length: 256
},
true, // Extractable only for fingerprint
['encrypt', 'decrypt']
);
} catch (sha384Error) {
fingerprintKey = await crypto.subtle.deriveKey(
{
name: 'HKDF',
hash: 'SHA-256',
salt: saltBytes,
info: encoder.encode('fingerprint-generation-v4')
},
sharedSecret,
{
name: 'AES-GCM',
length: 256
},
true, // Extractable only for fingerprint
['encrypt', 'decrypt']
);
}
// Generate key fingerprint for verification
const fingerprintKeyData = await crypto.subtle.exportKey('raw', fingerprintKey);
const fingerprint = await EnhancedSecureCryptoUtils.generateKeyFingerprint(Array.from(new Uint8Array(fingerprintKeyData)));
// Validate that all derived keys are CryptoKey instances
if (!(encryptionKey instanceof CryptoKey)) {
EnhancedSecureCryptoUtils.secureLog.log('error', 'Derived encryption key is not a CryptoKey', {
encryptionKeyType: typeof encryptionKey,
encryptionKeyAlgorithm: encryptionKey?.algorithm?.name
});
throw new Error('The derived encryption key is not a valid CryptoKey.');
}
if (!(macKey instanceof CryptoKey)) {
EnhancedSecureCryptoUtils.secureLog.log('error', 'Derived MAC key is not a CryptoKey', {
macKeyType: typeof macKey,
macKeyAlgorithm: macKey?.algorithm?.name
});
throw new Error('The derived MAC key is not a valid CryptoKey.');
}
if (!(metadataKey instanceof CryptoKey)) {
EnhancedSecureCryptoUtils.secureLog.log('error', 'Derived metadata key is not a CryptoKey', {
metadataKeyType: typeof metadataKey,
metadataKeyAlgorithm: metadataKey?.algorithm?.name
});
throw new Error('The derived metadata key is not a valid CryptoKey.');
}
EnhancedSecureCryptoUtils.secureLog.log('info', 'Enhanced shared keys derived successfully', {
saltSize: salt.length,
hasMetadataKey: true,
nonExtractable: true,
version: '4.0',
allKeysValid: true
});
return {
encryptionKey,
macKey,
metadataKey,
fingerprint,
timestamp: Date.now(),
version: '4.0'
};
} catch (error) {
EnhancedSecureCryptoUtils.secureLog.log('error', 'Enhanced key derivation failed', { error: error.message });
throw new Error(`Failed to create shared encryption keys: ${error.message}`);
}
}
static async generateKeyFingerprint(keyData) {
const keyBuffer = new Uint8Array(keyData);
const hashBuffer = await crypto.subtle.digest('SHA-384', keyBuffer);
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray.slice(0, 12).map(b => b.toString(16).padStart(2, '0')).join(':');
}
// Generate mutual authentication challenge
static generateMutualAuthChallenge() {
const challenge = crypto.getRandomValues(new Uint8Array(48)); // Increased to 48 bytes
const timestamp = Date.now();
const nonce = crypto.getRandomValues(new Uint8Array(16));
return {
challenge: Array.from(challenge),
timestamp,
nonce: Array.from(nonce),
version: '4.0'
};
}
// Create cryptographic proof for mutual authentication
static async createAuthProof(challenge, privateKey, publicKey) {
try {
if (!challenge || !challenge.challenge || !challenge.timestamp || !challenge.nonce) {
throw new Error('Invalid challenge structure');
}
// Check challenge age (max 2 minutes)
const challengeAge = Date.now() - challenge.timestamp;
if (challengeAge > 120000) {
throw new Error('Challenge expired');
}
// Create proof data
const proofData = {
challenge: challenge.challenge,
timestamp: challenge.timestamp,
nonce: challenge.nonce,
responseTimestamp: Date.now(),
publicKeyHash: await EnhancedSecureCryptoUtils.hashPublicKey(publicKey)
};
// Sign the proof
const proofString = JSON.stringify(proofData);
const signature = await EnhancedSecureCryptoUtils.signData(privateKey, proofString);
const proof = {
...proofData,
signature,
version: '4.0'
};
EnhancedSecureCryptoUtils.secureLog.log('info', 'Authentication proof created', {
challengeAge: Math.round(challengeAge / 1000) + 's'
});
return proof;
} catch (error) {
EnhancedSecureCryptoUtils.secureLog.log('error', 'Authentication proof creation failed', { error: error.message });
throw new Error(`Failed to create cryptographic proof: ${error.message}`);
}
}
// Verify mutual authentication proof
static async verifyAuthProof(proof, challenge, publicKey) {
try {
await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 20) + 5));
// Assert the public key is valid and has the correct usage
EnhancedSecureCryptoUtils.assertCryptoKey(publicKey, 'ECDSA', ['verify']);
if (!proof || !challenge || !publicKey) {
throw new Error('Missing required parameters for proof verification');
}
// Validate proof structure
const requiredFields = ['challenge', 'timestamp', 'nonce', 'responseTimestamp', 'publicKeyHash', 'signature'];
for (const field of requiredFields) {
if (!proof[field]) {
throw new Error(`Missing required field: ${field}`);
}
}
// Verify challenge matches
if (!EnhancedSecureCryptoUtils.constantTimeCompareArrays(proof.challenge, challenge.challenge) ||
proof.timestamp !== challenge.timestamp ||
!EnhancedSecureCryptoUtils.constantTimeCompareArrays(proof.nonce, challenge.nonce)) {
throw new Error('Challenge mismatch - possible replay attack');
}
// Check response time (max 5 minutes)
const responseAge = Date.now() - proof.responseTimestamp;
if (responseAge > 300000) {
throw new Error('Proof response expired');
}
// Verify public key hash
const expectedHash = await EnhancedSecureCryptoUtils.hashPublicKey(publicKey);
if (!EnhancedSecureCryptoUtils.constantTimeCompare(proof.publicKeyHash, expectedHash)) {
throw new Error('Public key hash mismatch');
}
// Verify signature
const proofCopy = { ...proof };
delete proofCopy.signature;
const proofString = JSON.stringify(proofCopy);
const isValidSignature = await EnhancedSecureCryptoUtils.verifySignature(publicKey, proof.signature, proofString);
if (!isValidSignature) {
throw new Error('Invalid proof signature');
}
EnhancedSecureCryptoUtils.secureLog.log('info', 'Authentication proof verified successfully', {
responseAge: Math.round(responseAge / 1000) + 's'
});
return true;
} catch (error) {
EnhancedSecureCryptoUtils.secureLog.log('error', 'Authentication proof verification failed', { error: error.message });
throw new Error(`Failed to verify cryptographic proof: ${error.message}`);
}
}
// Hash public key for verification
static async hashPublicKey(publicKey) {
try {
const exported = await crypto.subtle.exportKey('spki', publicKey);
const hash = await crypto.subtle.digest('SHA-384', exported);
const hashArray = Array.from(new Uint8Array(hash));
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
} catch (error) {
EnhancedSecureCryptoUtils.secureLog.log('error', 'Public key hashing failed', { error: error.message });
throw new Error('Failed to create hash of the public key');
}
}
// Legacy authentication challenge for backward compatibility
static generateAuthChallenge() {
const challenge = crypto.getRandomValues(new Uint8Array(32));
return Array.from(challenge);
}
// Generate verification code for out-of-band authentication
static generateVerificationCode() {
const chars = '0123456789ABCDEF';
let result = '';
const values = crypto.getRandomValues(new Uint8Array(6));
for (let i = 0; i < 6; i++) {
result += chars[values[i] % chars.length];
}
return result.match(/.{1,2}/g).join('-');
}
// Enhanced message encryption with metadata protection and sequence numbers
static async encryptMessage(message, encryptionKey, macKey, metadataKey, messageId, sequenceNumber = 0) {
try {
if (!message || typeof message !== 'string') {
throw new Error('Invalid message format');
}
EnhancedSecureCryptoUtils.assertCryptoKey(encryptionKey, 'AES-GCM', ['encrypt']);
EnhancedSecureCryptoUtils.assertCryptoKey(macKey, 'HMAC', ['sign']);
EnhancedSecureCryptoUtils.assertCryptoKey(metadataKey, 'AES-GCM', ['encrypt']);
const encoder = new TextEncoder();
const messageData = encoder.encode(message);
const messageIv = crypto.getRandomValues(new Uint8Array(12));
const metadataIv = crypto.getRandomValues(new Uint8Array(12));
const timestamp = Date.now();
const paddingSize = 16 - (messageData.length % 16);
const paddedMessage = new Uint8Array(messageData.length + paddingSize);
paddedMessage.set(messageData);
const padding = crypto.getRandomValues(new Uint8Array(paddingSize));
paddedMessage.set(padding, messageData.length);
const encryptedMessage = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv: messageIv },
encryptionKey,
paddedMessage
);
const metadata = {
id: messageId,
timestamp: timestamp,
sequenceNumber: sequenceNumber,
originalLength: messageData.length,
version: '4.0'
};
const metadataStr = JSON.stringify(EnhancedSecureCryptoUtils.sortObjectKeys(metadata));
const encryptedMetadata = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv: metadataIv },
metadataKey,
encoder.encode(metadataStr)
);
const payload = {
messageIv: Array.from(messageIv),
messageData: Array.from(new Uint8Array(encryptedMessage)),
metadataIv: Array.from(metadataIv),
metadataData: Array.from(new Uint8Array(encryptedMetadata)),
version: '4.0'
};
const sortedPayload = EnhancedSecureCryptoUtils.sortObjectKeys(payload);
const payloadStr = JSON.stringify(sortedPayload);
const mac = await crypto.subtle.sign(
'HMAC',
macKey,
encoder.encode(payloadStr)
);
payload.mac = Array.from(new Uint8Array(mac));
EnhancedSecureCryptoUtils.secureLog.log('info', 'Message encrypted with metadata protection', {
messageId,
sequenceNumber,
hasMetadataProtection: true,
hasPadding: true
});
return payload;
} catch (error) {
EnhancedSecureCryptoUtils.secureLog.log('error', 'Message encryption failed', {
error: error.message,
messageId
});
throw new Error(`Failed to encrypt the message: ${error.message}`);
}
}
// Enhanced message decryption with metadata protection and sequence validation
static async decryptMessage(encryptedPayload, encryptionKey, macKey, metadataKey, expectedSequenceNumber = null) {
try {
EnhancedSecureCryptoUtils.assertCryptoKey(encryptionKey, 'AES-GCM', ['decrypt']);
EnhancedSecureCryptoUtils.assertCryptoKey(macKey, 'HMAC', ['verify']);
EnhancedSecureCryptoUtils.assertCryptoKey(metadataKey, 'AES-GCM', ['decrypt']);
const requiredFields = ['messageIv', 'messageData', 'metadataIv', 'metadataData', 'mac', 'version'];
for (const field of requiredFields) {
if (!encryptedPayload[field]) {
throw new Error(`Missing required field: ${field}`);
}
}
const payloadCopy = { ...encryptedPayload };
delete payloadCopy.mac;
const sortedPayloadCopy = EnhancedSecureCryptoUtils.sortObjectKeys(payloadCopy);
const payloadStr = JSON.stringify(sortedPayloadCopy);
const macValid = await crypto.subtle.verify(
'HMAC',
macKey,
new Uint8Array(encryptedPayload.mac),
new TextEncoder().encode(payloadStr)
);
if (!macValid) {
EnhancedSecureCryptoUtils.secureLog.log('error', 'MAC verification failed', {
payloadFields: Object.keys(encryptedPayload),
macLength: encryptedPayload.mac?.length
});
throw new Error('Message authentication failed - possible tampering');
}
const metadataIv = new Uint8Array(encryptedPayload.metadataIv);
const metadataData = new Uint8Array(encryptedPayload.metadataData);
const decryptedMetadataBuffer = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv: metadataIv },
metadataKey,
metadataData
);
const metadataStr = new TextDecoder().decode(decryptedMetadataBuffer);
const metadata = JSON.parse(metadataStr);
if (!metadata.id || !metadata.timestamp || metadata.sequenceNumber === undefined || !metadata.originalLength) {
throw new Error('Invalid metadata structure');
}
const messageAge = Date.now() - metadata.timestamp;
if (messageAge > 300000) {
throw new Error('Message expired (older than 5 minutes)');
}
if (expectedSequenceNumber !== null) {
if (metadata.sequenceNumber < expectedSequenceNumber) {
EnhancedSecureCryptoUtils.secureLog.log('warn', 'Received message with lower sequence number, possible queued message', {
expected: expectedSequenceNumber,
received: metadata.sequenceNumber,
messageId: metadata.id
});
} else if (metadata.sequenceNumber > expectedSequenceNumber + 10) {
throw new Error(`Sequence number gap too large: expected around ${expectedSequenceNumber}, got ${metadata.sequenceNumber}`);
}
}
const messageIv = new Uint8Array(encryptedPayload.messageIv);
const messageData = new Uint8Array(encryptedPayload.messageData);
const decryptedMessageBuffer = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv: messageIv },
encryptionKey,
messageData
);
const paddedMessage = new Uint8Array(decryptedMessageBuffer);
const originalMessage = paddedMessage.slice(0, metadata.originalLength);
const decoder = new TextDecoder();
const message = decoder.decode(originalMessage);
EnhancedSecureCryptoUtils.secureLog.log('info', 'Message decrypted successfully', {
messageId: metadata.id,
sequenceNumber: metadata.sequenceNumber,
messageAge: Math.round(messageAge / 1000) + 's'
});
return {
message: message,
messageId: metadata.id,
timestamp: metadata.timestamp,
sequenceNumber: metadata.sequenceNumber
};
} catch (error) {
EnhancedSecureCryptoUtils.secureLog.log('error', 'Message decryption failed', { error: error.message });
throw new Error(`Failed to decrypt the message: ${error.message}`);
}
}
// Enhanced input sanitization
static sanitizeMessage(message) {
if (typeof message !== 'string') {
throw new Error('Message must be a string');
}
return message
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
.replace(/javascript:/gi, '')
.replace(/data:/gi, '')
.replace(/vbscript:/gi, '')
.replace(/onload\s*=/gi, '')
.replace(/onerror\s*=/gi, '')
.replace(/onclick\s*=/gi, '')
.trim()
.substring(0, 2000); // Increased limit
}
// Generate cryptographically secure salt (64 bytes for enhanced security)
static generateSalt() {
return Array.from(crypto.getRandomValues(new Uint8Array(64)));
}
// Calculate key fingerprint for MITM protection
static async calculateKeyFingerprint(keyData) {
try {
const encoder = new TextEncoder();
const keyBytes = new Uint8Array(keyData);
// Create a hash of the key data for fingerprinting
const hashBuffer = await crypto.subtle.digest('SHA-256', keyBytes);
const hashArray = Array.from(new Uint8Array(hashBuffer));
// Convert to hexadecimal string
const fingerprint = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
EnhancedSecureCryptoUtils.secureLog.log('info', 'Key fingerprint calculated', {
keySize: keyData.length,
fingerprintLength: fingerprint.length
});
return fingerprint;
} catch (error) {
EnhancedSecureCryptoUtils.secureLog.log('error', 'Key fingerprint calculation failed', { error: error.message });
throw new Error('Failed to compute the key fingerprint');
}
}
static constantTimeCompare(a, b) {
const strA = typeof a === 'string' ? a : JSON.stringify(a);
const strB = typeof b === 'string' ? b : JSON.stringify(b);
if (strA.length !== strB.length) {
let dummy = 0;
for (let i = 0; i < Math.max(strA.length, strB.length); i++) {
dummy |= (strA.charCodeAt(i % strA.length) || 0) ^ (strB.charCodeAt(i % strB.length) || 0);
}
return false;
}
let result = 0;
for (let i = 0; i < strA.length; i++) {
result |= strA.charCodeAt(i) ^ strB.charCodeAt(i);
}
return result === 0;
}
static constantTimeCompareArrays(arr1, arr2) {
if (!Array.isArray(arr1) || !Array.isArray(arr2)) {
return false;
}
if (arr1.length !== arr2.length) {
let dummy = 0;
const maxLen = Math.max(arr1.length, arr2.length);
for (let i = 0; i < maxLen; i++) {
dummy |= (arr1[i % arr1.length] || 0) ^ (arr2[i % arr2.length] || 0);
}
return false;
}
let result = 0;
for (let i = 0; i < arr1.length; i++) {
result |= arr1[i] ^ arr2[i];
}
return result === 0;
}
/**
* CRITICAL SECURITY: Encrypt data with AAD (Additional Authenticated Data)
* This method provides authenticated encryption with additional data binding
*/
static async encryptDataWithAAD(data, key, aad) {
try {
const dataString = typeof data === 'string' ? data : JSON.stringify(data);
const encoder = new TextEncoder();
const dataBuffer = encoder.encode(dataString);
const aadBuffer = encoder.encode(aad);
// Generate random IV
const iv = crypto.getRandomValues(new Uint8Array(12));
// Encrypt with AAD
const encrypted = await crypto.subtle.encrypt(
{
name: 'AES-GCM',
iv: iv,
additionalData: aadBuffer
},
key,
dataBuffer
);
// Package encrypted data
const encryptedPackage = {
version: '1.0',
iv: Array.from(iv),
data: Array.from(new Uint8Array(encrypted)),
aad: aad,
timestamp: Date.now()
};
const packageString = JSON.stringify(encryptedPackage);
const packageBuffer = encoder.encode(packageString);
return EnhancedSecureCryptoUtils.arrayBufferToBase64(packageBuffer);
} catch (error) {
throw new Error(`AAD encryption failed: ${error.message}`);
}
}
/**
* CRITICAL SECURITY: Decrypt data with AAD validation
* This method provides authenticated decryption with additional data validation
*/
static async decryptDataWithAAD(encryptedData, key, expectedAad) {
try {
const packageBuffer = EnhancedSecureCryptoUtils.base64ToArrayBuffer(encryptedData);
const packageString = new TextDecoder().decode(packageBuffer);
const encryptedPackage = JSON.parse(packageString);
if (!encryptedPackage.version || !encryptedPackage.iv || !encryptedPackage.data || !encryptedPackage.aad) {
throw new Error('Invalid encrypted data format');
}
// Validate AAD matches expected
if (encryptedPackage.aad !== expectedAad) {
throw new Error('AAD mismatch - possible tampering or replay attack');
}
const iv = new Uint8Array(encryptedPackage.iv);
const encrypted = new Uint8Array(encryptedPackage.data);
const aadBuffer = new TextEncoder().encode(encryptedPackage.aad);
// Decrypt with AAD validation
const decrypted = await crypto.subtle.decrypt(
{
name: 'AES-GCM',
iv: iv,
additionalData: aadBuffer
},
key,
encrypted
);
const decryptedString = new TextDecoder().decode(decrypted);
try {
return JSON.parse(decryptedString);
} catch {
return decryptedString;
}
} catch (error) {
throw new Error(`AAD decryption failed: ${error.message}`);
}
}
// Initialize secure logging system after class definition
static {
if (EnhancedSecureCryptoUtils.secureLog && typeof EnhancedSecureCryptoUtils.secureLog.init === 'function') {
EnhancedSecureCryptoUtils.secureLog.init();
}
}
}
export { EnhancedSecureCryptoUtils };