fix: resolve message sending - _createMessageAAD method not found
- Move methods to constructor for early availability - Add connectionId initialization - Remove duplicate definitions - Fix AAD creation for anti-replay protection
This commit is contained in:
@@ -255,11 +255,23 @@ this._secureLog('info', '🔒 Enhanced Mutex system fully initialized and valida
|
||||
// CRITICAL SECURITY: Store expected DTLS fingerprint for validation
|
||||
this.expectedDTLSFingerprint = null;
|
||||
this.strictDTLSValidation = true; // Can be disabled for debugging
|
||||
|
||||
// CRITICAL SECURITY: Real Perfect Forward Secrecy implementation
|
||||
this.ephemeralKeyPairs = new Map(); // Store ephemeral keys for current session only
|
||||
this.sessionStartTime = Date.now(); // Track session lifetime for PFS
|
||||
this.messageCounter = 0;
|
||||
this.sequenceNumber = 0;
|
||||
this.expectedSequenceNumber = 0;
|
||||
this.sessionSalt = null;
|
||||
|
||||
// CRITICAL SECURITY: Anti-Replay and Message Ordering Protection
|
||||
this.replayWindowSize = 64; // Sliding window for replay protection
|
||||
this.replayWindow = new Set(); // Track recent sequence numbers
|
||||
this.maxSequenceGap = 100; // Maximum allowed sequence gap
|
||||
this.replayProtectionEnabled = true; // Enable/disable replay protection
|
||||
this.sessionId = null; // MITM protection: Session identifier
|
||||
this.connectionId = Array.from(crypto.getRandomValues(new Uint8Array(8)))
|
||||
.map(b => b.toString(16).padStart(2, '0')).join(''); // Connection identifier for AAD
|
||||
this.peerPublicKey = null; // Store peer's public key for PFS
|
||||
this.rateLimiterId = null;
|
||||
this.intentionalDisconnect = false;
|
||||
@@ -376,7 +388,7 @@ this._secureLog('info', '🔒 Enhanced Mutex system fully initialized and valida
|
||||
hasNonExtractableKeys: false,
|
||||
hasRateLimiting: true,
|
||||
hasEnhancedValidation: false,
|
||||
hasPFS: false,
|
||||
hasPFS: true, // CRITICAL SECURITY: Real Perfect Forward Secrecy enabled
|
||||
|
||||
// Advanced Features (Session Managed)
|
||||
hasNestedEncryption: false,
|
||||
@@ -537,6 +549,70 @@ this._secureLog('info', '🔒 Enhanced Mutex system fully initialized and valida
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* CRITICAL SECURITY: Create AAD with sequence number for anti-replay protection
|
||||
* This binds each message to its sequence number and prevents replay attacks
|
||||
*/
|
||||
_createMessageAAD(messageType, messageData = null, isFileMessage = false) {
|
||||
try {
|
||||
const aad = {
|
||||
sessionId: this.currentSession?.sessionId || this.sessionId || 'unknown',
|
||||
keyFingerprint: this.keyFingerprint || 'unknown',
|
||||
sequenceNumber: this._generateNextSequenceNumber(),
|
||||
messageType: messageType,
|
||||
timestamp: Date.now(),
|
||||
connectionId: this.connectionId || 'unknown',
|
||||
isFileMessage: isFileMessage
|
||||
};
|
||||
|
||||
// Add message-specific data if available
|
||||
if (messageData && typeof messageData === 'object') {
|
||||
if (messageData.fileId) aad.fileId = messageData.fileId;
|
||||
if (messageData.chunkIndex !== undefined) aad.chunkIndex = messageData.chunkIndex;
|
||||
if (messageData.totalChunks !== undefined) aad.totalChunks = messageData.totalChunks;
|
||||
}
|
||||
|
||||
return JSON.stringify(aad);
|
||||
} catch (error) {
|
||||
this._secureLog('error', '❌ Failed to create message AAD', {
|
||||
errorType: error.constructor.name,
|
||||
message: error.message,
|
||||
messageType: messageType
|
||||
});
|
||||
// Fallback to basic AAD
|
||||
return JSON.stringify({
|
||||
sessionId: 'unknown',
|
||||
keyFingerprint: 'unknown',
|
||||
sequenceNumber: Date.now(),
|
||||
messageType: messageType,
|
||||
timestamp: Date.now(),
|
||||
connectionId: 'unknown',
|
||||
isFileMessage: isFileMessage
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* CRITICAL SECURITY: Generate next sequence number for outgoing messages
|
||||
* This ensures unique ordering and prevents replay attacks
|
||||
*/
|
||||
_generateNextSequenceNumber() {
|
||||
const nextSeq = this.sequenceNumber++;
|
||||
|
||||
// CRITICAL SECURITY: Reset sequence number if it gets too large
|
||||
if (this.sequenceNumber > Number.MAX_SAFE_INTEGER - 1000) {
|
||||
this.sequenceNumber = 0;
|
||||
this.expectedSequenceNumber = 0;
|
||||
this.replayWindow.clear();
|
||||
this._secureLog('warn', '⚠️ Sequence number reset due to overflow', {
|
||||
timestamp: Date.now()
|
||||
});
|
||||
}
|
||||
|
||||
return nextSeq;
|
||||
}
|
||||
|
||||
/**
|
||||
* CRITICAL FIX: Enhanced mutex system initialization with atomic protection
|
||||
*/
|
||||
@@ -2561,6 +2637,11 @@ this._secureLog('info', '🔒 Enhanced Mutex system fully initialized and valida
|
||||
this.keyFingerprint = null;
|
||||
}
|
||||
|
||||
if (this.connectionId) {
|
||||
this._secureWipeMemory(this.connectionId, 'connectionId');
|
||||
this.connectionId = null;
|
||||
}
|
||||
|
||||
this._secureLog('info', '🔒 Cryptographic materials securely cleaned up');
|
||||
|
||||
} catch (error) {
|
||||
@@ -3069,6 +3150,7 @@ this._secureLog('info', '🔒 Enhanced Mutex system fully initialized and valida
|
||||
this.metadataKey = null;
|
||||
this.verificationCode = null;
|
||||
this.keyFingerprint = null;
|
||||
this.connectionId = null;
|
||||
|
||||
// Close connections
|
||||
if (this.dataChannel) {
|
||||
@@ -3208,23 +3290,12 @@ this._secureLog('info', '🔒 Enhanced Mutex system fully initialized and valida
|
||||
* This binds file messages to the current session and prevents replay attacks
|
||||
*/
|
||||
_createFileMessageAAD(messageType, messageData = null) {
|
||||
const aad = {
|
||||
sessionId: this.currentSession?.sessionId || 'unknown',
|
||||
keyFingerprint: this.keyFingerprint || 'unknown',
|
||||
sequenceNumber: this.sequenceNumber || 0,
|
||||
messageType: messageType,
|
||||
timestamp: Date.now(),
|
||||
connectionId: this.connectionId || 'unknown'
|
||||
};
|
||||
|
||||
// Add file-specific data if available
|
||||
if (messageData && typeof messageData === 'object') {
|
||||
if (messageData.fileId) aad.fileId = messageData.fileId;
|
||||
if (messageData.chunkIndex !== undefined) aad.chunkIndex = messageData.chunkIndex;
|
||||
if (messageData.totalChunks !== undefined) aad.totalChunks = messageData.totalChunks;
|
||||
// Verify that _createMessageAAD method is available
|
||||
if (typeof this._createMessageAAD !== 'function') {
|
||||
throw new Error('_createMessageAAD method is not available in _createFileMessageAAD. Manager may not be fully initialized.');
|
||||
}
|
||||
|
||||
return JSON.stringify(aad);
|
||||
// Use the unified AAD creation method with file message flag
|
||||
return this._createMessageAAD(messageType, messageData, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3378,11 +3449,18 @@ this._secureLog('info', '🔒 Enhanced Mutex system fully initialized and valida
|
||||
this._secureWipeMemory(this.encryptionKey, 'emergency_wipe');
|
||||
this._secureWipeMemory(this.macKey, 'emergency_wipe');
|
||||
this._secureWipeMemory(this.metadataKey, 'emergency_wipe');
|
||||
|
||||
// CRITICAL SECURITY: Wipe ephemeral keys for PFS
|
||||
this._wipeEphemeralKeys();
|
||||
|
||||
// CRITICAL SECURITY: Hard wipe old keys for PFS
|
||||
this._hardWipeOldKeys();
|
||||
|
||||
// Reset verification status
|
||||
this.isVerified = false;
|
||||
this.isVerified = null;
|
||||
this.verificationCode = null;
|
||||
this.keyFingerprint = null;
|
||||
this.connectionId = null;
|
||||
this.expectedDTLSFingerprint = null;
|
||||
|
||||
// Disconnect immediately
|
||||
@@ -3471,6 +3549,134 @@ this._secureLog('info', '🔒 Enhanced Mutex system fully initialized and valida
|
||||
this.deliverMessageToUI('✅ DTLS validation re-enabled', 'system');
|
||||
}
|
||||
|
||||
/**
|
||||
* CRITICAL SECURITY: Generate ephemeral ECDH keys for Perfect Forward Secrecy
|
||||
* This ensures each session has unique, non-persistent keys
|
||||
*/
|
||||
async _generateEphemeralECDHKeys() {
|
||||
try {
|
||||
this._secureLog('info', '🔑 Generating ephemeral ECDH keys for PFS', {
|
||||
sessionStartTime: this.sessionStartTime,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
|
||||
// Generate new ephemeral ECDH key pair
|
||||
const ephemeralKeyPair = await window.EnhancedSecureCryptoUtils.generateECDHKeyPair();
|
||||
|
||||
if (!ephemeralKeyPair || !this._validateKeyPairConstantTime(ephemeralKeyPair)) {
|
||||
throw new Error('Ephemeral ECDH key pair validation failed');
|
||||
}
|
||||
|
||||
// Store ephemeral keys with session binding
|
||||
const sessionId = this.currentSession?.sessionId || `session_${Date.now()}`;
|
||||
this.ephemeralKeyPairs.set(sessionId, {
|
||||
keyPair: ephemeralKeyPair,
|
||||
timestamp: Date.now(),
|
||||
sessionId: sessionId
|
||||
});
|
||||
|
||||
this._secureLog('info', '✅ Ephemeral ECDH keys generated for PFS', {
|
||||
sessionId: sessionId,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
|
||||
return ephemeralKeyPair;
|
||||
} catch (error) {
|
||||
this._secureLog('error', '❌ Failed to generate ephemeral ECDH keys', { error: error.message });
|
||||
throw new Error(`Ephemeral key generation failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* CRITICAL SECURITY: Hard wipe old keys for real PFS
|
||||
* This prevents retrospective decryption attacks
|
||||
*/
|
||||
_hardWipeOldKeys() {
|
||||
try {
|
||||
this._secureLog('info', '🧹 Performing hard wipe of old keys for PFS', {
|
||||
oldKeysCount: this.oldKeys.size,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
|
||||
// Hard wipe all old keys
|
||||
for (const [version, keySet] of this.oldKeys.entries()) {
|
||||
if (keySet.encryptionKey) {
|
||||
this._secureWipeMemory(keySet.encryptionKey, 'pfs_key_wipe');
|
||||
}
|
||||
if (keySet.macKey) {
|
||||
this._secureWipeMemory(keySet.macKey, 'pfs_key_wipe');
|
||||
}
|
||||
if (keySet.metadataKey) {
|
||||
this._secureWipeMemory(keySet.metadataKey, 'pfs_key_wipe');
|
||||
}
|
||||
|
||||
// Clear references
|
||||
keySet.encryptionKey = null;
|
||||
keySet.macKey = null;
|
||||
keySet.metadataKey = null;
|
||||
keySet.keyFingerprint = null;
|
||||
}
|
||||
|
||||
// Clear the oldKeys map completely
|
||||
this.oldKeys.clear();
|
||||
|
||||
// Force garbage collection if available
|
||||
if (typeof window.gc === 'function') {
|
||||
window.gc();
|
||||
}
|
||||
|
||||
this._secureLog('info', '✅ Hard wipe of old keys completed for PFS', {
|
||||
timestamp: Date.now()
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
this._secureLog('error', '❌ Failed to perform hard wipe of old keys', { error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* CRITICAL SECURITY: Wipe ephemeral keys when session ends
|
||||
* This ensures session-specific keys are destroyed
|
||||
*/
|
||||
_wipeEphemeralKeys() {
|
||||
try {
|
||||
this._secureLog('info', '🧹 Wiping ephemeral keys for PFS', {
|
||||
ephemeralKeysCount: this.ephemeralKeyPairs.size,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
|
||||
// Wipe all ephemeral key pairs
|
||||
for (const [sessionId, keyData] of this.ephemeralKeyPairs.entries()) {
|
||||
if (keyData.keyPair?.privateKey) {
|
||||
this._secureWipeMemory(keyData.keyPair.privateKey, 'ephemeral_key_wipe');
|
||||
}
|
||||
if (keyData.keyPair?.publicKey) {
|
||||
this._secureWipeMemory(keyData.keyPair.publicKey, 'ephemeral_key_wipe');
|
||||
}
|
||||
|
||||
// Clear references
|
||||
keyData.keyPair = null;
|
||||
keyData.timestamp = null;
|
||||
keyData.sessionId = null;
|
||||
}
|
||||
|
||||
// Clear the ephemeral keys map
|
||||
this.ephemeralKeyPairs.clear();
|
||||
|
||||
// Force garbage collection if available
|
||||
if (typeof window.gc === 'function') {
|
||||
window.gc();
|
||||
}
|
||||
|
||||
this._secureLog('info', '✅ Ephemeral keys wiped for PFS', {
|
||||
timestamp: Date.now()
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
this._secureLog('error', '❌ Failed to wipe ephemeral keys', { error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* CRITICAL SECURITY: Encrypt file messages with AAD
|
||||
* This ensures file messages are properly authenticated and bound to session
|
||||
@@ -3524,8 +3730,8 @@ this._secureLog('info', '🔒 Enhanced Mutex system fully initialized and valida
|
||||
throw new Error('Key fingerprint mismatch in encrypted file message');
|
||||
}
|
||||
|
||||
// Validate AAD
|
||||
const aad = this._validateFileMessageAAD(encryptedMessage.aad);
|
||||
// CRITICAL SECURITY: Validate AAD with sequence number
|
||||
const aad = this._validateMessageAAD(encryptedMessage.aad, 'file_message');
|
||||
|
||||
if (!this.encryptionKey) {
|
||||
throw new Error('No encryption key available for file message decryption');
|
||||
@@ -5637,12 +5843,21 @@ async processOrderedPackets() {
|
||||
}
|
||||
}
|
||||
|
||||
// SECURE: For regular text messages, send via secure path
|
||||
// SECURE: For regular text messages, send via secure path with AAD
|
||||
if (typeof validation.sanitizedData === 'string') {
|
||||
// Verify that _createMessageAAD method is available
|
||||
if (typeof this._createMessageAAD !== 'function') {
|
||||
throw new Error('_createMessageAAD method is not available. Manager may not be fully initialized.');
|
||||
}
|
||||
|
||||
// Create AAD with sequence number for anti-replay protection
|
||||
const aad = this._createMessageAAD('message', { content: validation.sanitizedData });
|
||||
|
||||
return await this.sendSecureMessage({
|
||||
type: 'message',
|
||||
data: validation.sanitizedData,
|
||||
timestamp: Date.now()
|
||||
timestamp: Date.now(),
|
||||
aad: aad // Include AAD for sequence number validation
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5799,6 +6014,48 @@ async processMessage(data) {
|
||||
return;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// ENHANCED MESSAGES WITH AAD VALIDATION (WITHOUT MUTEX)
|
||||
// ============================================
|
||||
|
||||
if (parsed.type === 'enhanced_message') {
|
||||
this._secureLog('debug', '🔐 Enhanced message detected in processMessage');
|
||||
|
||||
try {
|
||||
// Decrypt enhanced message
|
||||
const decryptedData = await window.EnhancedSecureCryptoUtils.decryptMessage(
|
||||
parsed.data,
|
||||
this.encryptionKey,
|
||||
this.macKey,
|
||||
this.metadataKey
|
||||
);
|
||||
|
||||
// Parse decrypted data
|
||||
const decryptedParsed = JSON.parse(decryptedData.data);
|
||||
|
||||
// CRITICAL SECURITY: Validate AAD with sequence number
|
||||
if (decryptedData.metadata && decryptedData.metadata.sequenceNumber !== undefined) {
|
||||
if (!this._validateIncomingSequenceNumber(decryptedData.metadata.sequenceNumber, 'enhanced_message')) {
|
||||
this._secureLog('warn', '⚠️ Enhanced message sequence number validation failed - possible replay attack', {
|
||||
received: decryptedData.metadata.sequenceNumber,
|
||||
expected: this.expectedSequenceNumber
|
||||
});
|
||||
return; // Drop message with invalid sequence number
|
||||
}
|
||||
}
|
||||
|
||||
// Process decrypted message
|
||||
if (decryptedParsed.type === 'message' && this.onMessage && decryptedParsed.data) {
|
||||
this.deliverMessageToUI(decryptedParsed.data, 'received');
|
||||
}
|
||||
|
||||
return;
|
||||
} catch (error) {
|
||||
this._secureLog('error', '❌ Failed to decrypt enhanced message', { error: error.message });
|
||||
return; // Drop invalid enhanced message
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// REGULAR USER MESSAGES (WITHOUT MUTEX)
|
||||
// ============================================
|
||||
@@ -6376,6 +6633,12 @@ async processMessage(data) {
|
||||
|
||||
// Clean up chunk queue
|
||||
this.chunkQueue = [];
|
||||
|
||||
// CRITICAL SECURITY: Wipe ephemeral keys for PFS on disconnect
|
||||
this._wipeEphemeralKeys();
|
||||
|
||||
// CRITICAL SECURITY: Hard wipe old keys for PFS
|
||||
this._hardWipeOldKeys();
|
||||
|
||||
} catch (error) {
|
||||
this._secureLog('error', '❌ Error during enhanced disconnect:', { errorType: error?.constructor?.name || 'Unknown' });
|
||||
@@ -6452,6 +6715,9 @@ async processMessage(data) {
|
||||
throw new Error('Data channel not ready for key rotation');
|
||||
}
|
||||
|
||||
// CRITICAL SECURITY: Perform hard wipe of old keys for real PFS
|
||||
this._hardWipeOldKeys();
|
||||
|
||||
// Wait for peer confirmation
|
||||
return new Promise((resolve) => {
|
||||
this.pendingRotation = {
|
||||
@@ -6480,20 +6746,48 @@ async processMessage(data) {
|
||||
}, 10000); // 10 seconds timeout for the entire operation
|
||||
}
|
||||
|
||||
// PFS: Clean up old keys that are no longer needed
|
||||
// CRITICAL SECURITY: Real PFS - Clean up old keys with hard wipe
|
||||
cleanupOldKeys() {
|
||||
const now = Date.now();
|
||||
const maxKeyAge = EnhancedSecureWebRTCManager.LIMITS.MAX_KEY_AGE; // 15 minutes - keys older than this are deleted
|
||||
|
||||
let wipedKeysCount = 0;
|
||||
|
||||
for (const [version, keySet] of this.oldKeys.entries()) {
|
||||
if (now - keySet.timestamp > maxKeyAge) {
|
||||
// CRITICAL SECURITY: Hard wipe old keys before deletion
|
||||
if (keySet.encryptionKey) {
|
||||
this._secureWipeMemory(keySet.encryptionKey, 'pfs_cleanup_wipe');
|
||||
}
|
||||
if (keySet.macKey) {
|
||||
this._secureWipeMemory(keySet.macKey, 'pfs_cleanup_wipe');
|
||||
}
|
||||
if (keySet.metadataKey) {
|
||||
this._secureWipeMemory(keySet.metadataKey, 'pfs_cleanup_wipe');
|
||||
}
|
||||
|
||||
// Clear references
|
||||
keySet.encryptionKey = null;
|
||||
keySet.macKey = null;
|
||||
keySet.metadataKey = null;
|
||||
keySet.keyFingerprint = null;
|
||||
|
||||
this.oldKeys.delete(version);
|
||||
window.EnhancedSecureCryptoUtils.secureLog.log('info', 'Old PFS keys cleaned up', {
|
||||
wipedKeysCount++;
|
||||
|
||||
this._secureLog('info', '🧹 Old PFS keys hard wiped and cleaned up', {
|
||||
version: version,
|
||||
age: Math.round((now - keySet.timestamp) / 1000) + 's'
|
||||
age: Math.round((now - keySet.timestamp) / 1000) + 's',
|
||||
timestamp: Date.now()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (wipedKeysCount > 0) {
|
||||
this._secureLog('info', `✅ PFS cleanup completed: ${wipedKeysCount} keys hard wiped`, {
|
||||
timestamp: Date.now()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// PFS: Get keys for specific version (for decryption)
|
||||
@@ -6670,6 +6964,9 @@ async processMessage(data) {
|
||||
}
|
||||
}
|
||||
|
||||
// CRITICAL SECURITY: Wipe ephemeral keys when session ends for PFS
|
||||
this._wipeEphemeralKeys();
|
||||
|
||||
this.stopHeartbeat();
|
||||
this.isVerified = false;
|
||||
};
|
||||
@@ -7473,32 +7770,33 @@ async processMessage(data) {
|
||||
let ecdhKeyPair = null;
|
||||
let ecdsaKeyPair = null;
|
||||
|
||||
// Generate ECDH keys with retry mechanism
|
||||
// CRITICAL SECURITY: Generate ephemeral ECDH keys for PFS
|
||||
try {
|
||||
ecdhKeyPair = await window.EnhancedSecureCryptoUtils.generateECDHKeyPair();
|
||||
ecdhKeyPair = await this._generateEphemeralECDHKeys();
|
||||
|
||||
// CRITICAL FIX: Validate ECDH keys immediately
|
||||
if (!ecdhKeyPair || !ecdhKeyPair.privateKey || !ecdhKeyPair.publicKey) {
|
||||
throw new Error('ECDH key pair validation failed');
|
||||
}
|
||||
|
||||
// SECURE: Constant-time validation for key types
|
||||
if (!this._validateKeyPairConstantTime(ecdhKeyPair)) {
|
||||
throw new Error('ECDH keys are not valid CryptoKey instances');
|
||||
}
|
||||
// CRITICAL FIX: Validate ECDH keys immediately
|
||||
if (!ecdhKeyPair || !ecdhKeyPair.privateKey || !ecdhKeyPair.publicKey) {
|
||||
throw new Error('Ephemeral ECDH key pair validation failed');
|
||||
}
|
||||
|
||||
this._secureLog('debug', '✅ ECDH keys generated and validated', {
|
||||
// SECURE: Constant-time validation for key types
|
||||
if (!this._validateKeyPairConstantTime(ecdhKeyPair)) {
|
||||
throw new Error('Ephemeral ECDH keys are not valid CryptoKey instances');
|
||||
}
|
||||
|
||||
this._secureLog('debug', '✅ Ephemeral ECDH keys generated and validated for PFS', {
|
||||
operationId: operationId,
|
||||
privateKeyType: ecdhKeyPair.privateKey.algorithm?.name,
|
||||
publicKeyType: ecdhKeyPair.publicKey.algorithm?.name
|
||||
publicKeyType: ecdhKeyPair.publicKey.algorithm?.name,
|
||||
isEphemeral: true
|
||||
});
|
||||
|
||||
} catch (ecdhError) {
|
||||
this._secureLog('error', '❌ ECDH key generation failed', {
|
||||
this._secureLog('error', '❌ Ephemeral ECDH key generation failed', {
|
||||
operationId: operationId,
|
||||
errorType: ecdhError.constructor.name
|
||||
});
|
||||
this._throwSecureError(ecdhError, 'ecdh_key_generation');
|
||||
this._throwSecureError(ecdhError, 'ephemeral_ecdh_key_generation');
|
||||
}
|
||||
|
||||
// Generate ECDSA keys with retry mechanism
|
||||
@@ -7590,13 +7888,20 @@ async processMessage(data) {
|
||||
this._secureLog('info', '🔒 Additional encryption-dependent features enabled');
|
||||
}
|
||||
|
||||
// CRITICAL SECURITY: Enable PFS after ephemeral key generation
|
||||
if (ecdhKeyPair && this.ephemeralKeyPairs.size > 0) {
|
||||
this.securityFeatures.hasPFS = true;
|
||||
this._secureLog('info', '🔒 Perfect Forward Secrecy enabled with ephemeral keys');
|
||||
}
|
||||
|
||||
this._secureLog('info', '🔒 Security features updated after key generation', {
|
||||
hasEncryption: this.securityFeatures.hasEncryption,
|
||||
hasECDH: this.securityFeatures.hasECDH,
|
||||
hasECDSA: this.securityFeatures.hasECDSA,
|
||||
hasMetadataProtection: this.securityFeatures.hasMetadataProtection,
|
||||
hasEnhancedReplayProtection: this.securityFeatures.hasEnhancedReplayProtection,
|
||||
hasNonExtractableKeys: this.securityFeatures.hasNonExtractableKeys
|
||||
hasNonExtractableKeys: this.securityFeatures.hasNonExtractableKeys,
|
||||
hasPFS: this.securityFeatures.hasPFS
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
@@ -8829,6 +9134,10 @@ async processMessage(data) {
|
||||
throw new Error('Failed to generate valid session ID');
|
||||
}
|
||||
|
||||
// Generate connection ID for AAD
|
||||
this.connectionId = Array.from(crypto.getRandomValues(new Uint8Array(8)))
|
||||
.map(b => b.toString(16).padStart(2, '0')).join('');
|
||||
|
||||
// ============================================
|
||||
// PHASE 11: SECURITY LEVEL CALCULATION
|
||||
// ============================================
|
||||
@@ -8873,6 +9182,7 @@ async processMessage(data) {
|
||||
// Session data
|
||||
salt: this.sessionSalt,
|
||||
sessionId: this.sessionId,
|
||||
connectionId: this.connectionId,
|
||||
|
||||
// Authentication
|
||||
verificationCode: this.verificationCode,
|
||||
@@ -9767,6 +10077,7 @@ async processMessage(data) {
|
||||
this.expectedSequenceNumber = 0;
|
||||
this.messageCounter = 0;
|
||||
this.processedMessageIds.clear();
|
||||
this.replayWindow.clear(); // CRITICAL SECURITY: Clear replay window
|
||||
|
||||
// CRITICAL FIX: Reset security features to baseline
|
||||
this._updateSecurityFeatures({
|
||||
@@ -9828,11 +10139,12 @@ async processMessage(data) {
|
||||
this.metadataKey = metadataKey;
|
||||
this.keyFingerprint = keyFingerprint;
|
||||
|
||||
// Reset counters
|
||||
this.sequenceNumber = 0;
|
||||
this.expectedSequenceNumber = 0;
|
||||
this.messageCounter = 0;
|
||||
this.processedMessageIds.clear();
|
||||
// Reset counters
|
||||
this.sequenceNumber = 0;
|
||||
this.expectedSequenceNumber = 0;
|
||||
this.messageCounter = 0;
|
||||
this.processedMessageIds.clear();
|
||||
this.replayWindow.clear(); // CRITICAL SECURITY: Clear replay window
|
||||
|
||||
this._secureLog('info', '✅ Encryption keys set successfully', {
|
||||
operationId: operationId,
|
||||
@@ -10021,6 +10333,12 @@ async processMessage(data) {
|
||||
|
||||
// Store peer's public key for PFS key rotation
|
||||
this.peerPublicKey = peerPublicKey;
|
||||
|
||||
// Initialize connection ID if not already set
|
||||
if (!this.connectionId) {
|
||||
this.connectionId = Array.from(crypto.getRandomValues(new Uint8Array(8)))
|
||||
.map(b => b.toString(16).padStart(2, '0')).join('');
|
||||
}
|
||||
|
||||
// SECURITY: DTLS protection removed - was security theater
|
||||
//
|
||||
@@ -10047,6 +10365,7 @@ async processMessage(data) {
|
||||
this.expectedSequenceNumber = 0;
|
||||
this.messageCounter = 0;
|
||||
this.processedMessageIds.clear();
|
||||
this.replayWindow.clear(); // CRITICAL SECURITY: Clear replay window
|
||||
// Validate that all keys are properly set
|
||||
if (!(this.encryptionKey instanceof CryptoKey) ||
|
||||
!(this.macKey instanceof CryptoKey) ||
|
||||
@@ -10533,14 +10852,20 @@ async processMessage(data) {
|
||||
const sanitizedMessage = window.EnhancedSecureCryptoUtils.sanitizeMessage(textToSend);
|
||||
const messageId = `msg_${Date.now()}_${this.messageCounter++}`;
|
||||
|
||||
// SECURE: Use enhanced encryption with metadata protection
|
||||
// CRITICAL SECURITY: Create AAD with sequence number for anti-replay protection
|
||||
if (typeof this._createMessageAAD !== 'function') {
|
||||
throw new Error('_createMessageAAD method is not available in sendSecureMessage. Manager may not be fully initialized.');
|
||||
}
|
||||
const aad = message.aad || this._createMessageAAD('enhanced_message', { content: sanitizedMessage });
|
||||
|
||||
// SECURE: Use enhanced encryption with AAD and sequence number
|
||||
const encryptedData = await window.EnhancedSecureCryptoUtils.encryptMessage(
|
||||
sanitizedMessage,
|
||||
this.encryptionKey,
|
||||
this.macKey,
|
||||
this.metadataKey,
|
||||
messageId,
|
||||
this.sequenceNumber++
|
||||
JSON.parse(aad).sequenceNumber // Use sequence number from AAD
|
||||
);
|
||||
|
||||
const payload = {
|
||||
@@ -10835,6 +11160,7 @@ async processMessage(data) {
|
||||
// CRITICAL FIX: Reset message counters
|
||||
this.sequenceNumber = 0;
|
||||
this.expectedSequenceNumber = 0;
|
||||
this.replayWindow.clear(); // CRITICAL SECURITY: Clear replay window
|
||||
|
||||
// CRITICAL FIX: Reset security features
|
||||
this.securityFeatures = {
|
||||
@@ -11658,6 +11984,175 @@ class SecureKeyStorage {
|
||||
};
|
||||
}
|
||||
|
||||
// Method _generateNextSequenceNumber moved to constructor area for early availability
|
||||
|
||||
/**
|
||||
* CRITICAL SECURITY: Validate incoming message sequence number
|
||||
* This prevents replay attacks and ensures message ordering
|
||||
*/
|
||||
_validateIncomingSequenceNumber(receivedSeq, context = 'unknown') {
|
||||
try {
|
||||
if (!this.replayProtectionEnabled) {
|
||||
return true; // Skip validation if disabled
|
||||
}
|
||||
|
||||
// Check if sequence number is within acceptable range
|
||||
if (receivedSeq < this.expectedSequenceNumber - this.replayWindowSize) {
|
||||
this._secureLog('warn', '⚠️ Sequence number too old - possible replay attack', {
|
||||
received: receivedSeq,
|
||||
expected: this.expectedSequenceNumber,
|
||||
context: context,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if sequence number is too far ahead (DoS protection)
|
||||
if (receivedSeq > this.expectedSequenceNumber + this.maxSequenceGap) {
|
||||
this._secureLog('warn', '⚠️ Sequence number gap too large - possible DoS attack', {
|
||||
received: receivedSeq,
|
||||
expected: this.expectedSequenceNumber,
|
||||
gap: receivedSeq - this.expectedSequenceNumber,
|
||||
context: context,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if sequence number is already in replay window
|
||||
if (this.replayWindow.has(receivedSeq)) {
|
||||
this._secureLog('warn', '⚠️ Duplicate sequence number detected - replay attack', {
|
||||
received: receivedSeq,
|
||||
context: context,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add to replay window
|
||||
this.replayWindow.add(receivedSeq);
|
||||
|
||||
// Maintain sliding window size
|
||||
if (this.replayWindow.size > this.replayWindowSize) {
|
||||
const oldestSeq = Math.min(...this.replayWindow);
|
||||
this.replayWindow.delete(oldestSeq);
|
||||
}
|
||||
|
||||
// Update expected sequence number if this is the next expected
|
||||
if (receivedSeq === this.expectedSequenceNumber) {
|
||||
this.expectedSequenceNumber++;
|
||||
|
||||
// Clean up replay window entries that are no longer needed
|
||||
while (this.replayWindow.has(this.expectedSequenceNumber - this.replayWindowSize - 1)) {
|
||||
this.replayWindow.delete(this.expectedSequenceNumber - this.replayWindowSize - 1);
|
||||
}
|
||||
}
|
||||
|
||||
this._secureLog('debug', '✅ Sequence number validation successful', {
|
||||
received: receivedSeq,
|
||||
expected: this.expectedSequenceNumber,
|
||||
context: context,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
this._secureLog('error', '❌ Sequence number validation failed', {
|
||||
error: error.message,
|
||||
context: context,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Method _createMessageAAD moved to constructor area for early availability
|
||||
|
||||
/**
|
||||
* CRITICAL SECURITY: Validate message AAD with sequence number
|
||||
* This ensures message integrity and prevents replay attacks
|
||||
*/
|
||||
_validateMessageAAD(aadString, expectedMessageType = null) {
|
||||
try {
|
||||
const aad = JSON.parse(aadString);
|
||||
|
||||
// Validate session binding
|
||||
if (aad.sessionId !== (this.currentSession?.sessionId || 'unknown')) {
|
||||
throw new Error('AAD sessionId mismatch - possible replay attack');
|
||||
}
|
||||
|
||||
if (aad.keyFingerprint !== (this.keyFingerprint || 'unknown')) {
|
||||
throw new Error('AAD keyFingerprint mismatch - possible key substitution attack');
|
||||
}
|
||||
|
||||
// CRITICAL SECURITY: Validate sequence number
|
||||
if (!this._validateIncomingSequenceNumber(aad.sequenceNumber, aad.messageType)) {
|
||||
throw new Error('Sequence number validation failed - possible replay or DoS attack');
|
||||
}
|
||||
|
||||
// Validate message type if specified
|
||||
if (expectedMessageType && aad.messageType !== expectedMessageType) {
|
||||
throw new Error(`AAD messageType mismatch - expected ${expectedMessageType}, got ${aad.messageType}`);
|
||||
}
|
||||
|
||||
return aad;
|
||||
} catch (error) {
|
||||
this._secureLog('error', 'AAD validation failed', { error: error.message, aadString });
|
||||
throw new Error(`AAD validation failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* CRITICAL SECURITY: Get anti-replay protection status
|
||||
* This shows the current state of replay protection
|
||||
*/
|
||||
getAntiReplayStatus() {
|
||||
const status = {
|
||||
replayProtectionEnabled: this.replayProtectionEnabled,
|
||||
replayWindowSize: this.replayWindowSize,
|
||||
currentReplayWindowSize: this.replayWindow.size,
|
||||
sequenceNumber: this.sequenceNumber,
|
||||
expectedSequenceNumber: this.expectedSequenceNumber,
|
||||
maxSequenceGap: this.maxSequenceGap,
|
||||
replayWindowEntries: Array.from(this.replayWindow).sort((a, b) => a - b)
|
||||
};
|
||||
|
||||
this._secureLog('info', 'Anti-replay status retrieved', status);
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* CRITICAL SECURITY: Configure anti-replay protection
|
||||
* This allows fine-tuning of replay protection parameters
|
||||
*/
|
||||
configureAntiReplayProtection(config) {
|
||||
try {
|
||||
if (config.windowSize !== undefined) {
|
||||
if (config.windowSize < 16 || config.windowSize > 1024) {
|
||||
throw new Error('Replay window size must be between 16 and 1024');
|
||||
}
|
||||
this.replayWindowSize = config.windowSize;
|
||||
}
|
||||
|
||||
if (config.maxGap !== undefined) {
|
||||
if (config.maxGap < 10 || config.maxGap > 1000) {
|
||||
throw new Error('Max sequence gap must be between 10 and 1000');
|
||||
}
|
||||
this.maxSequenceGap = config.maxGap;
|
||||
}
|
||||
|
||||
if (config.enabled !== undefined) {
|
||||
this.replayProtectionEnabled = config.enabled;
|
||||
}
|
||||
|
||||
this._secureLog('info', 'Anti-replay protection configured', config);
|
||||
return true;
|
||||
} catch (error) {
|
||||
this._secureLog('error', 'Failed to configure anti-replay protection', { error: error.message });
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
70
test_methods.html
Normal file
70
test_methods.html
Normal file
@@ -0,0 +1,70 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Test Methods</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Testing EnhancedSecureWebRTCManager Methods</h1>
|
||||
<div id="output"></div>
|
||||
|
||||
<script type="module">
|
||||
import { EnhancedSecureWebRTCManager } from './src/network/EnhancedSecureWebRTCManager.js';
|
||||
|
||||
const output = document.getElementById('output');
|
||||
|
||||
try {
|
||||
// Create a mock manager instance
|
||||
const manager = new EnhancedSecureWebRTCManager(
|
||||
() => {}, // onMessage
|
||||
() => {}, // onStatusChange
|
||||
() => {}, // onKeyExchange
|
||||
() => {} // onVerificationRequired
|
||||
);
|
||||
|
||||
// Test if _createMessageAAD method exists
|
||||
if (typeof manager._createMessageAAD === 'function') {
|
||||
output.innerHTML += '<p style="color: green;">✅ _createMessageAAD method exists</p>';
|
||||
|
||||
// Test if it can be called
|
||||
try {
|
||||
const aad = manager._createMessageAAD('test', { content: 'test' });
|
||||
output.innerHTML += `<p style="color: green;">✅ _createMessageAAD called successfully: ${aad}</p>`;
|
||||
} catch (error) {
|
||||
output.innerHTML += `<p style="color: red;">❌ _createMessageAAD call failed: ${error.message}</p>`;
|
||||
}
|
||||
} else {
|
||||
output.innerHTML += '<p style="color: red;">❌ _createMessageAAD method does not exist</p>';
|
||||
}
|
||||
|
||||
// Test if _generateNextSequenceNumber method exists
|
||||
if (typeof manager._generateNextSequenceNumber === 'function') {
|
||||
output.innerHTML += '<p style="color: green;">✅ _generateNextSequenceNumber method exists</p>';
|
||||
} else {
|
||||
output.innerHTML += '<p style="color: red;">❌ _generateNextSequenceNumber method does not exist</p>';
|
||||
}
|
||||
|
||||
// Test if sendMessage method exists
|
||||
if (typeof manager.sendMessage === 'function') {
|
||||
output.innerHTML += '<p style="color: green;">✅ sendMessage method exists</p>';
|
||||
} else {
|
||||
output.innerHTML += '<p style="color: red;">❌ sendMessage method does not exist</p>';
|
||||
}
|
||||
|
||||
// List all methods
|
||||
const methods = Object.getOwnPropertyNames(Object.getPrototypeOf(manager))
|
||||
.filter(name => name.startsWith('_') && typeof manager[name] === 'function');
|
||||
|
||||
output.innerHTML += '<h3>Available private methods:</h3><ul>';
|
||||
methods.forEach(method => {
|
||||
output.innerHTML += `<li>${method}</li>`;
|
||||
});
|
||||
output.innerHTML += '</ul>';
|
||||
|
||||
} catch (error) {
|
||||
output.innerHTML += `<p style="color: red;">❌ Error creating manager: ${error.message}</p>`;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user