Merge pull request #2100 from famedly/krille/vodozemac

feat: Migrate to vodozemac
This commit is contained in:
Krille-chan 2025-06-10 08:39:12 +02:00 committed by GitHub
commit a881249126
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
46 changed files with 740 additions and 770 deletions

View File

@ -18,15 +18,21 @@ jobs:
- uses: dart-lang/setup-dart@a57a6c04cf7d4840e88432aad6281d1e125f0d46 - uses: dart-lang/setup-dart@a57a6c04cf7d4840e88432aad6281d1e125f0d46
with: with:
sdk: ${{ env.dart_version }} 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 - name: Run tests
run: | run: |
export HOMESERVER_IMPLEMENTATION=${{matrix.homeserver}} export HOMESERVER_IMPLEMENTATION=${{matrix.homeserver}}
export HOMESERVER="localhost:80" export HOMESERVER="localhost:80"
scripts/integration-server-${{matrix.homeserver}}.sh 2>&1 > /dev/null & 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 source scripts/integration-create-environment-variables.sh
scripts/integration-prepare-homeserver.sh scripts/integration-prepare-homeserver.sh
scripts/prepare.sh scripts/prepare.sh
scripts/prepare_vodozemac.sh
scripts/test_driver.sh scripts/test_driver.sh
coverage_without_olm: coverage_without_olm:
@ -54,7 +60,7 @@ jobs:
coverage: coverage:
#runs-on: arm-ubuntu-latest-16core #runs-on: arm-ubuntu-latest-16core
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 5 timeout-minutes: 10
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- run: cat .github/workflows/versions.env >> $GITHUB_ENV - run: cat .github/workflows/versions.env >> $GITHUB_ENV
@ -62,9 +68,15 @@ jobs:
with: with:
sdk: ${{ env.dart_version }} sdk: ${{ env.dart_version }}
#architecture: "arm64" #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 - name: Run tests
run: | 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 ./scripts/test.sh
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
if: ${{ !cancelled() }} if: ${{ !cancelled() }}

1
.gitignore vendored
View File

@ -18,6 +18,7 @@ coverage/
coverage_badge.svg coverage_badge.svg
coverage.xml coverage.xml
TEST-report.* TEST-report.*
rust
# IntelliJ related # IntelliJ related
*.iml *.iml

View File

@ -4,15 +4,17 @@ Matrix (matrix.org) SDK written in dart.
## Native libraries ## 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. 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 ```sh
flutter pub add matrix 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 flutter pub add flutter_openssl_crypto
``` ```

View File

@ -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).

View File

@ -6,10 +6,12 @@ In your `pubspec.yaml` file add the following dependencies:
```yaml ```yaml
matrix: <latest-version> matrix: <latest-version>
# 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: <latest-version> sqflite: <latest-version>
# For end to end encryption: # (Optional) For end to end encryption, please head on the
flutter_olm: <latest-version> # encryption guide and add these dependencies:
flutter_vodozemac: <latest-version>
flutter_openssl_crypto: <latest-version> flutter_openssl_crypto: <latest-version>
``` ```

View File

@ -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
<html>
<head>
...
<script src="path/to/assets/olm.js"></script>
</head>
...
</html>
```

View File

@ -16,9 +16,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import 'dart:convert';
import 'dart:typed_data'; 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/encryption.dart';
import 'package:matrix/encryption/ssss.dart'; import 'package:matrix/encryption/ssss.dart';
@ -31,26 +32,22 @@ class CrossSigning {
CrossSigning(this.encryption) { CrossSigning(this.encryption) {
encryption.ssss.setValidator(EventTypes.CrossSigningSelfSigning, encryption.ssss.setValidator(EventTypes.CrossSigningSelfSigning,
(String secret) async { (String secret) async {
final keyObj = olm.PkSigning();
try { try {
return keyObj.init_with_seed(base64decodeUnpadded(secret)) == final keyObj = vod.PkSigning.fromSecretKey(secret);
return keyObj.publicKey.toBase64() ==
client.userDeviceKeys[client.userID]!.selfSigningKey!.ed25519Key; client.userDeviceKeys[client.userID]!.selfSigningKey!.ed25519Key;
} catch (_) { } catch (_) {
return false; return false;
} finally {
keyObj.free();
} }
}); });
encryption.ssss.setValidator(EventTypes.CrossSigningUserSigning, encryption.ssss.setValidator(EventTypes.CrossSigningUserSigning,
(String secret) async { (String secret) async {
final keyObj = olm.PkSigning();
try { try {
return keyObj.init_with_seed(base64decodeUnpadded(secret)) == final keyObj = vod.PkSigning.fromSecretKey(secret);
return keyObj.publicKey.toBase64() ==
client.userDeviceKeys[client.userID]!.userSigningKey!.ed25519Key; client.userDeviceKeys[client.userID]!.userSigningKey!.ed25519Key;
} catch (_) { } catch (_) {
return false; return false;
} finally {
keyObj.free();
} }
}); });
} }
@ -92,14 +89,13 @@ class CrossSigning {
final masterPrivateKey = base64decodeUnpadded( final masterPrivateKey = base64decodeUnpadded(
await handle.getStored(EventTypes.CrossSigningMasterKey), await handle.getStored(EventTypes.CrossSigningMasterKey),
); );
final keyObj = olm.PkSigning();
String? masterPubkey; String? masterPubkey;
try { try {
masterPubkey = keyObj.init_with_seed(masterPrivateKey); masterPubkey = vod.PkSigning.fromSecretKey(base64Encode(masterPrivateKey))
.publicKey
.toBase64();
} catch (e) { } catch (e) {
masterPubkey = null; masterPubkey = null;
} finally {
keyObj.free();
} }
final userDeviceKeys = final userDeviceKeys =
client.userDeviceKeys[client.userID]?.deviceKeys[client.deviceID]; client.userDeviceKeys[client.userID]?.deviceKeys[client.deviceID];
@ -210,12 +206,7 @@ class CrossSigning {
} }
String _sign(String canonicalJson, Uint8List key) { String _sign(String canonicalJson, Uint8List key) {
final keyObj = olm.PkSigning(); final keyObj = vod.PkSigning.fromSecretKey(base64Encode(key));
try { return keyObj.sign(canonicalJson).toBase64();
keyObj.init_with_seed(key);
return keyObj.sign(canonicalJson);
} finally {
keyObj.free();
}
} }
} }

View File

@ -19,8 +19,6 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'package:olm/olm.dart' as olm;
import 'package:matrix/encryption/cross_signing.dart'; import 'package:matrix/encryption/cross_signing.dart';
import 'package:matrix/encryption/key_manager.dart'; import 'package:matrix/encryption/key_manager.dart';
import 'package:matrix/encryption/key_verification_manager.dart'; import 'package:matrix/encryption/key_verification_manager.dart';
@ -87,18 +85,6 @@ class Encryption {
if (!isDehydratedDevice) keyManager.startAutoUploadKeys(); 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( Bootstrap bootstrap({void Function(Bootstrap)? onUpdate}) => Bootstrap(
encryption: this, encryption: this,
onUpdate: onUpdate, onUpdate: onUpdate,
@ -178,7 +164,7 @@ class Encryption {
return await olmManager.decryptToDeviceEvent(event); return await olmManager.decryptToDeviceEvent(event);
} catch (e, s) { } catch (e, s) {
Logs().w( 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, e,
s, s,
); );
@ -227,7 +213,7 @@ class Encryption {
canRequestSession = false; canRequestSession = false;
// we can't have the key be an int, else json-serializing will fail, thus we need it to be a string // 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 = final messageIndexValue =
'${event.eventId}|${event.originServerTs.millisecondsSinceEpoch}'; '${event.eventId}|${event.originServerTs.millisecondsSinceEpoch}';
final haveIndex = final haveIndex =
@ -256,12 +242,13 @@ class Encryption {
} }
decryptedPayload = json.decode(decryptResult.plaintext); decryptedPayload = json.decode(decryptResult.plaintext);
} catch (exception) { } 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 // alright, if this was actually by our own outbound group session, we might as well clear it
if (exception.toString() != DecryptException.unknownSession && if (exception.toString() != DecryptException.unknownSession &&
(keyManager (keyManager
.getOutboundGroupSession(event.room.id) .getOutboundGroupSession(event.room.id)
?.outboundGroupSession ?.outboundGroupSession
?.session_id() ?? ?.sessionId ??
'') == '') ==
content.sessionId) { content.sessionId) {
runInRoot( runInRoot(
@ -409,7 +396,7 @@ class Encryption {
// they're deprecated. Just left here for compatibility // they're deprecated. Just left here for compatibility
'device_id': client.deviceID, 'device_id': client.deviceID,
'sender_key': identityKey, 'sender_key': identityKey,
'session_id': sess.outboundGroupSession!.session_id(), 'session_id': sess.outboundGroupSession!.sessionId,
if (mRelatesTo != null) 'm.relates_to': mRelatesTo, if (mRelatesTo != null) 'm.relates_to': mRelatesTo,
}; };
await keyManager.storeOutboundGroupSession(roomId, sess); await keyManager.storeOutboundGroupSession(roomId, sess);

View File

@ -20,11 +20,12 @@ import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'package:collection/collection.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/encryption.dart';
import 'package:matrix/encryption/utils/base64_unpadded.dart'; import 'package:matrix/encryption/utils/base64_unpadded.dart';
import 'package:matrix/encryption/utils/outbound_group_session.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/session_key.dart';
import 'package:matrix/encryption/utils/stored_inbound_group_session.dart'; import 'package:matrix/encryption/utils/stored_inbound_group_session.dart';
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
@ -45,19 +46,18 @@ class KeyManager {
KeyManager(this.encryption) { KeyManager(this.encryption) {
encryption.ssss.setValidator(megolmKey, (String secret) async { encryption.ssss.setValidator(megolmKey, (String secret) async {
final keyObj = olm.PkDecryption();
try { try {
final keyObj = vod.PkDecryption.fromSecretKey(
vod.Curve25519PublicKey.fromBase64(secret),
);
final info = await getRoomKeysBackupInfo(false); final info = await getRoomKeysBackupInfo(false);
if (info.algorithm != if (info.algorithm !=
BackupAlgorithm.mMegolmBackupV1Curve25519AesSha2) { BackupAlgorithm.mMegolmBackupV1Curve25519AesSha2) {
return false; return false;
} }
return keyObj.init_with_private_key(base64decodeUnpadded(secret)) == return keyObj.publicKey == info.authData['public_key'];
info.authData['public_key'];
} catch (_) { } catch (_) {
return false; return false;
} finally {
keyObj.free();
} }
}); });
encryption.ssss.setCacheCallback(megolmKey, (String secret) { encryption.ssss.setCacheCallback(megolmKey, (String secret) {
@ -118,17 +118,16 @@ class KeyManager {
if (content['algorithm'] != AlgorithmTypes.megolmV1AesSha2) { if (content['algorithm'] != AlgorithmTypes.megolmV1AesSha2) {
return; return;
} }
late olm.InboundGroupSession inboundGroupSession; late vod.InboundGroupSession inboundGroupSession;
try { try {
inboundGroupSession = olm.InboundGroupSession();
if (forwarded) { if (forwarded) {
inboundGroupSession.import_session(content['session_key']); inboundGroupSession =
vod.InboundGroupSession.import(content['session_key']);
} else { } else {
inboundGroupSession.create(content['session_key']); inboundGroupSession = vod.InboundGroupSession(content['session_key']);
} }
} catch (e, s) { } catch (e, s) {
inboundGroupSession.free(); Logs().e('[Vodozemac] Could not create new InboundGroupSession', e, s);
Logs().e('[LibOlm] Could not create new InboundGroupSession', e, s);
return Future.value(); return Future.value();
} }
final newSession = SessionKey( final newSession = SessionKey(
@ -142,19 +141,16 @@ class KeyManager {
senderClaimedKeys: senderClaimedKeys_, senderClaimedKeys: senderClaimedKeys_,
allowedAtIndex: allowedAtIndex_, allowedAtIndex: allowedAtIndex_,
); );
final oldFirstIndex = final oldFirstIndex = oldSession?.inboundGroupSession?.firstKnownIndex ?? 0;
oldSession?.inboundGroupSession?.first_known_index() ?? 0; final newFirstIndex = newSession.inboundGroupSession!.firstKnownIndex;
final newFirstIndex = newSession.inboundGroupSession!.first_known_index();
if (oldSession == null || if (oldSession == null ||
newFirstIndex < oldFirstIndex || newFirstIndex < oldFirstIndex ||
(oldFirstIndex == newFirstIndex && (oldFirstIndex == newFirstIndex &&
newSession.forwardingCurve25519KeyChain.length < newSession.forwardingCurve25519KeyChain.length <
oldSession.forwardingCurve25519KeyChain.length)) { oldSession.forwardingCurve25519KeyChain.length)) {
// use new session // use new session
oldSession?.dispose();
} else { } else {
// we are gonna keep our old session // we are gonna keep our old session
newSession.dispose();
return; return;
} }
@ -169,7 +165,7 @@ class KeyManager {
.storeInboundGroupSession( .storeInboundGroupSession(
roomId, roomId,
sessionId, sessionId,
inboundGroupSession.pickle(userId), inboundGroupSession.toPickleEncrypted(userId.toPickleKey()),
json.encode(content), json.encode(content),
json.encode({}), json.encode({}),
json.encode(allowedAtIndex_), json.encode(allowedAtIndex_),
@ -344,7 +340,7 @@ class KeyManager {
final inboundSess = await loadInboundGroupSession( final inboundSess = await loadInboundGroupSession(
room.id, room.id,
sess.outboundGroupSession!.session_id(), sess.outboundGroupSession!.sessionId,
); );
if (inboundSess == null) { if (inboundSess == null) {
Logs().w('No inbound megolm session found for outbound session!'); Logs().w('No inbound megolm session found for outbound session!');
@ -436,8 +432,8 @@ class KeyManager {
final rawSession = <String, dynamic>{ final rawSession = <String, dynamic>{
'algorithm': AlgorithmTypes.megolmV1AesSha2, 'algorithm': AlgorithmTypes.megolmV1AesSha2,
'room_id': room.id, 'room_id': room.id,
'session_id': sess.outboundGroupSession!.session_id(), 'session_id': sess.outboundGroupSession!.sessionId,
'session_key': sess.outboundGroupSession!.session_key(), 'session_key': sess.outboundGroupSession!.sessionKey,
}; };
try { try {
devicesToReceive.removeWhere((k) => !k.encryptToDevice); devicesToReceive.removeWhere((k) => !k.encryptToDevice);
@ -449,16 +445,16 @@ class KeyManager {
.containsKey(device.curve25519Key) || .containsKey(device.curve25519Key) ||
inboundSess.allowedAtIndex[device.userId]![ inboundSess.allowedAtIndex[device.userId]![
device.curve25519Key]! > device.curve25519Key]! >
sess.outboundGroupSession!.message_index()) { sess.outboundGroupSession!.messageIndex) {
inboundSess inboundSess
.allowedAtIndex[device.userId]![device.curve25519Key!] = .allowedAtIndex[device.userId]![device.curve25519Key!] =
sess.outboundGroupSession!.message_index(); sess.outboundGroupSession!.messageIndex;
} }
} }
await client.database.updateInboundGroupSessionAllowedAtIndex( await client.database.updateInboundGroupSessionAllowedAtIndex(
json.encode(inboundSess!.allowedAtIndex), json.encode(inboundSess!.allowedAtIndex),
room.id, room.id,
sess.outboundGroupSession!.session_id(), sess.outboundGroupSession!.sessionId,
); );
// send out the key // send out the key
await client.sendToDeviceEncryptedChunked( await client.sendToDeviceEncryptedChunked(
@ -469,7 +465,7 @@ class KeyManager {
} }
} catch (e, s) { } catch (e, s) {
Logs().e( 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, e,
s, s,
); );
@ -477,7 +473,6 @@ class KeyManager {
return false; return false;
} }
} }
sess.dispose();
_outboundGroupSessions.remove(roomId); _outboundGroupSessions.remove(roomId);
await client.database.removeOutboundGroupSession(roomId); await client.database.removeOutboundGroupSession(roomId);
return true; return true;
@ -492,7 +487,7 @@ class KeyManager {
if (userID == null) return; if (userID == null) return;
await client.database.storeOutboundGroupSession( await client.database.storeOutboundGroupSession(
roomId, roomId,
sess.outboundGroupSession!.pickle(userID), sess.outboundGroupSession!.toPickleEncrypted(userID.toPickleKey()),
json.encode(sess.devices), json.encode(sess.devices),
sess.creationTime.millisecondsSinceEpoch, sess.creationTime.millisecondsSinceEpoch,
); );
@ -553,19 +548,13 @@ class KeyManager {
final deviceKeys = await room.getUserDeviceKeys(); final deviceKeys = await room.getUserDeviceKeys();
final deviceKeyIds = _getDeviceKeyIdMap(deviceKeys); final deviceKeyIds = _getDeviceKeyIdMap(deviceKeys);
deviceKeys.removeWhere((k) => !k.encryptToDevice); deviceKeys.removeWhere((k) => !k.encryptToDevice);
final outboundGroupSession = olm.OutboundGroupSession(); final outboundGroupSession = vod.GroupSession();
try {
outboundGroupSession.create();
} catch (e, s) {
outboundGroupSession.free();
Logs().e('[LibOlm] Unable to create new outboundGroupSession', e, s);
rethrow;
}
final rawSession = <String, dynamic>{ final rawSession = <String, dynamic>{
'algorithm': AlgorithmTypes.megolmV1AesSha2, 'algorithm': AlgorithmTypes.megolmV1AesSha2,
'room_id': room.id, 'room_id': room.id,
'session_id': outboundGroupSession.session_id(), 'session_id': outboundGroupSession.sessionId,
'session_key': outboundGroupSession.session_key(), 'session_key': outboundGroupSession.sessionKey,
}; };
final allowedAtIndex = <String, Map<String, int>>{}; final allowedAtIndex = <String, Map<String, int>>{};
for (final device in deviceKeys) { for (final device in deviceKeys) {
@ -575,7 +564,7 @@ class KeyManager {
} }
allowedAtIndex[device.userId] ??= <String, int>{}; allowedAtIndex[device.userId] ??= <String, int>{};
allowedAtIndex[device.userId]![device.curve25519Key!] = allowedAtIndex[device.userId]![device.curve25519Key!] =
outboundGroupSession.message_index(); outboundGroupSession.messageIndex;
} }
await setInboundGroupSession( await setInboundGroupSession(
roomId, roomId,
@ -600,11 +589,10 @@ class KeyManager {
_outboundGroupSessions[roomId] = sess; _outboundGroupSessions[roomId] = sess;
} catch (e, s) { } catch (e, s) {
Logs().e( 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, e,
s, s,
); );
sess.dispose();
rethrow; rethrow;
} }
return sess; return sess;
@ -669,11 +657,13 @@ class KeyManager {
} }
final privateKey = final privateKey =
base64decodeUnpadded((await encryption.ssss.getCached(megolmKey))!); base64decodeUnpadded((await encryption.ssss.getCached(megolmKey))!);
final decryption = olm.PkDecryption();
final info = await getRoomKeysBackupInfo(); final info = await getRoomKeysBackupInfo();
String backupPubKey; String backupPubKey;
try {
backupPubKey = decryption.init_with_private_key(privateKey); final decryption = vod.PkDecryption.fromSecretKey(
vod.Curve25519PublicKey.fromBytes(privateKey),
);
backupPubKey = decryption.publicKey;
if (info.algorithm != BackupAlgorithm.mMegolmBackupV1Curve25519AesSha2 || if (info.algorithm != BackupAlgorithm.mMegolmBackupV1Curve25519AesSha2 ||
info.authData['public_key'] != backupPubKey) { info.authData['public_key'] != backupPubKey) {
@ -689,13 +679,15 @@ class KeyManager {
try { try {
decrypted = json.decode( decrypted = json.decode(
decryption.decrypt( decryption.decrypt(
sessionData['ephemeral'] as String, vod.PkMessage.fromBase64(
sessionData['mac'] as String, ciphertext: sessionData['ciphertext'] as String,
sessionData['ciphertext'] as String, mac: sessionData['mac'] as String,
ephemeralKey: sessionData['ephemeral'] as String,
),
), ),
); );
} catch (e, s) { } catch (e, s) {
Logs().e('[LibOlm] Error decrypting room key', e, s); Logs().e('[Vodozemac] Error decrypting room key', e, s);
} }
final senderKey = decrypted?.tryGet<String>('sender_key'); final senderKey = decrypted?.tryGet<String>('sender_key');
if (decrypted != null && senderKey != null) { if (decrypted != null && senderKey != null) {
@ -715,9 +707,6 @@ class KeyManager {
} }
} }
} }
} finally {
decryption.free();
}
} }
/// Loads and stores all keys from the online key backup. This may take a /// Loads and stores all keys from the online key backup. This may take a
@ -875,16 +864,18 @@ class KeyManager {
final privateKey = final privateKey =
base64decodeUnpadded((await encryption.ssss.getCached(megolmKey))!); 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 // 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); final info = await getRoomKeysBackupInfo(false);
String backupPubKey; String backupPubKey;
try {
backupPubKey = decryption.init_with_private_key(privateKey); final decryption = vod.PkDecryption.fromSecretKey(
vod.Curve25519PublicKey.fromBytes(privateKey),
);
backupPubKey = decryption.publicKey;
if (info.algorithm != if (info.algorithm !=
BackupAlgorithm.mMegolmBackupV1Curve25519AesSha2 || BackupAlgorithm.mMegolmBackupV1Curve25519AesSha2 ||
info.authData['public_key'] != backupPubKey) { info.authData['public_key'] != backupPubKey) {
decryption.free();
return; return;
} }
final args = GenerateUploadKeysArgs( final args = GenerateUploadKeysArgs(
@ -924,9 +915,6 @@ class KeyManager {
dbSession.sessionId, dbSession.sessionId,
); );
} }
} finally {
decryption.free();
}
} catch (e, s) { } catch (e, s) {
Logs().e('[Key Manager] Error uploading room keys', e, s); Logs().e('[Key Manager] Error uploading room keys', e, s);
} }
@ -1161,14 +1149,6 @@ class KeyManager {
void dispose() { void dispose() {
// ignore: discarded_futures // ignore: discarded_futures
_uploadKeysOnSync?.cancel(); _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 (session.forwardingCurve25519KeyChain.isEmpty
? keyManager.encryption.fingerprintKey ? keyManager.encryption.fingerprintKey
: null); : null);
message['session_key'] = session.inboundGroupSession!.export_session( message['session_key'] = session.inboundGroupSession!.exportAt(
index ?? session.inboundGroupSession!.first_known_index(), index ?? session.inboundGroupSession!.firstKnownIndex,
); );
// send the actual reply of the key back to the requester // send the actual reply of the key back to the requester
await keyManager.client.sendToDeviceEncrypted( await keyManager.client.sendToDeviceEncrypted(
@ -1247,9 +1227,10 @@ class RoomKeyRequest extends ToDeviceEvent {
/// you would likely want to use [NativeImplementations] and /// you would likely want to use [NativeImplementations] and
/// [Client.nativeImplementations] instead /// [Client.nativeImplementations] instead
RoomKeys generateUploadKeysImplementation(GenerateUploadKeysArgs args) { RoomKeys generateUploadKeysImplementation(GenerateUploadKeysArgs args) {
final enc = olm.PkEncryption();
try { 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 // first we generate the payload to upload all the session keys in this chunk
final roomKeys = RoomKeys(rooms: {}); final roomKeys = RoomKeys(rooms: {});
for (final dbSession in args.dbSessions) { for (final dbSession in args.dbSessions) {
@ -1266,30 +1247,28 @@ RoomKeys generateUploadKeysImplementation(GenerateUploadKeysArgs args) {
'forwarding_curve25519_key_chain': sess.forwardingCurve25519KeyChain, 'forwarding_curve25519_key_chain': sess.forwardingCurve25519KeyChain,
'sender_key': sess.senderKey, 'sender_key': sess.senderKey,
'sender_claimed_keys': sess.senderClaimedKeys, 'sender_claimed_keys': sess.senderClaimedKeys,
'session_key': sess.inboundGroupSession! 'session_key': sess.inboundGroupSession!.exportAtFirstKnownIndex(),
.export_session(sess.inboundGroupSession!.first_known_index()),
}; };
// encrypt the content // encrypt the content
final encrypted = enc.encrypt(json.encode(payload)); final encrypted = enc.encrypt(json.encode(payload));
// fetch the device, if available... // fetch the device, if available...
//final device = args.client.getUserDeviceKeysByCurve25519Key(sess.senderKey); //final device = args.client.getUserDeviceKeysByCurve25519Key(sess.senderKey);
// aaaand finally add the session key to our payload // aaaand finally add the session key to our payload
final (ciphertext, mac, ephemeral) = encrypted.toBase64();
roomKeyBackup.sessions[sess.sessionId] = KeyBackupData( roomKeyBackup.sessions[sess.sessionId] = KeyBackupData(
firstMessageIndex: sess.inboundGroupSession!.first_known_index(), firstMessageIndex: sess.inboundGroupSession!.firstKnownIndex,
forwardedCount: sess.forwardingCurve25519KeyChain.length, forwardedCount: sess.forwardingCurve25519KeyChain.length,
isVerified: dbSession.verified, //device?.verified ?? false, isVerified: dbSession.verified, //device?.verified ?? false,
sessionData: { sessionData: {
'ephemeral': encrypted.ephemeral, 'ephemeral': ephemeral,
'ciphertext': encrypted.ciphertext, 'ciphertext': ciphertext,
'mac': encrypted.mac, 'mac': mac,
}, },
); );
} }
enc.free();
return roomKeys; return roomKeys;
} catch (e, s) { } catch (e, s) {
Logs().e('[Key Manager] Error generating payload', e, s); Logs().e('[Key Manager] Error generating payload', e, s);
enc.free();
rethrow; rethrow;
} }
} }

View File

@ -21,11 +21,12 @@ import 'dart:convert';
import 'package:async/async.dart'; import 'package:async/async.dart';
import 'package:canonical_json/canonical_json.dart'; import 'package:canonical_json/canonical_json.dart';
import 'package:collection/collection.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/encryption.dart';
import 'package:matrix/encryption/utils/json_signature_check_extension.dart'; import 'package:matrix/encryption/utils/json_signature_check_extension.dart';
import 'package:matrix/encryption/utils/olm_session.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/matrix.dart';
import 'package:matrix/msc_extensions/msc_3814_dehydrated_devices/api.dart'; import 'package:matrix/msc_extensions/msc_3814_dehydrated_devices/api.dart';
import 'package:matrix/src/utils/run_benchmarked.dart'; import 'package:matrix/src/utils/run_benchmarked.dart';
@ -34,20 +35,24 @@ import 'package:matrix/src/utils/run_in_root.dart';
class OlmManager { class OlmManager {
final Encryption encryption; final Encryption encryption;
Client get client => encryption.client; Client get client => encryption.client;
olm.Account? _olmAccount; vod.Account? _olmAccount;
String? ourDeviceId; String? ourDeviceId;
/// Returns the base64 encoded keys to store them in a store. /// Returns the base64 encoded keys to store them in a store.
/// This String should **never** leave the device! /// This String should **never** leave the device!
String? get pickledOlmAccount => String? get pickledOlmAccount {
enabled ? _olmAccount!.pickle(client.userID!) : null; return enabled
? _olmAccount!.toPickleEncrypted(client.userID!.toPickleKey())
: null;
}
String? get fingerprintKey => String? get fingerprintKey =>
enabled ? json.decode(_olmAccount!.identity_keys())['ed25519'] : null; enabled ? _olmAccount!.identityKeys.ed25519.toBase64() : null;
String? get identityKey => String? get identityKey =>
enabled ? json.decode(_olmAccount!.identity_keys())['curve25519'] : null; enabled ? _olmAccount!.identityKeys.curve25519.toBase64() : null;
String? pickleOlmAccountWithKey(String key) => String? pickleOlmAccountWithKey(String key) =>
enabled ? _olmAccount!.pickle(key) : null; enabled ? _olmAccount!.toPickleEncrypted(key.toPickleKey()) : null;
bool get enabled => _olmAccount != null; bool get enabled => _olmAccount != null;
@ -66,10 +71,7 @@ class OlmManager {
}) async { }) async {
ourDeviceId = deviceId; ourDeviceId = deviceId;
if (olmAccount == null) { if (olmAccount == null) {
try { _olmAccount = vod.Account();
await olm.init();
_olmAccount = olm.Account();
_olmAccount!.create();
if (!await uploadKeys( if (!await uploadKeys(
uploadDeviceKeys: true, uploadDeviceKeys: true,
updateDatabase: false, updateDatabase: false,
@ -79,20 +81,21 @@ class OlmManager {
)) { )) {
throw ('Upload key failed'); throw ('Upload key failed');
} }
} catch (_) {
_olmAccount?.free();
_olmAccount = null;
rethrow;
}
} else { } else {
try { try {
await olm.init(); _olmAccount = vod.Account.fromPickleEncrypted(
_olmAccount = olm.Account(); pickle: olmAccount,
_olmAccount!.unpickle(pickleKey ?? client.userID!, olmAccount); pickleKey: (pickleKey ?? client.userID!).toPickleKey(),
} catch (_) { );
_olmAccount?.free(); } catch (e) {
_olmAccount = null; Logs().d(
rethrow; '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)) { if (!payload['signatures'].containsKey(client.userID)) {
payload['signatures'][client.userID] = <String, dynamic>{}; payload['signatures'][client.userID] = <String, dynamic>{};
} }
payload['signatures'][client.userID]['ed25519:$ourDeviceId'] = signature; payload['signatures'][client.userID]['ed25519:$ourDeviceId'] =
signature.toBase64();
if (unsigned != null) { if (unsigned != null) {
payload['unsigned'] = unsigned; payload['unsigned'] = unsigned;
} }
@ -123,13 +127,13 @@ class OlmManager {
} }
String signString(String s) { String signString(String s) {
return _olmAccount!.sign(s); return _olmAccount!.sign(s).toBase64();
} }
bool _uploadKeysLock = false; bool _uploadKeysLock = false;
CancelableOperation<Map<String, int>>? currentUpload; CancelableOperation<Map<String, int>>? 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. /// 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. /// If `retry` is > 0, the request will be retried with new OTKs on upload failure.
@ -158,26 +162,24 @@ class OlmManager {
if (oldKeyCount != null) { if (oldKeyCount != null) {
// check if we have OTKs that still need uploading. If we do, we don't try to generate new ones, // 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 // instead we try to upload the old ones first
final oldOTKsNeedingUpload = json final oldOTKsNeedingUpload = olmAccount.oneTimeKeys.length;
.decode(olmAccount.one_time_keys())['curve25519']
.entries
.length as int;
// generate one-time keys // generate one-time keys
// we generate 2/3rds of max, so that other keys people may still have can // we generate 2/3rds of max, so that other keys people may still have can
// still be used // still be used
final oneTimeKeysCount = final oneTimeKeysCount =
(olmAccount.max_number_of_one_time_keys() * 2 / 3).floor() - (olmAccount.maxNumberOfOneTimeKeys * 2 / 3).floor() -
oldKeyCount - oldKeyCount -
oldOTKsNeedingUpload; oldOTKsNeedingUpload;
if (oneTimeKeysCount > 0) { if (oneTimeKeysCount > 0) {
olmAccount.generate_one_time_keys(oneTimeKeysCount); olmAccount.generateOneTimeKeys(oneTimeKeysCount);
} }
uploadedOneTimeKeysCount = oneTimeKeysCount + oldOTKsNeedingUpload; 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! // 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. // we save the generated OTKs into the database.
@ -199,39 +201,33 @@ class OlmManager {
}; };
if (uploadDeviceKeys) { if (uploadDeviceKeys) {
final Map<String, dynamic> keys = final keys = olmAccount.identityKeys;
json.decode(olmAccount.identity_keys()); deviceKeys['keys']['curve25519:$ourDeviceId'] =
for (final entry in keys.entries) { keys.curve25519.toBase64();
final algorithm = entry.key; deviceKeys['keys']['ed25519:$ourDeviceId'] = keys.ed25519.toBase64();
final value = entry.value;
deviceKeys['keys']['$algorithm:$ourDeviceId'] = value;
}
deviceKeys = signJson(deviceKeys); deviceKeys = signJson(deviceKeys);
} }
// now sign all the one-time keys // now sign all the one-time keys
for (final entry for (final entry in olmAccount.oneTimeKeys.entries) {
in json.decode(olmAccount.one_time_keys())['curve25519'].entries) {
final key = entry.key; final key = entry.key;
final value = entry.value; final value = entry.value.toBase64();
signedOneTimeKeys['signed_curve25519:$key'] = signJson({ signedOneTimeKeys['signed_curve25519:$key'] = signJson({
'key': value, 'key': value,
}); });
} }
final signedFallbackKeys = <String, dynamic>{}; final signedFallbackKeys = <String, dynamic>{};
if (encryption.isMinOlmVersion(3, 2, 7)) { final fallbackKey = olmAccount.fallbackKey;
final fallbackKey = json.decode(olmAccount.unpublished_fallback_key());
// now sign all the fallback keys // now sign all the fallback keys
for (final entry in fallbackKey['curve25519'].entries) { for (final entry in fallbackKey.entries) {
final key = entry.key; final key = entry.key;
final value = entry.value; final value = entry.value.toBase64();
signedFallbackKeys['signed_curve25519:$key'] = signJson({ signedFallbackKeys['signed_curve25519:$key'] = signJson({
'key': value, 'key': value,
'fallback': true, 'fallback': true,
}); });
} }
}
if (signedFallbackKeys.isEmpty && if (signedFallbackKeys.isEmpty &&
signedOneTimeKeys.isEmpty && signedOneTimeKeys.isEmpty &&
@ -281,7 +277,7 @@ class OlmManager {
} }
// mark the OTKs as published and save that to datbase // mark the OTKs as published and save that to datbase
olmAccount.mark_keys_as_published(); olmAccount.markKeysAsPublished();
if (updateDatabase) { if (updateDatabase) {
await encryption.olmDatabase?.updateClientKeys(pickledOlmAccount!); await encryption.olmDatabase?.updateClientKeys(pickledOlmAccount!);
} }
@ -300,18 +296,9 @@ class OlmManager {
exception.error == MatrixError.M_UNKNOWN) { exception.error == MatrixError.M_UNKNOWN) {
Logs().w('Rotating otks because upload failed', exception); Logs().w('Rotating otks because upload failed', exception);
for (final otk in signedOneTimeKeys.values) { 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<String>('key'); final key = otk.tryGet<String>('key');
if (key != null) { if (key != null) {
session.create_outbound(_olmAccount!, identity, key); olmAccount.removeOneTimeKey(key);
olmAccount.remove_one_time_keys(session);
}
} finally {
session.free();
} }
} }
@ -342,7 +329,6 @@ class OlmManager {
await _otkUpdateDedup.fetch( await _otkUpdateDedup.fetch(
() => runBenchmarked('handleOtkUpdate', () async { () => 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 // 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. // 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. // 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 = { final requestingKeysFrom = {
client.userID!: {ourDeviceId!: 'signed_curve25519'}, 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 // 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) { !unusedFallbackKey) {
await uploadKeys( await uploadKeys(
oldKeyCount: oldKeyCount: keyCount < (_olmAccount!.maxNumberOfOneTimeKeys / 2)
keyCount < (_olmAccount!.max_number_of_one_time_keys() / 2)
? keyCount ? keyCount
: null, : null,
unusedFallbackKey: haveFallbackKeys ? unusedFallbackKey : null, unusedFallbackKey: unusedFallbackKey,
); );
} }
}), }),
@ -449,21 +434,12 @@ class OlmManager {
if (session.session == null) { if (session.session == null) {
continue; continue;
} }
if (type == 0 && session.session!.matches_inbound(body)) {
try { try {
plaintext = session.session!.decrypt(type, body); plaintext = session.session!.decrypt(
} catch (e) { messageType: type,
// The message was encrypted during this session, but is unable to decrypt ciphertext: body,
throw DecryptException(
DecryptException.decryptionFailed,
e.toString(),
); );
}
await updateSessionUsage(session);
break;
} else if (type == 1) {
try {
plaintext = session.session!.decrypt(type, body);
await updateSessionUsage(session); await updateSessionUsage(session);
break; break;
} catch (_) { } catch (_) {
@ -471,32 +447,32 @@ class OlmManager {
} }
} }
} }
}
if (plaintext == null && type != 0) { if (plaintext == null && type != 0) {
throw DecryptException(DecryptException.unableToDecryptWithAnyOlmSession); throw DecryptException(DecryptException.unableToDecryptWithAnyOlmSession);
} }
if (plaintext == null) { if (plaintext == null) {
final newSession = olm.Session();
try { try {
newSession.create_inbound_from(_olmAccount!, senderKey, body); final result = _olmAccount!.createInboundSession(
_olmAccount!.remove_one_time_keys(newSession); theirIdentityKey: vod.Curve25519PublicKey.fromBase64(senderKey),
await encryption.olmDatabase?.updateClientKeys(pickledOlmAccount!); preKeyMessageBase64: body,
);
plaintext = result.plaintext;
final newSession = result.session;
plaintext = newSession.decrypt(type, body); await encryption.olmDatabase?.updateClientKeys(pickledOlmAccount!);
await storeOlmSession( await storeOlmSession(
OlmSession( OlmSession(
key: client.userID!, key: client.userID!,
identityKey: senderKey, identityKey: senderKey,
sessionId: newSession.session_id(), sessionId: newSession.sessionId,
session: newSession, session: newSession,
lastReceived: DateTime.now(), lastReceived: DateTime.now(),
), ),
); );
await updateSessionUsage(); await updateSessionUsage();
} catch (e) { } catch (e) {
newSession.free();
throw DecryptException(DecryptException.decryptionFailed, e.toString()); throw DecryptException(DecryptException.decryptionFailed, e.toString());
} }
} }
@ -660,27 +636,30 @@ class OlmManager {
continue; continue;
} }
Logs().v('[OlmManager] Starting session with $userId:$deviceId'); Logs().v('[OlmManager] Starting session with $userId:$deviceId');
final session = olm.Session();
try { try {
session.create_outbound( final session = _olmAccount!.createOutboundSession(
_olmAccount!, identityKey: vod.Curve25519PublicKey.fromBase64(identityKey),
identityKey, oneTimeKey: vod.Curve25519PublicKey.fromBase64(
deviceKey.tryGet<String>('key')!, deviceKey.tryGet<String>('key')!,
),
); );
await storeOlmSession( await storeOlmSession(
OlmSession( OlmSession(
key: client.userID!, key: client.userID!,
identityKey: identityKey, identityKey: identityKey,
sessionId: session.session_id(), sessionId: session.sessionId,
session: session, session: session,
lastReceived: lastReceived:
DateTime.now(), // we want to use a newly created session DateTime.now(), // we want to use a newly created session
), ),
); );
} catch (e, s) { } catch (e, s) {
session.free(); Logs().e(
Logs() '[Vodozemac] Could not create new outbound olm session',
.e('[LibOlm] Could not create new outbound olm session', e, s); e,
s,
);
} }
} }
} }
@ -733,8 +712,8 @@ class OlmManager {
'ciphertext': <String, dynamic>{}, 'ciphertext': <String, dynamic>{},
}; };
encryptedBody['ciphertext'][device.curve25519Key] = { encryptedBody['ciphertext'][device.curve25519Key] = {
'type': encryptResult.type, 'type': encryptResult.messageType,
'body': encryptResult.body, 'body': encryptResult.ciphertext,
}; };
return encryptedBody; return encryptedBody;
} }
@ -769,10 +748,10 @@ class OlmManager {
getFromDb: false, getFromDb: false,
); );
} on NoOlmSessionFoundException catch (e) { } on NoOlmSessionFoundException catch (e) {
Logs().d('[LibOlm] Error encrypting to-device event', e); Logs().d('[Vodozemac] Error encrypting to-device event', e);
continue; continue;
} catch (e, s) { } catch (e, s) {
Logs().wtf('[LibOlm] Error encrypting to-device event', e, s); Logs().wtf('[Vodozemac] Error encrypting to-device event', e, s);
continue; continue;
} }
} }
@ -820,13 +799,6 @@ class OlmManager {
Future<void> dispose() async { Future<void> dispose() async {
await currentUpload?.cancel(); await currentUpload?.cancel();
for (final sessions in olmSessions.values) {
for (final sess in sessions) {
sess.dispose();
}
}
_olmAccount?.free();
_olmAccount = null;
} }
} }

View File

@ -20,12 +20,11 @@ import 'dart:convert';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:canonical_json/canonical_json.dart'; 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/encryption.dart';
import 'package:matrix/encryption/key_manager.dart'; import 'package:matrix/encryption/key_manager.dart';
import 'package:matrix/encryption/ssss.dart'; import 'package:matrix/encryption/ssss.dart';
import 'package:matrix/encryption/utils/base64_unpadded.dart';
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
enum BootstrapState { enum BootstrapState {
@ -371,17 +370,16 @@ class Bootstrap {
} }
final userID = client.userID!; final userID = client.userID!;
try { try {
Uint8List masterSigningKey; String masterSigningKey;
final secretsToStore = <String, String>{}; final secretsToStore = <String, String>{};
MatrixCrossSigningKey? masterKey; MatrixCrossSigningKey? masterKey;
MatrixCrossSigningKey? selfSigningKey; MatrixCrossSigningKey? selfSigningKey;
MatrixCrossSigningKey? userSigningKey; MatrixCrossSigningKey? userSigningKey;
String? masterPub; String? masterPub;
if (setupMasterKey) { if (setupMasterKey) {
final master = olm.PkSigning(); final master = vod.PkSigning();
try { masterSigningKey = master.secretKey;
masterSigningKey = master.generate_seed(); masterPub = master.publicKey.toBase64();
masterPub = master.init_with_seed(masterSigningKey);
final json = <String, dynamic>{ final json = <String, dynamic>{
'user_id': userID, 'user_id': userID,
'usage': ['master'], 'usage': ['master'],
@ -390,43 +388,29 @@ class Bootstrap {
}, },
}; };
masterKey = MatrixCrossSigningKey.fromJson(json); masterKey = MatrixCrossSigningKey.fromJson(json);
secretsToStore[EventTypes.CrossSigningMasterKey] = secretsToStore[EventTypes.CrossSigningMasterKey] = masterSigningKey;
base64.encode(masterSigningKey);
} finally {
master.free();
}
} else { } else {
Logs().v('Get stored key...'); Logs().v('Get stored key...');
masterSigningKey = base64decodeUnpadded( masterSigningKey =
await newSsssKey?.getStored(EventTypes.CrossSigningMasterKey) ?? '', await newSsssKey?.getStored(EventTypes.CrossSigningMasterKey) ?? '';
);
if (masterSigningKey.isEmpty) { if (masterSigningKey.isEmpty) {
// no master signing key :( // no master signing key :(
throw BootstrapBadStateException('No master key'); throw BootstrapBadStateException('No master key');
} }
final master = olm.PkSigning(); final master = vod.PkSigning.fromSecretKey(masterSigningKey);
try { masterPub = master.publicKey.toBase64();
masterPub = master.init_with_seed(masterSigningKey);
} finally {
master.free();
}
} }
String? sign(Map<String, dynamic> object) { String? sign(Map<String, dynamic> object) {
final keyObj = olm.PkSigning(); final keyObj = vod.PkSigning.fromSecretKey(masterSigningKey);
try {
keyObj.init_with_seed(masterSigningKey);
return keyObj return keyObj
.sign(String.fromCharCodes(canonicalJson.encode(object))); .sign(String.fromCharCodes(canonicalJson.encode(object)))
} finally { .toBase64();
keyObj.free();
}
} }
if (setupSelfSigningKey) { if (setupSelfSigningKey) {
final selfSigning = olm.PkSigning(); final selfSigning = vod.PkSigning();
try { final selfSigningPriv = selfSigning.secretKey;
final selfSigningPriv = selfSigning.generate_seed(); final selfSigningPub = selfSigning.publicKey.toBase64();
final selfSigningPub = selfSigning.init_with_seed(selfSigningPriv);
final json = <String, dynamic>{ final json = <String, dynamic>{
'user_id': userID, 'user_id': userID,
'usage': ['self_signing'], 'usage': ['self_signing'],
@ -441,17 +425,12 @@ class Bootstrap {
}, },
}; };
selfSigningKey = MatrixCrossSigningKey.fromJson(json); selfSigningKey = MatrixCrossSigningKey.fromJson(json);
secretsToStore[EventTypes.CrossSigningSelfSigning] = secretsToStore[EventTypes.CrossSigningSelfSigning] = selfSigningPriv;
base64.encode(selfSigningPriv);
} finally {
selfSigning.free();
}
} }
if (setupUserSigningKey) { if (setupUserSigningKey) {
final userSigning = olm.PkSigning(); final userSigning = vod.PkSigning();
try { final userSigningPriv = userSigning.secretKey;
final userSigningPriv = userSigning.generate_seed(); final userSigningPub = userSigning.publicKey.toBase64();
final userSigningPub = userSigning.init_with_seed(userSigningPriv);
final json = <String, dynamic>{ final json = <String, dynamic>{
'user_id': userID, 'user_id': userID,
'usage': ['user_signing'], 'usage': ['user_signing'],
@ -466,11 +445,7 @@ class Bootstrap {
}, },
}; };
userSigningKey = MatrixCrossSigningKey.fromJson(json); userSigningKey = MatrixCrossSigningKey.fromJson(json);
secretsToStore[EventTypes.CrossSigningUserSigning] = secretsToStore[EventTypes.CrossSigningUserSigning] = userSigningPriv;
base64.encode(userSigningPriv);
} finally {
userSigning.free();
}
} }
// upload the keys! // upload the keys!
state = BootstrapState.loading; state = BootstrapState.loading;
@ -561,15 +536,13 @@ class Bootstrap {
return; return;
} }
try { try {
final keyObj = olm.PkDecryption(); final keyObj = vod.PkDecryption();
String pubKey; String pubKey;
Uint8List privKey; Uint8List privKey;
try {
pubKey = keyObj.generate_key(); pubKey = keyObj.publicKey;
privKey = keyObj.get_private_key(); privKey = keyObj.privateKey;
} finally {
keyObj.free();
}
Logs().v('Create the new backup version...'); Logs().v('Create the new backup version...');
await client.postRoomKeysVersion( await client.postRoomKeysVersion(
BackupAlgorithm.mMegolmBackupV1Curve25519AesSha2, BackupAlgorithm.mMegolmBackupV1Curve25519AesSha2,

View File

@ -17,7 +17,7 @@
*/ */
import 'package:canonical_json/canonical_json.dart'; 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'; import 'package:matrix/matrix.dart';
@ -37,15 +37,15 @@ extension JsonSignatureCheckExtension on Map<String, dynamic> {
final canonical = canonicalJson.encode(this); final canonical = canonicalJson.encode(this);
final message = String.fromCharCodes(canonical); final message = String.fromCharCodes(canonical);
var isValid = false; var isValid = false;
final olmutil = olm.Utility();
try { try {
olmutil.ed25519_verify(key, message, signature); vod.Ed25519PublicKey.fromBase64(key).verify(
message: message,
signature: vod.Ed25519Signature.fromBase64(signature),
);
isValid = true; isValid = true;
} catch (e, s) { } catch (e, s) {
isValid = false; isValid = false;
Logs().w('[LibOlm] Signature check failed', e, s); Logs().w('[Vodozemac] Signature check failed', e, s);
} finally {
olmutil.free();
} }
return isValid; return isValid;
} }

View File

@ -21,8 +21,9 @@ import 'dart:convert';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:canonical_json/canonical_json.dart'; 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:typed_data/typed_data.dart';
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/encryption/encryption.dart'; import 'package:matrix/encryption/encryption.dart';
import 'package:matrix/encryption/utils/base64_unpadded.dart'; import 'package:matrix/encryption/utils/base64_unpadded.dart';
@ -1258,12 +1259,8 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod {
String? commitment; String? commitment;
late String theirPublicKey; late String theirPublicKey;
Map<String, dynamic>? macPayload; Map<String, dynamic>? macPayload;
olm.SAS? sas; vod.Sas? sas;
vod.EstablishedSas? establishedSas;
@override
void dispose() {
sas?.free();
}
List<String> get knownAuthentificationTypes { List<String> get knownAuthentificationTypes {
final types = <String>[]; final types = <String>[];
@ -1322,7 +1319,7 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod {
await _sendKey(); await _sendKey();
} else { } else {
// we already sent our key, time to verify the commitment being valid // we already sent our key, time to verify the commitment being valid
if (!_validateCommitment()) { if (await _validateCommitment() == false) {
await request.cancel('m.mismatched_commitment'); await request.cancel('m.mismatched_commitment');
return; return;
} }
@ -1415,8 +1412,8 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod {
} }
Future<void> _sendAccept() async { Future<void> _sendAccept() async {
final sas = this.sas = olm.SAS(); final sas = this.sas = vod.Sas();
commitment = _makeCommitment(sas.get_pubkey(), startCanonicalJson); commitment = await _makeCommitment(sas.publicKey, startCanonicalJson);
await request.send(EventTypes.KeyVerificationAccept, { await request.send(EventTypes.KeyVerificationAccept, {
'method': type, 'method': type,
'key_agreement_protocol': keyAgreementProtocol, 'key_agreement_protocol': keyAgreementProtocol,
@ -1451,31 +1448,35 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod {
} }
authenticationTypes = possibleAuthenticationTypes; authenticationTypes = possibleAuthenticationTypes;
commitment = payload['commitment']; commitment = payload['commitment'];
sas = olm.SAS(); sas = vod.Sas();
return true; return true;
} }
Future<void> _sendKey() async { Future<void> _sendKey() async {
await request.send('m.key.verification.key', { await request.send('m.key.verification.key', {
'key': sas!.get_pubkey(), 'key': sas!.publicKey,
}); });
} }
void _handleKey(Map<String, dynamic> payload) { void _handleKey(Map<String, dynamic> payload) {
theirPublicKey = payload['key']; 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() { Future<bool> _validateCommitment() async {
final checkCommitment = _makeCommitment(theirPublicKey, startCanonicalJson); final checkCommitment =
await _makeCommitment(theirPublicKey, startCanonicalJson);
return commitment == checkCommitment; return commitment == checkCommitment;
} }
Uint8List makeSas(int bytes) { Uint8List makeSas(int bytes) {
var sasInfo = ''; var sasInfo = '';
if (keyAgreementProtocol == 'curve25519-hkdf-sha256') { if (keyAgreementProtocol == 'curve25519-hkdf-sha256') {
final ourInfo = final ourInfo = '${client.userID}|${client.deviceID}|${sas!.publicKey}|';
'${client.userID}|${client.deviceID}|${sas!.get_pubkey()}|';
final theirInfo = final theirInfo =
'${request.userId}|${request.deviceId}|$theirPublicKey|'; '${request.userId}|${request.deviceId}|$theirPublicKey|';
sasInfo = sasInfo =
@ -1488,7 +1489,7 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod {
} else { } else {
throw Exception('Unknown key agreement protocol'); throw Exception('Unknown key agreement protocol');
} }
return sas!.generate_bytes(sasInfo, bytes); return establishedSas!.generateBytes(sasInfo, bytes);
} }
Future<void> _sendMac() async { Future<void> _sendMac() async {
@ -1554,21 +1555,20 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod {
}); });
} }
String _makeCommitment(String pubKey, String canonicalJson) { Future<String> _makeCommitment(String pubKey, String canonicalJson) async {
if (hash == 'sha256') { if (hash == 'sha256') {
final olmutil = olm.Utility(); final bytes = utf8.encoder.convert(pubKey + canonicalJson);
final ret = olmutil.sha256(pubKey + canonicalJson); final digest = crypto.sha256.convert(bytes);
olmutil.free(); return encodeBase64Unpadded(digest.bytes);
return ret;
} }
throw Exception('Unknown hash method'); throw Exception('Unknown hash method');
} }
String _calculateMac(String input, String info) { String _calculateMac(String input, String info) {
if (messageAuthenticationCode == 'hkdf-hmac-sha256.v2') { 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') { } else if (messageAuthenticationCode == 'hkdf-hmac-sha256') {
return sas!.calculate_mac(input, info); return establishedSas!.calculateMacDeprecated(input, info);
} else { } else {
throw Exception('Unknown message authentification code'); throw Exception('Unknown message authentification code');
} }

View File

@ -16,17 +16,20 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
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'; import 'package:matrix/matrix.dart';
class OlmSession { class OlmSession {
String identityKey; String identityKey;
String? sessionId; String? sessionId;
olm.Session? session; vod.Session? session;
DateTime? lastReceived; DateTime? lastReceived;
final String key; final String key;
String? get pickledSession => session?.pickle(key); String? get pickledSession => session?.toPickleEncrypted(key.toPickleKey());
bool get isValid => session != null; bool get isValid => session != null;
@ -40,21 +43,25 @@ class OlmSession {
OlmSession.fromJson(Map<String, dynamic> dbEntry, this.key) OlmSession.fromJson(Map<String, dynamic> dbEntry, this.key)
: identityKey = dbEntry['identity_key'] ?? '' { : identityKey = dbEntry['identity_key'] ?? '' {
session = olm.Session();
try { 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']; sessionId = dbEntry['session_id'];
lastReceived = lastReceived =
DateTime.fromMillisecondsSinceEpoch(dbEntry['last_received'] ?? 0); DateTime.fromMillisecondsSinceEpoch(dbEntry['last_received'] ?? 0);
assert(sessionId == session!.session_id()); assert(sessionId == session!.sessionId);
} catch (e, s) { } catch (e, s) {
Logs().e('[LibOlm] Could not unpickle olm session', e, s); Logs().e('[Vodozemac] Could not unpickle olm session', e, s);
dispose();
} }
} }
void dispose() {
session?.free();
session = null;
}
} }

View File

@ -18,8 +18,9 @@
import 'dart:convert'; 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'; import 'package:matrix/matrix.dart';
class OutboundGroupSession { class OutboundGroupSession {
@ -30,8 +31,8 @@ class OutboundGroupSession {
Map<String, Map<String, bool>> devices = {}; Map<String, Map<String, bool>> devices = {};
// Default to a date, that would get this session rotated in any case to make handling easier // Default to a date, that would get this session rotated in any case to make handling easier
DateTime creationTime = DateTime.fromMillisecondsSinceEpoch(0); DateTime creationTime = DateTime.fromMillisecondsSinceEpoch(0);
olm.OutboundGroupSession? outboundGroupSession; vod.GroupSession? outboundGroupSession;
int? get sentMessages => outboundGroupSession?.message_index(); int? get sentMessages => outboundGroupSession?.messageIndex;
bool get isValid => outboundGroupSession != null; bool get isValid => outboundGroupSession != null;
final String key; final String key;
@ -54,19 +55,24 @@ class OutboundGroupSession {
); );
return; return;
} }
outboundGroupSession = olm.OutboundGroupSession();
try {
outboundGroupSession!.unpickle(key, dbEntry['pickle']);
creationTime = creationTime =
DateTime.fromMillisecondsSinceEpoch(dbEntry['creation_time']); DateTime.fromMillisecondsSinceEpoch(dbEntry['creation_time']);
} catch (e, s) {
dispose();
Logs().e('[LibOlm] Unable to unpickle outboundGroupSession', e, s);
}
}
void dispose() { try {
outboundGroupSession?.free(); outboundGroupSession = vod.GroupSession.fromPickleEncrypted(
outboundGroupSession = null; pickleKey: key.toPickleKey(),
pickle: dbEntry['pickle'],
);
} catch (e, s) {
try {
outboundGroupSession = vod.GroupSession.fromOlmPickleEncrypted(
pickleKey: utf8.encode(key),
pickle: dbEntry['pickle'],
);
} catch (_) {
Logs().e('[Vodozemac] Unable to unpickle outboundGroupSession', e, s);
}
}
} }
} }

View File

@ -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());
}
}

View File

@ -16,8 +16,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
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/encryption/utils/stored_inbound_group_session.dart';
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
@ -33,7 +36,7 @@ class SessionKey {
Map<String, Map<String, int>> allowedAtIndex; Map<String, Map<String, int>> allowedAtIndex;
/// Underlying olm [InboundGroupSession] object /// Underlying olm [InboundGroupSession] object
olm.InboundGroupSession? inboundGroupSession; vod.InboundGroupSession? inboundGroupSession;
/// Key for libolm pickle / unpickle /// Key for libolm pickle / unpickle
final String key; final String key;
@ -81,8 +84,7 @@ class SessionKey {
.catchMap((k, v) => MapEntry(k, Map<String, int>.from(v))), .catchMap((k, v) => MapEntry(k, Map<String, int>.from(v))),
roomId = dbEntry.roomId, roomId = dbEntry.roomId,
sessionId = dbEntry.sessionId, sessionId = dbEntry.sessionId,
senderKey = dbEntry.senderKey, senderKey = dbEntry.senderKey {
inboundGroupSession = olm.InboundGroupSession() {
final parsedSenderClaimedKeys = final parsedSenderClaimedKeys =
Event.getMapFromPayload(dbEntry.senderClaimedKeys) Event.getMapFromPayload(dbEntry.senderClaimedKeys)
.catchMap((k, v) => MapEntry<String, String>(k, v)); .catchMap((k, v) => MapEntry<String, String>(k, v));
@ -99,15 +101,21 @@ class SessionKey {
: <String, String>{})); : <String, String>{}));
try { try {
inboundGroupSession!.unpickle(key, dbEntry.pickle); inboundGroupSession = vod.InboundGroupSession.fromPickleEncrypted(
pickle: dbEntry.pickle,
pickleKey: key.toPickleKey(),
);
} catch (e, s) { } catch (e, s) {
dispose(); try {
Logs().e('[LibOlm] Unable to unpickle inboundGroupSession', e, s); 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;
} }
} }

View File

@ -26,8 +26,8 @@ import 'package:async/async.dart';
import 'package:collection/collection.dart' show IterableExtension; import 'package:collection/collection.dart' show IterableExtension;
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:mime/mime.dart'; import 'package:mime/mime.dart';
import 'package:olm/olm.dart' as olm;
import 'package:random_string/random_string.dart'; import 'package:random_string/random_string.dart';
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/encryption.dart'; import 'package:matrix/encryption.dart';
import 'package:matrix/matrix.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/models/timeline_chunk.dart';
import 'package:matrix/src/utils/cached_stream_controller.dart'; import 'package:matrix/src/utils/cached_stream_controller.dart';
import 'package:matrix/src/utils/client_init_exception.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/multilock.dart';
import 'package:matrix/src/utils/run_benchmarked.dart'; import 'package:matrix/src/utils/run_benchmarked.dart';
import 'package:matrix/src/utils/run_in_root.dart'; import 'package:matrix/src/utils/run_in_root.dart';
@ -107,20 +106,6 @@ class Client extends MatrixApi {
final bool convertLinebreaksInFormatting; final bool convertLinebreaksInFormatting;
final ComputeCallback? compute;
@Deprecated('Use [nativeImplementations] instead')
Future<T> runInBackground<T, U>(
FutureOr<T> 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; final Duration sendTimelineEventTimeout;
/// The timeout until a typing indicator gets removed automatically. /// The timeout until a typing indicator gets removed automatically.
@ -209,8 +194,7 @@ class Client extends MatrixApi {
Set<String>? supportedLoginTypes, Set<String>? supportedLoginTypes,
this.mxidLocalPartFallback = true, this.mxidLocalPartFallback = true,
this.formatLocalpart = true, this.formatLocalpart = true,
@Deprecated('Use [nativeImplementations] instead') this.compute, this.nativeImplementations = NativeImplementations.dummy,
NativeImplementations nativeImplementations = NativeImplementations.dummy,
Level? logLevel, Level? logLevel,
Filter? syncFilter, Filter? syncFilter,
Duration defaultNetworkRequestTimeout = const Duration(seconds: 35), Duration defaultNetworkRequestTimeout = const Duration(seconds: 35),
@ -248,9 +232,6 @@ class Client extends MatrixApi {
supportedLoginTypes = supportedLoginTypes =
supportedLoginTypes ?? {AuthenticationTypes.password}, supportedLoginTypes ?? {AuthenticationTypes.password},
verificationMethods = verificationMethods ?? <KeyVerificationMethod>{}, verificationMethods = verificationMethods ?? <KeyVerificationMethod>{},
nativeImplementations = compute != null
? NativeImplementationsIsolate(compute)
: nativeImplementations,
super( super(
httpClient: FixedTimeoutHttpClient( httpClient: FixedTimeoutHttpClient(
httpClient ?? http.Client(), httpClient ?? http.Client(),
@ -2101,16 +2082,15 @@ class Client extends MatrixApi {
} }
await encryption?.dispose(); await encryption?.dispose();
if (vod.isInitialized()) {
try { try {
// make sure to throw an exception if libolm doesn't exist
await olm.init();
olm.get_library_version();
_encryption = Encryption(client: this); _encryption = Encryption(client: this);
} catch (e) { } catch (e) {
Logs().e('Error initializing encryption $e'); Logs().e('Error initializing encryption $e');
await encryption?.dispose(); await encryption?.dispose();
_encryption = null; _encryption = null;
} }
}
onInitStateChanged?.call(InitState.settingUpEncryption); onInitStateChanged?.call(InitState.settingUpEncryption);
await encryption?.init(olmAccount); await encryption?.init(olmAccount);
@ -3431,7 +3411,7 @@ class Client extends MatrixApi {
}); });
} }
} catch (e, s) { } 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);
} }
} }

View File

@ -11,12 +11,3 @@ typedef ComputeRunner = Future<T> Function<T, U>(
FutureOr<T> Function(U arg) function, FutureOr<T> Function(U arg) function,
U arg, U arg,
); );
ComputeCallback computeCallbackFromRunInBackground(ComputeRunner runner) {
return <U, T>(
FutureOr<T> Function(U arg) callback,
U arg, {
String? debugLabel,
}) =>
runner.call(callback, arg);
}

View File

@ -20,7 +20,7 @@ import 'dart:convert';
import 'package:canonical_json/canonical_json.dart'; import 'package:canonical_json/canonical_json.dart';
import 'package:collection/collection.dart' show IterableExtension; 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/encryption.dart';
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
@ -240,24 +240,17 @@ abstract class SignableKey extends MatrixSignableKey {
String signature, { String signature, {
bool isSignatureWithoutLibolmValid = false, 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; var valid = false;
try { try {
olmutil.ed25519_verify(pubKey, signingContent, signature); vod.Ed25519PublicKey.fromBase64(pubKey).verify(
message: signingContent,
signature: vod.Ed25519Signature.fromBase64(signature),
);
valid = true; valid = true;
} catch (_) { } catch (e) {
Logs().d('Invalid Ed25519 signature', e);
// bad signature // bad signature
valid = false; valid = false;
} finally {
olmutil.free();
} }
return valid; return valid;
} }

View File

@ -27,7 +27,6 @@ import 'package:image/image.dart';
import 'package:mime/mime.dart'; import 'package:mime/mime.dart';
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
import 'package:matrix/src/utils/compute_callback.dart';
class MatrixFile { class MatrixFile {
final Uint8List bytes; final Uint8List bytes;
@ -112,13 +111,8 @@ class MatrixImageFile extends MatrixFile {
required Uint8List bytes, required Uint8List bytes,
required String name, required String name,
String? mimeType, String? mimeType,
@Deprecated('Use [nativeImplementations] instead') ComputeRunner? compute,
NativeImplementations nativeImplementations = NativeImplementations.dummy, NativeImplementations nativeImplementations = NativeImplementations.dummy,
}) async { }) async {
if (compute != null) {
nativeImplementations =
NativeImplementationsIsolate.fromRunInBackground(compute);
}
final metaData = await nativeImplementations.calcImageMetadata(bytes); final metaData = await nativeImplementations.calcImageMetadata(bytes);
return MatrixImageFile( return MatrixImageFile(
@ -142,13 +136,8 @@ class MatrixImageFile extends MatrixFile {
Future<MatrixImageFileResizedResponse?> Function( Future<MatrixImageFileResizedResponse?> Function(
MatrixImageFileResizeArguments, MatrixImageFileResizeArguments,
)? customImageResizer, )? customImageResizer,
@Deprecated('Use [nativeImplementations] instead') ComputeRunner? compute,
NativeImplementations nativeImplementations = NativeImplementations.dummy, NativeImplementations nativeImplementations = NativeImplementations.dummy,
}) async { }) async {
if (compute != null) {
nativeImplementations =
NativeImplementationsIsolate.fromRunInBackground(compute);
}
final image = MatrixImageFile(name: name, mimeType: mimeType, bytes: bytes); final image = MatrixImageFile(name: name, mimeType: mimeType, bytes: bytes);
return await image.generateThumbnail( return await image.generateThumbnail(
@ -196,13 +185,8 @@ class MatrixImageFile extends MatrixFile {
Future<MatrixImageFileResizedResponse?> Function( Future<MatrixImageFileResizedResponse?> Function(
MatrixImageFileResizeArguments, MatrixImageFileResizeArguments,
)? customImageResizer, )? customImageResizer,
@Deprecated('Use [nativeImplementations] instead') ComputeRunner? compute,
NativeImplementations nativeImplementations = NativeImplementations.dummy, NativeImplementations nativeImplementations = NativeImplementations.dummy,
}) async { }) async {
if (compute != null) {
nativeImplementations =
NativeImplementationsIsolate.fromRunInBackground(compute);
}
final arguments = MatrixImageFileResizeArguments( final arguments = MatrixImageFileResizeArguments(
bytes: bytes, bytes: bytes,
maxDimension: dimension, maxDimension: dimension,

View File

@ -133,19 +133,13 @@ class NativeImplementationsDummy extends NativeImplementations {
class NativeImplementationsIsolate extends NativeImplementations { class NativeImplementationsIsolate extends NativeImplementations {
/// pass by Flutter's compute function here /// pass by Flutter's compute function here
final ComputeCallback compute; final ComputeCallback compute;
final Future<void> Function()? vodozemacInit;
NativeImplementationsIsolate(this.compute); NativeImplementationsIsolate(
this.compute, {
/// creates a [NativeImplementationsIsolate] based on a [ComputeRunner] as /// To generate upload keys, vodozemac needs to be initialized in the isolate.
// ignore: deprecated_member_use_from_same_package this.vodozemacInit,
/// known from [Client.runInBackground] });
factory NativeImplementationsIsolate.fromRunInBackground(
ComputeRunner runInBackground,
) {
return NativeImplementationsIsolate(
computeCallbackFromRunInBackground(runInBackground),
);
}
Future<T> runInBackground<T, U>( Future<T> runInBackground<T, U>(
FutureOr<T> Function(U arg) function, FutureOr<T> Function(U arg) function,
@ -172,7 +166,10 @@ class NativeImplementationsIsolate extends NativeImplementations {
bool retryInDummy = true, bool retryInDummy = true,
}) async { }) async {
return runInBackground<RoomKeys, GenerateUploadKeysArgs>( return runInBackground<RoomKeys, GenerateUploadKeysArgs>(
NativeImplementations.dummy.generateUploadKeys, (GenerateUploadKeysArgs args) async {
await vodozemacInit?.call();
return NativeImplementations.dummy.generateUploadKeys(args);
},
args, args,
); );
} }

View File

@ -24,13 +24,13 @@ dependencies:
js: ^0.6.3 js: ^0.6.3
markdown: ^7.1.1 markdown: ^7.1.1
mime: ">=1.0.0 <3.0.0" mime: ">=1.0.0 <3.0.0"
olm: ^3.1.0
random_string: ^2.3.1 random_string: ^2.3.1
sdp_transform: ^0.3.2 sdp_transform: ^0.3.2
slugify: ^2.0.0 slugify: ^2.0.0
sqflite_common: ^2.4.5 sqflite_common: ^2.4.5
sqlite3: ^2.1.0 sqlite3: ^2.1.0
typed_data: ^1.3.2 typed_data: ^1.3.2
vodozemac: ^0.2.0
webrtc_interface: ^1.2.0 webrtc_interface: ^1.2.0
dev_dependencies: dev_dependencies:

View File

@ -1,35 +1,5 @@
#!/usr/bin/env bash #!/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 if which flutter >/dev/null; then
flutter pub get flutter pub get
else else

9
scripts/prepare_vodozemac.sh Executable file
View File

@ -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 ..

View File

@ -23,9 +23,9 @@ import 'dart:typed_data';
import 'package:canonical_json/canonical_json.dart'; import 'package:canonical_json/canonical_json.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:olm/olm.dart' as olm;
import 'package:path/path.dart' show join; import 'package:path/path.dart' show join;
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
import 'package:matrix/src/utils/client_init_exception.dart'; import 'package:matrix/src/utils/client_init_exception.dart';
@ -35,7 +35,7 @@ import 'fake_database.dart';
void main() { void main() {
// key @test:fakeServer.notExisting // key @test:fakeServer.notExisting
const pickledOlmAccount = 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 identityKey = '7rvl3jORJkBiK4XX1e5TnGnqz068XfYJ0W++Ml63rgk';
const fingerprintKey = 'gjL//fyaFHADt9KBADGag8g7F8Up78B/K1zXeiEPLJo'; const fingerprintKey = 'gjL//fyaFHADt9KBADGag8g7F8Up78B/K1zXeiEPLJo';
@ -67,10 +67,7 @@ void main() {
group('client mem', tags: 'olm', () { group('client mem', tags: 'olm', () {
late Client matrix; late Client matrix;
Logs().level = Level.error;
/// Check if all Elements get created /// Check if all Elements get created
setUp(() async { setUp(() async {
matrix = await getClient(); matrix = await getClient();
}); });
@ -1072,9 +1069,8 @@ void main() {
final deviceKeys = <DeviceKeys>[]; final deviceKeys = <DeviceKeys>[];
for (var i = 0; i < 30; i++) { for (var i = 0; i < 30; i++) {
final account = olm.Account(); final account = vod.Account();
account.create(); final keys = account.identityKeys;
final keys = json.decode(account.identity_keys());
final userId = '@testuser:example.org'; final userId = '@testuser:example.org';
final deviceId = 'DEVICE$i'; final deviceId = 'DEVICE$i';
final keyObj = { final keyObj = {
@ -1085,18 +1081,17 @@ void main() {
'm.megolm.v1.aes-sha2', 'm.megolm.v1.aes-sha2',
], ],
'keys': { 'keys': {
'curve25519:$deviceId': keys['curve25519'], 'curve25519:$deviceId': keys.curve25519.toBase64(),
'ed25519:$deviceId': keys['ed25519'], 'ed25519:$deviceId': keys.ed25519.toBase64(),
}, },
}; };
final signature = final signature =
account.sign(String.fromCharCodes(canonicalJson.encode(keyObj))); account.sign(String.fromCharCodes(canonicalJson.encode(keyObj)));
keyObj['signatures'] = { keyObj['signatures'] = {
userId: { userId: {
'ed25519:$deviceId': signature, 'ed25519:$deviceId': signature.toBase64(),
}, },
}; };
account.free();
deviceKeys.add(DeviceKeys.fromJson(keyObj, matrix)); deviceKeys.add(DeviceKeys.fromJson(keyObj, matrix));
} }
FakeMatrixApi.calledEndpoints.clear(); FakeMatrixApi.calledEndpoints.clear();
@ -1754,7 +1749,10 @@ void main() {
expect(error.homeserver, Uri.parse('https://test.server')); expect(error.homeserver, Uri.parse('https://test.server'));
expect(error.olmAccount, 'abcd'); expect(error.olmAccount, 'abcd');
expect(error.userId, '@user:server'); expect(error.userId, '@user:server');
expect(error.toString(), 'Exception: BAD_ACCOUNT_KEY'); expect(
error.originalException.runtimeType.toString(),
'AnyhowException',
);
} }
await customClient.dispose(closeDatabase: true); await customClient.dispose(closeDatabase: true);
}, },

View File

@ -20,6 +20,7 @@ import 'dart:convert';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
import 'fake_client.dart'; import 'fake_client.dart';
@ -47,6 +48,10 @@ void main() {
} }
test('setupClient', () async { test('setupClient', () async {
await vod.init(
wasmPath: './pkg/',
libraryPath: './rust/target/debug/',
);
client = await getClient(); client = await getClient();
room = Room(id: '!1234:fakeServer.notExisting', client: client); room = Room(id: '!1234:fakeServer.notExisting', client: client);
room.setState( room.setState(

View File

@ -19,18 +19,26 @@
import 'dart:convert'; import 'dart:convert';
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
import './fake_client.dart'; import './fake_client.dart';
void main() { void main() async {
/// All Tests related to device keys /// All Tests related to device keys
group('Device keys', tags: 'olm', () { group('Device keys', tags: 'olm', () {
Logs().level = Level.error; Logs().level = Level.error;
late Client client; late Client client;
Future? vodInit;
test('setupClient', () async { test('setupClient', () async {
vodInit ??= vod.init(
wasmPath: './pkg/',
libraryPath: './rust/target/debug/',
);
await vodInit;
client = await getClient(); client = await getClient();
await client.abortSync(); await client.abortSync();
}); });

View File

@ -19,8 +19,8 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'package:olm/olm.dart' as olm;
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/encryption.dart'; import 'package:matrix/encryption.dart';
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
@ -35,8 +35,11 @@ void main() {
late String origKeyId; late String origKeyId;
setUpAll(() async { setUpAll(() async {
await olm.init(); await vod.init(
olm.get_library_version(); wasmPath: './pkg/',
libraryPath: './rust/target/debug/',
);
client = await getClient(); client = await getClient();
}); });
@ -75,20 +78,16 @@ void main() {
// test all the x-signing keys match up // test all the x-signing keys match up
for (final keyType in {'master', 'user_signing', 'self_signing'}) { for (final keyType in {'master', 'user_signing', 'self_signing'}) {
final privateKey = base64 final privateKey =
.decode(await defaultKey.getStored('m.cross_signing.$keyType')); await defaultKey.getStored('m.cross_signing.$keyType');
final keyObj = olm.PkSigning(); final keyObj = vod.PkSigning.fromSecretKey(privateKey);
try { final pubKey = keyObj.publicKey.toBase64();
final pubKey = keyObj.init_with_seed(privateKey);
expect( expect(
pubKey, pubKey,
client.userDeviceKeys[client.userID] client.userDeviceKeys[client.userID]
?.getCrossSigningKey(keyType) ?.getCrossSigningKey(keyType)
?.publicKey, ?.publicKey,
); );
} finally {
keyObj.free();
}
} }
await defaultKey.store('foxes', 'floof'); await defaultKey.store('foxes', 'floof');
@ -133,20 +132,16 @@ void main() {
// test all the x-signing keys match up // test all the x-signing keys match up
for (final keyType in {'master', 'user_signing', 'self_signing'}) { for (final keyType in {'master', 'user_signing', 'self_signing'}) {
final privateKey = base64 final privateKey =
.decode(await defaultKey.getStored('m.cross_signing.$keyType')); await defaultKey.getStored('m.cross_signing.$keyType');
final keyObj = olm.PkSigning(); final keyObj = vod.PkSigning.fromSecretKey(privateKey);
try { final pubKey = keyObj.publicKey.toBase64();
final pubKey = keyObj.init_with_seed(privateKey);
expect( expect(
pubKey, pubKey,
client.userDeviceKeys[client.userID] client.userDeviceKeys[client.userID]
?.getCrossSigningKey(keyType) ?.getCrossSigningKey(keyType)
?.publicKey, ?.publicKey,
); );
} finally {
keyObj.free();
}
} }
expect(await defaultKey.getStored('foxes'), 'floof'); expect(await defaultKey.getStored('foxes'), 'floof');
@ -192,20 +187,16 @@ void main() {
// test all the x-signing keys match up // test all the x-signing keys match up
for (final keyType in {'master', 'user_signing', 'self_signing'}) { for (final keyType in {'master', 'user_signing', 'self_signing'}) {
final privateKey = base64 final privateKey =
.decode(await defaultKey.getStored('m.cross_signing.$keyType')); await defaultKey.getStored('m.cross_signing.$keyType');
final keyObj = olm.PkSigning(); final keyObj = vod.PkSigning.fromSecretKey(privateKey);
try { final pubKey = keyObj.publicKey.toBase64();
final pubKey = keyObj.init_with_seed(privateKey);
expect( expect(
pubKey, pubKey,
client.userDeviceKeys[client.userID] client.userDeviceKeys[client.userID]
?.getCrossSigningKey(keyType) ?.getCrossSigningKey(keyType)
?.publicKey, ?.publicKey,
); );
} finally {
keyObj.free();
}
} }
expect(await defaultKey.getStored('foxes'), 'floof'); expect(await defaultKey.getStored('foxes'), 'floof');

View File

@ -18,8 +18,8 @@
import 'dart:convert'; import 'dart:convert';
import 'package:olm/olm.dart' as olm;
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
import '../fake_client.dart'; import '../fake_client.dart';
@ -31,8 +31,11 @@ void main() {
late Client client; late Client client;
setUpAll(() async { setUpAll(() async {
await olm.init(); await vod.init(
olm.get_library_version(); wasmPath: './pkg/',
libraryPath: './rust/target/debug/',
);
client = await getClient(); client = await getClient();
await client.abortSync(); await client.abortSync();
}); });

View File

@ -16,8 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import 'package:olm/olm.dart' as olm;
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
import '../fake_client.dart'; import '../fake_client.dart';
@ -33,8 +33,11 @@ void main() {
final now = DateTime.now(); final now = DateTime.now();
setUpAll(() async { setUpAll(() async {
await olm.init(); await vod.init(
olm.get_library_version(); wasmPath: './pkg/',
libraryPath: './rust/target/debug/',
);
client = await getClient(); client = await getClient();
room = client.getRoomById(roomId)!; room = client.getRoomById(roomId)!;
}); });

View File

@ -16,18 +16,16 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import 'package:olm/olm.dart' as olm;
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
import '../fake_client.dart'; import '../fake_client.dart';
import '../fake_database.dart'; import '../fake_database.dart';
void main() async { void main() async {
// key @othertest:fakeServer.notExisting
const otherPickledOlmAccount =
'VWhVApbkcilKAEGppsPDf9nNVjaK8/IxT3asSR0sYg0S5KgbfE8vXEPwoiKBX2cEvwX3OessOBOkk+ZE7TTbjlrh/KEd31p8Wo+47qj0AP+Ky+pabnhi+/rTBvZy+gfzTqUfCxZrkzfXI9Op4JnP6gYmy7dVX2lMYIIs9WCO1jcmIXiXum5jnfXu1WLfc7PZtO2hH+k9CDKosOFaXRBmsu8k/BGXPSoWqUpvu6WpEG9t5STk4FeAzA';
final database = await getDatabase(); final database = await getDatabase();
group('Encrypt/Decrypt to-device messages', tags: 'olm', () { group('Encrypt/Decrypt to-device messages', tags: 'olm', () {
Logs().level = Level.error; Logs().level = Level.error;
@ -41,8 +39,11 @@ void main() async {
late Map<String, dynamic> payload; late Map<String, dynamic> payload;
setUpAll(() async { setUpAll(() async {
await olm.init(); await vod.init(
olm.get_library_version(); wasmPath: './pkg/',
libraryPath: './rust/target/debug/',
);
client = await getClient(); client = await getClient();
}); });
@ -59,7 +60,6 @@ void main() async {
newHomeserver: otherClient.homeserver, newHomeserver: otherClient.homeserver,
newDeviceName: 'Text Matrix Client', newDeviceName: 'Text Matrix Client',
newDeviceID: 'FOXDEVICE', newDeviceID: 'FOXDEVICE',
newOlmAccount: otherPickledOlmAccount,
); );
await otherClient.abortSync(); await otherClient.abortSync();

View File

@ -18,8 +18,8 @@
import 'dart:convert'; import 'dart:convert';
import 'package:olm/olm.dart' as olm;
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
import '../fake_client.dart'; import '../fake_client.dart';
@ -30,8 +30,11 @@ void main() {
late Client client; late Client client;
setUpAll(() async { setUpAll(() async {
await olm.init(); await vod.init(
olm.get_library_version(); wasmPath: './pkg/',
libraryPath: './rust/target/debug/',
);
client = await getClient(); client = await getClient();
}); });
@ -110,7 +113,7 @@ void main() {
); );
var inbound = client.encryption!.keyManager.getInboundGroupSession( var inbound = client.encryption!.keyManager.getInboundGroupSession(
roomId, roomId,
sess.outboundGroupSession!.session_id(), sess.outboundGroupSession!.sessionId,
); );
expect(inbound != null, true); expect(inbound != null, true);
expect( expect(
@ -152,7 +155,7 @@ void main() {
// lazy-create if it would rotate // lazy-create if it would rotate
sess = await client.encryption!.keyManager sess = await client.encryption!.keyManager
.createOutboundGroupSession(roomId); .createOutboundGroupSession(roomId);
final oldSessKey = sess.outboundGroupSession!.session_key(); final oldSessKey = sess.outboundGroupSession!.sessionKey;
client.userDeviceKeys['@alice:example.com']!.deviceKeys['JLAFKJWSCS']! client.userDeviceKeys['@alice:example.com']!.deviceKeys['JLAFKJWSCS']!
.blocked = true; .blocked = true;
await client.encryption!.keyManager.prepareOutboundGroupSession(roomId); await client.encryption!.keyManager.prepareOutboundGroupSession(roomId);
@ -164,7 +167,7 @@ void main() {
client.encryption!.keyManager client.encryption!.keyManager
.getOutboundGroupSession(roomId)! .getOutboundGroupSession(roomId)!
.outboundGroupSession! .outboundGroupSession!
.session_key() != .sessionKey !=
oldSessKey, oldSessKey,
true, true,
); );
@ -236,7 +239,7 @@ void main() {
); );
inbound = client.encryption!.keyManager.getInboundGroupSession( inbound = client.encryption!.keyManager.getInboundGroupSession(
roomId, roomId,
sess.outboundGroupSession!.session_id(), sess.outboundGroupSession!.sessionId,
); );
expect( expect(
inbound!.allowedAtIndex['@alice:example.com'] inbound!.allowedAtIndex['@alice:example.com']
@ -353,13 +356,13 @@ void main() {
}); });
test('setInboundGroupSession', () async { test('setInboundGroupSession', () async {
final session = olm.OutboundGroupSession(); final session = vod.GroupSession();
session.create();
final inbound = olm.InboundGroupSession(); final inbound = vod.InboundGroupSession(session.sessionKey);
inbound.create(session.session_key());
final senderKey = client.identityKey; final senderKey = client.identityKey;
final roomId = '!someroom:example.org'; final roomId = '!someroom:example.org';
final sessionId = inbound.session_id(); final sessionId = inbound.sessionId;
final room = Room(id: roomId, client: client); final room = Room(id: roomId, client: client);
final nextSyncUpdateFuture = client.onSync.stream final nextSyncUpdateFuture = client.onSync.stream
.firstWhere((update) => update.rooms != null) .firstWhere((update) => update.rooms != null)
@ -408,7 +411,7 @@ void main() {
'room_id': roomId, 'room_id': roomId,
'forwarding_curve25519_key_chain': [client.identityKey], 'forwarding_curve25519_key_chain': [client.identityKey],
'session_id': sessionId, 'session_id': sessionId,
'session_key': inbound.export_session(1), 'session_key': inbound.exportAt(1),
'sender_key': senderKey, 'sender_key': senderKey,
'sender_claimed_ed25519_key': client.fingerprintKey, 'sender_claimed_ed25519_key': client.fingerprintKey,
}; };
@ -423,7 +426,7 @@ void main() {
client.encryption!.keyManager client.encryption!.keyManager
.getInboundGroupSession(roomId, sessionId) .getInboundGroupSession(roomId, sessionId)
?.inboundGroupSession ?.inboundGroupSession
?.first_known_index(), ?.firstKnownIndex,
1, 1,
); );
expect( expect(
@ -440,7 +443,7 @@ void main() {
'room_id': roomId, 'room_id': roomId,
'forwarding_curve25519_key_chain': [client.identityKey], 'forwarding_curve25519_key_chain': [client.identityKey],
'session_id': sessionId, 'session_id': sessionId,
'session_key': inbound.export_session(2), 'session_key': inbound.exportAt(2),
'sender_key': senderKey, 'sender_key': senderKey,
'sender_claimed_ed25519_key': client.fingerprintKey, 'sender_claimed_ed25519_key': client.fingerprintKey,
}; };
@ -455,7 +458,7 @@ void main() {
client.encryption!.keyManager client.encryption!.keyManager
.getInboundGroupSession(roomId, sessionId) .getInboundGroupSession(roomId, sessionId)
?.inboundGroupSession ?.inboundGroupSession
?.first_known_index(), ?.firstKnownIndex,
1, 1,
); );
expect( expect(
@ -472,7 +475,7 @@ void main() {
'room_id': roomId, 'room_id': roomId,
'forwarding_curve25519_key_chain': [client.identityKey], 'forwarding_curve25519_key_chain': [client.identityKey],
'session_id': sessionId, 'session_id': sessionId,
'session_key': inbound.export_session(0), 'session_key': inbound.exportAt(0),
'sender_key': senderKey, 'sender_key': senderKey,
'sender_claimed_ed25519_key': client.fingerprintKey, 'sender_claimed_ed25519_key': client.fingerprintKey,
}; };
@ -487,7 +490,7 @@ void main() {
client.encryption!.keyManager client.encryption!.keyManager
.getInboundGroupSession(roomId, sessionId) .getInboundGroupSession(roomId, sessionId)
?.inboundGroupSession ?.inboundGroupSession
?.first_known_index(), ?.firstKnownIndex,
0, 0,
); );
expect( expect(
@ -504,7 +507,7 @@ void main() {
'room_id': roomId, 'room_id': roomId,
'forwarding_curve25519_key_chain': [client.identityKey, 'beep'], 'forwarding_curve25519_key_chain': [client.identityKey, 'beep'],
'session_id': sessionId, 'session_id': sessionId,
'session_key': inbound.export_session(0), 'session_key': inbound.exportAt(0),
'sender_key': senderKey, 'sender_key': senderKey,
'sender_claimed_ed25519_key': client.fingerprintKey, 'sender_claimed_ed25519_key': client.fingerprintKey,
}; };
@ -519,7 +522,7 @@ void main() {
client.encryption!.keyManager client.encryption!.keyManager
.getInboundGroupSession(roomId, sessionId) .getInboundGroupSession(roomId, sessionId)
?.inboundGroupSession ?.inboundGroupSession
?.first_known_index(), ?.firstKnownIndex,
0, 0,
); );
expect( expect(
@ -536,7 +539,7 @@ void main() {
'room_id': roomId, 'room_id': roomId,
'forwarding_curve25519_key_chain': [], 'forwarding_curve25519_key_chain': [],
'session_id': sessionId, 'session_id': sessionId,
'session_key': inbound.export_session(0), 'session_key': inbound.exportAt(0),
'sender_key': senderKey, 'sender_key': senderKey,
'sender_claimed_ed25519_key': client.fingerprintKey, 'sender_claimed_ed25519_key': client.fingerprintKey,
}; };
@ -551,7 +554,7 @@ void main() {
client.encryption!.keyManager client.encryption!.keyManager
.getInboundGroupSession(roomId, sessionId) .getInboundGroupSession(roomId, sessionId)
?.inboundGroupSession ?.inboundGroupSession
?.first_known_index(), ?.firstKnownIndex,
0, 0,
); );
expect( expect(
@ -570,9 +573,6 @@ void main() {
// decrypted last event // decrypted last event
final syncUpdate = await nextSyncUpdateFuture; final syncUpdate = await nextSyncUpdateFuture;
expect(syncUpdate.rooms?.join?.containsKey(room.id), true); expect(syncUpdate.rooms?.join?.containsKey(room.id), true);
inbound.free();
session.free();
}); });
test('Reused deviceID attack', () async { test('Reused deviceID attack', () async {

View File

@ -18,8 +18,8 @@
import 'dart:convert'; import 'dart:convert';
import 'package:olm/olm.dart' as olm;
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
import '../fake_client.dart'; import '../fake_client.dart';
@ -42,8 +42,10 @@ void main() {
Logs().level = Level.error; Logs().level = Level.error;
setUpAll(() async { setUpAll(() async {
await olm.init(); await vod.init(
olm.get_library_version(); wasmPath: './pkg/',
libraryPath: './rust/target/debug/',
);
}); });
final validSessionId = 'ciM/JWTPrmiWPPZNkRLDPQYf9AW/I46bxyLSr+Bx5oU'; final validSessionId = 'ciM/JWTPrmiWPPZNkRLDPQYf9AW/I46bxyLSr+Bx5oU';
@ -309,8 +311,7 @@ void main() {
final session = (await matrix.encryption!.keyManager final session = (await matrix.encryption!.keyManager
.loadInboundGroupSession(requestRoom.id, validSessionId))!; .loadInboundGroupSession(requestRoom.id, validSessionId))!;
final sessionKey = session.inboundGroupSession! final sessionKey = session.inboundGroupSession!.exportAtFirstKnownIndex();
.export_session(session.inboundGroupSession!.first_known_index());
matrix.encryption!.keyManager.clearInboundGroupSessions(); matrix.encryption!.keyManager.clearInboundGroupSessions();
var event = ToDeviceEvent( var event = ToDeviceEvent(
sender: '@alice:example.com', sender: '@alice:example.com',

View File

@ -20,8 +20,8 @@ import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:olm/olm.dart' as olm;
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/encryption.dart'; import 'package:matrix/encryption.dart';
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
@ -52,14 +52,16 @@ void main() async {
// key @othertest:fakeServer.notExisting // key @othertest:fakeServer.notExisting
const otherPickledOlmAccount = 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 client1;
late Client client2; late Client client2;
setUpAll(() async { setUpAll(() async {
await olm.init(); await vod.init(
olm.get_library_version(); wasmPath: './pkg/',
libraryPath: './rust/target/debug/',
);
}); });
setUp(() async { setUp(() async {

View File

@ -18,8 +18,8 @@
import 'dart:convert'; import 'dart:convert';
import 'package:olm/olm.dart' as olm;
import 'package:test/test.dart'; 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/encryption/utils/json_signature_check_extension.dart';
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
@ -32,8 +32,11 @@ void main() {
late Client client; late Client client;
setUpAll(() async { setUpAll(() async {
await olm.init(); await vod.init(
olm.get_library_version(); wasmPath: './pkg/',
libraryPath: './rust/target/debug/',
);
client = await getClient(); client = await getClient();
}); });
@ -62,7 +65,7 @@ void main() {
); );
expect(sent['device_keys'] != null, true); expect(sent['device_keys'] != null, true);
expect(sent['one_time_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'] != null, true);
expect(sent['fallback_keys'].keys.length, 1); expect(sent['fallback_keys'].keys.length, 1);
FakeMatrixApi.calledEndpoints.clear(); FakeMatrixApi.calledEndpoints.clear();
@ -78,7 +81,7 @@ void main() {
sent = json.decode( sent = json.decode(
FakeMatrixApi.calledEndpoints['/client/v3/keys/upload']!.first, 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); expect(sent['fallback_keys'].keys.length, 0);
}); });

View File

@ -18,8 +18,8 @@
import 'dart:convert'; import 'dart:convert';
import 'package:olm/olm.dart' as olm;
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
import '../fake_client.dart'; import '../fake_client.dart';
@ -35,8 +35,11 @@ void main() {
final senderKey = 'JBG7ZaPn54OBC7TuIEiylW3BZ+7WcGQhFBPB9pogbAg'; final senderKey = 'JBG7ZaPn54OBC7TuIEiylW3BZ+7WcGQhFBPB9pogbAg';
setUpAll(() async { setUpAll(() async {
await olm.init(); await vod.init(
olm.get_library_version(); wasmPath: './pkg/',
libraryPath: './rust/target/debug/',
);
client = await getClient(); client = await getClient();
}); });
@ -86,20 +89,19 @@ void main() {
}); });
test('upload key', () async { test('upload key', () async {
final session = olm.OutboundGroupSession(); final session = vod.GroupSession();
session.create(); final inbound = vod.InboundGroupSession(session.sessionKey);
final inbound = olm.InboundGroupSession();
inbound.create(session.session_key());
final senderKey = client.identityKey; final senderKey = client.identityKey;
final roomId = '!someroom:example.org'; final roomId = '!someroom:example.org';
final sessionId = inbound.session_id(); final sessionId = inbound.sessionId;
// set a payload... // set a payload...
final sessionPayload = <String, dynamic>{ final sessionPayload = <String, dynamic>{
'algorithm': AlgorithmTypes.megolmV1AesSha2, 'algorithm': AlgorithmTypes.megolmV1AesSha2,
'room_id': roomId, 'room_id': roomId,
'forwarding_curve25519_key_chain': [client.identityKey], 'forwarding_curve25519_key_chain': [client.identityKey],
'session_id': sessionId, 'session_id': sessionId,
'session_key': inbound.export_session(1), 'session_key': inbound.exportAt(1),
'sender_key': senderKey, 'sender_key': senderKey,
'sender_claimed_ed25519_key': client.fingerprintKey, 'sender_claimed_ed25519_key': client.fingerprintKey,
}; };

View File

@ -20,6 +20,7 @@ import 'dart:async';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/encryption.dart'; import 'package:matrix/encryption.dart';
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
@ -69,7 +70,14 @@ void main() async {
late Client client1; late Client client1;
late Client client2; late Client client2;
Future? vodInit;
setUp(() async { setUp(() async {
vodInit ??= vod.init(
wasmPath: './pkg/',
libraryPath: './rust/target/debug/',
);
await vodInit;
client1 = await getClient(); client1 = await getClient();
client2 = await getOtherClient(); client2 = await getOtherClient();

View File

@ -20,8 +20,8 @@ import 'dart:convert';
import 'dart:math'; import 'dart:math';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:olm/olm.dart' as olm;
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/encryption.dart'; import 'package:matrix/encryption.dart';
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
@ -54,8 +54,11 @@ void main() {
late Client client; late Client client;
setUpAll(() async { setUpAll(() async {
await olm.init(); await vod.init(
olm.get_library_version(); wasmPath: './pkg/',
libraryPath: './rust/target/debug/',
);
client = await getClient(); client = await getClient();
}); });

View File

@ -17,10 +17,12 @@
*/ */
import 'dart:convert'; import 'dart:convert';
import 'dart:typed_data';
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'package:matrix/encryption/utils/base64_unpadded.dart'; import 'package:matrix/encryption/utils/base64_unpadded.dart';
import 'package:matrix/encryption/utils/pickle_key.dart';
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
void main() { 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',
);
});
});
} }

View File

@ -20,6 +20,7 @@ import 'dart:convert';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/encryption.dart'; import 'package:matrix/encryption.dart';
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
@ -276,6 +277,15 @@ void main() async {
}); });
test('sendAgain', () 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( final matrix = Client(
'testclient', 'testclient',
httpClient: FakeMatrixApi(), httpClient: FakeMatrixApi(),

View File

@ -16,6 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
import 'fake_database.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 // key @test:fakeServer.notExisting
const pickledOlmAccount = 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 /// only use `path` if you explicitly if you need a db on path instead of in mem
Future<Client> getClient({ Future<Client> getClient({
Duration sendTimelineEventTimeout = const Duration(minutes: 1), Duration sendTimelineEventTimeout = const Duration(minutes: 1),
String? databasePath, String? databasePath,
}) async { }) async {
try {
vodInit ??= vod.init(
wasmPath: './pkg/',
libraryPath: './rust/target/debug/',
);
await vodInit;
} catch (_) {
Logs().d('Encryption via Vodozemac not enabled');
}
final client = Client( final client = Client(
logLevel: Level.verbose, logLevel: Level.verbose,
'testclient', 'testclient',

View File

@ -138,7 +138,7 @@ void main() async {
final inboundSession = final inboundSession =
client.encryption!.keyManager.getInboundGroupSession( client.encryption!.keyManager.getInboundGroupSession(
roomid, roomid,
outboundSession!.outboundGroupSession!.session_id(), outboundSession!.outboundGroupSession!.sessionId,
)!; )!;
// ensure encryption is "enabled" // ensure encryption is "enabled"

View File

@ -671,12 +671,16 @@ void main() {
expect(fetchedParticipants.length, newParticipants.length); expect(fetchedParticipants.length, newParticipants.length);
}); });
test('calcEncryptionHealthState', () async { test(
'calcEncryptionHealthState',
() async {
expect( expect(
await room.calcEncryptionHealthState(), await room.calcEncryptionHealthState(),
EncryptionHealthState.unverifiedDevices, EncryptionHealthState.unverifiedDevices,
); );
}); },
tags: 'olm',
);
test('getEventByID', () async { test('getEventByID', () async {
final event = await room.getEventById('1234'); final event = await room.getEventById('1234');

View File

@ -17,6 +17,7 @@
*/ */
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
import 'fake_database.dart'; import 'fake_database.dart';
@ -27,7 +28,17 @@ void main() async {
late Client client; late Client client;
late Room room; late Room room;
late User user1, user2; late User user1, user2;
Future? vodInit;
setUp(() async { setUp(() async {
try {
vodInit ??= vod.init(
wasmPath: './pkg/',
libraryPath: './rust/target/debug/',
);
await vodInit;
} catch (_) {
Logs().d('Encryption via Vodozemac not enabled');
}
client = Client( client = Client(
'testclient', 'testclient',
httpClient: FakeMatrixApi(), httpClient: FakeMatrixApi(),

View File

@ -18,8 +18,8 @@
import 'dart:io'; import 'dart:io';
import 'package:olm/olm.dart' as olm;
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
import '../test/fake_database.dart'; import '../test/fake_database.dart';
@ -39,9 +39,10 @@ void main() => group(
Client? testClientA, testClientB; Client? testClientA, testClientB;
try { try {
await olm.init(); await vod.init(
olm.Account(); wasmPath: './pkg/',
Logs().i('[LibOlm] Enabled'); libraryPath: './rust/target/debug/',
);
final homeserverUri = Uri.parse(homeserver); final homeserverUri = Uri.parse(homeserver);
Logs().i('++++ Using homeserver $homeserverUri ++++'); Logs().i('++++ Using homeserver $homeserverUri ++++');
@ -229,7 +230,7 @@ void main() => group(
var currentSessionIdA = room.client.encryption!.keyManager var currentSessionIdA = room.client.encryption!.keyManager
.getOutboundGroupSession(room.id)! .getOutboundGroupSession(room.id)!
.outboundGroupSession! .outboundGroupSession!
.session_id(); .sessionId;
/*expect(room.client.encryption.keyManager /*expect(room.client.encryption.keyManager
.getInboundGroupSession(room.id, currentSessionIdA, '') != .getInboundGroupSession(room.id, currentSessionIdA, '') !=
null);*/ null);*/
@ -284,7 +285,7 @@ void main() => group(
room.client.encryption!.keyManager room.client.encryption!.keyManager
.getOutboundGroupSession(room.id)! .getOutboundGroupSession(room.id)!
.outboundGroupSession! .outboundGroupSession!
.session_id(), .sessionId,
currentSessionIdA, currentSessionIdA,
); );
/*expect(room.client.encryption.keyManager /*expect(room.client.encryption.keyManager
@ -315,7 +316,7 @@ void main() => group(
room.client.encryption!.keyManager room.client.encryption!.keyManager
.getOutboundGroupSession(room.id)! .getOutboundGroupSession(room.id)!
.outboundGroupSession! .outboundGroupSession!
.session_id(), .sessionId,
currentSessionIdA, currentSessionIdA,
); );
final inviteRoomOutboundGroupSession = inviteRoom final inviteRoomOutboundGroupSession = inviteRoom
@ -325,12 +326,12 @@ void main() => group(
expect(inviteRoomOutboundGroupSession.isValid, isTrue); expect(inviteRoomOutboundGroupSession.isValid, isTrue);
/*expect(inviteRoom.client.encryption.keyManager.getInboundGroupSession( /*expect(inviteRoom.client.encryption.keyManager.getInboundGroupSession(
inviteRoom.id, inviteRoom.id,
inviteRoomOutboundGroupSession.outboundGroupSession.session_id(), inviteRoomOutboundGroupSession.outboundGroupSession.sessionId,
'') != '') !=
null); null);
expect(room.client.encryption.keyManager.getInboundGroupSession( expect(room.client.encryption.keyManager.getInboundGroupSession(
room.id, room.id,
inviteRoomOutboundGroupSession.outboundGroupSession.session_id(), inviteRoomOutboundGroupSession.outboundGroupSession.sessionId,
'') != '') !=
null);*/ null);*/
expect(inviteRoom.lastEvent!.body, testMessage3); expect(inviteRoom.lastEvent!.body, testMessage3);
@ -392,7 +393,7 @@ void main() => group(
room.client.encryption!.keyManager room.client.encryption!.keyManager
.getOutboundGroupSession(room.id)! .getOutboundGroupSession(room.id)!
.outboundGroupSession! .outboundGroupSession!
.session_id(), .sessionId,
currentSessionIdA, currentSessionIdA,
); );
/*expect(inviteRoom.client.encryption.keyManager /*expect(inviteRoom.client.encryption.keyManager
@ -440,14 +441,14 @@ void main() => group(
room.client.encryption!.keyManager room.client.encryption!.keyManager
.getOutboundGroupSession(room.id)! .getOutboundGroupSession(room.id)!
.outboundGroupSession! .outboundGroupSession!
.session_id(), .sessionId,
isNot(currentSessionIdA), isNot(currentSessionIdA),
); );
} }
currentSessionIdA = room.client.encryption!.keyManager currentSessionIdA = room.client.encryption!.keyManager
.getOutboundGroupSession(room.id)! .getOutboundGroupSession(room.id)!
.outboundGroupSession! .outboundGroupSession!
.session_id(); .sessionId;
/*expect(inviteRoom.client.encryption.keyManager /*expect(inviteRoom.client.encryption.keyManager
.getInboundGroupSession(inviteRoom.id, currentSessionIdA, '') != .getInboundGroupSession(inviteRoom.id, currentSessionIdA, '') !=
null);*/ null);*/
@ -485,10 +486,6 @@ void main() => group(
Client? testClientA, testClientB; Client? testClientA, testClientB;
try { try {
await olm.init();
olm.Account();
Logs().i('[LibOlm] Enabled');
final homeserverUri = Uri.parse(homeserver); final homeserverUri = Uri.parse(homeserver);
Logs().i('++++ Using homeserver $homeserverUri ++++'); Logs().i('++++ Using homeserver $homeserverUri ++++');