diff --git a/.github/workflows/app.yml b/.github/workflows/app.yml index 4322566d..e916e2d9 100644 --- a/.github/workflows/app.yml +++ b/.github/workflows/app.yml @@ -18,15 +18,21 @@ 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}} 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 + 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 + 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 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/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/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/encryption.dart b/lib/encryption/encryption.dart index 2e711fcd..090cfb63 100644 --- a/lib/encryption/encryption.dart +++ b/lib/encryption/encryption.dart @@ -19,8 +19,6 @@ import 'dart:async'; import 'dart:convert'; -import 'package:olm/olm.dart' as olm; - import 'package:matrix/encryption/cross_signing.dart'; import 'package:matrix/encryption/key_manager.dart'; import 'package:matrix/encryption/key_verification_manager.dart'; @@ -87,18 +85,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, @@ -178,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, ); @@ -227,7 +213,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 = @@ -256,12 +242,13 @@ class Encryption { } decryptedPayload = json.decode(decryptResult.plaintext); } 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 .getOutboundGroupSession(event.room.id) ?.outboundGroupSession - ?.session_id() ?? + ?.sessionId ?? '') == content.sessionId) { runInRoot( @@ -409,7 +396,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 77fe1679..49dd8639 100644 --- a/lib/encryption/key_manager.dart +++ b/lib/encryption/key_manager.dart @@ -20,11 +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'; @@ -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) { @@ -118,17 +118,16 @@ 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); + Logs().e('[Vodozemac] Could not create new InboundGroupSession', e, s); return Future.value(); } final newSession = SessionKey( @@ -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( @@ -469,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, ); @@ -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, @@ -600,11 +589,10 @@ 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, ); - sess.dispose(); rethrow; } return sess; @@ -669,54 +657,55 @@ 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.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); - } - 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('[Vodozemac] 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 +864,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); @@ -1161,14 +1149,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(); - } - } } } @@ -1231,8 +1211,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( @@ -1247,9 +1227,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) { @@ -1266,30 +1247,28 @@ 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)); // 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!.first_known_index(), + firstMessageIndex: sess.inboundGroupSession!.firstKnownIndex, forwardedCount: sess.forwardingCurve25519KeyChain.length, isVerified: dbSession.verified, //device?.verified ?? false, sessionData: { - 'ephemeral': encrypted.ephemeral, - 'ciphertext': encrypted.ciphertext, - 'mac': encrypted.mac, + 'ephemeral': ephemeral, + 'ciphertext': ciphertext, + 'mac': 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/olm_manager.dart b/lib/encryption/olm_manager.dart index d1958152..9b41d3c7 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!); } @@ -300,18 +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 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 key = otk.tryGet('key'); + if (key != null) { + olmAccount.removeOneTimeKey(key); } } @@ -342,7 +329,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 +343,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 +351,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,26 +434,16 @@ class OlmManager { if (session.session == null) { continue; } - if (type == 0 && session.session!.matches_inbound(body)) { - try { - plaintext = session.session!.decrypt(type, 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(type, body); - await updateSessionUsage(session); - break; - } catch (_) { - plaintext = null; - } + } catch (_) { + plaintext = null; } } } @@ -477,26 +452,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,27 +636,30 @@ 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); + Logs().e( + '[Vodozemac] Could not create new outbound olm session', + e, + s, + ); } } } @@ -733,8 +712,8 @@ class OlmManager { 'ciphertext': {}, }; encryptedBody['ciphertext'][device.curve25519Key] = { - 'type': encryptResult.type, - 'body': encryptResult.body, + 'type': encryptResult.messageType, + 'body': encryptResult.ciphertext, }; return encryptedBody; } @@ -769,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; } } @@ -820,13 +799,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/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..e0d0be37 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(); + 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 007c00b2..35cc121f 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,35 @@ 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']); + final sas = this.sas; + if (sas == null || sas.disposed) { + throw Exception('SAS object is disposed'); + } + 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 +1489,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 +1555,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.encoder.convert(pubKey + canonicalJson); + final digest = crypto.sha256.convert(bytes); + return encodeBase64Unpadded(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/encryption/utils/olm_session.dart b/lib/encryption/utils/olm_session.dart index 513699ae..6c5a385c 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,25 @@ 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 (_) { + Logs().d('Unable to unpickle Olm session. Try LibOlm format.'); + 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(); + Logs().e('[Vodozemac] Could not unpickle olm session', e, s); } } - - void dispose() { - session?.free(); - session = null; - } } diff --git a/lib/encryption/utils/outbound_group_session.dart b/lib/encryption/utils/outbound_group_session.dart index f04a5998..94219026 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('[Vodozemac] 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..77d83fd8 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,21 @@ 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 { + Logs().d('Unable to unpickle inboundGroupSession. Try LibOlm format.'); + inboundGroupSession = vod.InboundGroupSession.fromOlmPickleEncrypted( + pickle: dbEntry.pickle, + pickleKey: utf8.encode(key), + ); + } catch (_) { + Logs().e('[Vodozemac] Unable to unpickle inboundGroupSession', e, s); + rethrow; + } } } - - void dispose() { - inboundGroupSession?.free(); - inboundGroupSession = null; - } } diff --git a/lib/src/client.dart b/lib/src/client.dart index bdb788a6..dff72e3a 100644 --- a/lib/src/client.dart +++ b/lib/src/client.dart @@ -26,8 +26,8 @@ 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:vodozemac/vodozemac.dart' as vod; import 'package:matrix/encryption.dart'; import 'package:matrix/matrix.dart'; @@ -36,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'; @@ -107,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. @@ -209,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), @@ -248,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(), @@ -2101,15 +2082,14 @@ 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'); - 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); @@ -3431,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/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/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 8f282c70..8f8ee085 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -24,13 +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: ^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 new file mode 100755 index 00000000..47f65044 --- /dev/null +++ b/scripts/prepare_vodozemac.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +rm -rf rust +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..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'; @@ -35,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'; @@ -67,10 +67,7 @@ void main() { group('client mem', tags: 'olm', () { late Client matrix; - Logs().level = Level.error; - /// Check if all Elements get created - setUp(() async { matrix = await getClient(); }); @@ -1072,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 = { @@ -1085,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(); @@ -1754,7 +1749,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/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/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..61106b9d 100644 --- a/test/encryption/bootstrap_test.dart +++ b/test/encryption/bootstrap_test.dart @@ -19,8 +19,8 @@ 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; import 'package:matrix/encryption.dart'; import 'package:matrix/matrix.dart'; @@ -35,8 +35,11 @@ void main() { late String origKeyId; setUpAll(() async { - await olm.init(); - olm.get_library_version(); + await vod.init( + wasmPath: './pkg/', + libraryPath: './rust/target/debug/', + ); + client = await getClient(); }); @@ -75,20 +78,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 +132,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 +187,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..dc78db98 100644 --- a/test/encryption/cross_signing_test.dart +++ b/test/encryption/cross_signing_test.dart @@ -18,8 +18,8 @@ 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,8 +31,11 @@ void main() { late Client client; setUpAll(() async { - await olm.init(); - olm.get_library_version(); + await vod.init( + wasmPath: './pkg/', + libraryPath: './rust/target/debug/', + ); + 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 afa44c4e..047e1ee8 100644 --- a/test/encryption/encrypt_decrypt_room_message_test.dart +++ b/test/encryption/encrypt_decrypt_room_message_test.dart @@ -16,8 +16,8 @@ * 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; import 'package:matrix/matrix.dart'; import '../fake_client.dart'; @@ -33,8 +33,11 @@ void main() { final now = DateTime.now(); setUpAll(() async { - await olm.init(); - olm.get_library_version(); + await vod.init( + wasmPath: './pkg/', + libraryPath: './rust/target/debug/', + ); + 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 2476617e..67a21533 100644 --- a/test/encryption/encrypt_decrypt_to_device_test.dart +++ b/test/encryption/encrypt_decrypt_to_device_test.dart @@ -16,18 +16,16 @@ * 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; import 'package:matrix/matrix.dart'; 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; @@ -41,8 +39,11 @@ void main() async { late Map payload; setUpAll(() async { - await olm.init(); - olm.get_library_version(); + await vod.init( + wasmPath: './pkg/', + libraryPath: './rust/target/debug/', + ); + client = await getClient(); }); @@ -59,7 +60,6 @@ void main() async { newHomeserver: otherClient.homeserver, newDeviceName: 'Text Matrix Client', newDeviceID: 'FOXDEVICE', - newOlmAccount: otherPickledOlmAccount, ); await otherClient.abortSync(); diff --git a/test/encryption/key_manager_test.dart b/test/encryption/key_manager_test.dart index 300d97d3..bfbec9e7 100644 --- a/test/encryption/key_manager_test.dart +++ b/test/encryption/key_manager_test.dart @@ -18,8 +18,8 @@ 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,8 +30,11 @@ void main() { late Client client; setUpAll(() async { - await olm.init(); - olm.get_library_version(); + await vod.init( + wasmPath: './pkg/', + libraryPath: './rust/target/debug/', + ); + client = await getClient(); }); @@ -110,7 +113,7 @@ void main() { ); var inbound = client.encryption!.keyManager.getInboundGroupSession( roomId, - sess.outboundGroupSession!.session_id(), + sess.outboundGroupSession!.sessionId, ); expect(inbound != null, true); expect( @@ -152,7 +155,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); @@ -164,7 +167,7 @@ void main() { client.encryption!.keyManager .getOutboundGroupSession(roomId)! .outboundGroupSession! - .session_key() != + .sessionKey != oldSessKey, true, ); @@ -236,7 +239,7 @@ void main() { ); inbound = client.encryption!.keyManager.getInboundGroupSession( roomId, - sess.outboundGroupSession!.session_id(), + sess.outboundGroupSession!.sessionId, ); expect( inbound!.allowedAtIndex['@alice:example.com'] @@ -353,13 +356,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) @@ -408,7 +411,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, }; @@ -423,7 +426,7 @@ void main() { client.encryption!.keyManager .getInboundGroupSession(roomId, sessionId) ?.inboundGroupSession - ?.first_known_index(), + ?.firstKnownIndex, 1, ); expect( @@ -440,7 +443,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, }; @@ -455,7 +458,7 @@ void main() { client.encryption!.keyManager .getInboundGroupSession(roomId, sessionId) ?.inboundGroupSession - ?.first_known_index(), + ?.firstKnownIndex, 1, ); expect( @@ -472,7 +475,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, }; @@ -487,7 +490,7 @@ void main() { client.encryption!.keyManager .getInboundGroupSession(roomId, sessionId) ?.inboundGroupSession - ?.first_known_index(), + ?.firstKnownIndex, 0, ); expect( @@ -504,7 +507,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, }; @@ -519,7 +522,7 @@ void main() { client.encryption!.keyManager .getInboundGroupSession(roomId, sessionId) ?.inboundGroupSession - ?.first_known_index(), + ?.firstKnownIndex, 0, ); expect( @@ -536,7 +539,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, }; @@ -551,7 +554,7 @@ void main() { client.encryption!.keyManager .getInboundGroupSession(roomId, sessionId) ?.inboundGroupSession - ?.first_known_index(), + ?.firstKnownIndex, 0, ); expect( @@ -570,9 +573,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 fb0af33a..d107697d 100644 --- a/test/encryption/key_request_test.dart +++ b/test/encryption/key_request_test.dart @@ -18,8 +18,8 @@ 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,8 +42,10 @@ void main() { Logs().level = Level.error; setUpAll(() async { - await olm.init(); - olm.get_library_version(); + await vod.init( + wasmPath: './pkg/', + libraryPath: './rust/target/debug/', + ); }); final validSessionId = 'ciM/JWTPrmiWPPZNkRLDPQYf9AW/I46bxyLSr+Bx5oU'; @@ -309,8 +311,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/key_verification_test.dart b/test/encryption/key_verification_test.dart index 06fafcd9..8c71f7e3 100644 --- a/test/encryption/key_verification_test.dart +++ b/test/encryption/key_verification_test.dart @@ -20,8 +20,8 @@ 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; import 'package:matrix/encryption.dart'; import 'package:matrix/matrix.dart'; @@ -52,14 +52,16 @@ 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; setUpAll(() async { - await olm.init(); - olm.get_library_version(); + await vod.init( + wasmPath: './pkg/', + libraryPath: './rust/target/debug/', + ); }); setUp(() async { diff --git a/test/encryption/olm_manager_test.dart b/test/encryption/olm_manager_test.dart index d6a7e06a..8f013d64 100644 --- a/test/encryption/olm_manager_test.dart +++ b/test/encryption/olm_manager_test.dart @@ -18,8 +18,8 @@ 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,8 +32,11 @@ void main() { late Client client; setUpAll(() async { - await olm.init(); - olm.get_library_version(); + await vod.init( + wasmPath: './pkg/', + libraryPath: './rust/target/debug/', + ); + client = await getClient(); }); @@ -62,7 +65,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(); @@ -78,7 +81,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/encryption/online_key_backup_test.dart b/test/encryption/online_key_backup_test.dart index 0774f436..f7d68751 100644 --- a/test/encryption/online_key_backup_test.dart +++ b/test/encryption/online_key_backup_test.dart @@ -18,8 +18,8 @@ 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,8 +35,11 @@ void main() { final senderKey = 'JBG7ZaPn54OBC7TuIEiylW3BZ+7WcGQhFBPB9pogbAg'; setUpAll(() async { - await olm.init(); - olm.get_library_version(); + await vod.init( + wasmPath: './pkg/', + libraryPath: './rust/target/debug/', + ); + client = await getClient(); }); @@ -86,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/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..96726d76 100644 --- a/test/encryption/ssss_test.dart +++ b/test/encryption/ssss_test.dart @@ -20,8 +20,8 @@ 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; import 'package:matrix/encryption.dart'; import 'package:matrix/matrix.dart'; @@ -54,8 +54,11 @@ void main() { late Client client; setUpAll(() async { - await olm.init(); - olm.get_library_version(); + await vod.init( + wasmPath: './pkg/', + libraryPath: './rust/target/debug/', + ); + client = await getClient(); }); 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/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 a6afe5aa..b7a94277 100644 --- a/test/fake_client.dart +++ b/test/fake_client.dart @@ -16,6 +16,8 @@ * along with this program. If not, see . */ +import 'package:vodozemac/vodozemac.dart' as vod; + import 'package:matrix/matrix.dart'; import 'fake_database.dart'; @@ -24,13 +26,24 @@ 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; /// 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 d51d698c..0739afd0 100644 --- a/test/room_test.dart +++ b/test/room_test.dart @@ -671,12 +671,16 @@ void main() { expect(fetchedParticipants.length, newParticipants.length); }); - test('calcEncryptionHealthState', () async { - expect( - await room.calcEncryptionHealthState(), - EncryptionHealthState.unverifiedDevices, - ); - }); + test( + 'calcEncryptionHealthState', + () async { + expect( + await room.calcEncryptionHealthState(), + EncryptionHealthState.unverifiedDevices, + ); + }, + tags: 'olm', + ); test('getEventByID', () async { final event = await room.getEventById('1234'); 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(), diff --git a/test_driver/matrixsdk_test.dart b/test_driver/matrixsdk_test.dart index 2945a39f..4aa6b414 100644 --- a/test_driver/matrixsdk_test.dart +++ b/test_driver/matrixsdk_test.dart @@ -18,8 +18,8 @@ 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,9 +39,10 @@ void main() => group( Client? testClientA, testClientB; try { - await olm.init(); - olm.Account(); - Logs().i('[LibOlm] Enabled'); + await vod.init( + wasmPath: './pkg/', + libraryPath: './rust/target/debug/', + ); final homeserverUri = Uri.parse(homeserver); Logs().i('++++ Using homeserver $homeserverUri ++++'); @@ -229,7 +230,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);*/ @@ -284,7 +285,7 @@ void main() => group( room.client.encryption!.keyManager .getOutboundGroupSession(room.id)! .outboundGroupSession! - .session_id(), + .sessionId, currentSessionIdA, ); /*expect(room.client.encryption.keyManager @@ -315,7 +316,7 @@ void main() => group( room.client.encryption!.keyManager .getOutboundGroupSession(room.id)! .outboundGroupSession! - .session_id(), + .sessionId, currentSessionIdA, ); final inviteRoomOutboundGroupSession = inviteRoom @@ -325,12 +326,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); @@ -392,7 +393,7 @@ void main() => group( room.client.encryption!.keyManager .getOutboundGroupSession(room.id)! .outboundGroupSession! - .session_id(), + .sessionId, currentSessionIdA, ); /*expect(inviteRoom.client.encryption.keyManager @@ -440,14 +441,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);*/ @@ -485,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 ++++');