248 lines
7.6 KiB
Dart
248 lines
7.6 KiB
Dart
///
|
|
/// Crypto Isolate Manager
|
|
/// Manages worker pool for encryption/decryption operations
|
|
///
|
|
import 'dart:async';
|
|
import 'package:worker_manager/worker_manager.dart';
|
|
import 'ccc_iso_operation.dart';
|
|
import 'ccc_iso_result.dart';
|
|
import 'ccc_iso_operation_id.dart';
|
|
import 'ccc_iso.dart';
|
|
import 'cipher_constants.dart';
|
|
|
|
/// Manages isolate workers for cryptographic operations
|
|
///
|
|
/// CCC (Copius Cipher Chain) Architecture:
|
|
/// - Single encryption system for ALL data (messages, attachments, thumbnails)
|
|
/// - Uses channel UUID for key derivation scope
|
|
/// - Same key encrypts message + its attachments (bundled together)
|
|
/// - Used for both over-the-wire AND local storage encryption
|
|
///
|
|
/// FUTURE WORK: Ratchet System
|
|
/// - Per-message key derivation using Double Ratchet algorithm
|
|
/// - Keys derived from: channelUuid + messageSequence + rootKey
|
|
/// - Forward secrecy: compromise of one key doesn't reveal past messages
|
|
/// - Break-in recovery: new keys derived after compromise
|
|
///
|
|
/// Current State: Full CCC encryption with isolate workers
|
|
/// Future: Ratchet key derivation integration
|
|
class CryptoIsolateManager {
|
|
// Performance tracking
|
|
final CryptoMetrics _metrics = CryptoMetrics();
|
|
|
|
// Configuration
|
|
static const Duration OPERATION_TIMEOUT = Duration(seconds: 30);
|
|
static const int WORKER_POOL_SIZE = 4; // Fixed pool of 4 workers
|
|
|
|
bool _initialized = false;
|
|
bool _disposed = false;
|
|
|
|
/// Initialize the crypto isolate manager
|
|
Future<void> initialize() async {
|
|
if (_initialized) return;
|
|
|
|
// Initialize operation ID generator
|
|
CryptoOperationId.initialize();
|
|
|
|
// Configure worker manager with fixed pool size
|
|
await workerManager.init(isolatesCount: WORKER_POOL_SIZE);
|
|
|
|
_initialized = true;
|
|
}
|
|
|
|
/// Encrypt data with specified cipher sequence
|
|
/// In passthrough mode, returns data unchanged (for development)
|
|
///
|
|
/// [channelUuid] - Channel UUID for key derivation scope
|
|
/// [messageSequence] - Message sequence number for per-message key derivation (future: ratchet)
|
|
/// [isUserMessage] - Priority flag for worker queue
|
|
///
|
|
/// FUTURE: When ratchet is implemented, key = derive(channelRootKey, messageSequence)
|
|
/// Same key will encrypt: MsgData + all attachment files + all thumbnails for that message
|
|
Future<CryptoResult> encrypt(
|
|
List<int> plaintext, {
|
|
required String channelUuid,
|
|
int? messageSequence, // Future: used for ratchet key derivation
|
|
List<int>? cipherSequence,
|
|
Map<String, dynamic>? params,
|
|
bool isUserMessage = false,
|
|
}) async {
|
|
_ensureInitialized();
|
|
|
|
final operation = CryptoOperation.encrypt(
|
|
plaintext: plaintext,
|
|
cipherSequence: cipherSequence ?? CipherConstants.PHASE1_SEQUENCE,
|
|
params: params ?? CipherConstants.DEFAULT_CIPHER_PARAMS,
|
|
channelUuid: channelUuid,
|
|
messageSequence: messageSequence,
|
|
isUserMessage: isUserMessage,
|
|
);
|
|
|
|
return await _executeOperation(operation);
|
|
}
|
|
|
|
/// Decrypt data with specified cipher sequence
|
|
/// In passthrough mode, returns data unchanged (for development)
|
|
///
|
|
/// [channelUuid] - Channel UUID for key derivation scope
|
|
/// [messageSequence] - Message sequence number for per-message key derivation (future: ratchet)
|
|
///
|
|
/// FUTURE: When ratchet is implemented, key = derive(channelRootKey, messageSequence)
|
|
Future<CryptoResult> decrypt(
|
|
List<int> ciphertext, {
|
|
required String channelUuid,
|
|
int? messageSequence, // Future: used for ratchet key derivation
|
|
List<int>? cipherSequence,
|
|
Map<String, dynamic>? params,
|
|
}) async {
|
|
_ensureInitialized();
|
|
|
|
final operation = CryptoOperation.decrypt(
|
|
ciphertext: ciphertext,
|
|
cipherSequence: cipherSequence ?? CipherConstants.PHASE1_SEQUENCE,
|
|
params: params ?? CipherConstants.DEFAULT_CIPHER_PARAMS,
|
|
channelUuid: channelUuid,
|
|
messageSequence: messageSequence,
|
|
);
|
|
|
|
return await _executeOperation(operation);
|
|
}
|
|
|
|
/// Execute a crypto operation
|
|
Future<CryptoResult> _executeOperation(CryptoOperation operation) async {
|
|
_ensureInitialized();
|
|
|
|
if (_disposed) {
|
|
throw StateError('CryptoIsolateManager has been disposed');
|
|
}
|
|
|
|
try {
|
|
// Execute using the global workerManager with correct API
|
|
final resultMap = await workerManager.execute<Map<String, dynamic>>(
|
|
() => cryptoWorkerFunction(operation.toMap()),
|
|
priority: operation.priority,
|
|
).timeout(OPERATION_TIMEOUT);
|
|
|
|
// Deserialize result
|
|
final result = CryptoResult.fromMap(resultMap);
|
|
|
|
// Update metrics
|
|
_updateMetrics(operation, result);
|
|
|
|
return result;
|
|
} catch (error, stackTrace) {
|
|
// Create error result
|
|
final result = CryptoResult.error(
|
|
operationId: operation.id,
|
|
error: error.toString(),
|
|
stackTrace: stackTrace.toString(),
|
|
processingTime: operation.age,
|
|
workerId: 'manager_error',
|
|
);
|
|
|
|
// Update metrics
|
|
_metrics.recordError();
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
/// Update performance metrics
|
|
void _updateMetrics(CryptoOperation operation, CryptoResult result) {
|
|
if (result.success) {
|
|
if (operation.type == OperationType.encrypt) {
|
|
_metrics.recordEncryption(result.processingTime, operation.isHighPriority);
|
|
} else {
|
|
_metrics.recordDecryption(result.processingTime);
|
|
}
|
|
} else {
|
|
_metrics.recordError();
|
|
}
|
|
}
|
|
|
|
/// Get current performance metrics
|
|
CryptoMetrics get metrics => _metrics;
|
|
|
|
/// Get worker manager stats
|
|
String get workerStats {
|
|
return 'Crypto Manager Stats: ${_metrics.toString()}';
|
|
}
|
|
|
|
/// Check if manager is ready
|
|
bool get isReady => _initialized && !_disposed;
|
|
|
|
/// Ensure manager is initialized
|
|
void _ensureInitialized() {
|
|
if (!_initialized) {
|
|
throw StateError('CryptoIsolateManager must be initialized before use');
|
|
}
|
|
if (_disposed) {
|
|
throw StateError('CryptoIsolateManager has been disposed');
|
|
}
|
|
}
|
|
|
|
/// Dispose the isolate manager and clean up resources
|
|
Future<void> dispose() async {
|
|
if (_disposed) return;
|
|
|
|
_disposed = true;
|
|
|
|
// Dispose global worker manager
|
|
try {
|
|
await workerManager.dispose();
|
|
} catch (e) {
|
|
// Ignore disposal errors
|
|
}
|
|
|
|
// Clear metrics
|
|
_metrics.reset();
|
|
}
|
|
|
|
/// Create a test operation (for debugging)
|
|
Future<CryptoResult> testOperation({
|
|
String testData = 'Hello, Crypto World!',
|
|
String channelUuid = 'test-channel-uuid',
|
|
}) async {
|
|
final plaintext = testData.codeUnits;
|
|
|
|
// Encrypt
|
|
final encryptResult = await encrypt(
|
|
plaintext,
|
|
channelUuid: channelUuid,
|
|
isUserMessage: true,
|
|
);
|
|
|
|
if (!encryptResult.success) {
|
|
return encryptResult;
|
|
}
|
|
|
|
// Decrypt
|
|
final decryptResult = await decrypt(
|
|
encryptResult.data,
|
|
channelUuid: channelUuid,
|
|
);
|
|
|
|
if (!decryptResult.success) {
|
|
return decryptResult;
|
|
}
|
|
|
|
// Verify roundtrip
|
|
final decryptedText = String.fromCharCodes(decryptResult.data);
|
|
if (decryptedText == testData) {
|
|
return CryptoResult.success(
|
|
operationId: 'test_roundtrip',
|
|
data: decryptResult.data,
|
|
processingTime: encryptResult.processingTime + decryptResult.processingTime,
|
|
workerId: 'test_manager',
|
|
);
|
|
} else {
|
|
return CryptoResult.error(
|
|
operationId: 'test_roundtrip',
|
|
error: 'Roundtrip failed: expected "$testData", got "$decryptedText"',
|
|
processingTime: encryptResult.processingTime + decryptResult.processingTime,
|
|
workerId: 'test_manager',
|
|
);
|
|
}
|
|
}
|
|
}
|