Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion lib/src/core/room.dart
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,10 @@ class Room extends DisposableChangeNotifier with EventsEmittable<RoomEvent> {
}
// ignore: deprecated_member_use_from_same_package
final e2eeOptions = roomOptions.encryption ?? roomOptions.e2eeOptions;
_e2eeManager = E2EEManager(e2eeOptions!.keyProvider, dcEncryptionEnabled: roomOptions.encryption != null);
_e2eeManager = E2EEManager(
options: e2eeOptions!,
dcEncryptionEnabled: roomOptions.encryption != null,
);
await _e2eeManager!.setup(this);
engine.setE2eeManager(_e2eeManager);
} else {
Expand Down
37 changes: 24 additions & 13 deletions lib/src/e2ee/e2ee_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,28 @@ import 'options.dart';
class E2EEManager {
Room? _room;
final Map<Map<String, String>, FrameCryptor> _frameCryptors = {};
final BaseKeyProvider _keyProvider;
final E2EEOptions _options;
BaseKeyProvider? _keyProvider;
final Algorithm _algorithm = Algorithm.kAesGcm;
DataPacketCryptor? _dataPacketCryptor;
bool _enabled = true;
bool _encryptionEnabled = false;
EventsListener<RoomEvent>? _listener;
E2EEManager(this._keyProvider, {bool dcEncryptionEnabled = false}) {
E2EEManager({
required E2EEOptions options,
bool dcEncryptionEnabled = false,
}) : _options = options {
_encryptionEnabled = dcEncryptionEnabled;
}

static Future<BaseKeyProvider> _buildSharedKeyProvider(String key) async {
final provider = await BaseKeyProvider.create();
await provider.setSharedKey(key);
return provider;
}

Future<void> setup(Room room) async {
_keyProvider ??= _options.keyProviderOrNull ?? await _buildSharedKeyProvider(_options.sharedKey!);
if (_room != room) {
await _cleanUp();
_room = room;
Expand Down Expand Up @@ -117,20 +128,20 @@ class E2EEManager {
}
});
_dataPacketCryptor ??= await dataPacketCryptorFactory.createDataPacketCryptor(
algorithm: _algorithm, keyProvider: _keyProvider.keyProvider);
algorithm: _algorithm, keyProvider: _keyProvider!.keyProvider);
}
}

BaseKeyProvider get keyProvider => _keyProvider;
BaseKeyProvider get keyProvider => _keyProvider!;

Future<void> ratchetKey({String? participantId, int? keyIndex}) async {
if (participantId != null) {
final newKey = await _keyProvider.ratchetKey(participantId, keyIndex);
final newKey = await _keyProvider!.ratchetKey(participantId, keyIndex);
if (kDebugMode) {
print('newKey: $newKey');
}
} else {
final newKey = await _keyProvider.ratchetSharedKey(keyIndex: keyIndex);
final newKey = await _keyProvider!.ratchetSharedKey(keyIndex: keyIndex);
if (kDebugMode) {
print('newKey: $newKey');
}
Expand All @@ -153,22 +164,22 @@ class E2EEManager {
Future<FrameCryptor> _addRtpSender(
{required RTCRtpSender sender, required String identity, required String sid}) async {
final frameCryptor = await frameCryptorFactory.createFrameCryptorForRtpSender(
participantId: identity, sender: sender, algorithm: _algorithm, keyProvider: _keyProvider.keyProvider);
participantId: identity, sender: sender, algorithm: _algorithm, keyProvider: _keyProvider!.keyProvider);
_frameCryptors[{identity: sid}] = frameCryptor;
await frameCryptor.setEnabled(_enabled);
logger.info('_addRtpSender, setKeyIndex: ${_keyProvider.getLatestIndex(identity)}');
await frameCryptor.setKeyIndex(_keyProvider.getLatestIndex(identity));
logger.info('_addRtpSender, setKeyIndex: ${_keyProvider!.getLatestIndex(identity)}');
await frameCryptor.setKeyIndex(_keyProvider!.getLatestIndex(identity));
return frameCryptor;
}

Future<FrameCryptor> _addRtpReceiver(
{required RTCRtpReceiver receiver, required String identity, required String sid}) async {
final frameCryptor = await frameCryptorFactory.createFrameCryptorForRtpReceiver(
participantId: identity, receiver: receiver, algorithm: _algorithm, keyProvider: _keyProvider.keyProvider);
participantId: identity, receiver: receiver, algorithm: _algorithm, keyProvider: _keyProvider!.keyProvider);
_frameCryptors[{identity: sid}] = frameCryptor;
await frameCryptor.setEnabled(_enabled);
logger.info('_addRtpReceiver, setKeyIndex: ${_keyProvider.getLatestIndex(identity)}');
await frameCryptor.setKeyIndex(_keyProvider.getLatestIndex(identity));
logger.info('_addRtpReceiver, setKeyIndex: ${_keyProvider!.getLatestIndex(identity)}');
await frameCryptor.setKeyIndex(_keyProvider!.getLatestIndex(identity));
return frameCryptor;
}

Expand Down Expand Up @@ -258,6 +269,6 @@ class E2EEManager {
throw Exception('DataPacketCryptor is not initialized');
}
return await _dataPacketCryptor!
.encrypt(participantId: participantId, keyIndex: _keyProvider.getLatestIndex(participantId), data: data);
.encrypt(participantId: participantId, keyIndex: _keyProvider!.getLatestIndex(participantId), data: data);
}
}
41 changes: 39 additions & 2 deletions lib/src/e2ee/options.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import 'package:meta/meta.dart';

import 'key_provider.dart';

enum EncryptionType {
Expand All @@ -20,8 +22,43 @@ enum EncryptionType {
kCustom,
}

/// End-to-end encryption configuration for a [Room].
///
/// Build with either a pre-constructed [BaseKeyProvider] (default constructor)
/// or a shared passphrase ([E2EEOptions.sharedKey]). Resolution of the
/// passphrase into a native key provider is deferred to [E2EEManager.setup].
class E2EEOptions {
final BaseKeyProvider keyProvider;
final BaseKeyProvider? _keyProvider;

/// A shared passphrase, or `null` when a [BaseKeyProvider] was provided.
final String? sharedKey;

final EncryptionType encryptionType = EncryptionType.kGcm;
const E2EEOptions({required this.keyProvider});

const E2EEOptions({required BaseKeyProvider keyProvider})
: _keyProvider = keyProvider,
sharedKey = null;

const E2EEOptions.sharedKey(String key)
: sharedKey = key,
_keyProvider = null;

/// The underlying [BaseKeyProvider].
///
/// Available immediately when built via the default constructor. Throws
/// [StateError] when built via [E2EEOptions.sharedKey] — the provider is
/// created lazily inside [E2EEManager.setup]; read it from
/// `room.e2eeManager!.keyProvider` after connect instead.
BaseKeyProvider get keyProvider =>
_keyProvider ??
(throw StateError(
'E2EEOptions was constructed via E2EEOptions.sharedKey(...); '
'BaseKeyProvider is built lazily inside E2EEManager.setup(). '
'Access it via room.e2eeManager!.keyProvider after connect.',
));

/// Internal accessor used by [E2EEManager.setup] to read the pre-built
/// provider without triggering the throwing [keyProvider] getter.
@internal
BaseKeyProvider? get keyProviderOrNull => _keyProvider;
}
Loading