Refactored file encryption/decryption logic for P2P transfers
- Reworked the core logic for encrypting and decrypting files exchanged between users - Improved key derivation and session handling for file chunks - Enhanced integrity checks to prevent tampering and replay attacks - Work in progress: adding hardened encryption schemes and conducting fault-tolerance testing
This commit is contained in:
@@ -4053,6 +4053,18 @@ handleSystemMessage(message) {
|
|||||||
|
|
||||||
console.log('✅ Session activation handled successfully');
|
console.log('✅ Session activation handled successfully');
|
||||||
|
|
||||||
|
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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Failed to handle session activation:', error);
|
console.error('❌ Failed to handle session activation:', error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,9 +32,8 @@ class EnhancedSecureFileTransfer {
|
|||||||
this.transferQueue = []; // Queue for pending transfers
|
this.transferQueue = []; // Queue for pending transfers
|
||||||
this.pendingChunks = new Map();
|
this.pendingChunks = new Map();
|
||||||
|
|
||||||
// Session key derivation - КРИТИЧЕСКОЕ ИСПРАВЛЕНИЕ
|
// Session key derivation
|
||||||
this.sessionKeys = new Map(); // fileId -> derived session key
|
this.sessionKeys = new Map(); // fileId -> derived session key
|
||||||
this.sharedSecretCache = new Map(); // Кэш для shared secret чтобы sender и receiver использовали одинаковый
|
|
||||||
|
|
||||||
// Security
|
// Security
|
||||||
this.processedChunks = new Set(); // Prevent replay attacks
|
this.processedChunks = new Set(); // Prevent replay attacks
|
||||||
@@ -47,138 +46,67 @@ class EnhancedSecureFileTransfer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
// КРИТИЧЕСКОЕ ИСПРАВЛЕНИЕ: ДЕТЕРМИНИСТИЧЕСКОЕ СОЗДАНИЕ КЛЮЧЕЙ
|
// SIMPLIFIED KEY DERIVATION - USE SHARED DATA
|
||||||
// ============================================
|
// ============================================
|
||||||
|
|
||||||
async createDeterministicSharedSecret(fileId, fileSize, salt = null) {
|
async deriveFileSessionKey(fileId) {
|
||||||
try {
|
|
||||||
console.log('🔑 Creating deterministic shared secret for:', fileId);
|
|
||||||
|
|
||||||
// Создаем уникальную строку-идентификатор для файла
|
|
||||||
const fileIdentifier = `${fileId}-${fileSize}`;
|
|
||||||
|
|
||||||
// Проверяем кэш
|
|
||||||
if (this.sharedSecretCache.has(fileIdentifier)) {
|
|
||||||
console.log('✅ Using cached shared secret for:', fileIdentifier);
|
|
||||||
return this.sharedSecretCache.get(fileIdentifier);
|
|
||||||
}
|
|
||||||
|
|
||||||
const encoder = new TextEncoder();
|
|
||||||
let seedComponents = [];
|
|
||||||
|
|
||||||
// 1. Добавляем fileId и размер файла (одинаково у отправителя и получателя)
|
|
||||||
seedComponents.push(encoder.encode(fileIdentifier));
|
|
||||||
|
|
||||||
// 2. Пытаемся использовать существующие ключи сессии
|
|
||||||
if (this.webrtcManager.encryptionKey) {
|
|
||||||
try {
|
|
||||||
// Создаем детерминистическую производную из существующего ключа шифрования
|
|
||||||
const keyMaterial = encoder.encode(`FileTransfer-Session-${fileIdentifier}`);
|
|
||||||
const derivedKeyMaterial = await crypto.subtle.sign(
|
|
||||||
'HMAC',
|
|
||||||
this.webrtcManager.macKey, // Используем MAC ключ для HMAC
|
|
||||||
keyMaterial
|
|
||||||
);
|
|
||||||
seedComponents.push(new Uint8Array(derivedKeyMaterial));
|
|
||||||
console.log('✅ Used session MAC key for deterministic seed');
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('⚠️ Could not use MAC key, using alternative approach:', error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Добавляем соль если есть (от sender к receiver)
|
|
||||||
if (salt && Array.isArray(salt)) {
|
|
||||||
seedComponents.push(new Uint8Array(salt));
|
|
||||||
console.log('✅ Added salt to deterministic seed');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. Если нет других источников, используем fingerprint сессии
|
|
||||||
if (this.webrtcManager.keyFingerprint) {
|
|
||||||
seedComponents.push(encoder.encode(this.webrtcManager.keyFingerprint));
|
|
||||||
console.log('✅ Added session fingerprint to seed');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Объединяем все компоненты
|
|
||||||
const totalLength = seedComponents.reduce((sum, comp) => sum + comp.length, 0);
|
|
||||||
const combinedSeed = new Uint8Array(totalLength);
|
|
||||||
let offset = 0;
|
|
||||||
|
|
||||||
for (const component of seedComponents) {
|
|
||||||
combinedSeed.set(component, offset);
|
|
||||||
offset += component.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Хешируем для получения консистентной длины
|
|
||||||
const sharedSecret = await crypto.subtle.digest('SHA-384', combinedSeed);
|
|
||||||
|
|
||||||
// Кэшируем результат
|
|
||||||
this.sharedSecretCache.set(fileIdentifier, sharedSecret);
|
|
||||||
|
|
||||||
console.log('🔑 Created deterministic shared secret, length:', sharedSecret.byteLength);
|
|
||||||
return sharedSecret;
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ Failed to create deterministic shared secret:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================
|
|
||||||
// ИСПРАВЛЕННЫЙ МЕТОД СОЗДАНИЯ КЛЮЧА СЕССИИ
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
async deriveFileSessionKey(fileId, fileSize, providedSalt = null) {
|
|
||||||
try {
|
try {
|
||||||
console.log('🔑 Deriving file session key for:', fileId);
|
console.log('🔑 Deriving file session key for:', fileId);
|
||||||
|
|
||||||
// Получаем детерминистический shared secret
|
// КРИТИЧЕСКОЕ ИСПРАВЛЕНИЕ: Используем keyFingerprint и sessionSalt
|
||||||
const sharedSecret = await this.createDeterministicSharedSecret(fileId, fileSize, providedSalt);
|
// которые уже согласованы между пирами
|
||||||
|
|
||||||
// Создаем или используем предоставленную соль
|
if (!this.webrtcManager.keyFingerprint || !this.webrtcManager.sessionSalt) {
|
||||||
let salt;
|
throw new Error('WebRTC session data not available');
|
||||||
if (providedSalt && Array.isArray(providedSalt)) {
|
|
||||||
salt = new Uint8Array(providedSalt);
|
|
||||||
console.log('🔑 Using provided salt from metadata');
|
|
||||||
} else {
|
|
||||||
salt = crypto.getRandomValues(new Uint8Array(32));
|
|
||||||
console.log('🔑 Generated new salt for file transfer');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Импортируем shared secret как PBKDF2 ключ
|
// Генерируем соль для этого конкретного файла
|
||||||
const keyForDerivation = await crypto.subtle.importKey(
|
const fileSalt = crypto.getRandomValues(new Uint8Array(32));
|
||||||
'raw',
|
|
||||||
sharedSecret,
|
// Создаем seed из согласованных данных
|
||||||
{ name: 'PBKDF2' },
|
const encoder = new TextEncoder();
|
||||||
false,
|
const fingerprintData = encoder.encode(this.webrtcManager.keyFingerprint);
|
||||||
['deriveKey']
|
const fileIdData = encoder.encode(fileId);
|
||||||
|
|
||||||
|
// Объединяем все компоненты для создания уникального seed
|
||||||
|
const sessionSaltArray = new Uint8Array(this.webrtcManager.sessionSalt);
|
||||||
|
const combinedSeed = new Uint8Array(
|
||||||
|
fingerprintData.length +
|
||||||
|
sessionSaltArray.length +
|
||||||
|
fileSalt.length +
|
||||||
|
fileIdData.length
|
||||||
);
|
);
|
||||||
|
|
||||||
// Создаем файловый ключ сессии с PBKDF2
|
let offset = 0;
|
||||||
const fileSessionKey = await crypto.subtle.deriveKey(
|
combinedSeed.set(fingerprintData, offset);
|
||||||
{
|
offset += fingerprintData.length;
|
||||||
name: 'PBKDF2',
|
combinedSeed.set(sessionSaltArray, offset);
|
||||||
salt: salt,
|
offset += sessionSaltArray.length;
|
||||||
iterations: 100000,
|
combinedSeed.set(fileSalt, offset);
|
||||||
hash: 'SHA-384'
|
offset += fileSalt.length;
|
||||||
},
|
combinedSeed.set(fileIdData, offset);
|
||||||
keyForDerivation,
|
|
||||||
{
|
// Хешируем для получения ключевого материала
|
||||||
name: 'AES-GCM',
|
const keyMaterial = await crypto.subtle.digest('SHA-256', combinedSeed);
|
||||||
length: 256
|
|
||||||
},
|
// Импортируем как AES ключ напрямую
|
||||||
|
const fileSessionKey = await crypto.subtle.importKey(
|
||||||
|
'raw',
|
||||||
|
keyMaterial,
|
||||||
|
{ name: 'AES-GCM' },
|
||||||
false,
|
false,
|
||||||
['encrypt', 'decrypt']
|
['encrypt', 'decrypt']
|
||||||
);
|
);
|
||||||
|
|
||||||
// Сохраняем ключ сессии
|
// Сохраняем ключ и соль
|
||||||
this.sessionKeys.set(fileId, {
|
this.sessionKeys.set(fileId, {
|
||||||
key: fileSessionKey,
|
key: fileSessionKey,
|
||||||
salt: Array.from(salt),
|
salt: Array.from(fileSalt),
|
||||||
created: Date.now()
|
created: Date.now()
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('✅ File session key derived successfully for:', fileId);
|
console.log('✅ File session key derived successfully for:', fileId);
|
||||||
return { key: fileSessionKey, salt: Array.from(salt) };
|
return { key: fileSessionKey, salt: Array.from(fileSalt) };
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Failed to derive file session key:', error);
|
console.error('❌ Failed to derive file session key:', error);
|
||||||
@@ -186,45 +114,53 @@ class EnhancedSecureFileTransfer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================
|
async deriveFileSessionKeyFromSalt(fileId, saltArray) {
|
||||||
// ИСПРАВЛЕННЫЙ МЕТОД ДЛЯ ПОЛУЧАТЕЛЯ
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
async deriveFileSessionKeyFromSalt(fileId, fileSize, saltArray) {
|
|
||||||
try {
|
try {
|
||||||
console.log('🔑 Deriving session key from salt for receiver:', fileId);
|
console.log('🔑 Deriving session key from salt for receiver:', fileId);
|
||||||
|
|
||||||
if (!saltArray || !Array.isArray(saltArray)) {
|
// Проверка соли
|
||||||
throw new Error('Invalid salt provided for key derivation');
|
if (!saltArray || !Array.isArray(saltArray) || saltArray.length !== 32) {
|
||||||
|
throw new Error(`Invalid salt: ${saltArray?.length || 0} bytes`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Получаем тот же детерминистический shared secret что и отправитель
|
if (!this.webrtcManager.keyFingerprint || !this.webrtcManager.sessionSalt) {
|
||||||
const sharedSecret = await this.createDeterministicSharedSecret(fileId, fileSize, saltArray);
|
throw new Error('WebRTC session data not available');
|
||||||
|
}
|
||||||
|
|
||||||
const salt = new Uint8Array(saltArray);
|
// Используем тот же процесс что и отправитель
|
||||||
|
const encoder = new TextEncoder();
|
||||||
|
const fingerprintData = encoder.encode(this.webrtcManager.keyFingerprint);
|
||||||
|
const fileIdData = encoder.encode(fileId);
|
||||||
|
|
||||||
// Импортируем shared secret как PBKDF2 ключ
|
// Используем полученную соль файла
|
||||||
const keyForDerivation = await crypto.subtle.importKey(
|
const fileSalt = new Uint8Array(saltArray);
|
||||||
'raw',
|
const sessionSaltArray = new Uint8Array(this.webrtcManager.sessionSalt);
|
||||||
sharedSecret,
|
|
||||||
{ name: 'PBKDF2' },
|
// Объединяем компоненты в том же порядке
|
||||||
false,
|
const combinedSeed = new Uint8Array(
|
||||||
['deriveKey']
|
fingerprintData.length +
|
||||||
|
sessionSaltArray.length +
|
||||||
|
fileSalt.length +
|
||||||
|
fileIdData.length
|
||||||
);
|
);
|
||||||
|
|
||||||
// Создаем точно такой же ключ как у отправителя
|
let offset = 0;
|
||||||
const fileSessionKey = await crypto.subtle.deriveKey(
|
combinedSeed.set(fingerprintData, offset);
|
||||||
{
|
offset += fingerprintData.length;
|
||||||
name: 'PBKDF2',
|
combinedSeed.set(sessionSaltArray, offset);
|
||||||
salt: salt,
|
offset += sessionSaltArray.length;
|
||||||
iterations: 100000, // Те же параметры что у отправителя
|
combinedSeed.set(fileSalt, offset);
|
||||||
hash: 'SHA-384'
|
offset += fileSalt.length;
|
||||||
},
|
combinedSeed.set(fileIdData, offset);
|
||||||
keyForDerivation,
|
|
||||||
{
|
// Хешируем для получения того же ключевого материала
|
||||||
name: 'AES-GCM',
|
const keyMaterial = await crypto.subtle.digest('SHA-256', combinedSeed);
|
||||||
length: 256
|
|
||||||
},
|
// Импортируем как AES ключ
|
||||||
|
const fileSessionKey = await crypto.subtle.importKey(
|
||||||
|
'raw',
|
||||||
|
keyMaterial,
|
||||||
|
{ name: 'AES-GCM' },
|
||||||
false,
|
false,
|
||||||
['encrypt', 'decrypt']
|
['encrypt', 'decrypt']
|
||||||
);
|
);
|
||||||
@@ -260,7 +196,6 @@ class EnhancedSecureFileTransfer {
|
|||||||
webrtcManagerType: this.webrtcManager.constructor?.name,
|
webrtcManagerType: this.webrtcManager.constructor?.name,
|
||||||
hasEncryptionKey: !!this.webrtcManager.encryptionKey,
|
hasEncryptionKey: !!this.webrtcManager.encryptionKey,
|
||||||
hasMacKey: !!this.webrtcManager.macKey,
|
hasMacKey: !!this.webrtcManager.macKey,
|
||||||
hasEcdhKeyPair: !!this.webrtcManager.ecdhKeyPair,
|
|
||||||
isConnected: this.webrtcManager.isConnected?.(),
|
isConnected: this.webrtcManager.isConnected?.(),
|
||||||
isVerified: this.webrtcManager.isVerified
|
isVerified: this.webrtcManager.isVerified
|
||||||
});
|
});
|
||||||
@@ -284,8 +219,8 @@ class EnhancedSecureFileTransfer {
|
|||||||
// Calculate file hash for integrity verification
|
// Calculate file hash for integrity verification
|
||||||
const fileHash = await this.calculateFileHash(file);
|
const fileHash = await this.calculateFileHash(file);
|
||||||
|
|
||||||
// Derive session key for this file - ИСПРАВЛЕНИЕ
|
// Derive session key for this file
|
||||||
const keyResult = await this.deriveFileSessionKey(fileId, file.size);
|
const keyResult = await this.deriveFileSessionKey(fileId);
|
||||||
const sessionKey = keyResult.key;
|
const sessionKey = keyResult.key;
|
||||||
const salt = keyResult.salt;
|
const salt = keyResult.salt;
|
||||||
|
|
||||||
@@ -323,7 +258,6 @@ class EnhancedSecureFileTransfer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ИСПРАВЛЕННЫЙ метод отправки метаданных
|
|
||||||
async sendFileMetadata(transferState) {
|
async sendFileMetadata(transferState) {
|
||||||
try {
|
try {
|
||||||
const metadata = {
|
const metadata = {
|
||||||
@@ -337,7 +271,7 @@ class EnhancedSecureFileTransfer {
|
|||||||
chunkSize: this.CHUNK_SIZE,
|
chunkSize: this.CHUNK_SIZE,
|
||||||
salt: transferState.salt, // Отправляем соль получателю
|
salt: transferState.salt, // Отправляем соль получателю
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
version: '1.0'
|
version: '2.0'
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('📁 Sending file metadata for:', transferState.file.name);
|
console.log('📁 Sending file metadata for:', transferState.file.name);
|
||||||
@@ -366,7 +300,6 @@ class EnhancedSecureFileTransfer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start chunk transmission
|
|
||||||
async startChunkTransmission(transferState) {
|
async startChunkTransmission(transferState) {
|
||||||
try {
|
try {
|
||||||
transferState.status = 'transmitting';
|
transferState.status = 'transmitting';
|
||||||
@@ -426,7 +359,6 @@ class EnhancedSecureFileTransfer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read file chunk
|
|
||||||
async readFileChunk(file, start, end) {
|
async readFileChunk(file, start, end) {
|
||||||
try {
|
try {
|
||||||
const blob = file.slice(start, end);
|
const blob = file.slice(start, end);
|
||||||
@@ -437,7 +369,6 @@ class EnhancedSecureFileTransfer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send file chunk
|
|
||||||
async sendFileChunk(transferState, chunkIndex, chunkData) {
|
async sendFileChunk(transferState, chunkIndex, chunkData) {
|
||||||
try {
|
try {
|
||||||
const sessionKey = transferState.sessionKey;
|
const sessionKey = transferState.sessionKey;
|
||||||
@@ -473,10 +404,9 @@ class EnhancedSecureFileTransfer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send secure message through WebRTC
|
|
||||||
async sendSecureMessage(message) {
|
async sendSecureMessage(message) {
|
||||||
try {
|
try {
|
||||||
// Send through existing Double Ratchet channel
|
// Send through existing WebRTC channel
|
||||||
const messageString = JSON.stringify(message);
|
const messageString = JSON.stringify(message);
|
||||||
|
|
||||||
// Use the WebRTC manager's sendMessage method
|
// Use the WebRTC manager's sendMessage method
|
||||||
@@ -491,11 +421,10 @@ class EnhancedSecureFileTransfer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate file hash for integrity verification
|
|
||||||
async calculateFileHash(file) {
|
async calculateFileHash(file) {
|
||||||
try {
|
try {
|
||||||
const arrayBuffer = await file.arrayBuffer();
|
const arrayBuffer = await file.arrayBuffer();
|
||||||
const hashBuffer = await crypto.subtle.digest('SHA-384', arrayBuffer);
|
const hashBuffer = await crypto.subtle.digest('SHA-256', arrayBuffer);
|
||||||
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
||||||
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -509,49 +438,10 @@ class EnhancedSecureFileTransfer {
|
|||||||
// ============================================
|
// ============================================
|
||||||
|
|
||||||
setupFileMessageHandlers() {
|
setupFileMessageHandlers() {
|
||||||
// Store original message handler
|
// This is now handled by WebRTC manager's processMessage method
|
||||||
const originalHandler = this.webrtcManager.onMessage;
|
// No need to override onMessage here
|
||||||
|
|
||||||
// Wrap message handler to intercept file transfer messages
|
|
||||||
this.webrtcManager.onMessage = (message, type) => {
|
|
||||||
try {
|
|
||||||
// Try to parse as JSON for file transfer messages
|
|
||||||
if (typeof message === 'string' && message.startsWith('{')) {
|
|
||||||
const parsed = JSON.parse(message);
|
|
||||||
|
|
||||||
switch (parsed.type) {
|
|
||||||
case 'file_transfer_start':
|
|
||||||
this.handleFileTransferStart(parsed);
|
|
||||||
return;
|
|
||||||
case 'file_chunk':
|
|
||||||
this.handleFileChunk(parsed);
|
|
||||||
return;
|
|
||||||
case 'file_transfer_response':
|
|
||||||
this.handleTransferResponse(parsed);
|
|
||||||
return;
|
|
||||||
case 'chunk_confirmation':
|
|
||||||
this.handleChunkConfirmation(parsed);
|
|
||||||
return;
|
|
||||||
case 'file_transfer_complete':
|
|
||||||
this.handleTransferComplete(parsed);
|
|
||||||
return;
|
|
||||||
case 'file_transfer_error':
|
|
||||||
this.handleTransferError(parsed);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// Not a file transfer message, continue with normal handling
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pass to original handler for regular messages
|
|
||||||
if (originalHandler) {
|
|
||||||
originalHandler(message, type);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// ИСПРАВЛЕННЫЙ Handle incoming file transfer start
|
|
||||||
async handleFileTransferStart(metadata) {
|
async handleFileTransferStart(metadata) {
|
||||||
try {
|
try {
|
||||||
console.log('📥 Receiving file transfer:', metadata.fileName);
|
console.log('📥 Receiving file transfer:', metadata.fileName);
|
||||||
@@ -567,10 +457,9 @@ class EnhancedSecureFileTransfer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// КРИТИЧЕСКОЕ ИСПРАВЛЕНИЕ: Используем соль из метаданных
|
// Derive session key from salt
|
||||||
const sessionKey = await this.deriveFileSessionKeyFromSalt(
|
const sessionKey = await this.deriveFileSessionKeyFromSalt(
|
||||||
metadata.fileId,
|
metadata.fileId,
|
||||||
metadata.fileSize,
|
|
||||||
metadata.salt
|
metadata.salt
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -584,6 +473,7 @@ class EnhancedSecureFileTransfer {
|
|||||||
totalChunks: metadata.totalChunks,
|
totalChunks: metadata.totalChunks,
|
||||||
chunkSize: metadata.chunkSize || this.CHUNK_SIZE,
|
chunkSize: metadata.chunkSize || this.CHUNK_SIZE,
|
||||||
sessionKey: sessionKey,
|
sessionKey: sessionKey,
|
||||||
|
salt: metadata.salt,
|
||||||
receivedChunks: new Map(),
|
receivedChunks: new Map(),
|
||||||
receivedCount: 0,
|
receivedCount: 0,
|
||||||
startTime: Date.now(),
|
startTime: Date.now(),
|
||||||
@@ -632,7 +522,6 @@ class EnhancedSecureFileTransfer {
|
|||||||
console.error('❌ Failed to handle file transfer start:', error);
|
console.error('❌ Failed to handle file transfer start:', error);
|
||||||
|
|
||||||
// Send error response
|
// Send error response
|
||||||
try {
|
|
||||||
const errorResponse = {
|
const errorResponse = {
|
||||||
type: 'file_transfer_response',
|
type: 'file_transfer_response',
|
||||||
fileId: metadata.fileId,
|
fileId: metadata.fileId,
|
||||||
@@ -641,13 +530,9 @@ class EnhancedSecureFileTransfer {
|
|||||||
timestamp: Date.now()
|
timestamp: Date.now()
|
||||||
};
|
};
|
||||||
await this.sendSecureMessage(errorResponse);
|
await this.sendSecureMessage(errorResponse);
|
||||||
} catch (responseError) {
|
|
||||||
console.error('❌ Failed to send error response:', responseError);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ИСПРАВЛЕННЫЙ Handle incoming file chunk
|
|
||||||
async handleFileChunk(chunkMessage) {
|
async handleFileChunk(chunkMessage) {
|
||||||
try {
|
try {
|
||||||
let receivingState = this.receivingTransfers.get(chunkMessage.fileId);
|
let receivingState = this.receivingTransfers.get(chunkMessage.fileId);
|
||||||
@@ -678,20 +563,13 @@ class EnhancedSecureFileTransfer {
|
|||||||
throw new Error(`Invalid chunk index: ${chunkMessage.chunkIndex}`);
|
throw new Error(`Invalid chunk index: ${chunkMessage.chunkIndex}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ИСПРАВЛЕНИЕ: Улучшенное декодирование чанка
|
// Decrypt chunk
|
||||||
const nonce = new Uint8Array(chunkMessage.nonce);
|
const nonce = new Uint8Array(chunkMessage.nonce);
|
||||||
const encryptedData = new Uint8Array(chunkMessage.encryptedData);
|
const encryptedData = new Uint8Array(chunkMessage.encryptedData);
|
||||||
|
|
||||||
console.log('🔓 Decrypting chunk:', chunkMessage.chunkIndex, {
|
console.log('🔓 Decrypting chunk:', chunkMessage.chunkIndex);
|
||||||
nonceLength: nonce.length,
|
|
||||||
encryptedDataLength: encryptedData.length,
|
|
||||||
expectedSize: chunkMessage.chunkSize
|
|
||||||
});
|
|
||||||
|
|
||||||
// Decrypt chunk with better error handling
|
const decryptedChunk = await crypto.subtle.decrypt(
|
||||||
let decryptedChunk;
|
|
||||||
try {
|
|
||||||
decryptedChunk = await crypto.subtle.decrypt(
|
|
||||||
{
|
{
|
||||||
name: 'AES-GCM',
|
name: 'AES-GCM',
|
||||||
iv: nonce
|
iv: nonce
|
||||||
@@ -699,28 +577,6 @@ class EnhancedSecureFileTransfer {
|
|||||||
receivingState.sessionKey,
|
receivingState.sessionKey,
|
||||||
encryptedData
|
encryptedData
|
||||||
);
|
);
|
||||||
} catch (decryptError) {
|
|
||||||
console.error('❌ Chunk decryption failed:', decryptError);
|
|
||||||
console.error('Decryption details:', {
|
|
||||||
chunkIndex: chunkMessage.chunkIndex,
|
|
||||||
fileId: chunkMessage.fileId,
|
|
||||||
nonceLength: nonce.length,
|
|
||||||
encryptedDataLength: encryptedData.length,
|
|
||||||
sessionKeyType: receivingState.sessionKey?.constructor?.name,
|
|
||||||
sessionKeyAlgorithm: receivingState.sessionKey?.algorithm?.name
|
|
||||||
});
|
|
||||||
|
|
||||||
// Send specific error message
|
|
||||||
const errorMessage = {
|
|
||||||
type: 'file_transfer_error',
|
|
||||||
fileId: chunkMessage.fileId,
|
|
||||||
error: `Chunk ${chunkMessage.chunkIndex} decryption failed: ${decryptError.message}`,
|
|
||||||
chunkIndex: chunkMessage.chunkIndex,
|
|
||||||
timestamp: Date.now()
|
|
||||||
};
|
|
||||||
await this.sendSecureMessage(errorMessage);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify chunk size
|
// Verify chunk size
|
||||||
if (decryptedChunk.byteLength !== chunkMessage.chunkSize) {
|
if (decryptedChunk.byteLength !== chunkMessage.chunkSize) {
|
||||||
@@ -766,21 +622,27 @@ class EnhancedSecureFileTransfer {
|
|||||||
console.error('❌ Failed to handle file chunk:', error);
|
console.error('❌ Failed to handle file chunk:', error);
|
||||||
|
|
||||||
// Send error notification
|
// Send error notification
|
||||||
try {
|
|
||||||
const errorMessage = {
|
const errorMessage = {
|
||||||
type: 'file_transfer_error',
|
type: 'file_transfer_error',
|
||||||
fileId: chunkMessage.fileId,
|
fileId: chunkMessage.fileId,
|
||||||
error: error.message,
|
error: error.message,
|
||||||
|
chunkIndex: chunkMessage.chunkIndex,
|
||||||
timestamp: Date.now()
|
timestamp: Date.now()
|
||||||
};
|
};
|
||||||
await this.sendSecureMessage(errorMessage);
|
await this.sendSecureMessage(errorMessage);
|
||||||
} catch (errorSendError) {
|
|
||||||
console.error('❌ Failed to send chunk error:', errorSendError);
|
// Mark transfer as failed
|
||||||
|
const receivingState = this.receivingTransfers.get(chunkMessage.fileId);
|
||||||
|
if (receivingState) {
|
||||||
|
receivingState.status = 'failed';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.onError) {
|
||||||
|
this.onError(`Chunk processing failed: ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assemble received file
|
|
||||||
async assembleFile(receivingState) {
|
async assembleFile(receivingState) {
|
||||||
try {
|
try {
|
||||||
console.log('🔄 Assembling file:', receivingState.fileName);
|
console.log('🔄 Assembling file:', receivingState.fileName);
|
||||||
@@ -863,7 +725,6 @@ class EnhancedSecureFileTransfer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Send error notification
|
// Send error notification
|
||||||
try {
|
|
||||||
const errorMessage = {
|
const errorMessage = {
|
||||||
type: 'file_transfer_complete',
|
type: 'file_transfer_complete',
|
||||||
fileId: receivingState.fileId,
|
fileId: receivingState.fileId,
|
||||||
@@ -872,19 +733,15 @@ class EnhancedSecureFileTransfer {
|
|||||||
timestamp: Date.now()
|
timestamp: Date.now()
|
||||||
};
|
};
|
||||||
await this.sendSecureMessage(errorMessage);
|
await this.sendSecureMessage(errorMessage);
|
||||||
} catch (errorSendError) {
|
|
||||||
console.error('❌ Failed to send assembly error:', errorSendError);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cleanup failed transfer
|
// Cleanup failed transfer
|
||||||
this.cleanupReceivingTransfer(receivingState.fileId);
|
this.cleanupReceivingTransfer(receivingState.fileId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate hash from data
|
|
||||||
async calculateFileHashFromData(data) {
|
async calculateFileHashFromData(data) {
|
||||||
try {
|
try {
|
||||||
const hashBuffer = await crypto.subtle.digest('SHA-384', data);
|
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
|
||||||
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
||||||
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -893,7 +750,6 @@ class EnhancedSecureFileTransfer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle transfer response
|
|
||||||
handleTransferResponse(response) {
|
handleTransferResponse(response) {
|
||||||
try {
|
try {
|
||||||
console.log('📨 File transfer response:', response);
|
console.log('📨 File transfer response:', response);
|
||||||
@@ -923,7 +779,6 @@ class EnhancedSecureFileTransfer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle chunk confirmation
|
|
||||||
handleChunkConfirmation(confirmation) {
|
handleChunkConfirmation(confirmation) {
|
||||||
try {
|
try {
|
||||||
const transferState = this.activeTransfers.get(confirmation.fileId);
|
const transferState = this.activeTransfers.get(confirmation.fileId);
|
||||||
@@ -940,7 +795,6 @@ class EnhancedSecureFileTransfer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle transfer completion
|
|
||||||
handleTransferComplete(completion) {
|
handleTransferComplete(completion) {
|
||||||
try {
|
try {
|
||||||
console.log('🏁 Transfer completion:', completion);
|
console.log('🏁 Transfer completion:', completion);
|
||||||
@@ -980,7 +834,6 @@ class EnhancedSecureFileTransfer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle transfer error
|
|
||||||
handleTransferError(errorMessage) {
|
handleTransferError(errorMessage) {
|
||||||
try {
|
try {
|
||||||
console.error('❌ Transfer error received:', errorMessage);
|
console.error('❌ Transfer error received:', errorMessage);
|
||||||
@@ -1010,7 +863,6 @@ class EnhancedSecureFileTransfer {
|
|||||||
// UTILITY METHODS
|
// UTILITY METHODS
|
||||||
// ============================================
|
// ============================================
|
||||||
|
|
||||||
// Get active transfers
|
|
||||||
getActiveTransfers() {
|
getActiveTransfers() {
|
||||||
return Array.from(this.activeTransfers.values()).map(transfer => ({
|
return Array.from(this.activeTransfers.values()).map(transfer => ({
|
||||||
fileId: transfer.fileId,
|
fileId: transfer.fileId,
|
||||||
@@ -1022,7 +874,6 @@ class EnhancedSecureFileTransfer {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get receiving transfers
|
|
||||||
getReceivingTransfers() {
|
getReceivingTransfers() {
|
||||||
return Array.from(this.receivingTransfers.values()).map(transfer => ({
|
return Array.from(this.receivingTransfers.values()).map(transfer => ({
|
||||||
fileId: transfer.fileId,
|
fileId: transfer.fileId,
|
||||||
@@ -1034,7 +885,6 @@ class EnhancedSecureFileTransfer {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cancel transfer
|
|
||||||
cancelTransfer(fileId) {
|
cancelTransfer(fileId) {
|
||||||
try {
|
try {
|
||||||
if (this.activeTransfers.has(fileId)) {
|
if (this.activeTransfers.has(fileId)) {
|
||||||
@@ -1052,19 +902,11 @@ class EnhancedSecureFileTransfer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup transfer
|
|
||||||
cleanupTransfer(fileId) {
|
cleanupTransfer(fileId) {
|
||||||
this.activeTransfers.delete(fileId);
|
this.activeTransfers.delete(fileId);
|
||||||
this.sessionKeys.delete(fileId);
|
this.sessionKeys.delete(fileId);
|
||||||
this.transferNonces.delete(fileId);
|
this.transferNonces.delete(fileId);
|
||||||
|
|
||||||
// Remove from shared secret cache
|
|
||||||
const transfers = this.activeTransfers.get(fileId) || this.receivingTransfers.get(fileId);
|
|
||||||
if (transfers && transfers.file) {
|
|
||||||
const fileIdentifier = `${fileId}-${transfers.file.size}`;
|
|
||||||
this.sharedSecretCache.delete(fileIdentifier);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove processed chunk IDs for this transfer
|
// Remove processed chunk IDs for this transfer
|
||||||
for (const chunkId of this.processedChunks) {
|
for (const chunkId of this.processedChunks) {
|
||||||
if (chunkId.startsWith(fileId)) {
|
if (chunkId.startsWith(fileId)) {
|
||||||
@@ -1073,17 +915,12 @@ class EnhancedSecureFileTransfer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup receiving transfer
|
|
||||||
cleanupReceivingTransfer(fileId) {
|
cleanupReceivingTransfer(fileId) {
|
||||||
this.pendingChunks.delete(fileId);
|
this.pendingChunks.delete(fileId);
|
||||||
const receivingState = this.receivingTransfers.get(fileId);
|
const receivingState = this.receivingTransfers.get(fileId);
|
||||||
if (receivingState) {
|
if (receivingState) {
|
||||||
// Clear chunk data from memory
|
// Clear chunk data from memory
|
||||||
receivingState.receivedChunks.clear();
|
receivingState.receivedChunks.clear();
|
||||||
|
|
||||||
// Remove from shared secret cache
|
|
||||||
const fileIdentifier = `${fileId}-${receivingState.fileSize}`;
|
|
||||||
this.sharedSecretCache.delete(fileIdentifier);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.receivingTransfers.delete(fileId);
|
this.receivingTransfers.delete(fileId);
|
||||||
@@ -1097,7 +934,6 @@ class EnhancedSecureFileTransfer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get transfer status
|
|
||||||
getTransferStatus(fileId) {
|
getTransferStatus(fileId) {
|
||||||
if (this.activeTransfers.has(fileId)) {
|
if (this.activeTransfers.has(fileId)) {
|
||||||
const transfer = this.activeTransfers.get(fileId);
|
const transfer = this.activeTransfers.get(fileId);
|
||||||
@@ -1126,7 +962,6 @@ class EnhancedSecureFileTransfer {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get system status
|
|
||||||
getSystemStatus() {
|
getSystemStatus() {
|
||||||
return {
|
return {
|
||||||
initialized: true,
|
initialized: true,
|
||||||
@@ -1137,12 +972,10 @@ class EnhancedSecureFileTransfer {
|
|||||||
maxFileSize: this.MAX_FILE_SIZE,
|
maxFileSize: this.MAX_FILE_SIZE,
|
||||||
chunkSize: this.CHUNK_SIZE,
|
chunkSize: this.CHUNK_SIZE,
|
||||||
hasWebrtcManager: !!this.webrtcManager,
|
hasWebrtcManager: !!this.webrtcManager,
|
||||||
isConnected: this.webrtcManager?.isConnected?.() || false,
|
isConnected: this.webrtcManager?.isConnected?.() || false
|
||||||
sharedSecretCacheSize: this.sharedSecretCache.size
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup all transfers (called on disconnect)
|
|
||||||
cleanup() {
|
cleanup() {
|
||||||
console.log('🧹 Cleaning up file transfer system');
|
console.log('🧹 Cleaning up file transfer system');
|
||||||
|
|
||||||
@@ -1163,123 +996,75 @@ class EnhancedSecureFileTransfer {
|
|||||||
this.sessionKeys.clear();
|
this.sessionKeys.clear();
|
||||||
this.transferNonces.clear();
|
this.transferNonces.clear();
|
||||||
this.processedChunks.clear();
|
this.processedChunks.clear();
|
||||||
this.sharedSecretCache.clear(); // Очищаем кэш shared secret
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// SESSION UPDATE HANDLER - FIXED
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
onSessionUpdate(sessionData) {
|
||||||
|
console.log('🔄 File transfer system: session updated', sessionData);
|
||||||
|
|
||||||
|
// Clear session keys cache for resync
|
||||||
|
this.sessionKeys.clear();
|
||||||
|
|
||||||
|
console.log('✅ File transfer keys cache cleared for resync');
|
||||||
|
|
||||||
|
// If there are active transfers, log warning
|
||||||
|
if (this.activeTransfers.size > 0 || this.receivingTransfers.size > 0) {
|
||||||
|
console.warn('⚠️ Session updated during active file transfers - may cause issues');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
// DEBUGGING AND DIAGNOSTICS
|
// DEBUGGING AND DIAGNOSTICS
|
||||||
// ============================================
|
// ============================================
|
||||||
|
|
||||||
// Debug method to check key derivation
|
async debugKeyDerivation(fileId) {
|
||||||
async debugKeyDerivation(fileId, fileSize, salt = null) {
|
|
||||||
try {
|
try {
|
||||||
console.log('🔍 Debug: Testing key derivation for:', fileId);
|
console.log('🔍 Debug: Testing key derivation for:', fileId);
|
||||||
|
|
||||||
const sharedSecret = await this.createDeterministicSharedSecret(fileId, fileSize, salt);
|
if (!this.webrtcManager.macKey) {
|
||||||
console.log('🔍 Shared secret created, length:', sharedSecret.byteLength);
|
throw new Error('MAC key not available');
|
||||||
|
|
||||||
const testSalt = salt ? new Uint8Array(salt) : crypto.getRandomValues(new Uint8Array(32));
|
|
||||||
console.log('🔍 Using salt, length:', testSalt.length);
|
|
||||||
|
|
||||||
const keyForDerivation = await crypto.subtle.importKey(
|
|
||||||
'raw',
|
|
||||||
sharedSecret,
|
|
||||||
{ name: 'PBKDF2' },
|
|
||||||
false,
|
|
||||||
['deriveKey']
|
|
||||||
);
|
|
||||||
|
|
||||||
const derivedKey = await crypto.subtle.deriveKey(
|
|
||||||
{
|
|
||||||
name: 'PBKDF2',
|
|
||||||
salt: testSalt,
|
|
||||||
iterations: 100000,
|
|
||||||
hash: 'SHA-384'
|
|
||||||
},
|
|
||||||
keyForDerivation,
|
|
||||||
{
|
|
||||||
name: 'AES-GCM',
|
|
||||||
length: 256
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
['encrypt', 'decrypt']
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log('✅ Key derivation test successful');
|
|
||||||
console.log('🔍 Derived key:', derivedKey.algorithm);
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
sharedSecretLength: sharedSecret.byteLength,
|
|
||||||
saltLength: testSalt.length,
|
|
||||||
keyAlgorithm: derivedKey.algorithm
|
|
||||||
};
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ Key derivation test failed:', error);
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: error.message
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debug method to verify encryption/decryption
|
const salt = crypto.getRandomValues(new Uint8Array(32));
|
||||||
async debugEncryptionDecryption(fileId, fileSize, testData = 'test data') {
|
|
||||||
try {
|
|
||||||
console.log('🔍 Debug: Testing encryption/decryption for:', fileId);
|
|
||||||
|
|
||||||
const keyResult = await this.deriveFileSessionKey(fileId, fileSize);
|
// Test sender derivation
|
||||||
const sessionKey = keyResult.key;
|
const senderResult = await this.deriveFileSessionKey(fileId);
|
||||||
const salt = keyResult.salt;
|
console.log('✅ Sender key derived successfully');
|
||||||
|
|
||||||
// Test encryption
|
// Test receiver derivation with same salt
|
||||||
|
const receiverKey = await this.deriveFileSessionKeyFromSalt(fileId, senderResult.salt);
|
||||||
|
console.log('✅ Receiver key derived successfully');
|
||||||
|
|
||||||
|
// Test encryption/decryption
|
||||||
|
const testData = new TextEncoder().encode('test data');
|
||||||
const nonce = crypto.getRandomValues(new Uint8Array(12));
|
const nonce = crypto.getRandomValues(new Uint8Array(12));
|
||||||
const testDataBuffer = new TextEncoder().encode(testData);
|
|
||||||
|
|
||||||
const encrypted = await crypto.subtle.encrypt(
|
const encrypted = await crypto.subtle.encrypt(
|
||||||
{ name: 'AES-GCM', iv: nonce },
|
{ name: 'AES-GCM', iv: nonce },
|
||||||
sessionKey,
|
senderResult.key,
|
||||||
testDataBuffer
|
testData
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log('✅ Encryption test successful');
|
|
||||||
|
|
||||||
// Test decryption with same key
|
|
||||||
const decrypted = await crypto.subtle.decrypt(
|
const decrypted = await crypto.subtle.decrypt(
|
||||||
{ name: 'AES-GCM', iv: nonce },
|
|
||||||
sessionKey,
|
|
||||||
encrypted
|
|
||||||
);
|
|
||||||
|
|
||||||
const decryptedText = new TextDecoder().decode(decrypted);
|
|
||||||
|
|
||||||
if (decryptedText === testData) {
|
|
||||||
console.log('✅ Decryption test successful');
|
|
||||||
|
|
||||||
// Test with receiver key derivation
|
|
||||||
const receiverKey = await this.deriveFileSessionKeyFromSalt(fileId, fileSize, salt);
|
|
||||||
|
|
||||||
const decryptedByReceiver = await crypto.subtle.decrypt(
|
|
||||||
{ name: 'AES-GCM', iv: nonce },
|
{ name: 'AES-GCM', iv: nonce },
|
||||||
receiverKey,
|
receiverKey,
|
||||||
encrypted
|
encrypted
|
||||||
);
|
);
|
||||||
|
|
||||||
const receiverDecryptedText = new TextDecoder().decode(decryptedByReceiver);
|
const decryptedText = new TextDecoder().decode(decrypted);
|
||||||
|
|
||||||
if (receiverDecryptedText === testData) {
|
if (decryptedText === 'test data') {
|
||||||
console.log('✅ Receiver key derivation test successful');
|
console.log('✅ Cross-key encryption/decryption test successful');
|
||||||
return { success: true, message: 'All tests passed' };
|
return { success: true, message: 'All tests passed' };
|
||||||
} else {
|
|
||||||
throw new Error('Receiver decryption failed');
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Decryption verification failed');
|
throw new Error('Decryption verification failed');
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Encryption/decryption test failed:', error);
|
console.error('❌ Key derivation test failed:', error);
|
||||||
return { success: false, error: error.message };
|
return { success: false, error: error.message };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user