2025-08-18 21:45:50 -04:00
|
|
|
|
// Import EnhancedSecureFileTransfer
|
|
|
|
|
|
import { EnhancedSecureFileTransfer } from '../transfer/EnhancedSecureFileTransfer.js';
|
|
|
|
|
|
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// MUTEX SYSTEM FIXES - RESOLVING MESSAGE DELIVERY ISSUES
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Issue: After introducing the Mutex system, messages stopped being delivered between users
|
|
|
|
|
|
// Fix: Simplified locking logic — mutex is used ONLY for critical operations
|
|
|
|
|
|
// - Regular messages are processed WITHOUT mutex
|
|
|
|
|
|
// - File messages are processed WITHOUT mutex
|
|
|
|
|
|
// - Mutex is used ONLY for cryptographic operations
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
2025-08-11 20:52:14 -04:00
|
|
|
|
class EnhancedSecureWebRTCManager {
|
2025-08-20 23:04:29 -04:00
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// CONSTANTS
|
2025-08-20 23:04:29 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
|
|
static TIMEOUTS = {
|
|
|
|
|
|
KEY_ROTATION_INTERVAL: 300000, // 5 minutes
|
|
|
|
|
|
CONNECTION_TIMEOUT: 10000, // 10 seconds
|
|
|
|
|
|
HEARTBEAT_INTERVAL: 30000, // 30 seconds
|
|
|
|
|
|
SECURITY_CALC_DELAY: 1000, // 1 second
|
|
|
|
|
|
SECURITY_CALC_RETRY_DELAY: 3000, // 3 seconds
|
|
|
|
|
|
CLEANUP_INTERVAL: 300000, // 5 minutes (periodic cleanup)
|
|
|
|
|
|
CLEANUP_CHECK_INTERVAL: 60000, // 1 minute (cleanup check)
|
|
|
|
|
|
ICE_GATHERING_TIMEOUT: 10000, // 10 seconds
|
|
|
|
|
|
DISCONNECT_CLEANUP_DELAY: 500, // 500ms
|
|
|
|
|
|
PEER_DISCONNECT_CLEANUP: 2000, // 2 seconds
|
|
|
|
|
|
STAGE2_ACTIVATION_DELAY: 10000, // 10 seconds
|
|
|
|
|
|
STAGE3_ACTIVATION_DELAY: 15000, // 15 seconds
|
|
|
|
|
|
STAGE4_ACTIVATION_DELAY: 20000, // 20 seconds
|
|
|
|
|
|
FILE_TRANSFER_INIT_DELAY: 1000, // 1 second
|
|
|
|
|
|
FAKE_TRAFFIC_MIN_INTERVAL: 15000, // 15 seconds
|
|
|
|
|
|
FAKE_TRAFFIC_MAX_INTERVAL: 30000, // 30 seconds
|
|
|
|
|
|
DECOY_INITIAL_DELAY: 5000, // 5 seconds
|
|
|
|
|
|
DECOY_TRAFFIC_MIN: 10000, // 10 seconds
|
|
|
|
|
|
DECOY_TRAFFIC_MAX: 25000, // 25 seconds
|
|
|
|
|
|
REORDER_TIMEOUT: 3000, // 3 seconds
|
|
|
|
|
|
RETRY_CONNECTION_DELAY: 2000 // 2 seconds
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static LIMITS = {
|
|
|
|
|
|
MAX_CONNECTION_ATTEMPTS: 3,
|
|
|
|
|
|
MAX_OLD_KEYS: 3,
|
|
|
|
|
|
MAX_PROCESSED_MESSAGE_IDS: 1000,
|
|
|
|
|
|
MAX_OUT_OF_ORDER_PACKETS: 5,
|
|
|
|
|
|
MAX_DECOY_CHANNELS: 1,
|
|
|
|
|
|
MESSAGE_RATE_LIMIT: 60, // messages per minute
|
|
|
|
|
|
MAX_KEY_AGE: 900000, // 15 minutes
|
|
|
|
|
|
OFFER_MAX_AGE: 3600000, // 1 hour
|
|
|
|
|
|
SALT_SIZE_V3: 32, // bytes
|
|
|
|
|
|
SALT_SIZE_V4: 64 // bytes
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static SIZES = {
|
|
|
|
|
|
VERIFICATION_CODE_MIN_LENGTH: 6,
|
|
|
|
|
|
FAKE_TRAFFIC_MIN_SIZE: 32,
|
|
|
|
|
|
FAKE_TRAFFIC_MAX_SIZE: 128,
|
|
|
|
|
|
PACKET_PADDING_MIN: 64,
|
|
|
|
|
|
PACKET_PADDING_MAX: 512,
|
|
|
|
|
|
CHUNK_SIZE_MAX: 2048,
|
|
|
|
|
|
CHUNK_DELAY_MIN: 100,
|
|
|
|
|
|
CHUNK_DELAY_MAX: 500,
|
|
|
|
|
|
FINGERPRINT_DISPLAY_LENGTH: 8,
|
|
|
|
|
|
SESSION_ID_LENGTH: 16,
|
|
|
|
|
|
NESTED_ENCRYPTION_IV_SIZE: 12
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static MESSAGE_TYPES = {
|
|
|
|
|
|
// Regular messages
|
|
|
|
|
|
MESSAGE: 'message',
|
|
|
|
|
|
ENHANCED_MESSAGE: 'enhanced_message',
|
|
|
|
|
|
|
|
|
|
|
|
// System messages
|
|
|
|
|
|
HEARTBEAT: 'heartbeat',
|
|
|
|
|
|
VERIFICATION: 'verification',
|
|
|
|
|
|
VERIFICATION_RESPONSE: 'verification_response',
|
|
|
|
|
|
PEER_DISCONNECT: 'peer_disconnect',
|
|
|
|
|
|
SECURITY_UPGRADE: 'security_upgrade',
|
|
|
|
|
|
KEY_ROTATION_SIGNAL: 'key_rotation_signal',
|
|
|
|
|
|
KEY_ROTATION_READY: 'key_rotation_ready',
|
|
|
|
|
|
|
|
|
|
|
|
// File transfer messages
|
|
|
|
|
|
FILE_TRANSFER_START: 'file_transfer_start',
|
|
|
|
|
|
FILE_TRANSFER_RESPONSE: 'file_transfer_response',
|
|
|
|
|
|
FILE_CHUNK: 'file_chunk',
|
|
|
|
|
|
CHUNK_CONFIRMATION: 'chunk_confirmation',
|
|
|
|
|
|
FILE_TRANSFER_COMPLETE: 'file_transfer_complete',
|
|
|
|
|
|
FILE_TRANSFER_ERROR: 'file_transfer_error',
|
|
|
|
|
|
|
|
|
|
|
|
// Fake traffic
|
|
|
|
|
|
FAKE: 'fake'
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static FILTERED_RESULTS = {
|
|
|
|
|
|
FAKE_MESSAGE: 'FAKE_MESSAGE_FILTERED',
|
|
|
|
|
|
FILE_MESSAGE: 'FILE_MESSAGE_FILTERED',
|
|
|
|
|
|
SYSTEM_MESSAGE: 'SYSTEM_MESSAGE_FILTERED'
|
|
|
|
|
|
};
|
2025-08-24 16:30:06 -04:00
|
|
|
|
|
|
|
|
|
|
// ============================================
|
|
|
|
|
|
// DTLS CLIENTHELLO RACE CONDITION PROTECTION
|
|
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
|
|
// Защита от DTLS ClientHello race condition (октябрь 2024)
|
|
|
|
|
|
static DTLS_PROTECTION = {
|
|
|
|
|
|
SUPPORTED_CIPHERS: [
|
|
|
|
|
|
'TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256',
|
|
|
|
|
|
'TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384',
|
|
|
|
|
|
'TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256',
|
|
|
|
|
|
'TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384'
|
|
|
|
|
|
],
|
|
|
|
|
|
MIN_TLS_VERSION: '1.2',
|
|
|
|
|
|
MAX_TLS_VERSION: '1.3',
|
|
|
|
|
|
CLIENTHELLO_TIMEOUT: 5000, // 5 seconds
|
|
|
|
|
|
ICE_VERIFICATION_TIMEOUT: 3000 // 3 seconds
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-08-11 20:52:14 -04:00
|
|
|
|
constructor(onMessage, onStatusChange, onKeyExchange, onVerificationRequired, onAnswerError = null) {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Determine runtime mode
|
2025-08-20 23:34:56 -04:00
|
|
|
|
this._isProductionMode = this._detectProductionMode();
|
|
|
|
|
|
this._debugMode = !this._isProductionMode && window.DEBUG_MODE;
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Initialize the secure logging system
|
2025-08-20 23:34:56 -04:00
|
|
|
|
this._initializeSecureLogging();
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this._disableConsoleLogInProduction();
|
2025-08-21 05:16:41 -04:00
|
|
|
|
this._redirectConsoleLogToSecure();
|
2025-08-14 03:28:23 -04:00
|
|
|
|
// Check the availability of the global object
|
2025-08-20 23:04:29 -04:00
|
|
|
|
this._setupSecureGlobalAPI();
|
2025-08-14 03:28:23 -04:00
|
|
|
|
if (!window.EnhancedSecureCryptoUtils) {
|
|
|
|
|
|
throw new Error('EnhancedSecureCryptoUtils is not loaded. Please ensure the module is loaded first.');
|
|
|
|
|
|
}
|
2025-08-20 23:04:29 -04:00
|
|
|
|
this.getSecurityData = () => {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Return only public information
|
2025-08-20 23:04:29 -04:00
|
|
|
|
return this.lastSecurityCalculation ? {
|
|
|
|
|
|
level: this.lastSecurityCalculation.level,
|
|
|
|
|
|
score: this.lastSecurityCalculation.score,
|
|
|
|
|
|
timestamp: this.lastSecurityCalculation.timestamp,
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Do NOT return check details or sensitive data
|
2025-08-20 23:04:29 -04:00
|
|
|
|
} : null;
|
|
|
|
|
|
};
|
2025-08-20 23:34:56 -04:00
|
|
|
|
this._secureLog('info', '🔒 Enhanced WebRTC Manager initialized with secure API');
|
2025-08-17 02:22:55 -04:00
|
|
|
|
this.currentSessionType = null;
|
|
|
|
|
|
this.currentSecurityLevel = 'basic';
|
|
|
|
|
|
this.sessionConstraints = null;
|
2025-08-14 03:28:23 -04:00
|
|
|
|
this.peerConnection = null;
|
|
|
|
|
|
this.dataChannel = null;
|
2025-08-21 00:06:28 -04:00
|
|
|
|
|
|
|
|
|
|
|
2025-08-14 03:28:23 -04:00
|
|
|
|
this.onMessage = onMessage;
|
|
|
|
|
|
this.onStatusChange = onStatusChange;
|
|
|
|
|
|
this.onKeyExchange = onKeyExchange;
|
|
|
|
|
|
this.onVerificationRequired = onVerificationRequired;
|
|
|
|
|
|
this.onAnswerError = onAnswerError; // Callback for response processing errors
|
|
|
|
|
|
this.isInitiator = false;
|
|
|
|
|
|
this.connectionAttempts = 0;
|
2025-08-20 23:04:29 -04:00
|
|
|
|
this.maxConnectionAttempts = EnhancedSecureWebRTCManager.LIMITS.MAX_CONNECTION_ATTEMPTS;
|
2025-08-21 04:07:16 -04:00
|
|
|
|
try {
|
|
|
|
|
|
this._initializeMutexSystem();
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
this._secureLog('error', '❌ Failed to initialize mutex system', {
|
|
|
|
|
|
errorType: error.constructor.name
|
|
|
|
|
|
});
|
|
|
|
|
|
throw new Error('Critical: Mutex system initialization failed');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Post-initialization validation of the mutex system
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (!this._validateMutexSystem()) {
|
|
|
|
|
|
this._secureLog('error', '❌ Mutex system validation failed after initialization');
|
|
|
|
|
|
throw new Error('Critical: Mutex system validation failed');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Add global emergency handlers
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (typeof window !== 'undefined') {
|
|
|
|
|
|
window.emergencyUnlockMutexes = () => {
|
|
|
|
|
|
return this._emergencyUnlockAllMutexes();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
window.getMutexDiagnostics = () => {
|
|
|
|
|
|
return this._getMutexSystemDiagnostics();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
window.recoverMutexSystem = () => {
|
|
|
|
|
|
return this._emergencyRecoverMutexSystem();
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this._secureLog('info', '🔒 Enhanced Mutex system fully initialized and validated');
|
2025-08-14 03:28:23 -04:00
|
|
|
|
this.heartbeatInterval = null;
|
|
|
|
|
|
this.messageQueue = [];
|
|
|
|
|
|
this.ecdhKeyPair = null;
|
|
|
|
|
|
this.ecdsaKeyPair = null;
|
2025-08-18 21:45:50 -04:00
|
|
|
|
if (this.fileTransferSystem) {
|
|
|
|
|
|
this.fileTransferSystem.cleanup();
|
|
|
|
|
|
this.fileTransferSystem = null;
|
|
|
|
|
|
}
|
2025-08-14 03:28:23 -04:00
|
|
|
|
this.verificationCode = null;
|
|
|
|
|
|
this.isVerified = false;
|
|
|
|
|
|
this.processedMessageIds = new Set();
|
|
|
|
|
|
this.messageCounter = 0;
|
|
|
|
|
|
this.sequenceNumber = 0;
|
|
|
|
|
|
this.expectedSequenceNumber = 0;
|
|
|
|
|
|
this.sessionSalt = null;
|
|
|
|
|
|
this.sessionId = null; // MITM protection: Session identifier
|
|
|
|
|
|
this.peerPublicKey = null; // Store peer's public key for PFS
|
|
|
|
|
|
this.rateLimiterId = null;
|
|
|
|
|
|
this.intentionalDisconnect = false;
|
|
|
|
|
|
this.lastCleanupTime = Date.now();
|
2025-08-20 18:19:42 -04:00
|
|
|
|
|
2025-08-20 23:04:29 -04:00
|
|
|
|
// Reset notification flags for new connection
|
|
|
|
|
|
this._resetNotificationFlags();
|
2025-08-21 00:06:28 -04:00
|
|
|
|
|
|
|
|
|
|
|
2025-08-20 23:04:29 -04:00
|
|
|
|
|
2025-08-20 18:19:42 -04:00
|
|
|
|
this.verificationInitiationSent = false;
|
|
|
|
|
|
this.disconnectNotificationSent = false;
|
|
|
|
|
|
this.reconnectionFailedNotificationSent = false;
|
|
|
|
|
|
this.peerDisconnectNotificationSent = false;
|
|
|
|
|
|
this.connectionClosedNotificationSent = false;
|
|
|
|
|
|
this.fakeTrafficDisabledNotificationSent = false;
|
|
|
|
|
|
this.advancedFeaturesDisabledNotificationSent = false;
|
|
|
|
|
|
this.securityUpgradeNotificationSent = false;
|
|
|
|
|
|
this.lastSecurityUpgradeStage = null;
|
|
|
|
|
|
this.securityCalculationNotificationSent = false;
|
|
|
|
|
|
this.lastSecurityCalculationLevel = null;
|
|
|
|
|
|
|
2025-08-18 21:45:50 -04:00
|
|
|
|
// File transfer integration
|
|
|
|
|
|
this.fileTransferSystem = null;
|
|
|
|
|
|
this.onFileProgress = null;
|
|
|
|
|
|
this.onFileReceived = null;
|
|
|
|
|
|
this.onFileError = null;
|
2025-08-14 03:28:23 -04:00
|
|
|
|
|
|
|
|
|
|
// PFS (Perfect Forward Secrecy) Implementation
|
2025-08-20 23:04:29 -04:00
|
|
|
|
this.keyRotationInterval = EnhancedSecureWebRTCManager.TIMEOUTS.KEY_ROTATION_INTERVAL;
|
2025-08-14 03:28:23 -04:00
|
|
|
|
this.lastKeyRotation = Date.now();
|
|
|
|
|
|
this.currentKeyVersion = 0;
|
|
|
|
|
|
this.keyVersions = new Map(); // Store key versions for PFS
|
|
|
|
|
|
this.oldKeys = new Map(); // Store old keys temporarily for decryption
|
2025-08-20 23:04:29 -04:00
|
|
|
|
this.maxOldKeys = EnhancedSecureWebRTCManager.LIMITS.MAX_OLD_KEYS; // Keep last 3 key versions for decryption
|
2025-08-17 02:22:55 -04:00
|
|
|
|
this.peerConnection = null;
|
|
|
|
|
|
this.dataChannel = null;
|
2025-08-24 16:30:06 -04:00
|
|
|
|
// DTLS Race Condition Protection
|
|
|
|
|
|
this.verifiedICEEndpoints = new Set(); // Верифицированные ICE endpoints
|
|
|
|
|
|
this.dtlsClientHelloQueue = new Map(); // Очередь DTLS ClientHello сообщений
|
|
|
|
|
|
this.iceVerificationInProgress = false; // Флаг процесса ICE верификации
|
|
|
|
|
|
this.dtlsProtectionEnabled = true; // Включена ли защита от DTLS race condition
|
|
|
|
|
|
|
|
|
|
|
|
this.securityFeatures = {
|
|
|
|
|
|
hasEncryption: true,
|
|
|
|
|
|
hasECDH: true,
|
|
|
|
|
|
hasECDSA: false,
|
|
|
|
|
|
hasMutualAuth: false,
|
|
|
|
|
|
hasMetadataProtection: false,
|
|
|
|
|
|
hasEnhancedReplayProtection: false,
|
|
|
|
|
|
hasNonExtractableKeys: false,
|
2025-08-17 02:22:55 -04:00
|
|
|
|
hasRateLimiting: true,
|
|
|
|
|
|
hasEnhancedValidation: false,
|
|
|
|
|
|
hasPFS: false,
|
|
|
|
|
|
|
|
|
|
|
|
// Advanced Features (Session Managed)
|
|
|
|
|
|
hasNestedEncryption: false,
|
|
|
|
|
|
hasPacketPadding: false,
|
|
|
|
|
|
hasPacketReordering: false,
|
|
|
|
|
|
hasAntiFingerprinting: false,
|
|
|
|
|
|
hasFakeTraffic: false,
|
|
|
|
|
|
hasDecoyChannels: false,
|
|
|
|
|
|
hasMessageChunking: false
|
|
|
|
|
|
};
|
2025-08-20 23:04:29 -04:00
|
|
|
|
console.log('🔒 Enhanced WebRTC Manager initialized with tiered security');
|
2025-08-14 03:28:23 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
// ENHANCED SECURITY FEATURES
|
|
|
|
|
|
// ============================================
|
2025-08-20 23:04:29 -04:00
|
|
|
|
|
|
|
|
|
|
// 1. Nested Encryption Layer
|
2025-08-18 21:45:50 -04:00
|
|
|
|
this.nestedEncryptionKey = null;
|
|
|
|
|
|
this.nestedEncryptionIV = null;
|
|
|
|
|
|
this.nestedEncryptionCounter = 0;
|
2025-08-20 23:04:29 -04:00
|
|
|
|
|
2025-08-18 21:45:50 -04:00
|
|
|
|
// 2. Packet Padding
|
|
|
|
|
|
this.paddingConfig = {
|
|
|
|
|
|
enabled: true,
|
2025-08-20 23:04:29 -04:00
|
|
|
|
minPadding: EnhancedSecureWebRTCManager.SIZES.PACKET_PADDING_MIN,
|
|
|
|
|
|
maxPadding: EnhancedSecureWebRTCManager.SIZES.PACKET_PADDING_MAX,
|
2025-08-18 21:45:50 -04:00
|
|
|
|
useRandomPadding: true,
|
|
|
|
|
|
preserveMessageSize: false
|
|
|
|
|
|
};
|
2025-08-20 23:04:29 -04:00
|
|
|
|
|
2025-08-18 21:45:50 -04:00
|
|
|
|
// 3. Fake Traffic Generation
|
|
|
|
|
|
this.fakeTrafficConfig = {
|
|
|
|
|
|
enabled: !window.DISABLE_FAKE_TRAFFIC,
|
2025-08-20 23:04:29 -04:00
|
|
|
|
minInterval: EnhancedSecureWebRTCManager.TIMEOUTS.FAKE_TRAFFIC_MIN_INTERVAL,
|
|
|
|
|
|
maxInterval: EnhancedSecureWebRTCManager.TIMEOUTS.FAKE_TRAFFIC_MAX_INTERVAL,
|
|
|
|
|
|
minSize: EnhancedSecureWebRTCManager.SIZES.FAKE_TRAFFIC_MIN_SIZE,
|
|
|
|
|
|
maxSize: EnhancedSecureWebRTCManager.SIZES.FAKE_TRAFFIC_MAX_SIZE,
|
2025-08-18 21:45:50 -04:00
|
|
|
|
patterns: ['heartbeat', 'status', 'sync']
|
|
|
|
|
|
};
|
|
|
|
|
|
this.fakeTrafficTimer = null;
|
|
|
|
|
|
this.lastFakeTraffic = 0;
|
2025-08-20 23:04:29 -04:00
|
|
|
|
|
2025-08-18 21:45:50 -04:00
|
|
|
|
// 4. Message Chunking
|
|
|
|
|
|
this.chunkingConfig = {
|
|
|
|
|
|
enabled: false,
|
2025-08-20 23:04:29 -04:00
|
|
|
|
maxChunkSize: EnhancedSecureWebRTCManager.SIZES.CHUNK_SIZE_MAX,
|
|
|
|
|
|
minDelay: EnhancedSecureWebRTCManager.SIZES.CHUNK_DELAY_MIN,
|
|
|
|
|
|
maxDelay: EnhancedSecureWebRTCManager.SIZES.CHUNK_DELAY_MAX,
|
2025-08-18 21:45:50 -04:00
|
|
|
|
useRandomDelays: true,
|
|
|
|
|
|
addChunkHeaders: true
|
|
|
|
|
|
};
|
|
|
|
|
|
this.chunkQueue = [];
|
|
|
|
|
|
this.chunkingInProgress = false;
|
2025-08-20 23:04:29 -04:00
|
|
|
|
|
2025-08-18 21:45:50 -04:00
|
|
|
|
// 5. Decoy Channels
|
|
|
|
|
|
this.decoyChannels = new Map();
|
|
|
|
|
|
this.decoyChannelConfig = {
|
|
|
|
|
|
enabled: !window.DISABLE_DECOY_CHANNELS,
|
2025-08-20 23:04:29 -04:00
|
|
|
|
maxDecoyChannels: EnhancedSecureWebRTCManager.LIMITS.MAX_DECOY_CHANNELS,
|
2025-08-18 21:45:50 -04:00
|
|
|
|
decoyChannelNames: ['heartbeat'],
|
|
|
|
|
|
sendDecoyData: true,
|
|
|
|
|
|
randomDecoyIntervals: true
|
|
|
|
|
|
};
|
|
|
|
|
|
this.decoyTimers = new Map();
|
2025-08-20 23:04:29 -04:00
|
|
|
|
|
2025-08-18 21:45:50 -04:00
|
|
|
|
// 6. Packet Reordering Protection
|
|
|
|
|
|
this.reorderingConfig = {
|
|
|
|
|
|
enabled: false,
|
2025-08-20 23:04:29 -04:00
|
|
|
|
maxOutOfOrder: EnhancedSecureWebRTCManager.LIMITS.MAX_OUT_OF_ORDER_PACKETS,
|
|
|
|
|
|
reorderTimeout: EnhancedSecureWebRTCManager.TIMEOUTS.REORDER_TIMEOUT,
|
2025-08-18 21:45:50 -04:00
|
|
|
|
useSequenceNumbers: true,
|
|
|
|
|
|
useTimestamps: true
|
|
|
|
|
|
};
|
|
|
|
|
|
this.packetBuffer = new Map(); // sequence -> {data, timestamp}
|
|
|
|
|
|
this.lastProcessedSequence = -1;
|
2025-08-20 23:04:29 -04:00
|
|
|
|
|
2025-08-18 21:45:50 -04:00
|
|
|
|
// 7. Anti-Fingerprinting
|
|
|
|
|
|
this.antiFingerprintingConfig = {
|
|
|
|
|
|
enabled: false,
|
|
|
|
|
|
randomizeTiming: true,
|
|
|
|
|
|
randomizeSizes: false,
|
|
|
|
|
|
addNoise: true,
|
|
|
|
|
|
maskPatterns: false,
|
|
|
|
|
|
useRandomHeaders: false
|
|
|
|
|
|
};
|
|
|
|
|
|
this.fingerprintMask = this.generateFingerprintMask();
|
2025-08-20 23:04:29 -04:00
|
|
|
|
|
2025-08-18 21:45:50 -04:00
|
|
|
|
// Initialize rate limiter ID
|
|
|
|
|
|
this.rateLimiterId = `webrtc_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
2025-08-20 23:04:29 -04:00
|
|
|
|
|
2025-08-18 21:45:50 -04:00
|
|
|
|
// Start periodic cleanup
|
|
|
|
|
|
this.startPeriodicCleanup();
|
2025-08-20 23:04:29 -04:00
|
|
|
|
|
2025-08-18 21:45:50 -04:00
|
|
|
|
this.initializeEnhancedSecurity();
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// MUTEX SYSTEM TO PREVENT RACE CONDITIONS
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Mutex for key operations
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this._keyOperationMutex = {
|
|
|
|
|
|
locked: false,
|
|
|
|
|
|
queue: [],
|
|
|
|
|
|
lockId: null,
|
|
|
|
|
|
lockTimeout: null
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Mutex for encryption/decryption operations
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this._cryptoOperationMutex = {
|
|
|
|
|
|
locked: false,
|
|
|
|
|
|
queue: [],
|
|
|
|
|
|
lockId: null,
|
|
|
|
|
|
lockTimeout: null
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Mutex for connection initialization
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this._connectionOperationMutex = {
|
|
|
|
|
|
locked: false,
|
|
|
|
|
|
queue: [],
|
|
|
|
|
|
lockId: null,
|
|
|
|
|
|
lockTimeout: null
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Key system state
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this._keySystemState = {
|
|
|
|
|
|
isInitializing: false,
|
|
|
|
|
|
isRotating: false,
|
|
|
|
|
|
isDestroying: false,
|
|
|
|
|
|
lastOperation: null,
|
|
|
|
|
|
lastOperationTime: Date.now()
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Operation counters
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this._operationCounters = {
|
|
|
|
|
|
keyOperations: 0,
|
|
|
|
|
|
cryptoOperations: 0,
|
|
|
|
|
|
connectionOperations: 0
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-08-18 21:45:50 -04:00
|
|
|
|
}
|
2025-08-21 04:07:16 -04:00
|
|
|
|
_initializeMutexSystem() {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Initialize standard mutexes expected by the system
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this._keyOperationMutex = {
|
|
|
|
|
|
locked: false,
|
|
|
|
|
|
queue: [],
|
|
|
|
|
|
lockId: null,
|
|
|
|
|
|
lockTimeout: null
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
this._cryptoOperationMutex = {
|
|
|
|
|
|
locked: false,
|
|
|
|
|
|
queue: [],
|
|
|
|
|
|
lockId: null,
|
|
|
|
|
|
lockTimeout: null
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
this._connectionOperationMutex = {
|
|
|
|
|
|
locked: false,
|
|
|
|
|
|
queue: [],
|
|
|
|
|
|
lockId: null,
|
|
|
|
|
|
lockTimeout: null
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Key system state
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this._keySystemState = {
|
|
|
|
|
|
isInitializing: false,
|
|
|
|
|
|
isRotating: false,
|
|
|
|
|
|
isDestroying: false,
|
|
|
|
|
|
lastOperation: null,
|
|
|
|
|
|
lastOperationTime: Date.now()
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Operation counters
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this._operationCounters = {
|
|
|
|
|
|
keyOperations: 0,
|
|
|
|
|
|
cryptoOperations: 0,
|
|
|
|
|
|
connectionOperations: 0
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
this._secureLog('info', '🔒 Mutex system initialized successfully', {
|
|
|
|
|
|
mutexes: ['keyOperation', 'cryptoOperation', 'connectionOperation'],
|
|
|
|
|
|
timestamp: Date.now()
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 00:06:28 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
// SECURE KEY STORAGE MANAGEMENT
|
|
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* Initializes the secure key storage
|
2025-08-21 00:06:28 -04:00
|
|
|
|
*/
|
|
|
|
|
|
_initializeSecureKeyStorage() {
|
|
|
|
|
|
this._secureKeyStorage = new Map();
|
|
|
|
|
|
this._keyStorageStats = {
|
|
|
|
|
|
totalKeys: 0,
|
|
|
|
|
|
activeKeys: 0,
|
|
|
|
|
|
lastAccess: null,
|
|
|
|
|
|
lastRotation: null,
|
|
|
|
|
|
};
|
|
|
|
|
|
this._secureLog('info', '🔐 Secure key storage initialized');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// Helper: ensure file transfer system is ready (lazy init on receiver)
|
|
|
|
|
|
async _ensureFileTransferReady() {
|
|
|
|
|
|
try {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// If already initialized — done
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (this.fileTransferSystem) {
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Requires an open data channel and a verified connection
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (!this.dataChannel || this.dataChannel.readyState !== 'open') {
|
|
|
|
|
|
throw new Error('Data channel not open');
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!this.isVerified) {
|
|
|
|
|
|
throw new Error('Connection not verified');
|
|
|
|
|
|
}
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Initialization
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this.initializeFileTransfer();
|
2025-08-21 17:40:17 -04:00
|
|
|
|
|
|
|
|
|
|
// КРИТИЧЕСКОЕ ИСПРАВЛЕНИЕ: Ждем инициализации с таймаутом
|
|
|
|
|
|
let attempts = 0;
|
|
|
|
|
|
const maxAttempts = 50; // 5 секунд максимум
|
|
|
|
|
|
while (!this.fileTransferSystem && attempts < maxAttempts) {
|
|
|
|
|
|
await new Promise(r => setTimeout(r, 100));
|
|
|
|
|
|
attempts++;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!this.fileTransferSystem) {
|
|
|
|
|
|
throw new Error('File transfer system initialization timeout');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
2025-08-21 04:07:16 -04:00
|
|
|
|
} catch (e) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ _ensureFileTransferReady failed', {
|
|
|
|
|
|
errorType: e?.constructor?.name || 'Unknown',
|
|
|
|
|
|
hasMessage: !!e?.message
|
|
|
|
|
|
});
|
2025-08-21 04:07:16 -04:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 00:06:28 -04:00
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* Retrieves a key from secure storage
|
|
|
|
|
|
* @param {string} keyId - Key identifier
|
|
|
|
|
|
* @returns {CryptoKey|null} The key or null if not found
|
2025-08-21 00:06:28 -04:00
|
|
|
|
*/
|
|
|
|
|
|
_getSecureKey(keyId) {
|
|
|
|
|
|
if (!this._secureKeyStorage.has(keyId)) {
|
|
|
|
|
|
this._secureLog('warn', `⚠️ Key ${keyId} not found in secure storage`);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
this._keyStorageStats.lastAccess = Date.now();
|
|
|
|
|
|
return this._secureKeyStorage.get(keyId);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* Stores a key in secure storage
|
|
|
|
|
|
* @param {string} keyId - Key identifier
|
|
|
|
|
|
* @param {CryptoKey} key - Key to store
|
2025-08-21 00:06:28 -04:00
|
|
|
|
*/
|
|
|
|
|
|
_setSecureKey(keyId, key) {
|
|
|
|
|
|
if (!(key instanceof CryptoKey)) {
|
|
|
|
|
|
this._secureLog('error', '❌ Attempt to store non-CryptoKey in secure storage');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
this._secureKeyStorage.set(keyId, key);
|
|
|
|
|
|
this._keyStorageStats.totalKeys++;
|
|
|
|
|
|
this._keyStorageStats.activeKeys++;
|
|
|
|
|
|
this._keyStorageStats.lastAccess = Date.now();
|
|
|
|
|
|
this._secureLog('info', `🔑 Key ${keyId} stored securely`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* Validates a key value
|
|
|
|
|
|
* @param {CryptoKey} key - Key to validate
|
|
|
|
|
|
* @returns {boolean} true if the key is valid
|
2025-08-21 00:06:28 -04:00
|
|
|
|
*/
|
|
|
|
|
|
_validateKeyValue(key) {
|
|
|
|
|
|
return key instanceof CryptoKey &&
|
|
|
|
|
|
key.algorithm &&
|
|
|
|
|
|
key.usages &&
|
|
|
|
|
|
key.usages.length > 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* Securely wipes all keys from storage
|
2025-08-21 00:06:28 -04:00
|
|
|
|
*/
|
|
|
|
|
|
_secureWipeKeys() {
|
|
|
|
|
|
this._secureKeyStorage.clear();
|
|
|
|
|
|
this._keyStorageStats = {
|
|
|
|
|
|
totalKeys: 0,
|
|
|
|
|
|
activeKeys: 0,
|
|
|
|
|
|
lastAccess: null,
|
|
|
|
|
|
lastRotation: null,
|
|
|
|
|
|
};
|
|
|
|
|
|
this._secureLog('info', '🧹 All keys securely wiped from storage');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* Validates key storage state
|
|
|
|
|
|
* @returns {boolean} true if the storage is ready
|
2025-08-21 00:06:28 -04:00
|
|
|
|
*/
|
|
|
|
|
|
_validateKeyStorage() {
|
|
|
|
|
|
return this._secureKeyStorage instanceof Map;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* Returns secure key storage statistics
|
|
|
|
|
|
* @returns {object} Storage metrics
|
2025-08-21 00:06:28 -04:00
|
|
|
|
*/
|
|
|
|
|
|
_getKeyStorageStats() {
|
|
|
|
|
|
return {
|
2025-08-21 04:07:16 -04:00
|
|
|
|
totalKeysCount: this._keyStorageStats.totalKeys,
|
|
|
|
|
|
activeKeysCount: this._keyStorageStats.activeKeys,
|
|
|
|
|
|
hasLastAccess: !!this._keyStorageStats.lastAccess,
|
|
|
|
|
|
hasLastRotation: !!this._keyStorageStats.lastRotation,
|
|
|
|
|
|
storageType: 'SecureMap',
|
|
|
|
|
|
timestamp: Date.now()
|
2025-08-21 00:06:28 -04:00
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* Performs key rotation in storage
|
2025-08-21 00:06:28 -04:00
|
|
|
|
*/
|
|
|
|
|
|
_rotateKeys() {
|
|
|
|
|
|
const oldKeys = Array.from(this._secureKeyStorage.keys());
|
|
|
|
|
|
this._secureKeyStorage.clear();
|
|
|
|
|
|
this._keyStorageStats.lastRotation = Date.now();
|
|
|
|
|
|
this._keyStorageStats.activeKeys = 0;
|
|
|
|
|
|
this._secureLog('info', `🔄 Key rotation completed. ${oldKeys.length} keys rotated`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* Emergency key wipe (e.g., upon detecting a threat)
|
2025-08-21 00:06:28 -04:00
|
|
|
|
*/
|
|
|
|
|
|
_emergencyKeyWipe() {
|
|
|
|
|
|
this._secureWipeKeys();
|
|
|
|
|
|
this._secureLog('error', '🚨 EMERGENCY: All keys wiped due to security threat');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* Starts key security monitoring
|
2025-08-21 00:06:28 -04:00
|
|
|
|
*/
|
|
|
|
|
|
_startKeySecurityMonitoring() {
|
|
|
|
|
|
setInterval(() => {
|
|
|
|
|
|
if (this._keyStorageStats.activeKeys > 10) {
|
|
|
|
|
|
this._secureLog('warn', '⚠️ High number of active keys detected. Consider rotation.');
|
|
|
|
|
|
}
|
|
|
|
|
|
if (Date.now() - (this._keyStorageStats.lastRotation || 0) > 3600000) {
|
|
|
|
|
|
this._rotateKeys();
|
|
|
|
|
|
}
|
2025-08-21 05:16:41 -04:00
|
|
|
|
}, 300000); // Check every 5 minutes
|
2025-08-21 00:06:28 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-20 23:04:29 -04:00
|
|
|
|
|
|
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// HELPER METHODS
|
2025-08-20 23:04:29 -04:00
|
|
|
|
// ============================================
|
2025-08-20 23:34:56 -04:00
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* Initializes the secure logging system
|
2025-08-20 23:34:56 -04:00
|
|
|
|
*/
|
|
|
|
|
|
_initializeSecureLogging() {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Logging levels
|
2025-08-20 23:34:56 -04:00
|
|
|
|
this._logLevels = {
|
|
|
|
|
|
error: 0,
|
|
|
|
|
|
warn: 1,
|
|
|
|
|
|
info: 2,
|
|
|
|
|
|
debug: 3,
|
|
|
|
|
|
trace: 4
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// FIX: Stricter levels for production
|
2025-08-20 23:34:56 -04:00
|
|
|
|
this._currentLogLevel = this._isProductionMode ?
|
2025-08-21 05:16:41 -04:00
|
|
|
|
this._logLevels.error : // In production, ONLY errors
|
|
|
|
|
|
this._logLevels.info; // In development, up to info
|
2025-08-20 23:34:56 -04:00
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Log counter to prevent spam
|
2025-08-20 23:34:56 -04:00
|
|
|
|
this._logCounts = new Map();
|
2025-08-21 05:16:41 -04:00
|
|
|
|
this._maxLogCount = this._isProductionMode ? 10 : 100; // Max logs of one type
|
2025-08-21 04:07:16 -04:00
|
|
|
|
|
|
|
|
|
|
this._absoluteBlacklist = new Set([
|
|
|
|
|
|
'encryptionKey', 'macKey', 'metadataKey', 'privateKey',
|
|
|
|
|
|
'verificationCode', 'sessionSalt', 'keyFingerprint',
|
|
|
|
|
|
'password', 'token', 'secret', 'credential', 'signature',
|
|
|
|
|
|
'ecdhKeyPair', 'ecdsaKeyPair', 'peerPublicKey',
|
|
|
|
|
|
'sessionId', 'authChallenge', 'authProof'
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// NEW: Whitelist of safe fields
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this._safeFieldsWhitelist = new Set([
|
|
|
|
|
|
'timestamp', 'type', 'length', 'size', 'count', 'level',
|
|
|
|
|
|
'status', 'state', 'readyState', 'connectionState',
|
|
|
|
|
|
'isConnected', 'isVerified', 'isInitiator', 'version',
|
|
|
|
|
|
'activeFeaturesCount', 'totalFeatures', 'stage'
|
|
|
|
|
|
]);
|
2025-08-20 23:34:56 -04:00
|
|
|
|
|
|
|
|
|
|
this._secureLog('info', `🔧 Secure logging initialized (Production: ${this._isProductionMode})`);
|
|
|
|
|
|
}
|
2025-08-21 04:07:16 -04:00
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* Shim to redirect arbitrary console.log calls to _secureLog('info', ...)
|
|
|
|
|
|
*/
|
|
|
|
|
|
_secureLogShim(...args) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (!args || args.length === 0) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
const [message, ...rest] = args;
|
|
|
|
|
|
if (rest.length === 0) {
|
|
|
|
|
|
this._secureLog('info', String(message));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (rest.length === 1) {
|
|
|
|
|
|
this._secureLog('info', String(message), rest[0]);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
this._secureLog('info', String(message), { args: rest });
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
// Fallback — do not disrupt main execution flow
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Redirects global console.log to this instance's secure logger
|
|
|
|
|
|
*/
|
|
|
|
|
|
_redirectConsoleLogToSecure() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (typeof console === 'undefined') return;
|
|
|
|
|
|
// Preserve originals once
|
|
|
|
|
|
if (!this._originalConsole) {
|
|
|
|
|
|
this._originalConsole = {
|
|
|
|
|
|
log: console.log?.bind(console),
|
|
|
|
|
|
info: console.info?.bind(console),
|
|
|
|
|
|
warn: console.warn?.bind(console),
|
|
|
|
|
|
error: console.error?.bind(console),
|
|
|
|
|
|
debug: console.debug?.bind(console)
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
const self = this;
|
|
|
|
|
|
// Only console.log is redirected to secure; warn/error stay intact
|
|
|
|
|
|
console.log = function(...args) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
self._secureLogShim(...args);
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
// Fallback to original log on failure
|
|
|
|
|
|
if (self._originalConsole?.log) self._originalConsole.log(...args);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
// Safe degradation
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Disables noisy logging in production: console.log/debug become no-ops
|
|
|
|
|
|
* Intentionally preserves warn/error for problem visibility
|
2025-08-21 04:07:16 -04:00
|
|
|
|
*/
|
|
|
|
|
|
_disableConsoleLogInProduction() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (this._isProductionMode && typeof console !== 'undefined') {
|
|
|
|
|
|
const originalWarn = console.warn?.bind(console);
|
|
|
|
|
|
const originalError = console.error?.bind(console);
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Safely mute informational logs
|
2025-08-21 04:07:16 -04:00
|
|
|
|
console.log = () => {};
|
|
|
|
|
|
console.debug = () => {};
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Preserve warnings and errors
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (originalWarn) console.warn = (...args) => originalWarn(...args);
|
|
|
|
|
|
if (originalError) console.error = (...args) => originalError(...args);
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// No action required, non-functional safeguard
|
2025-08-21 04:07:16 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-20 23:34:56 -04:00
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* Secure logging
|
|
|
|
|
|
* @param {string} level - Log level (error, warn, info, debug, trace)
|
|
|
|
|
|
* @param {string} message - Message
|
|
|
|
|
|
* @param {object} data - Optional payload (will be sanitized)
|
2025-08-20 23:34:56 -04:00
|
|
|
|
*/
|
|
|
|
|
|
_secureLog(level, message, data = null) {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Check log level
|
2025-08-20 23:34:56 -04:00
|
|
|
|
if (this._logLevels[level] > this._currentLogLevel) {
|
2025-08-21 04:07:16 -04:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// NEW: Audit check before logging
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (data && !this._auditLogMessage(message, data)) {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
return; // Logging blocked due to potential data leakage
|
2025-08-20 23:34:56 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Prevent log spam
|
2025-08-20 23:34:56 -04:00
|
|
|
|
const logKey = `${level}:${message}`;
|
|
|
|
|
|
const currentCount = this._logCounts.get(logKey) || 0;
|
|
|
|
|
|
|
|
|
|
|
|
if (currentCount >= this._maxLogCount) {
|
2025-08-21 04:07:16 -04:00
|
|
|
|
return;
|
2025-08-20 23:34:56 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this._logCounts.set(logKey, currentCount + 1);
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Sanitize data
|
2025-08-20 23:34:56 -04:00
|
|
|
|
const sanitizedData = data ? this._sanitizeLogData(data) : null;
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// NEW: In production do not output payloads
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (this._isProductionMode && level !== 'error') {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
console[level] || console.log(message); // Only message without payload
|
2025-08-20 23:34:56 -04:00
|
|
|
|
} else {
|
2025-08-21 04:07:16 -04:00
|
|
|
|
const logMethod = console[level] || console.log;
|
|
|
|
|
|
if (sanitizedData) {
|
|
|
|
|
|
logMethod(message, sanitizedData);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
logMethod(message);
|
|
|
|
|
|
}
|
2025-08-20 23:34:56 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* Sanitize data for logging
|
2025-08-20 23:34:56 -04:00
|
|
|
|
*/
|
|
|
|
|
|
_sanitizeLogData(data) {
|
|
|
|
|
|
if (!data || typeof data !== 'object') {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// For primitive types — check for sensitive patterns
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (typeof data === 'string') {
|
|
|
|
|
|
return this._sanitizeString(data);
|
|
|
|
|
|
}
|
2025-08-20 23:34:56 -04:00
|
|
|
|
return data;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const sanitized = {};
|
|
|
|
|
|
|
|
|
|
|
|
for (const [key, value] of Object.entries(data)) {
|
|
|
|
|
|
const lowerKey = key.toLowerCase();
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// ABSOLUTE BLACKLIST — never log
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (this._absoluteBlacklist.has(key) ||
|
|
|
|
|
|
Array.from(this._absoluteBlacklist).some(banned => lowerKey.includes(banned))) {
|
|
|
|
|
|
sanitized[key] = '[ABSOLUTELY_FORBIDDEN]';
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// WHITELIST — safe fields are logged as-is
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (this._safeFieldsWhitelist.has(key)) {
|
2025-08-20 23:34:56 -04:00
|
|
|
|
sanitized[key] = value;
|
2025-08-21 04:07:16 -04:00
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Strict handling for all other fields
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (typeof value === 'boolean' || typeof value === 'number') {
|
2025-08-20 23:34:56 -04:00
|
|
|
|
sanitized[key] = value;
|
2025-08-21 04:07:16 -04:00
|
|
|
|
} else if (typeof value === 'string') {
|
|
|
|
|
|
sanitized[key] = this._sanitizeString(value);
|
|
|
|
|
|
} else if (value instanceof ArrayBuffer || value instanceof Uint8Array) {
|
|
|
|
|
|
sanitized[key] = `[${value.constructor.name}(${value.byteLength || value.length} bytes)]`;
|
|
|
|
|
|
} else if (value && typeof value === 'object') {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Recursive with depth limitation
|
2025-08-20 23:34:56 -04:00
|
|
|
|
sanitized[key] = this._sanitizeLogData(value);
|
|
|
|
|
|
} else {
|
2025-08-21 04:07:16 -04:00
|
|
|
|
sanitized[key] = `[${typeof value}]`;
|
2025-08-20 23:34:56 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return sanitized;
|
|
|
|
|
|
}
|
2025-08-21 04:07:16 -04:00
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* NEW: Specialized sanitization for strings
|
2025-08-21 04:07:16 -04:00
|
|
|
|
*/
|
|
|
|
|
|
_sanitizeString(str) {
|
|
|
|
|
|
if (typeof str !== 'string' || str.length === 0) {
|
|
|
|
|
|
return str;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// CRITICAL: Detect sensitive patterns
|
2025-08-21 04:07:16 -04:00
|
|
|
|
const sensitivePatterns = [
|
2025-08-21 05:16:41 -04:00
|
|
|
|
/[a-f0-9]{32,}/i, // Hex strings (keys)
|
|
|
|
|
|
/[A-Za-z0-9+/=]{20,}/, // Base64 strings
|
|
|
|
|
|
/\b[A-Za-z0-9]{20,}\b/, // Long alphanumeric strings
|
|
|
|
|
|
/BEGIN\s+(PRIVATE|PUBLIC)\s+KEY/i, // PEM keys
|
|
|
|
|
|
/\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/, // Credit cards
|
2025-08-21 04:07:16 -04:00
|
|
|
|
/\b\d{3}-\d{2}-\d{4}\b/, // SSN
|
2025-08-21 05:16:41 -04:00
|
|
|
|
/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/, // Email (partial)
|
2025-08-21 04:07:16 -04:00
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
for (const pattern of sensitivePatterns) {
|
|
|
|
|
|
if (pattern.test(str)) {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// If short — fully hide
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (str.length <= 10) {
|
|
|
|
|
|
return '[SENSITIVE]';
|
|
|
|
|
|
}
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// If long — reveal only start and end
|
2025-08-21 04:07:16 -04:00
|
|
|
|
return `${str.substring(0, 3)}...[REDACTED]...${str.substring(str.length - 3)}`;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// For regular strings — limit length
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (str.length > 100) {
|
|
|
|
|
|
return str.substring(0, 50) + '...[TRUNCATED]';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return str;
|
|
|
|
|
|
}
|
2025-08-20 23:34:56 -04:00
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* Checks whether a string contains sensitive content
|
2025-08-20 23:34:56 -04:00
|
|
|
|
*/
|
|
|
|
|
|
_containsSensitiveContent(str) {
|
|
|
|
|
|
if (typeof str !== 'string') return false;
|
|
|
|
|
|
|
|
|
|
|
|
const sensitivePatterns = [
|
2025-08-21 05:16:41 -04:00
|
|
|
|
/[a-f0-9]{32,}/i, // Hex strings (keys)
|
|
|
|
|
|
/[A-Za-z0-9+/=]{20,}/, // Base64 strings
|
|
|
|
|
|
/\b[A-Za-z0-9]{20,}\b/, // Long alphanumeric strings
|
|
|
|
|
|
/BEGIN\s+(PRIVATE|PUBLIC)\s+KEY/i, // PEM keys
|
2025-08-20 23:34:56 -04:00
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
return sensitivePatterns.some(pattern => pattern.test(str));
|
|
|
|
|
|
}
|
2025-08-24 16:30:06 -04:00
|
|
|
|
|
|
|
|
|
|
// ============================================
|
|
|
|
|
|
// DTLS CLIENTHELLO RACE CONDITION PROTECTION
|
|
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ✅ ДОБАВИТЬ проверку источника DTLS пакетов
|
|
|
|
|
|
* Защита от DTLS ClientHello race condition (октябрь 2024)
|
|
|
|
|
|
*/
|
|
|
|
|
|
async validateDTLSSource(clientHelloData, expectedSource) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// Проверяем, что ClientHello приходит от верифицированного ICE endpoint
|
|
|
|
|
|
if (!this.verifiedICEEndpoints.has(expectedSource)) {
|
|
|
|
|
|
this._secureLog('error', 'DTLS ClientHello from unverified source - possible race condition attack', {
|
|
|
|
|
|
source: expectedSource,
|
|
|
|
|
|
verifiedEndpoints: Array.from(this.verifiedICEEndpoints),
|
|
|
|
|
|
timestamp: Date.now()
|
|
|
|
|
|
});
|
|
|
|
|
|
throw new Error('DTLS ClientHello from unverified source - possible race condition attack');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Дополнительная валидация cipher suites
|
|
|
|
|
|
if (!clientHelloData.cipherSuite ||
|
|
|
|
|
|
!EnhancedSecureWebRTCManager.DTLS_PROTECTION.SUPPORTED_CIPHERS.includes(clientHelloData.cipherSuite)) {
|
|
|
|
|
|
this._secureLog('error', 'Invalid cipher suite in ClientHello', {
|
|
|
|
|
|
receivedCipher: clientHelloData.cipherSuite,
|
|
|
|
|
|
supportedCiphers: EnhancedSecureWebRTCManager.DTLS_PROTECTION.SUPPORTED_CIPHERS
|
|
|
|
|
|
});
|
|
|
|
|
|
throw new Error('Invalid cipher suite in ClientHello');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Проверка версии TLS
|
|
|
|
|
|
if (clientHelloData.tlsVersion) {
|
|
|
|
|
|
const version = clientHelloData.tlsVersion;
|
|
|
|
|
|
if (version < EnhancedSecureWebRTCManager.DTLS_PROTECTION.MIN_TLS_VERSION ||
|
|
|
|
|
|
version > EnhancedSecureWebRTCManager.DTLS_PROTECTION.MAX_TLS_VERSION) {
|
|
|
|
|
|
this._secureLog('error', 'Unsupported TLS version in ClientHello', {
|
|
|
|
|
|
receivedVersion: version,
|
|
|
|
|
|
minVersion: EnhancedSecureWebRTCManager.DTLS_PROTECTION.MIN_TLS_VERSION,
|
|
|
|
|
|
maxVersion: EnhancedSecureWebRTCManager.DTLS_PROTECTION.MAX_TLS_VERSION
|
|
|
|
|
|
});
|
|
|
|
|
|
throw new Error('Unsupported TLS version in ClientHello');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this._secureLog('info', 'DTLS ClientHello validation passed', {
|
|
|
|
|
|
source: expectedSource,
|
|
|
|
|
|
cipherSuite: clientHelloData.cipherSuite,
|
|
|
|
|
|
tlsVersion: clientHelloData.tlsVersion
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
this._secureLog('error', 'DTLS ClientHello validation failed', {
|
|
|
|
|
|
error: error.message,
|
|
|
|
|
|
source: expectedSource,
|
|
|
|
|
|
timestamp: Date.now()
|
|
|
|
|
|
});
|
|
|
|
|
|
throw error;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Добавляет ICE endpoint в список верифицированных
|
|
|
|
|
|
*/
|
|
|
|
|
|
addVerifiedICEEndpoint(endpoint) {
|
|
|
|
|
|
this.verifiedICEEndpoints.add(endpoint);
|
|
|
|
|
|
this._secureLog('info', 'ICE endpoint verified and added to DTLS protection', {
|
|
|
|
|
|
endpoint: endpoint,
|
|
|
|
|
|
totalVerified: this.verifiedICEEndpoints.size
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Обрабатывает DTLS ClientHello с защитой от race condition
|
|
|
|
|
|
*/
|
|
|
|
|
|
async handleDTLSClientHello(clientHelloData, sourceEndpoint) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// Проверяем, что ICE верификация завершена
|
|
|
|
|
|
if (this.iceVerificationInProgress) {
|
|
|
|
|
|
// Помещаем в очередь до завершения ICE верификации
|
|
|
|
|
|
this.dtlsClientHelloQueue.set(sourceEndpoint, {
|
|
|
|
|
|
data: clientHelloData,
|
|
|
|
|
|
timestamp: Date.now(),
|
|
|
|
|
|
attempts: 0
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
this._secureLog('warn', 'DTLS ClientHello queued - ICE verification in progress', {
|
|
|
|
|
|
source: sourceEndpoint,
|
|
|
|
|
|
queueSize: this.dtlsClientHelloQueue.size
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return false; // Обработка отложена
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Валидируем источник DTLS пакета
|
|
|
|
|
|
await this.validateDTLSSource(clientHelloData, sourceEndpoint);
|
|
|
|
|
|
|
|
|
|
|
|
// Обрабатываем валидный ClientHello
|
|
|
|
|
|
this._secureLog('info', 'DTLS ClientHello processed successfully', {
|
|
|
|
|
|
source: sourceEndpoint,
|
|
|
|
|
|
cipherSuite: clientHelloData.cipherSuite
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
this._secureLog('error', 'DTLS ClientHello handling failed', {
|
|
|
|
|
|
error: error.message,
|
|
|
|
|
|
source: sourceEndpoint,
|
|
|
|
|
|
timestamp: Date.now()
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Блокируем подозрительный endpoint
|
|
|
|
|
|
this.verifiedICEEndpoints.delete(sourceEndpoint);
|
|
|
|
|
|
|
|
|
|
|
|
throw error;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Завершает ICE верификацию и обрабатывает отложенные DTLS сообщения
|
|
|
|
|
|
*/
|
|
|
|
|
|
async completeICEVerification(verifiedEndpoints) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
this.iceVerificationInProgress = false;
|
|
|
|
|
|
|
|
|
|
|
|
// Добавляем верифицированные endpoints
|
|
|
|
|
|
for (const endpoint of verifiedEndpoints) {
|
|
|
|
|
|
this.addVerifiedICEEndpoint(endpoint);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Обрабатываем отложенные DTLS ClientHello сообщения
|
|
|
|
|
|
for (const [endpoint, queuedData] of this.dtlsClientHelloQueue.entries()) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (this.verifiedICEEndpoints.has(endpoint)) {
|
|
|
|
|
|
await this.handleDTLSClientHello(queuedData.data, endpoint);
|
|
|
|
|
|
this.dtlsClientHelloQueue.delete(endpoint);
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
this._secureLog('error', 'Failed to process queued DTLS ClientHello', {
|
|
|
|
|
|
endpoint: endpoint,
|
|
|
|
|
|
error: error.message
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this._secureLog('info', 'ICE verification completed and DTLS queue processed', {
|
|
|
|
|
|
verifiedEndpoints: verifiedEndpoints.length,
|
|
|
|
|
|
processedQueue: this.dtlsClientHelloQueue.size
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
this._secureLog('error', 'ICE verification completion failed', {
|
|
|
|
|
|
error: error.message
|
|
|
|
|
|
});
|
|
|
|
|
|
throw error;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-20 23:34:56 -04:00
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// SECURE LOGGING SYSTEM
|
2025-08-20 23:34:56 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* Detects production mode
|
2025-08-20 23:34:56 -04:00
|
|
|
|
*/
|
|
|
|
|
|
_detectProductionMode() {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Check various production mode indicators
|
2025-08-20 23:34:56 -04:00
|
|
|
|
return (
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Standard env variables
|
2025-08-20 23:34:56 -04:00
|
|
|
|
(typeof process !== 'undefined' && process.env?.NODE_ENV === 'production') ||
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// No debug flags
|
2025-08-20 23:34:56 -04:00
|
|
|
|
(!window.DEBUG_MODE && !window.DEVELOPMENT_MODE) ||
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Production domains
|
2025-08-20 23:34:56 -04:00
|
|
|
|
(window.location.hostname && !window.location.hostname.includes('localhost') &&
|
|
|
|
|
|
!window.location.hostname.includes('127.0.0.1') &&
|
|
|
|
|
|
!window.location.hostname.includes('.local')) ||
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Minified code (heuristic check)
|
2025-08-20 23:34:56 -04:00
|
|
|
|
(typeof window.webpackHotUpdate === 'undefined' && !window.location.search.includes('debug'))
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
2025-08-20 23:04:29 -04:00
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// FIXED SECURE GLOBAL API
|
2025-08-20 23:04:29 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* Sets up a secure global API with limited access
|
2025-08-20 23:04:29 -04:00
|
|
|
|
*/
|
|
|
|
|
|
_setupSecureGlobalAPI() {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Create a restricted public API
|
2025-08-20 23:04:29 -04:00
|
|
|
|
const secureAPI = {
|
|
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// SAFE PUBLIC METHODS
|
2025-08-20 23:04:29 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* Send a message (safe wrapper)
|
2025-08-20 23:04:29 -04:00
|
|
|
|
*/
|
|
|
|
|
|
sendMessage: (message) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (typeof message !== 'string' || message.length === 0) {
|
|
|
|
|
|
throw new Error('Invalid message format');
|
|
|
|
|
|
}
|
|
|
|
|
|
if (message.length > 10000) {
|
|
|
|
|
|
throw new Error('Message too long');
|
|
|
|
|
|
}
|
|
|
|
|
|
return this.sendMessage(message);
|
|
|
|
|
|
} catch (error) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ Failed to send message through secure API', { errorType: error.constructor.name });
|
2025-08-20 23:04:29 -04:00
|
|
|
|
throw new Error('Failed to send message');
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* Get connection status (public information only)
|
2025-08-20 23:04:29 -04:00
|
|
|
|
*/
|
|
|
|
|
|
getConnectionStatus: () => {
|
|
|
|
|
|
return {
|
|
|
|
|
|
isConnected: this.isConnected(),
|
|
|
|
|
|
isVerified: this.isVerified,
|
|
|
|
|
|
connectionState: this.peerConnection?.connectionState || 'disconnected',
|
|
|
|
|
|
};
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* Get security status (limited information)
|
2025-08-20 23:04:29 -04:00
|
|
|
|
*/
|
|
|
|
|
|
getSecurityStatus: () => {
|
|
|
|
|
|
const status = this.getSecurityStatus();
|
|
|
|
|
|
return {
|
|
|
|
|
|
securityLevel: status.securityLevel,
|
|
|
|
|
|
stage: status.stage,
|
|
|
|
|
|
activeFeaturesCount: status.activeFeaturesCount,
|
|
|
|
|
|
};
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* Send a file (safe wrapper)
|
2025-08-20 23:04:29 -04:00
|
|
|
|
*/
|
|
|
|
|
|
sendFile: async (file) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (!file || !(file instanceof File)) {
|
|
|
|
|
|
throw new Error('Invalid file object');
|
|
|
|
|
|
}
|
2025-08-21 05:16:41 -04:00
|
|
|
|
if (file.size > 100 * 1024 * 1024) { // 100MB limit
|
2025-08-20 23:04:29 -04:00
|
|
|
|
throw new Error('File too large');
|
|
|
|
|
|
}
|
|
|
|
|
|
return await this.sendFile(file);
|
|
|
|
|
|
} catch (error) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ Failed to send file through secure API', { errorType: error.constructor.name });
|
2025-08-20 23:04:29 -04:00
|
|
|
|
throw new Error('Failed to send file');
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* Get file transfer status
|
2025-08-20 23:04:29 -04:00
|
|
|
|
*/
|
|
|
|
|
|
getFileTransferStatus: () => {
|
|
|
|
|
|
const status = this.getFileTransferStatus();
|
|
|
|
|
|
return {
|
|
|
|
|
|
initialized: status.initialized,
|
|
|
|
|
|
status: status.status,
|
|
|
|
|
|
activeTransfers: status.activeTransfers || 0,
|
|
|
|
|
|
receivingTransfers: status.receivingTransfers || 0,
|
|
|
|
|
|
};
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* Disconnect (safe)
|
2025-08-20 23:04:29 -04:00
|
|
|
|
*/
|
|
|
|
|
|
disconnect: () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
this.disconnect();
|
|
|
|
|
|
return true;
|
|
|
|
|
|
} catch (error) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ Failed to disconnect through secure API', { errorType: error.constructor.name });
|
2025-08-20 23:04:29 -04:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// API meta information
|
2025-08-20 23:04:29 -04:00
|
|
|
|
_api: {
|
|
|
|
|
|
version: '4.0.1-secure',
|
|
|
|
|
|
type: 'secure-wrapper',
|
|
|
|
|
|
methods: [
|
|
|
|
|
|
'sendMessage', 'getConnectionStatus', 'getSecurityStatus',
|
|
|
|
|
|
'sendFile', 'getFileTransferStatus', 'disconnect'
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// INSTALL SECURE GLOBAL API
|
2025-08-20 23:04:29 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Make the API immutable
|
2025-08-20 23:04:29 -04:00
|
|
|
|
Object.freeze(secureAPI);
|
|
|
|
|
|
Object.freeze(secureAPI._api);
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Set global API only if it does not exist yet
|
2025-08-20 23:04:29 -04:00
|
|
|
|
if (!window.secureBitChat) {
|
|
|
|
|
|
Object.defineProperty(window, 'secureBitChat', {
|
|
|
|
|
|
value: secureAPI,
|
|
|
|
|
|
writable: false,
|
|
|
|
|
|
enumerable: true,
|
|
|
|
|
|
configurable: false
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
console.log('🔒 Secure global API established: window.secureBitChat');
|
|
|
|
|
|
} else {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('warn', '⚠️ Global API already exists, skipping setup');
|
2025-08-20 23:04:29 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// SIMPLIFIED PROTECTION WITHOUT PROXY
|
2025-08-20 23:04:29 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
this._setupSimpleProtection();
|
|
|
|
|
|
}
|
|
|
|
|
|
_setupSimpleProtection() {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Protect via monitoring only, without overriding window
|
2025-08-20 23:04:29 -04:00
|
|
|
|
this._monitorGlobalExposure();
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Console notice
|
2025-08-20 23:04:29 -04:00
|
|
|
|
if (window.DEBUG_MODE) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('warn', '⚠️ 🔒 Security Notice: WebRTC Manager is protected. Use window.secureBitChat for safe access.');
|
2025-08-20 23:04:29 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* Monitoring global exposure without Proxy
|
2025-08-20 23:04:29 -04:00
|
|
|
|
*/
|
|
|
|
|
|
_monitorGlobalExposure() {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Potentially dangerous global names
|
2025-08-20 23:04:29 -04:00
|
|
|
|
const dangerousNames = [
|
|
|
|
|
|
'webrtcManager', 'globalWebRTCManager', 'webrtcInstance',
|
|
|
|
|
|
'rtcManager', 'secureWebRTC', 'enhancedWebRTC'
|
|
|
|
|
|
];
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Periodic check
|
2025-08-20 23:04:29 -04:00
|
|
|
|
const checkForExposure = () => {
|
|
|
|
|
|
const exposures = [];
|
|
|
|
|
|
|
|
|
|
|
|
dangerousNames.forEach(name => {
|
|
|
|
|
|
if (window[name] === this ||
|
|
|
|
|
|
(window[name] && window[name].constructor === this.constructor)) {
|
|
|
|
|
|
exposures.push(name);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (exposures.length > 0) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('warn', '⚠️ 🚫 WARNING: Potential security exposure detected:', { details: exposures });
|
2025-08-20 23:04:29 -04:00
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// In production, remove automatically
|
2025-08-20 23:04:29 -04:00
|
|
|
|
if (!window.DEBUG_MODE) {
|
|
|
|
|
|
exposures.forEach(name => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
delete window[name];
|
|
|
|
|
|
console.log(`🧹 Removed exposure: ${name}`);
|
|
|
|
|
|
} catch (error) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ Failed to remove: ${name}');
|
2025-08-20 23:04:29 -04:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return exposures;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Immediate check
|
2025-08-20 23:04:29 -04:00
|
|
|
|
checkForExposure();
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Periodic check
|
|
|
|
|
|
const interval = window.DEBUG_MODE ? 30000 : 300000; // 30s in dev, 5min in prod
|
2025-08-20 23:04:29 -04:00
|
|
|
|
setInterval(checkForExposure, interval);
|
|
|
|
|
|
}
|
|
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* Prevents accidental global exposure
|
2025-08-20 23:04:29 -04:00
|
|
|
|
*/
|
|
|
|
|
|
_preventGlobalExposure() {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Monitor attempts to add webrtc objects to window
|
2025-08-20 23:04:29 -04:00
|
|
|
|
const originalDefineProperty = Object.defineProperty;
|
|
|
|
|
|
const self = this;
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Override defineProperty for window only for webrtc-related properties
|
2025-08-20 23:04:29 -04:00
|
|
|
|
const webrtcRelatedNames = [
|
|
|
|
|
|
'webrtcManager', 'globalWebRTCManager', 'webrtcInstance',
|
|
|
|
|
|
'rtcManager', 'secureWebRTC', 'enhancedWebRTC'
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
Object.defineProperty = function(obj, prop, descriptor) {
|
|
|
|
|
|
if (obj === window && webrtcRelatedNames.includes(prop)) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('warn', '⚠️ 🚫 Prevented potential global exposure of: ${prop}');
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Do not set the property, only log
|
2025-08-20 23:04:29 -04:00
|
|
|
|
return obj;
|
|
|
|
|
|
}
|
|
|
|
|
|
return originalDefineProperty.call(this, obj, prop, descriptor);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Protect against direct assignment
|
2025-08-20 23:04:29 -04:00
|
|
|
|
const webrtcRelatedPatterns = /webrtc|rtc|secure.*chat/i;
|
|
|
|
|
|
const handler = {
|
|
|
|
|
|
set(target, property, value) {
|
|
|
|
|
|
if (typeof property === 'string' && webrtcRelatedPatterns.test(property)) {
|
|
|
|
|
|
if (value === self || (value && value.constructor === self.constructor)) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('warn', '⚠️ 🚫 Prevented global exposure attempt: window.${property}');
|
2025-08-21 05:16:41 -04:00
|
|
|
|
return true; // Pretend we set it, but do not
|
2025-08-20 23:04:29 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
target[property] = value;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Apply Proxy only in development mode for performance
|
2025-08-20 23:04:29 -04:00
|
|
|
|
if (window.DEBUG_MODE) {
|
|
|
|
|
|
window = new Proxy(window, handler);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* API integrity check
|
2025-08-20 23:04:29 -04:00
|
|
|
|
*/
|
|
|
|
|
|
_verifyAPIIntegrity() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (!window.secureBitChat) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ SECURITY ALERT: Secure API has been removed!');
|
2025-08-20 23:04:29 -04:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const requiredMethods = ['sendMessage', 'getConnectionStatus', 'disconnect'];
|
|
|
|
|
|
const missingMethods = requiredMethods.filter(method =>
|
|
|
|
|
|
typeof window.secureBitChat[method] !== 'function'
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (missingMethods.length > 0) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ SECURITY ALERT: API tampering detected, missing methods:', { errorType: missingMethods?.constructor?.name || 'Unknown' });
|
2025-08-20 23:04:29 -04:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
} catch (error) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ SECURITY ALERT: API integrity check failed:', { errorType: error?.constructor?.name || 'Unknown' });
|
2025-08-20 23:04:29 -04:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// ADDITIONAL SECURITY METHODS
|
2025-08-20 23:04:29 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* Checks for accidental exposure in global scope
|
2025-08-20 23:04:29 -04:00
|
|
|
|
*/
|
|
|
|
|
|
_auditGlobalExposure() {
|
|
|
|
|
|
const dangerousExposures = [];
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Check window for WebRTC manager
|
2025-08-20 23:04:29 -04:00
|
|
|
|
for (const prop in window) {
|
|
|
|
|
|
const value = window[prop];
|
|
|
|
|
|
if (value === this || (value && value.constructor === this.constructor)) {
|
|
|
|
|
|
dangerousExposures.push(prop);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (dangerousExposures.length > 0) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ SECURITY ALERT: WebRTC Manager exposed globally:', { errorType: dangerousExposures?.constructor?.name || 'Unknown' });
|
2025-08-20 23:04:29 -04:00
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// In production mode, remove exposure automatically
|
2025-08-20 23:04:29 -04:00
|
|
|
|
if (!window.DEBUG_MODE) {
|
|
|
|
|
|
dangerousExposures.forEach(prop => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
delete window[prop];
|
|
|
|
|
|
console.log(`🧹 Removed dangerous global exposure: ${prop}`);
|
|
|
|
|
|
} catch (error) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ Failed to remove exposure: ${prop}', { errorType: error?.constructor?.name || 'Unknown' });
|
2025-08-20 23:04:29 -04:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return dangerousExposures;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* Periodic security audit
|
2025-08-20 23:04:29 -04:00
|
|
|
|
*/
|
|
|
|
|
|
_startSecurityAudit() {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Check every 30 seconds in development, every 5 minutes in production
|
2025-08-20 23:04:29 -04:00
|
|
|
|
const auditInterval = window.DEBUG_MODE ? 30000 : 300000;
|
|
|
|
|
|
|
|
|
|
|
|
setInterval(() => {
|
|
|
|
|
|
const exposures = this._auditGlobalExposure();
|
|
|
|
|
|
|
|
|
|
|
|
if (exposures.length > 0 && !window.DEBUG_MODE) {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// In production, this is a critical issue
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ CRITICAL: Unauthorized global exposure detected in production');
|
2025-08-20 23:04:29 -04:00
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Could add alert sending or forced shutdown
|
2025-08-20 23:04:29 -04:00
|
|
|
|
// this.emergencyShutdown();
|
|
|
|
|
|
}
|
|
|
|
|
|
}, auditInterval);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* Emergency shutdown for critical issues
|
2025-08-20 23:04:29 -04:00
|
|
|
|
*/
|
|
|
|
|
|
_emergencyShutdown(reason = 'Security breach') {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ EMERGENCY SHUTDOWN: ${reason}');
|
2025-08-20 23:04:29 -04:00
|
|
|
|
|
|
|
|
|
|
try {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Clear critical data
|
2025-08-20 23:04:29 -04:00
|
|
|
|
this.encryptionKey = null;
|
|
|
|
|
|
this.macKey = null;
|
|
|
|
|
|
this.metadataKey = null;
|
|
|
|
|
|
this.verificationCode = null;
|
|
|
|
|
|
this.keyFingerprint = null;
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Close connections
|
2025-08-20 23:04:29 -04:00
|
|
|
|
if (this.dataChannel) {
|
|
|
|
|
|
this.dataChannel.close();
|
|
|
|
|
|
this.dataChannel = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (this.peerConnection) {
|
|
|
|
|
|
this.peerConnection.close();
|
|
|
|
|
|
this.peerConnection = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Clear buffers
|
2025-08-20 23:04:29 -04:00
|
|
|
|
this.messageQueue = [];
|
|
|
|
|
|
this.processedMessageIds.clear();
|
|
|
|
|
|
this.packetBuffer.clear();
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Notify UI
|
2025-08-20 23:04:29 -04:00
|
|
|
|
if (this.onStatusChange) {
|
|
|
|
|
|
this.onStatusChange('security_breach');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
console.log('🔒 Emergency shutdown completed');
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ Error during emergency shutdown:', { errorType: error?.constructor?.name || 'Unknown' });
|
2025-08-20 23:04:29 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
_finalizeSecureInitialization() {
|
2025-08-21 00:06:28 -04:00
|
|
|
|
this._startKeySecurityMonitoring();
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Verify API integrity
|
2025-08-20 23:04:29 -04:00
|
|
|
|
if (!this._verifyAPIIntegrity()) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ Security initialization failed');
|
2025-08-20 23:04:29 -04:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Start monitoring
|
2025-08-20 23:04:29 -04:00
|
|
|
|
this._startSecurityMonitoring();
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Start periodic log cleanup
|
2025-08-20 23:34:56 -04:00
|
|
|
|
setInterval(() => {
|
|
|
|
|
|
this._cleanupLogs();
|
|
|
|
|
|
}, 300000);
|
2025-08-20 23:04:29 -04:00
|
|
|
|
|
|
|
|
|
|
console.log('✅ Secure WebRTC Manager initialization completed');
|
|
|
|
|
|
}
|
|
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* Start security monitoring
|
2025-08-20 23:04:29 -04:00
|
|
|
|
*/
|
|
|
|
|
|
_startSecurityMonitoring() {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Check every 5 minutes
|
2025-08-20 23:04:29 -04:00
|
|
|
|
setInterval(() => {
|
|
|
|
|
|
this._verifyAPIIntegrity();
|
|
|
|
|
|
}, 300000);
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// In development mode, perform more frequent checks
|
2025-08-20 23:04:29 -04:00
|
|
|
|
if (window.DEBUG_MODE) {
|
|
|
|
|
|
setInterval(() => {
|
|
|
|
|
|
this._monitorGlobalExposure();
|
|
|
|
|
|
}, 30000);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* Validates connection readiness for sending data
|
|
|
|
|
|
* @param {boolean} throwError - whether to throw on not ready
|
|
|
|
|
|
* @returns {boolean} true if connection is ready
|
2025-08-20 23:04:29 -04:00
|
|
|
|
*/
|
|
|
|
|
|
_validateConnection(throwError = true) {
|
|
|
|
|
|
const isDataChannelReady = this.dataChannel && this.dataChannel.readyState === 'open';
|
|
|
|
|
|
const isConnectionVerified = this.isVerified;
|
|
|
|
|
|
const isValid = isDataChannelReady && isConnectionVerified;
|
|
|
|
|
|
|
|
|
|
|
|
if (!isValid && throwError) {
|
|
|
|
|
|
if (!isDataChannelReady) {
|
|
|
|
|
|
throw new Error('Data channel not ready');
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!isConnectionVerified) {
|
|
|
|
|
|
throw new Error('Connection not verified');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return isValid;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* Validates encryption keys readiness
|
|
|
|
|
|
* @param {boolean} throwError - whether to throw on not ready
|
|
|
|
|
|
* @returns {boolean} true if keys are ready
|
2025-08-20 23:04:29 -04:00
|
|
|
|
*/
|
|
|
|
|
|
_validateEncryptionKeys(throwError = true) {
|
|
|
|
|
|
const hasAllKeys = !!(this.encryptionKey && this.macKey && this.metadataKey);
|
|
|
|
|
|
|
|
|
|
|
|
if (!hasAllKeys && throwError) {
|
|
|
|
|
|
throw new Error('Encryption keys not initialized');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return hasAllKeys;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* Checks whether a message is a file-transfer message
|
|
|
|
|
|
* @param {string|object} data - message payload
|
|
|
|
|
|
* @returns {boolean} true if it's a file message
|
2025-08-20 23:04:29 -04:00
|
|
|
|
*/
|
|
|
|
|
|
_isFileMessage(data) {
|
|
|
|
|
|
if (typeof data === 'string') {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const parsed = JSON.parse(data);
|
|
|
|
|
|
return parsed.type && parsed.type.startsWith('file_');
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (typeof data === 'object' && data.type) {
|
|
|
|
|
|
return data.type.startsWith('file_');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* Checks whether a message is a system message
|
|
|
|
|
|
* @param {string|object} data - message payload
|
|
|
|
|
|
* @returns {boolean} true if it's a system message
|
2025-08-20 23:04:29 -04:00
|
|
|
|
*/
|
|
|
|
|
|
_isSystemMessage(data) {
|
|
|
|
|
|
const systemTypes = [
|
|
|
|
|
|
EnhancedSecureWebRTCManager.MESSAGE_TYPES.HEARTBEAT,
|
|
|
|
|
|
EnhancedSecureWebRTCManager.MESSAGE_TYPES.VERIFICATION,
|
|
|
|
|
|
EnhancedSecureWebRTCManager.MESSAGE_TYPES.VERIFICATION_RESPONSE,
|
|
|
|
|
|
EnhancedSecureWebRTCManager.MESSAGE_TYPES.PEER_DISCONNECT,
|
|
|
|
|
|
EnhancedSecureWebRTCManager.MESSAGE_TYPES.SECURITY_UPGRADE,
|
|
|
|
|
|
EnhancedSecureWebRTCManager.MESSAGE_TYPES.KEY_ROTATION_SIGNAL,
|
|
|
|
|
|
EnhancedSecureWebRTCManager.MESSAGE_TYPES.KEY_ROTATION_READY
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
if (typeof data === 'string') {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const parsed = JSON.parse(data);
|
|
|
|
|
|
return systemTypes.includes(parsed.type);
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (typeof data === 'object' && data.type) {
|
|
|
|
|
|
return systemTypes.includes(data.type);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* Checks whether a message is fake traffic
|
|
|
|
|
|
* @param {any} data - message payload
|
|
|
|
|
|
* @returns {boolean} true if it's a fake message
|
2025-08-20 23:04:29 -04:00
|
|
|
|
*/
|
|
|
|
|
|
_isFakeMessage(data) {
|
|
|
|
|
|
if (typeof data === 'string') {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const parsed = JSON.parse(data);
|
|
|
|
|
|
return parsed.type === EnhancedSecureWebRTCManager.MESSAGE_TYPES.FAKE ||
|
|
|
|
|
|
parsed.isFakeTraffic === true;
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (typeof data === 'object' && data !== null) {
|
|
|
|
|
|
return data.type === EnhancedSecureWebRTCManager.MESSAGE_TYPES.FAKE ||
|
|
|
|
|
|
data.isFakeTraffic === true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* Safely executes an operation with error handling
|
|
|
|
|
|
* @param {Function} operation - operation to execute
|
|
|
|
|
|
* @param {string} errorMessage - error message to log
|
|
|
|
|
|
* @param {any} fallback - default value on error
|
|
|
|
|
|
* @returns {any} operation result or fallback
|
2025-08-20 23:04:29 -04:00
|
|
|
|
*/
|
|
|
|
|
|
_withErrorHandling(operation, errorMessage, fallback = null) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
return operation();
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
if (window.DEBUG_MODE) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ ${errorMessage}:', { errorType: error?.constructor?.name || 'Unknown' });
|
2025-08-20 23:04:29 -04:00
|
|
|
|
}
|
|
|
|
|
|
return fallback;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* Safely executes an async operation with error handling
|
|
|
|
|
|
* @param {Function} operation - async operation
|
|
|
|
|
|
* @param {string} errorMessage - error message to log
|
|
|
|
|
|
* @param {any} fallback - default value on error
|
|
|
|
|
|
* @returns {Promise<any>} operation result or fallback
|
2025-08-20 23:04:29 -04:00
|
|
|
|
*/
|
|
|
|
|
|
async _withAsyncErrorHandling(operation, errorMessage, fallback = null) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
return await operation();
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
if (window.DEBUG_MODE) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ ${errorMessage}:', { errorType: error?.constructor?.name || 'Unknown' });
|
2025-08-20 23:04:29 -04:00
|
|
|
|
}
|
|
|
|
|
|
return fallback;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* Checks rate limits
|
|
|
|
|
|
* @returns {boolean} true if allowed to proceed
|
2025-08-20 23:04:29 -04:00
|
|
|
|
*/
|
|
|
|
|
|
_checkRateLimit() {
|
|
|
|
|
|
return window.EnhancedSecureCryptoUtils.rateLimiter.checkConnectionRate(this.rateLimiterId);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* Extracts message type from data
|
|
|
|
|
|
* @param {string|object} data - message data
|
|
|
|
|
|
* @returns {string|null} message type or null
|
2025-08-20 23:04:29 -04:00
|
|
|
|
*/
|
|
|
|
|
|
_getMessageType(data) {
|
|
|
|
|
|
if (typeof data === 'string') {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const parsed = JSON.parse(data);
|
|
|
|
|
|
return parsed.type || null;
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (typeof data === 'object' && data !== null) {
|
|
|
|
|
|
return data.type || null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* Resets notification flags for a new connection
|
2025-08-20 23:04:29 -04:00
|
|
|
|
*/
|
|
|
|
|
|
_resetNotificationFlags() {
|
|
|
|
|
|
this.lastSecurityLevelNotification = null;
|
|
|
|
|
|
this.verificationNotificationSent = false;
|
|
|
|
|
|
this.verificationInitiationSent = false;
|
|
|
|
|
|
this.disconnectNotificationSent = false;
|
|
|
|
|
|
this.reconnectionFailedNotificationSent = false;
|
|
|
|
|
|
this.peerDisconnectNotificationSent = false;
|
|
|
|
|
|
this.connectionClosedNotificationSent = false;
|
|
|
|
|
|
this.fakeTrafficDisabledNotificationSent = false;
|
|
|
|
|
|
this.advancedFeaturesDisabledNotificationSent = false;
|
|
|
|
|
|
this.securityUpgradeNotificationSent = false;
|
|
|
|
|
|
this.lastSecurityUpgradeStage = null;
|
|
|
|
|
|
this.securityCalculationNotificationSent = false;
|
|
|
|
|
|
this.lastSecurityCalculationLevel = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* Checks whether a message was filtered out
|
|
|
|
|
|
* @param {any} result - processing result
|
|
|
|
|
|
* @returns {boolean} true if filtered
|
2025-08-20 23:04:29 -04:00
|
|
|
|
*/
|
|
|
|
|
|
_isFilteredMessage(result) {
|
|
|
|
|
|
const filteredResults = Object.values(EnhancedSecureWebRTCManager.FILTERED_RESULTS);
|
|
|
|
|
|
return filteredResults.includes(result);
|
|
|
|
|
|
}
|
2025-08-20 23:34:56 -04:00
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* Cleans up log counters to prevent memory leaks
|
2025-08-20 23:34:56 -04:00
|
|
|
|
*/
|
|
|
|
|
|
_cleanupLogs() {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Clear log counters if there are too many
|
2025-08-20 23:34:56 -04:00
|
|
|
|
if (this._logCounts.size > 1000) {
|
|
|
|
|
|
this._logCounts.clear();
|
|
|
|
|
|
this._secureLog('debug', '🧹 Log counts cleared');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* Returns logging stats (for diagnostics)
|
2025-08-20 23:34:56 -04:00
|
|
|
|
*/
|
|
|
|
|
|
_getLoggingStats() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
isProductionMode: this._isProductionMode,
|
|
|
|
|
|
debugMode: this._debugMode,
|
|
|
|
|
|
currentLogLevel: this._currentLogLevel,
|
|
|
|
|
|
logCountsSize: this._logCounts.size,
|
|
|
|
|
|
maxLogCount: this._maxLogCount
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* Emergency logging disable
|
2025-08-20 23:34:56 -04:00
|
|
|
|
*/
|
|
|
|
|
|
_emergencyDisableLogging() {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
this._currentLogLevel = -1; // Disable all logs
|
2025-08-20 23:34:56 -04:00
|
|
|
|
this._logCounts.clear();
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Override _secureLog to a no-op
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this._secureLog = () => {};
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Only critical error to console (no payload)
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ SECURITY: Logging disabled due to potential data exposure');
|
2025-08-21 04:07:16 -04:00
|
|
|
|
}
|
|
|
|
|
|
_auditLogMessage(message, data) {
|
|
|
|
|
|
if (!data || typeof data !== 'object') return true;
|
|
|
|
|
|
|
|
|
|
|
|
const dataString = JSON.stringify(data).toLowerCase();
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Check for accidental leaks
|
|
|
|
|
|
// Narrow patterns to avoid false positives (e.g., words like "keyOperation")
|
2025-08-21 04:07:16 -04:00
|
|
|
|
const dangerousPatterns = [
|
|
|
|
|
|
'secret', 'token', 'password', 'credential',
|
|
|
|
|
|
'fingerprint', 'salt', 'signature', 'private_key', 'api_key', 'private'
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
for (const pattern of dangerousPatterns) {
|
|
|
|
|
|
if (dataString.includes(pattern) && !this._safeFieldsWhitelist.has(pattern)) {
|
|
|
|
|
|
this._emergencyDisableLogging();
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ SECURITY BREACH: Potential sensitive data in log: ${pattern}');
|
2025-08-21 04:07:16 -04:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-20 23:34:56 -04:00
|
|
|
|
|
2025-08-21 04:07:16 -04:00
|
|
|
|
return true;
|
2025-08-20 23:34:56 -04:00
|
|
|
|
}
|
2025-08-20 23:04:29 -04:00
|
|
|
|
|
2025-08-18 21:45:50 -04:00
|
|
|
|
initializeFileTransfer() {
|
2025-08-20 23:04:29 -04:00
|
|
|
|
try {
|
2025-08-21 04:07:16 -04:00
|
|
|
|
console.log('🔧 Initializing Enhanced Secure File Transfer system...');
|
|
|
|
|
|
|
2025-08-21 17:40:17 -04:00
|
|
|
|
// КРИТИЧЕСКОЕ ИСПРАВЛЕНИЕ: Проверяем, не инициализирована ли уже система
|
|
|
|
|
|
if (this.fileTransferSystem) {
|
|
|
|
|
|
console.log('✅ File transfer system already initialized');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// CRITICAL FIX: Step-by-step readiness check
|
2025-08-21 04:07:16 -04:00
|
|
|
|
const channelReady = !!(this.dataChannel && this.dataChannel.readyState === 'open');
|
|
|
|
|
|
if (!channelReady) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('warn', '⚠️ Data channel not open, deferring file transfer initialization');
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (this.dataChannel) {
|
|
|
|
|
|
const initHandler = () => {
|
|
|
|
|
|
console.log('🔄 DataChannel opened, initializing file transfer...');
|
|
|
|
|
|
this.initializeFileTransfer();
|
|
|
|
|
|
};
|
|
|
|
|
|
this.dataChannel.addEventListener('open', initHandler, { once: true });
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!this.isVerified) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('warn', '⚠️ Connection not verified yet, deferring file transfer initialization');
|
2025-08-21 04:07:16 -04:00
|
|
|
|
setTimeout(() => this.initializeFileTransfer(), 500);
|
|
|
|
|
|
return;
|
2025-08-20 18:19:42 -04:00
|
|
|
|
}
|
2025-08-20 23:04:29 -04:00
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// FIX: Clean up previous system if present
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (this.fileTransferSystem) {
|
|
|
|
|
|
console.log('🧹 Cleaning up existing file transfer system');
|
|
|
|
|
|
this.fileTransferSystem.cleanup();
|
|
|
|
|
|
this.fileTransferSystem = null;
|
|
|
|
|
|
}
|
2025-08-20 23:04:29 -04:00
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// CRITICAL FIX: Ensure encryption keys are present
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (!this.encryptionKey || !this.macKey) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('warn', '⚠️ Encryption keys not ready, deferring file transfer initialization');
|
2025-08-21 04:07:16 -04:00
|
|
|
|
setTimeout(() => this.initializeFileTransfer(), 1000);
|
|
|
|
|
|
return;
|
2025-08-18 21:45:50 -04:00
|
|
|
|
}
|
2025-08-20 23:04:29 -04:00
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// IMPORTANT: callback order: (onProgress, onComplete, onError, onFileReceived)
|
2025-08-21 04:07:16 -04:00
|
|
|
|
const safeOnComplete = (summary) => {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Sender: finalize transfer, no Blob handling
|
2025-08-21 04:07:16 -04:00
|
|
|
|
try {
|
|
|
|
|
|
console.log('🏁 Sender transfer summary:', summary);
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Optionally forward as progress/UI event
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (this.onFileProgress) {
|
|
|
|
|
|
this.onFileProgress({ type: 'complete', ...summary });
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('warn', '⚠️ onComplete handler failed:', { details: e.message });
|
2025-08-21 04:07:16 -04:00
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
this.fileTransferSystem = new EnhancedSecureFileTransfer(
|
|
|
|
|
|
this,
|
|
|
|
|
|
this.onFileProgress || null,
|
|
|
|
|
|
safeOnComplete,
|
|
|
|
|
|
this.onFileError || null,
|
|
|
|
|
|
this.onFileReceived || null
|
|
|
|
|
|
);
|
2025-08-20 23:04:29 -04:00
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// CRITICAL FIX: Set global references
|
2025-08-21 04:07:16 -04:00
|
|
|
|
window.FILE_TRANSFER_ACTIVE = true;
|
|
|
|
|
|
window.fileTransferSystem = this.fileTransferSystem;
|
2025-08-20 23:04:29 -04:00
|
|
|
|
|
2025-08-21 04:07:16 -04:00
|
|
|
|
console.log('✅ Enhanced Secure File Transfer system initialized successfully');
|
2025-08-20 23:04:29 -04:00
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Verify the system is ready
|
2025-08-21 04:07:16 -04:00
|
|
|
|
const status = this.fileTransferSystem.getSystemStatus();
|
|
|
|
|
|
console.log('🔍 File transfer system status after init:', status);
|
2025-08-20 23:04:29 -04:00
|
|
|
|
|
2025-08-21 04:07:16 -04:00
|
|
|
|
} catch (error) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ Failed to initialize file transfer system', { errorType: error.constructor.name });
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this.fileTransferSystem = null;
|
|
|
|
|
|
window.FILE_TRANSFER_ACTIVE = false;
|
|
|
|
|
|
window.fileTransferSystem = null;
|
|
|
|
|
|
}
|
2025-08-18 21:45:50 -04:00
|
|
|
|
}
|
2025-08-14 03:28:23 -04:00
|
|
|
|
|
|
|
|
|
|
// ============================================
|
|
|
|
|
|
// ENHANCED SECURITY INITIALIZATION
|
|
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
|
|
async initializeEnhancedSecurity() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// Generate nested encryption key
|
|
|
|
|
|
await this.generateNestedEncryptionKey();
|
|
|
|
|
|
|
|
|
|
|
|
// Initialize decoy channels
|
|
|
|
|
|
if (this.decoyChannelConfig.enabled) {
|
|
|
|
|
|
this.initializeDecoyChannels();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Start fake traffic generation
|
|
|
|
|
|
if (this.fakeTrafficConfig.enabled) {
|
|
|
|
|
|
this.startFakeTrafficGeneration();
|
|
|
|
|
|
}
|
2025-08-14 04:01:08 -04:00
|
|
|
|
|
2025-08-14 03:28:23 -04:00
|
|
|
|
} catch (error) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ Failed to initialize enhanced security', { errorType: error.constructor.name });
|
2025-08-14 03:28:23 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-17 20:38:47 -04:00
|
|
|
|
|
2025-08-14 03:28:23 -04:00
|
|
|
|
// Generate fingerprint mask for anti-fingerprinting
|
|
|
|
|
|
generateFingerprintMask() {
|
|
|
|
|
|
const mask = {
|
|
|
|
|
|
timingOffset: Math.random() * 1000,
|
|
|
|
|
|
sizeVariation: Math.random() * 0.5 + 0.75, // 0.75 to 1.25
|
|
|
|
|
|
noisePattern: Array.from(crypto.getRandomValues(new Uint8Array(32))),
|
|
|
|
|
|
headerVariations: [
|
|
|
|
|
|
'X-Client-Version',
|
|
|
|
|
|
'X-Session-ID',
|
|
|
|
|
|
'X-Request-ID',
|
|
|
|
|
|
'X-Timestamp',
|
|
|
|
|
|
'X-Signature'
|
|
|
|
|
|
]
|
|
|
|
|
|
};
|
|
|
|
|
|
return mask;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-17 02:22:55 -04:00
|
|
|
|
// Security configuration for session type
|
|
|
|
|
|
configureSecurityForSession(sessionType, securityLevel) {
|
|
|
|
|
|
console.log(`🔧 Configuring security for ${sessionType} session (${securityLevel} level)`);
|
|
|
|
|
|
|
|
|
|
|
|
this.currentSessionType = sessionType;
|
|
|
|
|
|
this.currentSecurityLevel = securityLevel;
|
|
|
|
|
|
|
|
|
|
|
|
if (window.sessionManager && window.sessionManager.isFeatureAllowedForSession) {
|
|
|
|
|
|
this.sessionConstraints = {};
|
|
|
|
|
|
|
|
|
|
|
|
Object.keys(this.securityFeatures).forEach(feature => {
|
|
|
|
|
|
this.sessionConstraints[feature] = window.sessionManager.isFeatureAllowedForSession(sessionType, feature);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
this.applySessionConstraints();
|
|
|
|
|
|
|
|
|
|
|
|
console.log(`✅ Security configured for ${sessionType}:`, this.sessionConstraints);
|
|
|
|
|
|
|
|
|
|
|
|
this.notifySecurityLevel();
|
2025-08-17 20:38:47 -04:00
|
|
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
this.calculateAndReportSecurityLevel();
|
2025-08-20 23:04:29 -04:00
|
|
|
|
}, EnhancedSecureWebRTCManager.TIMEOUTS.SECURITY_CALC_DELAY);
|
2025-08-17 20:38:47 -04:00
|
|
|
|
|
2025-08-17 02:22:55 -04:00
|
|
|
|
} else {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('warn', '⚠️ Session manager not available, using default security');
|
2025-08-17 02:22:55 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Applying session restrictions
|
|
|
|
|
|
applySessionConstraints() {
|
|
|
|
|
|
if (!this.sessionConstraints) return;
|
|
|
|
|
|
|
|
|
|
|
|
// Applying restrictions to security features
|
|
|
|
|
|
Object.keys(this.sessionConstraints).forEach(feature => {
|
|
|
|
|
|
const allowed = this.sessionConstraints[feature];
|
|
|
|
|
|
|
|
|
|
|
|
if (!allowed && this.securityFeatures[feature]) {
|
|
|
|
|
|
console.log(`🔒 Disabling ${feature} for ${this.currentSessionType} session`);
|
|
|
|
|
|
this.securityFeatures[feature] = false;
|
|
|
|
|
|
|
|
|
|
|
|
// Disabling linked configurations
|
|
|
|
|
|
switch (feature) {
|
|
|
|
|
|
case 'hasFakeTraffic':
|
|
|
|
|
|
this.fakeTrafficConfig.enabled = false;
|
|
|
|
|
|
this.stopFakeTrafficGeneration();
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 'hasDecoyChannels':
|
|
|
|
|
|
this.decoyChannelConfig.enabled = false;
|
|
|
|
|
|
this.cleanupDecoyChannels();
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 'hasPacketReordering':
|
|
|
|
|
|
this.reorderingConfig.enabled = false;
|
|
|
|
|
|
this.packetBuffer.clear();
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 'hasAntiFingerprinting':
|
|
|
|
|
|
this.antiFingerprintingConfig.enabled = false;
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 'hasMessageChunking':
|
|
|
|
|
|
this.chunkingConfig.enabled = false;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if (allowed && !this.securityFeatures[feature]) {
|
|
|
|
|
|
console.log(`🔓 Enabling ${feature} for ${this.currentSessionType} session`);
|
|
|
|
|
|
this.securityFeatures[feature] = true;
|
|
|
|
|
|
|
|
|
|
|
|
// Enable linked configurations
|
|
|
|
|
|
switch (feature) {
|
|
|
|
|
|
case 'hasFakeTraffic':
|
|
|
|
|
|
this.fakeTrafficConfig.enabled = true;
|
|
|
|
|
|
if (this.isConnected()) {
|
|
|
|
|
|
this.startFakeTrafficGeneration();
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 'hasDecoyChannels':
|
|
|
|
|
|
this.decoyChannelConfig.enabled = true;
|
|
|
|
|
|
if (this.isConnected()) {
|
|
|
|
|
|
this.initializeDecoyChannels();
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 'hasPacketReordering':
|
|
|
|
|
|
this.reorderingConfig.enabled = true;
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 'hasAntiFingerprinting':
|
|
|
|
|
|
this.antiFingerprintingConfig.enabled = true;
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 'hasMessageChunking':
|
|
|
|
|
|
this.chunkingConfig.enabled = true;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2025-08-21 04:07:16 -04:00
|
|
|
|
deliverMessageToUI(message, type = 'received') {
|
2025-08-20 23:04:29 -04:00
|
|
|
|
try {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Add debug logs
|
2025-08-21 04:07:16 -04:00
|
|
|
|
console.log('📤 deliverMessageToUI called:', {
|
|
|
|
|
|
message: message,
|
|
|
|
|
|
type: type,
|
|
|
|
|
|
messageType: typeof message,
|
|
|
|
|
|
hasOnMessage: !!this.onMessage
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Filter out file-transfer and system messages
|
2025-08-20 23:04:29 -04:00
|
|
|
|
if (typeof message === 'object' && message.type) {
|
|
|
|
|
|
const blockedTypes = [
|
|
|
|
|
|
EnhancedSecureWebRTCManager.MESSAGE_TYPES.FILE_TRANSFER_START,
|
|
|
|
|
|
EnhancedSecureWebRTCManager.MESSAGE_TYPES.FILE_TRANSFER_RESPONSE,
|
|
|
|
|
|
EnhancedSecureWebRTCManager.MESSAGE_TYPES.FILE_CHUNK,
|
|
|
|
|
|
EnhancedSecureWebRTCManager.MESSAGE_TYPES.CHUNK_CONFIRMATION,
|
|
|
|
|
|
EnhancedSecureWebRTCManager.MESSAGE_TYPES.FILE_TRANSFER_COMPLETE,
|
|
|
|
|
|
EnhancedSecureWebRTCManager.MESSAGE_TYPES.FILE_TRANSFER_ERROR,
|
|
|
|
|
|
EnhancedSecureWebRTCManager.MESSAGE_TYPES.HEARTBEAT,
|
|
|
|
|
|
EnhancedSecureWebRTCManager.MESSAGE_TYPES.VERIFICATION,
|
|
|
|
|
|
EnhancedSecureWebRTCManager.MESSAGE_TYPES.VERIFICATION_RESPONSE,
|
|
|
|
|
|
EnhancedSecureWebRTCManager.MESSAGE_TYPES.PEER_DISCONNECT,
|
|
|
|
|
|
EnhancedSecureWebRTCManager.MESSAGE_TYPES.KEY_ROTATION_SIGNAL,
|
|
|
|
|
|
EnhancedSecureWebRTCManager.MESSAGE_TYPES.KEY_ROTATION_READY,
|
|
|
|
|
|
EnhancedSecureWebRTCManager.MESSAGE_TYPES.SECURITY_UPGRADE
|
|
|
|
|
|
];
|
|
|
|
|
|
if (blockedTypes.includes(message.type)) {
|
|
|
|
|
|
if (window.DEBUG_MODE) {
|
|
|
|
|
|
console.log(`🛑 Blocked system/file message from UI: ${message.type}`);
|
|
|
|
|
|
}
|
2025-08-21 05:16:41 -04:00
|
|
|
|
return; // do not show in chat
|
2025-08-20 18:19:42 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Additional check for string messages containing JSON
|
2025-08-20 23:04:29 -04:00
|
|
|
|
if (typeof message === 'string' && message.trim().startsWith('{')) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const parsedMessage = JSON.parse(message);
|
|
|
|
|
|
if (parsedMessage.type) {
|
|
|
|
|
|
const blockedTypes = [
|
|
|
|
|
|
EnhancedSecureWebRTCManager.MESSAGE_TYPES.FILE_TRANSFER_START,
|
|
|
|
|
|
EnhancedSecureWebRTCManager.MESSAGE_TYPES.FILE_TRANSFER_RESPONSE,
|
|
|
|
|
|
EnhancedSecureWebRTCManager.MESSAGE_TYPES.FILE_CHUNK,
|
|
|
|
|
|
EnhancedSecureWebRTCManager.MESSAGE_TYPES.CHUNK_CONFIRMATION,
|
|
|
|
|
|
EnhancedSecureWebRTCManager.MESSAGE_TYPES.FILE_TRANSFER_COMPLETE,
|
|
|
|
|
|
EnhancedSecureWebRTCManager.MESSAGE_TYPES.FILE_TRANSFER_ERROR,
|
|
|
|
|
|
EnhancedSecureWebRTCManager.MESSAGE_TYPES.HEARTBEAT,
|
|
|
|
|
|
EnhancedSecureWebRTCManager.MESSAGE_TYPES.VERIFICATION,
|
|
|
|
|
|
EnhancedSecureWebRTCManager.MESSAGE_TYPES.VERIFICATION_RESPONSE,
|
|
|
|
|
|
EnhancedSecureWebRTCManager.MESSAGE_TYPES.PEER_DISCONNECT,
|
|
|
|
|
|
EnhancedSecureWebRTCManager.MESSAGE_TYPES.KEY_ROTATION_SIGNAL,
|
|
|
|
|
|
EnhancedSecureWebRTCManager.MESSAGE_TYPES.KEY_ROTATION_READY,
|
|
|
|
|
|
EnhancedSecureWebRTCManager.MESSAGE_TYPES.SECURITY_UPGRADE
|
|
|
|
|
|
];
|
|
|
|
|
|
if (blockedTypes.includes(parsedMessage.type)) {
|
|
|
|
|
|
if (window.DEBUG_MODE) {
|
|
|
|
|
|
console.log(`🛑 Blocked system/file message from UI (string): ${parsedMessage.type}`);
|
2025-08-20 18:19:42 -04:00
|
|
|
|
}
|
2025-08-21 05:16:41 -04:00
|
|
|
|
return; // do not show in chat
|
2025-08-20 18:19:42 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-20 23:04:29 -04:00
|
|
|
|
} catch (parseError) {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Not JSON — fine for plain text messages
|
2025-08-20 18:19:42 -04:00
|
|
|
|
}
|
2025-08-20 23:04:29 -04:00
|
|
|
|
}
|
2025-08-20 18:19:42 -04:00
|
|
|
|
|
2025-08-20 23:04:29 -04:00
|
|
|
|
if (this.onMessage) {
|
2025-08-21 04:07:16 -04:00
|
|
|
|
console.log('📤 Calling this.onMessage callback with:', { message, type });
|
2025-08-20 23:04:29 -04:00
|
|
|
|
this.onMessage(message, type);
|
2025-08-21 04:07:16 -04:00
|
|
|
|
} else {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('warn', '⚠️ this.onMessage callback is null or undefined');
|
2025-08-20 23:04:29 -04:00
|
|
|
|
}
|
|
|
|
|
|
} catch (err) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ Failed to deliver message to UI:', { errorType: err?.constructor?.name || 'Unknown' });
|
2025-08-20 18:19:42 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-17 02:22:55 -04:00
|
|
|
|
|
|
|
|
|
|
// Security Level Notification
|
|
|
|
|
|
notifySecurityLevel() {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Avoid duplicate notifications for the same security level
|
2025-08-20 18:19:42 -04:00
|
|
|
|
if (this.lastSecurityLevelNotification === this.currentSecurityLevel) {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
return; // prevent duplication
|
2025-08-20 18:19:42 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this.lastSecurityLevelNotification = this.currentSecurityLevel;
|
|
|
|
|
|
|
2025-08-17 02:22:55 -04:00
|
|
|
|
const levelMessages = {
|
|
|
|
|
|
'basic': '🔒 Basic Security Active - Demo session with essential protection',
|
|
|
|
|
|
'enhanced': '🔐 Enhanced Security Active - Paid session with advanced protection',
|
|
|
|
|
|
'maximum': '🛡️ Maximum Security Active - Premium session with complete protection'
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const message = levelMessages[this.currentSecurityLevel] || levelMessages['basic'];
|
|
|
|
|
|
|
|
|
|
|
|
if (this.onMessage) {
|
2025-08-20 18:19:42 -04:00
|
|
|
|
this.deliverMessageToUI(message, 'system');
|
2025-08-17 02:22:55 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Showing details of functions for paid sessions
|
|
|
|
|
|
if (this.currentSecurityLevel !== 'basic' && this.onMessage) {
|
|
|
|
|
|
const activeFeatures = Object.entries(this.securityFeatures)
|
|
|
|
|
|
.filter(([key, value]) => value === true)
|
|
|
|
|
|
.map(([key]) => key.replace('has', '').replace(/([A-Z])/g, ' $1').trim().toLowerCase())
|
|
|
|
|
|
.slice(0, 5);
|
|
|
|
|
|
|
2025-08-20 18:19:42 -04:00
|
|
|
|
this.deliverMessageToUI(`🔧 Active: ${activeFeatures.join(', ')}...`, 'system');
|
2025-08-17 02:22:55 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Cleaning decoy channels
|
|
|
|
|
|
cleanupDecoyChannels() {
|
|
|
|
|
|
// Stopping decoy traffic
|
|
|
|
|
|
for (const [channelName, timer] of this.decoyTimers.entries()) {
|
|
|
|
|
|
clearTimeout(timer);
|
|
|
|
|
|
}
|
|
|
|
|
|
this.decoyTimers.clear();
|
|
|
|
|
|
|
|
|
|
|
|
// Closing decoy channels
|
|
|
|
|
|
for (const [channelName, channel] of this.decoyChannels.entries()) {
|
|
|
|
|
|
if (channel.readyState === 'open') {
|
|
|
|
|
|
channel.close();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
this.decoyChannels.clear();
|
|
|
|
|
|
|
|
|
|
|
|
console.log('🧹 Decoy channels cleaned up');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-14 03:28:23 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
// 1. NESTED ENCRYPTION LAYER
|
|
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
|
|
async generateNestedEncryptionKey() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// Generate additional encryption key for nested encryption
|
|
|
|
|
|
this.nestedEncryptionKey = await crypto.subtle.generateKey(
|
|
|
|
|
|
{ name: 'AES-GCM', length: 256 },
|
|
|
|
|
|
false,
|
|
|
|
|
|
['encrypt', 'decrypt']
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// Generate random IV for nested encryption
|
2025-08-20 23:04:29 -04:00
|
|
|
|
this.nestedEncryptionIV = crypto.getRandomValues(new Uint8Array(EnhancedSecureWebRTCManager.SIZES.NESTED_ENCRYPTION_IV_SIZE));
|
2025-08-14 03:28:23 -04:00
|
|
|
|
this.nestedEncryptionCounter = 0;
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ Failed to generate nested encryption key:', { errorType: error?.constructor?.name || 'Unknown' });
|
2025-08-14 03:28:23 -04:00
|
|
|
|
throw error;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async applyNestedEncryption(data) {
|
|
|
|
|
|
if (!this.nestedEncryptionKey || !this.securityFeatures.hasNestedEncryption) {
|
|
|
|
|
|
return data;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// Create unique IV for each encryption
|
2025-08-20 23:04:29 -04:00
|
|
|
|
const uniqueIV = new Uint8Array(EnhancedSecureWebRTCManager.SIZES.NESTED_ENCRYPTION_IV_SIZE);
|
2025-08-14 03:28:23 -04:00
|
|
|
|
uniqueIV.set(this.nestedEncryptionIV);
|
|
|
|
|
|
uniqueIV[11] = (this.nestedEncryptionCounter++) & 0xFF;
|
|
|
|
|
|
|
|
|
|
|
|
// Encrypt data with nested layer
|
|
|
|
|
|
const encrypted = await crypto.subtle.encrypt(
|
|
|
|
|
|
{ name: 'AES-GCM', iv: uniqueIV },
|
|
|
|
|
|
this.nestedEncryptionKey,
|
|
|
|
|
|
data
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// Combine IV and encrypted data
|
2025-08-20 23:04:29 -04:00
|
|
|
|
const result = new Uint8Array(EnhancedSecureWebRTCManager.SIZES.NESTED_ENCRYPTION_IV_SIZE + encrypted.byteLength);
|
2025-08-14 03:28:23 -04:00
|
|
|
|
result.set(uniqueIV, 0);
|
2025-08-20 23:04:29 -04:00
|
|
|
|
result.set(new Uint8Array(encrypted), EnhancedSecureWebRTCManager.SIZES.NESTED_ENCRYPTION_IV_SIZE);
|
2025-08-14 03:28:23 -04:00
|
|
|
|
|
|
|
|
|
|
return result.buffer;
|
|
|
|
|
|
} catch (error) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ Nested encryption failed:', { errorType: error?.constructor?.name || 'Unknown' });
|
2025-08-14 03:28:23 -04:00
|
|
|
|
return data; // Fallback to original data
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async removeNestedEncryption(data) {
|
|
|
|
|
|
if (!this.nestedEncryptionKey || !this.securityFeatures.hasNestedEncryption) {
|
|
|
|
|
|
return data;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-20 23:04:29 -04:00
|
|
|
|
// FIX: Check that the data is actually encrypted
|
2025-08-14 23:34:54 -04:00
|
|
|
|
if (!(data instanceof ArrayBuffer) || data.byteLength < 20) {
|
|
|
|
|
|
if (window.DEBUG_MODE) {
|
|
|
|
|
|
console.log('📝 Data not encrypted or too short for nested decryption');
|
|
|
|
|
|
}
|
|
|
|
|
|
return data;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-14 03:28:23 -04:00
|
|
|
|
try {
|
|
|
|
|
|
const dataArray = new Uint8Array(data);
|
2025-08-20 23:04:29 -04:00
|
|
|
|
const iv = dataArray.slice(0, EnhancedSecureWebRTCManager.SIZES.NESTED_ENCRYPTION_IV_SIZE);
|
|
|
|
|
|
const encryptedData = dataArray.slice(EnhancedSecureWebRTCManager.SIZES.NESTED_ENCRYPTION_IV_SIZE);
|
2025-08-14 03:28:23 -04:00
|
|
|
|
|
2025-08-14 23:34:54 -04:00
|
|
|
|
// Check that there is data to decrypt
|
|
|
|
|
|
if (encryptedData.length === 0) {
|
|
|
|
|
|
if (window.DEBUG_MODE) {
|
|
|
|
|
|
console.log('📝 No encrypted data found');
|
|
|
|
|
|
}
|
|
|
|
|
|
return data;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-14 03:28:23 -04:00
|
|
|
|
// Decrypt nested layer
|
|
|
|
|
|
const decrypted = await crypto.subtle.decrypt(
|
|
|
|
|
|
{ name: 'AES-GCM', iv: iv },
|
|
|
|
|
|
this.nestedEncryptionKey,
|
|
|
|
|
|
encryptedData
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
return decrypted;
|
|
|
|
|
|
} catch (error) {
|
2025-08-14 23:34:54 -04:00
|
|
|
|
// FIX: Better error handling
|
|
|
|
|
|
if (error.name === 'OperationError') {
|
|
|
|
|
|
if (window.DEBUG_MODE) {
|
|
|
|
|
|
console.log('📝 Data not encrypted with nested encryption, skipping...');
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
if (window.DEBUG_MODE) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('warn', '⚠️ Nested decryption failed:', { details: error.message });
|
2025-08-14 23:34:54 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-14 03:28:23 -04:00
|
|
|
|
return data; // Fallback to original data
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================
|
|
|
|
|
|
// 2. PACKET PADDING
|
|
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
|
|
applyPacketPadding(data) {
|
|
|
|
|
|
if (!this.securityFeatures.hasPacketPadding) {
|
|
|
|
|
|
return data;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const originalSize = data.byteLength;
|
|
|
|
|
|
let paddingSize;
|
|
|
|
|
|
|
|
|
|
|
|
if (this.paddingConfig.useRandomPadding) {
|
|
|
|
|
|
// Generate random padding size
|
|
|
|
|
|
paddingSize = Math.floor(Math.random() *
|
|
|
|
|
|
(this.paddingConfig.maxPadding - this.paddingConfig.minPadding + 1)) +
|
|
|
|
|
|
this.paddingConfig.minPadding;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// Use fixed padding size
|
|
|
|
|
|
paddingSize = this.paddingConfig.minPadding;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Generate random padding data
|
|
|
|
|
|
const padding = crypto.getRandomValues(new Uint8Array(paddingSize));
|
|
|
|
|
|
|
|
|
|
|
|
// Create padded message
|
|
|
|
|
|
const paddedData = new Uint8Array(originalSize + paddingSize + 4);
|
|
|
|
|
|
|
|
|
|
|
|
// Add original size (4 bytes)
|
|
|
|
|
|
const sizeView = new DataView(paddedData.buffer, 0, 4);
|
|
|
|
|
|
sizeView.setUint32(0, originalSize, false);
|
|
|
|
|
|
|
|
|
|
|
|
// Add original data
|
|
|
|
|
|
paddedData.set(new Uint8Array(data), 4);
|
|
|
|
|
|
|
|
|
|
|
|
// Add padding
|
|
|
|
|
|
paddedData.set(padding, 4 + originalSize);
|
|
|
|
|
|
|
|
|
|
|
|
return paddedData.buffer;
|
|
|
|
|
|
} catch (error) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ Packet padding failed:', { errorType: error?.constructor?.name || 'Unknown' });
|
2025-08-14 03:28:23 -04:00
|
|
|
|
return data; // Fallback to original data
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
removePacketPadding(data) {
|
|
|
|
|
|
if (!this.securityFeatures.hasPacketPadding) {
|
|
|
|
|
|
return data;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const dataArray = new Uint8Array(data);
|
|
|
|
|
|
|
2025-08-14 23:34:54 -04:00
|
|
|
|
// Check for minimum data length (4 bytes for size + minimum 1 byte of data)
|
|
|
|
|
|
if (dataArray.length < 5) {
|
|
|
|
|
|
if (window.DEBUG_MODE) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('warn', '⚠️ Data too short for packet padding removal, skipping');
|
2025-08-14 23:34:54 -04:00
|
|
|
|
}
|
|
|
|
|
|
return data;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-14 03:28:23 -04:00
|
|
|
|
// Extract original size (first 4 bytes)
|
|
|
|
|
|
const sizeView = new DataView(dataArray.buffer, 0, 4);
|
|
|
|
|
|
const originalSize = sizeView.getUint32(0, false);
|
|
|
|
|
|
|
2025-08-14 23:34:54 -04:00
|
|
|
|
// Checking the reasonableness of the size
|
|
|
|
|
|
if (originalSize <= 0 || originalSize > dataArray.length - 4) {
|
|
|
|
|
|
if (window.DEBUG_MODE) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('warn', '⚠️ Invalid packet padding size, skipping removal');
|
2025-08-14 23:34:54 -04:00
|
|
|
|
}
|
|
|
|
|
|
return data;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-14 03:28:23 -04:00
|
|
|
|
// Extract original data
|
|
|
|
|
|
const originalData = dataArray.slice(4, 4 + originalSize);
|
|
|
|
|
|
|
|
|
|
|
|
return originalData.buffer;
|
|
|
|
|
|
} catch (error) {
|
2025-08-14 23:34:54 -04:00
|
|
|
|
if (window.DEBUG_MODE) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ Packet padding removal failed:', { errorType: error?.constructor?.name || 'Unknown' });
|
2025-08-14 23:34:54 -04:00
|
|
|
|
}
|
2025-08-14 03:28:23 -04:00
|
|
|
|
return data; // Fallback to original data
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================
|
|
|
|
|
|
// 3. FAKE TRAFFIC GENERATION
|
|
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
|
|
startFakeTrafficGeneration() {
|
|
|
|
|
|
if (!this.fakeTrafficConfig.enabled || !this.isConnected()) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Prevent multiple fake traffic generators
|
|
|
|
|
|
if (this.fakeTrafficTimer) {
|
|
|
|
|
|
console.log('⚠️ Fake traffic generation already running');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const sendFakeMessage = async () => {
|
|
|
|
|
|
if (!this.isConnected()) {
|
|
|
|
|
|
this.stopFakeTrafficGeneration();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const fakeMessage = this.generateFakeMessage();
|
|
|
|
|
|
await this.sendFakeMessage(fakeMessage);
|
|
|
|
|
|
|
2025-08-14 23:34:54 -04:00
|
|
|
|
// FIX: Increase intervals to reduce load
|
2025-08-14 03:28:23 -04:00
|
|
|
|
const nextInterval = this.fakeTrafficConfig.randomDecoyIntervals ?
|
|
|
|
|
|
Math.random() * (this.fakeTrafficConfig.maxInterval - this.fakeTrafficConfig.minInterval) +
|
|
|
|
|
|
this.fakeTrafficConfig.minInterval :
|
|
|
|
|
|
this.fakeTrafficConfig.minInterval;
|
|
|
|
|
|
|
2025-08-14 23:34:54 -04:00
|
|
|
|
// Minimum interval 15 seconds for stability
|
2025-08-20 23:04:29 -04:00
|
|
|
|
const safeInterval = Math.max(nextInterval, EnhancedSecureWebRTCManager.TIMEOUTS.FAKE_TRAFFIC_MIN_INTERVAL);
|
2025-08-14 23:34:54 -04:00
|
|
|
|
|
|
|
|
|
|
this.fakeTrafficTimer = setTimeout(sendFakeMessage, safeInterval);
|
2025-08-14 03:28:23 -04:00
|
|
|
|
} catch (error) {
|
2025-08-14 23:34:54 -04:00
|
|
|
|
if (window.DEBUG_MODE) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ Fake traffic generation failed:', { errorType: error?.constructor?.name || 'Unknown' });
|
2025-08-14 23:34:54 -04:00
|
|
|
|
}
|
2025-08-14 03:28:23 -04:00
|
|
|
|
this.stopFakeTrafficGeneration();
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Start fake traffic generation with longer initial delay
|
2025-08-20 23:04:29 -04:00
|
|
|
|
const initialDelay = Math.random() * this.fakeTrafficConfig.maxInterval + EnhancedSecureWebRTCManager.TIMEOUTS.DECOY_INITIAL_DELAY; // Add 5 seconds minimum
|
2025-08-14 03:28:23 -04:00
|
|
|
|
this.fakeTrafficTimer = setTimeout(sendFakeMessage, initialDelay);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
stopFakeTrafficGeneration() {
|
|
|
|
|
|
if (this.fakeTrafficTimer) {
|
|
|
|
|
|
clearTimeout(this.fakeTrafficTimer);
|
|
|
|
|
|
this.fakeTrafficTimer = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
generateFakeMessage() {
|
2025-08-20 23:04:29 -04:00
|
|
|
|
const pattern = this.fakeTrafficConfig.patterns[
|
|
|
|
|
|
Math.floor(Math.random() * this.fakeTrafficConfig.patterns.length)
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
const size = Math.floor(Math.random() *
|
|
|
|
|
|
(this.fakeTrafficConfig.maxSize - this.fakeTrafficConfig.minSize + 1)) +
|
|
|
|
|
|
this.fakeTrafficConfig.minSize;
|
|
|
|
|
|
|
|
|
|
|
|
const fakeData = crypto.getRandomValues(new Uint8Array(size));
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
type: EnhancedSecureWebRTCManager.MESSAGE_TYPES.FAKE,
|
|
|
|
|
|
pattern: pattern,
|
|
|
|
|
|
data: Array.from(fakeData).map(b => b.toString(16).padStart(2, '0')).join(''),
|
|
|
|
|
|
timestamp: Date.now(),
|
|
|
|
|
|
size: size,
|
|
|
|
|
|
isFakeTraffic: true,
|
|
|
|
|
|
source: 'fake_traffic_generator',
|
|
|
|
|
|
fakeId: crypto.getRandomValues(new Uint32Array(1))[0].toString(36)
|
|
|
|
|
|
};
|
2025-08-14 03:28:23 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-20 23:04:29 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
// EMERGENCY SHUT-OFF OF ADVANCED FUNCTIONS
|
|
|
|
|
|
// ============================================
|
2025-08-14 23:34:54 -04:00
|
|
|
|
|
2025-08-20 23:04:29 -04:00
|
|
|
|
emergencyDisableAdvancedFeatures() {
|
|
|
|
|
|
console.log('🚨 Emergency disabling advanced security features due to errors');
|
2025-08-14 03:28:23 -04:00
|
|
|
|
|
2025-08-20 23:04:29 -04:00
|
|
|
|
// Disable problematic functions
|
|
|
|
|
|
this.securityFeatures.hasNestedEncryption = false;
|
|
|
|
|
|
this.securityFeatures.hasPacketReordering = false;
|
|
|
|
|
|
this.securityFeatures.hasAntiFingerprinting = false;
|
2025-08-14 03:28:23 -04:00
|
|
|
|
|
2025-08-20 23:04:29 -04:00
|
|
|
|
// Disable configurations
|
|
|
|
|
|
this.reorderingConfig.enabled = false;
|
|
|
|
|
|
this.antiFingerprintingConfig.enabled = false;
|
2025-08-14 03:28:23 -04:00
|
|
|
|
|
2025-08-20 23:04:29 -04:00
|
|
|
|
// Clear the buffers
|
|
|
|
|
|
this.packetBuffer.clear();
|
2025-08-14 03:28:23 -04:00
|
|
|
|
|
2025-08-20 23:04:29 -04:00
|
|
|
|
// Stopping fake traffic
|
|
|
|
|
|
this.emergencyDisableFakeTraffic();
|
2025-08-14 03:28:23 -04:00
|
|
|
|
|
2025-08-20 23:04:29 -04:00
|
|
|
|
console.log('✅ Advanced features disabled, keeping basic encryption');
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Check that advanced-features-disabled notification wasn't already sent
|
2025-08-20 23:04:29 -04:00
|
|
|
|
if (!this.advancedFeaturesDisabledNotificationSent) {
|
|
|
|
|
|
this.advancedFeaturesDisabledNotificationSent = true;
|
|
|
|
|
|
if (this.onMessage) {
|
|
|
|
|
|
this.deliverMessageToUI('🚨 Advanced security features temporarily disabled due to compatibility issues', 'system');
|
|
|
|
|
|
}
|
2025-08-14 23:34:54 -04:00
|
|
|
|
}
|
2025-08-20 23:04:29 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async sendFakeMessage(fakeMessage) {
|
|
|
|
|
|
if (!this._validateConnection(false)) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
2025-08-20 23:34:56 -04:00
|
|
|
|
this._secureLog('debug', '🎭 Sending fake message', {
|
2025-08-21 04:07:16 -04:00
|
|
|
|
hasPattern: !!fakeMessage.pattern,
|
|
|
|
|
|
sizeRange: fakeMessage.size > 100 ? 'large' : 'small'
|
2025-08-20 23:34:56 -04:00
|
|
|
|
});
|
2025-08-20 23:04:29 -04:00
|
|
|
|
|
|
|
|
|
|
const fakeData = JSON.stringify({
|
|
|
|
|
|
...fakeMessage,
|
|
|
|
|
|
type: EnhancedSecureWebRTCManager.MESSAGE_TYPES.FAKE,
|
|
|
|
|
|
isFakeTraffic: true,
|
|
|
|
|
|
timestamp: Date.now()
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const fakeBuffer = new TextEncoder().encode(fakeData);
|
|
|
|
|
|
const encryptedFake = await this.applySecurityLayers(fakeBuffer, true);
|
|
|
|
|
|
this.dataChannel.send(encryptedFake);
|
|
|
|
|
|
|
2025-08-20 23:34:56 -04:00
|
|
|
|
this._secureLog('debug', '🎭 Fake message sent successfully', {
|
|
|
|
|
|
pattern: fakeMessage.pattern
|
|
|
|
|
|
});
|
2025-08-20 23:04:29 -04:00
|
|
|
|
} catch (error) {
|
2025-08-20 23:34:56 -04:00
|
|
|
|
this._secureLog('error', '❌ Failed to send fake message', {
|
|
|
|
|
|
error: error.message
|
|
|
|
|
|
});
|
2025-08-14 23:34:54 -04:00
|
|
|
|
}
|
2025-08-14 03:28:23 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
checkFakeTrafficStatus() {
|
2025-08-20 23:04:29 -04:00
|
|
|
|
const status = {
|
|
|
|
|
|
fakeTrafficEnabled: this.securityFeatures.hasFakeTraffic,
|
|
|
|
|
|
fakeTrafficConfigEnabled: this.fakeTrafficConfig.enabled,
|
|
|
|
|
|
timerActive: !!this.fakeTrafficTimer,
|
|
|
|
|
|
patterns: this.fakeTrafficConfig.patterns,
|
|
|
|
|
|
intervals: {
|
|
|
|
|
|
min: this.fakeTrafficConfig.minInterval,
|
|
|
|
|
|
max: this.fakeTrafficConfig.maxInterval
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
if (window.DEBUG_MODE) {
|
|
|
|
|
|
console.log('🎭 Fake Traffic Status:', status);
|
2025-08-14 03:28:23 -04:00
|
|
|
|
}
|
2025-08-20 23:04:29 -04:00
|
|
|
|
return status;
|
2025-08-14 23:34:54 -04:00
|
|
|
|
}
|
2025-08-14 03:28:23 -04:00
|
|
|
|
emergencyDisableFakeTraffic() {
|
2025-08-20 23:04:29 -04:00
|
|
|
|
if (window.DEBUG_MODE) {
|
|
|
|
|
|
console.log('🚨 Emergency disabling fake traffic');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this.securityFeatures.hasFakeTraffic = false;
|
|
|
|
|
|
this.fakeTrafficConfig.enabled = false;
|
|
|
|
|
|
this.stopFakeTrafficGeneration();
|
|
|
|
|
|
|
|
|
|
|
|
if (window.DEBUG_MODE) {
|
|
|
|
|
|
console.log('✅ Fake traffic disabled');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Check that fake-traffic-disabled notification wasn't already sent
|
2025-08-20 23:04:29 -04:00
|
|
|
|
if (!this.fakeTrafficDisabledNotificationSent) {
|
|
|
|
|
|
this.fakeTrafficDisabledNotificationSent = true;
|
|
|
|
|
|
if (this.onMessage) {
|
|
|
|
|
|
this.deliverMessageToUI('🚨 Fake traffic emergency disabled', 'system');
|
|
|
|
|
|
}
|
2025-08-20 18:19:42 -04:00
|
|
|
|
}
|
2025-08-14 03:28:23 -04:00
|
|
|
|
}
|
2025-08-21 04:07:16 -04:00
|
|
|
|
async _applySecurityLayersWithoutMutex(data, isFakeMessage = false) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
let processedData = data;
|
|
|
|
|
|
|
|
|
|
|
|
if (isFakeMessage) {
|
|
|
|
|
|
if (this.encryptionKey && typeof processedData === 'string') {
|
|
|
|
|
|
processedData = await window.EnhancedSecureCryptoUtils.encryptData(processedData, this.encryptionKey);
|
2025-08-14 03:28:23 -04:00
|
|
|
|
}
|
2025-08-21 04:07:16 -04:00
|
|
|
|
return processedData;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Nested Encryption (if enabled)
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (this.securityFeatures.hasNestedEncryption && this.nestedEncryptionKey && processedData instanceof ArrayBuffer) {
|
|
|
|
|
|
processedData = await this.applyNestedEncryption(processedData);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Packet Reordering (if enabled)
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (this.securityFeatures.hasPacketReordering && this.reorderingConfig?.enabled && processedData instanceof ArrayBuffer) {
|
|
|
|
|
|
processedData = this.applyPacketReordering(processedData);
|
2025-08-14 03:28:23 -04:00
|
|
|
|
}
|
2025-08-21 04:07:16 -04:00
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Packet Padding (if enabled)
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (this.securityFeatures.hasPacketPadding && processedData instanceof ArrayBuffer) {
|
|
|
|
|
|
processedData = this.applyPacketPadding(processedData);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Anti-Fingerprinting (if enabled)
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (this.securityFeatures.hasAntiFingerprinting && processedData instanceof ArrayBuffer) {
|
|
|
|
|
|
processedData = this.applyAntiFingerprinting(processedData);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Final encryption (if keys are present)
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (this.encryptionKey && typeof processedData === 'string') {
|
|
|
|
|
|
processedData = await window.EnhancedSecureCryptoUtils.encryptData(processedData, this.encryptionKey);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return processedData;
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ Error in applySecurityLayersWithoutMutex:', { errorType: error?.constructor?.name || 'Unknown' });
|
2025-08-21 05:16:41 -04:00
|
|
|
|
return data; // Return original data on error
|
2025-08-14 03:28:23 -04:00
|
|
|
|
}
|
2025-08-21 04:07:16 -04:00
|
|
|
|
}
|
|
|
|
|
|
// ============================================
|
|
|
|
|
|
// 4. MESSAGE CHUNKING
|
|
|
|
|
|
// ============================================
|
2025-08-14 03:28:23 -04:00
|
|
|
|
|
|
|
|
|
|
async processChunkedMessage(chunkData) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (!this.chunkingConfig.addChunkHeaders) {
|
|
|
|
|
|
// No headers, treat as regular message
|
|
|
|
|
|
return this.processMessage(chunkData);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const chunkArray = new Uint8Array(chunkData);
|
|
|
|
|
|
if (chunkArray.length < 16) {
|
|
|
|
|
|
// Too small to be a chunk with header
|
|
|
|
|
|
return this.processMessage(chunkData);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Extract chunk header
|
|
|
|
|
|
const headerView = new DataView(chunkArray.buffer, 0, 16);
|
|
|
|
|
|
const messageId = headerView.getUint32(0, false);
|
|
|
|
|
|
const chunkIndex = headerView.getUint32(4, false);
|
|
|
|
|
|
const totalChunks = headerView.getUint32(8, false);
|
|
|
|
|
|
const chunkSize = headerView.getUint32(12, false);
|
|
|
|
|
|
|
|
|
|
|
|
// Extract chunk data
|
|
|
|
|
|
const chunk = chunkArray.slice(16, 16 + chunkSize);
|
|
|
|
|
|
|
|
|
|
|
|
// Store chunk in buffer
|
|
|
|
|
|
if (!this.chunkQueue[messageId]) {
|
|
|
|
|
|
this.chunkQueue[messageId] = {
|
|
|
|
|
|
chunks: new Array(totalChunks),
|
|
|
|
|
|
received: 0,
|
|
|
|
|
|
timestamp: Date.now()
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const messageBuffer = this.chunkQueue[messageId];
|
|
|
|
|
|
messageBuffer.chunks[chunkIndex] = chunk;
|
|
|
|
|
|
messageBuffer.received++;
|
|
|
|
|
|
|
|
|
|
|
|
console.log(`📦 Received chunk ${chunkIndex + 1}/${totalChunks} for message ${messageId}`);
|
|
|
|
|
|
|
|
|
|
|
|
// Check if all chunks received
|
|
|
|
|
|
if (messageBuffer.received === totalChunks) {
|
|
|
|
|
|
// Combine all chunks
|
|
|
|
|
|
const totalSize = messageBuffer.chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
|
|
|
|
const combinedData = new Uint8Array(totalSize);
|
|
|
|
|
|
|
|
|
|
|
|
let offset = 0;
|
|
|
|
|
|
for (const chunk of messageBuffer.chunks) {
|
|
|
|
|
|
combinedData.set(chunk, offset);
|
|
|
|
|
|
offset += chunk.length;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Process complete message
|
|
|
|
|
|
await this.processMessage(combinedData.buffer);
|
|
|
|
|
|
|
|
|
|
|
|
// Clean up
|
|
|
|
|
|
delete this.chunkQueue[messageId];
|
|
|
|
|
|
|
|
|
|
|
|
console.log(`📦 Chunked message ${messageId} reassembled and processed`);
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ Chunked message processing failed:', { errorType: error?.constructor?.name || 'Unknown' });
|
2025-08-14 03:28:23 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================
|
|
|
|
|
|
// 5. DECOY CHANNELS
|
|
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
|
|
initializeDecoyChannels() {
|
|
|
|
|
|
if (!this.decoyChannelConfig.enabled || !this.peerConnection) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Prevent multiple initializations
|
|
|
|
|
|
if (this.decoyChannels.size > 0) {
|
|
|
|
|
|
console.log('⚠️ Decoy channels already initialized, skipping...');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const numDecoyChannels = Math.min(
|
|
|
|
|
|
this.decoyChannelConfig.maxDecoyChannels,
|
|
|
|
|
|
this.decoyChannelConfig.decoyChannelNames.length
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < numDecoyChannels; i++) {
|
|
|
|
|
|
const channelName = this.decoyChannelConfig.decoyChannelNames[i];
|
|
|
|
|
|
const decoyChannel = this.peerConnection.createDataChannel(channelName, {
|
|
|
|
|
|
ordered: Math.random() > 0.5,
|
|
|
|
|
|
maxRetransmits: Math.floor(Math.random() * 3)
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
this.setupDecoyChannel(decoyChannel, channelName);
|
|
|
|
|
|
this.decoyChannels.set(channelName, decoyChannel);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-14 23:34:54 -04:00
|
|
|
|
if (window.DEBUG_MODE) {
|
|
|
|
|
|
console.log(`🎭 Initialized ${numDecoyChannels} decoy channels`);
|
|
|
|
|
|
}
|
2025-08-14 03:28:23 -04:00
|
|
|
|
} catch (error) {
|
2025-08-14 23:34:54 -04:00
|
|
|
|
if (window.DEBUG_MODE) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ Failed to initialize decoy channels:', { errorType: error?.constructor?.name || 'Unknown' });
|
2025-08-14 23:34:54 -04:00
|
|
|
|
}
|
2025-08-14 03:28:23 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
setupDecoyChannel(channel, channelName) {
|
|
|
|
|
|
channel.onopen = () => {
|
2025-08-14 23:34:54 -04:00
|
|
|
|
if (window.DEBUG_MODE) {
|
|
|
|
|
|
console.log(`🎭 Decoy channel "${channelName}" opened`);
|
|
|
|
|
|
}
|
2025-08-14 03:28:23 -04:00
|
|
|
|
this.startDecoyTraffic(channel, channelName);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
channel.onmessage = (event) => {
|
2025-08-14 23:34:54 -04:00
|
|
|
|
if (window.DEBUG_MODE) {
|
|
|
|
|
|
console.log(`🎭 Received decoy message on "${channelName}": ${event.data?.length || 'undefined'} bytes`);
|
|
|
|
|
|
}
|
2025-08-14 03:28:23 -04:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
channel.onclose = () => {
|
2025-08-14 23:34:54 -04:00
|
|
|
|
if (window.DEBUG_MODE) {
|
|
|
|
|
|
console.log(`🎭 Decoy channel "${channelName}" closed`);
|
|
|
|
|
|
}
|
2025-08-14 03:28:23 -04:00
|
|
|
|
this.stopDecoyTraffic(channelName);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
channel.onerror = (error) => {
|
2025-08-14 23:34:54 -04:00
|
|
|
|
if (window.DEBUG_MODE) {
|
|
|
|
|
|
console.error(`❌ Decoy channel "${channelName}" error:`, error);
|
|
|
|
|
|
}
|
2025-08-14 03:28:23 -04:00
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
startDecoyTraffic(channel, channelName) {
|
|
|
|
|
|
const sendDecoyData = async () => {
|
|
|
|
|
|
if (channel.readyState !== 'open') {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const decoyData = this.generateDecoyData(channelName);
|
|
|
|
|
|
channel.send(decoyData);
|
|
|
|
|
|
|
|
|
|
|
|
const interval = this.decoyChannelConfig.randomDecoyIntervals ?
|
2025-08-14 23:34:54 -04:00
|
|
|
|
Math.random() * 15000 + 10000 :
|
|
|
|
|
|
20000;
|
2025-08-14 03:28:23 -04:00
|
|
|
|
|
|
|
|
|
|
this.decoyTimers.set(channelName, setTimeout(() => sendDecoyData(), interval));
|
|
|
|
|
|
} catch (error) {
|
2025-08-14 23:34:54 -04:00
|
|
|
|
if (window.DEBUG_MODE) {
|
|
|
|
|
|
console.error(`❌ Failed to send decoy data on "${channelName}":`, error);
|
|
|
|
|
|
}
|
2025-08-14 03:28:23 -04:00
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-08-14 23:34:54 -04:00
|
|
|
|
const initialDelay = Math.random() * 10000 + 5000;
|
2025-08-14 03:28:23 -04:00
|
|
|
|
this.decoyTimers.set(channelName, setTimeout(() => sendDecoyData(), initialDelay));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
stopDecoyTraffic(channelName) {
|
|
|
|
|
|
const timer = this.decoyTimers.get(channelName);
|
|
|
|
|
|
if (timer) {
|
|
|
|
|
|
clearTimeout(timer);
|
|
|
|
|
|
this.decoyTimers.delete(channelName);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
generateDecoyData(channelName) {
|
|
|
|
|
|
const decoyTypes = {
|
|
|
|
|
|
'sync': () => JSON.stringify({
|
|
|
|
|
|
type: 'sync',
|
|
|
|
|
|
timestamp: Date.now(),
|
|
|
|
|
|
sequence: Math.floor(Math.random() * 1000),
|
|
|
|
|
|
data: Array.from(crypto.getRandomValues(new Uint8Array(32)))
|
|
|
|
|
|
.map(b => b.toString(16).padStart(2, '0')).join('')
|
|
|
|
|
|
}),
|
|
|
|
|
|
'status': () => JSON.stringify({
|
|
|
|
|
|
type: 'status',
|
|
|
|
|
|
status: ['online', 'away', 'busy'][Math.floor(Math.random() * 3)],
|
|
|
|
|
|
uptime: Math.floor(Math.random() * 3600),
|
|
|
|
|
|
data: Array.from(crypto.getRandomValues(new Uint8Array(16)))
|
|
|
|
|
|
.map(b => b.toString(16).padStart(2, '0')).join('')
|
|
|
|
|
|
}),
|
|
|
|
|
|
'heartbeat': () => JSON.stringify({
|
|
|
|
|
|
type: 'heartbeat',
|
|
|
|
|
|
timestamp: Date.now(),
|
|
|
|
|
|
data: Array.from(crypto.getRandomValues(new Uint8Array(24)))
|
|
|
|
|
|
.map(b => b.toString(16).padStart(2, '0')).join('')
|
|
|
|
|
|
}),
|
|
|
|
|
|
'metrics': () => JSON.stringify({
|
|
|
|
|
|
type: 'metrics',
|
|
|
|
|
|
cpu: Math.random() * 100,
|
|
|
|
|
|
memory: Math.random() * 100,
|
|
|
|
|
|
network: Math.random() * 1000,
|
|
|
|
|
|
data: Array.from(crypto.getRandomValues(new Uint8Array(20)))
|
|
|
|
|
|
.map(b => b.toString(16).padStart(2, '0')).join('')
|
|
|
|
|
|
}),
|
|
|
|
|
|
'debug': () => JSON.stringify({
|
|
|
|
|
|
type: 'debug',
|
|
|
|
|
|
level: ['info', 'warn', 'error'][Math.floor(Math.random() * 3)],
|
|
|
|
|
|
message: 'Debug message',
|
|
|
|
|
|
data: Array.from(crypto.getRandomValues(new Uint8Array(28)))
|
|
|
|
|
|
.map(b => b.toString(16).padStart(2, '0')).join('')
|
|
|
|
|
|
})
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return decoyTypes[channelName] ? decoyTypes[channelName]() :
|
|
|
|
|
|
Array.from(crypto.getRandomValues(new Uint8Array(64)))
|
|
|
|
|
|
.map(b => b.toString(16).padStart(2, '0')).join('');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================
|
|
|
|
|
|
// 6. PACKET REORDERING PROTECTION
|
|
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
|
|
addReorderingHeaders(data) {
|
|
|
|
|
|
if (!this.reorderingConfig.enabled) {
|
|
|
|
|
|
return data;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const dataArray = new Uint8Array(data);
|
|
|
|
|
|
const headerSize = this.reorderingConfig.useTimestamps ? 12 : 8;
|
|
|
|
|
|
const header = new ArrayBuffer(headerSize);
|
|
|
|
|
|
const headerView = new DataView(header);
|
|
|
|
|
|
|
|
|
|
|
|
// Add sequence number
|
|
|
|
|
|
if (this.reorderingConfig.useSequenceNumbers) {
|
|
|
|
|
|
headerView.setUint32(0, this.sequenceNumber++, false);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Add timestamp
|
|
|
|
|
|
if (this.reorderingConfig.useTimestamps) {
|
|
|
|
|
|
headerView.setUint32(4, Date.now(), false);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Add data size
|
|
|
|
|
|
headerView.setUint32(this.reorderingConfig.useTimestamps ? 8 : 4, dataArray.length, false);
|
|
|
|
|
|
|
|
|
|
|
|
// Combine header and data
|
|
|
|
|
|
const result = new Uint8Array(headerSize + dataArray.length);
|
|
|
|
|
|
result.set(new Uint8Array(header), 0);
|
|
|
|
|
|
result.set(dataArray, headerSize);
|
|
|
|
|
|
|
|
|
|
|
|
return result.buffer;
|
|
|
|
|
|
} catch (error) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ Failed to add reordering headers:', { errorType: error?.constructor?.name || 'Unknown' });
|
2025-08-14 03:28:23 -04:00
|
|
|
|
return data;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async processReorderedPacket(data) {
|
2025-08-14 23:34:54 -04:00
|
|
|
|
if (!this.reorderingConfig.enabled) {
|
|
|
|
|
|
return this.processMessage(data);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const dataArray = new Uint8Array(data);
|
|
|
|
|
|
const headerSize = this.reorderingConfig.useTimestamps ? 12 : 8;
|
|
|
|
|
|
|
|
|
|
|
|
if (dataArray.length < headerSize) {
|
|
|
|
|
|
if (window.DEBUG_MODE) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('warn', '⚠️ Data too short for reordering headers, processing directly');
|
2025-08-14 23:34:54 -04:00
|
|
|
|
}
|
2025-08-14 03:28:23 -04:00
|
|
|
|
return this.processMessage(data);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-14 23:34:54 -04:00
|
|
|
|
const headerView = new DataView(dataArray.buffer, 0, headerSize);
|
|
|
|
|
|
let sequence = 0;
|
|
|
|
|
|
let timestamp = 0;
|
|
|
|
|
|
let dataSize = 0;
|
2025-08-14 03:28:23 -04:00
|
|
|
|
|
2025-08-14 23:34:54 -04:00
|
|
|
|
if (this.reorderingConfig.useSequenceNumbers) {
|
|
|
|
|
|
sequence = headerView.getUint32(0, false);
|
|
|
|
|
|
}
|
2025-08-14 03:28:23 -04:00
|
|
|
|
|
2025-08-14 23:34:54 -04:00
|
|
|
|
if (this.reorderingConfig.useTimestamps) {
|
|
|
|
|
|
timestamp = headerView.getUint32(4, false);
|
|
|
|
|
|
}
|
2025-08-14 03:28:23 -04:00
|
|
|
|
|
2025-08-14 23:34:54 -04:00
|
|
|
|
dataSize = headerView.getUint32(this.reorderingConfig.useTimestamps ? 8 : 4, false);
|
2025-08-14 03:28:23 -04:00
|
|
|
|
|
2025-08-14 23:34:54 -04:00
|
|
|
|
if (dataSize > dataArray.length - headerSize || dataSize <= 0) {
|
|
|
|
|
|
if (window.DEBUG_MODE) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('warn', '⚠️ Invalid reordered packet data size, processing directly');
|
2025-08-14 03:28:23 -04:00
|
|
|
|
}
|
2025-08-14 23:34:54 -04:00
|
|
|
|
return this.processMessage(data);
|
|
|
|
|
|
}
|
2025-08-14 03:28:23 -04:00
|
|
|
|
|
2025-08-14 23:34:54 -04:00
|
|
|
|
const actualData = dataArray.slice(headerSize, headerSize + dataSize);
|
2025-08-14 03:28:23 -04:00
|
|
|
|
|
2025-08-14 23:34:54 -04:00
|
|
|
|
try {
|
|
|
|
|
|
const textData = new TextDecoder().decode(actualData);
|
|
|
|
|
|
const content = JSON.parse(textData);
|
|
|
|
|
|
if (content.type === 'fake' || content.isFakeTraffic === true) {
|
|
|
|
|
|
if (window.DEBUG_MODE) {
|
|
|
|
|
|
console.log(`🎭 BLOCKED: Reordered fake message: ${content.pattern || 'unknown'}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
2025-08-14 03:28:23 -04:00
|
|
|
|
|
2025-08-14 23:34:54 -04:00
|
|
|
|
}
|
2025-08-14 03:28:23 -04:00
|
|
|
|
|
2025-08-14 23:34:54 -04:00
|
|
|
|
this.packetBuffer.set(sequence, {
|
|
|
|
|
|
data: actualData.buffer,
|
|
|
|
|
|
timestamp: timestamp || Date.now()
|
|
|
|
|
|
});
|
2025-08-14 03:28:23 -04:00
|
|
|
|
|
2025-08-14 23:34:54 -04:00
|
|
|
|
await this.processOrderedPackets();
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ Failed to process reordered packet:', { errorType: error?.constructor?.name || 'Unknown' });
|
2025-08-14 23:34:54 -04:00
|
|
|
|
return this.processMessage(data);
|
2025-08-14 03:28:23 -04:00
|
|
|
|
}
|
2025-08-14 23:34:54 -04:00
|
|
|
|
}
|
2025-08-14 03:28:23 -04:00
|
|
|
|
|
2025-08-14 23:34:54 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
// IMPROVED PROCESSORDEREDPACKETS with filtering
|
|
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
|
|
async processOrderedPackets() {
|
|
|
|
|
|
const now = Date.now();
|
|
|
|
|
|
const timeout = this.reorderingConfig.reorderTimeout;
|
|
|
|
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
|
|
const nextSequence = this.lastProcessedSequence + 1;
|
|
|
|
|
|
const packet = this.packetBuffer.get(nextSequence);
|
|
|
|
|
|
|
|
|
|
|
|
if (!packet) {
|
|
|
|
|
|
const oldestPacket = this.findOldestPacket();
|
|
|
|
|
|
if (oldestPacket && (now - oldestPacket.timestamp) > timeout) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('warn', '⚠️ Packet ${oldestPacket.sequence} timed out, processing out of order');
|
2025-08-14 23:34:54 -04:00
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const textData = new TextDecoder().decode(oldestPacket.data);
|
|
|
|
|
|
const content = JSON.parse(textData);
|
|
|
|
|
|
if (content.type === 'fake' || content.isFakeTraffic === true) {
|
|
|
|
|
|
console.log(`🎭 BLOCKED: Timed out fake message: ${content.pattern || 'unknown'}`);
|
|
|
|
|
|
this.packetBuffer.delete(oldestPacket.sequence);
|
|
|
|
|
|
this.lastProcessedSequence = oldestPacket.sequence;
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
2025-08-14 03:28:23 -04:00
|
|
|
|
}
|
2025-08-14 23:34:54 -04:00
|
|
|
|
|
|
|
|
|
|
await this.processMessage(oldestPacket.data);
|
|
|
|
|
|
this.packetBuffer.delete(oldestPacket.sequence);
|
|
|
|
|
|
this.lastProcessedSequence = oldestPacket.sequence;
|
2025-08-14 03:28:23 -04:00
|
|
|
|
} else {
|
2025-08-14 23:34:54 -04:00
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const textData = new TextDecoder().decode(packet.data);
|
|
|
|
|
|
const content = JSON.parse(textData);
|
|
|
|
|
|
if (content.type === 'fake' || content.isFakeTraffic === true) {
|
|
|
|
|
|
console.log(`🎭 BLOCKED: Ordered fake message: ${content.pattern || 'unknown'}`);
|
|
|
|
|
|
this.packetBuffer.delete(nextSequence);
|
|
|
|
|
|
this.lastProcessedSequence = nextSequence;
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
2025-08-14 03:28:23 -04:00
|
|
|
|
}
|
2025-08-14 23:34:54 -04:00
|
|
|
|
|
|
|
|
|
|
await this.processMessage(packet.data);
|
|
|
|
|
|
this.packetBuffer.delete(nextSequence);
|
|
|
|
|
|
this.lastProcessedSequence = nextSequence;
|
2025-08-14 03:28:23 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-14 23:34:54 -04:00
|
|
|
|
this.cleanupOldPackets(now, timeout);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-08-14 03:28:23 -04:00
|
|
|
|
findOldestPacket() {
|
|
|
|
|
|
let oldest = null;
|
|
|
|
|
|
for (const [sequence, packet] of this.packetBuffer.entries()) {
|
|
|
|
|
|
if (!oldest || packet.timestamp < oldest.timestamp) {
|
|
|
|
|
|
oldest = { sequence, ...packet };
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return oldest;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
cleanupOldPackets(now, timeout) {
|
|
|
|
|
|
for (const [sequence, packet] of this.packetBuffer.entries()) {
|
|
|
|
|
|
if ((now - packet.timestamp) > timeout) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('warn', '⚠️ 🗑️ Removing timed out packet ${sequence}');
|
2025-08-14 03:28:23 -04:00
|
|
|
|
this.packetBuffer.delete(sequence);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================
|
|
|
|
|
|
// 7. ANTI-FINGERPRINTING
|
|
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
|
|
applyAntiFingerprinting(data) {
|
|
|
|
|
|
if (!this.antiFingerprintingConfig.enabled) {
|
|
|
|
|
|
return data;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
let processedData = data;
|
|
|
|
|
|
|
|
|
|
|
|
// Add random noise
|
|
|
|
|
|
if (this.antiFingerprintingConfig.addNoise) {
|
|
|
|
|
|
processedData = this.addNoise(processedData);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Randomize sizes
|
|
|
|
|
|
if (this.antiFingerprintingConfig.randomizeSizes) {
|
|
|
|
|
|
processedData = this.randomizeSize(processedData);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Mask patterns
|
|
|
|
|
|
if (this.antiFingerprintingConfig.maskPatterns) {
|
|
|
|
|
|
processedData = this.maskPatterns(processedData);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Add random headers
|
|
|
|
|
|
if (this.antiFingerprintingConfig.useRandomHeaders) {
|
|
|
|
|
|
processedData = this.addRandomHeaders(processedData);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return processedData;
|
|
|
|
|
|
} catch (error) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ Anti-fingerprinting failed:', { errorType: error?.constructor?.name || 'Unknown' });
|
2025-08-14 03:28:23 -04:00
|
|
|
|
return data;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
addNoise(data) {
|
|
|
|
|
|
const dataArray = new Uint8Array(data);
|
|
|
|
|
|
const noiseSize = Math.floor(Math.random() * 32) + 8; // 8-40 bytes
|
|
|
|
|
|
const noise = crypto.getRandomValues(new Uint8Array(noiseSize));
|
|
|
|
|
|
|
|
|
|
|
|
const result = new Uint8Array(dataArray.length + noiseSize);
|
|
|
|
|
|
result.set(dataArray, 0);
|
|
|
|
|
|
result.set(noise, dataArray.length);
|
|
|
|
|
|
|
|
|
|
|
|
return result.buffer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
randomizeSize(data) {
|
|
|
|
|
|
const dataArray = new Uint8Array(data);
|
|
|
|
|
|
const variation = this.fingerprintMask.sizeVariation;
|
|
|
|
|
|
const targetSize = Math.floor(dataArray.length * variation);
|
|
|
|
|
|
|
|
|
|
|
|
if (targetSize > dataArray.length) {
|
|
|
|
|
|
// Add padding to increase size
|
|
|
|
|
|
const padding = crypto.getRandomValues(new Uint8Array(targetSize - dataArray.length));
|
|
|
|
|
|
const result = new Uint8Array(targetSize);
|
|
|
|
|
|
result.set(dataArray, 0);
|
|
|
|
|
|
result.set(padding, dataArray.length);
|
|
|
|
|
|
return result.buffer;
|
|
|
|
|
|
} else if (targetSize < dataArray.length) {
|
|
|
|
|
|
// Truncate to decrease size
|
|
|
|
|
|
return dataArray.slice(0, targetSize).buffer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return data;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
maskPatterns(data) {
|
|
|
|
|
|
const dataArray = new Uint8Array(data);
|
|
|
|
|
|
const result = new Uint8Array(dataArray.length);
|
|
|
|
|
|
|
|
|
|
|
|
// Apply XOR with noise pattern
|
|
|
|
|
|
for (let i = 0; i < dataArray.length; i++) {
|
|
|
|
|
|
const noiseByte = this.fingerprintMask.noisePattern[i % this.fingerprintMask.noisePattern.length];
|
|
|
|
|
|
result[i] = dataArray[i] ^ noiseByte;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return result.buffer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
addRandomHeaders(data) {
|
|
|
|
|
|
const dataArray = new Uint8Array(data);
|
|
|
|
|
|
const headerCount = Math.floor(Math.random() * 3) + 1; // 1-3 headers
|
|
|
|
|
|
let totalHeaderSize = 0;
|
|
|
|
|
|
|
|
|
|
|
|
// Calculate total header size
|
|
|
|
|
|
for (let i = 0; i < headerCount; i++) {
|
|
|
|
|
|
totalHeaderSize += 4 + Math.floor(Math.random() * 16) + 4; // size + data + checksum
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const result = new Uint8Array(totalHeaderSize + dataArray.length);
|
|
|
|
|
|
let offset = 0;
|
|
|
|
|
|
|
|
|
|
|
|
// Add random headers
|
|
|
|
|
|
for (let i = 0; i < headerCount; i++) {
|
|
|
|
|
|
const headerName = this.fingerprintMask.headerVariations[
|
|
|
|
|
|
Math.floor(Math.random() * this.fingerprintMask.headerVariations.length)
|
|
|
|
|
|
];
|
|
|
|
|
|
const headerData = crypto.getRandomValues(new Uint8Array(Math.floor(Math.random() * 16) + 4));
|
|
|
|
|
|
|
|
|
|
|
|
// Header structure: [size:4][name:4][data:variable][checksum:4]
|
|
|
|
|
|
const headerView = new DataView(result.buffer, offset);
|
|
|
|
|
|
headerView.setUint32(0, headerData.length + 8, false); // Total header size
|
|
|
|
|
|
headerView.setUint32(4, this.hashString(headerName), false); // Name hash
|
|
|
|
|
|
|
|
|
|
|
|
result.set(headerData, offset + 8);
|
|
|
|
|
|
|
|
|
|
|
|
// Add checksum
|
|
|
|
|
|
const checksum = this.calculateChecksum(result.slice(offset, offset + 8 + headerData.length));
|
|
|
|
|
|
const checksumView = new DataView(result.buffer, offset + 8 + headerData.length);
|
|
|
|
|
|
checksumView.setUint32(0, checksum, false);
|
|
|
|
|
|
|
|
|
|
|
|
offset += 8 + headerData.length + 4;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Add original data
|
|
|
|
|
|
result.set(dataArray, offset);
|
|
|
|
|
|
|
|
|
|
|
|
return result.buffer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
hashString(str) {
|
|
|
|
|
|
let hash = 0;
|
|
|
|
|
|
for (let i = 0; i < str.length; i++) {
|
|
|
|
|
|
const char = str.charCodeAt(i);
|
|
|
|
|
|
hash = ((hash << 5) - hash) + char;
|
|
|
|
|
|
hash = hash & hash;
|
|
|
|
|
|
}
|
|
|
|
|
|
return Math.abs(hash);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
calculateChecksum(data) {
|
|
|
|
|
|
let checksum = 0;
|
|
|
|
|
|
for (let i = 0; i < data.length; i++) {
|
|
|
|
|
|
checksum = (checksum + data[i]) & 0xFFFFFFFF;
|
|
|
|
|
|
}
|
|
|
|
|
|
return checksum;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================
|
|
|
|
|
|
// ENHANCED MESSAGE SENDING AND RECEIVING
|
|
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
|
|
async removeSecurityLayers(data) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const status = this.getSecurityStatus();
|
2025-08-14 23:34:54 -04:00
|
|
|
|
if (window.DEBUG_MODE) {
|
|
|
|
|
|
console.log(`🔍 removeSecurityLayers (Stage ${status.stage}):`, {
|
|
|
|
|
|
dataType: typeof data,
|
|
|
|
|
|
dataLength: data?.length || data?.byteLength || 0,
|
|
|
|
|
|
activeFeatures: status.activeFeaturesCount
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2025-08-14 03:28:23 -04:00
|
|
|
|
|
|
|
|
|
|
if (!data) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('warn', '⚠️ Received empty data');
|
2025-08-14 03:28:23 -04:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let processedData = data;
|
|
|
|
|
|
|
2025-08-14 04:01:08 -04:00
|
|
|
|
// IMPORTANT: Early check for fake messages
|
2025-08-14 03:28:23 -04:00
|
|
|
|
if (typeof data === 'string') {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const jsonData = JSON.parse(data);
|
|
|
|
|
|
|
2025-08-14 04:01:08 -04:00
|
|
|
|
// PRIORITY ONE: Filtering out fake messages
|
2025-08-14 03:28:23 -04:00
|
|
|
|
if (jsonData.type === 'fake') {
|
2025-08-14 23:34:54 -04:00
|
|
|
|
if (window.DEBUG_MODE) {
|
|
|
|
|
|
console.log(`🎭 Fake message filtered out: ${jsonData.pattern} (size: ${jsonData.size})`);
|
|
|
|
|
|
}
|
2025-08-14 04:01:08 -04:00
|
|
|
|
return 'FAKE_MESSAGE_FILTERED';
|
2025-08-14 03:28:23 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// System messages — do NOT return for re-processing
|
2025-08-14 23:34:54 -04:00
|
|
|
|
if (jsonData.type && ['heartbeat', 'verification', 'verification_response', 'peer_disconnect', 'key_rotation_signal', 'key_rotation_ready', 'security_upgrade'].includes(jsonData.type)) {
|
|
|
|
|
|
if (window.DEBUG_MODE) {
|
2025-08-20 18:19:42 -04:00
|
|
|
|
console.log('🔧 System message detected, blocking from chat:', jsonData.type);
|
2025-08-14 23:34:54 -04:00
|
|
|
|
}
|
2025-08-20 18:19:42 -04:00
|
|
|
|
return 'SYSTEM_MESSAGE_FILTERED';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// File transfer messages — do NOT return for display
|
2025-08-20 18:19:42 -04:00
|
|
|
|
if (jsonData.type && ['file_transfer_start', 'file_transfer_response', 'file_chunk', 'chunk_confirmation', 'file_transfer_complete', 'file_transfer_error'].includes(jsonData.type)) {
|
|
|
|
|
|
if (window.DEBUG_MODE) {
|
|
|
|
|
|
console.log('📁 File transfer message detected, blocking from chat:', jsonData.type);
|
|
|
|
|
|
}
|
|
|
|
|
|
return 'FILE_MESSAGE_FILTERED';
|
2025-08-14 03:28:23 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-14 23:34:54 -04:00
|
|
|
|
// Regular text messages - extract the actual message text
|
|
|
|
|
|
if (jsonData.type === 'message') {
|
|
|
|
|
|
if (window.DEBUG_MODE) {
|
|
|
|
|
|
console.log('📝 Regular message detected, extracting text:', jsonData.data);
|
|
|
|
|
|
}
|
|
|
|
|
|
return jsonData.data; // Return the actual message text, not the JSON
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-14 04:01:08 -04:00
|
|
|
|
// Enhanced messages
|
2025-08-14 03:28:23 -04:00
|
|
|
|
if (jsonData.type === 'enhanced_message' && jsonData.data) {
|
2025-08-14 23:34:54 -04:00
|
|
|
|
if (window.DEBUG_MODE) {
|
|
|
|
|
|
console.log('🔐 Enhanced message detected, decrypting...');
|
|
|
|
|
|
}
|
2025-08-14 03:28:23 -04:00
|
|
|
|
|
|
|
|
|
|
if (!this.encryptionKey || !this.macKey || !this.metadataKey) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ Missing encryption keys');
|
2025-08-14 03:28:23 -04:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const decryptedResult = await window.EnhancedSecureCryptoUtils.decryptMessage(
|
|
|
|
|
|
jsonData.data,
|
|
|
|
|
|
this.encryptionKey,
|
|
|
|
|
|
this.macKey,
|
|
|
|
|
|
this.metadataKey
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2025-08-14 23:34:54 -04:00
|
|
|
|
if (window.DEBUG_MODE) {
|
|
|
|
|
|
console.log('✅ Enhanced message decrypted, extracting...');
|
|
|
|
|
|
console.log('🔍 decryptedResult:', {
|
|
|
|
|
|
type: typeof decryptedResult,
|
|
|
|
|
|
hasMessage: !!decryptedResult?.message,
|
|
|
|
|
|
messageType: typeof decryptedResult?.message,
|
|
|
|
|
|
messageLength: decryptedResult?.message?.length || 0,
|
|
|
|
|
|
messageSample: decryptedResult?.message?.substring(0, 50) || 'no message'
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2025-08-14 03:28:23 -04:00
|
|
|
|
|
2025-08-14 04:01:08 -04:00
|
|
|
|
// CHECKING FOR FAKE MESSAGES AFTER DECRYPTION
|
2025-08-14 03:28:23 -04:00
|
|
|
|
try {
|
|
|
|
|
|
const decryptedContent = JSON.parse(decryptedResult.message);
|
2025-08-14 23:34:54 -04:00
|
|
|
|
if (decryptedContent.type === 'fake' || decryptedContent.isFakeTraffic === true) {
|
|
|
|
|
|
if (window.DEBUG_MODE) {
|
|
|
|
|
|
console.log(`🎭 BLOCKED: Encrypted fake message: ${decryptedContent.pattern || 'unknown'}`);
|
|
|
|
|
|
}
|
2025-08-14 03:28:23 -04:00
|
|
|
|
return 'FAKE_MESSAGE_FILTERED';
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
2025-08-14 23:34:54 -04:00
|
|
|
|
if (window.DEBUG_MODE) {
|
|
|
|
|
|
console.log('📝 Decrypted content is not JSON, treating as plain text message');
|
|
|
|
|
|
}
|
2025-08-14 03:28:23 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-14 23:34:54 -04:00
|
|
|
|
if (window.DEBUG_MODE) {
|
|
|
|
|
|
console.log('📤 Returning decrypted message:', decryptedResult.message?.substring(0, 50));
|
|
|
|
|
|
}
|
2025-08-14 03:28:23 -04:00
|
|
|
|
return decryptedResult.message;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-14 23:34:54 -04:00
|
|
|
|
// Regular messages
|
2025-08-14 03:28:23 -04:00
|
|
|
|
if (jsonData.type === 'message' && jsonData.data) {
|
2025-08-14 23:34:54 -04:00
|
|
|
|
if (window.DEBUG_MODE) {
|
|
|
|
|
|
console.log('📝 Regular message detected, extracting data');
|
|
|
|
|
|
}
|
|
|
|
|
|
return jsonData.data; // Return the actual message text
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// If it's a regular message with type 'message', let it continue processing
|
|
|
|
|
|
if (jsonData.type === 'message') {
|
|
|
|
|
|
if (window.DEBUG_MODE) {
|
|
|
|
|
|
console.log('📝 Regular message detected, returning for display');
|
|
|
|
|
|
}
|
|
|
|
|
|
return data; // Return the original JSON string for processing
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// If it's not a special type, return the original data for display
|
2025-08-20 18:19:42 -04:00
|
|
|
|
if (!jsonData.type || (jsonData.type !== 'fake' && !['heartbeat', 'verification', 'verification_response', 'peer_disconnect', 'key_rotation_signal', 'key_rotation_ready', 'enhanced_message', 'security_upgrade', 'file_transfer_start', 'file_transfer_response', 'file_chunk', 'chunk_confirmation', 'file_transfer_complete', 'file_transfer_error'].includes(jsonData.type))) {
|
2025-08-14 23:34:54 -04:00
|
|
|
|
if (window.DEBUG_MODE) {
|
|
|
|
|
|
console.log('📝 Regular message detected, returning for display');
|
|
|
|
|
|
}
|
|
|
|
|
|
return data;
|
2025-08-14 03:28:23 -04:00
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
2025-08-14 23:34:54 -04:00
|
|
|
|
if (window.DEBUG_MODE) {
|
|
|
|
|
|
console.log('📄 Not JSON, processing as raw data');
|
|
|
|
|
|
}
|
|
|
|
|
|
// If it's not JSON, it might be a plain text message - return as-is
|
|
|
|
|
|
return data;
|
2025-08-14 03:28:23 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Standard Decryption
|
|
|
|
|
|
if (this.encryptionKey && typeof processedData === 'string' && processedData.length > 50) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const base64Regex = /^[A-Za-z0-9+/=]+$/;
|
|
|
|
|
|
if (base64Regex.test(processedData.trim())) {
|
2025-08-14 23:34:54 -04:00
|
|
|
|
if (window.DEBUG_MODE) {
|
|
|
|
|
|
console.log('🔓 Applying standard decryption...');
|
|
|
|
|
|
}
|
2025-08-14 03:28:23 -04:00
|
|
|
|
processedData = await window.EnhancedSecureCryptoUtils.decryptData(processedData, this.encryptionKey);
|
2025-08-14 23:34:54 -04:00
|
|
|
|
if (window.DEBUG_MODE) {
|
|
|
|
|
|
console.log('✅ Standard decryption successful');
|
|
|
|
|
|
}
|
2025-08-14 03:28:23 -04:00
|
|
|
|
|
2025-08-14 04:01:08 -04:00
|
|
|
|
// CHECKING FOR FAKE MESSAGES AFTER LEGACY DECRYPTION
|
2025-08-14 03:28:23 -04:00
|
|
|
|
if (typeof processedData === 'string') {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const legacyContent = JSON.parse(processedData);
|
2025-08-14 23:34:54 -04:00
|
|
|
|
if (legacyContent.type === 'fake' || legacyContent.isFakeTraffic === true) {
|
|
|
|
|
|
if (window.DEBUG_MODE) {
|
|
|
|
|
|
console.log(`🎭 BLOCKED: Legacy fake message: ${legacyContent.pattern || 'unknown'}`);
|
|
|
|
|
|
}
|
2025-08-14 03:28:23 -04:00
|
|
|
|
return 'FAKE_MESSAGE_FILTERED';
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
2025-08-14 23:34:54 -04:00
|
|
|
|
|
2025-08-14 03:28:23 -04:00
|
|
|
|
}
|
|
|
|
|
|
processedData = new TextEncoder().encode(processedData).buffer;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
2025-08-14 23:34:54 -04:00
|
|
|
|
if (window.DEBUG_MODE) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('warn', '⚠️ Standard decryption failed:', { details: error.message });
|
2025-08-14 23:34:54 -04:00
|
|
|
|
}
|
|
|
|
|
|
return data;
|
2025-08-14 03:28:23 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-14 23:34:54 -04:00
|
|
|
|
if (this.securityFeatures.hasNestedEncryption &&
|
|
|
|
|
|
this.nestedEncryptionKey &&
|
|
|
|
|
|
processedData instanceof ArrayBuffer &&
|
|
|
|
|
|
processedData.byteLength > 12) {
|
|
|
|
|
|
|
2025-08-14 03:28:23 -04:00
|
|
|
|
try {
|
|
|
|
|
|
processedData = await this.removeNestedEncryption(processedData);
|
2025-08-14 23:34:54 -04:00
|
|
|
|
|
|
|
|
|
|
if (processedData instanceof ArrayBuffer) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const textData = new TextDecoder().decode(processedData);
|
|
|
|
|
|
const nestedContent = JSON.parse(textData);
|
|
|
|
|
|
if (nestedContent.type === 'fake' || nestedContent.isFakeTraffic === true) {
|
|
|
|
|
|
if (window.DEBUG_MODE) {
|
|
|
|
|
|
console.log(`🎭 BLOCKED: Nested fake message: ${nestedContent.pattern || 'unknown'}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
return 'FAKE_MESSAGE_FILTERED';
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-14 03:28:23 -04:00
|
|
|
|
} catch (error) {
|
2025-08-14 23:34:54 -04:00
|
|
|
|
if (window.DEBUG_MODE) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('warn', '⚠️ Nested decryption failed - skipping this layer:', { details: error.message });
|
2025-08-14 23:34:54 -04:00
|
|
|
|
}
|
2025-08-14 03:28:23 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-14 23:34:54 -04:00
|
|
|
|
if (this.securityFeatures.hasPacketReordering &&
|
|
|
|
|
|
this.reorderingConfig.enabled &&
|
|
|
|
|
|
processedData instanceof ArrayBuffer) {
|
2025-08-14 03:28:23 -04:00
|
|
|
|
try {
|
2025-08-14 23:34:54 -04:00
|
|
|
|
const headerSize = this.reorderingConfig.useTimestamps ? 12 : 8;
|
|
|
|
|
|
if (processedData.byteLength > headerSize) {
|
|
|
|
|
|
return await this.processReorderedPacket(processedData);
|
|
|
|
|
|
}
|
2025-08-14 03:28:23 -04:00
|
|
|
|
} catch (error) {
|
2025-08-14 23:34:54 -04:00
|
|
|
|
if (window.DEBUG_MODE) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('warn', '⚠️ Reordering processing failed - using direct processing:', { details: error.message });
|
2025-08-14 23:34:54 -04:00
|
|
|
|
}
|
2025-08-14 03:28:23 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Packet Padding Removal
|
|
|
|
|
|
if (this.securityFeatures.hasPacketPadding && processedData instanceof ArrayBuffer) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
processedData = this.removePacketPadding(processedData);
|
|
|
|
|
|
} catch (error) {
|
2025-08-14 23:34:54 -04:00
|
|
|
|
if (window.DEBUG_MODE) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('warn', '⚠️ Padding removal failed:', { details: error.message });
|
2025-08-14 23:34:54 -04:00
|
|
|
|
}
|
2025-08-14 03:28:23 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Anti-Fingerprinting Removal
|
|
|
|
|
|
if (this.securityFeatures.hasAntiFingerprinting && processedData instanceof ArrayBuffer) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
processedData = this.removeAntiFingerprinting(processedData);
|
|
|
|
|
|
} catch (error) {
|
2025-08-14 23:34:54 -04:00
|
|
|
|
if (window.DEBUG_MODE) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('warn', '⚠️ Anti-fingerprinting removal failed:', { details: error.message });
|
2025-08-14 23:34:54 -04:00
|
|
|
|
}
|
2025-08-14 03:28:23 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-14 04:01:08 -04:00
|
|
|
|
// Final transformation
|
2025-08-14 03:28:23 -04:00
|
|
|
|
if (processedData instanceof ArrayBuffer) {
|
|
|
|
|
|
processedData = new TextDecoder().decode(processedData);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (typeof processedData === 'string') {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const finalContent = JSON.parse(processedData);
|
2025-08-14 23:34:54 -04:00
|
|
|
|
if (finalContent.type === 'fake' || finalContent.isFakeTraffic === true) {
|
|
|
|
|
|
if (window.DEBUG_MODE) {
|
|
|
|
|
|
console.log(`🎭 BLOCKED: Final check fake message: ${finalContent.pattern || 'unknown'}`);
|
|
|
|
|
|
}
|
2025-08-14 03:28:23 -04:00
|
|
|
|
return 'FAKE_MESSAGE_FILTERED';
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return processedData;
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ Critical error in removeSecurityLayers:', { errorType: error?.constructor?.name || 'Unknown' });
|
2025-08-14 03:28:23 -04:00
|
|
|
|
return data;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
removeAntiFingerprinting(data) {
|
|
|
|
|
|
// This is a simplified version - in practice, you'd need to reverse all operations
|
|
|
|
|
|
// For now, we'll just return the data as-is since the operations are mostly additive
|
|
|
|
|
|
return data;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-14 23:34:54 -04:00
|
|
|
|
async applySecurityLayers(data, isFakeMessage = false) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
let processedData = data;
|
|
|
|
|
|
|
|
|
|
|
|
if (isFakeMessage) {
|
|
|
|
|
|
if (this.encryptionKey && typeof processedData === 'string') {
|
|
|
|
|
|
processedData = await window.EnhancedSecureCryptoUtils.encryptData(processedData, this.encryptionKey);
|
|
|
|
|
|
}
|
|
|
|
|
|
return processedData;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (this.securityFeatures.hasNestedEncryption && this.nestedEncryptionKey && processedData instanceof ArrayBuffer) {
|
|
|
|
|
|
processedData = await this.applyNestedEncryption(processedData);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (this.securityFeatures.hasPacketReordering && this.reorderingConfig?.enabled && processedData instanceof ArrayBuffer) {
|
|
|
|
|
|
processedData = this.applyPacketReordering(processedData);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (this.securityFeatures.hasPacketPadding && processedData instanceof ArrayBuffer) {
|
|
|
|
|
|
processedData = this.applyPacketPadding(processedData);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (this.securityFeatures.hasAntiFingerprinting && processedData instanceof ArrayBuffer) {
|
|
|
|
|
|
processedData = this.applyAntiFingerprinting(processedData);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (this.encryptionKey && typeof processedData === 'string') {
|
|
|
|
|
|
processedData = await window.EnhancedSecureCryptoUtils.encryptData(processedData, this.encryptionKey);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return processedData;
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ Error in applySecurityLayers:', { errorType: error?.constructor?.name || 'Unknown' });
|
2025-08-14 23:34:54 -04:00
|
|
|
|
return data;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-14 03:28:23 -04:00
|
|
|
|
async sendMessage(data) {
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (!this.dataChannel || this.dataChannel.readyState !== 'open') {
|
|
|
|
|
|
throw new Error('Data channel not ready');
|
|
|
|
|
|
}
|
2025-08-14 23:34:54 -04:00
|
|
|
|
|
2025-08-21 04:07:16 -04:00
|
|
|
|
try {
|
|
|
|
|
|
this._secureLog('debug', 'sendMessage called', {
|
|
|
|
|
|
hasDataChannel: !!this.dataChannel,
|
|
|
|
|
|
dataChannelReady: this.dataChannel?.readyState === 'open',
|
|
|
|
|
|
isInitiator: this.isInitiator,
|
|
|
|
|
|
isVerified: this.isVerified,
|
|
|
|
|
|
connectionReady: this.peerConnection?.connectionState === 'connected'
|
|
|
|
|
|
});
|
2025-08-18 21:45:50 -04:00
|
|
|
|
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this._secureLog('debug', '🔍 sendMessage DEBUG', {
|
|
|
|
|
|
dataType: typeof data,
|
|
|
|
|
|
isString: typeof data === 'string',
|
|
|
|
|
|
isArrayBuffer: data instanceof ArrayBuffer,
|
|
|
|
|
|
dataLength: data?.length || data?.byteLength || 0,
|
|
|
|
|
|
});
|
2025-08-20 23:34:56 -04:00
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// FIX: Check whether this is a file-transfer message
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (typeof data === 'string') {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const parsed = JSON.parse(data);
|
2025-08-14 23:34:54 -04:00
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Send file messages directly without additional encryption
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (parsed.type && parsed.type.startsWith('file_')) {
|
|
|
|
|
|
this._secureLog('debug', '📁 Sending file message directly', { type: parsed.type });
|
|
|
|
|
|
this.dataChannel.send(data);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (jsonError) {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Not JSON — continue normal handling
|
2025-08-14 03:28:23 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// For regular text messages, send via secure path
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (typeof data === 'string') {
|
|
|
|
|
|
return await this.sendSecureMessage({ type: 'message', data, timestamp: Date.now() });
|
2025-08-14 23:34:54 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// For binary data, apply security layers with a limited mutex
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this._secureLog('debug', '🔐 Applying security layers to non-string data');
|
|
|
|
|
|
const securedData = await this._applySecurityLayersWithLimitedMutex(data, false);
|
|
|
|
|
|
this.dataChannel.send(securedData);
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
this._secureLog('error', '❌ Failed to send message', {
|
|
|
|
|
|
error: error.message,
|
|
|
|
|
|
errorType: error.constructor.name
|
|
|
|
|
|
});
|
|
|
|
|
|
throw error;
|
2025-08-14 23:34:54 -04:00
|
|
|
|
}
|
2025-08-21 04:07:16 -04:00
|
|
|
|
}
|
2025-08-14 23:34:54 -04:00
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// FIX: New method applying security layers with limited mutex use
|
2025-08-21 04:07:16 -04:00
|
|
|
|
async _applySecurityLayersWithLimitedMutex(data, isFakeMessage = false) {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Use mutex ONLY for cryptographic operations
|
2025-08-21 04:07:16 -04:00
|
|
|
|
return this._withMutex('cryptoOperation', async (operationId) => {
|
2025-08-18 21:45:50 -04:00
|
|
|
|
try {
|
2025-08-21 04:07:16 -04:00
|
|
|
|
let processedData = data;
|
2025-08-18 21:45:50 -04:00
|
|
|
|
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (isFakeMessage) {
|
|
|
|
|
|
if (this.encryptionKey && typeof processedData === 'string') {
|
|
|
|
|
|
processedData = await window.EnhancedSecureCryptoUtils.encryptData(processedData, this.encryptionKey);
|
2025-08-14 23:34:54 -04:00
|
|
|
|
}
|
2025-08-21 04:07:16 -04:00
|
|
|
|
return processedData;
|
2025-08-18 21:45:50 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (this.securityFeatures.hasNestedEncryption && this.nestedEncryptionKey && processedData instanceof ArrayBuffer) {
|
|
|
|
|
|
processedData = await this.applyNestedEncryption(processedData);
|
|
|
|
|
|
}
|
2025-08-20 18:19:42 -04:00
|
|
|
|
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (this.securityFeatures.hasPacketReordering && this.reorderingConfig?.enabled && processedData instanceof ArrayBuffer) {
|
|
|
|
|
|
processedData = this.applyPacketReordering(processedData);
|
2025-08-18 21:45:50 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (this.securityFeatures.hasPacketPadding && processedData instanceof ArrayBuffer) {
|
|
|
|
|
|
processedData = this.applyPacketPadding(processedData);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (this.securityFeatures.hasAntiFingerprinting && processedData instanceof ArrayBuffer) {
|
|
|
|
|
|
processedData = this.applyAntiFingerprinting(processedData);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (this.encryptionKey && typeof processedData === 'string') {
|
|
|
|
|
|
processedData = await window.EnhancedSecureCryptoUtils.encryptData(processedData, this.encryptionKey);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return processedData;
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ Error in applySecurityLayers:', { errorType: error?.constructor?.name || 'Unknown' });
|
2025-08-21 04:07:16 -04:00
|
|
|
|
return data;
|
|
|
|
|
|
}
|
2025-08-21 05:16:41 -04:00
|
|
|
|
}, 3000); // Short timeout for crypto operations
|
2025-08-21 04:07:16 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async sendSystemMessage(messageData) {
|
|
|
|
|
|
if (!this.dataChannel || this.dataChannel.readyState !== 'open') {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('warn', '⚠️ Cannot send system message - data channel not ready');
|
2025-08-21 04:07:16 -04:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const systemMessage = JSON.stringify({
|
|
|
|
|
|
type: messageData.type,
|
|
|
|
|
|
data: messageData,
|
|
|
|
|
|
timestamp: Date.now()
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
console.log('🔧 Sending system message:', messageData.type);
|
|
|
|
|
|
this.dataChannel.send(systemMessage);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
} catch (error) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ Failed to send system message:', { errorType: error?.constructor?.name || 'Unknown' });
|
2025-08-21 04:07:16 -04:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// FIX 1: Simplified mutex system for message processing
|
2025-08-21 04:07:16 -04:00
|
|
|
|
async processMessage(data) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
this._secureLog('debug', '�� Processing message', {
|
|
|
|
|
|
dataType: typeof data,
|
|
|
|
|
|
isArrayBuffer: data instanceof ArrayBuffer,
|
|
|
|
|
|
hasData: !!(data?.length || data?.byteLength)
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// CRITICAL: Early check for file messages WITHOUT mutex
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (typeof data === 'string') {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const parsed = JSON.parse(data);
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// FILE MESSAGES — PRIORITY 1 (WITHOUT MUTEX)
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
|
|
const fileMessageTypes = [
|
|
|
|
|
|
'file_transfer_start',
|
|
|
|
|
|
'file_transfer_response',
|
|
|
|
|
|
'file_chunk',
|
|
|
|
|
|
'chunk_confirmation',
|
|
|
|
|
|
'file_transfer_complete',
|
|
|
|
|
|
'file_transfer_error'
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
if (parsed.type && fileMessageTypes.includes(parsed.type)) {
|
|
|
|
|
|
console.log('📁 File message detected in processMessage:', parsed.type);
|
2025-08-18 21:45:50 -04:00
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Process file messages WITHOUT mutex
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (this.fileTransferSystem && typeof this.fileTransferSystem.handleFileMessage === 'function') {
|
|
|
|
|
|
console.log('📁 Processing file message directly:', parsed.type);
|
|
|
|
|
|
await this.fileTransferSystem.handleFileMessage(parsed);
|
2025-08-18 21:45:50 -04:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-08-21 17:40:17 -04:00
|
|
|
|
|
|
|
|
|
|
// КРИТИЧЕСКОЕ ИСПРАВЛЕНИЕ: Автоматическая инициализация файловой системы
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('warn', '⚠️ File transfer system not available, attempting automatic initialization...');
|
2025-08-21 04:07:16 -04:00
|
|
|
|
try {
|
2025-08-21 17:40:17 -04:00
|
|
|
|
// Проверяем готовность соединения
|
|
|
|
|
|
if (!this.isVerified) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('warn', '⚠️ Connection not verified, cannot initialize file transfer');
|
2025-08-21 17:40:17 -04:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!this.dataChannel || this.dataChannel.readyState !== 'open') {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('warn', '⚠️ Data channel not open, cannot initialize file transfer');
|
2025-08-21 17:40:17 -04:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Инициализируем файловую систему
|
|
|
|
|
|
this.initializeFileTransfer();
|
|
|
|
|
|
|
|
|
|
|
|
// Ждем инициализации
|
|
|
|
|
|
let attempts = 0;
|
|
|
|
|
|
const maxAttempts = 30; // 3 секунды максимум
|
|
|
|
|
|
while (!this.fileTransferSystem && attempts < maxAttempts) {
|
|
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
|
|
|
|
attempts++;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (this.fileTransferSystem && typeof this.fileTransferSystem.handleFileMessage === 'function') {
|
2025-08-21 17:40:17 -04:00
|
|
|
|
console.log('✅ File transfer system initialized, processing message:', parsed.type);
|
2025-08-21 04:07:16 -04:00
|
|
|
|
await this.fileTransferSystem.handleFileMessage(parsed);
|
|
|
|
|
|
return;
|
2025-08-21 17:40:17 -04:00
|
|
|
|
} else {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ File transfer system initialization failed');
|
2025-08-21 04:07:16 -04:00
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ Automatic file transfer initialization failed:', { errorType: e?.message || e?.constructor?.name || 'Unknown' });
|
2025-08-18 21:45:50 -04:00
|
|
|
|
}
|
2025-08-21 17:40:17 -04:00
|
|
|
|
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ File transfer system not available for:', { errorType: parsed.type?.constructor?.name || 'Unknown' });
|
2025-08-21 05:16:41 -04:00
|
|
|
|
return; // IMPORTANT: Exit after handling
|
2025-08-21 04:07:16 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// REGULAR USER MESSAGES (WITHOUT MUTEX)
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
|
|
if (parsed.type === 'message') {
|
|
|
|
|
|
console.log('📝 Regular user message detected in processMessage');
|
|
|
|
|
|
if (this.onMessage && parsed.data) {
|
|
|
|
|
|
this.deliverMessageToUI(parsed.data, 'received');
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// SYSTEM MESSAGES (WITHOUT MUTEX)
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
|
|
if (parsed.type && ['heartbeat', 'verification', 'verification_response', 'peer_disconnect', 'security_upgrade'].includes(parsed.type)) {
|
|
|
|
|
|
this.handleSystemMessage(parsed);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// FAKE MESSAGES (WITHOUT MUTEX)
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
|
|
if (parsed.type === 'fake') {
|
|
|
|
|
|
console.log('🎭 Fake message blocked in processMessage:', parsed.pattern);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} catch (jsonError) {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Not JSON — treat as text WITHOUT mutex
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (this.onMessage) {
|
|
|
|
|
|
this.deliverMessageToUI(data, 'received');
|
2025-08-14 23:34:54 -04:00
|
|
|
|
}
|
2025-08-18 21:45:50 -04:00
|
|
|
|
return;
|
2025-08-14 03:28:23 -04:00
|
|
|
|
}
|
2025-08-21 04:07:16 -04:00
|
|
|
|
}
|
2025-08-14 03:28:23 -04:00
|
|
|
|
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// ENCRYPTED DATA PROCESSING (WITH MUTEX ONLY FOR CRYPTO)
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// If here — apply security layers with limited mutex
|
2025-08-21 04:07:16 -04:00
|
|
|
|
const originalData = await this._processEncryptedDataWithLimitedMutex(data);
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Check processing result
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (originalData === 'FAKE_MESSAGE_FILTERED' ||
|
|
|
|
|
|
originalData === 'FILE_MESSAGE_FILTERED' ||
|
|
|
|
|
|
originalData === 'SYSTEM_MESSAGE_FILTERED') {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!originalData) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('warn', '⚠️ No data returned from removeSecurityLayers');
|
2025-08-21 04:07:16 -04:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Handle result after removeSecurityLayers
|
2025-08-21 04:07:16 -04:00
|
|
|
|
let messageText;
|
|
|
|
|
|
|
|
|
|
|
|
if (typeof originalData === 'string') {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const message = JSON.parse(originalData);
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// SECOND CHECK FOR FILE MESSAGES AFTER DECRYPTION
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (message.type && fileMessageTypes.includes(message.type)) {
|
|
|
|
|
|
console.log('📁 File message detected after decryption:', message.type);
|
|
|
|
|
|
if (this.fileTransferSystem) {
|
|
|
|
|
|
await this.fileTransferSystem.handleFileMessage(message);
|
2025-08-20 18:19:42 -04:00
|
|
|
|
}
|
2025-08-21 04:07:16 -04:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (message.type && ['heartbeat', 'verification', 'verification_response', 'peer_disconnect', 'security_upgrade'].includes(message.type)) {
|
|
|
|
|
|
this.handleSystemMessage(message);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (message.type === 'fake') {
|
|
|
|
|
|
console.log(`🎭 Post-decryption fake message blocked: ${message.pattern}`);
|
|
|
|
|
|
return;
|
2025-08-14 23:34:54 -04:00
|
|
|
|
}
|
2025-08-21 04:07:16 -04:00
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Regular messages
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (message.type === 'message' && message.data) {
|
|
|
|
|
|
messageText = message.data;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
messageText = originalData;
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
messageText = originalData;
|
2025-08-14 03:28:23 -04:00
|
|
|
|
}
|
2025-08-21 04:07:16 -04:00
|
|
|
|
} else if (originalData instanceof ArrayBuffer) {
|
|
|
|
|
|
messageText = new TextDecoder().decode(originalData);
|
|
|
|
|
|
} else if (originalData && typeof originalData === 'object' && originalData.message) {
|
|
|
|
|
|
messageText = originalData.message;
|
|
|
|
|
|
} else {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('warn', '⚠️ Unexpected data type after processing:', { details: typeof originalData });
|
2025-08-21 04:07:16 -04:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-08-14 03:28:23 -04:00
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Final check for fake and file messages
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (messageText && messageText.trim().startsWith('{')) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const finalCheck = JSON.parse(messageText);
|
|
|
|
|
|
if (finalCheck.type === 'fake') {
|
|
|
|
|
|
console.log(`�� Final fake message check blocked: ${finalCheck.pattern}`);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Additional check for file and system messages
|
2025-08-21 04:07:16 -04:00
|
|
|
|
const blockedTypes = [
|
|
|
|
|
|
'file_transfer_start', 'file_transfer_response', 'file_chunk',
|
|
|
|
|
|
'chunk_confirmation', 'file_transfer_complete', 'file_transfer_error',
|
|
|
|
|
|
'heartbeat', 'verification', 'verification_response',
|
|
|
|
|
|
'peer_disconnect', 'key_rotation_signal', 'key_rotation_ready', 'security_upgrade'
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
if (finalCheck.type && blockedTypes.includes(finalCheck.type)) {
|
|
|
|
|
|
console.log(`📁 Final system/file message check blocked: ${finalCheck.type}`);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Not JSON — fine for plain text
|
2025-08-14 23:34:54 -04:00
|
|
|
|
}
|
2025-08-21 04:07:16 -04:00
|
|
|
|
}
|
2025-08-14 03:28:23 -04:00
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Deliver message to the UI
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (this.onMessage && messageText) {
|
|
|
|
|
|
console.log('📤 Calling message handler with:', messageText.substring(0, 100));
|
|
|
|
|
|
this.deliverMessageToUI(messageText, 'received');
|
2025-08-18 21:45:50 -04:00
|
|
|
|
}
|
2025-08-21 04:07:16 -04:00
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ Failed to process message:', { errorType: error?.constructor?.name || 'Unknown' });
|
2025-08-21 04:07:16 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// FIX: New method with limited mutex when processing encrypted data
|
2025-08-21 04:07:16 -04:00
|
|
|
|
async _processEncryptedDataWithLimitedMutex(data) {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Use mutex ONLY for cryptographic operations
|
2025-08-21 04:07:16 -04:00
|
|
|
|
return this._withMutex('cryptoOperation', async (operationId) => {
|
|
|
|
|
|
this._secureLog('debug', '🔐 Processing encrypted data with limited mutex', {
|
|
|
|
|
|
operationId: operationId,
|
|
|
|
|
|
dataType: typeof data
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Apply security layers
|
2025-08-21 04:07:16 -04:00
|
|
|
|
const originalData = await this.removeSecurityLayers(data);
|
|
|
|
|
|
return originalData;
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
this._secureLog('error', '❌ Error processing encrypted data', {
|
|
|
|
|
|
operationId: operationId,
|
|
|
|
|
|
errorType: error.constructor.name
|
|
|
|
|
|
});
|
2025-08-21 05:16:41 -04:00
|
|
|
|
return data; // Return original data on error
|
2025-08-21 04:07:16 -04:00
|
|
|
|
}
|
2025-08-21 05:16:41 -04:00
|
|
|
|
}, 2000); // Short timeout for crypto operations
|
2025-08-14 03:28:23 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-17 20:38:47 -04:00
|
|
|
|
notifySecurityUpdate() {
|
|
|
|
|
|
try {
|
2025-08-20 23:34:56 -04:00
|
|
|
|
this._secureLog('debug', '🔒 Notifying about security level update', {
|
2025-08-17 20:38:47 -04:00
|
|
|
|
isConnected: this.isConnected(),
|
|
|
|
|
|
isVerified: this.isVerified,
|
|
|
|
|
|
hasKeys: !!(this.encryptionKey && this.macKey && this.metadataKey),
|
|
|
|
|
|
hasLastCalculation: !!this.lastSecurityCalculation
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Send an event about security level update
|
|
|
|
|
|
document.dispatchEvent(new CustomEvent('security-level-updated', {
|
|
|
|
|
|
detail: {
|
|
|
|
|
|
timestamp: Date.now(),
|
|
|
|
|
|
manager: 'webrtc',
|
|
|
|
|
|
webrtcManager: this,
|
|
|
|
|
|
isConnected: this.isConnected(),
|
|
|
|
|
|
isVerified: this.isVerified,
|
|
|
|
|
|
hasKeys: !!(this.encryptionKey && this.macKey && this.metadataKey),
|
|
|
|
|
|
lastCalculation: this.lastSecurityCalculation
|
|
|
|
|
|
}
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
// FIX: Force header refresh with correct manager
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
if (window.forceHeaderSecurityUpdate) {
|
|
|
|
|
|
window.forceHeaderSecurityUpdate(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 100);
|
|
|
|
|
|
|
|
|
|
|
|
// FIX: Direct update if there is a calculation
|
|
|
|
|
|
if (this.lastSecurityCalculation) {
|
|
|
|
|
|
document.dispatchEvent(new CustomEvent('real-security-calculated', {
|
|
|
|
|
|
detail: {
|
|
|
|
|
|
securityData: this.lastSecurityCalculation,
|
|
|
|
|
|
webrtcManager: this,
|
|
|
|
|
|
timestamp: Date.now()
|
|
|
|
|
|
}
|
|
|
|
|
|
}));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
2025-08-20 23:34:56 -04:00
|
|
|
|
this._secureLog('error', '❌ Error in notifySecurityUpdate', {
|
|
|
|
|
|
error: error.message
|
|
|
|
|
|
});
|
2025-08-17 20:38:47 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-14 03:28:23 -04:00
|
|
|
|
handleSystemMessage(message) {
|
|
|
|
|
|
console.log('🔧 Handling system message:', message.type);
|
|
|
|
|
|
|
|
|
|
|
|
switch (message.type) {
|
|
|
|
|
|
case 'heartbeat':
|
|
|
|
|
|
this.handleHeartbeat();
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 'verification':
|
|
|
|
|
|
this.handleVerificationRequest(message.data);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 'verification_response':
|
|
|
|
|
|
this.handleVerificationResponse(message.data);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 'peer_disconnect':
|
|
|
|
|
|
this.handlePeerDisconnectNotification(message);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 'key_rotation_signal':
|
|
|
|
|
|
console.log('🔄 Key rotation signal received (ignored for stability)');
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 'key_rotation_ready':
|
|
|
|
|
|
console.log('🔄 Key rotation ready signal received (ignored for stability)');
|
|
|
|
|
|
break;
|
2025-08-14 23:34:54 -04:00
|
|
|
|
case 'security_upgrade':
|
|
|
|
|
|
console.log('🔒 Security upgrade notification received:', message);
|
2025-08-20 18:19:42 -04:00
|
|
|
|
// Security upgrade messages are handled internally, not displayed to user
|
|
|
|
|
|
// to prevent duplicate system messages
|
2025-08-14 23:34:54 -04:00
|
|
|
|
break;
|
2025-08-14 03:28:23 -04:00
|
|
|
|
default:
|
|
|
|
|
|
console.log('🔧 Unknown system message type:', message.type);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-17 02:22:55 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
// FUNCTION MANAGEMENT METHODS
|
|
|
|
|
|
// ============================================
|
2025-08-14 03:28:23 -04:00
|
|
|
|
|
2025-08-17 02:22:55 -04:00
|
|
|
|
// Method to enable Stage 2 functions
|
|
|
|
|
|
enableStage2Security() {
|
|
|
|
|
|
if (this.sessionConstraints?.hasPacketReordering) {
|
|
|
|
|
|
this.securityFeatures.hasPacketReordering = true;
|
|
|
|
|
|
this.reorderingConfig.enabled = true;
|
|
|
|
|
|
}
|
2025-08-14 03:28:23 -04:00
|
|
|
|
|
2025-08-17 02:22:55 -04:00
|
|
|
|
if (this.sessionConstraints?.hasAntiFingerprinting) {
|
|
|
|
|
|
this.securityFeatures.hasAntiFingerprinting = true;
|
|
|
|
|
|
this.antiFingerprintingConfig.enabled = true;
|
|
|
|
|
|
if (this.currentSecurityLevel === 'enhanced') {
|
|
|
|
|
|
this.antiFingerprintingConfig.randomizeSizes = false;
|
|
|
|
|
|
this.antiFingerprintingConfig.maskPatterns = false;
|
|
|
|
|
|
this.antiFingerprintingConfig.useRandomHeaders = false;
|
|
|
|
|
|
}
|
2025-08-14 03:28:23 -04:00
|
|
|
|
}
|
2025-08-17 02:22:55 -04:00
|
|
|
|
|
|
|
|
|
|
this.notifySecurityUpgrade(2);
|
2025-08-17 20:38:47 -04:00
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
this.calculateAndReportSecurityLevel();
|
|
|
|
|
|
}, 500);
|
2025-08-14 03:28:23 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-17 02:22:55 -04:00
|
|
|
|
// Method to enable Stage 3 features (traffic obfuscation)
|
|
|
|
|
|
enableStage3Security() {
|
|
|
|
|
|
if (this.currentSecurityLevel !== 'maximum') {
|
|
|
|
|
|
console.log('🔒 Stage 3 features only available for premium sessions');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (this.sessionConstraints?.hasMessageChunking) {
|
|
|
|
|
|
this.securityFeatures.hasMessageChunking = true;
|
|
|
|
|
|
this.chunkingConfig.enabled = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (this.sessionConstraints?.hasFakeTraffic) {
|
|
|
|
|
|
this.securityFeatures.hasFakeTraffic = true;
|
|
|
|
|
|
this.fakeTrafficConfig.enabled = true;
|
|
|
|
|
|
this.startFakeTrafficGeneration();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this.notifySecurityUpgrade(3);
|
2025-08-17 20:38:47 -04:00
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
this.calculateAndReportSecurityLevel();
|
|
|
|
|
|
}, 500);
|
2025-08-17 02:22:55 -04:00
|
|
|
|
}
|
2025-08-14 03:28:23 -04:00
|
|
|
|
|
2025-08-17 02:22:55 -04:00
|
|
|
|
// Method for enabling Stage 4 functions (maximum safety)
|
|
|
|
|
|
enableStage4Security() {
|
|
|
|
|
|
if (this.currentSecurityLevel !== 'maximum') {
|
|
|
|
|
|
console.log('🔒 Stage 4 features only available for premium sessions');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (this.sessionConstraints?.hasDecoyChannels && this.isConnected() && this.isVerified) {
|
|
|
|
|
|
this.securityFeatures.hasDecoyChannels = true;
|
|
|
|
|
|
this.decoyChannelConfig.enabled = true;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
this.initializeDecoyChannels();
|
|
|
|
|
|
} catch (error) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('warn', '⚠️ Decoy channels initialization failed:', { details: error.message });
|
2025-08-17 02:22:55 -04:00
|
|
|
|
this.securityFeatures.hasDecoyChannels = false;
|
|
|
|
|
|
this.decoyChannelConfig.enabled = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Full anti-fingerprinting for maximum sessions
|
|
|
|
|
|
if (this.sessionConstraints?.hasAntiFingerprinting) {
|
|
|
|
|
|
this.antiFingerprintingConfig.randomizeSizes = true;
|
|
|
|
|
|
this.antiFingerprintingConfig.maskPatterns = true;
|
|
|
|
|
|
this.antiFingerprintingConfig.useRandomHeaders = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this.notifySecurityUpgrade(4);
|
2025-08-17 20:38:47 -04:00
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
this.calculateAndReportSecurityLevel();
|
|
|
|
|
|
}, 500);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
forceSecurityUpdate() {
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
this.calculateAndReportSecurityLevel();
|
|
|
|
|
|
this.notifySecurityUpdate();
|
|
|
|
|
|
}, 100);
|
2025-08-17 02:22:55 -04:00
|
|
|
|
}
|
2025-08-14 04:01:08 -04:00
|
|
|
|
|
2025-08-17 02:22:55 -04:00
|
|
|
|
// Method for getting security status
|
|
|
|
|
|
getSecurityStatus() {
|
|
|
|
|
|
const activeFeatures = Object.entries(this.securityFeatures)
|
|
|
|
|
|
.filter(([key, value]) => value === true)
|
|
|
|
|
|
.map(([key]) => key);
|
|
|
|
|
|
|
|
|
|
|
|
const stage = this.currentSecurityLevel === 'basic' ? 1 :
|
|
|
|
|
|
this.currentSecurityLevel === 'enhanced' ? 2 :
|
|
|
|
|
|
this.currentSecurityLevel === 'maximum' ? 4 : 1;
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
2025-08-14 23:34:54 -04:00
|
|
|
|
stage: stage,
|
2025-08-17 02:22:55 -04:00
|
|
|
|
sessionType: this.currentSessionType,
|
|
|
|
|
|
securityLevel: this.currentSecurityLevel,
|
|
|
|
|
|
activeFeatures: activeFeatures,
|
|
|
|
|
|
totalFeatures: Object.keys(this.securityFeatures).length,
|
|
|
|
|
|
activeFeaturesCount: activeFeatures.length,
|
|
|
|
|
|
activeFeaturesNames: activeFeatures,
|
|
|
|
|
|
sessionConstraints: this.sessionConstraints
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Method to notify UI about security update
|
|
|
|
|
|
notifySecurityUpgrade(stage) {
|
|
|
|
|
|
const stageNames = {
|
|
|
|
|
|
1: 'Basic Enhanced',
|
|
|
|
|
|
2: 'Medium Security',
|
|
|
|
|
|
3: 'High Security',
|
|
|
|
|
|
4: 'Maximum Security'
|
2025-08-14 23:34:54 -04:00
|
|
|
|
};
|
|
|
|
|
|
|
2025-08-17 02:22:55 -04:00
|
|
|
|
const message = `🔒 Security upgraded to Stage ${stage}: ${stageNames[stage]}`;
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Avoid duplicate security-upgrade notifications
|
2025-08-20 18:19:42 -04:00
|
|
|
|
if (!this.securityUpgradeNotificationSent || this.lastSecurityUpgradeStage !== stage) {
|
|
|
|
|
|
this.securityUpgradeNotificationSent = true;
|
|
|
|
|
|
this.lastSecurityUpgradeStage = stage;
|
|
|
|
|
|
|
|
|
|
|
|
// Notify local UI via onMessage
|
|
|
|
|
|
if (this.onMessage) {
|
|
|
|
|
|
this.deliverMessageToUI(message, 'system');
|
|
|
|
|
|
}
|
2025-08-17 02:22:55 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Send security upgrade notification to peer via WebRTC
|
|
|
|
|
|
if (this.dataChannel && this.dataChannel.readyState === 'open') {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const securityNotification = {
|
|
|
|
|
|
type: 'security_upgrade',
|
|
|
|
|
|
stage: stage,
|
|
|
|
|
|
stageName: stageNames[stage],
|
|
|
|
|
|
message: message,
|
|
|
|
|
|
timestamp: Date.now()
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
console.log('🔒 Sending security upgrade notification to peer:', securityNotification);
|
|
|
|
|
|
this.dataChannel.send(JSON.stringify(securityNotification));
|
|
|
|
|
|
} catch (error) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('warn', '⚠️ Failed to send security upgrade notification to peer:', { details: error.message });
|
2025-08-17 02:22:55 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const status = this.getSecurityStatus();
|
2025-08-14 23:34:54 -04:00
|
|
|
|
}
|
2025-08-17 20:38:47 -04:00
|
|
|
|
|
|
|
|
|
|
async calculateAndReportSecurityLevel() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (!window.EnhancedSecureCryptoUtils) {
|
2025-08-20 23:34:56 -04:00
|
|
|
|
this._secureLog('warn', '⚠️ EnhancedSecureCryptoUtils not available for security calculation');
|
2025-08-17 20:38:47 -04:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!this.isConnected() || !this.isVerified || !this.encryptionKey || !this.macKey) {
|
2025-08-20 23:34:56 -04:00
|
|
|
|
this._secureLog('debug', '⚠️ WebRTC not ready for security calculation', {
|
|
|
|
|
|
connected: this.isConnected(),
|
|
|
|
|
|
verified: this.isVerified,
|
|
|
|
|
|
hasEncryptionKey: !!this.encryptionKey,
|
|
|
|
|
|
hasMacKey: !!this.macKey
|
|
|
|
|
|
});
|
2025-08-17 20:38:47 -04:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-20 23:34:56 -04:00
|
|
|
|
this._secureLog('debug', '🔍 Calculating real security level', {
|
|
|
|
|
|
managerState: 'ready',
|
|
|
|
|
|
hasAllKeys: !!(this.encryptionKey && this.macKey && this.metadataKey)
|
|
|
|
|
|
});
|
2025-08-17 20:38:47 -04:00
|
|
|
|
|
|
|
|
|
|
const securityData = await window.EnhancedSecureCryptoUtils.calculateSecurityLevel(this);
|
|
|
|
|
|
|
2025-08-20 23:34:56 -04:00
|
|
|
|
this._secureLog('info', '🔐 Real security level calculated', {
|
2025-08-21 04:07:16 -04:00
|
|
|
|
hasSecurityLevel: !!securityData.level,
|
|
|
|
|
|
scoreRange: securityData.score > 80 ? 'high' : securityData.score > 50 ? 'medium' : 'low',
|
|
|
|
|
|
checksRatio: `${securityData.passedChecks}/${securityData.totalChecks}`,
|
|
|
|
|
|
isRealCalculation: securityData.isRealData
|
2025-08-20 23:34:56 -04:00
|
|
|
|
});
|
2025-08-17 20:38:47 -04:00
|
|
|
|
|
|
|
|
|
|
this.lastSecurityCalculation = securityData;
|
|
|
|
|
|
|
|
|
|
|
|
document.dispatchEvent(new CustomEvent('real-security-calculated', {
|
|
|
|
|
|
detail: {
|
|
|
|
|
|
securityData: securityData,
|
|
|
|
|
|
webrtcManager: this,
|
|
|
|
|
|
timestamp: Date.now(),
|
|
|
|
|
|
source: 'calculateAndReportSecurityLevel'
|
|
|
|
|
|
}
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
if (securityData.isRealData && this.onMessage) {
|
2025-08-20 18:19:42 -04:00
|
|
|
|
if (!this.securityCalculationNotificationSent || this.lastSecurityCalculationLevel !== securityData.level) {
|
|
|
|
|
|
this.securityCalculationNotificationSent = true;
|
|
|
|
|
|
this.lastSecurityCalculationLevel = securityData.level;
|
|
|
|
|
|
|
|
|
|
|
|
const message = `🔒 Security Level: ${securityData.level} (${securityData.score}%) - ${securityData.passedChecks}/${securityData.totalChecks} checks passed`;
|
|
|
|
|
|
this.deliverMessageToUI(message, 'system');
|
|
|
|
|
|
}
|
2025-08-17 20:38:47 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return securityData;
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
2025-08-20 23:34:56 -04:00
|
|
|
|
this._secureLog('error', '❌ Failed to calculate real security level', {
|
|
|
|
|
|
errorType: error.constructor.name
|
|
|
|
|
|
});
|
2025-08-17 20:38:47 -04:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-17 02:22:55 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
// AUTOMATIC STEP-BY-STEP SWITCHING ON
|
|
|
|
|
|
// ============================================
|
2025-08-14 23:34:54 -04:00
|
|
|
|
|
2025-08-17 02:22:55 -04:00
|
|
|
|
// Method for automatic feature enablement with stability check
|
|
|
|
|
|
async autoEnableSecurityFeatures() {
|
|
|
|
|
|
if (this.currentSessionType === 'demo') {
|
|
|
|
|
|
console.log('🔒 Demo session - keeping basic security only');
|
2025-08-17 20:38:47 -04:00
|
|
|
|
await this.calculateAndReportSecurityLevel();
|
2025-08-17 02:22:55 -04:00
|
|
|
|
this.notifySecurityUpgrade(1);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const checkStability = () => {
|
|
|
|
|
|
const isStable = this.isConnected() &&
|
|
|
|
|
|
this.isVerified &&
|
|
|
|
|
|
this.connectionAttempts === 0 &&
|
|
|
|
|
|
this.messageQueue.length === 0 &&
|
|
|
|
|
|
this.peerConnection?.connectionState === 'connected';
|
|
|
|
|
|
return isStable;
|
|
|
|
|
|
};
|
2025-08-11 20:52:14 -04:00
|
|
|
|
|
2025-08-17 02:22:55 -04:00
|
|
|
|
console.log(`🔒 ${this.currentSessionType} session - starting graduated security activation`);
|
2025-08-17 20:38:47 -04:00
|
|
|
|
await this.calculateAndReportSecurityLevel();
|
2025-08-17 02:22:55 -04:00
|
|
|
|
this.notifySecurityUpgrade(1);
|
2025-08-11 20:52:14 -04:00
|
|
|
|
|
2025-08-17 02:22:55 -04:00
|
|
|
|
if (this.currentSecurityLevel === 'enhanced' || this.currentSecurityLevel === 'maximum') {
|
2025-08-17 20:38:47 -04:00
|
|
|
|
setTimeout(async () => {
|
2025-08-14 03:28:23 -04:00
|
|
|
|
if (checkStability()) {
|
2025-08-17 02:22:55 -04:00
|
|
|
|
console.log('✅ Activating Stage 2 for paid session');
|
|
|
|
|
|
this.enableStage2Security();
|
2025-08-17 20:38:47 -04:00
|
|
|
|
await this.calculateAndReportSecurityLevel();
|
2025-08-14 03:28:23 -04:00
|
|
|
|
|
2025-08-17 02:22:55 -04:00
|
|
|
|
// For maximum sessions, turn on Stage 3 and 4
|
|
|
|
|
|
if (this.currentSecurityLevel === 'maximum') {
|
2025-08-17 20:38:47 -04:00
|
|
|
|
setTimeout(async () => {
|
2025-08-17 02:22:55 -04:00
|
|
|
|
if (checkStability()) {
|
|
|
|
|
|
console.log('✅ Activating Stage 3 for premium session');
|
|
|
|
|
|
this.enableStage3Security();
|
2025-08-17 20:38:47 -04:00
|
|
|
|
await this.calculateAndReportSecurityLevel();
|
2025-08-17 02:22:55 -04:00
|
|
|
|
|
2025-08-17 20:38:47 -04:00
|
|
|
|
setTimeout(async () => {
|
2025-08-17 02:22:55 -04:00
|
|
|
|
if (checkStability()) {
|
|
|
|
|
|
console.log('✅ Activating Stage 4 for premium session');
|
|
|
|
|
|
this.enableStage4Security();
|
2025-08-17 20:38:47 -04:00
|
|
|
|
await this.calculateAndReportSecurityLevel();
|
2025-08-17 02:22:55 -04:00
|
|
|
|
}
|
|
|
|
|
|
}, 20000);
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 15000);
|
|
|
|
|
|
}
|
2025-08-14 03:28:23 -04:00
|
|
|
|
}
|
2025-08-17 02:22:55 -04:00
|
|
|
|
}, 10000);
|
2025-08-14 03:28:23 -04:00
|
|
|
|
}
|
2025-08-17 02:22:55 -04:00
|
|
|
|
}
|
2025-08-14 03:28:23 -04:00
|
|
|
|
|
|
|
|
|
|
// ============================================
|
|
|
|
|
|
// CONNECTION MANAGEMENT WITH ENHANCED SECURITY
|
|
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
|
|
async establishConnection() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// Initialize enhanced security features
|
|
|
|
|
|
await this.initializeEnhancedSecurity();
|
|
|
|
|
|
|
|
|
|
|
|
// Start fake traffic generation
|
|
|
|
|
|
if (this.fakeTrafficConfig.enabled) {
|
|
|
|
|
|
this.startFakeTrafficGeneration();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Initialize decoy channels
|
|
|
|
|
|
if (this.decoyChannelConfig.enabled) {
|
|
|
|
|
|
this.initializeDecoyChannels();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ Failed to establish enhanced connection:', { errorType: error?.constructor?.name || 'Unknown' });
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Do not close the connection on setup errors — just log and continue
|
2025-08-18 21:45:50 -04:00
|
|
|
|
this.onStatusChange('disconnected');
|
2025-08-14 03:28:23 -04:00
|
|
|
|
throw error;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
disconnect() {
|
|
|
|
|
|
try {
|
2025-08-18 21:45:50 -04:00
|
|
|
|
console.log('🔌 Disconnecting WebRTC Manager...');
|
|
|
|
|
|
|
|
|
|
|
|
// Cleanup file transfer system
|
|
|
|
|
|
if (this.fileTransferSystem) {
|
|
|
|
|
|
console.log('🧹 Cleaning up file transfer system during disconnect...');
|
|
|
|
|
|
this.fileTransferSystem.cleanup();
|
|
|
|
|
|
this.fileTransferSystem = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-14 03:28:23 -04:00
|
|
|
|
// Stop fake traffic generation
|
|
|
|
|
|
this.stopFakeTrafficGeneration();
|
|
|
|
|
|
|
|
|
|
|
|
// Stop decoy traffic
|
|
|
|
|
|
for (const [channelName, timer] of this.decoyTimers.entries()) {
|
|
|
|
|
|
clearTimeout(timer);
|
|
|
|
|
|
}
|
|
|
|
|
|
this.decoyTimers.clear();
|
|
|
|
|
|
|
|
|
|
|
|
// Close decoy channels
|
|
|
|
|
|
for (const [channelName, channel] of this.decoyChannels.entries()) {
|
|
|
|
|
|
if (channel.readyState === 'open') {
|
|
|
|
|
|
channel.close();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
this.decoyChannels.clear();
|
|
|
|
|
|
|
|
|
|
|
|
// Clean up packet buffer
|
|
|
|
|
|
this.packetBuffer.clear();
|
|
|
|
|
|
|
|
|
|
|
|
// Clean up chunk queue
|
|
|
|
|
|
this.chunkQueue = [];
|
2025-08-14 04:01:08 -04:00
|
|
|
|
|
2025-08-14 03:28:23 -04:00
|
|
|
|
} catch (error) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ Error during enhanced disconnect:', { errorType: error?.constructor?.name || 'Unknown' });
|
2025-08-14 03:28:23 -04:00
|
|
|
|
}
|
2025-08-11 20:52:14 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Start periodic cleanup for rate limiting and security
|
|
|
|
|
|
startPeriodicCleanup() {
|
|
|
|
|
|
setInterval(() => {
|
|
|
|
|
|
const now = Date.now();
|
2025-08-20 23:04:29 -04:00
|
|
|
|
if (now - this.lastCleanupTime > EnhancedSecureWebRTCManager.TIMEOUTS.CLEANUP_INTERVAL) { // Every 5 minutes
|
2025-08-11 20:52:14 -04:00
|
|
|
|
window.EnhancedSecureCryptoUtils.rateLimiter.cleanup();
|
|
|
|
|
|
this.lastCleanupTime = now;
|
|
|
|
|
|
|
|
|
|
|
|
// Clean old processed message IDs (keep only last hour)
|
2025-08-20 23:04:29 -04:00
|
|
|
|
if (this.processedMessageIds.size > EnhancedSecureWebRTCManager.LIMITS.MAX_PROCESSED_MESSAGE_IDS) {
|
2025-08-11 20:52:14 -04:00
|
|
|
|
this.processedMessageIds.clear();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// PFS: Clean old keys that are no longer needed
|
|
|
|
|
|
this.cleanupOldKeys();
|
|
|
|
|
|
}
|
2025-08-20 23:04:29 -04:00
|
|
|
|
}, EnhancedSecureWebRTCManager.TIMEOUTS.CLEANUP_CHECK_INTERVAL); // Check every minute
|
2025-08-11 20:52:14 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Calculate current security level with real verification
|
|
|
|
|
|
async calculateSecurityLevel() {
|
|
|
|
|
|
return await window.EnhancedSecureCryptoUtils.calculateSecurityLevel(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// PFS: Check if key rotation is needed
|
|
|
|
|
|
shouldRotateKeys() {
|
|
|
|
|
|
if (!this.isConnected() || !this.isVerified) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const now = Date.now();
|
|
|
|
|
|
const timeSinceLastRotation = now - this.lastKeyRotation;
|
|
|
|
|
|
|
|
|
|
|
|
// Rotate keys every 5 minutes or after 100 messages
|
|
|
|
|
|
return timeSinceLastRotation > this.keyRotationInterval ||
|
|
|
|
|
|
this.messageCounter % 100 === 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// PFS: Rotate encryption keys for Perfect Forward Secrecy
|
|
|
|
|
|
async rotateKeys() {
|
2025-08-21 04:07:16 -04:00
|
|
|
|
return this._withMutex('keyOperation', async (operationId) => {
|
|
|
|
|
|
this._secureLog('info', '🔄 Starting key rotation with mutex', {
|
|
|
|
|
|
operationId: operationId
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Validate state inside the critical section
|
2025-08-11 20:52:14 -04:00
|
|
|
|
if (!this.isConnected() || !this.isVerified) {
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this._secureLog('warn', '⚠️ Key rotation aborted - connection not ready', {
|
|
|
|
|
|
operationId: operationId,
|
|
|
|
|
|
isConnected: this.isConnected(),
|
|
|
|
|
|
isVerified: this.isVerified
|
|
|
|
|
|
});
|
2025-08-11 20:52:14 -04:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Ensure rotation is not already in progress
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (this._keySystemState.isRotating) {
|
|
|
|
|
|
this._secureLog('warn', '⚠️ Key rotation already in progress', {
|
|
|
|
|
|
operationId: operationId
|
|
|
|
|
|
});
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2025-08-11 20:52:14 -04:00
|
|
|
|
|
2025-08-21 04:07:16 -04:00
|
|
|
|
try {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Set rotation flag
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this._keySystemState.isRotating = true;
|
|
|
|
|
|
this._keySystemState.lastOperation = 'rotation';
|
|
|
|
|
|
this._keySystemState.lastOperationTime = Date.now();
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Send rotation signal to peer
|
2025-08-21 04:07:16 -04:00
|
|
|
|
const rotationSignal = {
|
|
|
|
|
|
type: 'key_rotation_signal',
|
2025-08-11 20:52:14 -04:00
|
|
|
|
newVersion: this.currentKeyVersion + 1,
|
2025-08-21 04:07:16 -04:00
|
|
|
|
timestamp: Date.now(),
|
|
|
|
|
|
operationId: operationId
|
2025-08-11 20:52:14 -04:00
|
|
|
|
};
|
|
|
|
|
|
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (this.dataChannel && this.dataChannel.readyState === 'open') {
|
|
|
|
|
|
this.dataChannel.send(JSON.stringify(rotationSignal));
|
|
|
|
|
|
} else {
|
|
|
|
|
|
throw new Error('Data channel not ready for key rotation');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Wait for peer confirmation
|
2025-08-21 04:07:16 -04:00
|
|
|
|
return new Promise((resolve) => {
|
|
|
|
|
|
this.pendingRotation = {
|
|
|
|
|
|
newVersion: this.currentKeyVersion + 1,
|
|
|
|
|
|
operationId: operationId,
|
|
|
|
|
|
resolve: resolve,
|
|
|
|
|
|
timeout: setTimeout(() => {
|
|
|
|
|
|
this._secureLog('error', '⚠️ Key rotation timeout', {
|
|
|
|
|
|
operationId: operationId
|
|
|
|
|
|
});
|
|
|
|
|
|
this._keySystemState.isRotating = false;
|
|
|
|
|
|
this.pendingRotation = null;
|
|
|
|
|
|
resolve(false);
|
2025-08-21 05:16:41 -04:00
|
|
|
|
}, 10000) // 10 seconds timeout
|
2025-08-21 04:07:16 -04:00
|
|
|
|
};
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
this._secureLog('error', '❌ Key rotation failed in critical section', {
|
|
|
|
|
|
operationId: operationId,
|
|
|
|
|
|
errorType: error.constructor.name
|
|
|
|
|
|
});
|
|
|
|
|
|
this._keySystemState.isRotating = false;
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2025-08-21 05:16:41 -04:00
|
|
|
|
}, 10000); // 10 seconds timeout for the entire operation
|
2025-08-11 20:52:14 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// PFS: Clean up old keys that are no longer needed
|
|
|
|
|
|
cleanupOldKeys() {
|
|
|
|
|
|
const now = Date.now();
|
2025-08-20 23:04:29 -04:00
|
|
|
|
const maxKeyAge = EnhancedSecureWebRTCManager.LIMITS.MAX_KEY_AGE; // 15 minutes - keys older than this are deleted
|
2025-08-11 20:52:14 -04:00
|
|
|
|
|
|
|
|
|
|
for (const [version, keySet] of this.oldKeys.entries()) {
|
|
|
|
|
|
if (now - keySet.timestamp > maxKeyAge) {
|
|
|
|
|
|
this.oldKeys.delete(version);
|
|
|
|
|
|
window.EnhancedSecureCryptoUtils.secureLog.log('info', 'Old PFS keys cleaned up', {
|
|
|
|
|
|
version: version,
|
|
|
|
|
|
age: Math.round((now - keySet.timestamp) / 1000) + 's'
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// PFS: Get keys for specific version (for decryption)
|
|
|
|
|
|
getKeysForVersion(version) {
|
2025-08-13 14:48:24 -04:00
|
|
|
|
// First, we check the old keys (including version 0).
|
2025-08-11 20:52:14 -04:00
|
|
|
|
const oldKeySet = this.oldKeys.get(version);
|
|
|
|
|
|
if (oldKeySet && oldKeySet.encryptionKey && oldKeySet.macKey && oldKeySet.metadataKey) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
encryptionKey: oldKeySet.encryptionKey,
|
|
|
|
|
|
macKey: oldKeySet.macKey,
|
|
|
|
|
|
metadataKey: oldKeySet.metadataKey
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-13 14:48:24 -04:00
|
|
|
|
// If this is the current version, return the current keys.
|
2025-08-11 20:52:14 -04:00
|
|
|
|
if (version === this.currentKeyVersion) {
|
|
|
|
|
|
if (this.encryptionKey && this.macKey && this.metadataKey) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
encryptionKey: this.encryptionKey,
|
|
|
|
|
|
macKey: this.macKey,
|
|
|
|
|
|
metadataKey: this.metadataKey
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
window.EnhancedSecureCryptoUtils.secureLog.log('error', 'No valid keys found for version', {
|
|
|
|
|
|
requestedVersion: version,
|
|
|
|
|
|
currentVersion: this.currentKeyVersion,
|
|
|
|
|
|
availableVersions: Array.from(this.oldKeys.keys())
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
createPeerConnection() {
|
|
|
|
|
|
const config = {
|
|
|
|
|
|
iceServers: [
|
|
|
|
|
|
{ urls: 'stun:stun.l.google.com:19302' },
|
|
|
|
|
|
{ urls: 'stun:stun1.l.google.com:19302' },
|
|
|
|
|
|
{ urls: 'stun:stun2.l.google.com:19302' },
|
|
|
|
|
|
{ urls: 'stun:stun3.l.google.com:19302' },
|
|
|
|
|
|
{ urls: 'stun:stun4.l.google.com:19302' }
|
|
|
|
|
|
],
|
|
|
|
|
|
iceCandidatePoolSize: 10,
|
|
|
|
|
|
bundlePolicy: 'balanced'
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
this.peerConnection = new RTCPeerConnection(config);
|
|
|
|
|
|
|
|
|
|
|
|
this.peerConnection.onconnectionstatechange = () => {
|
|
|
|
|
|
const state = this.peerConnection.connectionState;
|
|
|
|
|
|
console.log('Connection state:', state);
|
|
|
|
|
|
|
|
|
|
|
|
if (state === 'connected' && !this.isVerified) {
|
|
|
|
|
|
this.onStatusChange('verifying');
|
|
|
|
|
|
} else if (state === 'connected' && this.isVerified) {
|
|
|
|
|
|
this.onStatusChange('connected');
|
|
|
|
|
|
} else if (state === 'disconnected' || state === 'closed') {
|
2025-08-13 14:48:24 -04:00
|
|
|
|
// If this is an intentional disconnect, clear immediately.
|
2025-08-11 20:52:14 -04:00
|
|
|
|
if (this.intentionalDisconnect) {
|
|
|
|
|
|
this.onStatusChange('disconnected');
|
2025-08-21 00:06:28 -04:00
|
|
|
|
setTimeout(() => this.disconnect(), 100);
|
2025-08-11 20:52:14 -04:00
|
|
|
|
} else {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Unexpected disconnection — do not auto-reconnect
|
2025-08-18 21:45:50 -04:00
|
|
|
|
this.onStatusChange('disconnected');
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Do not call cleanupConnection automatically
|
|
|
|
|
|
// to avoid closing the session on connection errors
|
2025-08-11 20:52:14 -04:00
|
|
|
|
}
|
|
|
|
|
|
} else if (state === 'failed') {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Do not auto-reconnect to avoid closing the session on errors
|
2025-08-18 21:45:50 -04:00
|
|
|
|
this.onStatusChange('disconnected');
|
|
|
|
|
|
// if (!this.intentionalDisconnect && this.connectionAttempts < this.maxConnectionAttempts) {
|
|
|
|
|
|
// this.connectionAttempts++;
|
|
|
|
|
|
// setTimeout(() => this.retryConnection(), 2000);
|
|
|
|
|
|
// } else {
|
|
|
|
|
|
// this.onStatusChange('disconnected');
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// // Do not call cleanupConnection automatically for 'failed'
|
|
|
|
|
|
// // to avoid closing the session on connection errors
|
2025-08-18 21:45:50 -04:00
|
|
|
|
// }
|
2025-08-11 20:52:14 -04:00
|
|
|
|
} else {
|
|
|
|
|
|
this.onStatusChange(state);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
this.peerConnection.ondatachannel = (event) => {
|
2025-08-14 23:34:54 -04:00
|
|
|
|
console.log('🔗 Data channel received:', {
|
|
|
|
|
|
channelLabel: event.channel.label,
|
|
|
|
|
|
channelState: event.channel.readyState,
|
|
|
|
|
|
isInitiator: this.isInitiator,
|
|
|
|
|
|
channelId: event.channel.id,
|
|
|
|
|
|
protocol: event.channel.protocol
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// CRITICAL: Store the received data channel
|
|
|
|
|
|
if (event.channel.label === 'securechat') {
|
|
|
|
|
|
console.log('🔗 MAIN DATA CHANNEL RECEIVED (answerer side)');
|
|
|
|
|
|
this.dataChannel = event.channel;
|
|
|
|
|
|
this.setupDataChannel(event.channel);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.log('🔗 ADDITIONAL DATA CHANNEL RECEIVED:', event.channel.label);
|
|
|
|
|
|
// Handle additional channels (heartbeat, etc.)
|
|
|
|
|
|
if (event.channel.label === 'heartbeat') {
|
|
|
|
|
|
this.heartbeatChannel = event.channel;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-11 20:52:14 -04:00
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
setupDataChannel(channel) {
|
2025-08-14 23:34:54 -04:00
|
|
|
|
console.log('🔗 setupDataChannel called:', {
|
|
|
|
|
|
channelLabel: channel.label,
|
|
|
|
|
|
channelState: channel.readyState,
|
|
|
|
|
|
isInitiator: this.isInitiator,
|
|
|
|
|
|
isVerified: this.isVerified
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-08-11 20:52:14 -04:00
|
|
|
|
this.dataChannel = channel;
|
|
|
|
|
|
|
2025-08-14 03:28:23 -04:00
|
|
|
|
this.dataChannel.onopen = async () => {
|
2025-08-14 23:34:54 -04:00
|
|
|
|
console.log('🔗 Data channel opened:', {
|
|
|
|
|
|
isInitiator: this.isInitiator,
|
|
|
|
|
|
isVerified: this.isVerified,
|
|
|
|
|
|
dataChannelState: this.dataChannel.readyState,
|
|
|
|
|
|
dataChannelLabel: this.dataChannel.label
|
|
|
|
|
|
});
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Configure backpressure for large transfers
|
2025-08-18 21:45:50 -04:00
|
|
|
|
try {
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (this.dataChannel && typeof this.dataChannel.bufferedAmountLowThreshold === 'number') {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// 1 MB threshold for bufferedamountlow event
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this.dataChannel.bufferedAmountLowThreshold = 1024 * 1024;
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
// ignore
|
|
|
|
|
|
}
|
2025-08-14 03:28:23 -04:00
|
|
|
|
|
2025-08-21 04:07:16 -04:00
|
|
|
|
try {
|
|
|
|
|
|
await this.establishConnection();
|
|
|
|
|
|
|
2025-08-21 17:40:17 -04:00
|
|
|
|
// КРИТИЧЕСКОЕ ИСПРАВЛЕНИЕ: Инициализируем файловую систему для обеих сторон
|
|
|
|
|
|
this.initializeFileTransfer();
|
2025-08-18 21:45:50 -04:00
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ Error in establishConnection:', { errorType: error?.constructor?.name || 'Unknown' });
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Continue despite errors
|
2025-08-18 21:45:50 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (this.isVerified) {
|
2025-08-11 20:52:14 -04:00
|
|
|
|
this.onStatusChange('connected');
|
|
|
|
|
|
this.processMessageQueue();
|
2025-08-14 03:28:23 -04:00
|
|
|
|
|
2025-08-17 20:38:47 -04:00
|
|
|
|
setTimeout(async () => {
|
|
|
|
|
|
await this.calculateAndReportSecurityLevel();
|
|
|
|
|
|
this.autoEnableSecurityFeatures();
|
|
|
|
|
|
this.notifySecurityUpdate();
|
|
|
|
|
|
}, 500);
|
2025-08-11 20:52:14 -04:00
|
|
|
|
} else {
|
|
|
|
|
|
this.onStatusChange('verifying');
|
|
|
|
|
|
this.initiateVerification();
|
|
|
|
|
|
}
|
|
|
|
|
|
this.startHeartbeat();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
this.dataChannel.onclose = () => {
|
|
|
|
|
|
if (!this.intentionalDisconnect) {
|
2025-08-18 21:45:50 -04:00
|
|
|
|
this.onStatusChange('disconnected');
|
2025-08-20 18:19:42 -04:00
|
|
|
|
|
|
|
|
|
|
if (!this.connectionClosedNotificationSent) {
|
|
|
|
|
|
this.connectionClosedNotificationSent = true;
|
|
|
|
|
|
this.deliverMessageToUI('🔌 Enhanced secure connection closed. Check connection status.', 'system');
|
|
|
|
|
|
}
|
2025-08-11 20:52:14 -04:00
|
|
|
|
} else {
|
|
|
|
|
|
this.onStatusChange('disconnected');
|
2025-08-20 18:19:42 -04:00
|
|
|
|
|
|
|
|
|
|
if (!this.connectionClosedNotificationSent) {
|
|
|
|
|
|
this.connectionClosedNotificationSent = true;
|
|
|
|
|
|
this.deliverMessageToUI('🔌 Enhanced secure connection closed', 'system');
|
|
|
|
|
|
}
|
2025-08-11 20:52:14 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this.stopHeartbeat();
|
|
|
|
|
|
this.isVerified = false;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// FIX 2: Remove mutex entirely from message processing path
|
2025-08-11 20:52:14 -04:00
|
|
|
|
this.dataChannel.onmessage = async (event) => {
|
2025-08-14 23:34:54 -04:00
|
|
|
|
try {
|
2025-08-21 04:07:16 -04:00
|
|
|
|
console.log('📨 Raw message received:', {
|
|
|
|
|
|
dataType: typeof event.data,
|
|
|
|
|
|
dataLength: event.data?.length || event.data?.byteLength || 0,
|
|
|
|
|
|
isString: typeof event.data === 'string'
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// IMPORTANT: Process ALL messages WITHOUT mutex
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (typeof event.data === 'string') {
|
2025-08-18 21:45:50 -04:00
|
|
|
|
try {
|
2025-08-20 18:19:42 -04:00
|
|
|
|
const parsed = JSON.parse(event.data);
|
2025-08-21 04:07:16 -04:00
|
|
|
|
console.log('📨 Parsed message:', {
|
|
|
|
|
|
type: parsed.type,
|
|
|
|
|
|
hasData: !!parsed.data,
|
|
|
|
|
|
timestamp: parsed.timestamp
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// CRITICAL: FILE MESSAGES (WITHOUT MUTEX)
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// ============================================
|
2025-08-18 21:45:50 -04:00
|
|
|
|
|
2025-08-20 18:19:42 -04:00
|
|
|
|
const fileMessageTypes = [
|
|
|
|
|
|
'file_transfer_start',
|
2025-08-21 04:07:16 -04:00
|
|
|
|
'file_transfer_response',
|
2025-08-20 18:19:42 -04:00
|
|
|
|
'file_chunk',
|
|
|
|
|
|
'chunk_confirmation',
|
|
|
|
|
|
'file_transfer_complete',
|
|
|
|
|
|
'file_transfer_error'
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
if (parsed.type && fileMessageTypes.includes(parsed.type)) {
|
2025-08-21 04:07:16 -04:00
|
|
|
|
console.log('📁 File message intercepted at WebRTC level:', parsed.type);
|
2025-08-20 18:19:42 -04:00
|
|
|
|
|
2025-08-21 17:40:17 -04:00
|
|
|
|
// КРИТИЧЕСКОЕ ИСПРАВЛЕНИЕ: Инициализируем файловую систему при получении файловых сообщений
|
|
|
|
|
|
if (!this.fileTransferSystem) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// Проверяем готовность соединения
|
|
|
|
|
|
if (this.isVerified && this.dataChannel && this.dataChannel.readyState === 'open') {
|
|
|
|
|
|
this.initializeFileTransfer();
|
|
|
|
|
|
|
|
|
|
|
|
// Ждем инициализации
|
|
|
|
|
|
let attempts = 0;
|
|
|
|
|
|
const maxAttempts = 30;
|
|
|
|
|
|
while (!this.fileTransferSystem && attempts < maxAttempts) {
|
|
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
|
|
|
|
attempts++;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (initError) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ Failed to initialize file transfer system for receiver:', { errorType: initError?.constructor?.name || 'Unknown' });
|
2025-08-21 17:40:17 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Handle directly WITHOUT extra checks
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (window.fileTransferSystem) {
|
|
|
|
|
|
console.log('📁 Forwarding to global file transfer system:', parsed.type);
|
2025-08-20 18:19:42 -04:00
|
|
|
|
await window.fileTransferSystem.handleFileMessage(parsed);
|
2025-08-21 04:07:16 -04:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (this.fileTransferSystem) {
|
|
|
|
|
|
console.log('📁 Forwarding to local file transfer system:', parsed.type);
|
|
|
|
|
|
await this.fileTransferSystem.handleFileMessage(parsed);
|
|
|
|
|
|
return;
|
2025-08-20 18:19:42 -04:00
|
|
|
|
}
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Attempt lazy initialization on receiver side
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('warn', '⚠️ File transfer system not ready, attempting lazy init...');
|
2025-08-21 04:07:16 -04:00
|
|
|
|
try {
|
|
|
|
|
|
await this._ensureFileTransferReady();
|
|
|
|
|
|
if (this.fileTransferSystem) {
|
|
|
|
|
|
await this.fileTransferSystem.handleFileMessage(parsed);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ Lazy init of file transfer failed:', { errorType: e?.message || e?.constructor?.name || 'Unknown' });
|
2025-08-21 04:07:16 -04:00
|
|
|
|
}
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ No file transfer system available for:', { errorType: parsed.type?.constructor?.name || 'Unknown' });
|
2025-08-21 05:16:41 -04:00
|
|
|
|
return; // IMPORTANT: Do not process further
|
2025-08-21 04:07:16 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// SYSTEM MESSAGES (WITHOUT MUTEX)
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
|
|
if (parsed.type && ['heartbeat', 'verification', 'verification_response', 'peer_disconnect', 'security_upgrade'].includes(parsed.type)) {
|
|
|
|
|
|
console.log('🔧 System message detected:', parsed.type);
|
|
|
|
|
|
this.handleSystemMessage(parsed);
|
|
|
|
|
|
return;
|
2025-08-18 21:45:50 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// REGULAR USER MESSAGES (WITHOUT MUTEX)
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
2025-08-20 18:19:42 -04:00
|
|
|
|
if (parsed.type === 'message' && parsed.data) {
|
2025-08-21 04:07:16 -04:00
|
|
|
|
console.log('📝 User message detected:', parsed.data.substring(0, 50));
|
2025-08-18 21:45:50 -04:00
|
|
|
|
if (this.onMessage) {
|
2025-08-20 18:19:42 -04:00
|
|
|
|
this.deliverMessageToUI(parsed.data, 'received');
|
2025-08-18 21:45:50 -04:00
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// ENHANCED MESSAGES (WITHOUT MUTEX)
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
|
|
if (parsed.type === 'enhanced_message' && parsed.data) {
|
|
|
|
|
|
console.log('🔐 Enhanced message detected, processing...');
|
|
|
|
|
|
await this._processEnhancedMessageWithoutMutex(parsed);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// FAKE MESSAGES (WITHOUT MUTEX)
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
|
|
if (parsed.type === 'fake') {
|
|
|
|
|
|
console.log('🎭 Fake message blocked:', parsed.pattern);
|
2025-08-18 21:45:50 -04:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-08-21 04:07:16 -04:00
|
|
|
|
|
|
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// UNKNOWN MESSAGE TYPES
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
|
|
console.log('❓ Unknown message type:', parsed.type);
|
|
|
|
|
|
|
2025-08-20 18:19:42 -04:00
|
|
|
|
} catch (jsonError) {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Not JSON — treat as regular text message
|
2025-08-21 04:07:16 -04:00
|
|
|
|
console.log('📄 Non-JSON message detected, treating as text');
|
2025-08-20 18:19:42 -04:00
|
|
|
|
if (this.onMessage) {
|
|
|
|
|
|
this.deliverMessageToUI(event.data, 'received');
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
2025-08-18 21:45:50 -04:00
|
|
|
|
}
|
2025-08-21 04:07:16 -04:00
|
|
|
|
} else if (event.data instanceof ArrayBuffer) {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Binary data — process WITHOUT mutex
|
2025-08-21 04:07:16 -04:00
|
|
|
|
console.log('🔢 Binary data received, processing...');
|
|
|
|
|
|
await this._processBinaryDataWithoutMutex(event.data);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.log('❓ Unknown data type:', typeof event.data);
|
2025-08-11 20:52:14 -04:00
|
|
|
|
}
|
2025-08-20 18:19:42 -04:00
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ Failed to process message in onmessage:', { errorType: error?.constructor?.name || 'Unknown' });
|
2025-08-11 20:52:14 -04:00
|
|
|
|
}
|
2025-08-18 21:45:50 -04:00
|
|
|
|
};
|
2025-08-11 20:52:14 -04:00
|
|
|
|
}
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// FIX 4: New method for processing binary data WITHOUT mutex
|
2025-08-21 04:07:16 -04:00
|
|
|
|
async _processBinaryDataWithoutMutex(data) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
console.log('🔢 Processing binary data without mutex...');
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Apply security layers WITHOUT mutex
|
2025-08-21 04:07:16 -04:00
|
|
|
|
let processedData = data;
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Nested Encryption Removal (if enabled)
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (this.securityFeatures.hasNestedEncryption &&
|
|
|
|
|
|
this.nestedEncryptionKey &&
|
|
|
|
|
|
processedData instanceof ArrayBuffer &&
|
|
|
|
|
|
processedData.byteLength > 12) {
|
2025-08-20 18:19:42 -04:00
|
|
|
|
|
2025-08-21 04:07:16 -04:00
|
|
|
|
try {
|
|
|
|
|
|
processedData = await this.removeNestedEncryption(processedData);
|
|
|
|
|
|
} catch (error) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('warn', '⚠️ Nested decryption failed, continuing with original data');
|
2025-08-11 20:52:14 -04:00
|
|
|
|
}
|
2025-08-21 04:07:16 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Packet Padding Removal (if enabled)
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (this.securityFeatures.hasPacketPadding && processedData instanceof ArrayBuffer) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
processedData = this.removePacketPadding(processedData);
|
|
|
|
|
|
} catch (error) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('warn', '⚠️ Packet padding removal failed, continuing with original data');
|
2025-08-21 04:07:16 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Anti-Fingerprinting Removal (if enabled)
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (this.securityFeatures.hasAntiFingerprinting && processedData instanceof ArrayBuffer) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
processedData = this.removeAntiFingerprinting(processedData);
|
|
|
|
|
|
} catch (error) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('warn', '⚠️ Anti-fingerprinting removal failed, continuing with original data');
|
2025-08-21 04:07:16 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Convert to text
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (processedData instanceof ArrayBuffer) {
|
|
|
|
|
|
const textData = new TextDecoder().decode(processedData);
|
2025-08-11 20:52:14 -04:00
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Check for fake messages
|
2025-08-21 04:07:16 -04:00
|
|
|
|
try {
|
|
|
|
|
|
const content = JSON.parse(textData);
|
|
|
|
|
|
if (content.type === 'fake' || content.isFakeTraffic === true) {
|
|
|
|
|
|
console.log(`🎭 BLOCKED: Binary fake message: ${content.pattern || 'unknown'}`);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Not JSON — fine for plain text
|
2025-08-21 04:07:16 -04:00
|
|
|
|
}
|
2025-08-11 20:52:14 -04:00
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Deliver message to user
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (this.onMessage) {
|
|
|
|
|
|
this.deliverMessageToUI(textData, 'received');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ Error processing binary data:', { errorType: error?.constructor?.name || 'Unknown' });
|
2025-08-21 04:07:16 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// FIX 3: New method for processing enhanced messages WITHOUT mutex
|
2025-08-21 04:07:16 -04:00
|
|
|
|
async _processEnhancedMessageWithoutMutex(parsedMessage) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
console.log('🔐 Processing enhanced message without mutex...');
|
|
|
|
|
|
|
|
|
|
|
|
if (!this.encryptionKey || !this.macKey || !this.metadataKey) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ Missing encryption keys for enhanced message');
|
2025-08-21 04:07:16 -04:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const decryptedResult = await window.EnhancedSecureCryptoUtils.decryptMessage(
|
|
|
|
|
|
parsedMessage.data,
|
|
|
|
|
|
this.encryptionKey,
|
|
|
|
|
|
this.macKey,
|
|
|
|
|
|
this.metadataKey
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (decryptedResult && decryptedResult.message) {
|
|
|
|
|
|
console.log('✅ Enhanced message decrypted successfully');
|
2025-08-11 20:52:14 -04:00
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Try parsing JSON and showing nested text if it's a chat message
|
2025-08-21 04:07:16 -04:00
|
|
|
|
try {
|
|
|
|
|
|
const decryptedContent = JSON.parse(decryptedResult.message);
|
|
|
|
|
|
if (decryptedContent.type === 'fake' || decryptedContent.isFakeTraffic === true) {
|
|
|
|
|
|
console.log(`�� BLOCKED: Encrypted fake message: ${decryptedContent.pattern || 'unknown'}`);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (decryptedContent && decryptedContent.type === 'message' && typeof decryptedContent.data === 'string') {
|
|
|
|
|
|
if (this.onMessage) {
|
|
|
|
|
|
this.deliverMessageToUI(decryptedContent.data, 'received');
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Not JSON — fine for plain text
|
2025-08-21 04:07:16 -04:00
|
|
|
|
}
|
2025-08-11 20:52:14 -04:00
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Otherwise pass as-is
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (this.onMessage) {
|
|
|
|
|
|
this.deliverMessageToUI(decryptedResult.message, 'received');
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('warn', '⚠️ No message content in decrypted result');
|
2025-08-21 04:07:16 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ Error processing enhanced message:', { errorType: error?.constructor?.name || 'Unknown' });
|
2025-08-21 04:07:16 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* Creates a unique ID for an operation
|
2025-08-21 04:07:16 -04:00
|
|
|
|
*/
|
|
|
|
|
|
_generateOperationId() {
|
|
|
|
|
|
return `op_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* Universal function to acquire a mutex
|
2025-08-21 04:07:16 -04:00
|
|
|
|
*/
|
|
|
|
|
|
async _acquireMutex(mutexName, operationId, timeout = 5000) {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// CRITICAL FIX: Build correct mutex property name
|
2025-08-21 04:07:16 -04:00
|
|
|
|
const mutexPropertyName = `_${mutexName}Mutex`;
|
|
|
|
|
|
const mutex = this[mutexPropertyName];
|
|
|
|
|
|
|
|
|
|
|
|
if (!mutex) {
|
|
|
|
|
|
this._secureLog('error', `❌ Unknown mutex: ${mutexName}`, {
|
|
|
|
|
|
mutexPropertyName: mutexPropertyName,
|
|
|
|
|
|
availableMutexes: this._getAvailableMutexes(),
|
|
|
|
|
|
operationId: operationId
|
|
|
|
|
|
});
|
|
|
|
|
|
throw new Error(`Unknown mutex: ${mutexName}. Available: ${this._getAvailableMutexes().join(', ')}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
const attemptLock = () => {
|
|
|
|
|
|
if (!mutex.locked) {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Acquire lock
|
2025-08-21 04:07:16 -04:00
|
|
|
|
mutex.locked = true;
|
|
|
|
|
|
mutex.lockId = operationId;
|
|
|
|
|
|
mutex.lockTimeout = setTimeout(() => {
|
|
|
|
|
|
this._secureLog('error', `⚠️ Mutex timeout for ${mutexName}`, {
|
|
|
|
|
|
operationId: operationId,
|
|
|
|
|
|
timeout: timeout,
|
|
|
|
|
|
queueLength: mutex.queue.length
|
|
|
|
|
|
});
|
|
|
|
|
|
this._releaseMutex(mutexName, operationId);
|
|
|
|
|
|
reject(new Error(`Mutex timeout for ${mutexName}`));
|
|
|
|
|
|
}, timeout);
|
|
|
|
|
|
|
|
|
|
|
|
this._secureLog('debug', `🔒 Mutex acquired: ${mutexName}`, {
|
|
|
|
|
|
operationId: operationId,
|
|
|
|
|
|
queueLength: mutex.queue.length
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
resolve();
|
|
|
|
|
|
} else {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Enqueue
|
2025-08-21 04:07:16 -04:00
|
|
|
|
mutex.queue.push({ resolve, reject, operationId, attemptLock });
|
|
|
|
|
|
|
|
|
|
|
|
this._secureLog('debug', `⏳ Mutex queued: ${mutexName}`, {
|
|
|
|
|
|
operationId: operationId,
|
|
|
|
|
|
queuePosition: mutex.queue.length,
|
|
|
|
|
|
currentLockId: mutex.lockId
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
2025-08-11 20:52:14 -04:00
|
|
|
|
|
2025-08-21 04:07:16 -04:00
|
|
|
|
attemptLock();
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* Release a mutex
|
2025-08-21 04:07:16 -04:00
|
|
|
|
*/
|
|
|
|
|
|
_releaseMutex(mutexName, operationId) {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// CRITICAL FIX: Build correct mutex property name
|
2025-08-21 04:07:16 -04:00
|
|
|
|
const mutexPropertyName = `_${mutexName}Mutex`;
|
|
|
|
|
|
const mutex = this[mutexPropertyName];
|
|
|
|
|
|
|
|
|
|
|
|
if (!mutex) {
|
|
|
|
|
|
this._secureLog('error', `❌ Unknown mutex for release: ${mutexName}`, {
|
|
|
|
|
|
mutexPropertyName: mutexPropertyName,
|
|
|
|
|
|
availableMutexes: this._getAvailableMutexes(),
|
|
|
|
|
|
operationId: operationId
|
2025-08-11 20:52:14 -04:00
|
|
|
|
});
|
2025-08-21 05:16:41 -04:00
|
|
|
|
return; // Do not throw, just log
|
2025-08-21 04:07:16 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (mutex.lockId !== operationId) {
|
|
|
|
|
|
this._secureLog('error', `❌ Invalid mutex release attempt`, {
|
|
|
|
|
|
mutexName: mutexName,
|
|
|
|
|
|
expectedLockId: mutex.lockId,
|
|
|
|
|
|
providedOperationId: operationId
|
|
|
|
|
|
});
|
2025-08-21 05:16:41 -04:00
|
|
|
|
return; // Do not throw, just log
|
2025-08-21 04:07:16 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Clear timeout
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (mutex.lockTimeout) {
|
|
|
|
|
|
clearTimeout(mutex.lockTimeout);
|
|
|
|
|
|
mutex.lockTimeout = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Release lock
|
2025-08-21 04:07:16 -04:00
|
|
|
|
mutex.locked = false;
|
|
|
|
|
|
mutex.lockId = null;
|
|
|
|
|
|
|
|
|
|
|
|
this._secureLog('debug', `🔓 Mutex released: ${mutexName}`, {
|
|
|
|
|
|
operationId: operationId,
|
|
|
|
|
|
queueLength: mutex.queue.length
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Process queue
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (mutex.queue.length > 0) {
|
|
|
|
|
|
const next = mutex.queue.shift();
|
|
|
|
|
|
setImmediate(() => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
next.attemptLock();
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
this._secureLog('error', '❌ Error processing mutex queue', {
|
|
|
|
|
|
mutexName: mutexName,
|
|
|
|
|
|
errorType: error.constructor.name
|
|
|
|
|
|
});
|
|
|
|
|
|
next.reject(error);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_getAvailableMutexes() {
|
|
|
|
|
|
const mutexes = [];
|
|
|
|
|
|
const propertyNames = Object.getOwnPropertyNames(this);
|
|
|
|
|
|
|
|
|
|
|
|
for (const prop of propertyNames) {
|
|
|
|
|
|
if (prop.endsWith('Mutex') && prop.startsWith('_')) {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Extract mutex name without prefix/suffix
|
|
|
|
|
|
const mutexName = prop.slice(1, -5); // Remove '_' prefix and 'Mutex' suffix
|
2025-08-21 04:07:16 -04:00
|
|
|
|
mutexes.push(mutexName);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return mutexes;
|
|
|
|
|
|
}
|
2025-08-11 20:52:14 -04:00
|
|
|
|
|
2025-08-21 04:07:16 -04:00
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* Safely execute an operation with a mutex
|
2025-08-21 04:07:16 -04:00
|
|
|
|
*/
|
|
|
|
|
|
async _withMutex(mutexName, operation, timeout = 5000) {
|
|
|
|
|
|
const operationId = this._generateOperationId();
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Validate before start
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (!this._validateMutexSystem()) {
|
|
|
|
|
|
this._secureLog('error', '❌ Mutex system not properly initialized', {
|
|
|
|
|
|
operationId: operationId,
|
|
|
|
|
|
mutexName: mutexName
|
|
|
|
|
|
});
|
|
|
|
|
|
throw new Error('Mutex system not properly initialized. Call _initializeMutexSystem() first.');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
await this._acquireMutex(mutexName, operationId, timeout);
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Increment operation counter
|
2025-08-21 04:07:16 -04:00
|
|
|
|
const counterKey = `${mutexName}Operations`;
|
|
|
|
|
|
if (this._operationCounters && this._operationCounters[counterKey] !== undefined) {
|
|
|
|
|
|
this._operationCounters[counterKey]++;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Execute the operation
|
2025-08-21 04:07:16 -04:00
|
|
|
|
const result = await operation(operationId);
|
|
|
|
|
|
return result;
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
this._secureLog('error', '❌ Error in mutex operation', {
|
|
|
|
|
|
operationId: operationId,
|
|
|
|
|
|
mutexName: mutexName,
|
|
|
|
|
|
errorType: error.constructor.name,
|
|
|
|
|
|
errorMessage: error.message
|
|
|
|
|
|
});
|
|
|
|
|
|
throw error;
|
|
|
|
|
|
} finally {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Always release the mutex in the finally block
|
2025-08-21 04:07:16 -04:00
|
|
|
|
try {
|
|
|
|
|
|
this._releaseMutex(mutexName, operationId);
|
|
|
|
|
|
} catch (releaseError) {
|
|
|
|
|
|
this._secureLog('error', '❌ Error releasing mutex in finally block', {
|
|
|
|
|
|
operationId: operationId,
|
|
|
|
|
|
mutexName: mutexName,
|
|
|
|
|
|
releaseErrorType: releaseError.constructor.name
|
2025-08-11 20:52:14 -04:00
|
|
|
|
});
|
2025-08-21 04:07:16 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-11 20:52:14 -04:00
|
|
|
|
|
2025-08-21 04:07:16 -04:00
|
|
|
|
_validateMutexSystem() {
|
|
|
|
|
|
const requiredMutexes = ['keyOperation', 'cryptoOperation', 'connectionOperation'];
|
|
|
|
|
|
|
|
|
|
|
|
for (const mutexName of requiredMutexes) {
|
|
|
|
|
|
const mutexPropertyName = `_${mutexName}Mutex`;
|
|
|
|
|
|
const mutex = this[mutexPropertyName];
|
|
|
|
|
|
|
|
|
|
|
|
if (!mutex || typeof mutex !== 'object') {
|
|
|
|
|
|
this._secureLog('error', `❌ Missing or invalid mutex: ${mutexName}`, {
|
|
|
|
|
|
mutexPropertyName: mutexPropertyName,
|
|
|
|
|
|
mutexType: typeof mutex
|
|
|
|
|
|
});
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Validate mutex structure
|
2025-08-21 04:07:16 -04:00
|
|
|
|
const requiredProps = ['locked', 'queue', 'lockId', 'lockTimeout'];
|
|
|
|
|
|
for (const prop of requiredProps) {
|
|
|
|
|
|
if (!(prop in mutex)) {
|
|
|
|
|
|
this._secureLog('error', `❌ Mutex ${mutexName} missing property: ${prop}`);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2025-08-11 20:52:14 -04:00
|
|
|
|
|
2025-08-21 04:07:16 -04:00
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* NEW: Emergency recovery of the mutex system
|
2025-08-21 04:07:16 -04:00
|
|
|
|
*/
|
|
|
|
|
|
_emergencyRecoverMutexSystem() {
|
|
|
|
|
|
this._secureLog('warn', '🚨 Emergency mutex system recovery initiated');
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Force re-initialize the system
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this._initializeMutexSystem();
|
|
|
|
|
|
|
|
|
|
|
|
this._secureLog('info', '✅ Mutex system recovered successfully');
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
this._secureLog('error', '❌ Failed to recover mutex system', {
|
|
|
|
|
|
errorType: error.constructor.name
|
|
|
|
|
|
});
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-11 20:52:14 -04:00
|
|
|
|
|
2025-08-21 04:07:16 -04:00
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* Secure key generation with mutex
|
2025-08-21 04:07:16 -04:00
|
|
|
|
*/
|
|
|
|
|
|
async _generateEncryptionKeys() {
|
|
|
|
|
|
return this._withMutex('keyOperation', async (operationId) => {
|
|
|
|
|
|
this._secureLog('info', '🔑 Generating encryption keys with mutex', {
|
|
|
|
|
|
operationId: operationId
|
|
|
|
|
|
});
|
2025-08-11 20:52:14 -04:00
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Ensure initialization is not already in progress
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (this._keySystemState.isInitializing) {
|
|
|
|
|
|
throw new Error('Key initialization already in progress');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
this._keySystemState.isInitializing = true;
|
|
|
|
|
|
this._keySystemState.lastOperation = 'generation';
|
|
|
|
|
|
this._keySystemState.lastOperationTime = Date.now();
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Generate ECDH keys
|
2025-08-21 04:07:16 -04:00
|
|
|
|
const ecdhKeyPair = await window.EnhancedSecureCryptoUtils.generateECDHKeyPair();
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Validate that keys were generated correctly
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (!ecdhKeyPair || !ecdhKeyPair.privateKey || !ecdhKeyPair.publicKey) {
|
|
|
|
|
|
throw new Error('Failed to generate valid ECDH key pair');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Generate ECDSA keys
|
2025-08-21 04:07:16 -04:00
|
|
|
|
const ecdsaKeyPair = await window.EnhancedSecureCryptoUtils.generateECDSAKeyPair();
|
|
|
|
|
|
|
|
|
|
|
|
if (!ecdsaKeyPair || !ecdsaKeyPair.privateKey || !ecdsaKeyPair.publicKey) {
|
|
|
|
|
|
throw new Error('Failed to generate valid ECDSA key pair');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this._secureLog('info', '✅ Encryption keys generated successfully', {
|
|
|
|
|
|
operationId: operationId,
|
|
|
|
|
|
hasECDHKeys: !!(ecdhKeyPair?.privateKey && ecdhKeyPair?.publicKey),
|
|
|
|
|
|
hasECDSAKeys: !!(ecdsaKeyPair?.privateKey && ecdsaKeyPair?.publicKey)
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return { ecdhKeyPair, ecdsaKeyPair };
|
|
|
|
|
|
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
this._keySystemState.isInitializing = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* Emergency unlocking of all mutexes
|
2025-08-21 04:07:16 -04:00
|
|
|
|
*/
|
|
|
|
|
|
_emergencyUnlockAllMutexes() {
|
|
|
|
|
|
const mutexes = ['keyOperation', 'cryptoOperation', 'connectionOperation'];
|
|
|
|
|
|
|
|
|
|
|
|
this._secureLog('error', '🚨 EMERGENCY: Unlocking all mutexes');
|
|
|
|
|
|
|
|
|
|
|
|
mutexes.forEach(mutexName => {
|
|
|
|
|
|
const mutex = this[`_${mutexName}Mutex`];
|
|
|
|
|
|
if (mutex) {
|
|
|
|
|
|
if (mutex.lockTimeout) {
|
|
|
|
|
|
clearTimeout(mutex.lockTimeout);
|
|
|
|
|
|
}
|
|
|
|
|
|
mutex.locked = false;
|
|
|
|
|
|
mutex.lockId = null;
|
|
|
|
|
|
mutex.lockTimeout = null;
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Clear the queue
|
2025-08-21 04:07:16 -04:00
|
|
|
|
mutex.queue.forEach(item => {
|
|
|
|
|
|
item.reject(new Error('Emergency mutex unlock'));
|
|
|
|
|
|
});
|
|
|
|
|
|
mutex.queue = [];
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* NEW: Diagnostics of the mutex system state
|
2025-08-21 04:07:16 -04:00
|
|
|
|
*/
|
|
|
|
|
|
_getMutexSystemDiagnostics() {
|
|
|
|
|
|
const diagnostics = {
|
|
|
|
|
|
timestamp: Date.now(),
|
|
|
|
|
|
systemValid: this._validateMutexSystem(),
|
|
|
|
|
|
mutexes: {},
|
|
|
|
|
|
counters: { ...this._operationCounters },
|
|
|
|
|
|
keySystemState: { ...this._keySystemState }
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const mutexNames = ['keyOperation', 'cryptoOperation', 'connectionOperation'];
|
|
|
|
|
|
|
|
|
|
|
|
mutexNames.forEach(mutexName => {
|
|
|
|
|
|
const mutexPropertyName = `_${mutexName}Mutex`;
|
|
|
|
|
|
const mutex = this[mutexPropertyName];
|
|
|
|
|
|
|
|
|
|
|
|
if (mutex) {
|
|
|
|
|
|
diagnostics.mutexes[mutexName] = {
|
|
|
|
|
|
locked: mutex.locked,
|
|
|
|
|
|
lockId: mutex.lockId,
|
|
|
|
|
|
queueLength: mutex.queue.length,
|
|
|
|
|
|
hasTimeout: !!mutex.lockTimeout
|
2025-08-11 20:52:14 -04:00
|
|
|
|
};
|
2025-08-21 04:07:16 -04:00
|
|
|
|
} else {
|
|
|
|
|
|
diagnostics.mutexes[mutexName] = { error: 'not_found' };
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return diagnostics;
|
|
|
|
|
|
}
|
2025-08-11 20:52:14 -04:00
|
|
|
|
|
2025-08-21 04:07:16 -04:00
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* FULLY FIXED createSecureOffer()
|
|
|
|
|
|
* With race-condition protection and improved security
|
2025-08-21 04:07:16 -04:00
|
|
|
|
*/
|
|
|
|
|
|
async createSecureOffer() {
|
|
|
|
|
|
return this._withMutex('connectionOperation', async (operationId) => {
|
|
|
|
|
|
this._secureLog('info', '📤 Creating secure offer with mutex', {
|
|
|
|
|
|
operationId: operationId,
|
|
|
|
|
|
connectionAttempts: this.connectionAttempts,
|
|
|
|
|
|
currentState: this.peerConnection?.connectionState || 'none'
|
2025-08-11 20:52:14 -04:00
|
|
|
|
});
|
2025-08-21 04:07:16 -04:00
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// PHASE 1: INITIALIZATION AND VALIDATION
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Reset notification flags for a new connection
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this._resetNotificationFlags();
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Rate limiting check
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (!this._checkRateLimit()) {
|
|
|
|
|
|
throw new Error('Connection rate limit exceeded. Please wait before trying again.');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Reset attempt counters
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this.connectionAttempts = 0;
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Generate session salt (64 bytes for v4.0)
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this.sessionSalt = window.EnhancedSecureCryptoUtils.generateSalt();
|
|
|
|
|
|
|
|
|
|
|
|
this._secureLog('debug', '🧂 Session salt generated', {
|
|
|
|
|
|
operationId: operationId,
|
|
|
|
|
|
saltLength: this.sessionSalt.length,
|
|
|
|
|
|
isValidSalt: Array.isArray(this.sessionSalt) && this.sessionSalt.length === 64
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// PHASE 2: SECURE KEY GENERATION
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Secure key generation via mutex
|
2025-08-21 04:07:16 -04:00
|
|
|
|
const keyPairs = await this._generateEncryptionKeys();
|
|
|
|
|
|
this.ecdhKeyPair = keyPairs.ecdhKeyPair;
|
|
|
|
|
|
this.ecdsaKeyPair = keyPairs.ecdsaKeyPair;
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Validate generated keys
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (!this.ecdhKeyPair?.privateKey || !this.ecdhKeyPair?.publicKey) {
|
|
|
|
|
|
throw new Error('Failed to generate valid ECDH key pair');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!this.ecdsaKeyPair?.privateKey || !this.ecdsaKeyPair?.publicKey) {
|
|
|
|
|
|
throw new Error('Failed to generate valid ECDSA key pair');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// PHASE 3: MITM PROTECTION AND FINGERPRINTING
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// MITM Protection: Compute unique key fingerprints
|
2025-08-21 04:07:16 -04:00
|
|
|
|
const ecdhFingerprint = await window.EnhancedSecureCryptoUtils.calculateKeyFingerprint(
|
|
|
|
|
|
await crypto.subtle.exportKey('spki', this.ecdhKeyPair.publicKey)
|
|
|
|
|
|
);
|
|
|
|
|
|
const ecdsaFingerprint = await window.EnhancedSecureCryptoUtils.calculateKeyFingerprint(
|
|
|
|
|
|
await crypto.subtle.exportKey('spki', this.ecdsaKeyPair.publicKey)
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Validate fingerprints
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (!ecdhFingerprint || !ecdsaFingerprint) {
|
|
|
|
|
|
throw new Error('Failed to generate key fingerprints');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this._secureLog('info', 'Generated unique key pairs for MITM protection', {
|
|
|
|
|
|
operationId: operationId,
|
|
|
|
|
|
hasECDHFingerprint: !!ecdhFingerprint,
|
|
|
|
|
|
hasECDSAFingerprint: !!ecdsaFingerprint,
|
|
|
|
|
|
fingerprintLength: ecdhFingerprint.length,
|
2025-08-16 20:58:42 -04:00
|
|
|
|
timestamp: Date.now()
|
2025-08-21 04:07:16 -04:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// PHASE 4: EXPORT SIGNED KEYS
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Export keys with digital signatures
|
2025-08-21 04:07:16 -04:00
|
|
|
|
const ecdhPublicKeyData = await window.EnhancedSecureCryptoUtils.exportPublicKeyWithSignature(
|
|
|
|
|
|
this.ecdhKeyPair.publicKey,
|
|
|
|
|
|
this.ecdsaKeyPair.privateKey,
|
|
|
|
|
|
'ECDH'
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
const ecdsaPublicKeyData = await window.EnhancedSecureCryptoUtils.exportPublicKeyWithSignature(
|
|
|
|
|
|
this.ecdsaKeyPair.publicKey,
|
|
|
|
|
|
this.ecdsaKeyPair.privateKey,
|
|
|
|
|
|
'ECDSA'
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Validate exported data
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (!ecdhPublicKeyData?.keyData || !ecdhPublicKeyData?.signature) {
|
|
|
|
|
|
throw new Error('Failed to export ECDH public key with signature');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!ecdsaPublicKeyData?.keyData || !ecdsaPublicKeyData?.signature) {
|
|
|
|
|
|
throw new Error('Failed to export ECDSA public key with signature');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// PHASE 5: UPDATE SECURITY FEATURES
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Atomic update of security features
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this._updateSecurityFeatures({
|
|
|
|
|
|
hasEncryption: true,
|
|
|
|
|
|
hasECDH: true,
|
|
|
|
|
|
hasECDSA: true,
|
|
|
|
|
|
hasMutualAuth: true,
|
|
|
|
|
|
hasMetadataProtection: true,
|
|
|
|
|
|
hasEnhancedReplayProtection: true,
|
|
|
|
|
|
hasNonExtractableKeys: true,
|
|
|
|
|
|
hasRateLimiting: true,
|
|
|
|
|
|
hasEnhancedValidation: true,
|
|
|
|
|
|
hasPFS: true
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// PHASE 6: INITIALIZE PEER CONNECTION
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
|
|
this.isInitiator = true;
|
|
|
|
|
|
this.onStatusChange('connecting');
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Create peer connection
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this.createPeerConnection();
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Create main data channel
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this.dataChannel = this.peerConnection.createDataChannel('securechat', {
|
|
|
|
|
|
ordered: true
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Setup data channel
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this.setupDataChannel(this.dataChannel);
|
|
|
|
|
|
|
|
|
|
|
|
this._secureLog('debug', '🔗 Data channel created', {
|
|
|
|
|
|
operationId: operationId,
|
|
|
|
|
|
channelLabel: this.dataChannel.label,
|
|
|
|
|
|
channelOrdered: this.dataChannel.ordered
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// PHASE 7: CREATE SDP OFFER
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Create WebRTC offer
|
2025-08-21 04:07:16 -04:00
|
|
|
|
const offer = await this.peerConnection.createOffer({
|
|
|
|
|
|
offerToReceiveAudio: false,
|
|
|
|
|
|
offerToReceiveVideo: false
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Set local description
|
2025-08-21 04:07:16 -04:00
|
|
|
|
await this.peerConnection.setLocalDescription(offer);
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Await ICE gathering
|
2025-08-21 04:07:16 -04:00
|
|
|
|
await this.waitForIceGathering();
|
|
|
|
|
|
|
|
|
|
|
|
this._secureLog('debug', '🧊 ICE gathering completed', {
|
|
|
|
|
|
operationId: operationId,
|
|
|
|
|
|
iceGatheringState: this.peerConnection.iceGatheringState,
|
|
|
|
|
|
connectionState: this.peerConnection.connectionState
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// PHASE 8: GENERATE VERIFICATION CODE
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Generate verification code for out-of-band auth
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this.verificationCode = window.EnhancedSecureCryptoUtils.generateVerificationCode();
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Validate verification code
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (!this.verificationCode || this.verificationCode.length < EnhancedSecureWebRTCManager.SIZES.VERIFICATION_CODE_MIN_LENGTH) {
|
|
|
|
|
|
throw new Error('Failed to generate valid verification code');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Notify UI about verification requirement
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this.onVerificationRequired(this.verificationCode);
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// PHASE 9: MUTUAL AUTHENTICATION CHALLENGE
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Generate challenge for mutual authentication
|
2025-08-21 04:07:16 -04:00
|
|
|
|
const authChallenge = window.EnhancedSecureCryptoUtils.generateMutualAuthChallenge();
|
|
|
|
|
|
|
|
|
|
|
|
if (!authChallenge) {
|
|
|
|
|
|
throw new Error('Failed to generate mutual authentication challenge');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// PHASE 10: SESSION ID FOR MITM PROTECTION
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// MITM Protection: Generate session-specific ID
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this.sessionId = Array.from(crypto.getRandomValues(new Uint8Array(EnhancedSecureWebRTCManager.SIZES.SESSION_ID_LENGTH)))
|
|
|
|
|
|
.map(b => b.toString(16).padStart(2, '0')).join('');
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Validate session ID
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (!this.sessionId || this.sessionId.length !== (EnhancedSecureWebRTCManager.SIZES.SESSION_ID_LENGTH * 2)) {
|
|
|
|
|
|
throw new Error('Failed to generate valid session ID');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// PHASE 11: SECURITY LEVEL CALCULATION
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Preliminary security level calculation
|
2025-08-21 04:07:16 -04:00
|
|
|
|
let securityLevel;
|
|
|
|
|
|
try {
|
|
|
|
|
|
securityLevel = await this.calculateSecurityLevel();
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
this._secureLog('warn', '⚠️ Security level calculation failed, using fallback', {
|
|
|
|
|
|
operationId: operationId,
|
|
|
|
|
|
errorType: error.constructor.name
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Fallback value
|
2025-08-21 04:07:16 -04:00
|
|
|
|
securityLevel = {
|
|
|
|
|
|
level: 'enhanced',
|
|
|
|
|
|
score: 75,
|
|
|
|
|
|
passedChecks: 10,
|
|
|
|
|
|
totalChecks: 15,
|
|
|
|
|
|
isRealData: false
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// PHASE 12: CREATE OFFER PACKAGE
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
|
|
const currentTimestamp = Date.now();
|
|
|
|
|
|
|
|
|
|
|
|
const offerPackage = {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Core information
|
2025-08-21 04:07:16 -04:00
|
|
|
|
type: 'enhanced_secure_offer',
|
|
|
|
|
|
sdp: this.peerConnection.localDescription.sdp,
|
|
|
|
|
|
version: '4.0',
|
|
|
|
|
|
timestamp: currentTimestamp,
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Cryptographic keys
|
2025-08-21 04:07:16 -04:00
|
|
|
|
ecdhPublicKey: ecdhPublicKeyData,
|
|
|
|
|
|
ecdsaPublicKey: ecdsaPublicKeyData,
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Session data
|
2025-08-21 04:07:16 -04:00
|
|
|
|
salt: this.sessionSalt,
|
|
|
|
|
|
sessionId: this.sessionId,
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Authentication
|
2025-08-21 04:07:16 -04:00
|
|
|
|
verificationCode: this.verificationCode,
|
|
|
|
|
|
authChallenge: authChallenge,
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Security metadata
|
2025-08-21 04:07:16 -04:00
|
|
|
|
securityLevel: securityLevel,
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Additional fields for validation
|
2025-08-21 04:07:16 -04:00
|
|
|
|
keyFingerprints: {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
ecdh: ecdhFingerprint.substring(0, 16), // First 16 chars for validation
|
2025-08-21 04:07:16 -04:00
|
|
|
|
ecdsa: ecdsaFingerprint.substring(0, 16)
|
|
|
|
|
|
},
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Optional capabilities info
|
2025-08-21 04:07:16 -04:00
|
|
|
|
capabilities: {
|
|
|
|
|
|
supportsFileTransfer: true,
|
|
|
|
|
|
supportsEnhancedSecurity: true,
|
|
|
|
|
|
supportsKeyRotation: true,
|
|
|
|
|
|
supportsFakeTraffic: this.fakeTrafficConfig.enabled,
|
|
|
|
|
|
supportsDecoyChannels: this.decoyChannelConfig.enabled
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// PHASE 13: VALIDATE OFFER PACKAGE
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Final validation of the generated package
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (!this.validateEnhancedOfferData(offerPackage)) {
|
|
|
|
|
|
throw new Error('Generated offer package failed validation');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// PHASE 14: LOGGING AND EVENTS
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
|
|
this._secureLog('info', 'Enhanced secure offer created successfully', {
|
|
|
|
|
|
operationId: operationId,
|
|
|
|
|
|
version: offerPackage.version,
|
|
|
|
|
|
hasECDSA: true,
|
|
|
|
|
|
hasMutualAuth: true,
|
|
|
|
|
|
hasSessionId: !!offerPackage.sessionId,
|
|
|
|
|
|
securityLevel: securityLevel.level,
|
|
|
|
|
|
timestamp: currentTimestamp,
|
|
|
|
|
|
capabilitiesCount: Object.keys(offerPackage.capabilities).length
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Dispatch event about new connection
|
2025-08-21 04:07:16 -04:00
|
|
|
|
document.dispatchEvent(new CustomEvent('new-connection', {
|
|
|
|
|
|
detail: {
|
|
|
|
|
|
type: 'offer',
|
|
|
|
|
|
timestamp: currentTimestamp,
|
|
|
|
|
|
securityLevel: securityLevel.level,
|
|
|
|
|
|
operationId: operationId
|
|
|
|
|
|
}
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// PHASE 15: RETURN RESULT
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
|
|
return offerPackage;
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// ERROR HANDLING
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
|
|
this._secureLog('error', '❌ Enhanced secure offer creation failed in critical section', {
|
|
|
|
|
|
operationId: operationId,
|
|
|
|
|
|
errorType: error.constructor.name,
|
|
|
|
|
|
errorMessage: error.message,
|
|
|
|
|
|
phase: this._determineErrorPhase(error),
|
|
|
|
|
|
connectionAttempts: this.connectionAttempts
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Cleanup state on error
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this._cleanupFailedOfferCreation();
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Update status
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this.onStatusChange('disconnected');
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Re-throw for upper-level handling
|
2025-08-21 04:07:16 -04:00
|
|
|
|
throw error;
|
|
|
|
|
|
}
|
2025-08-21 05:16:41 -04:00
|
|
|
|
}, 15000); // 15 seconds timeout for the entire offer creation
|
2025-08-21 04:07:16 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* HELPER: Determine the phase where the error occurred
|
2025-08-21 04:07:16 -04:00
|
|
|
|
*/
|
|
|
|
|
|
_determineErrorPhase(error) {
|
|
|
|
|
|
const message = error.message.toLowerCase();
|
|
|
|
|
|
|
|
|
|
|
|
if (message.includes('rate limit')) return 'rate_limiting';
|
|
|
|
|
|
if (message.includes('key pair') || message.includes('generate')) return 'key_generation';
|
|
|
|
|
|
if (message.includes('fingerprint')) return 'fingerprinting';
|
|
|
|
|
|
if (message.includes('export') || message.includes('signature')) return 'key_export';
|
|
|
|
|
|
if (message.includes('peer connection')) return 'webrtc_setup';
|
|
|
|
|
|
if (message.includes('offer') || message.includes('sdp')) return 'sdp_creation';
|
|
|
|
|
|
if (message.includes('verification')) return 'verification_setup';
|
|
|
|
|
|
if (message.includes('session')) return 'session_setup';
|
|
|
|
|
|
if (message.includes('validation')) return 'package_validation';
|
|
|
|
|
|
|
|
|
|
|
|
return 'unknown';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* HELPER: Cleanup state after failed offer creation
|
2025-08-21 04:07:16 -04:00
|
|
|
|
*/
|
|
|
|
|
|
_cleanupFailedOfferCreation() {
|
|
|
|
|
|
try {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Clear keys
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this.ecdhKeyPair = null;
|
|
|
|
|
|
this.ecdsaKeyPair = null;
|
|
|
|
|
|
this.sessionSalt = null;
|
|
|
|
|
|
this.sessionId = null;
|
|
|
|
|
|
this.verificationCode = null;
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Close peer connection if it was created
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (this.peerConnection) {
|
|
|
|
|
|
this.peerConnection.close();
|
|
|
|
|
|
this.peerConnection = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Clear data channel
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (this.dataChannel) {
|
|
|
|
|
|
this.dataChannel.close();
|
|
|
|
|
|
this.dataChannel = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Reset flags
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this.isInitiator = false;
|
|
|
|
|
|
this.isVerified = false;
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Reset security features to baseline
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this._updateSecurityFeatures({
|
|
|
|
|
|
hasEncryption: false,
|
|
|
|
|
|
hasECDH: false,
|
|
|
|
|
|
hasECDSA: false,
|
|
|
|
|
|
hasMutualAuth: false,
|
|
|
|
|
|
hasMetadataProtection: false,
|
|
|
|
|
|
hasEnhancedReplayProtection: false,
|
|
|
|
|
|
hasNonExtractableKeys: false,
|
|
|
|
|
|
hasEnhancedValidation: false,
|
|
|
|
|
|
hasPFS: false
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
this._secureLog('debug', '🧹 Failed offer creation cleanup completed');
|
|
|
|
|
|
|
|
|
|
|
|
} catch (cleanupError) {
|
|
|
|
|
|
this._secureLog('error', '❌ Error during offer creation cleanup', {
|
|
|
|
|
|
errorType: cleanupError.constructor.name
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* HELPER: Atomic update of security features (if not added yet)
|
2025-08-21 04:07:16 -04:00
|
|
|
|
*/
|
|
|
|
|
|
_updateSecurityFeatures(updates) {
|
|
|
|
|
|
const oldFeatures = { ...this.securityFeatures };
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
Object.assign(this.securityFeatures, updates);
|
|
|
|
|
|
|
|
|
|
|
|
this._secureLog('debug', '🔧 Security features updated', {
|
|
|
|
|
|
updatedCount: Object.keys(updates).length,
|
|
|
|
|
|
totalFeatures: Object.keys(this.securityFeatures).length
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Roll back on error
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this.securityFeatures = oldFeatures;
|
|
|
|
|
|
this._secureLog('error', '❌ Security features update failed, rolled back', {
|
|
|
|
|
|
errorType: error.constructor.name
|
|
|
|
|
|
});
|
|
|
|
|
|
throw error;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* FULLY FIXED METHOD createSecureAnswer()
|
|
|
|
|
|
* With race-condition protection and enhanced security
|
2025-08-21 04:07:16 -04:00
|
|
|
|
*/
|
|
|
|
|
|
async createSecureAnswer(offerData) {
|
|
|
|
|
|
return this._withMutex('connectionOperation', async (operationId) => {
|
|
|
|
|
|
this._secureLog('info', '📨 Creating secure answer with mutex', {
|
|
|
|
|
|
operationId: operationId,
|
|
|
|
|
|
hasOfferData: !!offerData,
|
|
|
|
|
|
offerType: offerData?.type,
|
|
|
|
|
|
offerVersion: offerData?.version,
|
|
|
|
|
|
offerTimestamp: offerData?.timestamp
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// PHASE 1: PRE-VALIDATION OF OFFER
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Reset notification flags for a new connection
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this._resetNotificationFlags();
|
|
|
|
|
|
|
|
|
|
|
|
this._secureLog('debug', 'Starting enhanced offer validation', {
|
|
|
|
|
|
operationId: operationId,
|
|
|
|
|
|
hasOfferData: !!offerData,
|
|
|
|
|
|
offerType: offerData?.type,
|
|
|
|
|
|
hasECDHKey: !!offerData?.ecdhPublicKey,
|
|
|
|
|
|
hasECDSAKey: !!offerData?.ecdsaPublicKey,
|
|
|
|
|
|
hasSalt: !!offerData?.salt
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Strict input validation
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (!this.validateEnhancedOfferData(offerData)) {
|
|
|
|
|
|
throw new Error('Invalid connection data format - failed enhanced validation');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Rate limiting check
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (!window.EnhancedSecureCryptoUtils.rateLimiter.checkConnectionRate(this.rateLimiterId)) {
|
|
|
|
|
|
throw new Error('Connection rate limit exceeded. Please wait before trying again.');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// PHASE 2: SECURITY AND ANTI-REPLAY PROTECTION
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// MITM Protection: Validate offer data structure
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (!offerData.timestamp || !offerData.version) {
|
|
|
|
|
|
throw new Error('Missing required security fields in offer data – possible MITM attack');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Replay attack protection (window reduced to 5 minutes)
|
2025-08-21 04:07:16 -04:00
|
|
|
|
const offerAge = Date.now() - offerData.timestamp;
|
2025-08-21 05:16:41 -04:00
|
|
|
|
const MAX_OFFER_AGE = 300000; // 5 minutes instead of 1 hour
|
2025-08-21 04:07:16 -04:00
|
|
|
|
|
|
|
|
|
|
if (offerAge > MAX_OFFER_AGE) {
|
|
|
|
|
|
this._secureLog('error', 'Offer data is too old - possible replay attack', {
|
|
|
|
|
|
operationId: operationId,
|
|
|
|
|
|
offerAge: Math.round(offerAge / 1000),
|
|
|
|
|
|
maxAllowedAge: Math.round(MAX_OFFER_AGE / 1000),
|
|
|
|
|
|
timestamp: offerData.timestamp
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Notify the main code about the replay attack
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (this.onAnswerError) {
|
|
|
|
|
|
this.onAnswerError('replay_attack', 'Offer data is too old – possible replay attack');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
throw new Error('Offer data is too old – possible replay attack');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Protocol version compatibility check
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (offerData.version !== '4.0') {
|
|
|
|
|
|
this._secureLog('warn', 'Protocol version mismatch detected', {
|
|
|
|
|
|
operationId: operationId,
|
|
|
|
|
|
expectedVersion: '4.0',
|
|
|
|
|
|
receivedVersion: offerData.version
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// For backward compatibility with v3.0, a fallback can be added
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (offerData.version !== '3.0') {
|
|
|
|
|
|
throw new Error(`Unsupported protocol version: ${offerData.version}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// PHASE 3: EXTRACT AND VALIDATE SESSION SALT
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Set session salt from offer
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this.sessionSalt = offerData.salt;
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Validate session salt
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (!Array.isArray(this.sessionSalt)) {
|
|
|
|
|
|
throw new Error('Invalid session salt format - must be array');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const expectedSaltLength = offerData.version === '4.0' ? 64 : 32;
|
|
|
|
|
|
if (this.sessionSalt.length !== expectedSaltLength) {
|
|
|
|
|
|
throw new Error(`Invalid session salt length: expected ${expectedSaltLength}, got ${this.sessionSalt.length}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// MITM Protection: Check salt integrity
|
2025-08-21 04:07:16 -04:00
|
|
|
|
const saltFingerprint = await window.EnhancedSecureCryptoUtils.calculateKeyFingerprint(this.sessionSalt);
|
|
|
|
|
|
|
|
|
|
|
|
this._secureLog('info', 'Session salt validated successfully', {
|
|
|
|
|
|
operationId: operationId,
|
|
|
|
|
|
saltLength: this.sessionSalt.length,
|
|
|
|
|
|
saltFingerprint: saltFingerprint.substring(0, 8)
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// PHASE 4: SECURE GENERATION OF OUR KEYS
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Secure generation of our keys via mutex
|
2025-08-21 04:07:16 -04:00
|
|
|
|
const keyPairs = await this._generateEncryptionKeys();
|
|
|
|
|
|
this.ecdhKeyPair = keyPairs.ecdhKeyPair;
|
|
|
|
|
|
this.ecdsaKeyPair = keyPairs.ecdsaKeyPair;
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Additional validation of generated keys
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (!(this.ecdhKeyPair?.privateKey instanceof CryptoKey)) {
|
|
|
|
|
|
this._secureLog('error', 'Local ECDH private key is not a CryptoKey', {
|
|
|
|
|
|
operationId: operationId,
|
|
|
|
|
|
hasKeyPair: !!this.ecdhKeyPair,
|
|
|
|
|
|
privateKeyType: typeof this.ecdhKeyPair?.privateKey,
|
|
|
|
|
|
privateKeyAlgorithm: this.ecdhKeyPair?.privateKey?.algorithm?.name
|
|
|
|
|
|
});
|
|
|
|
|
|
throw new Error('Local ECDH private key is not a valid CryptoKey');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// PHASE 5: IMPORT AND VERIFY PEER KEYS
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Import peer ECDSA public key for signature verification
|
2025-08-21 04:07:16 -04:00
|
|
|
|
let peerECDSAPublicKey;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
peerECDSAPublicKey = await crypto.subtle.importKey(
|
|
|
|
|
|
'spki',
|
|
|
|
|
|
new Uint8Array(offerData.ecdsaPublicKey.keyData),
|
|
|
|
|
|
{
|
|
|
|
|
|
name: 'ECDSA',
|
|
|
|
|
|
namedCurve: 'P-384'
|
|
|
|
|
|
},
|
|
|
|
|
|
false,
|
|
|
|
|
|
['verify']
|
|
|
|
|
|
);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
throw new Error(`Failed to import peer ECDSA public key: ${error.message}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Verify ECDSA key self-signature
|
2025-08-21 04:07:16 -04:00
|
|
|
|
const ecdsaPackageCopy = { ...offerData.ecdsaPublicKey };
|
|
|
|
|
|
delete ecdsaPackageCopy.signature;
|
|
|
|
|
|
const ecdsaPackageString = JSON.stringify(ecdsaPackageCopy);
|
|
|
|
|
|
|
|
|
|
|
|
const ecdsaSignatureValid = await window.EnhancedSecureCryptoUtils.verifySignature(
|
|
|
|
|
|
peerECDSAPublicKey,
|
|
|
|
|
|
offerData.ecdsaPublicKey.signature,
|
|
|
|
|
|
ecdsaPackageString
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (!ecdsaSignatureValid) {
|
|
|
|
|
|
this._secureLog('error', 'Invalid ECDSA signature detected - possible MITM attack', {
|
|
|
|
|
|
operationId: operationId,
|
|
|
|
|
|
timestamp: offerData.timestamp,
|
|
|
|
|
|
version: offerData.version
|
|
|
|
|
|
});
|
|
|
|
|
|
throw new Error('Invalid ECDSA key signature – possible MITM attack');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this._secureLog('info', 'ECDSA signature verification passed', {
|
|
|
|
|
|
operationId: operationId,
|
|
|
|
|
|
timestamp: offerData.timestamp,
|
|
|
|
|
|
version: offerData.version
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// PHASE 6: IMPORT AND VERIFY ECDH KEY
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Import and verify ECDH public key using verified ECDSA key
|
2025-08-21 04:07:16 -04:00
|
|
|
|
let peerECDHPublicKey;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
peerECDHPublicKey = await window.EnhancedSecureCryptoUtils.importSignedPublicKey(
|
|
|
|
|
|
offerData.ecdhPublicKey,
|
|
|
|
|
|
peerECDSAPublicKey,
|
|
|
|
|
|
'ECDH'
|
|
|
|
|
|
);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
this._secureLog('error', 'Failed to import signed ECDH public key', {
|
|
|
|
|
|
operationId: operationId,
|
|
|
|
|
|
errorType: error.constructor.name
|
|
|
|
|
|
});
|
|
|
|
|
|
throw new Error(`Failed to import peer ECDH public key: ${error.message}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Final validation of ECDH key
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (!(peerECDHPublicKey instanceof CryptoKey)) {
|
|
|
|
|
|
this._secureLog('error', 'Peer ECDH public key is not a CryptoKey', {
|
|
|
|
|
|
operationId: operationId,
|
|
|
|
|
|
publicKeyType: typeof peerECDHPublicKey,
|
|
|
|
|
|
publicKeyAlgorithm: peerECDHPublicKey?.algorithm?.name
|
|
|
|
|
|
});
|
|
|
|
|
|
throw new Error('Peer ECDH public key is not a valid CryptoKey');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Save peer key for PFS rotation
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this.peerPublicKey = peerECDHPublicKey;
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// PHASE 7: DERIVE SHARED ENCRYPTION KEYS
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Derive shared keys with metadata protection
|
2025-08-21 04:07:16 -04:00
|
|
|
|
let derivedKeys;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
derivedKeys = await window.EnhancedSecureCryptoUtils.deriveSharedKeys(
|
|
|
|
|
|
this.ecdhKeyPair.privateKey,
|
|
|
|
|
|
peerECDHPublicKey,
|
|
|
|
|
|
this.sessionSalt
|
|
|
|
|
|
);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
this._secureLog('error', 'Failed to derive shared keys', {
|
|
|
|
|
|
operationId: operationId,
|
|
|
|
|
|
errorType: error.constructor.name
|
|
|
|
|
|
});
|
|
|
|
|
|
throw new Error(`Key derivation failed: ${error.message}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Securely set keys via helper
|
2025-08-21 04:07:16 -04:00
|
|
|
|
await this._setEncryptionKeys(
|
|
|
|
|
|
derivedKeys.encryptionKey,
|
|
|
|
|
|
derivedKeys.macKey,
|
|
|
|
|
|
derivedKeys.metadataKey,
|
|
|
|
|
|
derivedKeys.fingerprint
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Additional validation of installed keys
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (!(this.encryptionKey instanceof CryptoKey) ||
|
|
|
|
|
|
!(this.macKey instanceof CryptoKey) ||
|
|
|
|
|
|
!(this.metadataKey instanceof CryptoKey)) {
|
|
|
|
|
|
|
|
|
|
|
|
this._secureLog('error', 'Invalid key types after derivation', {
|
|
|
|
|
|
operationId: operationId,
|
|
|
|
|
|
encryptionKeyType: typeof this.encryptionKey,
|
|
|
|
|
|
macKeyType: typeof this.macKey,
|
|
|
|
|
|
metadataKeyType: typeof this.metadataKey
|
|
|
|
|
|
});
|
|
|
|
|
|
throw new Error('Invalid key types after derivation');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Set verification code from offer
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this.verificationCode = offerData.verificationCode;
|
|
|
|
|
|
|
|
|
|
|
|
this._secureLog('info', 'Encryption keys derived and set successfully', {
|
|
|
|
|
|
operationId: operationId,
|
|
|
|
|
|
hasEncryptionKey: !!this.encryptionKey,
|
|
|
|
|
|
hasMacKey: !!this.macKey,
|
|
|
|
|
|
hasMetadataKey: !!this.metadataKey,
|
|
|
|
|
|
hasKeyFingerprint: !!this.keyFingerprint,
|
|
|
|
|
|
mitmProtection: 'enabled',
|
|
|
|
|
|
signatureVerified: true
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// PHASE 8: UPDATE SECURITY FEATURES
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Atomic update of security features
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this._updateSecurityFeatures({
|
|
|
|
|
|
hasEncryption: true,
|
|
|
|
|
|
hasECDH: true,
|
|
|
|
|
|
hasECDSA: true,
|
|
|
|
|
|
hasMutualAuth: true,
|
|
|
|
|
|
hasMetadataProtection: true,
|
|
|
|
|
|
hasEnhancedReplayProtection: true,
|
|
|
|
|
|
hasNonExtractableKeys: true,
|
|
|
|
|
|
hasRateLimiting: true,
|
|
|
|
|
|
hasEnhancedValidation: true,
|
|
|
|
|
|
hasPFS: true
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// PFS: Initialize key version tracking
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this.currentKeyVersion = 0;
|
|
|
|
|
|
this.lastKeyRotation = Date.now();
|
|
|
|
|
|
this.keyVersions.set(0, {
|
|
|
|
|
|
salt: this.sessionSalt,
|
|
|
|
|
|
timestamp: this.lastKeyRotation,
|
|
|
|
|
|
messageCount: 0
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// PHASE 9: CREATE AUTHENTICATION PROOF
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Create proof for mutual authentication
|
2025-08-21 04:07:16 -04:00
|
|
|
|
let authProof;
|
|
|
|
|
|
|
|
|
|
|
|
if (offerData.authChallenge) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
authProof = await window.EnhancedSecureCryptoUtils.createAuthProof(
|
|
|
|
|
|
offerData.authChallenge,
|
|
|
|
|
|
this.ecdsaKeyPair.privateKey,
|
|
|
|
|
|
this.ecdsaKeyPair.publicKey
|
|
|
|
|
|
);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
this._secureLog('error', 'Failed to create authentication proof', {
|
|
|
|
|
|
operationId: operationId,
|
|
|
|
|
|
errorType: error.constructor.name
|
|
|
|
|
|
});
|
|
|
|
|
|
throw new Error(`Authentication proof creation failed: ${error.message}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
this._secureLog('warn', 'No auth challenge in offer - mutual auth disabled', {
|
|
|
|
|
|
operationId: operationId
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// PHASE 10: INITIALIZE WEBRTC
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
|
|
this.isInitiator = false;
|
|
|
|
|
|
this.onStatusChange('connecting');
|
|
|
|
|
|
this.onKeyExchange(this.keyFingerprint);
|
|
|
|
|
|
this.onVerificationRequired(this.verificationCode);
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Create peer connection
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this.createPeerConnection();
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Set remote description from offer
|
2025-08-21 04:07:16 -04:00
|
|
|
|
try {
|
|
|
|
|
|
await this.peerConnection.setRemoteDescription(new RTCSessionDescription({
|
|
|
|
|
|
type: 'offer',
|
|
|
|
|
|
sdp: offerData.sdp
|
|
|
|
|
|
}));
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
throw new Error(`Failed to set remote description: ${error.message}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this._secureLog('debug', '🔗 Remote description set successfully', {
|
|
|
|
|
|
operationId: operationId,
|
|
|
|
|
|
connectionState: this.peerConnection.connectionState,
|
|
|
|
|
|
signalingState: this.peerConnection.signalingState
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// PHASE 11: CREATE SDP ANSWER
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Create WebRTC answer
|
2025-08-21 04:07:16 -04:00
|
|
|
|
let answer;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
answer = await this.peerConnection.createAnswer({
|
|
|
|
|
|
offerToReceiveAudio: false,
|
|
|
|
|
|
offerToReceiveVideo: false
|
|
|
|
|
|
});
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
throw new Error(`Failed to create answer: ${error.message}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Set local description
|
2025-08-21 04:07:16 -04:00
|
|
|
|
try {
|
|
|
|
|
|
await this.peerConnection.setLocalDescription(answer);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
throw new Error(`Failed to set local description: ${error.message}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Await ICE gathering
|
2025-08-21 04:07:16 -04:00
|
|
|
|
await this.waitForIceGathering();
|
|
|
|
|
|
|
|
|
|
|
|
this._secureLog('debug', '🧊 ICE gathering completed for answer', {
|
|
|
|
|
|
operationId: operationId,
|
|
|
|
|
|
iceGatheringState: this.peerConnection.iceGatheringState,
|
|
|
|
|
|
connectionState: this.peerConnection.connectionState
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// PHASE 12: EXPORT OUR KEYS
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Export our keys with signatures
|
2025-08-21 04:07:16 -04:00
|
|
|
|
const ecdhPublicKeyData = await window.EnhancedSecureCryptoUtils.exportPublicKeyWithSignature(
|
|
|
|
|
|
this.ecdhKeyPair.publicKey,
|
|
|
|
|
|
this.ecdsaKeyPair.privateKey,
|
|
|
|
|
|
'ECDH'
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
const ecdsaPublicKeyData = await window.EnhancedSecureCryptoUtils.exportPublicKeyWithSignature(
|
|
|
|
|
|
this.ecdsaKeyPair.publicKey,
|
|
|
|
|
|
this.ecdsaKeyPair.privateKey,
|
|
|
|
|
|
'ECDSA'
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Validate exported data
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (!ecdhPublicKeyData?.keyData || !ecdhPublicKeyData?.signature) {
|
|
|
|
|
|
throw new Error('Failed to export ECDH public key with signature');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!ecdsaPublicKeyData?.keyData || !ecdsaPublicKeyData?.signature) {
|
|
|
|
|
|
throw new Error('Failed to export ECDSA public key with signature');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// PHASE 13: SECURITY LEVEL CALCULATION
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Calculate security level
|
2025-08-21 04:07:16 -04:00
|
|
|
|
let securityLevel;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
securityLevel = await this.calculateSecurityLevel();
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
this._secureLog('warn', '⚠️ Security level calculation failed, using fallback', {
|
|
|
|
|
|
operationId: operationId,
|
|
|
|
|
|
errorType: error.constructor.name
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Fallback value
|
2025-08-21 04:07:16 -04:00
|
|
|
|
securityLevel = {
|
|
|
|
|
|
level: 'enhanced',
|
|
|
|
|
|
score: 80,
|
|
|
|
|
|
passedChecks: 12,
|
|
|
|
|
|
totalChecks: 15,
|
|
|
|
|
|
isRealData: false
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// PHASE 14: CREATE ANSWER PACKAGE
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
|
|
const currentTimestamp = Date.now();
|
|
|
|
|
|
|
|
|
|
|
|
const answerPackage = {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Core information
|
2025-08-21 04:07:16 -04:00
|
|
|
|
type: 'enhanced_secure_answer',
|
|
|
|
|
|
sdp: this.peerConnection.localDescription.sdp,
|
|
|
|
|
|
version: '4.0',
|
|
|
|
|
|
timestamp: currentTimestamp,
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Cryptographic keys
|
2025-08-21 04:07:16 -04:00
|
|
|
|
ecdhPublicKey: ecdhPublicKeyData,
|
|
|
|
|
|
ecdsaPublicKey: ecdsaPublicKeyData,
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Authentication
|
2025-08-21 04:07:16 -04:00
|
|
|
|
authProof: authProof,
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Security metadata
|
2025-08-21 04:07:16 -04:00
|
|
|
|
securityLevel: securityLevel,
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Additional security fields
|
2025-08-21 04:07:16 -04:00
|
|
|
|
sessionConfirmation: {
|
|
|
|
|
|
saltFingerprint: saltFingerprint.substring(0, 16),
|
|
|
|
|
|
keyDerivationSuccess: true,
|
|
|
|
|
|
mutualAuthEnabled: !!authProof
|
|
|
|
|
|
},
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Answerer capabilities
|
2025-08-21 04:07:16 -04:00
|
|
|
|
capabilities: {
|
|
|
|
|
|
supportsFileTransfer: true,
|
|
|
|
|
|
supportsEnhancedSecurity: true,
|
|
|
|
|
|
supportsKeyRotation: true,
|
|
|
|
|
|
supportsFakeTraffic: this.fakeTrafficConfig.enabled,
|
|
|
|
|
|
supportsDecoyChannels: this.decoyChannelConfig.enabled,
|
|
|
|
|
|
protocolVersion: '4.0'
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// PHASE 15: VALIDATION AND LOGGING
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Final validation of the answer package
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (!answerPackage.sdp || !answerPackage.ecdhPublicKey || !answerPackage.ecdsaPublicKey) {
|
|
|
|
|
|
throw new Error('Generated answer package is incomplete');
|
2025-08-16 20:58:42 -04:00
|
|
|
|
}
|
2025-08-21 04:07:16 -04:00
|
|
|
|
|
|
|
|
|
|
this._secureLog('info', 'Enhanced secure answer created successfully', {
|
|
|
|
|
|
operationId: operationId,
|
|
|
|
|
|
version: answerPackage.version,
|
|
|
|
|
|
hasECDSA: true,
|
|
|
|
|
|
hasMutualAuth: !!authProof,
|
|
|
|
|
|
hasSessionConfirmation: !!answerPackage.sessionConfirmation,
|
|
|
|
|
|
securityLevel: securityLevel.level,
|
|
|
|
|
|
timestamp: currentTimestamp,
|
|
|
|
|
|
processingTime: currentTimestamp - offerData.timestamp
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Dispatch event about new connection
|
2025-08-21 04:07:16 -04:00
|
|
|
|
document.dispatchEvent(new CustomEvent('new-connection', {
|
|
|
|
|
|
detail: {
|
|
|
|
|
|
type: 'answer',
|
|
|
|
|
|
timestamp: currentTimestamp,
|
|
|
|
|
|
securityLevel: securityLevel.level,
|
|
|
|
|
|
operationId: operationId
|
|
|
|
|
|
}
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// PHASE 16: SCHEDULE SECURITY CALCULATIONS
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Plan security calculation after connection
|
2025-08-21 04:07:16 -04:00
|
|
|
|
setTimeout(async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const realSecurityData = await this.calculateAndReportSecurityLevel();
|
|
|
|
|
|
if (realSecurityData) {
|
|
|
|
|
|
this.notifySecurityUpdate();
|
|
|
|
|
|
this._secureLog('info', '✅ Post-connection security level calculated', {
|
|
|
|
|
|
operationId: operationId,
|
|
|
|
|
|
level: realSecurityData.level
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
this._secureLog('error', '❌ Error calculating post-connection security', {
|
|
|
|
|
|
operationId: operationId,
|
|
|
|
|
|
errorType: error.constructor.name
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 1000);
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Retry if the first calculation fails
|
2025-08-21 04:07:16 -04:00
|
|
|
|
setTimeout(async () => {
|
|
|
|
|
|
if (!this.lastSecurityCalculation || this.lastSecurityCalculation.score < 50) {
|
|
|
|
|
|
this._secureLog('info', '🔄 Retrying security calculation', {
|
|
|
|
|
|
operationId: operationId
|
|
|
|
|
|
});
|
|
|
|
|
|
await this.calculateAndReportSecurityLevel();
|
|
|
|
|
|
this.notifySecurityUpdate();
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 3000);
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Final security update
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this.notifySecurityUpdate();
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// PHASE 17: RETURN RESULT
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
|
|
return answerPackage;
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
// ============================================
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// ERROR HANDLING
|
2025-08-21 04:07:16 -04:00
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
|
|
this._secureLog('error', '❌ Enhanced secure answer creation failed in critical section', {
|
|
|
|
|
|
operationId: operationId,
|
|
|
|
|
|
errorType: error.constructor.name,
|
|
|
|
|
|
errorMessage: error.message,
|
|
|
|
|
|
phase: this._determineAnswerErrorPhase(error),
|
|
|
|
|
|
offerAge: offerData?.timestamp ? Date.now() - offerData.timestamp : 'unknown'
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Cleanup state on error
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this._cleanupFailedAnswerCreation();
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Update status
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this.onStatusChange('disconnected');
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Special handling of security errors
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (this.onAnswerError) {
|
|
|
|
|
|
if (error.message.includes('too old') || error.message.includes('replay')) {
|
|
|
|
|
|
this.onAnswerError('replay_attack', error.message);
|
|
|
|
|
|
} else if (error.message.includes('MITM') || error.message.includes('signature')) {
|
|
|
|
|
|
this.onAnswerError('security_violation', error.message);
|
|
|
|
|
|
} else if (error.message.includes('validation') || error.message.includes('format')) {
|
|
|
|
|
|
this.onAnswerError('invalid_format', error.message);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
this.onAnswerError('general_error', error.message);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Re-throw for upper-level handling
|
2025-08-21 04:07:16 -04:00
|
|
|
|
throw error;
|
|
|
|
|
|
}
|
2025-08-21 05:16:41 -04:00
|
|
|
|
}, 20000); // 20 seconds timeout for the entire answer creation (longer than offer)
|
2025-08-21 04:07:16 -04:00
|
|
|
|
}
|
2025-08-16 20:58:42 -04:00
|
|
|
|
|
2025-08-21 04:07:16 -04:00
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* HELPER: Determine error phase for answer
|
2025-08-21 04:07:16 -04:00
|
|
|
|
*/
|
|
|
|
|
|
_determineAnswerErrorPhase(error) {
|
|
|
|
|
|
const message = error.message.toLowerCase();
|
|
|
|
|
|
|
|
|
|
|
|
if (message.includes('validation') || message.includes('format')) return 'offer_validation';
|
|
|
|
|
|
if (message.includes('rate limit')) return 'rate_limiting';
|
|
|
|
|
|
if (message.includes('replay') || message.includes('too old')) return 'replay_protection';
|
|
|
|
|
|
if (message.includes('salt')) return 'salt_validation';
|
|
|
|
|
|
if (message.includes('key pair') || message.includes('generate')) return 'key_generation';
|
|
|
|
|
|
if (message.includes('import') || message.includes('ecdsa') || message.includes('ecdh')) return 'key_import';
|
|
|
|
|
|
if (message.includes('signature') || message.includes('mitm')) return 'signature_verification';
|
|
|
|
|
|
if (message.includes('derive') || message.includes('shared')) return 'key_derivation';
|
|
|
|
|
|
if (message.includes('auth') || message.includes('proof')) return 'authentication';
|
|
|
|
|
|
if (message.includes('remote description') || message.includes('local description')) return 'webrtc_setup';
|
|
|
|
|
|
if (message.includes('answer') || message.includes('sdp')) return 'sdp_creation';
|
|
|
|
|
|
if (message.includes('export')) return 'key_export';
|
|
|
|
|
|
if (message.includes('security level')) return 'security_calculation';
|
|
|
|
|
|
|
|
|
|
|
|
return 'unknown';
|
2025-08-11 20:52:14 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 04:07:16 -04:00
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* HELPER: Cleanup state after failed answer creation
|
2025-08-21 04:07:16 -04:00
|
|
|
|
*/
|
|
|
|
|
|
_cleanupFailedAnswerCreation() {
|
2025-08-11 20:52:14 -04:00
|
|
|
|
try {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Clear keys and session data
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this.ecdhKeyPair = null;
|
|
|
|
|
|
this.ecdsaKeyPair = null;
|
|
|
|
|
|
this.peerPublicKey = null;
|
|
|
|
|
|
this.sessionSalt = null;
|
|
|
|
|
|
this.verificationCode = null;
|
|
|
|
|
|
this.encryptionKey = null;
|
|
|
|
|
|
this.macKey = null;
|
|
|
|
|
|
this.metadataKey = null;
|
|
|
|
|
|
this.keyFingerprint = null;
|
2025-08-11 20:52:14 -04:00
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Reset PFS key versions
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this.currentKeyVersion = 0;
|
|
|
|
|
|
this.keyVersions.clear();
|
|
|
|
|
|
this.oldKeys.clear();
|
2025-08-11 20:52:14 -04:00
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Close peer connection if created
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (this.peerConnection) {
|
|
|
|
|
|
this.peerConnection.close();
|
|
|
|
|
|
this.peerConnection = null;
|
2025-08-11 20:52:14 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Clear data channel
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (this.dataChannel) {
|
|
|
|
|
|
this.dataChannel.close();
|
|
|
|
|
|
this.dataChannel = null;
|
2025-08-11 20:52:14 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Reset flags and counters
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this.isInitiator = false;
|
|
|
|
|
|
this.isVerified = false;
|
2025-08-11 20:52:14 -04:00
|
|
|
|
this.sequenceNumber = 0;
|
|
|
|
|
|
this.expectedSequenceNumber = 0;
|
|
|
|
|
|
this.messageCounter = 0;
|
|
|
|
|
|
this.processedMessageIds.clear();
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Reset security features to baseline
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this._updateSecurityFeatures({
|
|
|
|
|
|
hasEncryption: false,
|
|
|
|
|
|
hasECDH: false,
|
|
|
|
|
|
hasECDSA: false,
|
|
|
|
|
|
hasMutualAuth: false,
|
|
|
|
|
|
hasMetadataProtection: false,
|
|
|
|
|
|
hasEnhancedReplayProtection: false,
|
|
|
|
|
|
hasNonExtractableKeys: false,
|
|
|
|
|
|
hasEnhancedValidation: false,
|
|
|
|
|
|
hasPFS: false
|
2025-08-11 20:52:14 -04:00
|
|
|
|
});
|
|
|
|
|
|
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this._secureLog('debug', '🧹 Failed answer creation cleanup completed');
|
2025-08-11 20:52:14 -04:00
|
|
|
|
|
2025-08-21 04:07:16 -04:00
|
|
|
|
} catch (cleanupError) {
|
|
|
|
|
|
this._secureLog('error', '❌ Error during answer creation cleanup', {
|
|
|
|
|
|
errorType: cleanupError.constructor.name
|
2025-08-11 20:52:14 -04:00
|
|
|
|
});
|
2025-08-21 04:07:16 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-11 20:52:14 -04:00
|
|
|
|
|
2025-08-21 04:07:16 -04:00
|
|
|
|
/**
|
2025-08-21 05:16:41 -04:00
|
|
|
|
* HELPER: Securely set encryption keys (if not set yet)
|
2025-08-21 04:07:16 -04:00
|
|
|
|
*/
|
|
|
|
|
|
async _setEncryptionKeys(encryptionKey, macKey, metadataKey, keyFingerprint) {
|
|
|
|
|
|
return this._withMutex('keyOperation', async (operationId) => {
|
|
|
|
|
|
this._secureLog('info', '🔐 Setting encryption keys with mutex', {
|
|
|
|
|
|
operationId: operationId
|
|
|
|
|
|
});
|
2025-08-11 20:52:14 -04:00
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Validate all keys before setting
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (!(encryptionKey instanceof CryptoKey) ||
|
|
|
|
|
|
!(macKey instanceof CryptoKey) ||
|
|
|
|
|
|
!(metadataKey instanceof CryptoKey)) {
|
|
|
|
|
|
throw new Error('Invalid key types provided');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!keyFingerprint || typeof keyFingerprint !== 'string') {
|
|
|
|
|
|
throw new Error('Invalid key fingerprint provided');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Atomically set all keys
|
2025-08-21 04:07:16 -04:00
|
|
|
|
const oldKeys = {
|
|
|
|
|
|
encryptionKey: this.encryptionKey,
|
|
|
|
|
|
macKey: this.macKey,
|
|
|
|
|
|
metadataKey: this.metadataKey,
|
|
|
|
|
|
keyFingerprint: this.keyFingerprint
|
2025-08-11 20:52:14 -04:00
|
|
|
|
};
|
2025-08-21 04:07:16 -04:00
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
this.encryptionKey = encryptionKey;
|
|
|
|
|
|
this.macKey = macKey;
|
|
|
|
|
|
this.metadataKey = metadataKey;
|
|
|
|
|
|
this.keyFingerprint = keyFingerprint;
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Reset counters
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this.sequenceNumber = 0;
|
|
|
|
|
|
this.expectedSequenceNumber = 0;
|
|
|
|
|
|
this.messageCounter = 0;
|
|
|
|
|
|
this.processedMessageIds.clear();
|
|
|
|
|
|
|
|
|
|
|
|
this._secureLog('info', '✅ Encryption keys set successfully', {
|
|
|
|
|
|
operationId: operationId,
|
|
|
|
|
|
hasAllKeys: !!(this.encryptionKey && this.macKey && this.metadataKey),
|
|
|
|
|
|
hasFingerprint: !!this.keyFingerprint
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Roll back on error
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this.encryptionKey = oldKeys.encryptionKey;
|
|
|
|
|
|
this.macKey = oldKeys.macKey;
|
|
|
|
|
|
this.metadataKey = oldKeys.metadataKey;
|
|
|
|
|
|
this.keyFingerprint = oldKeys.keyFingerprint;
|
|
|
|
|
|
|
|
|
|
|
|
this._secureLog('error', '❌ Key setting failed, rolled back', {
|
|
|
|
|
|
operationId: operationId,
|
|
|
|
|
|
errorType: error.constructor.name
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
throw error;
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
2025-08-11 20:52:14 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async handleSecureAnswer(answerData) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (!answerData || answerData.type !== 'enhanced_secure_answer' || !answerData.sdp) {
|
2025-08-13 14:48:24 -04:00
|
|
|
|
throw new Error('Invalid response format');
|
2025-08-11 20:52:14 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Import peer's ECDH public key from the signed package
|
|
|
|
|
|
if (!answerData.ecdhPublicKey || !answerData.ecdhPublicKey.keyData) {
|
2025-08-13 14:48:24 -04:00
|
|
|
|
throw new Error('Missing ECDH public key data');
|
2025-08-11 20:52:14 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// First, import and verify the ECDSA public key for signature verification
|
|
|
|
|
|
if (!answerData.ecdsaPublicKey || !answerData.ecdsaPublicKey.keyData) {
|
2025-08-13 14:48:24 -04:00
|
|
|
|
throw new Error('Missing ECDSA public key data for signature verification');
|
2025-08-11 20:52:14 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Additional MITM protection: Validate answer data structure
|
|
|
|
|
|
if (!answerData.timestamp || !answerData.version) {
|
2025-08-13 14:48:24 -04:00
|
|
|
|
throw new Error('Missing required fields in response data – possible MITM attack');
|
2025-08-11 20:52:14 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// MITM Protection: Verify session ID if present (for enhanced security)
|
|
|
|
|
|
if (answerData.sessionId && this.sessionId && answerData.sessionId !== this.sessionId) {
|
|
|
|
|
|
window.EnhancedSecureCryptoUtils.secureLog.log('error', 'Session ID mismatch detected - possible MITM attack', {
|
|
|
|
|
|
expectedSessionId: this.sessionId,
|
|
|
|
|
|
receivedSessionId: answerData.sessionId
|
|
|
|
|
|
});
|
2025-08-13 14:48:24 -04:00
|
|
|
|
throw new Error('Session ID mismatch – possible MITM attack');
|
2025-08-11 20:52:14 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Check for replay attacks (reject answers older than 1 hour)
|
|
|
|
|
|
const answerAge = Date.now() - answerData.timestamp;
|
|
|
|
|
|
if (answerAge > 3600000) { // 1 hour in milliseconds
|
|
|
|
|
|
window.EnhancedSecureCryptoUtils.secureLog.log('error', 'Answer data is too old - possible replay attack', {
|
|
|
|
|
|
answerAge: answerAge,
|
|
|
|
|
|
timestamp: answerData.timestamp
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-08-14 23:34:54 -04:00
|
|
|
|
// Notify the main code about the replay attack error
|
2025-08-11 20:52:14 -04:00
|
|
|
|
if (this.onAnswerError) {
|
2025-08-13 14:48:24 -04:00
|
|
|
|
this.onAnswerError('replay_attack', 'Response data is too old – possible replay attack');
|
2025-08-11 20:52:14 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-13 14:48:24 -04:00
|
|
|
|
throw new Error('Response data is too old – possible replay attack');
|
2025-08-11 20:52:14 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Check protocol version compatibility
|
|
|
|
|
|
if (answerData.version !== '4.0') {
|
|
|
|
|
|
window.EnhancedSecureCryptoUtils.secureLog.log('warn', 'Incompatible protocol version in answer', {
|
|
|
|
|
|
expectedVersion: '4.0',
|
|
|
|
|
|
receivedVersion: answerData.version
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Import ECDSA public key for verification (self-signed)
|
|
|
|
|
|
const peerECDSAPublicKey = await crypto.subtle.importKey(
|
|
|
|
|
|
'spki',
|
|
|
|
|
|
new Uint8Array(answerData.ecdsaPublicKey.keyData),
|
|
|
|
|
|
{
|
|
|
|
|
|
name: 'ECDSA',
|
|
|
|
|
|
namedCurve: 'P-384'
|
|
|
|
|
|
},
|
|
|
|
|
|
false,
|
|
|
|
|
|
['verify']
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// Verify ECDSA key's self-signature
|
|
|
|
|
|
const ecdsaPackageCopy = { ...answerData.ecdsaPublicKey };
|
|
|
|
|
|
delete ecdsaPackageCopy.signature;
|
|
|
|
|
|
const ecdsaPackageString = JSON.stringify(ecdsaPackageCopy);
|
|
|
|
|
|
const ecdsaSignatureValid = await window.EnhancedSecureCryptoUtils.verifySignature(
|
|
|
|
|
|
peerECDSAPublicKey,
|
|
|
|
|
|
answerData.ecdsaPublicKey.signature,
|
|
|
|
|
|
ecdsaPackageString
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (!ecdsaSignatureValid) {
|
|
|
|
|
|
window.EnhancedSecureCryptoUtils.secureLog.log('error', 'Invalid ECDSA signature detected - possible MITM attack', {
|
|
|
|
|
|
timestamp: answerData.timestamp,
|
|
|
|
|
|
version: answerData.version
|
|
|
|
|
|
});
|
2025-08-13 14:48:24 -04:00
|
|
|
|
throw new Error('Invalid ECDSA key signature – possible MITM attack');
|
2025-08-11 20:52:14 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
window.EnhancedSecureCryptoUtils.secureLog.log('info', 'ECDSA signature verification passed', {
|
|
|
|
|
|
timestamp: answerData.timestamp,
|
|
|
|
|
|
version: answerData.version
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Now import and verify the ECDH public key using the verified ECDSA key
|
|
|
|
|
|
const peerPublicKey = await window.EnhancedSecureCryptoUtils.importPublicKeyFromSignedPackage(
|
|
|
|
|
|
answerData.ecdhPublicKey,
|
|
|
|
|
|
peerECDSAPublicKey
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// Additional MITM protection: Verify session salt integrity
|
|
|
|
|
|
if (!this.sessionSalt || this.sessionSalt.length !== 64) {
|
|
|
|
|
|
window.EnhancedSecureCryptoUtils.secureLog.log('error', 'Invalid session salt detected - possible session hijacking', {
|
|
|
|
|
|
saltLength: this.sessionSalt ? this.sessionSalt.length : 0
|
|
|
|
|
|
});
|
2025-08-13 14:48:24 -04:00
|
|
|
|
throw new Error('Invalid session salt – possible session hijacking attempt');
|
2025-08-11 20:52:14 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Verify that the session salt hasn't been tampered with
|
|
|
|
|
|
const expectedSaltHash = await window.EnhancedSecureCryptoUtils.calculateKeyFingerprint(this.sessionSalt);
|
|
|
|
|
|
window.EnhancedSecureCryptoUtils.secureLog.log('info', 'Session salt integrity verified', {
|
|
|
|
|
|
saltFingerprint: expectedSaltHash.substring(0, 8)
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Additional validation: Ensure all keys are CryptoKey instances before derivation
|
|
|
|
|
|
if (!(this.ecdhKeyPair?.privateKey instanceof CryptoKey)) {
|
|
|
|
|
|
window.EnhancedSecureCryptoUtils.secureLog.log('error', 'Local ECDH private key is not a CryptoKey in handleSecureAnswer', {
|
|
|
|
|
|
hasKeyPair: !!this.ecdhKeyPair,
|
|
|
|
|
|
privateKeyType: typeof this.ecdhKeyPair?.privateKey,
|
|
|
|
|
|
privateKeyAlgorithm: this.ecdhKeyPair?.privateKey?.algorithm?.name
|
|
|
|
|
|
});
|
2025-08-13 14:48:24 -04:00
|
|
|
|
throw new Error('Local ECDH private key is not a CryptoKey');
|
2025-08-11 20:52:14 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!(peerPublicKey instanceof CryptoKey)) {
|
|
|
|
|
|
window.EnhancedSecureCryptoUtils.secureLog.log('error', 'Peer ECDH public key is not a CryptoKey in handleSecureAnswer', {
|
|
|
|
|
|
publicKeyType: typeof peerPublicKey,
|
|
|
|
|
|
publicKeyAlgorithm: peerPublicKey?.algorithm?.name
|
|
|
|
|
|
});
|
2025-08-13 14:48:24 -04:00
|
|
|
|
throw new Error('Peer ECDH public key is not a CryptoKey');
|
2025-08-11 20:52:14 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Store peer's public key for PFS key rotation
|
|
|
|
|
|
this.peerPublicKey = peerPublicKey;
|
|
|
|
|
|
|
2025-08-24 16:30:06 -04:00
|
|
|
|
// ✅ ДОБАВИТЬ: Проверка DTLS защиты перед генерацией ключей
|
|
|
|
|
|
if (this.dtlsProtectionEnabled) {
|
|
|
|
|
|
// Имитируем проверку DTLS ClientHello (в реальном WebRTC это происходит автоматически)
|
|
|
|
|
|
const mockClientHelloData = {
|
|
|
|
|
|
cipherSuite: 'TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256',
|
|
|
|
|
|
tlsVersion: '1.3'
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Получаем endpoint из peer connection
|
|
|
|
|
|
const localEndpoint = this.peerConnection?.localDescription?.sdp || 'local-endpoint';
|
|
|
|
|
|
const remoteEndpoint = this.peerConnection?.remoteDescription?.sdp || 'remote-endpoint';
|
|
|
|
|
|
|
|
|
|
|
|
// Добавляем endpoints в верифицированные
|
|
|
|
|
|
this.addVerifiedICEEndpoint(localEndpoint);
|
|
|
|
|
|
this.addVerifiedICEEndpoint(remoteEndpoint);
|
|
|
|
|
|
|
|
|
|
|
|
// Валидируем DTLS источник
|
|
|
|
|
|
await this.validateDTLSSource(mockClientHelloData, remoteEndpoint);
|
|
|
|
|
|
|
|
|
|
|
|
this._secureLog('info', 'DTLS protection validated before key derivation', {
|
|
|
|
|
|
localEndpoint: localEndpoint.substring(0, 50),
|
|
|
|
|
|
remoteEndpoint: remoteEndpoint.substring(0, 50),
|
|
|
|
|
|
verifiedEndpoints: this.verifiedICEEndpoints.size
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-11 20:52:14 -04:00
|
|
|
|
const derivedKeys = await window.EnhancedSecureCryptoUtils.deriveSharedKeys(
|
|
|
|
|
|
this.ecdhKeyPair.privateKey,
|
|
|
|
|
|
peerPublicKey,
|
|
|
|
|
|
this.sessionSalt
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
this.encryptionKey = derivedKeys.encryptionKey;
|
|
|
|
|
|
this.macKey = derivedKeys.macKey;
|
|
|
|
|
|
this.metadataKey = derivedKeys.metadataKey;
|
|
|
|
|
|
this.keyFingerprint = derivedKeys.fingerprint;
|
|
|
|
|
|
this.sequenceNumber = 0;
|
|
|
|
|
|
this.expectedSequenceNumber = 0;
|
|
|
|
|
|
this.messageCounter = 0;
|
|
|
|
|
|
this.processedMessageIds.clear();
|
|
|
|
|
|
// Validate that all keys are properly set
|
|
|
|
|
|
if (!(this.encryptionKey instanceof CryptoKey) ||
|
|
|
|
|
|
!(this.macKey instanceof CryptoKey) ||
|
|
|
|
|
|
!(this.metadataKey instanceof CryptoKey)) {
|
|
|
|
|
|
window.EnhancedSecureCryptoUtils.secureLog.log('error', 'Invalid key types after derivation in handleSecureAnswer', {
|
|
|
|
|
|
encryptionKeyType: typeof this.encryptionKey,
|
|
|
|
|
|
macKeyType: typeof this.macKey,
|
|
|
|
|
|
metadataKeyType: typeof this.metadataKey,
|
|
|
|
|
|
encryptionKeyAlgorithm: this.encryptionKey?.algorithm?.name,
|
|
|
|
|
|
macKeyAlgorithm: this.macKey?.algorithm?.name,
|
|
|
|
|
|
metadataKeyAlgorithm: this.metadataKey?.algorithm?.name
|
|
|
|
|
|
});
|
2025-08-13 14:48:24 -04:00
|
|
|
|
throw new Error('Invalid key types after export');
|
2025-08-11 20:52:14 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this._secureLog('info', 'Encryption keys set in handleSecureAnswer', {
|
2025-08-11 20:52:14 -04:00
|
|
|
|
hasEncryptionKey: !!this.encryptionKey,
|
|
|
|
|
|
hasMacKey: !!this.macKey,
|
|
|
|
|
|
hasMetadataKey: !!this.metadataKey,
|
2025-08-21 04:07:16 -04:00
|
|
|
|
hasKeyFingerprint: !!this.keyFingerprint,
|
2025-08-11 20:52:14 -04:00
|
|
|
|
mitmProtection: 'enabled',
|
2025-08-21 04:07:16 -04:00
|
|
|
|
signatureVerified: true
|
2025-08-11 20:52:14 -04:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Update security features for initiator after successful key exchange
|
|
|
|
|
|
this.securityFeatures.hasMutualAuth = true;
|
|
|
|
|
|
this.securityFeatures.hasMetadataProtection = true;
|
|
|
|
|
|
this.securityFeatures.hasEnhancedReplayProtection = true;
|
|
|
|
|
|
this.securityFeatures.hasPFS = true;
|
|
|
|
|
|
|
|
|
|
|
|
// PFS: Initialize key version tracking
|
|
|
|
|
|
this.currentKeyVersion = 0;
|
|
|
|
|
|
this.lastKeyRotation = Date.now();
|
|
|
|
|
|
this.keyVersions.set(0, {
|
|
|
|
|
|
salt: this.sessionSalt,
|
|
|
|
|
|
timestamp: this.lastKeyRotation,
|
|
|
|
|
|
messageCount: 0
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
this.onKeyExchange(this.keyFingerprint);
|
|
|
|
|
|
|
|
|
|
|
|
await this.peerConnection.setRemoteDescription({
|
|
|
|
|
|
type: 'answer',
|
|
|
|
|
|
sdp: answerData.sdp
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
console.log('Enhanced secure connection established');
|
2025-08-17 20:38:47 -04:00
|
|
|
|
|
|
|
|
|
|
setTimeout(async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const securityData = await this.calculateAndReportSecurityLevel();
|
|
|
|
|
|
if (securityData) {
|
|
|
|
|
|
console.log('✅ Security level calculated after connection:', securityData.level);
|
|
|
|
|
|
this.notifySecurityUpdate();
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ Error calculating security after connection:', { errorType: error?.constructor?.name || 'Unknown' });
|
2025-08-17 20:38:47 -04:00
|
|
|
|
}
|
|
|
|
|
|
}, 1000);
|
|
|
|
|
|
setTimeout(async () => {
|
|
|
|
|
|
if (!this.lastSecurityCalculation || this.lastSecurityCalculation.score < 50) {
|
|
|
|
|
|
console.log('🔄 Retrying security calculation...');
|
|
|
|
|
|
await this.calculateAndReportSecurityLevel();
|
|
|
|
|
|
this.notifySecurityUpdate();
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 3000);
|
|
|
|
|
|
this.notifySecurityUpdate();
|
2025-08-11 20:52:14 -04:00
|
|
|
|
} catch (error) {
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this._secureLog('error', 'Enhanced secure answer handling failed', {
|
|
|
|
|
|
errorType: error.constructor.name
|
|
|
|
|
|
});
|
2025-08-11 20:52:14 -04:00
|
|
|
|
this.onStatusChange('failed');
|
2025-08-13 14:48:24 -04:00
|
|
|
|
|
2025-08-11 20:52:14 -04:00
|
|
|
|
if (this.onAnswerError) {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
if (error.message.includes('too old') || error.message.includes('слишком старые')) {
|
2025-08-11 20:52:14 -04:00
|
|
|
|
this.onAnswerError('replay_attack', error.message);
|
2025-08-21 05:16:41 -04:00
|
|
|
|
} else if (error.message.includes('MITM') || error.message.includes('signature') || error.message.includes('подпись')) {
|
2025-08-11 20:52:14 -04:00
|
|
|
|
this.onAnswerError('security_violation', error.message);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
this.onAnswerError('general_error', error.message);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
throw error;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-17 20:38:47 -04:00
|
|
|
|
forceSecurityUpdate() {
|
|
|
|
|
|
console.log('🔄 Force security update requested');
|
|
|
|
|
|
setTimeout(async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const securityData = await this.calculateAndReportSecurityLevel();
|
|
|
|
|
|
if (securityData) {
|
|
|
|
|
|
this.notifySecurityUpdate();
|
|
|
|
|
|
console.log('✅ Force security update completed');
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ Force security update failed:', { errorType: error?.constructor?.name || 'Unknown' });
|
2025-08-17 20:38:47 -04:00
|
|
|
|
}
|
|
|
|
|
|
}, 100);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-11 20:52:14 -04:00
|
|
|
|
initiateVerification() {
|
|
|
|
|
|
if (this.isInitiator) {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Ensure verification initiation notice wasn't already sent
|
2025-08-20 18:19:42 -04:00
|
|
|
|
if (!this.verificationInitiationSent) {
|
|
|
|
|
|
this.verificationInitiationSent = true;
|
|
|
|
|
|
this.deliverMessageToUI('🔐 Confirm the security code with your peer to complete the connection', 'system');
|
|
|
|
|
|
}
|
2025-08-11 20:52:14 -04:00
|
|
|
|
} else {
|
|
|
|
|
|
// Responder confirms verification automatically if codes match
|
|
|
|
|
|
this.confirmVerification();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
confirmVerification() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const verificationPayload = {
|
|
|
|
|
|
type: 'verification',
|
|
|
|
|
|
data: {
|
|
|
|
|
|
code: this.verificationCode,
|
|
|
|
|
|
timestamp: Date.now()
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
this.dataChannel.send(JSON.stringify(verificationPayload));
|
|
|
|
|
|
this.isVerified = true;
|
|
|
|
|
|
this.onStatusChange('connected');
|
2025-08-20 18:19:42 -04:00
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Ensure verification success notice wasn't already sent
|
2025-08-20 18:19:42 -04:00
|
|
|
|
if (!this.verificationNotificationSent) {
|
|
|
|
|
|
this.verificationNotificationSent = true;
|
|
|
|
|
|
this.deliverMessageToUI('✅ Verification successful. The channel is now secure!', 'system');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-11 20:52:14 -04:00
|
|
|
|
this.processMessageQueue();
|
|
|
|
|
|
} catch (error) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ Verification failed:', { errorType: error?.constructor?.name || 'Unknown' });
|
2025-08-20 18:19:42 -04:00
|
|
|
|
this.deliverMessageToUI('❌ Verification failed', 'system');
|
2025-08-11 20:52:14 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
handleVerificationRequest(data) {
|
|
|
|
|
|
if (data.code === this.verificationCode) {
|
|
|
|
|
|
const responsePayload = {
|
|
|
|
|
|
type: 'verification_response',
|
|
|
|
|
|
data: {
|
|
|
|
|
|
verified: true,
|
|
|
|
|
|
timestamp: Date.now()
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
this.dataChannel.send(JSON.stringify(responsePayload));
|
|
|
|
|
|
this.isVerified = true;
|
|
|
|
|
|
this.onStatusChange('connected');
|
2025-08-20 18:19:42 -04:00
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Ensure verification success notice wasn't already sent
|
2025-08-20 18:19:42 -04:00
|
|
|
|
if (!this.verificationNotificationSent) {
|
|
|
|
|
|
this.verificationNotificationSent = true;
|
|
|
|
|
|
this.deliverMessageToUI('✅ Verification successful. The channel is now secure!', 'system');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-11 20:52:14 -04:00
|
|
|
|
this.processMessageQueue();
|
|
|
|
|
|
} else {
|
2025-08-20 18:19:42 -04:00
|
|
|
|
this.deliverMessageToUI('❌ Verification code mismatch! Possible MITM attack detected. Connection aborted for safety!', 'system');
|
2025-08-11 20:52:14 -04:00
|
|
|
|
this.disconnect();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
handleVerificationResponse(data) {
|
|
|
|
|
|
if (data.verified) {
|
|
|
|
|
|
this.isVerified = true;
|
|
|
|
|
|
this.onStatusChange('connected');
|
2025-08-20 18:19:42 -04:00
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Ensure verification success notice wasn't already sent
|
2025-08-20 18:19:42 -04:00
|
|
|
|
if (!this.verificationNotificationSent) {
|
|
|
|
|
|
this.verificationNotificationSent = true;
|
|
|
|
|
|
this.deliverMessageToUI('✅ Verification successful. The channel is now secure!', 'system');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-11 20:52:14 -04:00
|
|
|
|
this.processMessageQueue();
|
|
|
|
|
|
} else {
|
2025-08-20 18:19:42 -04:00
|
|
|
|
this.deliverMessageToUI('❌ Verification failed!', 'system');
|
2025-08-11 20:52:14 -04:00
|
|
|
|
this.disconnect();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
validateOfferData(offerData) {
|
|
|
|
|
|
return offerData &&
|
|
|
|
|
|
offerData.type === 'enhanced_secure_offer' &&
|
|
|
|
|
|
offerData.sdp &&
|
|
|
|
|
|
offerData.publicKey &&
|
|
|
|
|
|
offerData.salt &&
|
|
|
|
|
|
offerData.verificationCode &&
|
|
|
|
|
|
Array.isArray(offerData.publicKey) &&
|
|
|
|
|
|
Array.isArray(offerData.salt) &&
|
|
|
|
|
|
offerData.salt.length === 32;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Enhanced validation with backward compatibility
|
|
|
|
|
|
validateEnhancedOfferData(offerData) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (!offerData || typeof offerData !== 'object') {
|
|
|
|
|
|
throw new Error('Offer data must be an object');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Basic required fields for all versions
|
|
|
|
|
|
const basicFields = ['type', 'sdp'];
|
|
|
|
|
|
for (const field of basicFields) {
|
|
|
|
|
|
if (!offerData[field]) {
|
|
|
|
|
|
throw new Error(`Missing required field: ${field}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Validate offer type (support both v3.0 and v4.0 formats)
|
|
|
|
|
|
if (!['enhanced_secure_offer', 'secure_offer'].includes(offerData.type)) {
|
|
|
|
|
|
throw new Error('Invalid offer type');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Check if this is v4.0 format with enhanced features
|
|
|
|
|
|
const isV4Format = offerData.version === '4.0' && offerData.ecdhPublicKey && offerData.ecdsaPublicKey;
|
|
|
|
|
|
|
|
|
|
|
|
if (isV4Format) {
|
|
|
|
|
|
// v4.0 enhanced validation
|
|
|
|
|
|
const v4RequiredFields = [
|
|
|
|
|
|
'ecdhPublicKey', 'ecdsaPublicKey', 'salt', 'verificationCode',
|
|
|
|
|
|
'authChallenge', 'timestamp', 'version', 'securityLevel'
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
for (const field of v4RequiredFields) {
|
|
|
|
|
|
if (!offerData[field]) {
|
|
|
|
|
|
throw new Error(`Missing v4.0 field: ${field}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Validate salt (must be 64 bytes for v4.0)
|
|
|
|
|
|
if (!Array.isArray(offerData.salt) || offerData.salt.length !== 64) {
|
|
|
|
|
|
throw new Error('Salt must be exactly 64 bytes for v4.0');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Validate timestamp (not older than 1 hour)
|
|
|
|
|
|
const offerAge = Date.now() - offerData.timestamp;
|
|
|
|
|
|
if (offerAge > 3600000) {
|
|
|
|
|
|
throw new Error('Offer is too old (older than 1 hour)');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Validate key structures (more lenient)
|
|
|
|
|
|
if (!offerData.ecdhPublicKey || typeof offerData.ecdhPublicKey !== 'object') {
|
|
|
|
|
|
throw new Error('Invalid ECDH public key structure');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!offerData.ecdsaPublicKey || typeof offerData.ecdsaPublicKey !== 'object') {
|
|
|
|
|
|
throw new Error('Invalid ECDSA public key structure');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Validate verification code format (more flexible)
|
|
|
|
|
|
if (typeof offerData.verificationCode !== 'string' || offerData.verificationCode.length < 6) {
|
|
|
|
|
|
throw new Error('Invalid verification code format');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 04:07:16 -04:00
|
|
|
|
this._secureLog('info', 'v4.0 offer validation passed', {
|
2025-08-11 20:52:14 -04:00
|
|
|
|
version: offerData.version,
|
2025-08-21 04:07:16 -04:00
|
|
|
|
hasSecurityLevel: !!offerData.securityLevel?.level,
|
2025-08-11 20:52:14 -04:00
|
|
|
|
offerAge: Math.round(offerAge / 1000) + 's'
|
|
|
|
|
|
});
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// v3.0 backward compatibility validation
|
|
|
|
|
|
const v3RequiredFields = ['publicKey', 'salt', 'verificationCode'];
|
|
|
|
|
|
for (const field of v3RequiredFields) {
|
|
|
|
|
|
if (!offerData[field]) {
|
|
|
|
|
|
throw new Error(`Missing v3.0 field: ${field}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Validate salt (32 bytes for v3.0)
|
|
|
|
|
|
if (!Array.isArray(offerData.salt) || offerData.salt.length !== 32) {
|
|
|
|
|
|
throw new Error('Salt must be exactly 32 bytes for v3.0');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Validate public key
|
|
|
|
|
|
if (!Array.isArray(offerData.publicKey)) {
|
|
|
|
|
|
throw new Error('Invalid public key format for v3.0');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
window.EnhancedSecureCryptoUtils.secureLog.log('info', 'v3.0 offer validation passed (backward compatibility)', {
|
|
|
|
|
|
version: 'v3.0',
|
|
|
|
|
|
legacy: true
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Validate SDP structure (basic check for all versions)
|
|
|
|
|
|
if (typeof offerData.sdp !== 'string' || !offerData.sdp.includes('v=0')) {
|
|
|
|
|
|
throw new Error('Invalid SDP structure');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
window.EnhancedSecureCryptoUtils.secureLog.log('error', 'Offer validation failed', {
|
|
|
|
|
|
error: error.message
|
|
|
|
|
|
});
|
|
|
|
|
|
return false; // Return false instead of throwing to allow graceful handling
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async sendSecureMessage(message) {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Quick readiness check WITHOUT mutex
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (!this.isConnected() || !this.isVerified) {
|
|
|
|
|
|
if (message && typeof message === 'object' && message.type && message.type.startsWith('file_')) {
|
|
|
|
|
|
throw new Error('Connection not ready for file transfer. Please ensure the connection is established and verified.');
|
|
|
|
|
|
}
|
|
|
|
|
|
this.messageQueue.push(message);
|
|
|
|
|
|
throw new Error('Connection not ready. Message queued for sending.');
|
2025-08-14 03:28:23 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// FIX: Use mutex ONLY for cryptographic operations
|
2025-08-21 04:07:16 -04:00
|
|
|
|
return this._withMutex('cryptoOperation', async (operationId) => {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Re-check inside critical section
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (!this.isConnected() || !this.isVerified) {
|
|
|
|
|
|
throw new Error('Connection lost during message preparation');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Validate keys inside critical section
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (!this.encryptionKey || !this.macKey || !this.metadataKey) {
|
|
|
|
|
|
throw new Error('Encryption keys not initialized');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Rate limiting check
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (!window.EnhancedSecureCryptoUtils.rateLimiter.checkMessageRate(this.rateLimiterId)) {
|
|
|
|
|
|
throw new Error('Message rate limit exceeded (60 messages per minute)');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Accept strings and objects; stringify objects
|
2025-08-21 04:07:16 -04:00
|
|
|
|
const textToSend = typeof message === 'string' ? message : JSON.stringify(message);
|
|
|
|
|
|
const sanitizedMessage = window.EnhancedSecureCryptoUtils.sanitizeMessage(textToSend);
|
|
|
|
|
|
const messageId = `msg_${Date.now()}_${this.messageCounter++}`;
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Use enhanced encryption with metadata protection
|
2025-08-21 04:07:16 -04:00
|
|
|
|
const encryptedData = await window.EnhancedSecureCryptoUtils.encryptMessage(
|
|
|
|
|
|
sanitizedMessage,
|
|
|
|
|
|
this.encryptionKey,
|
|
|
|
|
|
this.macKey,
|
|
|
|
|
|
this.metadataKey,
|
|
|
|
|
|
messageId,
|
|
|
|
|
|
this.sequenceNumber++
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
const payload = {
|
|
|
|
|
|
type: 'enhanced_message',
|
|
|
|
|
|
data: encryptedData,
|
|
|
|
|
|
keyVersion: this.currentKeyVersion,
|
|
|
|
|
|
version: '4.0'
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
this.dataChannel.send(JSON.stringify(payload));
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Locally display only plain strings to avoid UI duplication
|
2025-08-21 04:07:16 -04:00
|
|
|
|
if (typeof message === 'string') {
|
|
|
|
|
|
this.deliverMessageToUI(message, 'sent');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this._secureLog('debug', '📤 Secure message sent successfully', {
|
|
|
|
|
|
operationId: operationId,
|
|
|
|
|
|
messageLength: sanitizedMessage.length,
|
|
|
|
|
|
keyVersion: this.currentKeyVersion
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
this._secureLog('error', '❌ Secure message sending failed', {
|
|
|
|
|
|
operationId: operationId,
|
|
|
|
|
|
errorType: error.constructor.name
|
|
|
|
|
|
});
|
|
|
|
|
|
throw error;
|
|
|
|
|
|
}
|
2025-08-21 05:16:41 -04:00
|
|
|
|
}, 2000); // Reduced timeout for crypto operations
|
2025-08-11 20:52:14 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
processMessageQueue() {
|
|
|
|
|
|
while (this.messageQueue.length > 0 && this.isConnected() && this.isVerified) {
|
|
|
|
|
|
const message = this.messageQueue.shift();
|
|
|
|
|
|
this.sendSecureMessage(message).catch(console.error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
startHeartbeat() {
|
|
|
|
|
|
this.heartbeatInterval = setInterval(() => {
|
|
|
|
|
|
if (this.isConnected()) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
this.dataChannel.send(JSON.stringify({
|
2025-08-20 23:04:29 -04:00
|
|
|
|
type: EnhancedSecureWebRTCManager.MESSAGE_TYPES.HEARTBEAT,
|
2025-08-11 20:52:14 -04:00
|
|
|
|
timestamp: Date.now()
|
|
|
|
|
|
}));
|
|
|
|
|
|
} catch (error) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ Heartbeat failed:', { errorType: error?.constructor?.name || 'Unknown' });
|
2025-08-11 20:52:14 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-20 23:04:29 -04:00
|
|
|
|
}, EnhancedSecureWebRTCManager.TIMEOUTS.HEARTBEAT_INTERVAL);
|
2025-08-11 20:52:14 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
stopHeartbeat() {
|
|
|
|
|
|
if (this.heartbeatInterval) {
|
|
|
|
|
|
clearInterval(this.heartbeatInterval);
|
|
|
|
|
|
this.heartbeatInterval = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
handleHeartbeat() {
|
|
|
|
|
|
console.log('Heartbeat received - connection alive');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
waitForIceGathering() {
|
|
|
|
|
|
return new Promise((resolve) => {
|
|
|
|
|
|
if (this.peerConnection.iceGatheringState === 'complete') {
|
|
|
|
|
|
resolve();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const checkState = () => {
|
|
|
|
|
|
if (this.peerConnection.iceGatheringState === 'complete') {
|
|
|
|
|
|
this.peerConnection.removeEventListener('icegatheringstatechange', checkState);
|
|
|
|
|
|
resolve();
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
this.peerConnection.addEventListener('icegatheringstatechange', checkState);
|
|
|
|
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
this.peerConnection.removeEventListener('icegatheringstatechange', checkState);
|
|
|
|
|
|
resolve();
|
2025-08-20 23:04:29 -04:00
|
|
|
|
}, EnhancedSecureWebRTCManager.TIMEOUTS.ICE_GATHERING_TIMEOUT);
|
2025-08-11 20:52:14 -04:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
retryConnection() {
|
|
|
|
|
|
console.log(`Retrying connection (attempt ${this.connectionAttempts}/${this.maxConnectionAttempts})`);
|
|
|
|
|
|
this.onStatusChange('retrying');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
isConnected() {
|
2025-08-14 23:34:54 -04:00
|
|
|
|
const hasDataChannel = !!this.dataChannel;
|
|
|
|
|
|
const dataChannelState = this.dataChannel?.readyState;
|
|
|
|
|
|
const isDataChannelOpen = dataChannelState === 'open';
|
|
|
|
|
|
const isVerified = this.isVerified;
|
|
|
|
|
|
const connectionState = this.peerConnection?.connectionState;
|
|
|
|
|
|
|
|
|
|
|
|
return this.dataChannel && this.dataChannel.readyState === 'open' && this.isVerified;
|
2025-08-11 20:52:14 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getConnectionInfo() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
fingerprint: this.keyFingerprint,
|
|
|
|
|
|
isConnected: this.isConnected(),
|
|
|
|
|
|
isVerified: this.isVerified,
|
|
|
|
|
|
connectionState: this.peerConnection?.connectionState,
|
|
|
|
|
|
iceConnectionState: this.peerConnection?.iceConnectionState,
|
|
|
|
|
|
verificationCode: this.verificationCode
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
disconnect() {
|
2025-08-18 21:45:50 -04:00
|
|
|
|
if (this.fileTransferSystem) {
|
|
|
|
|
|
this.fileTransferSystem.cleanup();
|
|
|
|
|
|
}
|
2025-08-11 20:52:14 -04:00
|
|
|
|
this.intentionalDisconnect = true;
|
|
|
|
|
|
|
|
|
|
|
|
window.EnhancedSecureCryptoUtils.secureLog.log('info', 'Starting intentional disconnect');
|
2025-08-13 14:48:24 -04:00
|
|
|
|
|
2025-08-11 20:52:14 -04:00
|
|
|
|
this.sendDisconnectNotification();
|
2025-08-13 14:48:24 -04:00
|
|
|
|
|
2025-08-11 20:52:14 -04:00
|
|
|
|
setTimeout(() => {
|
2025-08-13 14:48:24 -04:00
|
|
|
|
this.sendDisconnectNotification();
|
2025-08-11 20:52:14 -04:00
|
|
|
|
}, 100);
|
2025-08-16 20:58:42 -04:00
|
|
|
|
|
|
|
|
|
|
document.dispatchEvent(new CustomEvent('peer-disconnect', {
|
|
|
|
|
|
detail: {
|
|
|
|
|
|
reason: 'user_disconnect',
|
|
|
|
|
|
timestamp: Date.now()
|
|
|
|
|
|
}
|
|
|
|
|
|
}));
|
2025-08-11 20:52:14 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
handleUnexpectedDisconnect() {
|
|
|
|
|
|
this.sendDisconnectNotification();
|
|
|
|
|
|
this.isVerified = false;
|
2025-08-20 18:19:42 -04:00
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Ensure disconnect notification wasn't already sent
|
2025-08-20 18:19:42 -04:00
|
|
|
|
if (!this.disconnectNotificationSent) {
|
|
|
|
|
|
this.disconnectNotificationSent = true;
|
|
|
|
|
|
this.deliverMessageToUI('🔌 Connection lost. Attempting to reconnect...', 'system');
|
|
|
|
|
|
}
|
2025-08-11 20:52:14 -04:00
|
|
|
|
|
2025-08-18 21:45:50 -04:00
|
|
|
|
// Cleanup file transfer system on unexpected disconnect
|
|
|
|
|
|
if (this.fileTransferSystem) {
|
|
|
|
|
|
console.log('🧹 Cleaning up file transfer system on unexpected disconnect...');
|
|
|
|
|
|
this.fileTransferSystem.cleanup();
|
|
|
|
|
|
this.fileTransferSystem = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-16 20:58:42 -04:00
|
|
|
|
document.dispatchEvent(new CustomEvent('peer-disconnect', {
|
|
|
|
|
|
detail: {
|
|
|
|
|
|
reason: 'connection_lost',
|
|
|
|
|
|
timestamp: Date.now()
|
|
|
|
|
|
}
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Do not auto-reconnect to avoid closing the session on errors
|
2025-08-18 21:45:50 -04:00
|
|
|
|
// setTimeout(() => {
|
|
|
|
|
|
// if (!this.intentionalDisconnect) {
|
|
|
|
|
|
// this.attemptReconnection();
|
|
|
|
|
|
// }
|
|
|
|
|
|
// }, 3000);
|
2025-08-11 20:52:14 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
sendDisconnectNotification() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (this.dataChannel && this.dataChannel.readyState === 'open') {
|
|
|
|
|
|
const notification = {
|
|
|
|
|
|
type: 'peer_disconnect',
|
|
|
|
|
|
timestamp: Date.now(),
|
|
|
|
|
|
reason: this.intentionalDisconnect ? 'user_disconnect' : 'connection_lost'
|
|
|
|
|
|
};
|
2025-08-13 14:48:24 -04:00
|
|
|
|
|
2025-08-11 20:52:14 -04:00
|
|
|
|
for (let i = 0; i < 3; i++) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
this.dataChannel.send(JSON.stringify(notification));
|
|
|
|
|
|
window.EnhancedSecureCryptoUtils.secureLog.log('info', 'Disconnect notification sent', {
|
|
|
|
|
|
reason: notification.reason,
|
|
|
|
|
|
attempt: i + 1
|
|
|
|
|
|
});
|
|
|
|
|
|
break;
|
|
|
|
|
|
} catch (sendError) {
|
2025-08-13 14:48:24 -04:00
|
|
|
|
if (i === 2) {
|
2025-08-11 20:52:14 -04:00
|
|
|
|
window.EnhancedSecureCryptoUtils.secureLog.log('error', 'Failed to send disconnect notification', {
|
|
|
|
|
|
error: sendError.message
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
window.EnhancedSecureCryptoUtils.secureLog.log('error', 'Could not send disconnect notification', {
|
|
|
|
|
|
error: error.message
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
attemptReconnection() {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Ensure reconnection-failed notification wasn't already sent
|
2025-08-20 18:19:42 -04:00
|
|
|
|
if (!this.reconnectionFailedNotificationSent) {
|
|
|
|
|
|
this.reconnectionFailedNotificationSent = true;
|
|
|
|
|
|
this.deliverMessageToUI('❌ Unable to reconnect. A new connection is required.', 'system');
|
|
|
|
|
|
}
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Do not call cleanupConnection automatically to avoid closing the session on errors
|
2025-08-21 00:06:28 -04:00
|
|
|
|
// this.disconnect();
|
2025-08-11 20:52:14 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
handlePeerDisconnectNotification(data) {
|
|
|
|
|
|
const reason = data.reason || 'unknown';
|
2025-08-13 14:48:24 -04:00
|
|
|
|
const reasonText = reason === 'user_disconnect' ? 'manually disconnected.' : 'connection lost.';
|
2025-08-11 20:52:14 -04:00
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Ensure peer-disconnect notification wasn't already sent
|
2025-08-20 18:19:42 -04:00
|
|
|
|
if (!this.peerDisconnectNotificationSent) {
|
|
|
|
|
|
this.peerDisconnectNotificationSent = true;
|
|
|
|
|
|
this.deliverMessageToUI(`👋 Peer ${reasonText}`, 'system');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-11 20:52:14 -04:00
|
|
|
|
this.onStatusChange('peer_disconnected');
|
2025-08-13 14:48:24 -04:00
|
|
|
|
|
2025-08-11 20:52:14 -04:00
|
|
|
|
this.intentionalDisconnect = false;
|
|
|
|
|
|
this.isVerified = false;
|
|
|
|
|
|
this.stopHeartbeat();
|
|
|
|
|
|
|
2025-08-13 14:48:24 -04:00
|
|
|
|
this.onKeyExchange('');
|
|
|
|
|
|
this.onVerificationRequired('');
|
|
|
|
|
|
|
2025-08-16 20:58:42 -04:00
|
|
|
|
document.dispatchEvent(new CustomEvent('peer-disconnect', {
|
|
|
|
|
|
detail: {
|
|
|
|
|
|
reason: reason,
|
|
|
|
|
|
timestamp: Date.now()
|
|
|
|
|
|
}
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
2025-08-11 20:52:14 -04:00
|
|
|
|
setTimeout(() => {
|
2025-08-21 00:06:28 -04:00
|
|
|
|
this.disconnect();
|
2025-08-11 20:52:14 -04:00
|
|
|
|
}, 2000);
|
|
|
|
|
|
|
|
|
|
|
|
window.EnhancedSecureCryptoUtils.secureLog.log('info', 'Peer disconnect notification processed', {
|
|
|
|
|
|
reason: reason
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 00:06:28 -04:00
|
|
|
|
disconnect() {
|
2025-08-11 20:52:14 -04:00
|
|
|
|
this.stopHeartbeat();
|
|
|
|
|
|
this.isVerified = false;
|
|
|
|
|
|
this.processedMessageIds.clear();
|
|
|
|
|
|
this.messageCounter = 0;
|
2025-08-21 00:06:28 -04:00
|
|
|
|
this._initializeSecureKeyStorage();
|
2025-08-11 20:52:14 -04:00
|
|
|
|
this.encryptionKey = null;
|
|
|
|
|
|
this.macKey = null;
|
|
|
|
|
|
this.metadataKey = null;
|
|
|
|
|
|
|
2025-08-13 14:48:24 -04:00
|
|
|
|
// PFS: Clearing all key versions
|
2025-08-11 20:52:14 -04:00
|
|
|
|
this.keyVersions.clear();
|
|
|
|
|
|
this.oldKeys.clear();
|
|
|
|
|
|
this.currentKeyVersion = 0;
|
|
|
|
|
|
this.lastKeyRotation = Date.now();
|
|
|
|
|
|
|
2025-08-13 14:48:24 -04:00
|
|
|
|
// Clearing key pairs
|
2025-08-11 20:52:14 -04:00
|
|
|
|
this.ecdhKeyPair = null;
|
|
|
|
|
|
this.ecdsaKeyPair = null;
|
|
|
|
|
|
|
2025-08-13 14:48:24 -04:00
|
|
|
|
// Resetting message counters
|
2025-08-11 20:52:14 -04:00
|
|
|
|
this.sequenceNumber = 0;
|
|
|
|
|
|
this.expectedSequenceNumber = 0;
|
|
|
|
|
|
|
2025-08-13 14:48:24 -04:00
|
|
|
|
// Security flags reset completed
|
2025-08-11 20:52:14 -04:00
|
|
|
|
this.securityFeatures = {
|
|
|
|
|
|
hasEncryption: false,
|
|
|
|
|
|
hasECDH: false,
|
|
|
|
|
|
hasECDSA: false,
|
|
|
|
|
|
hasMutualAuth: false,
|
|
|
|
|
|
hasMetadataProtection: false,
|
|
|
|
|
|
hasEnhancedReplayProtection: false,
|
|
|
|
|
|
hasNonExtractableKeys: false,
|
|
|
|
|
|
hasRateLimiting: false,
|
|
|
|
|
|
hasEnhancedValidation: false,
|
|
|
|
|
|
hasPFS: false
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-08-13 14:48:24 -04:00
|
|
|
|
// Closing connections
|
2025-08-11 20:52:14 -04:00
|
|
|
|
if (this.dataChannel) {
|
|
|
|
|
|
this.dataChannel.close();
|
|
|
|
|
|
this.dataChannel = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (this.peerConnection) {
|
|
|
|
|
|
this.peerConnection.close();
|
|
|
|
|
|
this.peerConnection = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-13 14:48:24 -04:00
|
|
|
|
// Clearing message queue
|
2025-08-11 20:52:14 -04:00
|
|
|
|
this.messageQueue = [];
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-08-16 20:58:42 -04:00
|
|
|
|
document.dispatchEvent(new CustomEvent('connection-cleaned', {
|
|
|
|
|
|
detail: {
|
|
|
|
|
|
timestamp: Date.now(),
|
|
|
|
|
|
reason: this.intentionalDisconnect ? 'user_cleanup' : 'automatic_cleanup'
|
|
|
|
|
|
}
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
2025-08-13 14:48:24 -04:00
|
|
|
|
// Notifying the UI about complete cleanup
|
2025-08-11 20:52:14 -04:00
|
|
|
|
this.onStatusChange('disconnected');
|
|
|
|
|
|
this.onKeyExchange('');
|
|
|
|
|
|
this.onVerificationRequired('');
|
|
|
|
|
|
|
|
|
|
|
|
window.EnhancedSecureCryptoUtils.secureLog.log('info', 'Connection cleaned up completely');
|
|
|
|
|
|
|
2025-08-13 14:48:24 -04:00
|
|
|
|
// Resetting the intentional disconnect flag
|
2025-08-11 20:52:14 -04:00
|
|
|
|
this.intentionalDisconnect = false;
|
2025-08-13 14:48:24 -04:00
|
|
|
|
|
2025-08-11 20:52:14 -04:00
|
|
|
|
if (window.gc) {
|
|
|
|
|
|
window.gc();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-18 21:45:50 -04:00
|
|
|
|
// Public method to send files
|
|
|
|
|
|
async sendFile(file) {
|
|
|
|
|
|
if (!this.isConnected() || !this.isVerified) {
|
|
|
|
|
|
throw new Error('Connection not ready for file transfer. Please ensure the connection is established and verified.');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!this.fileTransferSystem) {
|
|
|
|
|
|
console.log('🔄 File transfer system not initialized, attempting to initialize...');
|
|
|
|
|
|
this.initializeFileTransfer();
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Allow time for initialization
|
2025-08-18 21:45:50 -04:00
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 500));
|
|
|
|
|
|
|
|
|
|
|
|
if (!this.fileTransferSystem) {
|
|
|
|
|
|
throw new Error('File transfer system could not be initialized. Please try reconnecting.');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// CRITICAL FIX: Verify key readiness
|
2025-08-18 21:45:50 -04:00
|
|
|
|
if (!this.encryptionKey || !this.macKey) {
|
|
|
|
|
|
throw new Error('Encryption keys not ready. Please wait for connection to be fully established.');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Debug logging for file transfer system
|
|
|
|
|
|
console.log('🔍 Debug: File transfer system in sendFile:', {
|
|
|
|
|
|
hasFileTransferSystem: !!this.fileTransferSystem,
|
|
|
|
|
|
fileTransferSystemType: this.fileTransferSystem.constructor?.name,
|
|
|
|
|
|
hasWebrtcManager: !!this.fileTransferSystem.webrtcManager,
|
|
|
|
|
|
webrtcManagerType: this.fileTransferSystem.webrtcManager?.constructor?.name
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
console.log('🚀 Starting file transfer for:', file.name, `(${(file.size / 1024 / 1024).toFixed(2)} MB)`);
|
|
|
|
|
|
const fileId = await this.fileTransferSystem.sendFile(file);
|
|
|
|
|
|
console.log('✅ File transfer initiated successfully with ID:', fileId);
|
|
|
|
|
|
return fileId;
|
|
|
|
|
|
} catch (error) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ File transfer error:', { errorType: error?.constructor?.name || 'Unknown' });
|
2025-08-18 21:45:50 -04:00
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Re-throw with a clearer message
|
2025-08-18 21:45:50 -04:00
|
|
|
|
if (error.message.includes('Connection not ready')) {
|
|
|
|
|
|
throw new Error('Connection not ready for file transfer. Check connection status.');
|
|
|
|
|
|
} else if (error.message.includes('Encryption keys not initialized')) {
|
|
|
|
|
|
throw new Error('Encryption keys not initialized. Try reconnecting.');
|
|
|
|
|
|
} else if (error.message.includes('Transfer timeout')) {
|
|
|
|
|
|
throw new Error('File transfer timeout. Check connection and try again.');
|
|
|
|
|
|
} else {
|
|
|
|
|
|
throw error;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Get active file transfers
|
|
|
|
|
|
getFileTransfers() {
|
|
|
|
|
|
if (!this.fileTransferSystem) {
|
|
|
|
|
|
return { sending: [], receiving: [] };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Check available methods in file transfer system
|
2025-08-18 21:45:50 -04:00
|
|
|
|
let sending = [];
|
|
|
|
|
|
let receiving = [];
|
|
|
|
|
|
|
|
|
|
|
|
if (typeof this.fileTransferSystem.getActiveTransfers === 'function') {
|
|
|
|
|
|
sending = this.fileTransferSystem.getActiveTransfers();
|
|
|
|
|
|
} else {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('warn', '⚠️ getActiveTransfers method not available in file transfer system');
|
2025-08-18 21:45:50 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (typeof this.fileTransferSystem.getReceivingTransfers === 'function') {
|
|
|
|
|
|
receiving = this.fileTransferSystem.getReceivingTransfers();
|
|
|
|
|
|
} else {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('warn', '⚠️ getReceivingTransfers method not available in file transfer system');
|
2025-08-18 21:45:50 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
sending: sending || [],
|
|
|
|
|
|
receiving: receiving || []
|
|
|
|
|
|
};
|
|
|
|
|
|
} catch (error) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ Error getting file transfers:', { errorType: error?.constructor?.name || 'Unknown' });
|
2025-08-18 21:45:50 -04:00
|
|
|
|
return { sending: [], receiving: [] };
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Get file transfer system status
|
|
|
|
|
|
getFileTransferStatus() {
|
|
|
|
|
|
if (!this.fileTransferSystem) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
initialized: false,
|
|
|
|
|
|
status: 'not_initialized',
|
|
|
|
|
|
message: 'File transfer system not initialized'
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const activeTransfers = this.fileTransferSystem.getActiveTransfers();
|
|
|
|
|
|
const receivingTransfers = this.fileTransferSystem.getReceivingTransfers();
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
initialized: true,
|
|
|
|
|
|
status: 'ready',
|
|
|
|
|
|
activeTransfers: activeTransfers.length,
|
|
|
|
|
|
receivingTransfers: receivingTransfers.length,
|
|
|
|
|
|
totalTransfers: activeTransfers.length + receivingTransfers.length
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Cancel file transfer
|
|
|
|
|
|
cancelFileTransfer(fileId) {
|
|
|
|
|
|
if (!this.fileTransferSystem) return false;
|
|
|
|
|
|
return this.fileTransferSystem.cancelTransfer(fileId);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Force cleanup of file transfer system
|
|
|
|
|
|
cleanupFileTransferSystem() {
|
|
|
|
|
|
if (this.fileTransferSystem) {
|
|
|
|
|
|
console.log('🧹 Force cleaning up file transfer system...');
|
|
|
|
|
|
this.fileTransferSystem.cleanup();
|
|
|
|
|
|
this.fileTransferSystem = null;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Reinitialize file transfer system
|
|
|
|
|
|
reinitializeFileTransfer() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
console.log('🔄 Reinitializing file transfer system...');
|
|
|
|
|
|
if (this.fileTransferSystem) {
|
|
|
|
|
|
this.fileTransferSystem.cleanup();
|
|
|
|
|
|
}
|
|
|
|
|
|
this.initializeFileTransfer();
|
|
|
|
|
|
return true;
|
|
|
|
|
|
} catch (error) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ Failed to reinitialize file transfer system:', { errorType: error?.constructor?.name || 'Unknown' });
|
2025-08-18 21:45:50 -04:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Set file transfer callbacks
|
|
|
|
|
|
setFileTransferCallbacks(onProgress, onReceived, onError) {
|
|
|
|
|
|
this.onFileProgress = onProgress;
|
|
|
|
|
|
this.onFileReceived = onReceived;
|
|
|
|
|
|
this.onFileError = onError;
|
|
|
|
|
|
|
|
|
|
|
|
console.log('🔧 File transfer callbacks set:', {
|
|
|
|
|
|
hasProgress: !!onProgress,
|
|
|
|
|
|
hasReceived: !!onReceived,
|
|
|
|
|
|
hasError: !!onError
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Reinitialize file transfer system if it exists to update callbacks
|
|
|
|
|
|
if (this.fileTransferSystem) {
|
|
|
|
|
|
console.log('🔄 Reinitializing file transfer system with new callbacks...');
|
|
|
|
|
|
this.initializeFileTransfer();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================
|
|
|
|
|
|
// SESSION ACTIVATION HANDLING
|
|
|
|
|
|
// ============================================
|
|
|
|
|
|
|
|
|
|
|
|
async handleSessionActivation(sessionData) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
console.log('🔐 Handling session activation:', sessionData);
|
|
|
|
|
|
|
|
|
|
|
|
// Update session state
|
|
|
|
|
|
this.currentSession = sessionData;
|
|
|
|
|
|
this.sessionManager = sessionData.sessionManager;
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// FIX: More lenient checks for activation
|
2025-08-18 21:45:50 -04:00
|
|
|
|
const hasKeys = !!(this.encryptionKey && this.macKey);
|
|
|
|
|
|
const hasSession = !!(this.sessionManager && (this.sessionManager.hasActiveSession?.() || sessionData.sessionId));
|
|
|
|
|
|
|
|
|
|
|
|
console.log('🔍 Session activation status:', {
|
|
|
|
|
|
hasKeys: hasKeys,
|
|
|
|
|
|
hasSession: hasSession,
|
|
|
|
|
|
sessionType: sessionData.sessionType,
|
|
|
|
|
|
isDemo: sessionData.isDemo
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Force connection status if there is an active session
|
2025-08-18 21:45:50 -04:00
|
|
|
|
if (hasSession) {
|
|
|
|
|
|
console.log('🔓 Session activated - forcing connection status to connected');
|
|
|
|
|
|
this.onStatusChange('connected');
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Set isVerified for active sessions
|
2025-08-18 21:45:50 -04:00
|
|
|
|
this.isVerified = true;
|
|
|
|
|
|
console.log('✅ Session verified - setting isVerified to true');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 17:40:17 -04:00
|
|
|
|
// КРИТИЧЕСКОЕ ИСПРАВЛЕНИЕ: Инициализируем файловую систему для обеих сторон
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
this.initializeFileTransfer();
|
|
|
|
|
|
} catch (error) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('warn', '⚠️ File transfer initialization failed during session activation:', { details: error.message });
|
2025-08-21 17:40:17 -04:00
|
|
|
|
}
|
|
|
|
|
|
}, 1000);
|
2025-08-18 21:45:50 -04:00
|
|
|
|
|
|
|
|
|
|
console.log('✅ Session activation handled successfully');
|
|
|
|
|
|
|
2025-08-18 23:56:10 -04:00
|
|
|
|
if (this.fileTransferSystem && this.isConnected()) {
|
|
|
|
|
|
console.log('🔄 Synchronizing file transfer keys after session activation...');
|
|
|
|
|
|
|
|
|
|
|
|
if (typeof this.fileTransferSystem.onSessionUpdate === 'function') {
|
|
|
|
|
|
this.fileTransferSystem.onSessionUpdate({
|
|
|
|
|
|
keyFingerprint: this.keyFingerprint,
|
|
|
|
|
|
sessionSalt: this.sessionSalt,
|
|
|
|
|
|
hasMacKey: !!this.macKey
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-18 21:45:50 -04:00
|
|
|
|
} catch (error) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ Failed to handle session activation:', { errorType: error?.constructor?.name || 'Unknown' });
|
2025-08-18 21:45:50 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Method to check readiness of file transfers
|
2025-08-18 21:45:50 -04:00
|
|
|
|
checkFileTransferReadiness() {
|
|
|
|
|
|
const status = {
|
|
|
|
|
|
hasFileTransferSystem: !!this.fileTransferSystem,
|
|
|
|
|
|
hasDataChannel: !!this.dataChannel,
|
|
|
|
|
|
dataChannelState: this.dataChannel?.readyState,
|
|
|
|
|
|
isConnected: this.isConnected(),
|
|
|
|
|
|
isVerified: this.isVerified,
|
|
|
|
|
|
hasEncryptionKey: !!this.encryptionKey,
|
|
|
|
|
|
hasMacKey: !!this.macKey,
|
|
|
|
|
|
ready: false
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
status.ready = status.hasFileTransferSystem &&
|
|
|
|
|
|
status.hasDataChannel &&
|
|
|
|
|
|
status.dataChannelState === 'open' &&
|
|
|
|
|
|
status.isConnected &&
|
|
|
|
|
|
status.isVerified;
|
|
|
|
|
|
|
|
|
|
|
|
console.log('🔍 File transfer readiness check:', status);
|
|
|
|
|
|
return status;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Method to force re-initialize file transfer system
|
2025-08-18 21:45:50 -04:00
|
|
|
|
forceReinitializeFileTransfer() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
console.log('🔄 Force reinitializing file transfer system...');
|
|
|
|
|
|
|
|
|
|
|
|
if (this.fileTransferSystem) {
|
|
|
|
|
|
this.fileTransferSystem.cleanup();
|
|
|
|
|
|
this.fileTransferSystem = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Small delay before reinitialization
|
2025-08-18 21:45:50 -04:00
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
this.initializeFileTransfer();
|
|
|
|
|
|
}, 500);
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
} catch (error) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ Failed to force reinitialize file transfer:', { errorType: error?.constructor?.name || 'Unknown' });
|
2025-08-18 21:45:50 -04:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 05:16:41 -04:00
|
|
|
|
// Method to get diagnostic information
|
2025-08-18 21:45:50 -04:00
|
|
|
|
getFileTransferDiagnostics() {
|
|
|
|
|
|
const diagnostics = {
|
|
|
|
|
|
timestamp: new Date().toISOString(),
|
|
|
|
|
|
webrtcManager: {
|
|
|
|
|
|
hasDataChannel: !!this.dataChannel,
|
|
|
|
|
|
dataChannelState: this.dataChannel?.readyState,
|
|
|
|
|
|
isConnected: this.isConnected(),
|
|
|
|
|
|
isVerified: this.isVerified,
|
2025-08-21 17:40:17 -04:00
|
|
|
|
isInitiator: this.isInitiator,
|
2025-08-18 21:45:50 -04:00
|
|
|
|
hasEncryptionKey: !!this.encryptionKey,
|
|
|
|
|
|
hasMacKey: !!this.macKey,
|
2025-08-21 17:40:17 -04:00
|
|
|
|
hasMetadataKey: !!this.metadataKey,
|
|
|
|
|
|
hasKeyFingerprint: !!this.keyFingerprint,
|
|
|
|
|
|
hasSessionSalt: !!this.sessionSalt
|
2025-08-18 21:45:50 -04:00
|
|
|
|
},
|
2025-08-21 17:40:17 -04:00
|
|
|
|
fileTransferSystem: null,
|
|
|
|
|
|
// КРИТИЧЕСКОЕ ИСПРАВЛЕНИЕ: Дополнительная диагностика
|
|
|
|
|
|
globalState: {
|
|
|
|
|
|
fileTransferActive: window.FILE_TRANSFER_ACTIVE,
|
|
|
|
|
|
hasGlobalFileTransferSystem: !!window.fileTransferSystem,
|
|
|
|
|
|
globalFileTransferSystemType: window.fileTransferSystem?.constructor?.name
|
|
|
|
|
|
}
|
2025-08-18 21:45:50 -04:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
if (this.fileTransferSystem) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
diagnostics.fileTransferSystem = this.fileTransferSystem.getSystemStatus();
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
diagnostics.fileTransferSystem = { error: error.message };
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return diagnostics;
|
|
|
|
|
|
}
|
2025-08-21 17:40:17 -04:00
|
|
|
|
|
|
|
|
|
|
getSupportedFileTypes() {
|
|
|
|
|
|
if (!this.fileTransferSystem) {
|
|
|
|
|
|
return { error: 'File transfer system not initialized' };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
return this.fileTransferSystem.getSupportedFileTypes();
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
return { error: error.message };
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
validateFile(file) {
|
|
|
|
|
|
if (!this.fileTransferSystem) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
isValid: false,
|
|
|
|
|
|
errors: ['File transfer system not initialized'],
|
|
|
|
|
|
fileType: null,
|
|
|
|
|
|
fileSize: file?.size || 0,
|
|
|
|
|
|
formattedSize: '0 B'
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
return this.fileTransferSystem.validateFile(file);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
isValid: false,
|
|
|
|
|
|
errors: [error.message],
|
|
|
|
|
|
fileType: null,
|
|
|
|
|
|
fileSize: file?.size || 0,
|
|
|
|
|
|
formattedSize: '0 B'
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getFileTypeInfo() {
|
|
|
|
|
|
if (!this.fileTransferSystem) {
|
|
|
|
|
|
return { error: 'File transfer system not initialized' };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
return this.fileTransferSystem.getFileTypeInfo();
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
return { error: error.message };
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// КРИТИЧЕСКОЕ ИСПРАВЛЕНИЕ: Метод для принудительной инициализации файловой системы
|
|
|
|
|
|
forceInitializeFileTransfer() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// Проверяем готовность соединения
|
|
|
|
|
|
if (!this.isVerified) {
|
|
|
|
|
|
throw new Error('Connection not verified');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!this.dataChannel || this.dataChannel.readyState !== 'open') {
|
|
|
|
|
|
throw new Error('Data channel not open');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!this.encryptionKey || !this.macKey) {
|
|
|
|
|
|
throw new Error('Encryption keys not ready');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Очищаем существующую систему
|
|
|
|
|
|
if (this.fileTransferSystem) {
|
|
|
|
|
|
this.fileTransferSystem.cleanup();
|
|
|
|
|
|
this.fileTransferSystem = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Инициализируем новую систему
|
|
|
|
|
|
this.initializeFileTransfer();
|
|
|
|
|
|
|
|
|
|
|
|
// Ждем инициализации
|
|
|
|
|
|
let attempts = 0;
|
|
|
|
|
|
const maxAttempts = 50;
|
|
|
|
|
|
while (!this.fileTransferSystem && attempts < maxAttempts) {
|
|
|
|
|
|
// Синхронное ожидание
|
|
|
|
|
|
const start = Date.now();
|
|
|
|
|
|
while (Date.now() - start < 100) {
|
|
|
|
|
|
// busy wait
|
|
|
|
|
|
}
|
|
|
|
|
|
attempts++;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (this.fileTransferSystem) {
|
|
|
|
|
|
return true;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
throw new Error('Force initialization timeout');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
2025-08-28 15:05:42 -04:00
|
|
|
|
this._secureLog('error', '❌ Force file transfer initialization failed:', { errorType: error?.constructor?.name || 'Unknown' });
|
2025-08-21 17:40:17 -04:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-11 20:52:14 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export { EnhancedSecureWebRTCManager };
|