fix(security): fixes in keystore and cryptography
- Metadata of extracted keys is now marked encrypted: true - Removed decryption bypass via _keyReferences, all keys are returned via decrypt - Nested encryption uses random 96-bit IV instead of single byte increment - Fixed _secureLogShim: correct argument destructuring (...args) - Removed busy-wait in forceInitializeFileTransfer, replaced with async wait
This commit is contained in:
@@ -274,8 +274,8 @@ this._secureLog('info', '🔒 Enhanced Mutex system fully initialized and valida
|
|||||||
|
|
||||||
// 1. Nested Encryption Layer
|
// 1. Nested Encryption Layer
|
||||||
this.nestedEncryptionKey = null;
|
this.nestedEncryptionKey = null;
|
||||||
this.nestedEncryptionIV = null;
|
// CRITICAL FIX: Removed nestedEncryptionIV and nestedEncryptionCounter
|
||||||
this.nestedEncryptionCounter = 0;
|
// Each nested encryption now generates fresh random IV for maximum security
|
||||||
|
|
||||||
// 2. Packet Padding
|
// 2. Packet Padding
|
||||||
this.paddingConfig = {
|
this.paddingConfig = {
|
||||||
@@ -642,55 +642,104 @@ _initializeMutexSystem() {
|
|||||||
this._secureLog('info', `🔧 Secure logging initialized (Production: ${this._isProductionMode})`);
|
this._secureLog('info', `🔧 Secure logging initialized (Production: ${this._isProductionMode})`);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Shim to redirect arbitrary console.log calls to _secureLog('info', ...)
|
* CRITICAL FIX: Shim to redirect arbitrary console.log calls to _secureLog('info', ...)
|
||||||
|
* Fixed syntax errors and improved error handling
|
||||||
*/
|
*/
|
||||||
_secureLogShim(...args) {
|
_secureLogShim(...args) {
|
||||||
try {
|
try {
|
||||||
if (!args || args.length === 0) {
|
// Validate arguments array
|
||||||
|
if (!Array.isArray(args) || args.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const [message, ...rest] = args;
|
|
||||||
if (rest.length === 0) {
|
// CRITICAL FIX: Proper destructuring with fallback
|
||||||
this._secureLog('info', String(message));
|
const message = args[0];
|
||||||
|
const restArgs = args.slice(1);
|
||||||
|
|
||||||
|
// Handle different argument patterns
|
||||||
|
if (restArgs.length === 0) {
|
||||||
|
this._secureLog('info', String(message || ''));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (rest.length === 1) {
|
|
||||||
this._secureLog('info', String(message), rest[0]);
|
if (restArgs.length === 1) {
|
||||||
|
this._secureLog('info', String(message || ''), restArgs[0]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._secureLog('info', String(message), { args: rest });
|
|
||||||
} catch (e) {
|
// CRITICAL FIX: Proper object structure for multiple args
|
||||||
// Fallback — do not disrupt main execution flow
|
this._secureLog('info', String(message || ''), {
|
||||||
|
additionalArgs: restArgs,
|
||||||
|
argCount: restArgs.length
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
// CRITICAL FIX: Better error handling - fallback to original console if available
|
||||||
|
try {
|
||||||
|
if (this._originalConsole?.log) {
|
||||||
|
this._originalConsole.log(...args);
|
||||||
|
}
|
||||||
|
} catch (fallbackError) {
|
||||||
|
// Silent failure to prevent execution disruption
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Redirects global console.log to this instance's secure logger
|
* CRITICAL FIX: Redirects global console.log to this instance's secure logger
|
||||||
|
* Improved error handling and validation
|
||||||
*/
|
*/
|
||||||
_redirectConsoleLogToSecure() {
|
_redirectConsoleLogToSecure() {
|
||||||
try {
|
try {
|
||||||
if (typeof console === 'undefined') return;
|
// Validate console availability
|
||||||
// Preserve originals once
|
if (typeof console === 'undefined' || !console) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CRITICAL FIX: Preserve originals once with better validation
|
||||||
if (!this._originalConsole) {
|
if (!this._originalConsole) {
|
||||||
this._originalConsole = {
|
this._originalConsole = {
|
||||||
log: console.log?.bind(console),
|
log: (typeof console.log === 'function') ? console.log.bind(console) : null,
|
||||||
info: console.info?.bind(console),
|
info: (typeof console.info === 'function') ? console.info.bind(console) : null,
|
||||||
warn: console.warn?.bind(console),
|
warn: (typeof console.warn === 'function') ? console.warn.bind(console) : null,
|
||||||
error: console.error?.bind(console),
|
error: (typeof console.error === 'function') ? console.error.bind(console) : null,
|
||||||
debug: console.debug?.bind(console)
|
debug: (typeof console.debug === 'function') ? console.debug.bind(console) : null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
// Only console.log is redirected to secure; warn/error stay intact
|
|
||||||
|
// CRITICAL FIX: Only console.log is redirected to secure; warn/error stay intact
|
||||||
|
// Better error handling and validation
|
||||||
console.log = function(...args) {
|
console.log = function(...args) {
|
||||||
try {
|
try {
|
||||||
self._secureLogShim(...args);
|
// Validate self and method availability
|
||||||
} catch (e) {
|
if (self && typeof self._secureLogShim === 'function') {
|
||||||
// Fallback to original log on failure
|
self._secureLogShim(...args);
|
||||||
if (self._originalConsole?.log) self._originalConsole.log(...args);
|
} else {
|
||||||
|
// Fallback to original if shim is not available
|
||||||
|
if (self._originalConsole?.log) {
|
||||||
|
self._originalConsole.log(...args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// CRITICAL FIX: Better fallback handling
|
||||||
|
try {
|
||||||
|
if (self._originalConsole?.log) {
|
||||||
|
self._originalConsole.log(...args);
|
||||||
|
}
|
||||||
|
} catch (fallbackError) {
|
||||||
|
// Silent failure to prevent execution disruption
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (error) {
|
||||||
// Safe degradation
|
// CRITICAL FIX: Better error logging for debugging
|
||||||
|
try {
|
||||||
|
if (this._originalConsole?.error) {
|
||||||
|
this._originalConsole.error('❌ Failed to redirect console.log to secure logger:', error);
|
||||||
|
}
|
||||||
|
} catch (logError) {
|
||||||
|
// Silent failure - last resort
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@@ -2053,8 +2102,8 @@ _initializeMutexSystem() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Generate random IV for nested encryption
|
// Generate random IV for nested encryption
|
||||||
this.nestedEncryptionIV = crypto.getRandomValues(new Uint8Array(EnhancedSecureWebRTCManager.SIZES.NESTED_ENCRYPTION_IV_SIZE));
|
// CRITICAL FIX: No need for base IV or counter - each encryption gets fresh random IV
|
||||||
this.nestedEncryptionCounter = 0;
|
// This ensures maximum entropy and prevents IV reuse attacks
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this._secureLog('error', '❌ Failed to generate nested encryption key:', { errorType: error?.constructor?.name || 'Unknown' });
|
this._secureLog('error', '❌ Failed to generate nested encryption key:', { errorType: error?.constructor?.name || 'Unknown' });
|
||||||
@@ -2068,10 +2117,9 @@ _initializeMutexSystem() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Create unique IV for each encryption
|
// CRITICAL FIX: Generate fresh random 96-bit IV for each encryption
|
||||||
const uniqueIV = new Uint8Array(EnhancedSecureWebRTCManager.SIZES.NESTED_ENCRYPTION_IV_SIZE);
|
// This prevents IV reuse attacks that completely break AES-GCM security
|
||||||
uniqueIV.set(this.nestedEncryptionIV);
|
const uniqueIV = crypto.getRandomValues(new Uint8Array(EnhancedSecureWebRTCManager.SIZES.NESTED_ENCRYPTION_IV_SIZE));
|
||||||
uniqueIV[11] = (this.nestedEncryptionCounter++) & 0xFF;
|
|
||||||
|
|
||||||
// Encrypt data with nested layer
|
// Encrypt data with nested layer
|
||||||
const encrypted = await crypto.subtle.encrypt(
|
const encrypted = await crypto.subtle.encrypt(
|
||||||
@@ -2097,10 +2145,10 @@ _initializeMutexSystem() {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIX: Check that the data is actually encrypted
|
// CRITICAL FIX: Check that the data is actually encrypted with proper IV size
|
||||||
if (!(data instanceof ArrayBuffer) || data.byteLength < 20) {
|
if (!(data instanceof ArrayBuffer) || data.byteLength < EnhancedSecureWebRTCManager.SIZES.NESTED_ENCRYPTION_IV_SIZE + 16) {
|
||||||
if (window.DEBUG_MODE) {
|
if (window.DEBUG_MODE) {
|
||||||
console.log('📝 Data not encrypted or too short for nested decryption');
|
console.log('📝 Data not encrypted or too short for nested decryption (need IV + minimum encrypted data)');
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
@@ -7349,8 +7397,17 @@ checkFileTransferReadiness() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// КРИТИЧЕСКОЕ ИСПРАВЛЕНИЕ: Метод для принудительной инициализации файловой системы
|
// CRITICAL FIX: Метод для принудительной инициализации файловой системы
|
||||||
forceInitializeFileTransfer() {
|
// Устранен busy-wait, заменен на асинхронное ожидание
|
||||||
|
async forceInitializeFileTransfer(options = {}) {
|
||||||
|
// CRITICAL FIX: Добавляем возможность отмены операции
|
||||||
|
const abortController = new AbortController();
|
||||||
|
const { signal = abortController.signal, timeout = 6000 } = options;
|
||||||
|
|
||||||
|
// Связываем внешний signal с внутренним abortController
|
||||||
|
if (signal && signal !== abortController.signal) {
|
||||||
|
signal.addEventListener('abort', () => abortController.abort());
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
// Проверяем готовность соединения
|
// Проверяем готовность соединения
|
||||||
if (!this.isVerified) {
|
if (!this.isVerified) {
|
||||||
@@ -7374,17 +7431,46 @@ checkFileTransferReadiness() {
|
|||||||
// Инициализируем новую систему
|
// Инициализируем новую систему
|
||||||
this.initializeFileTransfer();
|
this.initializeFileTransfer();
|
||||||
|
|
||||||
// Ждем инициализации
|
// CRITICAL FIX: Заменяем busy-wait на асинхронное ожидание
|
||||||
|
// Это предотвращает блокировку UI-потока и DoS-атаки
|
||||||
let attempts = 0;
|
let attempts = 0;
|
||||||
const maxAttempts = 50;
|
const maxAttempts = 50;
|
||||||
while (!this.fileTransferSystem && attempts < maxAttempts) {
|
const checkInterval = 100; // 100ms между проверками
|
||||||
// Синхронное ожидание
|
const maxWaitTime = maxAttempts * checkInterval; // Максимальное время ожидания
|
||||||
const start = Date.now();
|
|
||||||
while (Date.now() - start < 100) {
|
// CRITICAL FIX: Используем Promise.race для возможности отмены операции
|
||||||
// busy wait
|
const initializationPromise = new Promise((resolve, reject) => {
|
||||||
}
|
const checkInitialization = () => {
|
||||||
attempts++;
|
// Проверяем отмену операции
|
||||||
}
|
if (abortController.signal.aborted) {
|
||||||
|
reject(new Error('Operation cancelled'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.fileTransferSystem) {
|
||||||
|
resolve(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attempts >= maxAttempts) {
|
||||||
|
reject(new Error(`Initialization timeout after ${maxWaitTime}ms`));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
attempts++;
|
||||||
|
setTimeout(checkInitialization, checkInterval);
|
||||||
|
};
|
||||||
|
|
||||||
|
checkInitialization();
|
||||||
|
});
|
||||||
|
|
||||||
|
// CRITICAL FIX: Ждем инициализации с возможностью отмены и таймаутом
|
||||||
|
await Promise.race([
|
||||||
|
initializationPromise,
|
||||||
|
new Promise((_, reject) =>
|
||||||
|
setTimeout(() => reject(new Error(`Global timeout after ${timeout}ms`)), timeout)
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
|
||||||
if (this.fileTransferSystem) {
|
if (this.fileTransferSystem) {
|
||||||
return true;
|
return true;
|
||||||
@@ -7393,10 +7479,62 @@ checkFileTransferReadiness() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this._secureLog('error', '❌ Force file transfer initialization failed:', { errorType: error?.constructor?.name || 'Unknown' });
|
// CRITICAL FIX: Улучшенная обработка ошибок
|
||||||
|
if (error.name === 'AbortError' || error.message.includes('cancelled')) {
|
||||||
|
this._secureLog('info', '⏹️ File transfer initialization cancelled by user');
|
||||||
|
return { cancelled: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
this._secureLog('error', '❌ Force file transfer initialization failed:', {
|
||||||
|
errorType: error?.constructor?.name || 'Unknown',
|
||||||
|
message: error.message,
|
||||||
|
attempts: attempts
|
||||||
|
});
|
||||||
|
return { error: error.message, attempts: attempts };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CRITICAL FIX: Метод для отмены инициализации файловой системы
|
||||||
|
cancelFileTransferInitialization() {
|
||||||
|
try {
|
||||||
|
if (this.fileTransferSystem) {
|
||||||
|
this.fileTransferSystem.cleanup();
|
||||||
|
this.fileTransferSystem = null;
|
||||||
|
this._secureLog('info', '⏹️ File transfer initialization cancelled');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} catch (error) {
|
||||||
|
this._secureLog('error', '❌ Failed to cancel file transfer initialization:', {
|
||||||
|
errorType: error?.constructor?.name || 'Unknown'
|
||||||
|
});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CRITICAL FIX: Validate nested encryption security
|
||||||
|
_validateNestedEncryptionSecurity() {
|
||||||
|
if (this.securityFeatures.hasNestedEncryption && this.nestedEncryptionKey) {
|
||||||
|
// Test that we can generate fresh random IVs
|
||||||
|
try {
|
||||||
|
const testIV1 = crypto.getRandomValues(new Uint8Array(EnhancedSecureWebRTCManager.SIZES.NESTED_ENCRYPTION_IV_SIZE));
|
||||||
|
const testIV2 = crypto.getRandomValues(new Uint8Array(EnhancedSecureWebRTCManager.SIZES.NESTED_ENCRYPTION_IV_SIZE));
|
||||||
|
|
||||||
|
// Verify IVs are different (very unlikely to be the same with 96-bit random)
|
||||||
|
if (testIV1.every((byte, index) => byte === testIV2[index])) {
|
||||||
|
console.error('❌ CRITICAL: Nested encryption IV generation failed - IVs are identical!');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('✅ Nested encryption security validation passed - fresh random IVs generated');
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ CRITICAL: Nested encryption security validation failed:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SecureKeyStorage {
|
class SecureKeyStorage {
|
||||||
@@ -7409,6 +7547,15 @@ class SecureKeyStorage {
|
|||||||
// Master encryption key for storage encryption
|
// Master encryption key for storage encryption
|
||||||
this._storageMasterKey = null;
|
this._storageMasterKey = null;
|
||||||
this._initializeStorageMaster();
|
this._initializeStorageMaster();
|
||||||
|
|
||||||
|
// CRITICAL FIX: Validate storage integrity after initialization
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!this.validateStorageIntegrity()) {
|
||||||
|
console.error('❌ CRITICAL: Key storage integrity check failed');
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
// CRITICAL FIX: Storage integrity validation only (nested encryption validation is in main class)
|
||||||
}
|
}
|
||||||
|
|
||||||
async _initializeStorageMaster() {
|
async _initializeStorageMaster() {
|
||||||
@@ -7443,6 +7590,11 @@ class SecureKeyStorage {
|
|||||||
// For extractable keys, proceed with encryption
|
// For extractable keys, proceed with encryption
|
||||||
const keyData = await crypto.subtle.exportKey('jwk', cryptoKey);
|
const keyData = await crypto.subtle.exportKey('jwk', cryptoKey);
|
||||||
const encryptedKeyData = await this._encryptKeyData(keyData);
|
const encryptedKeyData = await this._encryptKeyData(keyData);
|
||||||
|
|
||||||
|
// CRITICAL FIX: Validate that extractable keys are properly encrypted
|
||||||
|
if (!encryptedKeyData || encryptedKeyData.byteLength === 0) {
|
||||||
|
throw new Error('Failed to encrypt extractable key data');
|
||||||
|
}
|
||||||
|
|
||||||
// Create a storage object
|
// Create a storage object
|
||||||
const storageObject = {
|
const storageObject = {
|
||||||
@@ -7465,7 +7617,9 @@ class SecureKeyStorage {
|
|||||||
this._keyMetadata.set(keyId, {
|
this._keyMetadata.set(keyId, {
|
||||||
...metadata,
|
...metadata,
|
||||||
created: Date.now(),
|
created: Date.now(),
|
||||||
lastAccessed: Date.now()
|
lastAccessed: Date.now(),
|
||||||
|
extractable: true,
|
||||||
|
encrypted: true // CRITICAL FIX: Mark extractable keys as encrypted
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -7486,7 +7640,18 @@ class SecureKeyStorage {
|
|||||||
|
|
||||||
// For non-encrypted keys (non-extractable), return directly
|
// For non-encrypted keys (non-extractable), return directly
|
||||||
if (!metadata.encrypted) {
|
if (!metadata.encrypted) {
|
||||||
return this._keyReferences.get(keyId);
|
// CRITICAL FIX: Only non-extractable keys should be non-encrypted
|
||||||
|
if (metadata.extractable === false) {
|
||||||
|
return this._keyReferences.get(keyId);
|
||||||
|
} else {
|
||||||
|
// This should never happen - extractable keys must be encrypted
|
||||||
|
this._secureLog('error', '❌ SECURITY VIOLATION: Extractable key marked as non-encrypted', {
|
||||||
|
keyId,
|
||||||
|
extractable: metadata.extractable,
|
||||||
|
encrypted: metadata.encrypted
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// For encrypted keys, decrypt and recreate
|
// For encrypted keys, decrypt and recreate
|
||||||
@@ -7593,6 +7758,38 @@ class SecureKeyStorage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CRITICAL FIX: Validate storage integrity
|
||||||
|
validateStorageIntegrity() {
|
||||||
|
const violations = [];
|
||||||
|
|
||||||
|
for (const [keyId, metadata] of this._keyMetadata.entries()) {
|
||||||
|
// Check: extractable keys must be encrypted
|
||||||
|
if (metadata.extractable === true && metadata.encrypted !== true) {
|
||||||
|
violations.push({
|
||||||
|
keyId,
|
||||||
|
type: 'EXTRACTABLE_KEY_NOT_ENCRYPTED',
|
||||||
|
metadata
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check: non-extractable keys should not be encrypted
|
||||||
|
if (metadata.extractable === false && metadata.encrypted === true) {
|
||||||
|
violations.push({
|
||||||
|
keyId,
|
||||||
|
type: 'NON_EXTRACTABLE_KEY_ENCRYPTED',
|
||||||
|
metadata
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (violations.length > 0) {
|
||||||
|
console.error('❌ Storage integrity violations detected:', violations);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
getStorageStats() {
|
getStorageStats() {
|
||||||
return {
|
return {
|
||||||
totalKeys: this._keyReferences.size,
|
totalKeys: this._keyReferences.size,
|
||||||
|
|||||||
Reference in New Issue
Block a user