From 31a32b014521b9e456c586015effc01d4771b592 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Ku=C3=9Fowski?= Date: Wed, 28 May 2025 11:18:24 +0200 Subject: [PATCH 1/4] feat: Migrate to vodozemac --- .github/workflows/app.yml | 14 +- .gitignore | 1 + lib/encryption/cross_signing.dart | 31 +-- lib/encryption/key_manager.dart | 201 +++++++++--------- lib/encryption/utils/bootstrap.dart | 153 ++++++------- .../utils/json_signature_check_extension.dart | 10 +- lib/encryption/utils/key_verification.dart | 46 ++-- lib/src/utils/device_keys_list.dart | 21 +- pubspec.yaml | 5 + scripts/prepare_vodozemac.sh | 8 + test/client_test.dart | 9 +- test/device_keys_list_test.dart | 10 +- test/encryption/bootstrap_test.dart | 77 +++---- test/encryption/cross_signing_test.dart | 5 + .../encrypt_decrypt_room_message_test.dart | 5 + .../encrypt_decrypt_to_device_test.dart | 5 + test/encryption/key_manager_test.dart | 5 + test/encryption/key_request_test.dart | 5 + test/encryption/key_verification_test.dart | 5 + test/encryption/olm_manager_test.dart | 5 + test/encryption/online_key_backup_test.dart | 5 + .../encryption/qr_verification_self_test.dart | 8 + test/encryption/ssss_test.dart | 5 + test/room_test.dart | 21 +- test_driver/matrixsdk_test.dart | 5 + 25 files changed, 359 insertions(+), 306 deletions(-) create mode 100755 scripts/prepare_vodozemac.sh diff --git a/.github/workflows/app.yml b/.github/workflows/app.yml index 4322566d..eb3ff6dc 100644 --- a/.github/workflows/app.yml +++ b/.github/workflows/app.yml @@ -18,6 +18,11 @@ jobs: - uses: dart-lang/setup-dart@a57a6c04cf7d4840e88432aad6281d1e125f0d46 with: sdk: ${{ env.dart_version }} + - uses: famedly/backend-build-workflows/.github/actions/rust-prepare@main + with: + gitlab_user: ${{ secrets.GITLAB_USER }} + gitlab_pass: ${{ secrets.GITLAB_PASS }} + gitlab_ssh: ${{ secrets.CI_SSH_PRIVATE_KEY}} - name: Run tests run: | export HOMESERVER_IMPLEMENTATION=${{matrix.homeserver}} @@ -27,6 +32,7 @@ jobs: source scripts/integration-create-environment-variables.sh scripts/integration-prepare-homeserver.sh scripts/prepare.sh + scripts/prepare_vodozemac.sh scripts/test_driver.sh coverage_without_olm: @@ -54,7 +60,7 @@ jobs: coverage: #runs-on: arm-ubuntu-latest-16core runs-on: ubuntu-latest - timeout-minutes: 5 + timeout-minutes: 10 steps: - uses: actions/checkout@v4 - run: cat .github/workflows/versions.env >> $GITHUB_ENV @@ -62,9 +68,15 @@ jobs: with: sdk: ${{ env.dart_version }} #architecture: "arm64" + - uses: famedly/backend-build-workflows/.github/actions/rust-prepare@main + with: + gitlab_user: ${{ secrets.GITLAB_USER }} + gitlab_pass: ${{ secrets.GITLAB_PASS }} + gitlab_ssh: ${{ secrets.CI_SSH_PRIVATE_KEY}} - name: Run tests run: | sudo apt-get update && sudo apt-get install --no-install-recommends --no-install-suggests -y lcov libsqlite3-0 libsqlite3-dev libolm3 libssl3 + ./scripts/prepare_vodozemac.sh ./scripts/test.sh - uses: actions/upload-artifact@v4 if: ${{ !cancelled() }} diff --git a/.gitignore b/.gitignore index 68373a03..5419541b 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ coverage/ coverage_badge.svg coverage.xml TEST-report.* +rust # IntelliJ related *.iml diff --git a/lib/encryption/cross_signing.dart b/lib/encryption/cross_signing.dart index 70c89a6d..f725fff2 100644 --- a/lib/encryption/cross_signing.dart +++ b/lib/encryption/cross_signing.dart @@ -16,9 +16,10 @@ * along with this program. If not, see . */ +import 'dart:convert'; import 'dart:typed_data'; -import 'package:olm/olm.dart' as olm; +import 'package:vodozemac/vodozemac.dart' as vod; import 'package:matrix/encryption/encryption.dart'; import 'package:matrix/encryption/ssss.dart'; @@ -31,26 +32,22 @@ class CrossSigning { CrossSigning(this.encryption) { encryption.ssss.setValidator(EventTypes.CrossSigningSelfSigning, (String secret) async { - final keyObj = olm.PkSigning(); try { - return keyObj.init_with_seed(base64decodeUnpadded(secret)) == + final keyObj = vod.PkSigning.fromSecretKey(secret); + return keyObj.publicKey.toBase64() == client.userDeviceKeys[client.userID]!.selfSigningKey!.ed25519Key; } catch (_) { return false; - } finally { - keyObj.free(); } }); encryption.ssss.setValidator(EventTypes.CrossSigningUserSigning, (String secret) async { - final keyObj = olm.PkSigning(); try { - return keyObj.init_with_seed(base64decodeUnpadded(secret)) == + final keyObj = vod.PkSigning.fromSecretKey(secret); + return keyObj.publicKey.toBase64() == client.userDeviceKeys[client.userID]!.userSigningKey!.ed25519Key; } catch (_) { return false; - } finally { - keyObj.free(); } }); } @@ -92,14 +89,13 @@ class CrossSigning { final masterPrivateKey = base64decodeUnpadded( await handle.getStored(EventTypes.CrossSigningMasterKey), ); - final keyObj = olm.PkSigning(); String? masterPubkey; try { - masterPubkey = keyObj.init_with_seed(masterPrivateKey); + masterPubkey = vod.PkSigning.fromSecretKey(base64Encode(masterPrivateKey)) + .publicKey + .toBase64(); } catch (e) { masterPubkey = null; - } finally { - keyObj.free(); } final userDeviceKeys = client.userDeviceKeys[client.userID]?.deviceKeys[client.deviceID]; @@ -210,12 +206,7 @@ class CrossSigning { } String _sign(String canonicalJson, Uint8List key) { - final keyObj = olm.PkSigning(); - try { - keyObj.init_with_seed(key); - return keyObj.sign(canonicalJson); - } finally { - keyObj.free(); - } + final keyObj = vod.PkSigning.fromSecretKey(base64Encode(key)); + return keyObj.sign(canonicalJson).toBase64(); } } diff --git a/lib/encryption/key_manager.dart b/lib/encryption/key_manager.dart index 77fe1679..ee7ef210 100644 --- a/lib/encryption/key_manager.dart +++ b/lib/encryption/key_manager.dart @@ -21,6 +21,7 @@ import 'dart:convert'; import 'package:collection/collection.dart'; import 'package:olm/olm.dart' as olm; +import 'package:vodozemac/vodozemac.dart' as vod; import 'package:matrix/encryption/encryption.dart'; import 'package:matrix/encryption/utils/base64_unpadded.dart'; @@ -45,19 +46,18 @@ class KeyManager { KeyManager(this.encryption) { encryption.ssss.setValidator(megolmKey, (String secret) async { - final keyObj = olm.PkDecryption(); try { + final keyObj = vod.PkDecryption.fromSecretKey( + vod.Curve25519PublicKey.fromBase64(secret), + ); final info = await getRoomKeysBackupInfo(false); if (info.algorithm != BackupAlgorithm.mMegolmBackupV1Curve25519AesSha2) { return false; } - return keyObj.init_with_private_key(base64decodeUnpadded(secret)) == - info.authData['public_key']; + return keyObj.publicKey == info.authData['public_key']; } catch (_) { return false; - } finally { - keyObj.free(); } }); encryption.ssss.setCacheCallback(megolmKey, (String secret) { @@ -669,54 +669,57 @@ class KeyManager { } final privateKey = base64decodeUnpadded((await encryption.ssss.getCached(megolmKey))!); - final decryption = olm.PkDecryption(); final info = await getRoomKeysBackupInfo(); String backupPubKey; - try { - backupPubKey = decryption.init_with_private_key(privateKey); - if (info.algorithm != BackupAlgorithm.mMegolmBackupV1Curve25519AesSha2 || - info.authData['public_key'] != backupPubKey) { - return; - } - for (final roomEntry in keys.rooms.entries) { - final roomId = roomEntry.key; - for (final sessionEntry in roomEntry.value.sessions.entries) { - final sessionId = sessionEntry.key; - final session = sessionEntry.value; - final sessionData = session.sessionData; - Map? decrypted; - try { - decrypted = json.decode( - decryption.decrypt( - sessionData['ephemeral'] as String, - sessionData['mac'] as String, - sessionData['ciphertext'] as String, + final decryption = vod.PkDecryption.fromSecretKey( + vod.Curve25519PublicKey.fromBytes(privateKey), + ); + backupPubKey = decryption.publicKey; + + if (info.algorithm != BackupAlgorithm.mMegolmBackupV1Curve25519AesSha2 || + info.authData['public_key'] != backupPubKey) { + return; + } + for (final roomEntry in keys.rooms.entries) { + final roomId = roomEntry.key; + for (final sessionEntry in roomEntry.value.sessions.entries) { + final sessionId = sessionEntry.key; + final session = sessionEntry.value; + final sessionData = session.sessionData; + Map? decrypted; + try { + decrypted = json.decode( + decryption.decrypt( + vod.PkMessage( + base64decodeUnpadded(sessionData['ciphertext'] as String), + base64decodeUnpadded(sessionData['mac'] as String), + vod.Curve25519PublicKey.fromBase64( + sessionData['ephemeral'] as String, + ), ), - ); - } catch (e, s) { - Logs().e('[LibOlm] Error decrypting room key', e, s); - } - final senderKey = decrypted?.tryGet('sender_key'); - if (decrypted != null && senderKey != null) { - decrypted['session_id'] = sessionId; - decrypted['room_id'] = roomId; - await setInboundGroupSession( - roomId, - sessionId, - senderKey, - decrypted, - forwarded: true, - senderClaimedKeys: - decrypted.tryGetMap('sender_claimed_keys') ?? - {}, - uploaded: true, - ); - } + ), + ); + } catch (e, s) { + Logs().e('[LibOlm] Error decrypting room key', e, s); + } + final senderKey = decrypted?.tryGet('sender_key'); + if (decrypted != null && senderKey != null) { + decrypted['session_id'] = sessionId; + decrypted['room_id'] = roomId; + await setInboundGroupSession( + roomId, + sessionId, + senderKey, + decrypted, + forwarded: true, + senderClaimedKeys: + decrypted.tryGetMap('sender_claimed_keys') ?? + {}, + uploaded: true, + ); } } - } finally { - decryption.free(); } } @@ -875,57 +878,56 @@ class KeyManager { final privateKey = base64decodeUnpadded((await encryption.ssss.getCached(megolmKey))!); // decryption is needed to calculate the public key and thus see if the claimed information is in fact valid - final decryption = olm.PkDecryption(); + final info = await getRoomKeysBackupInfo(false); String backupPubKey; - try { - backupPubKey = decryption.init_with_private_key(privateKey); - if (info.algorithm != - BackupAlgorithm.mMegolmBackupV1Curve25519AesSha2 || - info.authData['public_key'] != backupPubKey) { - decryption.free(); - return; - } - final args = GenerateUploadKeysArgs( - pubkey: backupPubKey, - dbSessions: [], - userId: userID, + final decryption = vod.PkDecryption.fromSecretKey( + vod.Curve25519PublicKey.fromBytes(privateKey), + ); + backupPubKey = decryption.publicKey; + + if (info.algorithm != + BackupAlgorithm.mMegolmBackupV1Curve25519AesSha2 || + info.authData['public_key'] != backupPubKey) { + return; + } + final args = GenerateUploadKeysArgs( + pubkey: backupPubKey, + dbSessions: [], + userId: userID, + ); + // we need to calculate verified beforehand, as else we pass a closure to an isolate + // with 500 keys they do, however, noticably block the UI, which is why we give brief async suspentions in here + // so that the event loop can progress + var i = 0; + for (final dbSession in dbSessions) { + final device = + client.getUserDeviceKeysByCurve25519Key(dbSession.senderKey); + args.dbSessions.add( + DbInboundGroupSessionBundle( + dbSession: dbSession, + verified: device?.verified ?? false, + ), ); - // we need to calculate verified beforehand, as else we pass a closure to an isolate - // with 500 keys they do, however, noticably block the UI, which is why we give brief async suspentions in here - // so that the event loop can progress - var i = 0; - for (final dbSession in dbSessions) { - final device = - client.getUserDeviceKeysByCurve25519Key(dbSession.senderKey); - args.dbSessions.add( - DbInboundGroupSessionBundle( - dbSession: dbSession, - verified: device?.verified ?? false, - ), - ); - i++; - if (i > 10) { - await Future.delayed(Duration(milliseconds: 1)); - i = 0; - } + i++; + if (i > 10) { + await Future.delayed(Duration(milliseconds: 1)); + i = 0; } - final roomKeys = - await client.nativeImplementations.generateUploadKeys(args); - Logs().i('[Key Manager] Uploading ${dbSessions.length} room keys...'); - // upload the payload... - await client.putRoomKeys(info.version, roomKeys); - // and now finally mark all the keys as uploaded - // no need to optimze this, as we only run it so seldomly and almost never with many keys at once - for (final dbSession in dbSessions) { - await database.markInboundGroupSessionAsUploaded( - dbSession.roomId, - dbSession.sessionId, - ); - } - } finally { - decryption.free(); + } + final roomKeys = + await client.nativeImplementations.generateUploadKeys(args); + Logs().i('[Key Manager] Uploading ${dbSessions.length} room keys...'); + // upload the payload... + await client.putRoomKeys(info.version, roomKeys); + // and now finally mark all the keys as uploaded + // no need to optimze this, as we only run it so seldomly and almost never with many keys at once + for (final dbSession in dbSessions) { + await database.markInboundGroupSessionAsUploaded( + dbSession.roomId, + dbSession.sessionId, + ); } } catch (e, s) { Logs().e('[Key Manager] Error uploading room keys', e, s); @@ -1247,9 +1249,10 @@ class RoomKeyRequest extends ToDeviceEvent { /// you would likely want to use [NativeImplementations] and /// [Client.nativeImplementations] instead RoomKeys generateUploadKeysImplementation(GenerateUploadKeysArgs args) { - final enc = olm.PkEncryption(); try { - enc.set_recipient_key(args.pubkey); + final enc = vod.PkEncryption.fromPublicKey( + vod.Curve25519PublicKey.fromBase64(args.pubkey), + ); // first we generate the payload to upload all the session keys in this chunk final roomKeys = RoomKeys(rooms: {}); for (final dbSession in args.dbSessions) { @@ -1279,17 +1282,15 @@ RoomKeys generateUploadKeysImplementation(GenerateUploadKeysArgs args) { forwardedCount: sess.forwardingCurve25519KeyChain.length, isVerified: dbSession.verified, //device?.verified ?? false, sessionData: { - 'ephemeral': encrypted.ephemeral, - 'ciphertext': encrypted.ciphertext, - 'mac': encrypted.mac, + 'ephemeral': encrypted.ephemeralKey.toBase64(), + 'ciphertext': base64Encode(encrypted.ciphertext), + 'mac': base64Encode(encrypted.mac), }, ); } - enc.free(); return roomKeys; } catch (e, s) { Logs().e('[Key Manager] Error generating payload', e, s); - enc.free(); rethrow; } } diff --git a/lib/encryption/utils/bootstrap.dart b/lib/encryption/utils/bootstrap.dart index 1746f28e..1645115f 100644 --- a/lib/encryption/utils/bootstrap.dart +++ b/lib/encryption/utils/bootstrap.dart @@ -20,12 +20,11 @@ import 'dart:convert'; import 'dart:typed_data'; import 'package:canonical_json/canonical_json.dart'; -import 'package:olm/olm.dart' as olm; +import 'package:vodozemac/vodozemac.dart' as vod; import 'package:matrix/encryption/encryption.dart'; import 'package:matrix/encryption/key_manager.dart'; import 'package:matrix/encryption/ssss.dart'; -import 'package:matrix/encryption/utils/base64_unpadded.dart'; import 'package:matrix/matrix.dart'; enum BootstrapState { @@ -371,106 +370,82 @@ class Bootstrap { } final userID = client.userID!; try { - Uint8List masterSigningKey; + String masterSigningKey; final secretsToStore = {}; MatrixCrossSigningKey? masterKey; MatrixCrossSigningKey? selfSigningKey; MatrixCrossSigningKey? userSigningKey; String? masterPub; if (setupMasterKey) { - final master = olm.PkSigning(); - try { - masterSigningKey = master.generate_seed(); - masterPub = master.init_with_seed(masterSigningKey); - final json = { - 'user_id': userID, - 'usage': ['master'], - 'keys': { - 'ed25519:$masterPub': masterPub, - }, - }; - masterKey = MatrixCrossSigningKey.fromJson(json); - secretsToStore[EventTypes.CrossSigningMasterKey] = - base64.encode(masterSigningKey); - } finally { - master.free(); - } + final master = vod.PkSigning(); + masterSigningKey = master.secretKey; + masterPub = master.publicKey.toBase64(); + final json = { + 'user_id': userID, + 'usage': ['master'], + 'keys': { + 'ed25519:$masterPub': masterPub, + }, + }; + masterKey = MatrixCrossSigningKey.fromJson(json); + secretsToStore[EventTypes.CrossSigningMasterKey] = masterSigningKey; } else { Logs().v('Get stored key...'); - masterSigningKey = base64decodeUnpadded( - await newSsssKey?.getStored(EventTypes.CrossSigningMasterKey) ?? '', - ); + masterSigningKey = + await newSsssKey?.getStored(EventTypes.CrossSigningMasterKey) ?? ''; if (masterSigningKey.isEmpty) { // no master signing key :( throw BootstrapBadStateException('No master key'); } - final master = olm.PkSigning(); - try { - masterPub = master.init_with_seed(masterSigningKey); - } finally { - master.free(); - } + final master = vod.PkSigning.fromSecretKey(masterSigningKey); + masterPub = master.publicKey.toBase64(); } String? sign(Map object) { - final keyObj = olm.PkSigning(); - try { - keyObj.init_with_seed(masterSigningKey); - return keyObj - .sign(String.fromCharCodes(canonicalJson.encode(object))); - } finally { - keyObj.free(); - } + final keyObj = vod.PkSigning.fromSecretKey(masterSigningKey); + return keyObj + .sign(String.fromCharCodes(canonicalJson.encode(object))) + .toBase64(); } if (setupSelfSigningKey) { - final selfSigning = olm.PkSigning(); - try { - final selfSigningPriv = selfSigning.generate_seed(); - final selfSigningPub = selfSigning.init_with_seed(selfSigningPriv); - final json = { - 'user_id': userID, - 'usage': ['self_signing'], - 'keys': { - 'ed25519:$selfSigningPub': selfSigningPub, - }, - }; - final signature = sign(json); - json['signatures'] = { - userID: { - 'ed25519:$masterPub': signature, - }, - }; - selfSigningKey = MatrixCrossSigningKey.fromJson(json); - secretsToStore[EventTypes.CrossSigningSelfSigning] = - base64.encode(selfSigningPriv); - } finally { - selfSigning.free(); - } + final selfSigning = vod.PkSigning(); + final selfSigningPriv = selfSigning.secretKey; + final selfSigningPub = selfSigning.publicKey.toBase64(); + final json = { + 'user_id': userID, + 'usage': ['self_signing'], + 'keys': { + 'ed25519:$selfSigningPub': selfSigningPub, + }, + }; + final signature = sign(json); + json['signatures'] = { + userID: { + 'ed25519:$masterPub': signature, + }, + }; + selfSigningKey = MatrixCrossSigningKey.fromJson(json); + secretsToStore[EventTypes.CrossSigningSelfSigning] = selfSigningPriv; } if (setupUserSigningKey) { - final userSigning = olm.PkSigning(); - try { - final userSigningPriv = userSigning.generate_seed(); - final userSigningPub = userSigning.init_with_seed(userSigningPriv); - final json = { - 'user_id': userID, - 'usage': ['user_signing'], - 'keys': { - 'ed25519:$userSigningPub': userSigningPub, - }, - }; - final signature = sign(json); - json['signatures'] = { - userID: { - 'ed25519:$masterPub': signature, - }, - }; - userSigningKey = MatrixCrossSigningKey.fromJson(json); - secretsToStore[EventTypes.CrossSigningUserSigning] = - base64.encode(userSigningPriv); - } finally { - userSigning.free(); - } + final userSigning = vod.PkSigning(); + final userSigningPriv = userSigning.secretKey; + final userSigningPub = userSigning.publicKey.toBase64(); + final json = { + 'user_id': userID, + 'usage': ['user_signing'], + 'keys': { + 'ed25519:$userSigningPub': userSigningPub, + }, + }; + final signature = sign(json); + json['signatures'] = { + userID: { + 'ed25519:$masterPub': signature, + }, + }; + userSigningKey = MatrixCrossSigningKey.fromJson(json); + secretsToStore[EventTypes.CrossSigningUserSigning] = userSigningPriv; } // upload the keys! state = BootstrapState.loading; @@ -561,15 +536,13 @@ class Bootstrap { return; } try { - final keyObj = olm.PkDecryption(); + final keyObj = vod.PkDecryption(); String pubKey; Uint8List privKey; - try { - pubKey = keyObj.generate_key(); - privKey = keyObj.get_private_key(); - } finally { - keyObj.free(); - } + + pubKey = keyObj.publicKey; + privKey = keyObj.privateKey; + Logs().v('Create the new backup version...'); await client.postRoomKeysVersion( BackupAlgorithm.mMegolmBackupV1Curve25519AesSha2, diff --git a/lib/encryption/utils/json_signature_check_extension.dart b/lib/encryption/utils/json_signature_check_extension.dart index 7c1f4f6c..e691d415 100644 --- a/lib/encryption/utils/json_signature_check_extension.dart +++ b/lib/encryption/utils/json_signature_check_extension.dart @@ -17,7 +17,7 @@ */ import 'package:canonical_json/canonical_json.dart'; -import 'package:olm/olm.dart' as olm; +import 'package:vodozemac/vodozemac.dart' as vod; import 'package:matrix/matrix.dart'; @@ -37,15 +37,15 @@ extension JsonSignatureCheckExtension on Map { final canonical = canonicalJson.encode(this); final message = String.fromCharCodes(canonical); var isValid = false; - final olmutil = olm.Utility(); try { - olmutil.ed25519_verify(key, message, signature); + vod.Ed25519PublicKey.fromBase64(key).verify( + message: message, + signature: vod.Ed25519Signature.fromBase64(signature), + ); isValid = true; } catch (e, s) { isValid = false; Logs().w('[LibOlm] Signature check failed', e, s); - } finally { - olmutil.free(); } return isValid; } diff --git a/lib/encryption/utils/key_verification.dart b/lib/encryption/utils/key_verification.dart index 007c00b2..467b0cde 100644 --- a/lib/encryption/utils/key_verification.dart +++ b/lib/encryption/utils/key_verification.dart @@ -21,8 +21,9 @@ import 'dart:convert'; import 'dart:typed_data'; import 'package:canonical_json/canonical_json.dart'; -import 'package:olm/olm.dart' as olm; +import 'package:crypto/crypto.dart' as crypto; import 'package:typed_data/typed_data.dart'; +import 'package:vodozemac/vodozemac.dart' as vod; import 'package:matrix/encryption/encryption.dart'; import 'package:matrix/encryption/utils/base64_unpadded.dart'; @@ -1258,12 +1259,8 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod { String? commitment; late String theirPublicKey; Map? macPayload; - olm.SAS? sas; - - @override - void dispose() { - sas?.free(); - } + vod.Sas? sas; + vod.EstablishedSas? establishedSas; List get knownAuthentificationTypes { final types = []; @@ -1322,7 +1319,7 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod { await _sendKey(); } else { // we already sent our key, time to verify the commitment being valid - if (!_validateCommitment()) { + if (await _validateCommitment() == false) { await request.cancel('m.mismatched_commitment'); return; } @@ -1415,8 +1412,8 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod { } Future _sendAccept() async { - final sas = this.sas = olm.SAS(); - commitment = _makeCommitment(sas.get_pubkey(), startCanonicalJson); + final sas = this.sas = vod.Sas(); + commitment = await _makeCommitment(sas.publicKey, startCanonicalJson); await request.send(EventTypes.KeyVerificationAccept, { 'method': type, 'key_agreement_protocol': keyAgreementProtocol, @@ -1451,31 +1448,31 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod { } authenticationTypes = possibleAuthenticationTypes; commitment = payload['commitment']; - sas = olm.SAS(); + sas = vod.Sas(); return true; } Future _sendKey() async { await request.send('m.key.verification.key', { - 'key': sas!.get_pubkey(), + 'key': sas!.publicKey, }); } void _handleKey(Map payload) { theirPublicKey = payload['key']; - sas!.set_their_key(payload['key']); + establishedSas = sas!.establishSasSecret(payload['key']); } - bool _validateCommitment() { - final checkCommitment = _makeCommitment(theirPublicKey, startCanonicalJson); + Future _validateCommitment() async { + final checkCommitment = + await _makeCommitment(theirPublicKey, startCanonicalJson); return commitment == checkCommitment; } Uint8List makeSas(int bytes) { var sasInfo = ''; if (keyAgreementProtocol == 'curve25519-hkdf-sha256') { - final ourInfo = - '${client.userID}|${client.deviceID}|${sas!.get_pubkey()}|'; + final ourInfo = '${client.userID}|${client.deviceID}|${sas!.publicKey}|'; final theirInfo = '${request.userId}|${request.deviceId}|$theirPublicKey|'; sasInfo = @@ -1488,7 +1485,7 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod { } else { throw Exception('Unknown key agreement protocol'); } - return sas!.generate_bytes(sasInfo, bytes); + return establishedSas!.generateBytes(sasInfo, bytes); } Future _sendMac() async { @@ -1554,21 +1551,20 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod { }); } - String _makeCommitment(String pubKey, String canonicalJson) { + Future _makeCommitment(String pubKey, String canonicalJson) async { if (hash == 'sha256') { - final olmutil = olm.Utility(); - final ret = olmutil.sha256(pubKey + canonicalJson); - olmutil.free(); - return ret; + final bytes = utf8.encode(pubKey + canonicalJson); + final digest = crypto.sha256.convert(bytes); + return base64.encode(digest.bytes); } throw Exception('Unknown hash method'); } String _calculateMac(String input, String info) { if (messageAuthenticationCode == 'hkdf-hmac-sha256.v2') { - return sas!.calculate_mac_fixed_base64(input, info); + return establishedSas!.calculateMac(input, info); } else if (messageAuthenticationCode == 'hkdf-hmac-sha256') { - return sas!.calculate_mac(input, info); + return establishedSas!.calculateMacDeprecated(input, info); } else { throw Exception('Unknown message authentification code'); } diff --git a/lib/src/utils/device_keys_list.dart b/lib/src/utils/device_keys_list.dart index a2e81cc5..073736ae 100644 --- a/lib/src/utils/device_keys_list.dart +++ b/lib/src/utils/device_keys_list.dart @@ -20,7 +20,7 @@ import 'dart:convert'; import 'package:canonical_json/canonical_json.dart'; import 'package:collection/collection.dart' show IterableExtension; -import 'package:olm/olm.dart' as olm; +import 'package:vodozemac/vodozemac.dart' as vod; import 'package:matrix/encryption.dart'; import 'package:matrix/matrix.dart'; @@ -240,24 +240,17 @@ abstract class SignableKey extends MatrixSignableKey { String signature, { bool isSignatureWithoutLibolmValid = false, }) { - olm.Utility olmutil; - try { - olmutil = olm.Utility(); - } catch (e) { - // if no libolm is present we land in this catch block, and return the default - // set if no libolm is there. Some signatures should be assumed-valid while others - // should be assumed-invalid - return isSignatureWithoutLibolmValid; - } var valid = false; try { - olmutil.ed25519_verify(pubKey, signingContent, signature); + vod.Ed25519PublicKey.fromBase64(pubKey).verify( + message: signingContent, + signature: vod.Ed25519Signature.fromBase64(signature), + ); valid = true; - } catch (_) { + } catch (e) { + Logs().d('Invalid Ed25519 signature', e); // bad signature valid = false; - } finally { - olmutil.free(); } return valid; } diff --git a/pubspec.yaml b/pubspec.yaml index 8f282c70..a1333227 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -31,6 +31,11 @@ dependencies: sqflite_common: ^2.4.5 sqlite3: ^2.1.0 typed_data: ^1.3.2 + vodozemac: + git: + url: https://github.com/famedly/dart-vodozemac.git + path: dart + ref: main webrtc_interface: ^1.2.0 dev_dependencies: diff --git a/scripts/prepare_vodozemac.sh b/scripts/prepare_vodozemac.sh new file mode 100755 index 00000000..d1b2b725 --- /dev/null +++ b/scripts/prepare_vodozemac.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +git clone https://github.com/famedly/dart-vodozemac.git +mv ./dart-vodozemac/rust ./ +rm -rf dart-vodozemac +cd ./rust +cargo build +cd .. \ No newline at end of file diff --git a/test/client_test.dart b/test/client_test.dart index f88a182d..6662ea1e 100644 --- a/test/client_test.dart +++ b/test/client_test.dart @@ -26,6 +26,7 @@ import 'package:collection/collection.dart'; import 'package:olm/olm.dart' as olm; import 'package:path/path.dart' show join; import 'package:test/test.dart'; +import 'package:vodozemac/vodozemac.dart' as vod; import 'package:matrix/matrix.dart'; import 'package:matrix/src/utils/client_init_exception.dart'; @@ -67,11 +68,15 @@ void main() { group('client mem', tags: 'olm', () { late Client matrix; - Logs().level = Level.error; + Future? vodInit; /// Check if all Elements get created - setUp(() async { + vodInit ??= vod.init( + wasmPath: './pkg/', + libraryPath: './rust/target/debug/', + ); + await vodInit; matrix = await getClient(); }); diff --git a/test/device_keys_list_test.dart b/test/device_keys_list_test.dart index e9e15ffe..b1b57f69 100644 --- a/test/device_keys_list_test.dart +++ b/test/device_keys_list_test.dart @@ -19,18 +19,26 @@ import 'dart:convert'; import 'package:test/test.dart'; +import 'package:vodozemac/vodozemac.dart' as vod; import 'package:matrix/matrix.dart'; import './fake_client.dart'; -void main() { +void main() async { /// All Tests related to device keys group('Device keys', tags: 'olm', () { Logs().level = Level.error; late Client client; + Future? vodInit; + test('setupClient', () async { + vodInit ??= vod.init( + wasmPath: './pkg/', + libraryPath: './rust/target/debug/', + ); + await vodInit; client = await getClient(); await client.abortSync(); }); diff --git a/test/encryption/bootstrap_test.dart b/test/encryption/bootstrap_test.dart index 6b4d1dbc..363e49bb 100644 --- a/test/encryption/bootstrap_test.dart +++ b/test/encryption/bootstrap_test.dart @@ -21,6 +21,7 @@ import 'dart:convert'; import 'package:olm/olm.dart' as olm; import 'package:test/test.dart'; +import 'package:vodozemac/vodozemac.dart' as vod; import 'package:matrix/encryption.dart'; import 'package:matrix/matrix.dart'; @@ -35,6 +36,10 @@ void main() { late String origKeyId; setUpAll(() async { + await vod.init( + wasmPath: './pkg/', + libraryPath: './rust/target/debug/', + ); await olm.init(); olm.get_library_version(); client = await getClient(); @@ -75,20 +80,16 @@ void main() { // test all the x-signing keys match up for (final keyType in {'master', 'user_signing', 'self_signing'}) { - final privateKey = base64 - .decode(await defaultKey.getStored('m.cross_signing.$keyType')); - final keyObj = olm.PkSigning(); - try { - final pubKey = keyObj.init_with_seed(privateKey); - expect( - pubKey, - client.userDeviceKeys[client.userID] - ?.getCrossSigningKey(keyType) - ?.publicKey, - ); - } finally { - keyObj.free(); - } + final privateKey = + await defaultKey.getStored('m.cross_signing.$keyType'); + final keyObj = vod.PkSigning.fromSecretKey(privateKey); + final pubKey = keyObj.publicKey.toBase64(); + expect( + pubKey, + client.userDeviceKeys[client.userID] + ?.getCrossSigningKey(keyType) + ?.publicKey, + ); } await defaultKey.store('foxes', 'floof'); @@ -133,20 +134,16 @@ void main() { // test all the x-signing keys match up for (final keyType in {'master', 'user_signing', 'self_signing'}) { - final privateKey = base64 - .decode(await defaultKey.getStored('m.cross_signing.$keyType')); - final keyObj = olm.PkSigning(); - try { - final pubKey = keyObj.init_with_seed(privateKey); - expect( - pubKey, - client.userDeviceKeys[client.userID] - ?.getCrossSigningKey(keyType) - ?.publicKey, - ); - } finally { - keyObj.free(); - } + final privateKey = + await defaultKey.getStored('m.cross_signing.$keyType'); + final keyObj = vod.PkSigning.fromSecretKey(privateKey); + final pubKey = keyObj.publicKey.toBase64(); + expect( + pubKey, + client.userDeviceKeys[client.userID] + ?.getCrossSigningKey(keyType) + ?.publicKey, + ); } expect(await defaultKey.getStored('foxes'), 'floof'); @@ -192,20 +189,16 @@ void main() { // test all the x-signing keys match up for (final keyType in {'master', 'user_signing', 'self_signing'}) { - final privateKey = base64 - .decode(await defaultKey.getStored('m.cross_signing.$keyType')); - final keyObj = olm.PkSigning(); - try { - final pubKey = keyObj.init_with_seed(privateKey); - expect( - pubKey, - client.userDeviceKeys[client.userID] - ?.getCrossSigningKey(keyType) - ?.publicKey, - ); - } finally { - keyObj.free(); - } + final privateKey = + await defaultKey.getStored('m.cross_signing.$keyType'); + final keyObj = vod.PkSigning.fromSecretKey(privateKey); + final pubKey = keyObj.publicKey.toBase64(); + expect( + pubKey, + client.userDeviceKeys[client.userID] + ?.getCrossSigningKey(keyType) + ?.publicKey, + ); } expect(await defaultKey.getStored('foxes'), 'floof'); diff --git a/test/encryption/cross_signing_test.dart b/test/encryption/cross_signing_test.dart index a901c0a7..d1c4ec04 100644 --- a/test/encryption/cross_signing_test.dart +++ b/test/encryption/cross_signing_test.dart @@ -20,6 +20,7 @@ import 'dart:convert'; import 'package:olm/olm.dart' as olm; import 'package:test/test.dart'; +import 'package:vodozemac/vodozemac.dart' as vod; import 'package:matrix/matrix.dart'; import '../fake_client.dart'; @@ -31,6 +32,10 @@ void main() { late Client client; setUpAll(() async { + await vod.init( + wasmPath: './pkg/', + libraryPath: './rust/target/debug/', + ); await olm.init(); olm.get_library_version(); client = await getClient(); diff --git a/test/encryption/encrypt_decrypt_room_message_test.dart b/test/encryption/encrypt_decrypt_room_message_test.dart index afa44c4e..18226297 100644 --- a/test/encryption/encrypt_decrypt_room_message_test.dart +++ b/test/encryption/encrypt_decrypt_room_message_test.dart @@ -18,6 +18,7 @@ import 'package:olm/olm.dart' as olm; import 'package:test/test.dart'; +import 'package:vodozemac/vodozemac.dart' as vod; import 'package:matrix/matrix.dart'; import '../fake_client.dart'; @@ -33,6 +34,10 @@ void main() { final now = DateTime.now(); setUpAll(() async { + await vod.init( + wasmPath: './pkg/', + libraryPath: './rust/target/debug/', + ); await olm.init(); olm.get_library_version(); client = await getClient(); diff --git a/test/encryption/encrypt_decrypt_to_device_test.dart b/test/encryption/encrypt_decrypt_to_device_test.dart index 2476617e..649b87c7 100644 --- a/test/encryption/encrypt_decrypt_to_device_test.dart +++ b/test/encryption/encrypt_decrypt_to_device_test.dart @@ -18,6 +18,7 @@ import 'package:olm/olm.dart' as olm; import 'package:test/test.dart'; +import 'package:vodozemac/vodozemac.dart' as vod; import 'package:matrix/matrix.dart'; import '../fake_client.dart'; @@ -41,6 +42,10 @@ void main() async { late Map payload; setUpAll(() async { + await vod.init( + wasmPath: './pkg/', + libraryPath: './rust/target/debug/', + ); await olm.init(); olm.get_library_version(); client = await getClient(); diff --git a/test/encryption/key_manager_test.dart b/test/encryption/key_manager_test.dart index 300d97d3..f2b4c809 100644 --- a/test/encryption/key_manager_test.dart +++ b/test/encryption/key_manager_test.dart @@ -20,6 +20,7 @@ import 'dart:convert'; import 'package:olm/olm.dart' as olm; import 'package:test/test.dart'; +import 'package:vodozemac/vodozemac.dart' as vod; import 'package:matrix/matrix.dart'; import '../fake_client.dart'; @@ -30,6 +31,10 @@ void main() { late Client client; setUpAll(() async { + await vod.init( + wasmPath: './pkg/', + libraryPath: './rust/target/debug/', + ); await olm.init(); olm.get_library_version(); client = await getClient(); diff --git a/test/encryption/key_request_test.dart b/test/encryption/key_request_test.dart index fb0af33a..b74f4a62 100644 --- a/test/encryption/key_request_test.dart +++ b/test/encryption/key_request_test.dart @@ -20,6 +20,7 @@ import 'dart:convert'; import 'package:olm/olm.dart' as olm; import 'package:test/test.dart'; +import 'package:vodozemac/vodozemac.dart' as vod; import 'package:matrix/matrix.dart'; import '../fake_client.dart'; @@ -42,6 +43,10 @@ void main() { Logs().level = Level.error; setUpAll(() async { + await vod.init( + wasmPath: './pkg/', + libraryPath: './rust/target/debug/', + ); await olm.init(); olm.get_library_version(); }); diff --git a/test/encryption/key_verification_test.dart b/test/encryption/key_verification_test.dart index 06fafcd9..35b08ea4 100644 --- a/test/encryption/key_verification_test.dart +++ b/test/encryption/key_verification_test.dart @@ -22,6 +22,7 @@ import 'dart:typed_data'; import 'package:olm/olm.dart' as olm; import 'package:test/test.dart'; +import 'package:vodozemac/vodozemac.dart' as vod; import 'package:matrix/encryption.dart'; import 'package:matrix/matrix.dart'; @@ -58,6 +59,10 @@ void main() async { late Client client2; setUpAll(() async { + await vod.init( + wasmPath: './pkg/', + libraryPath: './rust/target/debug/', + ); await olm.init(); olm.get_library_version(); }); diff --git a/test/encryption/olm_manager_test.dart b/test/encryption/olm_manager_test.dart index d6a7e06a..e3a8a9fa 100644 --- a/test/encryption/olm_manager_test.dart +++ b/test/encryption/olm_manager_test.dart @@ -20,6 +20,7 @@ import 'dart:convert'; import 'package:olm/olm.dart' as olm; import 'package:test/test.dart'; +import 'package:vodozemac/vodozemac.dart' as vod; import 'package:matrix/encryption/utils/json_signature_check_extension.dart'; import 'package:matrix/matrix.dart'; @@ -32,6 +33,10 @@ void main() { late Client client; setUpAll(() async { + await vod.init( + wasmPath: './pkg/', + libraryPath: './rust/target/debug/', + ); await olm.init(); olm.get_library_version(); client = await getClient(); diff --git a/test/encryption/online_key_backup_test.dart b/test/encryption/online_key_backup_test.dart index 0774f436..0930ad3b 100644 --- a/test/encryption/online_key_backup_test.dart +++ b/test/encryption/online_key_backup_test.dart @@ -20,6 +20,7 @@ import 'dart:convert'; import 'package:olm/olm.dart' as olm; import 'package:test/test.dart'; +import 'package:vodozemac/vodozemac.dart' as vod; import 'package:matrix/matrix.dart'; import '../fake_client.dart'; @@ -35,6 +36,10 @@ void main() { final senderKey = 'JBG7ZaPn54OBC7TuIEiylW3BZ+7WcGQhFBPB9pogbAg'; setUpAll(() async { + await vod.init( + wasmPath: './pkg/', + libraryPath: './rust/target/debug/', + ); await olm.init(); olm.get_library_version(); client = await getClient(); diff --git a/test/encryption/qr_verification_self_test.dart b/test/encryption/qr_verification_self_test.dart index 43ff4752..bc7e8a3a 100644 --- a/test/encryption/qr_verification_self_test.dart +++ b/test/encryption/qr_verification_self_test.dart @@ -20,6 +20,7 @@ import 'dart:async'; import 'dart:typed_data'; import 'package:test/test.dart'; +import 'package:vodozemac/vodozemac.dart' as vod; import 'package:matrix/encryption.dart'; import 'package:matrix/matrix.dart'; @@ -69,7 +70,14 @@ void main() async { late Client client1; late Client client2; + Future? vodInit; + setUp(() async { + vodInit ??= vod.init( + wasmPath: './pkg/', + libraryPath: './rust/target/debug/', + ); + await vodInit; client1 = await getClient(); client2 = await getOtherClient(); diff --git a/test/encryption/ssss_test.dart b/test/encryption/ssss_test.dart index c9670caa..4d5136ec 100644 --- a/test/encryption/ssss_test.dart +++ b/test/encryption/ssss_test.dart @@ -22,6 +22,7 @@ import 'dart:typed_data'; import 'package:olm/olm.dart' as olm; import 'package:test/test.dart'; +import 'package:vodozemac/vodozemac.dart' as vod; import 'package:matrix/encryption.dart'; import 'package:matrix/matrix.dart'; @@ -54,6 +55,10 @@ void main() { late Client client; setUpAll(() async { + await vod.init( + wasmPath: './pkg/', + libraryPath: './rust/target/debug/', + ); await olm.init(); olm.get_library_version(); client = await getClient(); diff --git a/test/room_test.dart b/test/room_test.dart index d51d698c..e11305b9 100644 --- a/test/room_test.dart +++ b/test/room_test.dart @@ -22,6 +22,7 @@ import 'dart:math'; import 'dart:typed_data'; import 'package:test/test.dart'; +import 'package:vodozemac/vodozemac.dart' as vod; import 'package:matrix/matrix.dart'; import 'fake_client.dart'; @@ -671,12 +672,20 @@ void main() { expect(fetchedParticipants.length, newParticipants.length); }); - test('calcEncryptionHealthState', () async { - expect( - await room.calcEncryptionHealthState(), - EncryptionHealthState.unverifiedDevices, - ); - }); + test( + 'calcEncryptionHealthState', + () async { + await vod.init( + wasmPath: './pkg/', + libraryPath: './rust/target/debug/', + ); + expect( + await room.calcEncryptionHealthState(), + EncryptionHealthState.allVerified, + ); + }, + tags: 'olm', + ); test('getEventByID', () async { final event = await room.getEventById('1234'); diff --git a/test_driver/matrixsdk_test.dart b/test_driver/matrixsdk_test.dart index 2945a39f..4a216aca 100644 --- a/test_driver/matrixsdk_test.dart +++ b/test_driver/matrixsdk_test.dart @@ -20,6 +20,7 @@ import 'dart:io'; import 'package:olm/olm.dart' as olm; import 'package:test/test.dart'; +import 'package:vodozemac/vodozemac.dart' as vod; import 'package:matrix/matrix.dart'; import '../test/fake_database.dart'; @@ -39,6 +40,10 @@ void main() => group( Client? testClientA, testClientB; try { + await vod.init( + wasmPath: './pkg/', + libraryPath: './rust/target/debug/', + ); await olm.init(); olm.Account(); Logs().i('[LibOlm] Enabled'); From 98fcd683a645ba082075e51fa0b37ec25a1a87eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Ku=C3=9Fowski?= Date: Fri, 30 May 2025 11:47:53 +0200 Subject: [PATCH 2/4] refactor: Migrate megolm to vodozemac --- lib/encryption/encryption.dart | 9 +-- lib/encryption/key_manager.dart | 69 +++++++------------ .../utils/outbound_group_session.dart | 34 +++++---- lib/encryption/utils/pickle_key.dart | 15 ++++ lib/encryption/utils/session_key.dart | 31 +++++---- test/client_test.dart | 7 -- test/commands_test.dart | 5 ++ test/encryption/key_manager_test.dart | 41 +++++------ test/encryption/key_request_test.dart | 3 +- test/encryption/utils_test.dart | 25 +++++++ test/fake_client.dart | 12 ++++ test/room_archived_test.dart | 2 +- test/room_test.dart | 6 +- test_driver/matrixsdk_test.dart | 16 ++--- 14 files changed, 155 insertions(+), 120 deletions(-) create mode 100644 lib/encryption/utils/pickle_key.dart diff --git a/lib/encryption/encryption.dart b/lib/encryption/encryption.dart index 2e711fcd..54a63ddd 100644 --- a/lib/encryption/encryption.dart +++ b/lib/encryption/encryption.dart @@ -227,7 +227,7 @@ class Encryption { canRequestSession = false; // we can't have the key be an int, else json-serializing will fail, thus we need it to be a string - final messageIndexKey = 'key-${decryptResult.message_index}'; + final messageIndexKey = 'key-${decryptResult.messageIndex}'; final messageIndexValue = '${event.eventId}|${event.originServerTs.millisecondsSinceEpoch}'; final haveIndex = @@ -255,13 +255,14 @@ class Encryption { .onError((e, _) => Logs().e('Ignoring error for updating indexes')); } decryptedPayload = json.decode(decryptResult.plaintext); - } catch (exception) { + } catch (exception, stackTrace) { + Logs().w('Could not decrypt event', exception, stackTrace); // alright, if this was actually by our own outbound group session, we might as well clear it if (exception.toString() != DecryptException.unknownSession && (keyManager .getOutboundGroupSession(event.room.id) ?.outboundGroupSession - ?.session_id() ?? + ?.sessionId ?? '') == content.sessionId) { runInRoot( @@ -409,7 +410,7 @@ class Encryption { // they're deprecated. Just left here for compatibility 'device_id': client.deviceID, 'sender_key': identityKey, - 'session_id': sess.outboundGroupSession!.session_id(), + 'session_id': sess.outboundGroupSession!.sessionId, if (mRelatesTo != null) 'm.relates_to': mRelatesTo, }; await keyManager.storeOutboundGroupSession(roomId, sess); diff --git a/lib/encryption/key_manager.dart b/lib/encryption/key_manager.dart index ee7ef210..0b3064ed 100644 --- a/lib/encryption/key_manager.dart +++ b/lib/encryption/key_manager.dart @@ -20,12 +20,12 @@ import 'dart:async'; import 'dart:convert'; import 'package:collection/collection.dart'; -import 'package:olm/olm.dart' as olm; import 'package:vodozemac/vodozemac.dart' as vod; import 'package:matrix/encryption/encryption.dart'; import 'package:matrix/encryption/utils/base64_unpadded.dart'; import 'package:matrix/encryption/utils/outbound_group_session.dart'; +import 'package:matrix/encryption/utils/pickle_key.dart'; import 'package:matrix/encryption/utils/session_key.dart'; import 'package:matrix/encryption/utils/stored_inbound_group_session.dart'; import 'package:matrix/matrix.dart'; @@ -118,16 +118,15 @@ class KeyManager { if (content['algorithm'] != AlgorithmTypes.megolmV1AesSha2) { return; } - late olm.InboundGroupSession inboundGroupSession; + late vod.InboundGroupSession inboundGroupSession; try { - inboundGroupSession = olm.InboundGroupSession(); if (forwarded) { - inboundGroupSession.import_session(content['session_key']); + inboundGroupSession = + vod.InboundGroupSession.import(content['session_key']); } else { - inboundGroupSession.create(content['session_key']); + inboundGroupSession = vod.InboundGroupSession(content['session_key']); } } catch (e, s) { - inboundGroupSession.free(); Logs().e('[LibOlm] Could not create new InboundGroupSession', e, s); return Future.value(); } @@ -142,19 +141,16 @@ class KeyManager { senderClaimedKeys: senderClaimedKeys_, allowedAtIndex: allowedAtIndex_, ); - final oldFirstIndex = - oldSession?.inboundGroupSession?.first_known_index() ?? 0; - final newFirstIndex = newSession.inboundGroupSession!.first_known_index(); + final oldFirstIndex = oldSession?.inboundGroupSession?.firstKnownIndex ?? 0; + final newFirstIndex = newSession.inboundGroupSession!.firstKnownIndex; if (oldSession == null || newFirstIndex < oldFirstIndex || (oldFirstIndex == newFirstIndex && newSession.forwardingCurve25519KeyChain.length < oldSession.forwardingCurve25519KeyChain.length)) { // use new session - oldSession?.dispose(); } else { // we are gonna keep our old session - newSession.dispose(); return; } @@ -169,7 +165,7 @@ class KeyManager { .storeInboundGroupSession( roomId, sessionId, - inboundGroupSession.pickle(userId), + inboundGroupSession.toPickleEncrypted(userId.toPickleKey()), json.encode(content), json.encode({}), json.encode(allowedAtIndex_), @@ -344,7 +340,7 @@ class KeyManager { final inboundSess = await loadInboundGroupSession( room.id, - sess.outboundGroupSession!.session_id(), + sess.outboundGroupSession!.sessionId, ); if (inboundSess == null) { Logs().w('No inbound megolm session found for outbound session!'); @@ -436,8 +432,8 @@ class KeyManager { final rawSession = { 'algorithm': AlgorithmTypes.megolmV1AesSha2, 'room_id': room.id, - 'session_id': sess.outboundGroupSession!.session_id(), - 'session_key': sess.outboundGroupSession!.session_key(), + 'session_id': sess.outboundGroupSession!.sessionId, + 'session_key': sess.outboundGroupSession!.sessionKey, }; try { devicesToReceive.removeWhere((k) => !k.encryptToDevice); @@ -449,16 +445,16 @@ class KeyManager { .containsKey(device.curve25519Key) || inboundSess.allowedAtIndex[device.userId]![ device.curve25519Key]! > - sess.outboundGroupSession!.message_index()) { + sess.outboundGroupSession!.messageIndex) { inboundSess .allowedAtIndex[device.userId]![device.curve25519Key!] = - sess.outboundGroupSession!.message_index(); + sess.outboundGroupSession!.messageIndex; } } await client.database.updateInboundGroupSessionAllowedAtIndex( json.encode(inboundSess!.allowedAtIndex), room.id, - sess.outboundGroupSession!.session_id(), + sess.outboundGroupSession!.sessionId, ); // send out the key await client.sendToDeviceEncryptedChunked( @@ -477,7 +473,6 @@ class KeyManager { return false; } } - sess.dispose(); _outboundGroupSessions.remove(roomId); await client.database.removeOutboundGroupSession(roomId); return true; @@ -492,7 +487,7 @@ class KeyManager { if (userID == null) return; await client.database.storeOutboundGroupSession( roomId, - sess.outboundGroupSession!.pickle(userID), + sess.outboundGroupSession!.toPickleEncrypted(userID.toPickleKey()), json.encode(sess.devices), sess.creationTime.millisecondsSinceEpoch, ); @@ -553,19 +548,13 @@ class KeyManager { final deviceKeys = await room.getUserDeviceKeys(); final deviceKeyIds = _getDeviceKeyIdMap(deviceKeys); deviceKeys.removeWhere((k) => !k.encryptToDevice); - final outboundGroupSession = olm.OutboundGroupSession(); - try { - outboundGroupSession.create(); - } catch (e, s) { - outboundGroupSession.free(); - Logs().e('[LibOlm] Unable to create new outboundGroupSession', e, s); - rethrow; - } + final outboundGroupSession = vod.GroupSession(); + final rawSession = { 'algorithm': AlgorithmTypes.megolmV1AesSha2, 'room_id': room.id, - 'session_id': outboundGroupSession.session_id(), - 'session_key': outboundGroupSession.session_key(), + 'session_id': outboundGroupSession.sessionId, + 'session_key': outboundGroupSession.sessionKey, }; final allowedAtIndex = >{}; for (final device in deviceKeys) { @@ -575,7 +564,7 @@ class KeyManager { } allowedAtIndex[device.userId] ??= {}; allowedAtIndex[device.userId]![device.curve25519Key!] = - outboundGroupSession.message_index(); + outboundGroupSession.messageIndex; } await setInboundGroupSession( roomId, @@ -604,7 +593,6 @@ class KeyManager { e, s, ); - sess.dispose(); rethrow; } return sess; @@ -1163,14 +1151,6 @@ class KeyManager { void dispose() { // ignore: discarded_futures _uploadKeysOnSync?.cancel(); - for (final sess in _outboundGroupSessions.values) { - sess.dispose(); - } - for (final entries in _inboundGroupSessions.values) { - for (final sess in entries.values) { - sess.dispose(); - } - } } } @@ -1233,8 +1213,8 @@ class RoomKeyRequest extends ToDeviceEvent { (session.forwardingCurve25519KeyChain.isEmpty ? keyManager.encryption.fingerprintKey : null); - message['session_key'] = session.inboundGroupSession!.export_session( - index ?? session.inboundGroupSession!.first_known_index(), + message['session_key'] = session.inboundGroupSession!.exportAt( + index ?? session.inboundGroupSession!.firstKnownIndex, ); // send the actual reply of the key back to the requester await keyManager.client.sendToDeviceEncrypted( @@ -1269,8 +1249,7 @@ RoomKeys generateUploadKeysImplementation(GenerateUploadKeysArgs args) { 'forwarding_curve25519_key_chain': sess.forwardingCurve25519KeyChain, 'sender_key': sess.senderKey, 'sender_claimed_keys': sess.senderClaimedKeys, - 'session_key': sess.inboundGroupSession! - .export_session(sess.inboundGroupSession!.first_known_index()), + 'session_key': sess.inboundGroupSession!.exportAtFirstKnownIndex(), }; // encrypt the content final encrypted = enc.encrypt(json.encode(payload)); @@ -1278,7 +1257,7 @@ RoomKeys generateUploadKeysImplementation(GenerateUploadKeysArgs args) { //final device = args.client.getUserDeviceKeysByCurve25519Key(sess.senderKey); // aaaand finally add the session key to our payload roomKeyBackup.sessions[sess.sessionId] = KeyBackupData( - firstMessageIndex: sess.inboundGroupSession!.first_known_index(), + firstMessageIndex: sess.inboundGroupSession!.firstKnownIndex, forwardedCount: sess.forwardingCurve25519KeyChain.length, isVerified: dbSession.verified, //device?.verified ?? false, sessionData: { diff --git a/lib/encryption/utils/outbound_group_session.dart b/lib/encryption/utils/outbound_group_session.dart index f04a5998..0d5bb9e7 100644 --- a/lib/encryption/utils/outbound_group_session.dart +++ b/lib/encryption/utils/outbound_group_session.dart @@ -18,8 +18,9 @@ import 'dart:convert'; -import 'package:olm/olm.dart' as olm; +import 'package:vodozemac/vodozemac.dart' as vod; +import 'package:matrix/encryption/utils/pickle_key.dart'; import 'package:matrix/matrix.dart'; class OutboundGroupSession { @@ -30,8 +31,8 @@ class OutboundGroupSession { Map> devices = {}; // Default to a date, that would get this session rotated in any case to make handling easier DateTime creationTime = DateTime.fromMillisecondsSinceEpoch(0); - olm.OutboundGroupSession? outboundGroupSession; - int? get sentMessages => outboundGroupSession?.message_index(); + vod.GroupSession? outboundGroupSession; + int? get sentMessages => outboundGroupSession?.messageIndex; bool get isValid => outboundGroupSession != null; final String key; @@ -54,19 +55,24 @@ class OutboundGroupSession { ); return; } - outboundGroupSession = olm.OutboundGroupSession(); + + creationTime = + DateTime.fromMillisecondsSinceEpoch(dbEntry['creation_time']); + try { - outboundGroupSession!.unpickle(key, dbEntry['pickle']); - creationTime = - DateTime.fromMillisecondsSinceEpoch(dbEntry['creation_time']); + outboundGroupSession = vod.GroupSession.fromPickleEncrypted( + pickleKey: key.toPickleKey(), + pickle: dbEntry['pickle'], + ); } catch (e, s) { - dispose(); - Logs().e('[LibOlm] Unable to unpickle outboundGroupSession', e, s); + try { + outboundGroupSession = vod.GroupSession.fromOlmPickleEncrypted( + pickleKey: utf8.encode(key), + pickle: dbEntry['pickle'], + ); + } catch (_) { + Logs().e('[LibOlm] Unable to unpickle outboundGroupSession', e, s); + } } } - - void dispose() { - outboundGroupSession?.free(); - outboundGroupSession = null; - } } diff --git a/lib/encryption/utils/pickle_key.dart b/lib/encryption/utils/pickle_key.dart new file mode 100644 index 00000000..6d85ac57 --- /dev/null +++ b/lib/encryption/utils/pickle_key.dart @@ -0,0 +1,15 @@ +import 'dart:typed_data'; + +extension PickleKeyStringExtension on String { + Uint8List toPickleKey() { + final bytes = Uint8List.fromList(codeUnits); + final missing = 32 - bytes.length; + if (missing > 0) { + return Uint8List.fromList([ + ...bytes, + ...List.filled(missing, 0), + ]); + } + return Uint8List.fromList(bytes.getRange(0, 32).toList()); + } +} diff --git a/lib/encryption/utils/session_key.dart b/lib/encryption/utils/session_key.dart index d2da4a15..dd2f487b 100644 --- a/lib/encryption/utils/session_key.dart +++ b/lib/encryption/utils/session_key.dart @@ -16,8 +16,11 @@ * along with this program. If not, see . */ -import 'package:olm/olm.dart' as olm; +import 'dart:convert'; +import 'package:vodozemac/vodozemac.dart' as vod; + +import 'package:matrix/encryption/utils/pickle_key.dart'; import 'package:matrix/encryption/utils/stored_inbound_group_session.dart'; import 'package:matrix/matrix.dart'; @@ -33,7 +36,7 @@ class SessionKey { Map> allowedAtIndex; /// Underlying olm [InboundGroupSession] object - olm.InboundGroupSession? inboundGroupSession; + vod.InboundGroupSession? inboundGroupSession; /// Key for libolm pickle / unpickle final String key; @@ -81,8 +84,7 @@ class SessionKey { .catchMap((k, v) => MapEntry(k, Map.from(v))), roomId = dbEntry.roomId, sessionId = dbEntry.sessionId, - senderKey = dbEntry.senderKey, - inboundGroupSession = olm.InboundGroupSession() { + senderKey = dbEntry.senderKey { final parsedSenderClaimedKeys = Event.getMapFromPayload(dbEntry.senderClaimedKeys) .catchMap((k, v) => MapEntry(k, v)); @@ -99,15 +101,20 @@ class SessionKey { : {})); try { - inboundGroupSession!.unpickle(key, dbEntry.pickle); + inboundGroupSession = vod.InboundGroupSession.fromPickleEncrypted( + pickle: dbEntry.pickle, + pickleKey: key.toPickleKey(), + ); } catch (e, s) { - dispose(); - Logs().e('[LibOlm] Unable to unpickle inboundGroupSession', e, s); + try { + inboundGroupSession = vod.InboundGroupSession.fromOlmPickleEncrypted( + pickle: dbEntry.pickle, + pickleKey: utf8.encode(key), + ); + } catch (_) { + Logs().e('[LibOlm] Unable to unpickle inboundGroupSession', e, s); + rethrow; + } } } - - void dispose() { - inboundGroupSession?.free(); - inboundGroupSession = null; - } } diff --git a/test/client_test.dart b/test/client_test.dart index 6662ea1e..98ceea80 100644 --- a/test/client_test.dart +++ b/test/client_test.dart @@ -68,15 +68,8 @@ void main() { group('client mem', tags: 'olm', () { late Client matrix; - Future? vodInit; - /// Check if all Elements get created setUp(() async { - vodInit ??= vod.init( - wasmPath: './pkg/', - libraryPath: './rust/target/debug/', - ); - await vodInit; matrix = await getClient(); }); diff --git a/test/commands_test.dart b/test/commands_test.dart index 92d0fd6a..238c00cb 100644 --- a/test/commands_test.dart +++ b/test/commands_test.dart @@ -20,6 +20,7 @@ import 'dart:convert'; import 'dart:typed_data'; import 'package:test/test.dart'; +import 'package:vodozemac/vodozemac.dart' as vod; import 'package:matrix/matrix.dart'; import 'fake_client.dart'; @@ -47,6 +48,10 @@ void main() { } test('setupClient', () async { + await vod.init( + wasmPath: './pkg/', + libraryPath: './rust/target/debug/', + ); client = await getClient(); room = Room(id: '!1234:fakeServer.notExisting', client: client); room.setState( diff --git a/test/encryption/key_manager_test.dart b/test/encryption/key_manager_test.dart index f2b4c809..044b0ef7 100644 --- a/test/encryption/key_manager_test.dart +++ b/test/encryption/key_manager_test.dart @@ -115,7 +115,7 @@ void main() { ); var inbound = client.encryption!.keyManager.getInboundGroupSession( roomId, - sess.outboundGroupSession!.session_id(), + sess.outboundGroupSession!.sessionId, ); expect(inbound != null, true); expect( @@ -157,7 +157,7 @@ void main() { // lazy-create if it would rotate sess = await client.encryption!.keyManager .createOutboundGroupSession(roomId); - final oldSessKey = sess.outboundGroupSession!.session_key(); + final oldSessKey = sess.outboundGroupSession!.sessionKey; client.userDeviceKeys['@alice:example.com']!.deviceKeys['JLAFKJWSCS']! .blocked = true; await client.encryption!.keyManager.prepareOutboundGroupSession(roomId); @@ -169,7 +169,7 @@ void main() { client.encryption!.keyManager .getOutboundGroupSession(roomId)! .outboundGroupSession! - .session_key() != + .sessionKey != oldSessKey, true, ); @@ -241,7 +241,7 @@ void main() { ); inbound = client.encryption!.keyManager.getInboundGroupSession( roomId, - sess.outboundGroupSession!.session_id(), + sess.outboundGroupSession!.sessionId, ); expect( inbound!.allowedAtIndex['@alice:example.com'] @@ -358,13 +358,13 @@ void main() { }); test('setInboundGroupSession', () async { - final session = olm.OutboundGroupSession(); - session.create(); - final inbound = olm.InboundGroupSession(); - inbound.create(session.session_key()); + final session = vod.GroupSession(); + + final inbound = vod.InboundGroupSession(session.sessionKey); + final senderKey = client.identityKey; final roomId = '!someroom:example.org'; - final sessionId = inbound.session_id(); + final sessionId = inbound.sessionId; final room = Room(id: roomId, client: client); final nextSyncUpdateFuture = client.onSync.stream .firstWhere((update) => update.rooms != null) @@ -413,7 +413,7 @@ void main() { 'room_id': roomId, 'forwarding_curve25519_key_chain': [client.identityKey], 'session_id': sessionId, - 'session_key': inbound.export_session(1), + 'session_key': inbound.exportAt(1), 'sender_key': senderKey, 'sender_claimed_ed25519_key': client.fingerprintKey, }; @@ -428,7 +428,7 @@ void main() { client.encryption!.keyManager .getInboundGroupSession(roomId, sessionId) ?.inboundGroupSession - ?.first_known_index(), + ?.firstKnownIndex, 1, ); expect( @@ -445,7 +445,7 @@ void main() { 'room_id': roomId, 'forwarding_curve25519_key_chain': [client.identityKey], 'session_id': sessionId, - 'session_key': inbound.export_session(2), + 'session_key': inbound.exportAt(2), 'sender_key': senderKey, 'sender_claimed_ed25519_key': client.fingerprintKey, }; @@ -460,7 +460,7 @@ void main() { client.encryption!.keyManager .getInboundGroupSession(roomId, sessionId) ?.inboundGroupSession - ?.first_known_index(), + ?.firstKnownIndex, 1, ); expect( @@ -477,7 +477,7 @@ void main() { 'room_id': roomId, 'forwarding_curve25519_key_chain': [client.identityKey], 'session_id': sessionId, - 'session_key': inbound.export_session(0), + 'session_key': inbound.exportAt(0), 'sender_key': senderKey, 'sender_claimed_ed25519_key': client.fingerprintKey, }; @@ -492,7 +492,7 @@ void main() { client.encryption!.keyManager .getInboundGroupSession(roomId, sessionId) ?.inboundGroupSession - ?.first_known_index(), + ?.firstKnownIndex, 0, ); expect( @@ -509,7 +509,7 @@ void main() { 'room_id': roomId, 'forwarding_curve25519_key_chain': [client.identityKey, 'beep'], 'session_id': sessionId, - 'session_key': inbound.export_session(0), + 'session_key': inbound.exportAt(0), 'sender_key': senderKey, 'sender_claimed_ed25519_key': client.fingerprintKey, }; @@ -524,7 +524,7 @@ void main() { client.encryption!.keyManager .getInboundGroupSession(roomId, sessionId) ?.inboundGroupSession - ?.first_known_index(), + ?.firstKnownIndex, 0, ); expect( @@ -541,7 +541,7 @@ void main() { 'room_id': roomId, 'forwarding_curve25519_key_chain': [], 'session_id': sessionId, - 'session_key': inbound.export_session(0), + 'session_key': inbound.exportAt(0), 'sender_key': senderKey, 'sender_claimed_ed25519_key': client.fingerprintKey, }; @@ -556,7 +556,7 @@ void main() { client.encryption!.keyManager .getInboundGroupSession(roomId, sessionId) ?.inboundGroupSession - ?.first_known_index(), + ?.firstKnownIndex, 0, ); expect( @@ -575,9 +575,6 @@ void main() { // decrypted last event final syncUpdate = await nextSyncUpdateFuture; expect(syncUpdate.rooms?.join?.containsKey(room.id), true); - - inbound.free(); - session.free(); }); test('Reused deviceID attack', () async { diff --git a/test/encryption/key_request_test.dart b/test/encryption/key_request_test.dart index b74f4a62..19a64388 100644 --- a/test/encryption/key_request_test.dart +++ b/test/encryption/key_request_test.dart @@ -314,8 +314,7 @@ void main() { final session = (await matrix.encryption!.keyManager .loadInboundGroupSession(requestRoom.id, validSessionId))!; - final sessionKey = session.inboundGroupSession! - .export_session(session.inboundGroupSession!.first_known_index()); + final sessionKey = session.inboundGroupSession!.exportAtFirstKnownIndex(); matrix.encryption!.keyManager.clearInboundGroupSessions(); var event = ToDeviceEvent( sender: '@alice:example.com', diff --git a/test/encryption/utils_test.dart b/test/encryption/utils_test.dart index 17c42547..44f7f032 100644 --- a/test/encryption/utils_test.dart +++ b/test/encryption/utils_test.dart @@ -17,10 +17,12 @@ */ import 'dart:convert'; +import 'dart:typed_data'; import 'package:test/test.dart'; import 'package:matrix/encryption/utils/base64_unpadded.dart'; +import 'package:matrix/encryption/utils/pickle_key.dart'; import 'package:matrix/matrix.dart'; void main() { @@ -84,4 +86,27 @@ void main() { ); }); }); + + group('toPickleKey', () { + test('toPickleKey', () { + const shortKey = 'abcd'; + var pickleKey = shortKey.toPickleKey(); + expect(pickleKey.length, 32, reason: 'Pickle key should be 32 bytes'); + expect( + shortKey, + String.fromCharCodes(pickleKey.take(4)), + reason: 'Pickle key should match the first 32 bytes of the input', + ); + + const longKey = + 'abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890'; + pickleKey = longKey.toPickleKey(); + expect(pickleKey.length, 32, reason: 'Pickle key should be 32 bytes'); + expect( + pickleKey, + Uint8List.fromList(longKey.codeUnits.take(32).toList()), + reason: 'Pickle key should match the first 32 bytes of the input', + ); + }); + }); } diff --git a/test/fake_client.dart b/test/fake_client.dart index a6afe5aa..139c805c 100644 --- a/test/fake_client.dart +++ b/test/fake_client.dart @@ -17,6 +17,7 @@ */ import 'package:matrix/matrix.dart'; +import 'package:vodozemac/vodozemac.dart' as vod; import 'fake_database.dart'; const ssssPassphrase = 'nae7ahDiequ7ohniufah3ieS2je1thohX4xeeka7aixohsho9O'; @@ -26,11 +27,22 @@ const ssssKey = 'EsT9 RzbW VhPW yqNp cC7j ViiW 5TZB LuY4 ryyv 9guN Ysmr WDPH'; const pickledOlmAccount = 'N2v1MkIFGcl0mQpo2OCwSopxPQJ0wnl7oe7PKiT4141AijfdTIhRu+ceXzXKy3Kr00nLqXtRv7kid6hU4a+V0rfJWLL0Y51+3Rp/ORDVnQy+SSeo6Fn4FHcXrxifJEJ0djla5u98fBcJ8BSkhIDmtXRPi5/oJAvpiYn+8zMjFHobOeZUAxYR0VfQ9JzSYBsSovoQ7uFkNks1M4EDUvHtuyg3RxViwdNxs3718fyAqQ/VSwbXsY0Nl+qQbF+nlVGHenGqk5SuNl1P6e1PzZxcR0IfXA94Xij1Ob5gDv5YH4UCn9wRMG0abZsQP0YzpDM0FLaHSCyo9i5JD/vMlhH+nZWrgAzPPCTNGYewNV8/h3c+VyJh8ZTx/fVi6Yq46Fv+27Ga2ETRZ3Qn+Oyx6dLBjnBZ9iUvIhqpe2XqaGA1PopOz8iDnaZitw'; +Future? vodInit; + /// only use `path` if you explicitly if you need a db on path instead of in mem Future getClient({ Duration sendTimelineEventTimeout = const Duration(minutes: 1), String? databasePath, }) async { + try { + vodInit ??= vod.init( + wasmPath: './pkg/', + libraryPath: './rust/target/debug/', + ); + await vodInit; + } catch (_) { + Logs().d('Encryption via Vodozemac not enabled'); + } final client = Client( logLevel: Level.verbose, 'testclient', diff --git a/test/room_archived_test.dart b/test/room_archived_test.dart index b9e65a4b..b291e99a 100644 --- a/test/room_archived_test.dart +++ b/test/room_archived_test.dart @@ -138,7 +138,7 @@ void main() async { final inboundSession = client.encryption!.keyManager.getInboundGroupSession( roomid, - outboundSession!.outboundGroupSession!.session_id(), + outboundSession!.outboundGroupSession!.sessionId, )!; // ensure encryption is "enabled" diff --git a/test/room_test.dart b/test/room_test.dart index e11305b9..eb5a2b25 100644 --- a/test/room_test.dart +++ b/test/room_test.dart @@ -675,13 +675,9 @@ void main() { test( 'calcEncryptionHealthState', () async { - await vod.init( - wasmPath: './pkg/', - libraryPath: './rust/target/debug/', - ); expect( await room.calcEncryptionHealthState(), - EncryptionHealthState.allVerified, + EncryptionHealthState.unverifiedDevices, ); }, tags: 'olm', diff --git a/test_driver/matrixsdk_test.dart b/test_driver/matrixsdk_test.dart index 4a216aca..965fdd1d 100644 --- a/test_driver/matrixsdk_test.dart +++ b/test_driver/matrixsdk_test.dart @@ -234,7 +234,7 @@ void main() => group( var currentSessionIdA = room.client.encryption!.keyManager .getOutboundGroupSession(room.id)! .outboundGroupSession! - .session_id(); + .sessionId; /*expect(room.client.encryption.keyManager .getInboundGroupSession(room.id, currentSessionIdA, '') != null);*/ @@ -289,7 +289,7 @@ void main() => group( room.client.encryption!.keyManager .getOutboundGroupSession(room.id)! .outboundGroupSession! - .session_id(), + .sessionId, currentSessionIdA, ); /*expect(room.client.encryption.keyManager @@ -320,7 +320,7 @@ void main() => group( room.client.encryption!.keyManager .getOutboundGroupSession(room.id)! .outboundGroupSession! - .session_id(), + .sessionId, currentSessionIdA, ); final inviteRoomOutboundGroupSession = inviteRoom @@ -330,12 +330,12 @@ void main() => group( expect(inviteRoomOutboundGroupSession.isValid, isTrue); /*expect(inviteRoom.client.encryption.keyManager.getInboundGroupSession( inviteRoom.id, - inviteRoomOutboundGroupSession.outboundGroupSession.session_id(), + inviteRoomOutboundGroupSession.outboundGroupSession.sessionId, '') != null); expect(room.client.encryption.keyManager.getInboundGroupSession( room.id, - inviteRoomOutboundGroupSession.outboundGroupSession.session_id(), + inviteRoomOutboundGroupSession.outboundGroupSession.sessionId, '') != null);*/ expect(inviteRoom.lastEvent!.body, testMessage3); @@ -397,7 +397,7 @@ void main() => group( room.client.encryption!.keyManager .getOutboundGroupSession(room.id)! .outboundGroupSession! - .session_id(), + .sessionId, currentSessionIdA, ); /*expect(inviteRoom.client.encryption.keyManager @@ -445,14 +445,14 @@ void main() => group( room.client.encryption!.keyManager .getOutboundGroupSession(room.id)! .outboundGroupSession! - .session_id(), + .sessionId, isNot(currentSessionIdA), ); } currentSessionIdA = room.client.encryption!.keyManager .getOutboundGroupSession(room.id)! .outboundGroupSession! - .session_id(); + .sessionId; /*expect(inviteRoom.client.encryption.keyManager .getInboundGroupSession(inviteRoom.id, currentSessionIdA, '') != null);*/ From 5fdcbf800682f33347f5f1634d3383986fe01616 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Ku=C3=9Fowski?= Date: Fri, 30 May 2025 14:22:53 +0200 Subject: [PATCH 3/4] refactor: Migrate olm account to vodozemac --- lib/encryption/encryption.dart | 14 +- lib/encryption/olm_manager.dart | 209 +++++++++--------- lib/encryption/utils/olm_session.dart | 30 ++- lib/matrix_api_lite/generated/api.dart | 1 + lib/src/client.dart | 4 - test/client_test.dart | 8 +- .../encrypt_decrypt_to_device_test.dart | 5 +- test/encryption/key_verification_test.dart | 2 +- test/encryption/olm_manager_test.dart | 4 +- test/event_test.dart | 10 + test/fake_client.dart | 5 +- test/room_test.dart | 1 - test/user_test.dart | 11 + 13 files changed, 153 insertions(+), 151 deletions(-) diff --git a/lib/encryption/encryption.dart b/lib/encryption/encryption.dart index 54a63ddd..13c81b63 100644 --- a/lib/encryption/encryption.dart +++ b/lib/encryption/encryption.dart @@ -19,7 +19,7 @@ import 'dart:async'; import 'dart:convert'; -import 'package:olm/olm.dart' as olm; +import 'package:vodozemac/vodozemac.dart' as vod; import 'package:matrix/encryption/cross_signing.dart'; import 'package:matrix/encryption/key_manager.dart'; @@ -87,18 +87,6 @@ class Encryption { if (!isDehydratedDevice) keyManager.startAutoUploadKeys(); } - bool isMinOlmVersion(int major, int minor, int patch) { - try { - final version = olm.get_library_version(); - return version[0] > major || - (version[0] == major && - (version[1] > minor || - (version[1] == minor && version[2] >= patch))); - } catch (_) { - return false; - } - } - Bootstrap bootstrap({void Function(Bootstrap)? onUpdate}) => Bootstrap( encryption: this, onUpdate: onUpdate, diff --git a/lib/encryption/olm_manager.dart b/lib/encryption/olm_manager.dart index d1958152..c445755c 100644 --- a/lib/encryption/olm_manager.dart +++ b/lib/encryption/olm_manager.dart @@ -21,11 +21,12 @@ import 'dart:convert'; import 'package:async/async.dart'; import 'package:canonical_json/canonical_json.dart'; import 'package:collection/collection.dart'; -import 'package:olm/olm.dart' as olm; +import 'package:vodozemac/vodozemac.dart' as vod; import 'package:matrix/encryption/encryption.dart'; import 'package:matrix/encryption/utils/json_signature_check_extension.dart'; import 'package:matrix/encryption/utils/olm_session.dart'; +import 'package:matrix/encryption/utils/pickle_key.dart'; import 'package:matrix/matrix.dart'; import 'package:matrix/msc_extensions/msc_3814_dehydrated_devices/api.dart'; import 'package:matrix/src/utils/run_benchmarked.dart'; @@ -34,20 +35,24 @@ import 'package:matrix/src/utils/run_in_root.dart'; class OlmManager { final Encryption encryption; Client get client => encryption.client; - olm.Account? _olmAccount; + vod.Account? _olmAccount; String? ourDeviceId; /// Returns the base64 encoded keys to store them in a store. /// This String should **never** leave the device! - String? get pickledOlmAccount => - enabled ? _olmAccount!.pickle(client.userID!) : null; + String? get pickledOlmAccount { + return enabled + ? _olmAccount!.toPickleEncrypted(client.userID!.toPickleKey()) + : null; + } + String? get fingerprintKey => - enabled ? json.decode(_olmAccount!.identity_keys())['ed25519'] : null; + enabled ? _olmAccount!.identityKeys.ed25519.toBase64() : null; String? get identityKey => - enabled ? json.decode(_olmAccount!.identity_keys())['curve25519'] : null; + enabled ? _olmAccount!.identityKeys.curve25519.toBase64() : null; String? pickleOlmAccountWithKey(String key) => - enabled ? _olmAccount!.pickle(key) : null; + enabled ? _olmAccount!.toPickleEncrypted(key.toPickleKey()) : null; bool get enabled => _olmAccount != null; @@ -66,33 +71,31 @@ class OlmManager { }) async { ourDeviceId = deviceId; if (olmAccount == null) { - try { - await olm.init(); - _olmAccount = olm.Account(); - _olmAccount!.create(); - if (!await uploadKeys( - uploadDeviceKeys: true, - updateDatabase: false, - dehydratedDeviceAlgorithm: dehydratedDeviceAlgorithm, - dehydratedDevicePickleKey: - dehydratedDeviceAlgorithm != null ? pickleKey : null, - )) { - throw ('Upload key failed'); - } - } catch (_) { - _olmAccount?.free(); - _olmAccount = null; - rethrow; + _olmAccount = vod.Account(); + if (!await uploadKeys( + uploadDeviceKeys: true, + updateDatabase: false, + dehydratedDeviceAlgorithm: dehydratedDeviceAlgorithm, + dehydratedDevicePickleKey: + dehydratedDeviceAlgorithm != null ? pickleKey : null, + )) { + throw ('Upload key failed'); } } else { try { - await olm.init(); - _olmAccount = olm.Account(); - _olmAccount!.unpickle(pickleKey ?? client.userID!, olmAccount); - } catch (_) { - _olmAccount?.free(); - _olmAccount = null; - rethrow; + _olmAccount = vod.Account.fromPickleEncrypted( + pickle: olmAccount, + pickleKey: (pickleKey ?? client.userID!).toPickleKey(), + ); + } catch (e) { + Logs().d( + 'Unable to unpickle account in vodozemac format. Trying Olm format...', + e, + ); + _olmAccount = vod.Account.fromOlmPickleEncrypted( + pickle: olmAccount, + pickleKey: utf8.encode(pickleKey ?? client.userID!), + ); } } } @@ -115,7 +118,8 @@ class OlmManager { if (!payload['signatures'].containsKey(client.userID)) { payload['signatures'][client.userID] = {}; } - payload['signatures'][client.userID]['ed25519:$ourDeviceId'] = signature; + payload['signatures'][client.userID]['ed25519:$ourDeviceId'] = + signature.toBase64(); if (unsigned != null) { payload['unsigned'] = unsigned; } @@ -123,13 +127,13 @@ class OlmManager { } String signString(String s) { - return _olmAccount!.sign(s); + return _olmAccount!.sign(s).toBase64(); } bool _uploadKeysLock = false; CancelableOperation>? currentUpload; - int? get maxNumberOfOneTimeKeys => _olmAccount?.max_number_of_one_time_keys(); + int? get maxNumberOfOneTimeKeys => _olmAccount?.maxNumberOfOneTimeKeys; /// Generates new one time keys, signs everything and upload it to the server. /// If `retry` is > 0, the request will be retried with new OTKs on upload failure. @@ -158,26 +162,24 @@ class OlmManager { if (oldKeyCount != null) { // check if we have OTKs that still need uploading. If we do, we don't try to generate new ones, // instead we try to upload the old ones first - final oldOTKsNeedingUpload = json - .decode(olmAccount.one_time_keys())['curve25519'] - .entries - .length as int; + final oldOTKsNeedingUpload = olmAccount.oneTimeKeys.length; + // generate one-time keys // we generate 2/3rds of max, so that other keys people may still have can // still be used final oneTimeKeysCount = - (olmAccount.max_number_of_one_time_keys() * 2 / 3).floor() - + (olmAccount.maxNumberOfOneTimeKeys * 2 / 3).floor() - oldKeyCount - oldOTKsNeedingUpload; if (oneTimeKeysCount > 0) { - olmAccount.generate_one_time_keys(oneTimeKeysCount); + olmAccount.generateOneTimeKeys(oneTimeKeysCount); } uploadedOneTimeKeysCount = oneTimeKeysCount + oldOTKsNeedingUpload; } - if (encryption.isMinOlmVersion(3, 2, 7) && unusedFallbackKey == false) { + if (unusedFallbackKey == false) { // we don't have an unused fallback key uploaded....so let's change that! - olmAccount.generate_fallback_key(); + olmAccount.generateFallbackKey(); } // we save the generated OTKs into the database. @@ -199,38 +201,32 @@ class OlmManager { }; if (uploadDeviceKeys) { - final Map keys = - json.decode(olmAccount.identity_keys()); - for (final entry in keys.entries) { - final algorithm = entry.key; - final value = entry.value; - deviceKeys['keys']['$algorithm:$ourDeviceId'] = value; - } + final keys = olmAccount.identityKeys; + deviceKeys['keys']['curve25519:$ourDeviceId'] = + keys.curve25519.toBase64(); + deviceKeys['keys']['ed25519:$ourDeviceId'] = keys.ed25519.toBase64(); deviceKeys = signJson(deviceKeys); } // now sign all the one-time keys - for (final entry - in json.decode(olmAccount.one_time_keys())['curve25519'].entries) { + for (final entry in olmAccount.oneTimeKeys.entries) { final key = entry.key; - final value = entry.value; + final value = entry.value.toBase64(); signedOneTimeKeys['signed_curve25519:$key'] = signJson({ 'key': value, }); } final signedFallbackKeys = {}; - if (encryption.isMinOlmVersion(3, 2, 7)) { - final fallbackKey = json.decode(olmAccount.unpublished_fallback_key()); - // now sign all the fallback keys - for (final entry in fallbackKey['curve25519'].entries) { - final key = entry.key; - final value = entry.value; - signedFallbackKeys['signed_curve25519:$key'] = signJson({ - 'key': value, - 'fallback': true, - }); - } + final fallbackKey = olmAccount.fallbackKey; + // now sign all the fallback keys + for (final entry in fallbackKey.entries) { + final key = entry.key; + final value = entry.value.toBase64(); + signedFallbackKeys['signed_curve25519:$key'] = signJson({ + 'key': value, + 'fallback': true, + }); } if (signedFallbackKeys.isEmpty && @@ -281,7 +277,7 @@ class OlmManager { } // mark the OTKs as published and save that to datbase - olmAccount.mark_keys_as_published(); + olmAccount.markKeysAsPublished(); if (updateDatabase) { await encryption.olmDatabase?.updateClientKeys(pickledOlmAccount!); } @@ -301,17 +297,14 @@ class OlmManager { Logs().w('Rotating otks because upload failed', exception); for (final otk in signedOneTimeKeys.values) { // Keys can only be removed by creating a session... - final session = olm.Session(); - try { - final String identity = - json.decode(olmAccount.identity_keys())['curve25519']; - final key = otk.tryGet('key'); - if (key != null) { - session.create_outbound(_olmAccount!, identity, key); - olmAccount.remove_one_time_keys(session); - } - } finally { - session.free(); + + final identity = olmAccount.identityKeys.curve25519.toBase64(); + final key = otk.tryGet('key'); + if (key != null) { + olmAccount.createOutboundSession( + identityKey: vod.Curve25519PublicKey.fromBase64(identity), + oneTimeKey: vod.Curve25519PublicKey.fromBase64(key), + ); } } @@ -342,7 +335,6 @@ class OlmManager { await _otkUpdateDedup.fetch( () => runBenchmarked('handleOtkUpdate', () async { - final haveFallbackKeys = encryption.isMinOlmVersion(3, 2, 0); // Check if there are at least half of max_number_of_one_time_keys left on the server // and generate and upload more if not. @@ -357,7 +349,7 @@ class OlmManager { } // fixup accidental too many uploads. We delete only one of them so that the server has time to update the counts and because we will get rate limited anyway. - if (keyCount > _olmAccount!.max_number_of_one_time_keys()) { + if (keyCount > _olmAccount!.maxNumberOfOneTimeKeys) { final requestingKeysFrom = { client.userID!: {ourDeviceId!: 'signed_curve25519'}, }; @@ -365,14 +357,13 @@ class OlmManager { } // Only upload keys if they are less than half of the max or we have no unused fallback key - if (keyCount < (_olmAccount!.max_number_of_one_time_keys() / 2) || + if (keyCount < (_olmAccount!.maxNumberOfOneTimeKeys / 2) || !unusedFallbackKey) { await uploadKeys( - oldKeyCount: - keyCount < (_olmAccount!.max_number_of_one_time_keys() / 2) - ? keyCount - : null, - unusedFallbackKey: haveFallbackKeys ? unusedFallbackKey : null, + oldKeyCount: keyCount < (_olmAccount!.maxNumberOfOneTimeKeys / 2) + ? keyCount + : null, + unusedFallbackKey: unusedFallbackKey, ); } }), @@ -449,9 +440,12 @@ class OlmManager { if (session.session == null) { continue; } - if (type == 0 && session.session!.matches_inbound(body)) { + if (type == 0) { try { - plaintext = session.session!.decrypt(type, body); + plaintext = session.session!.decrypt( + messageType: type, + ciphertext: body, + ); } catch (e) { // The message was encrypted during this session, but is unable to decrypt throw DecryptException( @@ -463,7 +457,10 @@ class OlmManager { break; } else if (type == 1) { try { - plaintext = session.session!.decrypt(type, body); + plaintext = session.session!.decrypt( + messageType: type, + ciphertext: body, + ); await updateSessionUsage(session); break; } catch (_) { @@ -477,26 +474,27 @@ class OlmManager { } if (plaintext == null) { - final newSession = olm.Session(); try { - newSession.create_inbound_from(_olmAccount!, senderKey, body); - _olmAccount!.remove_one_time_keys(newSession); - await encryption.olmDatabase?.updateClientKeys(pickledOlmAccount!); + final result = _olmAccount!.createInboundSession( + theirIdentityKey: vod.Curve25519PublicKey.fromBase64(senderKey), + preKeyMessageBase64: body, + ); + plaintext = result.plaintext; + final newSession = result.session; - plaintext = newSession.decrypt(type, body); + await encryption.olmDatabase?.updateClientKeys(pickledOlmAccount!); await storeOlmSession( OlmSession( key: client.userID!, identityKey: senderKey, - sessionId: newSession.session_id(), + sessionId: newSession.sessionId, session: newSession, lastReceived: DateTime.now(), ), ); await updateSessionUsage(); } catch (e) { - newSession.free(); throw DecryptException(DecryptException.decryptionFailed, e.toString()); } } @@ -660,25 +658,25 @@ class OlmManager { continue; } Logs().v('[OlmManager] Starting session with $userId:$deviceId'); - final session = olm.Session(); try { - session.create_outbound( - _olmAccount!, - identityKey, - deviceKey.tryGet('key')!, + final session = _olmAccount!.createOutboundSession( + identityKey: vod.Curve25519PublicKey.fromBase64(identityKey), + oneTimeKey: vod.Curve25519PublicKey.fromBase64( + deviceKey.tryGet('key')!, + ), ); + await storeOlmSession( OlmSession( key: client.userID!, identityKey: identityKey, - sessionId: session.session_id(), + sessionId: session.sessionId, session: session, lastReceived: DateTime.now(), // we want to use a newly created session ), ); } catch (e, s) { - session.free(); Logs() .e('[LibOlm] Could not create new outbound olm session', e, s); } @@ -733,8 +731,8 @@ class OlmManager { 'ciphertext': {}, }; encryptedBody['ciphertext'][device.curve25519Key] = { - 'type': encryptResult.type, - 'body': encryptResult.body, + 'type': encryptResult.messageType, + 'body': encryptResult.ciphertext, }; return encryptedBody; } @@ -820,13 +818,6 @@ class OlmManager { Future dispose() async { await currentUpload?.cancel(); - for (final sessions in olmSessions.values) { - for (final sess in sessions) { - sess.dispose(); - } - } - _olmAccount?.free(); - _olmAccount = null; } } diff --git a/lib/encryption/utils/olm_session.dart b/lib/encryption/utils/olm_session.dart index 513699ae..6f44b606 100644 --- a/lib/encryption/utils/olm_session.dart +++ b/lib/encryption/utils/olm_session.dart @@ -16,17 +16,20 @@ * along with this program. If not, see . */ -import 'package:olm/olm.dart' as olm; +import 'dart:convert'; +import 'package:vodozemac/vodozemac.dart' as vod; + +import 'package:matrix/encryption/utils/pickle_key.dart'; import 'package:matrix/matrix.dart'; class OlmSession { String identityKey; String? sessionId; - olm.Session? session; + vod.Session? session; DateTime? lastReceived; final String key; - String? get pickledSession => session?.pickle(key); + String? get pickledSession => session?.toPickleEncrypted(key.toPickleKey()); bool get isValid => session != null; @@ -40,21 +43,24 @@ class OlmSession { OlmSession.fromJson(Map dbEntry, this.key) : identityKey = dbEntry['identity_key'] ?? '' { - session = olm.Session(); try { - session!.unpickle(key, dbEntry['pickle']); + try { + session = vod.Session.fromPickleEncrypted( + pickleKey: key.toPickleKey(), + pickle: dbEntry['pickle'], + ); + } catch (_) { + session = vod.Session.fromOlmPickleEncrypted( + pickleKey: utf8.encode(key), + pickle: dbEntry['pickle'], + ); + } sessionId = dbEntry['session_id']; lastReceived = DateTime.fromMillisecondsSinceEpoch(dbEntry['last_received'] ?? 0); - assert(sessionId == session!.session_id()); + assert(sessionId == session!.sessionId); } catch (e, s) { Logs().e('[LibOlm] Could not unpickle olm session', e, s); - dispose(); } } - - void dispose() { - session?.free(); - session = null; - } } diff --git a/lib/matrix_api_lite/generated/api.dart b/lib/matrix_api_lite/generated/api.dart index c571cf34..bcf02678 100644 --- a/lib/matrix_api_lite/generated/api.dart +++ b/lib/matrix_api_lite/generated/api.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'dart:developer'; import 'dart:typed_data'; import 'package:http/http.dart'; diff --git a/lib/src/client.dart b/lib/src/client.dart index bdb788a6..af32a013 100644 --- a/lib/src/client.dart +++ b/lib/src/client.dart @@ -26,7 +26,6 @@ import 'package:async/async.dart'; import 'package:collection/collection.dart' show IterableExtension; import 'package:http/http.dart' as http; import 'package:mime/mime.dart'; -import 'package:olm/olm.dart' as olm; import 'package:random_string/random_string.dart'; import 'package:matrix/encryption.dart'; @@ -2102,9 +2101,6 @@ class Client extends MatrixApi { await encryption?.dispose(); try { - // make sure to throw an exception if libolm doesn't exist - await olm.init(); - olm.get_library_version(); _encryption = Encryption(client: this); } catch (e) { Logs().e('Error initializing encryption $e'); diff --git a/test/client_test.dart b/test/client_test.dart index 98ceea80..628343a7 100644 --- a/test/client_test.dart +++ b/test/client_test.dart @@ -26,7 +26,6 @@ import 'package:collection/collection.dart'; import 'package:olm/olm.dart' as olm; import 'package:path/path.dart' show join; import 'package:test/test.dart'; -import 'package:vodozemac/vodozemac.dart' as vod; import 'package:matrix/matrix.dart'; import 'package:matrix/src/utils/client_init_exception.dart'; @@ -36,7 +35,7 @@ import 'fake_database.dart'; void main() { // key @test:fakeServer.notExisting const pickledOlmAccount = - 'N2v1MkIFGcl0mQpo2OCwSopxPQJ0wnl7oe7PKiT4141AijfdTIhRu+ceXzXKy3Kr00nLqXtRv7kid6hU4a+V0rfJWLL0Y51+3Rp/ORDVnQy+SSeo6Fn4FHcXrxifJEJ0djla5u98fBcJ8BSkhIDmtXRPi5/oJAvpiYn+8zMjFHobOeZUAxYR0VfQ9JzSYBsSovoQ7uFkNks1M4EDUvHtuyg3RxViwdNxs3718fyAqQ/VSwbXsY0Nl+qQbF+nlVGHenGqk5SuNl1P6e1PzZxcR0IfXA94Xij1Ob5gDv5YH4UCn9wRMG0abZsQP0YzpDM0FLaHSCyo9i5JD/vMlhH+nZWrgAzPPCTNGYewNV8/h3c+VyJh8ZTx/fVi6Yq46Fv+27Ga2ETRZ3Qn+Oyx6dLBjnBZ9iUvIhqpe2XqaGA1PopOz8iDnaZitw'; + 'huxcPifHlyiQsX7cZeMMITbka3hLeUT3ss6DLL6dV7knaD4wgAYK6gcWknkixnX8C5KMIyxzytxiNqAOhDFRE5NsET8hr2dQ8OvXX7M95eQ7/3dPi7FkPUIbvneTSGgJYNDxJdHsDJ8OBHZ3BoqUJFDbTzFfVJjEzN4G9XQwPDafZ2p5WyerOK8Twj/rvk5N+ERmkt1XgVLQl66we/BO1ugTeM3YpDHm5lTzFUitJGTIuuONsKG9mmzdAmVUJ9YIrSxwmOBdegbGA+LAl5acg5VOol3KxRgZUMJQRQ58zpBAs72oauHizv1QVoQ7uIUiCUeb9lym+TEjmApvhru/1CPHU90K5jHNZ57wb/4V9VsqBWuoNibzDWG35YTFLcx0o+1lrCIjm1QjuC0777G+L1HNw5wnppV3z/k0YujjuPS3wvOA30TjHg'; const identityKey = '7rvl3jORJkBiK4XX1e5TnGnqz068XfYJ0W++Ml63rgk'; const fingerprintKey = 'gjL//fyaFHADt9KBADGag8g7F8Up78B/K1zXeiEPLJo'; @@ -1752,7 +1751,10 @@ void main() { expect(error.homeserver, Uri.parse('https://test.server')); expect(error.olmAccount, 'abcd'); expect(error.userId, '@user:server'); - expect(error.toString(), 'Exception: BAD_ACCOUNT_KEY'); + expect( + error.originalException.runtimeType.toString(), + 'AnyhowException', + ); } await customClient.dispose(closeDatabase: true); }, diff --git a/test/encryption/encrypt_decrypt_to_device_test.dart b/test/encryption/encrypt_decrypt_to_device_test.dart index 649b87c7..1ba5ce6d 100644 --- a/test/encryption/encrypt_decrypt_to_device_test.dart +++ b/test/encryption/encrypt_decrypt_to_device_test.dart @@ -25,10 +25,8 @@ import '../fake_client.dart'; import '../fake_database.dart'; void main() async { - // key @othertest:fakeServer.notExisting - const otherPickledOlmAccount = - 'VWhVApbkcilKAEGppsPDf9nNVjaK8/IxT3asSR0sYg0S5KgbfE8vXEPwoiKBX2cEvwX3OessOBOkk+ZE7TTbjlrh/KEd31p8Wo+47qj0AP+Ky+pabnhi+/rTBvZy+gfzTqUfCxZrkzfXI9Op4JnP6gYmy7dVX2lMYIIs9WCO1jcmIXiXum5jnfXu1WLfc7PZtO2hH+k9CDKosOFaXRBmsu8k/BGXPSoWqUpvu6WpEG9t5STk4FeAzA'; final database = await getDatabase(); + group('Encrypt/Decrypt to-device messages', tags: 'olm', () { Logs().level = Level.error; @@ -64,7 +62,6 @@ void main() async { newHomeserver: otherClient.homeserver, newDeviceName: 'Text Matrix Client', newDeviceID: 'FOXDEVICE', - newOlmAccount: otherPickledOlmAccount, ); await otherClient.abortSync(); diff --git a/test/encryption/key_verification_test.dart b/test/encryption/key_verification_test.dart index 35b08ea4..aa191488 100644 --- a/test/encryption/key_verification_test.dart +++ b/test/encryption/key_verification_test.dart @@ -53,7 +53,7 @@ void main() async { // key @othertest:fakeServer.notExisting const otherPickledOlmAccount = - 'VWhVApbkcilKAEGppsPDf9nNVjaK8/IxT3asSR0sYg0S5KgbfE8vXEPwoiKBX2cEvwX3OessOBOkk+ZE7TTbjlrh/KEd31p8Wo+47qj0AP+Ky+pabnhi+/rTBvZy+gfzTqUfCxZrkzfXI9Op4JnP6gYmy7dVX2lMYIIs9WCO1jcmIXiXum5jnfXu1WLfc7PZtO2hH+k9CDKosOFaXRBmsu8k/BGXPSoWqUpvu6WpEG9t5STk4FeAzA'; + '0aFMkSgJhj0kVLxVnactRpl3L2kgIR8bAqICFtDkvp/mkinITZjr1Vh6Jy9FmJzvhLfFUtjU2j/2bqrFn61CSrvRbRaLP6rCFegGJHNGpVfw+c24NthCwGF/SN10aPjPo6yQ3er9bc42I6AmJz5HgyfU6C4bE+LdWrML93C0iEnmQN/SYHnS1KHPXNl6NpFGITggbZQ9jwHOFILWo8wzJ4iqlJtMrNaOOLAAB7By7Fbxl4xoNz2K+w'; late Client client1; late Client client2; diff --git a/test/encryption/olm_manager_test.dart b/test/encryption/olm_manager_test.dart index e3a8a9fa..2d0e4004 100644 --- a/test/encryption/olm_manager_test.dart +++ b/test/encryption/olm_manager_test.dart @@ -67,7 +67,7 @@ void main() { ); expect(sent['device_keys'] != null, true); expect(sent['one_time_keys'] != null, true); - expect(sent['one_time_keys'].keys.length, 66); + expect(sent['one_time_keys'].keys.length, 33); expect(sent['fallback_keys'] != null, true); expect(sent['fallback_keys'].keys.length, 1); FakeMatrixApi.calledEndpoints.clear(); @@ -83,7 +83,7 @@ void main() { sent = json.decode( FakeMatrixApi.calledEndpoints['/client/v3/keys/upload']!.first, ); - expect(sent['one_time_keys'].keys.length, 46); + expect(sent['one_time_keys'].keys.length, 13); expect(sent['fallback_keys'].keys.length, 0); }); diff --git a/test/event_test.dart b/test/event_test.dart index d95114bd..7f244095 100644 --- a/test/event_test.dart +++ b/test/event_test.dart @@ -20,6 +20,7 @@ import 'dart:convert'; import 'dart:typed_data'; import 'package:test/test.dart'; +import 'package:vodozemac/vodozemac.dart' as vod; import 'package:matrix/encryption.dart'; import 'package:matrix/matrix.dart'; @@ -276,6 +277,15 @@ void main() async { }); test('sendAgain', () async { + try { + vodInit ??= vod.init( + wasmPath: './pkg/', + libraryPath: './rust/target/debug/', + ); + await vodInit; + } catch (_) { + Logs().d('Encryption via Vodozemac not enabled'); + } final matrix = Client( 'testclient', httpClient: FakeMatrixApi(), diff --git a/test/fake_client.dart b/test/fake_client.dart index 139c805c..b7a94277 100644 --- a/test/fake_client.dart +++ b/test/fake_client.dart @@ -16,8 +16,9 @@ * along with this program. If not, see . */ -import 'package:matrix/matrix.dart'; import 'package:vodozemac/vodozemac.dart' as vod; + +import 'package:matrix/matrix.dart'; import 'fake_database.dart'; const ssssPassphrase = 'nae7ahDiequ7ohniufah3ieS2je1thohX4xeeka7aixohsho9O'; @@ -25,7 +26,7 @@ const ssssKey = 'EsT9 RzbW VhPW yqNp cC7j ViiW 5TZB LuY4 ryyv 9guN Ysmr WDPH'; // key @test:fakeServer.notExisting const pickledOlmAccount = - 'N2v1MkIFGcl0mQpo2OCwSopxPQJ0wnl7oe7PKiT4141AijfdTIhRu+ceXzXKy3Kr00nLqXtRv7kid6hU4a+V0rfJWLL0Y51+3Rp/ORDVnQy+SSeo6Fn4FHcXrxifJEJ0djla5u98fBcJ8BSkhIDmtXRPi5/oJAvpiYn+8zMjFHobOeZUAxYR0VfQ9JzSYBsSovoQ7uFkNks1M4EDUvHtuyg3RxViwdNxs3718fyAqQ/VSwbXsY0Nl+qQbF+nlVGHenGqk5SuNl1P6e1PzZxcR0IfXA94Xij1Ob5gDv5YH4UCn9wRMG0abZsQP0YzpDM0FLaHSCyo9i5JD/vMlhH+nZWrgAzPPCTNGYewNV8/h3c+VyJh8ZTx/fVi6Yq46Fv+27Ga2ETRZ3Qn+Oyx6dLBjnBZ9iUvIhqpe2XqaGA1PopOz8iDnaZitw'; + 'huxcPifHlyiQsX7cZeMMITbka3hLeUT3ss6DLL6dV7knaD4wgAYK6gcWknkixnX8C5KMIyxzytxiNqAOhDFRE5NsET8hr2dQ8OvXX7M95eQ7/3dPi7FkPUIbvneTSGgJYNDxJdHsDJ8OBHZ3BoqUJFDbTzFfVJjEzN4G9XQwPDafZ2p5WyerOK8Twj/rvk5N+ERmkt1XgVLQl66we/BO1ugTeM3YpDHm5lTzFUitJGTIuuONsKG9mmzdAmVUJ9YIrSxwmOBdegbGA+LAl5acg5VOol3KxRgZUMJQRQ58zpBAs72oauHizv1QVoQ7uIUiCUeb9lym+TEjmApvhru/1CPHU90K5jHNZ57wb/4V9VsqBWuoNibzDWG35YTFLcx0o+1lrCIjm1QjuC0777G+L1HNw5wnppV3z/k0YujjuPS3wvOA30TjHg'; Future? vodInit; diff --git a/test/room_test.dart b/test/room_test.dart index eb5a2b25..0739afd0 100644 --- a/test/room_test.dart +++ b/test/room_test.dart @@ -22,7 +22,6 @@ import 'dart:math'; import 'dart:typed_data'; import 'package:test/test.dart'; -import 'package:vodozemac/vodozemac.dart' as vod; import 'package:matrix/matrix.dart'; import 'fake_client.dart'; diff --git a/test/user_test.dart b/test/user_test.dart index 8f778606..f732013f 100644 --- a/test/user_test.dart +++ b/test/user_test.dart @@ -17,6 +17,7 @@ */ import 'package:test/test.dart'; +import 'package:vodozemac/vodozemac.dart' as vod; import 'package:matrix/matrix.dart'; import 'fake_database.dart'; @@ -27,7 +28,17 @@ void main() async { late Client client; late Room room; late User user1, user2; + Future? vodInit; setUp(() async { + try { + vodInit ??= vod.init( + wasmPath: './pkg/', + libraryPath: './rust/target/debug/', + ); + await vodInit; + } catch (_) { + Logs().d('Encryption via Vodozemac not enabled'); + } client = Client( 'testclient', httpClient: FakeMatrixApi(), From 6df0fb5d06b9ea3795d70e65043f4e05433fb798 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Ku=C3=9Fowski?= Date: Fri, 30 May 2025 14:33:57 +0200 Subject: [PATCH 4/4] refactor: Remove olm dependency --- .github/workflows/app.yml | 4 +- README.md | 8 +-- doc/end-to-end-encryption.md | 29 +++++++++++ doc/get-started.md | 8 +-- doc/web.md | 25 --------- lib/encryption/encryption.dart | 8 ++- lib/encryption/key_manager.dart | 25 +++++---- lib/encryption/olm_manager.dart | 51 ++++++------------- .../utils/json_signature_check_extension.dart | 2 +- lib/encryption/utils/key_verification.dart | 10 ++-- lib/encryption/utils/olm_session.dart | 3 +- .../utils/outbound_group_session.dart | 2 +- lib/encryption/utils/session_key.dart | 3 +- lib/matrix_api_lite/generated/api.dart | 1 - lib/src/client.dart | 38 ++++---------- lib/src/utils/compute_callback.dart | 9 ---- lib/src/utils/matrix_file.dart | 16 ------ lib/src/utils/native_implementations.dart | 23 ++++----- pubspec.yaml | 7 +-- scripts/prepare.sh | 30 ----------- scripts/prepare_vodozemac.sh | 1 + test/client_test.dart | 14 +++-- test/encryption/bootstrap_test.dart | 4 +- test/encryption/cross_signing_test.dart | 4 +- .../encrypt_decrypt_room_message_test.dart | 4 +- .../encrypt_decrypt_to_device_test.dart | 4 +- test/encryption/key_manager_test.dart | 4 +- test/encryption/key_request_test.dart | 3 -- test/encryption/key_verification_test.dart | 3 -- test/encryption/olm_manager_test.dart | 4 +- test/encryption/online_key_backup_test.dart | 15 +++--- test/encryption/ssss_test.dart | 4 +- test_driver/matrixsdk_test.dart | 8 --- 33 files changed, 127 insertions(+), 247 deletions(-) create mode 100644 doc/end-to-end-encryption.md delete mode 100644 doc/web.md diff --git a/.github/workflows/app.yml b/.github/workflows/app.yml index eb3ff6dc..e916e2d9 100644 --- a/.github/workflows/app.yml +++ b/.github/workflows/app.yml @@ -28,7 +28,7 @@ jobs: export HOMESERVER_IMPLEMENTATION=${{matrix.homeserver}} export HOMESERVER="localhost:80" scripts/integration-server-${{matrix.homeserver}}.sh 2>&1 > /dev/null & - sudo apt-get update && sudo apt-get install --no-install-recommends --no-install-suggests -y libolm3 libssl3 sqlite3 libsqlite3-dev + sudo apt-get update && sudo apt-get install --no-install-recommends --no-install-suggests -y libssl3 sqlite3 libsqlite3-dev source scripts/integration-create-environment-variables.sh scripts/integration-prepare-homeserver.sh scripts/prepare.sh @@ -75,7 +75,7 @@ jobs: gitlab_ssh: ${{ secrets.CI_SSH_PRIVATE_KEY}} - name: Run tests run: | - sudo apt-get update && sudo apt-get install --no-install-recommends --no-install-suggests -y lcov libsqlite3-0 libsqlite3-dev libolm3 libssl3 + sudo apt-get update && sudo apt-get install --no-install-recommends --no-install-suggests -y lcov libsqlite3-0 libsqlite3-dev libssl3 ./scripts/prepare_vodozemac.sh ./scripts/test.sh - uses: actions/upload-artifact@v4 diff --git a/README.md b/README.md index 14c01c9e..59b84879 100644 --- a/README.md +++ b/README.md @@ -4,15 +4,17 @@ Matrix (matrix.org) SDK written in dart. ## Native libraries -For E2EE, libolm must be provided. +For E2EE, vodozemac must be provided. Additionally, OpenSSL (libcrypto) must be provided on native platforms for E2EE. -For flutter apps you can easily import it with the [flutter_olm](https://pub.dev/packages/flutter_olm) and the [flutter_openssl_crypto](https://pub.dev/packages/flutter_openssl_crypto) packages. +For flutter apps you can easily import it with the [flutter_vodozemac](https://pub.dev/packages/flutter_vodozemac) and the [flutter_openssl_crypto](https://pub.dev/packages/flutter_openssl_crypto) packages. ```sh flutter pub add matrix -flutter pub add flutter_olm + +# Optional: For end to end encryption: +flutter pub add flutter_vodozemac flutter pub add flutter_openssl_crypto ``` diff --git a/doc/end-to-end-encryption.md b/doc/end-to-end-encryption.md new file mode 100644 index 00000000..ac848cd3 --- /dev/null +++ b/doc/end-to-end-encryption.md @@ -0,0 +1,29 @@ +To enable end to end encryption you need to setup [Vodozemac](https://pub.dev/packages/vodozemac). For this you need Rust installed locally: [rust-lang.org/tools/install](https://www.rust-lang.org/tools/install) + +For Flutter you can use [flutter_vodozemac](https://pub.dev/packages/flutter_vodozemac). + +```sh +flutter pub add flutter_vodozemac +``` + +You also need [flutter_openssl_crypto](https://pub.dev/packages/flutter_openssl_crypto). + +```sh +flutter pub add flutter_openssl_crypto +``` + +Now before you create your `Client`, init vodozemac: + +```dart +import 'package:flutter_vodozemac/flutter_vodozemac' as vod; + +// ... + +await vod.init(); + +final client = Client(/*...*/); +``` + +This should work on Android, iOS, macOS, Linux and Windows. + +For web you need to compile vodozemac to wasm. [Please refer to the Vodozemac bindings documentation](https://pub.dev/packages/vodozemac#build-for-web). \ No newline at end of file diff --git a/doc/get-started.md b/doc/get-started.md index fa3d1267..f1113728 100644 --- a/doc/get-started.md +++ b/doc/get-started.md @@ -6,10 +6,12 @@ In your `pubspec.yaml` file add the following dependencies: ```yaml matrix: - # If you plan to use the SDK in a Flutter application on IO: + # (Optional) If you plan to use the SDK in a Flutter application on IO + # you need sqflite or sqflite_ffi: sqflite: - # For end to end encryption: - flutter_olm: + # (Optional) For end to end encryption, please head on the + # encryption guide and add these dependencies: + flutter_vodozemac: flutter_openssl_crypto: ``` diff --git a/doc/web.md b/doc/web.md deleted file mode 100644 index fd9f6f20..00000000 --- a/doc/web.md +++ /dev/null @@ -1,25 +0,0 @@ -To use end to end encryption in web you have to download the olm javascript/wasm library: - -```sh -#!/bin/sh -ve -rm -r assets/js/package - -OLM_VERSION=$(cat pubspec.yaml | yq .dependencies.flutter_olm) -DOWNLOAD_PATH="https://github.com/famedly/olm/releases/download/v$OLM_VERSION/olm.zip" - -curl -L $DOWNLOAD_PATH > olm.zip -unzip olm.zip -rm olm.zip -``` - -...and import it in your `index.html`: - -```html - - - ... - - - ... - -``` \ No newline at end of file diff --git a/lib/encryption/encryption.dart b/lib/encryption/encryption.dart index 13c81b63..090cfb63 100644 --- a/lib/encryption/encryption.dart +++ b/lib/encryption/encryption.dart @@ -19,8 +19,6 @@ import 'dart:async'; import 'dart:convert'; -import 'package:vodozemac/vodozemac.dart' as vod; - import 'package:matrix/encryption/cross_signing.dart'; import 'package:matrix/encryption/key_manager.dart'; import 'package:matrix/encryption/key_verification_manager.dart'; @@ -166,7 +164,7 @@ class Encryption { return await olmManager.decryptToDeviceEvent(event); } catch (e, s) { Logs().w( - '[LibOlm] Could not decrypt to device event from ${event.sender} with content: ${event.content}', + '[Vodozemac] Could not decrypt to device event from ${event.sender} with content: ${event.content}', e, s, ); @@ -243,8 +241,8 @@ class Encryption { .onError((e, _) => Logs().e('Ignoring error for updating indexes')); } decryptedPayload = json.decode(decryptResult.plaintext); - } catch (exception, stackTrace) { - Logs().w('Could not decrypt event', exception, stackTrace); + } catch (exception) { + Logs().d('Could not decrypt event', exception); // alright, if this was actually by our own outbound group session, we might as well clear it if (exception.toString() != DecryptException.unknownSession && (keyManager diff --git a/lib/encryption/key_manager.dart b/lib/encryption/key_manager.dart index 0b3064ed..49dd8639 100644 --- a/lib/encryption/key_manager.dart +++ b/lib/encryption/key_manager.dart @@ -127,7 +127,7 @@ class KeyManager { inboundGroupSession = vod.InboundGroupSession(content['session_key']); } } catch (e, s) { - Logs().e('[LibOlm] Could not create new InboundGroupSession', e, s); + Logs().e('[Vodozemac] Could not create new InboundGroupSession', e, s); return Future.value(); } final newSession = SessionKey( @@ -465,7 +465,7 @@ class KeyManager { } } catch (e, s) { Logs().e( - '[LibOlm] Unable to re-send the session key at later index to new devices', + '[Vodozemac] Unable to re-send the session key at later index to new devices', e, s, ); @@ -589,7 +589,7 @@ class KeyManager { _outboundGroupSessions[roomId] = sess; } catch (e, s) { Logs().e( - '[LibOlm] Unable to send the session key to the participating devices', + '[Vodozemac] Unable to send the session key to the participating devices', e, s, ); @@ -679,17 +679,15 @@ class KeyManager { try { decrypted = json.decode( decryption.decrypt( - vod.PkMessage( - base64decodeUnpadded(sessionData['ciphertext'] as String), - base64decodeUnpadded(sessionData['mac'] as String), - vod.Curve25519PublicKey.fromBase64( - sessionData['ephemeral'] as String, - ), + vod.PkMessage.fromBase64( + ciphertext: sessionData['ciphertext'] as String, + mac: sessionData['mac'] as String, + ephemeralKey: sessionData['ephemeral'] as String, ), ), ); } catch (e, s) { - Logs().e('[LibOlm] Error decrypting room key', e, s); + Logs().e('[Vodozemac] Error decrypting room key', e, s); } final senderKey = decrypted?.tryGet('sender_key'); if (decrypted != null && senderKey != null) { @@ -1256,14 +1254,15 @@ RoomKeys generateUploadKeysImplementation(GenerateUploadKeysArgs args) { // fetch the device, if available... //final device = args.client.getUserDeviceKeysByCurve25519Key(sess.senderKey); // aaaand finally add the session key to our payload + final (ciphertext, mac, ephemeral) = encrypted.toBase64(); roomKeyBackup.sessions[sess.sessionId] = KeyBackupData( firstMessageIndex: sess.inboundGroupSession!.firstKnownIndex, forwardedCount: sess.forwardingCurve25519KeyChain.length, isVerified: dbSession.verified, //device?.verified ?? false, sessionData: { - 'ephemeral': encrypted.ephemeralKey.toBase64(), - 'ciphertext': base64Encode(encrypted.ciphertext), - 'mac': base64Encode(encrypted.mac), + 'ephemeral': ephemeral, + 'ciphertext': ciphertext, + 'mac': mac, }, ); } diff --git a/lib/encryption/olm_manager.dart b/lib/encryption/olm_manager.dart index c445755c..9b41d3c7 100644 --- a/lib/encryption/olm_manager.dart +++ b/lib/encryption/olm_manager.dart @@ -296,15 +296,9 @@ class OlmManager { exception.error == MatrixError.M_UNKNOWN) { Logs().w('Rotating otks because upload failed', exception); for (final otk in signedOneTimeKeys.values) { - // Keys can only be removed by creating a session... - - final identity = olmAccount.identityKeys.curve25519.toBase64(); final key = otk.tryGet('key'); if (key != null) { - olmAccount.createOutboundSession( - identityKey: vod.Curve25519PublicKey.fromBase64(identity), - oneTimeKey: vod.Curve25519PublicKey.fromBase64(key), - ); + olmAccount.removeOneTimeKey(key); } } @@ -440,32 +434,16 @@ class OlmManager { if (session.session == null) { continue; } - if (type == 0) { - try { - plaintext = session.session!.decrypt( - messageType: type, - ciphertext: body, - ); - } catch (e) { - // The message was encrypted during this session, but is unable to decrypt - throw DecryptException( - DecryptException.decryptionFailed, - e.toString(), - ); - } + + try { + plaintext = session.session!.decrypt( + messageType: type, + ciphertext: body, + ); await updateSessionUsage(session); break; - } else if (type == 1) { - try { - plaintext = session.session!.decrypt( - messageType: type, - ciphertext: body, - ); - await updateSessionUsage(session); - break; - } catch (_) { - plaintext = null; - } + } catch (_) { + plaintext = null; } } } @@ -677,8 +655,11 @@ class OlmManager { ), ); } catch (e, s) { - Logs() - .e('[LibOlm] Could not create new outbound olm session', e, s); + Logs().e( + '[Vodozemac] Could not create new outbound olm session', + e, + s, + ); } } } @@ -767,10 +748,10 @@ class OlmManager { getFromDb: false, ); } on NoOlmSessionFoundException catch (e) { - Logs().d('[LibOlm] Error encrypting to-device event', e); + Logs().d('[Vodozemac] Error encrypting to-device event', e); continue; } catch (e, s) { - Logs().wtf('[LibOlm] Error encrypting to-device event', e, s); + Logs().wtf('[Vodozemac] Error encrypting to-device event', e, s); continue; } } diff --git a/lib/encryption/utils/json_signature_check_extension.dart b/lib/encryption/utils/json_signature_check_extension.dart index e691d415..e0d0be37 100644 --- a/lib/encryption/utils/json_signature_check_extension.dart +++ b/lib/encryption/utils/json_signature_check_extension.dart @@ -45,7 +45,7 @@ extension JsonSignatureCheckExtension on Map { isValid = true; } catch (e, s) { isValid = false; - Logs().w('[LibOlm] Signature check failed', e, s); + Logs().w('[Vodozemac] Signature check failed', e, s); } return isValid; } diff --git a/lib/encryption/utils/key_verification.dart b/lib/encryption/utils/key_verification.dart index 467b0cde..35cc121f 100644 --- a/lib/encryption/utils/key_verification.dart +++ b/lib/encryption/utils/key_verification.dart @@ -1460,7 +1460,11 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod { void _handleKey(Map payload) { theirPublicKey = payload['key']; - establishedSas = sas!.establishSasSecret(payload['key']); + final sas = this.sas; + if (sas == null || sas.disposed) { + throw Exception('SAS object is disposed'); + } + establishedSas = sas.establishSasSecret(payload['key']); } Future _validateCommitment() async { @@ -1553,9 +1557,9 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod { Future _makeCommitment(String pubKey, String canonicalJson) async { if (hash == 'sha256') { - final bytes = utf8.encode(pubKey + canonicalJson); + final bytes = utf8.encoder.convert(pubKey + canonicalJson); final digest = crypto.sha256.convert(bytes); - return base64.encode(digest.bytes); + return encodeBase64Unpadded(digest.bytes); } throw Exception('Unknown hash method'); } diff --git a/lib/encryption/utils/olm_session.dart b/lib/encryption/utils/olm_session.dart index 6f44b606..6c5a385c 100644 --- a/lib/encryption/utils/olm_session.dart +++ b/lib/encryption/utils/olm_session.dart @@ -50,6 +50,7 @@ class OlmSession { pickle: dbEntry['pickle'], ); } catch (_) { + Logs().d('Unable to unpickle Olm session. Try LibOlm format.'); session = vod.Session.fromOlmPickleEncrypted( pickleKey: utf8.encode(key), pickle: dbEntry['pickle'], @@ -60,7 +61,7 @@ class OlmSession { DateTime.fromMillisecondsSinceEpoch(dbEntry['last_received'] ?? 0); assert(sessionId == session!.sessionId); } catch (e, s) { - Logs().e('[LibOlm] Could not unpickle olm session', e, s); + Logs().e('[Vodozemac] Could not unpickle olm session', e, s); } } } diff --git a/lib/encryption/utils/outbound_group_session.dart b/lib/encryption/utils/outbound_group_session.dart index 0d5bb9e7..94219026 100644 --- a/lib/encryption/utils/outbound_group_session.dart +++ b/lib/encryption/utils/outbound_group_session.dart @@ -71,7 +71,7 @@ class OutboundGroupSession { pickle: dbEntry['pickle'], ); } catch (_) { - Logs().e('[LibOlm] Unable to unpickle outboundGroupSession', e, s); + Logs().e('[Vodozemac] Unable to unpickle outboundGroupSession', e, s); } } } diff --git a/lib/encryption/utils/session_key.dart b/lib/encryption/utils/session_key.dart index dd2f487b..77d83fd8 100644 --- a/lib/encryption/utils/session_key.dart +++ b/lib/encryption/utils/session_key.dart @@ -107,12 +107,13 @@ class SessionKey { ); } catch (e, s) { try { + Logs().d('Unable to unpickle inboundGroupSession. Try LibOlm format.'); inboundGroupSession = vod.InboundGroupSession.fromOlmPickleEncrypted( pickle: dbEntry.pickle, pickleKey: utf8.encode(key), ); } catch (_) { - Logs().e('[LibOlm] Unable to unpickle inboundGroupSession', e, s); + Logs().e('[Vodozemac] Unable to unpickle inboundGroupSession', e, s); rethrow; } } diff --git a/lib/matrix_api_lite/generated/api.dart b/lib/matrix_api_lite/generated/api.dart index bcf02678..c571cf34 100644 --- a/lib/matrix_api_lite/generated/api.dart +++ b/lib/matrix_api_lite/generated/api.dart @@ -1,5 +1,4 @@ import 'dart:convert'; -import 'dart:developer'; import 'dart:typed_data'; import 'package:http/http.dart'; diff --git a/lib/src/client.dart b/lib/src/client.dart index af32a013..dff72e3a 100644 --- a/lib/src/client.dart +++ b/lib/src/client.dart @@ -27,6 +27,7 @@ import 'package:collection/collection.dart' show IterableExtension; import 'package:http/http.dart' as http; import 'package:mime/mime.dart'; import 'package:random_string/random_string.dart'; +import 'package:vodozemac/vodozemac.dart' as vod; import 'package:matrix/encryption.dart'; import 'package:matrix/matrix.dart'; @@ -35,7 +36,6 @@ import 'package:matrix/msc_extensions/msc_unpublished_custom_refresh_token_lifet import 'package:matrix/src/models/timeline_chunk.dart'; import 'package:matrix/src/utils/cached_stream_controller.dart'; import 'package:matrix/src/utils/client_init_exception.dart'; -import 'package:matrix/src/utils/compute_callback.dart'; import 'package:matrix/src/utils/multilock.dart'; import 'package:matrix/src/utils/run_benchmarked.dart'; import 'package:matrix/src/utils/run_in_root.dart'; @@ -106,20 +106,6 @@ class Client extends MatrixApi { final bool convertLinebreaksInFormatting; - final ComputeCallback? compute; - - @Deprecated('Use [nativeImplementations] instead') - Future runInBackground( - FutureOr Function(U arg) function, - U arg, - ) async { - final compute = this.compute; - if (compute != null) { - return await compute(function, arg); - } - return await function(arg); - } - final Duration sendTimelineEventTimeout; /// The timeout until a typing indicator gets removed automatically. @@ -208,8 +194,7 @@ class Client extends MatrixApi { Set? supportedLoginTypes, this.mxidLocalPartFallback = true, this.formatLocalpart = true, - @Deprecated('Use [nativeImplementations] instead') this.compute, - NativeImplementations nativeImplementations = NativeImplementations.dummy, + this.nativeImplementations = NativeImplementations.dummy, Level? logLevel, Filter? syncFilter, Duration defaultNetworkRequestTimeout = const Duration(seconds: 35), @@ -247,9 +232,6 @@ class Client extends MatrixApi { supportedLoginTypes = supportedLoginTypes ?? {AuthenticationTypes.password}, verificationMethods = verificationMethods ?? {}, - nativeImplementations = compute != null - ? NativeImplementationsIsolate(compute) - : nativeImplementations, super( httpClient: FixedTimeoutHttpClient( httpClient ?? http.Client(), @@ -2100,12 +2082,14 @@ class Client extends MatrixApi { } await encryption?.dispose(); - try { - _encryption = Encryption(client: this); - } catch (e) { - Logs().e('Error initializing encryption $e'); - await encryption?.dispose(); - _encryption = null; + if (vod.isInitialized()) { + try { + _encryption = Encryption(client: this); + } catch (e) { + Logs().e('Error initializing encryption $e'); + await encryption?.dispose(); + _encryption = null; + } } onInitStateChanged?.call(InitState.settingUpEncryption); await encryption?.init(olmAccount); @@ -3427,7 +3411,7 @@ class Client extends MatrixApi { }); } } catch (e, s) { - Logs().e('[LibOlm] Unable to update user device keys', e, s); + Logs().e('[Vodozemac] Unable to update user device keys', e, s); } } diff --git a/lib/src/utils/compute_callback.dart b/lib/src/utils/compute_callback.dart index d81f4083..f15d9f7a 100644 --- a/lib/src/utils/compute_callback.dart +++ b/lib/src/utils/compute_callback.dart @@ -11,12 +11,3 @@ typedef ComputeRunner = Future Function( FutureOr Function(U arg) function, U arg, ); - -ComputeCallback computeCallbackFromRunInBackground(ComputeRunner runner) { - return ( - FutureOr Function(U arg) callback, - U arg, { - String? debugLabel, - }) => - runner.call(callback, arg); -} diff --git a/lib/src/utils/matrix_file.dart b/lib/src/utils/matrix_file.dart index b82adbbd..cce8e82f 100644 --- a/lib/src/utils/matrix_file.dart +++ b/lib/src/utils/matrix_file.dart @@ -27,7 +27,6 @@ import 'package:image/image.dart'; import 'package:mime/mime.dart'; import 'package:matrix/matrix.dart'; -import 'package:matrix/src/utils/compute_callback.dart'; class MatrixFile { final Uint8List bytes; @@ -112,13 +111,8 @@ class MatrixImageFile extends MatrixFile { required Uint8List bytes, required String name, String? mimeType, - @Deprecated('Use [nativeImplementations] instead') ComputeRunner? compute, NativeImplementations nativeImplementations = NativeImplementations.dummy, }) async { - if (compute != null) { - nativeImplementations = - NativeImplementationsIsolate.fromRunInBackground(compute); - } final metaData = await nativeImplementations.calcImageMetadata(bytes); return MatrixImageFile( @@ -142,13 +136,8 @@ class MatrixImageFile extends MatrixFile { Future Function( MatrixImageFileResizeArguments, )? customImageResizer, - @Deprecated('Use [nativeImplementations] instead') ComputeRunner? compute, NativeImplementations nativeImplementations = NativeImplementations.dummy, }) async { - if (compute != null) { - nativeImplementations = - NativeImplementationsIsolate.fromRunInBackground(compute); - } final image = MatrixImageFile(name: name, mimeType: mimeType, bytes: bytes); return await image.generateThumbnail( @@ -196,13 +185,8 @@ class MatrixImageFile extends MatrixFile { Future Function( MatrixImageFileResizeArguments, )? customImageResizer, - @Deprecated('Use [nativeImplementations] instead') ComputeRunner? compute, NativeImplementations nativeImplementations = NativeImplementations.dummy, }) async { - if (compute != null) { - nativeImplementations = - NativeImplementationsIsolate.fromRunInBackground(compute); - } final arguments = MatrixImageFileResizeArguments( bytes: bytes, maxDimension: dimension, diff --git a/lib/src/utils/native_implementations.dart b/lib/src/utils/native_implementations.dart index eab0426d..367909d3 100644 --- a/lib/src/utils/native_implementations.dart +++ b/lib/src/utils/native_implementations.dart @@ -133,19 +133,13 @@ class NativeImplementationsDummy extends NativeImplementations { class NativeImplementationsIsolate extends NativeImplementations { /// pass by Flutter's compute function here final ComputeCallback compute; + final Future Function()? vodozemacInit; - NativeImplementationsIsolate(this.compute); - - /// creates a [NativeImplementationsIsolate] based on a [ComputeRunner] as - // ignore: deprecated_member_use_from_same_package - /// known from [Client.runInBackground] - factory NativeImplementationsIsolate.fromRunInBackground( - ComputeRunner runInBackground, - ) { - return NativeImplementationsIsolate( - computeCallbackFromRunInBackground(runInBackground), - ); - } + NativeImplementationsIsolate( + this.compute, { + /// To generate upload keys, vodozemac needs to be initialized in the isolate. + this.vodozemacInit, + }); Future runInBackground( FutureOr Function(U arg) function, @@ -172,7 +166,10 @@ class NativeImplementationsIsolate extends NativeImplementations { bool retryInDummy = true, }) async { return runInBackground( - NativeImplementations.dummy.generateUploadKeys, + (GenerateUploadKeysArgs args) async { + await vodozemacInit?.call(); + return NativeImplementations.dummy.generateUploadKeys(args); + }, args, ); } diff --git a/pubspec.yaml b/pubspec.yaml index a1333227..8f8ee085 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -24,18 +24,13 @@ dependencies: js: ^0.6.3 markdown: ^7.1.1 mime: ">=1.0.0 <3.0.0" - olm: ^3.1.0 random_string: ^2.3.1 sdp_transform: ^0.3.2 slugify: ^2.0.0 sqflite_common: ^2.4.5 sqlite3: ^2.1.0 typed_data: ^1.3.2 - vodozemac: - git: - url: https://github.com/famedly/dart-vodozemac.git - path: dart - ref: main + vodozemac: ^0.2.0 webrtc_interface: ^1.2.0 dev_dependencies: diff --git a/scripts/prepare.sh b/scripts/prepare.sh index 5826a474..b0c72655 100755 --- a/scripts/prepare.sh +++ b/scripts/prepare.sh @@ -1,35 +1,5 @@ #!/usr/bin/env bash -ENTRYPOINT="$(pwd)" - -mkdir js -cd js - -curl -O 'https://packages.matrix.org/npm/olm/olm-3.1.4.tgz' -tar xaf olm-3.1.4.tgz - -cd .. - -if [ -f /usr/lib/x86_64-linux-gnu/libolm.so.3 ]; then - mkdir -p ffi/olm/ - ln -sf /usr/lib/x86_64-linux-gnu/libolm.so.3 ffi/olm/libolm.so -# alpine specific location -elif [ -f /usr/lib/libolm.so.3 ]; then - mkdir -p ffi/olm - ln -sf /usr/lib/libolm.so.3 ffi/olm/libolm.so -else - mkdir ffi - cd ffi - cd .. - git clone --depth 1 https://gitlab.matrix.org/matrix-org/olm.git - cd olm - cmake -DCMAKE_BUILD_TYPE=Release . - cmake --build . - cd .. -fi - -cd "$ENTRYPOINT" - if which flutter >/dev/null; then flutter pub get else diff --git a/scripts/prepare_vodozemac.sh b/scripts/prepare_vodozemac.sh index d1b2b725..47f65044 100755 --- a/scripts/prepare_vodozemac.sh +++ b/scripts/prepare_vodozemac.sh @@ -1,5 +1,6 @@ #!/usr/bin/env bash +rm -rf rust git clone https://github.com/famedly/dart-vodozemac.git mv ./dart-vodozemac/rust ./ rm -rf dart-vodozemac diff --git a/test/client_test.dart b/test/client_test.dart index 628343a7..015c73f0 100644 --- a/test/client_test.dart +++ b/test/client_test.dart @@ -23,9 +23,9 @@ import 'dart:typed_data'; import 'package:canonical_json/canonical_json.dart'; import 'package:collection/collection.dart'; -import 'package:olm/olm.dart' as olm; import 'package:path/path.dart' show join; import 'package:test/test.dart'; +import 'package:vodozemac/vodozemac.dart' as vod; import 'package:matrix/matrix.dart'; import 'package:matrix/src/utils/client_init_exception.dart'; @@ -1069,9 +1069,8 @@ void main() { final deviceKeys = []; for (var i = 0; i < 30; i++) { - final account = olm.Account(); - account.create(); - final keys = json.decode(account.identity_keys()); + final account = vod.Account(); + final keys = account.identityKeys; final userId = '@testuser:example.org'; final deviceId = 'DEVICE$i'; final keyObj = { @@ -1082,18 +1081,17 @@ void main() { 'm.megolm.v1.aes-sha2', ], 'keys': { - 'curve25519:$deviceId': keys['curve25519'], - 'ed25519:$deviceId': keys['ed25519'], + 'curve25519:$deviceId': keys.curve25519.toBase64(), + 'ed25519:$deviceId': keys.ed25519.toBase64(), }, }; final signature = account.sign(String.fromCharCodes(canonicalJson.encode(keyObj))); keyObj['signatures'] = { userId: { - 'ed25519:$deviceId': signature, + 'ed25519:$deviceId': signature.toBase64(), }, }; - account.free(); deviceKeys.add(DeviceKeys.fromJson(keyObj, matrix)); } FakeMatrixApi.calledEndpoints.clear(); diff --git a/test/encryption/bootstrap_test.dart b/test/encryption/bootstrap_test.dart index 363e49bb..61106b9d 100644 --- a/test/encryption/bootstrap_test.dart +++ b/test/encryption/bootstrap_test.dart @@ -19,7 +19,6 @@ import 'dart:async'; import 'dart:convert'; -import 'package:olm/olm.dart' as olm; import 'package:test/test.dart'; import 'package:vodozemac/vodozemac.dart' as vod; @@ -40,8 +39,7 @@ void main() { wasmPath: './pkg/', libraryPath: './rust/target/debug/', ); - await olm.init(); - olm.get_library_version(); + client = await getClient(); }); diff --git a/test/encryption/cross_signing_test.dart b/test/encryption/cross_signing_test.dart index d1c4ec04..dc78db98 100644 --- a/test/encryption/cross_signing_test.dart +++ b/test/encryption/cross_signing_test.dart @@ -18,7 +18,6 @@ import 'dart:convert'; -import 'package:olm/olm.dart' as olm; import 'package:test/test.dart'; import 'package:vodozemac/vodozemac.dart' as vod; @@ -36,8 +35,7 @@ void main() { wasmPath: './pkg/', libraryPath: './rust/target/debug/', ); - await olm.init(); - olm.get_library_version(); + client = await getClient(); await client.abortSync(); }); diff --git a/test/encryption/encrypt_decrypt_room_message_test.dart b/test/encryption/encrypt_decrypt_room_message_test.dart index 18226297..047e1ee8 100644 --- a/test/encryption/encrypt_decrypt_room_message_test.dart +++ b/test/encryption/encrypt_decrypt_room_message_test.dart @@ -16,7 +16,6 @@ * along with this program. If not, see . */ -import 'package:olm/olm.dart' as olm; import 'package:test/test.dart'; import 'package:vodozemac/vodozemac.dart' as vod; @@ -38,8 +37,7 @@ void main() { wasmPath: './pkg/', libraryPath: './rust/target/debug/', ); - await olm.init(); - olm.get_library_version(); + client = await getClient(); room = client.getRoomById(roomId)!; }); diff --git a/test/encryption/encrypt_decrypt_to_device_test.dart b/test/encryption/encrypt_decrypt_to_device_test.dart index 1ba5ce6d..67a21533 100644 --- a/test/encryption/encrypt_decrypt_to_device_test.dart +++ b/test/encryption/encrypt_decrypt_to_device_test.dart @@ -16,7 +16,6 @@ * along with this program. If not, see . */ -import 'package:olm/olm.dart' as olm; import 'package:test/test.dart'; import 'package:vodozemac/vodozemac.dart' as vod; @@ -44,8 +43,7 @@ void main() async { wasmPath: './pkg/', libraryPath: './rust/target/debug/', ); - await olm.init(); - olm.get_library_version(); + client = await getClient(); }); diff --git a/test/encryption/key_manager_test.dart b/test/encryption/key_manager_test.dart index 044b0ef7..bfbec9e7 100644 --- a/test/encryption/key_manager_test.dart +++ b/test/encryption/key_manager_test.dart @@ -18,7 +18,6 @@ import 'dart:convert'; -import 'package:olm/olm.dart' as olm; import 'package:test/test.dart'; import 'package:vodozemac/vodozemac.dart' as vod; @@ -35,8 +34,7 @@ void main() { wasmPath: './pkg/', libraryPath: './rust/target/debug/', ); - await olm.init(); - olm.get_library_version(); + client = await getClient(); }); diff --git a/test/encryption/key_request_test.dart b/test/encryption/key_request_test.dart index 19a64388..d107697d 100644 --- a/test/encryption/key_request_test.dart +++ b/test/encryption/key_request_test.dart @@ -18,7 +18,6 @@ import 'dart:convert'; -import 'package:olm/olm.dart' as olm; import 'package:test/test.dart'; import 'package:vodozemac/vodozemac.dart' as vod; @@ -47,8 +46,6 @@ void main() { wasmPath: './pkg/', libraryPath: './rust/target/debug/', ); - await olm.init(); - olm.get_library_version(); }); final validSessionId = 'ciM/JWTPrmiWPPZNkRLDPQYf9AW/I46bxyLSr+Bx5oU'; diff --git a/test/encryption/key_verification_test.dart b/test/encryption/key_verification_test.dart index aa191488..8c71f7e3 100644 --- a/test/encryption/key_verification_test.dart +++ b/test/encryption/key_verification_test.dart @@ -20,7 +20,6 @@ import 'dart:async'; import 'dart:convert'; import 'dart:typed_data'; -import 'package:olm/olm.dart' as olm; import 'package:test/test.dart'; import 'package:vodozemac/vodozemac.dart' as vod; @@ -63,8 +62,6 @@ void main() async { wasmPath: './pkg/', libraryPath: './rust/target/debug/', ); - await olm.init(); - olm.get_library_version(); }); setUp(() async { diff --git a/test/encryption/olm_manager_test.dart b/test/encryption/olm_manager_test.dart index 2d0e4004..8f013d64 100644 --- a/test/encryption/olm_manager_test.dart +++ b/test/encryption/olm_manager_test.dart @@ -18,7 +18,6 @@ import 'dart:convert'; -import 'package:olm/olm.dart' as olm; import 'package:test/test.dart'; import 'package:vodozemac/vodozemac.dart' as vod; @@ -37,8 +36,7 @@ void main() { wasmPath: './pkg/', libraryPath: './rust/target/debug/', ); - await olm.init(); - olm.get_library_version(); + client = await getClient(); }); diff --git a/test/encryption/online_key_backup_test.dart b/test/encryption/online_key_backup_test.dart index 0930ad3b..f7d68751 100644 --- a/test/encryption/online_key_backup_test.dart +++ b/test/encryption/online_key_backup_test.dart @@ -18,7 +18,6 @@ import 'dart:convert'; -import 'package:olm/olm.dart' as olm; import 'package:test/test.dart'; import 'package:vodozemac/vodozemac.dart' as vod; @@ -40,8 +39,7 @@ void main() { wasmPath: './pkg/', libraryPath: './rust/target/debug/', ); - await olm.init(); - olm.get_library_version(); + client = await getClient(); }); @@ -91,20 +89,19 @@ void main() { }); test('upload key', () async { - final session = olm.OutboundGroupSession(); - session.create(); - final inbound = olm.InboundGroupSession(); - inbound.create(session.session_key()); + final session = vod.GroupSession(); + final inbound = vod.InboundGroupSession(session.sessionKey); + final senderKey = client.identityKey; final roomId = '!someroom:example.org'; - final sessionId = inbound.session_id(); + final sessionId = inbound.sessionId; // set a payload... final sessionPayload = { 'algorithm': AlgorithmTypes.megolmV1AesSha2, 'room_id': roomId, 'forwarding_curve25519_key_chain': [client.identityKey], 'session_id': sessionId, - 'session_key': inbound.export_session(1), + 'session_key': inbound.exportAt(1), 'sender_key': senderKey, 'sender_claimed_ed25519_key': client.fingerprintKey, }; diff --git a/test/encryption/ssss_test.dart b/test/encryption/ssss_test.dart index 4d5136ec..96726d76 100644 --- a/test/encryption/ssss_test.dart +++ b/test/encryption/ssss_test.dart @@ -20,7 +20,6 @@ import 'dart:convert'; import 'dart:math'; import 'dart:typed_data'; -import 'package:olm/olm.dart' as olm; import 'package:test/test.dart'; import 'package:vodozemac/vodozemac.dart' as vod; @@ -59,8 +58,7 @@ void main() { wasmPath: './pkg/', libraryPath: './rust/target/debug/', ); - await olm.init(); - olm.get_library_version(); + client = await getClient(); }); diff --git a/test_driver/matrixsdk_test.dart b/test_driver/matrixsdk_test.dart index 965fdd1d..4aa6b414 100644 --- a/test_driver/matrixsdk_test.dart +++ b/test_driver/matrixsdk_test.dart @@ -18,7 +18,6 @@ import 'dart:io'; -import 'package:olm/olm.dart' as olm; import 'package:test/test.dart'; import 'package:vodozemac/vodozemac.dart' as vod; @@ -44,9 +43,6 @@ void main() => group( wasmPath: './pkg/', libraryPath: './rust/target/debug/', ); - await olm.init(); - olm.Account(); - Logs().i('[LibOlm] Enabled'); final homeserverUri = Uri.parse(homeserver); Logs().i('++++ Using homeserver $homeserverUri ++++'); @@ -490,10 +486,6 @@ void main() => group( Client? testClientA, testClientB; try { - await olm.init(); - olm.Account(); - Logs().i('[LibOlm] Enabled'); - final homeserverUri = Uri.parse(homeserver); Logs().i('++++ Using homeserver $homeserverUri ++++');