322 lines
10 KiB
Dart
322 lines
10 KiB
Dart
///
|
|
/// Channel-level CCC profile derivation.
|
|
///
|
|
import 'package:letusmsg/ccc/ccc_kdf.dart';
|
|
import 'package:letusmsg/ccc/cipher_constants.dart';
|
|
import 'package:letusmsg/ccc/ccc_provider_spec.dart';
|
|
import 'package:letusmsg/data/ccc_data.dart';
|
|
|
|
/// One resolved cipher execution step for a channel CCC route.
|
|
///
|
|
/// The step identifies a primary provider and optional ordered fallbacks.
|
|
class CccRouteStep {
|
|
final int cipher;
|
|
final CccCryptoProvider provider;
|
|
final List<CccCryptoProvider> fallbackProviders;
|
|
|
|
const CccRouteStep({
|
|
required this.cipher,
|
|
required this.provider,
|
|
required this.fallbackProviders,
|
|
});
|
|
|
|
Map<String, dynamic> toMap() {
|
|
return {
|
|
'cipher': cipher,
|
|
'provider': provider.name,
|
|
'fallbackProviders': fallbackProviders.map((item) => item.name).toList(growable: false),
|
|
};
|
|
}
|
|
}
|
|
|
|
/// Concrete cipher configuration derived from per-channel `CCCData`.
|
|
class ChannelCccProfile {
|
|
final int combo;
|
|
final int iterations;
|
|
final CccExecutionMode executionMode;
|
|
final bool allowFallback;
|
|
final List<CccProviderSpec> providers;
|
|
final List<CccRouteStep> route;
|
|
final List<int> cipherSequence;
|
|
final Map<String, dynamic> cipherParams;
|
|
|
|
const ChannelCccProfile({
|
|
required this.combo,
|
|
required this.iterations,
|
|
required this.executionMode,
|
|
required this.allowFallback,
|
|
required this.providers,
|
|
required this.route,
|
|
required this.cipherSequence,
|
|
required this.cipherParams,
|
|
});
|
|
|
|
static const int _maxIterations = 16;
|
|
|
|
factory ChannelCccProfile.fromCccData(CCCData? cccData) {
|
|
final combo = cccData?.combo ?? 0;
|
|
final rawIterations = cccData?.iterrations ?? 0;
|
|
final kdfFunctionValue = normalizeCccKdfFunctionValue(cccData?.kdfFunction);
|
|
final kdfFunction = cccKdfFunctionFromInt(kdfFunctionValue);
|
|
final executionMode = cccExecutionModeFromString(cccData?.executionMode ?? 'auto');
|
|
final iterations = rawIterations <= 0
|
|
? 1
|
|
: rawIterations.clamp(1, _maxIterations);
|
|
final allowFallback = executionMode != CccExecutionMode.strict;
|
|
|
|
final providers = _providersForCombo(combo);
|
|
final route = _resolveRoute(providers, executionMode: executionMode, allowFallback: allowFallback);
|
|
final baseSequence = route.map((item) => item.cipher).toList(growable: false);
|
|
final expandedSequence = <int>[];
|
|
for (var i = 0; i < iterations; i++) {
|
|
expandedSequence.addAll(baseSequence);
|
|
}
|
|
|
|
final params = <String, dynamic>{
|
|
...CipherConstants.DEFAULT_CIPHER_PARAMS,
|
|
'ccc_combo': combo,
|
|
'ccc_iterations': iterations,
|
|
'ccc_kdf_function': kdfFunction.name,
|
|
'ccc_kdf_function_value': kdfFunction.value,
|
|
'ccc_execution_mode': executionMode.name,
|
|
'ccc_allow_fallback': allowFallback,
|
|
'ccc_providers': providers.map((provider) => provider.toMap()).toList(growable: false),
|
|
'ccc_route': route.map((step) => step.toMap()).toList(growable: false),
|
|
// Phase 3: length-hiding padding parameters
|
|
'ccc_min_padding_bytes': cccData?.minPaddingBytes ?? 32,
|
|
'ccc_block_size': cccData?.blockSize ?? 256,
|
|
};
|
|
|
|
return ChannelCccProfile(
|
|
combo: combo,
|
|
iterations: iterations,
|
|
executionMode: executionMode,
|
|
allowFallback: allowFallback,
|
|
providers: providers,
|
|
route: route,
|
|
cipherSequence: expandedSequence,
|
|
cipherParams: params,
|
|
);
|
|
}
|
|
|
|
static List<CccRouteStep> _resolveRoute(
|
|
List<CccProviderSpec> providers, {
|
|
required CccExecutionMode executionMode,
|
|
required bool allowFallback,
|
|
}) {
|
|
final strictRoute = <CccRouteStep>[];
|
|
for (final provider in providers) {
|
|
for (final cipher in provider.ciphers) {
|
|
strictRoute.add(CccRouteStep(
|
|
cipher: cipher,
|
|
provider: provider.provider,
|
|
fallbackProviders: const [],
|
|
));
|
|
}
|
|
}
|
|
|
|
if (executionMode == CccExecutionMode.strict) {
|
|
return strictRoute;
|
|
}
|
|
|
|
final optimizedRoute = <CccRouteStep>[];
|
|
for (final step in strictRoute) {
|
|
final ordered = _orderedProviderCandidates(
|
|
cipher: step.cipher,
|
|
configuredProviders: providers.map((item) => item.provider).toSet(),
|
|
executionMode: executionMode,
|
|
);
|
|
|
|
final primary = ordered.isNotEmpty ? ordered.first : step.provider;
|
|
final fallbacks = allowFallback
|
|
? ordered.where((provider) => provider != primary).toList(growable: false)
|
|
: const <CccCryptoProvider>[];
|
|
|
|
optimizedRoute.add(CccRouteStep(
|
|
cipher: step.cipher,
|
|
provider: primary,
|
|
fallbackProviders: fallbacks,
|
|
));
|
|
}
|
|
|
|
return optimizedRoute;
|
|
}
|
|
|
|
static List<CccCryptoProvider> _orderedProviderCandidates({
|
|
required int cipher,
|
|
required Set<CccCryptoProvider> configuredProviders,
|
|
required CccExecutionMode executionMode,
|
|
}) {
|
|
final configured = configuredProviders.where((provider) {
|
|
return CccProviderCatalog.supports(provider, cipher);
|
|
}).toList(growable: false);
|
|
|
|
if (executionMode == CccExecutionMode.efficient) {
|
|
final rankedConfigured = List<CccCryptoProvider>.from(configured)
|
|
..sort((a, b) {
|
|
final capA = CccProviderCatalog.capability(a, cipher);
|
|
final capB = CccProviderCatalog.capability(b, cipher);
|
|
final perf = (capB?.efficiencyScore ?? 0).compareTo(capA?.efficiencyScore ?? 0);
|
|
if (perf != 0) return perf;
|
|
return (capB?.reliabilityScore ?? 0).compareTo(capA?.reliabilityScore ?? 0);
|
|
});
|
|
return rankedConfigured;
|
|
}
|
|
|
|
final availableRanked = CccProviderCatalog.providersSupporting(cipher, availableOnly: true)
|
|
.where(configuredProviders.contains)
|
|
.toList(growable: false);
|
|
|
|
final unavailableRanked = CccProviderCatalog.providersSupporting(cipher, availableOnly: false)
|
|
.where((provider) => configuredProviders.contains(provider) && !availableRanked.contains(provider))
|
|
.toList(growable: false);
|
|
|
|
return [
|
|
...availableRanked,
|
|
...unavailableRanked,
|
|
];
|
|
}
|
|
|
|
static List<CccProviderSpec> _providersForCombo(int combo) {
|
|
switch (combo) {
|
|
case 0:
|
|
// Combo 0 is reserved as the plaintext / unencrypted legacy combo.
|
|
// It produces an empty cipher sequence so the CCC pipeline serializes
|
|
// and deserializes JSON only, with no encryption layers applied.
|
|
// Backwards-compatible with all channels created before Normal Mode.
|
|
// When Normal Mode is complete, CCCData.blank() will be updated to a
|
|
// real encrypted combo (e.g. combo 5 or higher) and combo 0 will
|
|
// remain available only for migration/legacy channels.
|
|
return [];
|
|
|
|
case 1:
|
|
return [
|
|
CccProviderSpec(
|
|
provider: CccCryptoProvider.wolfssl,
|
|
ciphers: [
|
|
CipherConstants.AES_GCM_256,
|
|
CipherConstants.CHACHA20_POLY1305,
|
|
],
|
|
),
|
|
CccProviderSpec(
|
|
provider: CccCryptoProvider.cryptography,
|
|
ciphers: [
|
|
CipherConstants.HMAC_SHA512,
|
|
CipherConstants.BLAKE2B,
|
|
],
|
|
),
|
|
];
|
|
case 2:
|
|
return [
|
|
CccProviderSpec(
|
|
provider: CccCryptoProvider.boringssl,
|
|
ciphers: [
|
|
CipherConstants.CHACHA20_POLY1305,
|
|
CipherConstants.XCHACHA20_POLY1305,
|
|
],
|
|
),
|
|
CccProviderSpec(
|
|
provider: CccCryptoProvider.cryptography,
|
|
ciphers: [
|
|
CipherConstants.AES_GCM_256,
|
|
],
|
|
),
|
|
];
|
|
case 3:
|
|
return [
|
|
CccProviderSpec(
|
|
provider: CccCryptoProvider.openssl,
|
|
ciphers: [
|
|
CipherConstants.AES_GCM_256,
|
|
],
|
|
),
|
|
CccProviderSpec(
|
|
provider: CccCryptoProvider.wolfssl,
|
|
ciphers: [
|
|
CipherConstants.CHACHA20_POLY1305,
|
|
],
|
|
),
|
|
CccProviderSpec(
|
|
provider: CccCryptoProvider.cryptography,
|
|
ciphers: [
|
|
CipherConstants.BLAKE2B,
|
|
],
|
|
),
|
|
];
|
|
case 4:
|
|
return [
|
|
CccProviderSpec(
|
|
provider: CccCryptoProvider.wolfssl,
|
|
ciphers: [
|
|
CipherConstants.XCHACHA20_POLY1305,
|
|
],
|
|
),
|
|
CccProviderSpec(
|
|
provider: CccCryptoProvider.openssl,
|
|
ciphers: [
|
|
CipherConstants.HMAC_SHA512,
|
|
],
|
|
),
|
|
CccProviderSpec(
|
|
provider: CccCryptoProvider.cryptography,
|
|
ciphers: [
|
|
CipherConstants.BLAKE2B,
|
|
],
|
|
),
|
|
];
|
|
|
|
// --- Basic / user-selectable combos (5-9) ---
|
|
|
|
case 5:
|
|
// Basic: single AES-256-GCM
|
|
return [
|
|
CccProviderSpec(
|
|
provider: CccCryptoProvider.cryptography,
|
|
ciphers: List<int>.from(CipherConstants.BASIC_AES_SEQUENCE),
|
|
),
|
|
];
|
|
case 6:
|
|
// Basic: single ChaCha20-Poly1305
|
|
return [
|
|
CccProviderSpec(
|
|
provider: CccCryptoProvider.cryptography,
|
|
ciphers: List<int>.from(CipherConstants.BASIC_CHACHA_SEQUENCE),
|
|
),
|
|
];
|
|
case 7:
|
|
// Basic: single XChaCha20-Poly1305
|
|
return [
|
|
CccProviderSpec(
|
|
provider: CccCryptoProvider.cryptography,
|
|
ciphers: List<int>.from(CipherConstants.BASIC_XCHACHA_SEQUENCE),
|
|
),
|
|
];
|
|
case 8:
|
|
// Dual AEAD: AES-256-GCM + ChaCha20-Poly1305
|
|
return [
|
|
CccProviderSpec(
|
|
provider: CccCryptoProvider.cryptography,
|
|
ciphers: List<int>.from(CipherConstants.DUAL_AEAD_SEQUENCE),
|
|
),
|
|
];
|
|
case 9:
|
|
// Triple AEAD: AES + ChaCha20 + XChaCha20
|
|
return [
|
|
CccProviderSpec(
|
|
provider: CccCryptoProvider.cryptography,
|
|
ciphers: List<int>.from(CipherConstants.TRIPLE_AEAD_SEQUENCE),
|
|
),
|
|
];
|
|
|
|
default:
|
|
// Unknown combo falls back to standard 5-layer
|
|
return [
|
|
CccProviderSpec(
|
|
provider: CccCryptoProvider.cryptography,
|
|
ciphers: List<int>.from(CipherConstants.PHASE1_SEQUENCE),
|
|
),
|
|
];
|
|
}
|
|
}
|
|
}
|