Files
client-sdk-flutter/web/e2ee.worker.dart
T
CloudWebRTC 26947e96d6 e2ee. (#226)
* chore: e2ee.

* update.

* update.

* update.

* update.

* chore: Use feat/frame-encryption branch of flutter-webrtc.

* chore: Add E2EEKEY defines for dart environment, and e2ee switch.

* Add encodedInsertableStreams to RTCConfiguration.

* update.

* feat: Add e2ee indicator for Participant.

* feat: add e2ee worker js for flutter web.

* dart format.

* remove unused file.

* fix flutter analyze .

* update.

* update.

* add: indicate for decryption failure, and string key.

* remove .lock files.

* update.

* update.

* update e2ee.worker for web.

* feat: support setCodecPreferences.

* state TrackE2EEStateEvent.

* fix wrong import interface from dart_webrtc.

* update.

* update.

* update.

* update.

* update pubspec.lock.

* chore: update protocol and add EncryptionType for Participant.

* Update lib/src/e2ee/options.dart

Co-authored-by: Théo Monnom <theo.monnom@outlook.com>

* fix typo.

* revert changes for internal import.

* Add _cleanUp() for previous room.

* Add e2ee supports detection method for native/web.

* Remove redundant overriding methods.

* dart format.

* Add e2ee.worker code and deployment docs.

* chore: remove duplicate words.

* chore: using Pbkdf2 derive the key.

* Update pubspec.yaml

* fix e2ee for safari.

* fix key length.

* update e2ee.worker.dart.js.

* fix.

* update proto.

* update.

* chore: add simulate for rachetKey.

* update.

* chore: key ratchet for flutter web.

* update.

* update.

* update.

* chore: key ratchet export for web.

* bump version for xframeworks.

* update.

* chore: some changes for key safety ratcheting.

* update.

* fix typo.

* update.

* rename.

* magic bytes for web.

* bump version for flutter-webrtc.

* fix analyzer.

---------

Co-authored-by: Théo Monnom <theo.monnom@outlook.com>
2023-04-27 17:54:32 +08:00

282 lines
8.9 KiB
Dart

import 'dart:convert';
import 'dart:html' as html;
import 'dart:js_util' as js_util;
import 'dart:typed_data';
import 'package:js/js.dart';
import 'e2ee.cryptor.dart';
import 'package:flutter_webrtc/src/web/rtc_transform_stream.dart';
import 'package:collection/collection.dart';
import 'crypto.dart' as crypto;
@JS()
abstract class TransformMessage {
external String get msgType;
external String get kind;
}
@anonymous
@JS()
class EnableTransformMessage {
external factory EnableTransformMessage({
ReadableStream readable,
WritableStream writable,
String msgType,
String kind,
String participantId,
String trackId,
String codec,
});
external ReadableStream get readable;
external WritableStream get writable;
external String get msgType; // 'encode' or 'decode'
external String get participantId;
external String get trackId;
external String get kind;
external String get codec;
}
@anonymous
@JS()
class RemoveTransformMessage {
external factory RemoveTransformMessage(
{String msgType, String participantId, String trackId});
external String get msgType; // 'removeTransform'
external String get participantId;
external String get trackId;
}
@JS('self')
external html.DedicatedWorkerGlobalScope get self;
extension PropsRTCTransformEventHandler on html.DedicatedWorkerGlobalScope {
set onrtctransform(Function(dynamic) callback) =>
js_util.setProperty<Function>(this, 'onrtctransform', callback);
}
var participantCryptors = <FrameCryptor>[];
var publisherKeys = <String, html.CryptoKey>{};
bool isEncryptionEnabled = false;
KeyOptions keyProviderOptions = KeyOptions(
sharedKey: true,
ratchetSalt: Uint8List.fromList('ratchetSalt'.codeUnits),
ratchetWindowSize: 16);
void main() async {
print('E2EE Worker created');
if (js_util.getProperty(self, 'RTCTransformEvent') != null) {
print('setup transform event handler');
self.onrtctransform = allowInterop((event) {
print('got transform event');
var transformer = (event as RTCTransformEvent).transformer;
transformer.handled = true;
var options = transformer.options;
var kind = options.kind;
var participantId = options.participantId;
var trackId = options.trackId;
var codec = options.codec;
var msgType = options.msgType;
var cryptor =
participantCryptors.firstWhereOrNull((c) => c.trackId == trackId);
if (cryptor == null) {
cryptor = FrameCryptor(
worker: self,
participantId: participantId,
trackId: trackId,
keyOptions: keyProviderOptions,
);
participantCryptors.add(cryptor);
}
cryptor.setupTransform(
operation: msgType,
readable: transformer.readable,
writable: transformer.writable,
trackId: trackId,
kind: kind,
codec: codec);
});
}
self.onMessage.listen((e) {
var msg = e.data;
var msgType = msg['msgType'];
switch (msgType) {
case 'init':
var options = msg['keyOptions'];
keyProviderOptions = KeyOptions(
sharedKey: options['sharedKey'],
ratchetSalt: Uint8List.fromList(
base64Decode(options['ratchetSalt'] as String)),
ratchetWindowSize: options['ratchetWindowSize'],
uncryptedMagicBytes: options['ratchetSalt'] != null
? Uint8List.fromList(
base64Decode(options['uncryptedMagicBytes'] as String))
: null);
print('worker: init with keyOptions ${keyProviderOptions.toString()}');
break;
case 'enable':
{
var enabled = msg['enabled'] as bool;
var participantId = msg['participantId'] as String;
print('worker: set enable $enabled for participantId $participantId');
var cryptors = participantCryptors
.where((c) => c.participantId == participantId)
.toList();
for (var cryptor in cryptors) {
cryptor.setEnabled(enabled);
}
self.postMessage({
'type': 'cryptorEnabled',
'participantId': participantId,
'enable': enabled,
});
}
break;
case 'decode':
case 'encode':
{
var kind = msg['kind'];
var exist = msg['exist'] as bool;
var participantId = msg['participantId'] as String;
var trackId = msg['trackId'];
var readable = msg['readableStream'] as ReadableStream;
var writable = msg['writableStream'] as WritableStream;
print(
'worker: got $msgType, kind $kind, trackId $trackId, participantId $participantId, ${readable.runtimeType} ${writable.runtimeType}}');
var cryptor =
participantCryptors.firstWhereOrNull((c) => c.trackId == trackId);
if (cryptor == null) {
cryptor = FrameCryptor(
worker: self,
participantId: participantId,
trackId: trackId,
keyOptions: keyProviderOptions);
participantCryptors.add(cryptor);
}
if (!exist) {
cryptor.setupTransform(
operation: msgType,
readable: readable,
writable: writable,
trackId: trackId,
kind: kind);
}
cryptor.setParticipantId(participantId);
self.postMessage({
'type': 'cryptorSetup',
'participantId': participantId,
'trackId': trackId,
'exist': exist,
'operation': msgType,
});
cryptor.lastError = CryptorError.kNew;
}
break;
case 'removeTransform':
{
var trackId = msg['trackId'] as String;
print('worker: removing trackId $trackId');
participantCryptors.removeWhere((c) => c.trackId == trackId);
}
break;
case 'setKey':
{
var key = Uint8List.fromList(base64Decode(msg['key'] as String));
var keyIndex = msg['keyIndex'];
//print('worker: got setKey ${msg['key']}, key $key');
var participantId = msg['participantId'] as String;
print('worker: setup key for participant $participantId');
if (keyProviderOptions.sharedKey) {
for (var c in participantCryptors) {
c.setKey(keyIndex, key);
}
return;
}
var cryptors = participantCryptors
.where((c) => c.participantId == participantId)
.toList();
for (var c in cryptors) {
c.setKey(keyIndex, key);
}
}
break;
case 'ratchetKey':
{
var keyIndex = msg['keyIndex'];
var participantId = msg['participantId'] as String;
print(
'worker: ratchetKey for participant $participantId, keyIndex $keyIndex');
var cryptors = participantCryptors
.where((c) => c.participantId == participantId)
.toList();
for (var c in cryptors) {
var keySet = c.getKeySet(keyIndex);
c.ratchetKey(keyIndex).then((_) async {
var newKey = await c.ratchet(
keySet!.material, keyProviderOptions.ratchetSalt);
self.postMessage({
'type': 'ratchetKey',
'participantId': participantId,
'trackId': c.trackId,
'key': base64Encode(newKey),
});
});
}
}
break;
case 'setKeyIndex':
{
var keyIndex = msg['index'];
var participantId = msg['participantId'] as String;
print('worker: setup key index for participant $participantId');
var cryptors = participantCryptors
.where((c) => c.participantId == participantId)
.toList();
for (var c in cryptors) {
c.setKeyIndex(keyIndex);
}
}
break;
case 'updateCodec':
{
var codec = msg['codec'] as String;
var trackId = msg['trackId'] as String;
print('worker: update codec for trackId $trackId, codec $codec');
var cryptor =
participantCryptors.firstWhereOrNull((c) => c.trackId == trackId);
cryptor?.updateCodec(codec);
}
break;
case 'dispose':
{
var trackId = msg['trackId'] as String;
print('worker: dispose trackId $trackId');
var cryptor =
participantCryptors.firstWhereOrNull((c) => c.trackId == trackId);
if (cryptor != null) {
cryptor.lastError = CryptorError.kDisposed;
self.postMessage({
'type': 'cryptorDispose',
'participantId': cryptor.participantId,
'trackId': trackId,
});
}
}
break;
default:
print('worker: unknown message kind $msg');
}
});
}