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
with:
sdk: ${{ env.dart_version }}
- uses: famedly/backend-build-workflows/.github/actions/rust-prepare@main
with:
gitlab_user: ${{ secrets.GITLAB_USER }}
gitlab_pass: ${{ secrets.GITLAB_PASS }}
gitlab_ssh: ${{ secrets.CI_SSH_PRIVATE_KEY}}
- name: Run tests
run: |
export HOMESERVER_IMPLEMENTATION=${{matrix.homeserver}}
export HOMESERVER="localhost:80"
scripts/integration-server-${{matrix.homeserver}}.sh 2>&1 > /dev/null &
sudo apt-get update && sudo apt-get install --no-install-recommends --no-install-suggests -y libolm3 libssl3 sqlite3 libsqlite3-dev
sudo apt-get update && sudo apt-get install --no-install-recommends --no-install-suggests -y libssl3 sqlite3 libsqlite3-dev
source scripts/integration-create-environment-variables.sh
scripts/integration-prepare-homeserver.sh
scripts/prepare.sh
scripts/prepare_vodozemac.sh
scripts/test_driver.sh
coverage_without_olm:
@ -54,7 +60,7 @@ jobs:
coverage:
#runs-on: arm-ubuntu-latest-16core
runs-on: ubuntu-latest
timeout-minutes: 5
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- run: cat .github/workflows/versions.env >> $GITHUB_ENV
@ -62,9 +68,15 @@ jobs:
with:
sdk: ${{ env.dart_version }}
#architecture: "arm64"
- uses: famedly/backend-build-workflows/.github/actions/rust-prepare@main
with:
gitlab_user: ${{ secrets.GITLAB_USER }}
gitlab_pass: ${{ secrets.GITLAB_PASS }}
gitlab_ssh: ${{ secrets.CI_SSH_PRIVATE_KEY}}
- name: Run tests
run: |
sudo apt-get update && sudo apt-get install --no-install-recommends --no-install-suggests -y lcov libsqlite3-0 libsqlite3-dev libolm3 libssl3
sudo apt-get update && sudo apt-get install --no-install-recommends --no-install-suggests -y lcov libsqlite3-0 libsqlite3-dev libssl3
./scripts/prepare_vodozemac.sh
./scripts/test.sh
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}

1
.gitignore vendored
View File

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

View File

@ -4,15 +4,17 @@ Matrix (matrix.org) SDK written in dart.
## Native libraries
For E2EE, libolm must be provided.
For E2EE, vodozemac must be provided.
Additionally, OpenSSL (libcrypto) must be provided on native platforms for E2EE.
For flutter apps you can easily import it with the [flutter_olm](https://pub.dev/packages/flutter_olm) and the [flutter_openssl_crypto](https://pub.dev/packages/flutter_openssl_crypto) packages.
For flutter apps you can easily import it with the [flutter_vodozemac](https://pub.dev/packages/flutter_vodozemac) and the [flutter_openssl_crypto](https://pub.dev/packages/flutter_openssl_crypto) packages.
```sh
flutter pub add matrix
flutter pub add flutter_olm
# Optional: For end to end encryption:
flutter pub add flutter_vodozemac
flutter pub add flutter_openssl_crypto
```

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
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>
# For end to end encryption:
flutter_olm: <latest-version>
# (Optional) For end to end encryption, please head on the
# encryption guide and add these dependencies:
flutter_vodozemac: <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/>.
*/
import 'dart:convert';
import 'dart:typed_data';
import 'package:olm/olm.dart' as olm;
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/encryption/encryption.dart';
import 'package:matrix/encryption/ssss.dart';
@ -31,26 +32,22 @@ class CrossSigning {
CrossSigning(this.encryption) {
encryption.ssss.setValidator(EventTypes.CrossSigningSelfSigning,
(String secret) async {
final keyObj = olm.PkSigning();
try {
return keyObj.init_with_seed(base64decodeUnpadded(secret)) ==
final keyObj = vod.PkSigning.fromSecretKey(secret);
return keyObj.publicKey.toBase64() ==
client.userDeviceKeys[client.userID]!.selfSigningKey!.ed25519Key;
} catch (_) {
return false;
} finally {
keyObj.free();
}
});
encryption.ssss.setValidator(EventTypes.CrossSigningUserSigning,
(String secret) async {
final keyObj = olm.PkSigning();
try {
return keyObj.init_with_seed(base64decodeUnpadded(secret)) ==
final keyObj = vod.PkSigning.fromSecretKey(secret);
return keyObj.publicKey.toBase64() ==
client.userDeviceKeys[client.userID]!.userSigningKey!.ed25519Key;
} catch (_) {
return false;
} finally {
keyObj.free();
}
});
}
@ -92,14 +89,13 @@ class CrossSigning {
final masterPrivateKey = base64decodeUnpadded(
await handle.getStored(EventTypes.CrossSigningMasterKey),
);
final keyObj = olm.PkSigning();
String? masterPubkey;
try {
masterPubkey = keyObj.init_with_seed(masterPrivateKey);
masterPubkey = vod.PkSigning.fromSecretKey(base64Encode(masterPrivateKey))
.publicKey
.toBase64();
} catch (e) {
masterPubkey = null;
} finally {
keyObj.free();
}
final userDeviceKeys =
client.userDeviceKeys[client.userID]?.deviceKeys[client.deviceID];
@ -210,12 +206,7 @@ class CrossSigning {
}
String _sign(String canonicalJson, Uint8List key) {
final keyObj = olm.PkSigning();
try {
keyObj.init_with_seed(key);
return keyObj.sign(canonicalJson);
} finally {
keyObj.free();
}
final keyObj = vod.PkSigning.fromSecretKey(base64Encode(key));
return keyObj.sign(canonicalJson).toBase64();
}
}

View File

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

View File

@ -20,11 +20,12 @@ import 'dart:async';
import 'dart:convert';
import 'package:collection/collection.dart';
import 'package:olm/olm.dart' as olm;
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/encryption/encryption.dart';
import 'package:matrix/encryption/utils/base64_unpadded.dart';
import 'package:matrix/encryption/utils/outbound_group_session.dart';
import 'package:matrix/encryption/utils/pickle_key.dart';
import 'package:matrix/encryption/utils/session_key.dart';
import 'package:matrix/encryption/utils/stored_inbound_group_session.dart';
import 'package:matrix/matrix.dart';
@ -45,19 +46,18 @@ class KeyManager {
KeyManager(this.encryption) {
encryption.ssss.setValidator(megolmKey, (String secret) async {
final keyObj = olm.PkDecryption();
try {
final keyObj = vod.PkDecryption.fromSecretKey(
vod.Curve25519PublicKey.fromBase64(secret),
);
final info = await getRoomKeysBackupInfo(false);
if (info.algorithm !=
BackupAlgorithm.mMegolmBackupV1Curve25519AesSha2) {
return false;
}
return keyObj.init_with_private_key(base64decodeUnpadded(secret)) ==
info.authData['public_key'];
return keyObj.publicKey == info.authData['public_key'];
} catch (_) {
return false;
} finally {
keyObj.free();
}
});
encryption.ssss.setCacheCallback(megolmKey, (String secret) {
@ -118,17 +118,16 @@ class KeyManager {
if (content['algorithm'] != AlgorithmTypes.megolmV1AesSha2) {
return;
}
late olm.InboundGroupSession inboundGroupSession;
late vod.InboundGroupSession inboundGroupSession;
try {
inboundGroupSession = olm.InboundGroupSession();
if (forwarded) {
inboundGroupSession.import_session(content['session_key']);
inboundGroupSession =
vod.InboundGroupSession.import(content['session_key']);
} else {
inboundGroupSession.create(content['session_key']);
inboundGroupSession = vod.InboundGroupSession(content['session_key']);
}
} catch (e, s) {
inboundGroupSession.free();
Logs().e('[LibOlm] Could not create new InboundGroupSession', e, s);
Logs().e('[Vodozemac] Could not create new InboundGroupSession', e, s);
return Future.value();
}
final newSession = SessionKey(
@ -142,19 +141,16 @@ class KeyManager {
senderClaimedKeys: senderClaimedKeys_,
allowedAtIndex: allowedAtIndex_,
);
final oldFirstIndex =
oldSession?.inboundGroupSession?.first_known_index() ?? 0;
final newFirstIndex = newSession.inboundGroupSession!.first_known_index();
final oldFirstIndex = oldSession?.inboundGroupSession?.firstKnownIndex ?? 0;
final newFirstIndex = newSession.inboundGroupSession!.firstKnownIndex;
if (oldSession == null ||
newFirstIndex < oldFirstIndex ||
(oldFirstIndex == newFirstIndex &&
newSession.forwardingCurve25519KeyChain.length <
oldSession.forwardingCurve25519KeyChain.length)) {
// use new session
oldSession?.dispose();
} else {
// we are gonna keep our old session
newSession.dispose();
return;
}
@ -169,7 +165,7 @@ class KeyManager {
.storeInboundGroupSession(
roomId,
sessionId,
inboundGroupSession.pickle(userId),
inboundGroupSession.toPickleEncrypted(userId.toPickleKey()),
json.encode(content),
json.encode({}),
json.encode(allowedAtIndex_),
@ -344,7 +340,7 @@ class KeyManager {
final inboundSess = await loadInboundGroupSession(
room.id,
sess.outboundGroupSession!.session_id(),
sess.outboundGroupSession!.sessionId,
);
if (inboundSess == null) {
Logs().w('No inbound megolm session found for outbound session!');
@ -436,8 +432,8 @@ class KeyManager {
final rawSession = <String, dynamic>{
'algorithm': AlgorithmTypes.megolmV1AesSha2,
'room_id': room.id,
'session_id': sess.outboundGroupSession!.session_id(),
'session_key': sess.outboundGroupSession!.session_key(),
'session_id': sess.outboundGroupSession!.sessionId,
'session_key': sess.outboundGroupSession!.sessionKey,
};
try {
devicesToReceive.removeWhere((k) => !k.encryptToDevice);
@ -449,16 +445,16 @@ class KeyManager {
.containsKey(device.curve25519Key) ||
inboundSess.allowedAtIndex[device.userId]![
device.curve25519Key]! >
sess.outboundGroupSession!.message_index()) {
sess.outboundGroupSession!.messageIndex) {
inboundSess
.allowedAtIndex[device.userId]![device.curve25519Key!] =
sess.outboundGroupSession!.message_index();
sess.outboundGroupSession!.messageIndex;
}
}
await client.database.updateInboundGroupSessionAllowedAtIndex(
json.encode(inboundSess!.allowedAtIndex),
room.id,
sess.outboundGroupSession!.session_id(),
sess.outboundGroupSession!.sessionId,
);
// send out the key
await client.sendToDeviceEncryptedChunked(
@ -469,7 +465,7 @@ class KeyManager {
}
} catch (e, s) {
Logs().e(
'[LibOlm] Unable to re-send the session key at later index to new devices',
'[Vodozemac] Unable to re-send the session key at later index to new devices',
e,
s,
);
@ -477,7 +473,6 @@ class KeyManager {
return false;
}
}
sess.dispose();
_outboundGroupSessions.remove(roomId);
await client.database.removeOutboundGroupSession(roomId);
return true;
@ -492,7 +487,7 @@ class KeyManager {
if (userID == null) return;
await client.database.storeOutboundGroupSession(
roomId,
sess.outboundGroupSession!.pickle(userID),
sess.outboundGroupSession!.toPickleEncrypted(userID.toPickleKey()),
json.encode(sess.devices),
sess.creationTime.millisecondsSinceEpoch,
);
@ -553,19 +548,13 @@ class KeyManager {
final deviceKeys = await room.getUserDeviceKeys();
final deviceKeyIds = _getDeviceKeyIdMap(deviceKeys);
deviceKeys.removeWhere((k) => !k.encryptToDevice);
final outboundGroupSession = olm.OutboundGroupSession();
try {
outboundGroupSession.create();
} catch (e, s) {
outboundGroupSession.free();
Logs().e('[LibOlm] Unable to create new outboundGroupSession', e, s);
rethrow;
}
final outboundGroupSession = vod.GroupSession();
final rawSession = <String, dynamic>{
'algorithm': AlgorithmTypes.megolmV1AesSha2,
'room_id': room.id,
'session_id': outboundGroupSession.session_id(),
'session_key': outboundGroupSession.session_key(),
'session_id': outboundGroupSession.sessionId,
'session_key': outboundGroupSession.sessionKey,
};
final allowedAtIndex = <String, Map<String, int>>{};
for (final device in deviceKeys) {
@ -575,7 +564,7 @@ class KeyManager {
}
allowedAtIndex[device.userId] ??= <String, int>{};
allowedAtIndex[device.userId]![device.curve25519Key!] =
outboundGroupSession.message_index();
outboundGroupSession.messageIndex;
}
await setInboundGroupSession(
roomId,
@ -600,11 +589,10 @@ class KeyManager {
_outboundGroupSessions[roomId] = sess;
} catch (e, s) {
Logs().e(
'[LibOlm] Unable to send the session key to the participating devices',
'[Vodozemac] Unable to send the session key to the participating devices',
e,
s,
);
sess.dispose();
rethrow;
}
return sess;
@ -669,54 +657,55 @@ class KeyManager {
}
final privateKey =
base64decodeUnpadded((await encryption.ssss.getCached(megolmKey))!);
final decryption = olm.PkDecryption();
final info = await getRoomKeysBackupInfo();
String backupPubKey;
try {
backupPubKey = decryption.init_with_private_key(privateKey);
if (info.algorithm != BackupAlgorithm.mMegolmBackupV1Curve25519AesSha2 ||
info.authData['public_key'] != backupPubKey) {
return;
}
for (final roomEntry in keys.rooms.entries) {
final roomId = roomEntry.key;
for (final sessionEntry in roomEntry.value.sessions.entries) {
final sessionId = sessionEntry.key;
final session = sessionEntry.value;
final sessionData = session.sessionData;
Map<String, Object?>? decrypted;
try {
decrypted = json.decode(
decryption.decrypt(
sessionData['ephemeral'] as String,
sessionData['mac'] as String,
sessionData['ciphertext'] as String,
final decryption = vod.PkDecryption.fromSecretKey(
vod.Curve25519PublicKey.fromBytes(privateKey),
);
backupPubKey = decryption.publicKey;
if (info.algorithm != BackupAlgorithm.mMegolmBackupV1Curve25519AesSha2 ||
info.authData['public_key'] != backupPubKey) {
return;
}
for (final roomEntry in keys.rooms.entries) {
final roomId = roomEntry.key;
for (final sessionEntry in roomEntry.value.sessions.entries) {
final sessionId = sessionEntry.key;
final session = sessionEntry.value;
final sessionData = session.sessionData;
Map<String, Object?>? decrypted;
try {
decrypted = json.decode(
decryption.decrypt(
vod.PkMessage.fromBase64(
ciphertext: sessionData['ciphertext'] as String,
mac: sessionData['mac'] as String,
ephemeralKey: sessionData['ephemeral'] as String,
),
);
} catch (e, s) {
Logs().e('[LibOlm] Error decrypting room key', e, s);
}
final senderKey = decrypted?.tryGet<String>('sender_key');
if (decrypted != null && senderKey != null) {
decrypted['session_id'] = sessionId;
decrypted['room_id'] = roomId;
await setInboundGroupSession(
roomId,
sessionId,
senderKey,
decrypted,
forwarded: true,
senderClaimedKeys:
decrypted.tryGetMap<String, String>('sender_claimed_keys') ??
<String, String>{},
uploaded: true,
);
}
),
);
} catch (e, s) {
Logs().e('[Vodozemac] Error decrypting room key', e, s);
}
final senderKey = decrypted?.tryGet<String>('sender_key');
if (decrypted != null && senderKey != null) {
decrypted['session_id'] = sessionId;
decrypted['room_id'] = roomId;
await setInboundGroupSession(
roomId,
sessionId,
senderKey,
decrypted,
forwarded: true,
senderClaimedKeys:
decrypted.tryGetMap<String, String>('sender_claimed_keys') ??
<String, String>{},
uploaded: true,
);
}
}
} finally {
decryption.free();
}
}
@ -875,57 +864,56 @@ class KeyManager {
final privateKey =
base64decodeUnpadded((await encryption.ssss.getCached(megolmKey))!);
// decryption is needed to calculate the public key and thus see if the claimed information is in fact valid
final decryption = olm.PkDecryption();
final info = await getRoomKeysBackupInfo(false);
String backupPubKey;
try {
backupPubKey = decryption.init_with_private_key(privateKey);
if (info.algorithm !=
BackupAlgorithm.mMegolmBackupV1Curve25519AesSha2 ||
info.authData['public_key'] != backupPubKey) {
decryption.free();
return;
}
final args = GenerateUploadKeysArgs(
pubkey: backupPubKey,
dbSessions: <DbInboundGroupSessionBundle>[],
userId: userID,
final decryption = vod.PkDecryption.fromSecretKey(
vod.Curve25519PublicKey.fromBytes(privateKey),
);
backupPubKey = decryption.publicKey;
if (info.algorithm !=
BackupAlgorithm.mMegolmBackupV1Curve25519AesSha2 ||
info.authData['public_key'] != backupPubKey) {
return;
}
final args = GenerateUploadKeysArgs(
pubkey: backupPubKey,
dbSessions: <DbInboundGroupSessionBundle>[],
userId: userID,
);
// we need to calculate verified beforehand, as else we pass a closure to an isolate
// with 500 keys they do, however, noticably block the UI, which is why we give brief async suspentions in here
// so that the event loop can progress
var i = 0;
for (final dbSession in dbSessions) {
final device =
client.getUserDeviceKeysByCurve25519Key(dbSession.senderKey);
args.dbSessions.add(
DbInboundGroupSessionBundle(
dbSession: dbSession,
verified: device?.verified ?? false,
),
);
// we need to calculate verified beforehand, as else we pass a closure to an isolate
// with 500 keys they do, however, noticably block the UI, which is why we give brief async suspentions in here
// so that the event loop can progress
var i = 0;
for (final dbSession in dbSessions) {
final device =
client.getUserDeviceKeysByCurve25519Key(dbSession.senderKey);
args.dbSessions.add(
DbInboundGroupSessionBundle(
dbSession: dbSession,
verified: device?.verified ?? false,
),
);
i++;
if (i > 10) {
await Future.delayed(Duration(milliseconds: 1));
i = 0;
}
i++;
if (i > 10) {
await Future.delayed(Duration(milliseconds: 1));
i = 0;
}
final roomKeys =
await client.nativeImplementations.generateUploadKeys(args);
Logs().i('[Key Manager] Uploading ${dbSessions.length} room keys...');
// upload the payload...
await client.putRoomKeys(info.version, roomKeys);
// and now finally mark all the keys as uploaded
// no need to optimze this, as we only run it so seldomly and almost never with many keys at once
for (final dbSession in dbSessions) {
await database.markInboundGroupSessionAsUploaded(
dbSession.roomId,
dbSession.sessionId,
);
}
} finally {
decryption.free();
}
final roomKeys =
await client.nativeImplementations.generateUploadKeys(args);
Logs().i('[Key Manager] Uploading ${dbSessions.length} room keys...');
// upload the payload...
await client.putRoomKeys(info.version, roomKeys);
// and now finally mark all the keys as uploaded
// no need to optimze this, as we only run it so seldomly and almost never with many keys at once
for (final dbSession in dbSessions) {
await database.markInboundGroupSessionAsUploaded(
dbSession.roomId,
dbSession.sessionId,
);
}
} catch (e, s) {
Logs().e('[Key Manager] Error uploading room keys', e, s);
@ -1161,14 +1149,6 @@ class KeyManager {
void dispose() {
// ignore: discarded_futures
_uploadKeysOnSync?.cancel();
for (final sess in _outboundGroupSessions.values) {
sess.dispose();
}
for (final entries in _inboundGroupSessions.values) {
for (final sess in entries.values) {
sess.dispose();
}
}
}
}
@ -1231,8 +1211,8 @@ class RoomKeyRequest extends ToDeviceEvent {
(session.forwardingCurve25519KeyChain.isEmpty
? keyManager.encryption.fingerprintKey
: null);
message['session_key'] = session.inboundGroupSession!.export_session(
index ?? session.inboundGroupSession!.first_known_index(),
message['session_key'] = session.inboundGroupSession!.exportAt(
index ?? session.inboundGroupSession!.firstKnownIndex,
);
// send the actual reply of the key back to the requester
await keyManager.client.sendToDeviceEncrypted(
@ -1247,9 +1227,10 @@ class RoomKeyRequest extends ToDeviceEvent {
/// you would likely want to use [NativeImplementations] and
/// [Client.nativeImplementations] instead
RoomKeys generateUploadKeysImplementation(GenerateUploadKeysArgs args) {
final enc = olm.PkEncryption();
try {
enc.set_recipient_key(args.pubkey);
final enc = vod.PkEncryption.fromPublicKey(
vod.Curve25519PublicKey.fromBase64(args.pubkey),
);
// first we generate the payload to upload all the session keys in this chunk
final roomKeys = RoomKeys(rooms: {});
for (final dbSession in args.dbSessions) {
@ -1266,30 +1247,28 @@ RoomKeys generateUploadKeysImplementation(GenerateUploadKeysArgs args) {
'forwarding_curve25519_key_chain': sess.forwardingCurve25519KeyChain,
'sender_key': sess.senderKey,
'sender_claimed_keys': sess.senderClaimedKeys,
'session_key': sess.inboundGroupSession!
.export_session(sess.inboundGroupSession!.first_known_index()),
'session_key': sess.inboundGroupSession!.exportAtFirstKnownIndex(),
};
// encrypt the content
final encrypted = enc.encrypt(json.encode(payload));
// fetch the device, if available...
//final device = args.client.getUserDeviceKeysByCurve25519Key(sess.senderKey);
// aaaand finally add the session key to our payload
final (ciphertext, mac, ephemeral) = encrypted.toBase64();
roomKeyBackup.sessions[sess.sessionId] = KeyBackupData(
firstMessageIndex: sess.inboundGroupSession!.first_known_index(),
firstMessageIndex: sess.inboundGroupSession!.firstKnownIndex,
forwardedCount: sess.forwardingCurve25519KeyChain.length,
isVerified: dbSession.verified, //device?.verified ?? false,
sessionData: {
'ephemeral': encrypted.ephemeral,
'ciphertext': encrypted.ciphertext,
'mac': encrypted.mac,
'ephemeral': ephemeral,
'ciphertext': ciphertext,
'mac': mac,
},
);
}
enc.free();
return roomKeys;
} catch (e, s) {
Logs().e('[Key Manager] Error generating payload', e, s);
enc.free();
rethrow;
}
}

View File

@ -21,11 +21,12 @@ import 'dart:convert';
import 'package:async/async.dart';
import 'package:canonical_json/canonical_json.dart';
import 'package:collection/collection.dart';
import 'package:olm/olm.dart' as olm;
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/encryption/encryption.dart';
import 'package:matrix/encryption/utils/json_signature_check_extension.dart';
import 'package:matrix/encryption/utils/olm_session.dart';
import 'package:matrix/encryption/utils/pickle_key.dart';
import 'package:matrix/matrix.dart';
import 'package:matrix/msc_extensions/msc_3814_dehydrated_devices/api.dart';
import 'package:matrix/src/utils/run_benchmarked.dart';
@ -34,20 +35,24 @@ import 'package:matrix/src/utils/run_in_root.dart';
class OlmManager {
final Encryption encryption;
Client get client => encryption.client;
olm.Account? _olmAccount;
vod.Account? _olmAccount;
String? ourDeviceId;
/// Returns the base64 encoded keys to store them in a store.
/// This String should **never** leave the device!
String? get pickledOlmAccount =>
enabled ? _olmAccount!.pickle(client.userID!) : null;
String? get pickledOlmAccount {
return enabled
? _olmAccount!.toPickleEncrypted(client.userID!.toPickleKey())
: null;
}
String? get fingerprintKey =>
enabled ? json.decode(_olmAccount!.identity_keys())['ed25519'] : null;
enabled ? _olmAccount!.identityKeys.ed25519.toBase64() : null;
String? get identityKey =>
enabled ? json.decode(_olmAccount!.identity_keys())['curve25519'] : null;
enabled ? _olmAccount!.identityKeys.curve25519.toBase64() : null;
String? pickleOlmAccountWithKey(String key) =>
enabled ? _olmAccount!.pickle(key) : null;
enabled ? _olmAccount!.toPickleEncrypted(key.toPickleKey()) : null;
bool get enabled => _olmAccount != null;
@ -66,33 +71,31 @@ class OlmManager {
}) async {
ourDeviceId = deviceId;
if (olmAccount == null) {
try {
await olm.init();
_olmAccount = olm.Account();
_olmAccount!.create();
if (!await uploadKeys(
uploadDeviceKeys: true,
updateDatabase: false,
dehydratedDeviceAlgorithm: dehydratedDeviceAlgorithm,
dehydratedDevicePickleKey:
dehydratedDeviceAlgorithm != null ? pickleKey : null,
)) {
throw ('Upload key failed');
}
} catch (_) {
_olmAccount?.free();
_olmAccount = null;
rethrow;
_olmAccount = vod.Account();
if (!await uploadKeys(
uploadDeviceKeys: true,
updateDatabase: false,
dehydratedDeviceAlgorithm: dehydratedDeviceAlgorithm,
dehydratedDevicePickleKey:
dehydratedDeviceAlgorithm != null ? pickleKey : null,
)) {
throw ('Upload key failed');
}
} else {
try {
await olm.init();
_olmAccount = olm.Account();
_olmAccount!.unpickle(pickleKey ?? client.userID!, olmAccount);
} catch (_) {
_olmAccount?.free();
_olmAccount = null;
rethrow;
_olmAccount = vod.Account.fromPickleEncrypted(
pickle: olmAccount,
pickleKey: (pickleKey ?? client.userID!).toPickleKey(),
);
} catch (e) {
Logs().d(
'Unable to unpickle account in vodozemac format. Trying Olm format...',
e,
);
_olmAccount = vod.Account.fromOlmPickleEncrypted(
pickle: olmAccount,
pickleKey: utf8.encode(pickleKey ?? client.userID!),
);
}
}
}
@ -115,7 +118,8 @@ class OlmManager {
if (!payload['signatures'].containsKey(client.userID)) {
payload['signatures'][client.userID] = <String, dynamic>{};
}
payload['signatures'][client.userID]['ed25519:$ourDeviceId'] = signature;
payload['signatures'][client.userID]['ed25519:$ourDeviceId'] =
signature.toBase64();
if (unsigned != null) {
payload['unsigned'] = unsigned;
}
@ -123,13 +127,13 @@ class OlmManager {
}
String signString(String s) {
return _olmAccount!.sign(s);
return _olmAccount!.sign(s).toBase64();
}
bool _uploadKeysLock = false;
CancelableOperation<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.
/// If `retry` is > 0, the request will be retried with new OTKs on upload failure.
@ -158,26 +162,24 @@ class OlmManager {
if (oldKeyCount != null) {
// check if we have OTKs that still need uploading. If we do, we don't try to generate new ones,
// instead we try to upload the old ones first
final oldOTKsNeedingUpload = json
.decode(olmAccount.one_time_keys())['curve25519']
.entries
.length as int;
final oldOTKsNeedingUpload = olmAccount.oneTimeKeys.length;
// generate one-time keys
// we generate 2/3rds of max, so that other keys people may still have can
// still be used
final oneTimeKeysCount =
(olmAccount.max_number_of_one_time_keys() * 2 / 3).floor() -
(olmAccount.maxNumberOfOneTimeKeys * 2 / 3).floor() -
oldKeyCount -
oldOTKsNeedingUpload;
if (oneTimeKeysCount > 0) {
olmAccount.generate_one_time_keys(oneTimeKeysCount);
olmAccount.generateOneTimeKeys(oneTimeKeysCount);
}
uploadedOneTimeKeysCount = oneTimeKeysCount + oldOTKsNeedingUpload;
}
if (encryption.isMinOlmVersion(3, 2, 7) && unusedFallbackKey == false) {
if (unusedFallbackKey == false) {
// we don't have an unused fallback key uploaded....so let's change that!
olmAccount.generate_fallback_key();
olmAccount.generateFallbackKey();
}
// we save the generated OTKs into the database.
@ -199,38 +201,32 @@ class OlmManager {
};
if (uploadDeviceKeys) {
final Map<String, dynamic> keys =
json.decode(olmAccount.identity_keys());
for (final entry in keys.entries) {
final algorithm = entry.key;
final value = entry.value;
deviceKeys['keys']['$algorithm:$ourDeviceId'] = value;
}
final keys = olmAccount.identityKeys;
deviceKeys['keys']['curve25519:$ourDeviceId'] =
keys.curve25519.toBase64();
deviceKeys['keys']['ed25519:$ourDeviceId'] = keys.ed25519.toBase64();
deviceKeys = signJson(deviceKeys);
}
// now sign all the one-time keys
for (final entry
in json.decode(olmAccount.one_time_keys())['curve25519'].entries) {
for (final entry in olmAccount.oneTimeKeys.entries) {
final key = entry.key;
final value = entry.value;
final value = entry.value.toBase64();
signedOneTimeKeys['signed_curve25519:$key'] = signJson({
'key': value,
});
}
final signedFallbackKeys = <String, dynamic>{};
if (encryption.isMinOlmVersion(3, 2, 7)) {
final fallbackKey = json.decode(olmAccount.unpublished_fallback_key());
// now sign all the fallback keys
for (final entry in fallbackKey['curve25519'].entries) {
final key = entry.key;
final value = entry.value;
signedFallbackKeys['signed_curve25519:$key'] = signJson({
'key': value,
'fallback': true,
});
}
final fallbackKey = olmAccount.fallbackKey;
// now sign all the fallback keys
for (final entry in fallbackKey.entries) {
final key = entry.key;
final value = entry.value.toBase64();
signedFallbackKeys['signed_curve25519:$key'] = signJson({
'key': value,
'fallback': true,
});
}
if (signedFallbackKeys.isEmpty &&
@ -281,7 +277,7 @@ class OlmManager {
}
// mark the OTKs as published and save that to datbase
olmAccount.mark_keys_as_published();
olmAccount.markKeysAsPublished();
if (updateDatabase) {
await encryption.olmDatabase?.updateClientKeys(pickledOlmAccount!);
}
@ -300,18 +296,9 @@ class OlmManager {
exception.error == MatrixError.M_UNKNOWN) {
Logs().w('Rotating otks because upload failed', exception);
for (final otk in signedOneTimeKeys.values) {
// Keys can only be removed by creating a session...
final session = olm.Session();
try {
final String identity =
json.decode(olmAccount.identity_keys())['curve25519'];
final key = otk.tryGet<String>('key');
if (key != null) {
session.create_outbound(_olmAccount!, identity, key);
olmAccount.remove_one_time_keys(session);
}
} finally {
session.free();
final key = otk.tryGet<String>('key');
if (key != null) {
olmAccount.removeOneTimeKey(key);
}
}
@ -342,7 +329,6 @@ class OlmManager {
await _otkUpdateDedup.fetch(
() => runBenchmarked('handleOtkUpdate', () async {
final haveFallbackKeys = encryption.isMinOlmVersion(3, 2, 0);
// Check if there are at least half of max_number_of_one_time_keys left on the server
// and generate and upload more if not.
@ -357,7 +343,7 @@ class OlmManager {
}
// fixup accidental too many uploads. We delete only one of them so that the server has time to update the counts and because we will get rate limited anyway.
if (keyCount > _olmAccount!.max_number_of_one_time_keys()) {
if (keyCount > _olmAccount!.maxNumberOfOneTimeKeys) {
final requestingKeysFrom = {
client.userID!: {ourDeviceId!: 'signed_curve25519'},
};
@ -365,14 +351,13 @@ class OlmManager {
}
// Only upload keys if they are less than half of the max or we have no unused fallback key
if (keyCount < (_olmAccount!.max_number_of_one_time_keys() / 2) ||
if (keyCount < (_olmAccount!.maxNumberOfOneTimeKeys / 2) ||
!unusedFallbackKey) {
await uploadKeys(
oldKeyCount:
keyCount < (_olmAccount!.max_number_of_one_time_keys() / 2)
? keyCount
: null,
unusedFallbackKey: haveFallbackKeys ? unusedFallbackKey : null,
oldKeyCount: keyCount < (_olmAccount!.maxNumberOfOneTimeKeys / 2)
? keyCount
: null,
unusedFallbackKey: unusedFallbackKey,
);
}
}),
@ -449,26 +434,16 @@ class OlmManager {
if (session.session == null) {
continue;
}
if (type == 0 && session.session!.matches_inbound(body)) {
try {
plaintext = session.session!.decrypt(type, body);
} catch (e) {
// The message was encrypted during this session, but is unable to decrypt
throw DecryptException(
DecryptException.decryptionFailed,
e.toString(),
);
}
try {
plaintext = session.session!.decrypt(
messageType: type,
ciphertext: body,
);
await updateSessionUsage(session);
break;
} else if (type == 1) {
try {
plaintext = session.session!.decrypt(type, body);
await updateSessionUsage(session);
break;
} catch (_) {
plaintext = null;
}
} catch (_) {
plaintext = null;
}
}
}
@ -477,26 +452,27 @@ class OlmManager {
}
if (plaintext == null) {
final newSession = olm.Session();
try {
newSession.create_inbound_from(_olmAccount!, senderKey, body);
_olmAccount!.remove_one_time_keys(newSession);
await encryption.olmDatabase?.updateClientKeys(pickledOlmAccount!);
final result = _olmAccount!.createInboundSession(
theirIdentityKey: vod.Curve25519PublicKey.fromBase64(senderKey),
preKeyMessageBase64: body,
);
plaintext = result.plaintext;
final newSession = result.session;
plaintext = newSession.decrypt(type, body);
await encryption.olmDatabase?.updateClientKeys(pickledOlmAccount!);
await storeOlmSession(
OlmSession(
key: client.userID!,
identityKey: senderKey,
sessionId: newSession.session_id(),
sessionId: newSession.sessionId,
session: newSession,
lastReceived: DateTime.now(),
),
);
await updateSessionUsage();
} catch (e) {
newSession.free();
throw DecryptException(DecryptException.decryptionFailed, e.toString());
}
}
@ -660,27 +636,30 @@ class OlmManager {
continue;
}
Logs().v('[OlmManager] Starting session with $userId:$deviceId');
final session = olm.Session();
try {
session.create_outbound(
_olmAccount!,
identityKey,
deviceKey.tryGet<String>('key')!,
final session = _olmAccount!.createOutboundSession(
identityKey: vod.Curve25519PublicKey.fromBase64(identityKey),
oneTimeKey: vod.Curve25519PublicKey.fromBase64(
deviceKey.tryGet<String>('key')!,
),
);
await storeOlmSession(
OlmSession(
key: client.userID!,
identityKey: identityKey,
sessionId: session.session_id(),
sessionId: session.sessionId,
session: session,
lastReceived:
DateTime.now(), // we want to use a newly created session
),
);
} catch (e, s) {
session.free();
Logs()
.e('[LibOlm] Could not create new outbound olm session', e, s);
Logs().e(
'[Vodozemac] Could not create new outbound olm session',
e,
s,
);
}
}
}
@ -733,8 +712,8 @@ class OlmManager {
'ciphertext': <String, dynamic>{},
};
encryptedBody['ciphertext'][device.curve25519Key] = {
'type': encryptResult.type,
'body': encryptResult.body,
'type': encryptResult.messageType,
'body': encryptResult.ciphertext,
};
return encryptedBody;
}
@ -769,10 +748,10 @@ class OlmManager {
getFromDb: false,
);
} on NoOlmSessionFoundException catch (e) {
Logs().d('[LibOlm] Error encrypting to-device event', e);
Logs().d('[Vodozemac] Error encrypting to-device event', e);
continue;
} catch (e, s) {
Logs().wtf('[LibOlm] Error encrypting to-device event', e, s);
Logs().wtf('[Vodozemac] Error encrypting to-device event', e, s);
continue;
}
}
@ -820,13 +799,6 @@ class OlmManager {
Future<void> dispose() async {
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 'package:canonical_json/canonical_json.dart';
import 'package:olm/olm.dart' as olm;
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/encryption/encryption.dart';
import 'package:matrix/encryption/key_manager.dart';
import 'package:matrix/encryption/ssss.dart';
import 'package:matrix/encryption/utils/base64_unpadded.dart';
import 'package:matrix/matrix.dart';
enum BootstrapState {
@ -371,106 +370,82 @@ class Bootstrap {
}
final userID = client.userID!;
try {
Uint8List masterSigningKey;
String masterSigningKey;
final secretsToStore = <String, String>{};
MatrixCrossSigningKey? masterKey;
MatrixCrossSigningKey? selfSigningKey;
MatrixCrossSigningKey? userSigningKey;
String? masterPub;
if (setupMasterKey) {
final master = olm.PkSigning();
try {
masterSigningKey = master.generate_seed();
masterPub = master.init_with_seed(masterSigningKey);
final json = <String, dynamic>{
'user_id': userID,
'usage': ['master'],
'keys': <String, dynamic>{
'ed25519:$masterPub': masterPub,
},
};
masterKey = MatrixCrossSigningKey.fromJson(json);
secretsToStore[EventTypes.CrossSigningMasterKey] =
base64.encode(masterSigningKey);
} finally {
master.free();
}
final master = vod.PkSigning();
masterSigningKey = master.secretKey;
masterPub = master.publicKey.toBase64();
final json = <String, dynamic>{
'user_id': userID,
'usage': ['master'],
'keys': <String, dynamic>{
'ed25519:$masterPub': masterPub,
},
};
masterKey = MatrixCrossSigningKey.fromJson(json);
secretsToStore[EventTypes.CrossSigningMasterKey] = masterSigningKey;
} else {
Logs().v('Get stored key...');
masterSigningKey = base64decodeUnpadded(
await newSsssKey?.getStored(EventTypes.CrossSigningMasterKey) ?? '',
);
masterSigningKey =
await newSsssKey?.getStored(EventTypes.CrossSigningMasterKey) ?? '';
if (masterSigningKey.isEmpty) {
// no master signing key :(
throw BootstrapBadStateException('No master key');
}
final master = olm.PkSigning();
try {
masterPub = master.init_with_seed(masterSigningKey);
} finally {
master.free();
}
final master = vod.PkSigning.fromSecretKey(masterSigningKey);
masterPub = master.publicKey.toBase64();
}
String? sign(Map<String, dynamic> object) {
final keyObj = olm.PkSigning();
try {
keyObj.init_with_seed(masterSigningKey);
return keyObj
.sign(String.fromCharCodes(canonicalJson.encode(object)));
} finally {
keyObj.free();
}
final keyObj = vod.PkSigning.fromSecretKey(masterSigningKey);
return keyObj
.sign(String.fromCharCodes(canonicalJson.encode(object)))
.toBase64();
}
if (setupSelfSigningKey) {
final selfSigning = olm.PkSigning();
try {
final selfSigningPriv = selfSigning.generate_seed();
final selfSigningPub = selfSigning.init_with_seed(selfSigningPriv);
final json = <String, dynamic>{
'user_id': userID,
'usage': ['self_signing'],
'keys': <String, dynamic>{
'ed25519:$selfSigningPub': selfSigningPub,
},
};
final signature = sign(json);
json['signatures'] = <String, dynamic>{
userID: <String, dynamic>{
'ed25519:$masterPub': signature,
},
};
selfSigningKey = MatrixCrossSigningKey.fromJson(json);
secretsToStore[EventTypes.CrossSigningSelfSigning] =
base64.encode(selfSigningPriv);
} finally {
selfSigning.free();
}
final selfSigning = vod.PkSigning();
final selfSigningPriv = selfSigning.secretKey;
final selfSigningPub = selfSigning.publicKey.toBase64();
final json = <String, dynamic>{
'user_id': userID,
'usage': ['self_signing'],
'keys': <String, dynamic>{
'ed25519:$selfSigningPub': selfSigningPub,
},
};
final signature = sign(json);
json['signatures'] = <String, dynamic>{
userID: <String, dynamic>{
'ed25519:$masterPub': signature,
},
};
selfSigningKey = MatrixCrossSigningKey.fromJson(json);
secretsToStore[EventTypes.CrossSigningSelfSigning] = selfSigningPriv;
}
if (setupUserSigningKey) {
final userSigning = olm.PkSigning();
try {
final userSigningPriv = userSigning.generate_seed();
final userSigningPub = userSigning.init_with_seed(userSigningPriv);
final json = <String, dynamic>{
'user_id': userID,
'usage': ['user_signing'],
'keys': <String, dynamic>{
'ed25519:$userSigningPub': userSigningPub,
},
};
final signature = sign(json);
json['signatures'] = <String, dynamic>{
userID: <String, dynamic>{
'ed25519:$masterPub': signature,
},
};
userSigningKey = MatrixCrossSigningKey.fromJson(json);
secretsToStore[EventTypes.CrossSigningUserSigning] =
base64.encode(userSigningPriv);
} finally {
userSigning.free();
}
final userSigning = vod.PkSigning();
final userSigningPriv = userSigning.secretKey;
final userSigningPub = userSigning.publicKey.toBase64();
final json = <String, dynamic>{
'user_id': userID,
'usage': ['user_signing'],
'keys': <String, dynamic>{
'ed25519:$userSigningPub': userSigningPub,
},
};
final signature = sign(json);
json['signatures'] = <String, dynamic>{
userID: <String, dynamic>{
'ed25519:$masterPub': signature,
},
};
userSigningKey = MatrixCrossSigningKey.fromJson(json);
secretsToStore[EventTypes.CrossSigningUserSigning] = userSigningPriv;
}
// upload the keys!
state = BootstrapState.loading;
@ -561,15 +536,13 @@ class Bootstrap {
return;
}
try {
final keyObj = olm.PkDecryption();
final keyObj = vod.PkDecryption();
String pubKey;
Uint8List privKey;
try {
pubKey = keyObj.generate_key();
privKey = keyObj.get_private_key();
} finally {
keyObj.free();
}
pubKey = keyObj.publicKey;
privKey = keyObj.privateKey;
Logs().v('Create the new backup version...');
await client.postRoomKeysVersion(
BackupAlgorithm.mMegolmBackupV1Curve25519AesSha2,

View File

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

View File

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

View File

@ -16,17 +16,20 @@
* 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';
class OlmSession {
String identityKey;
String? sessionId;
olm.Session? session;
vod.Session? session;
DateTime? lastReceived;
final String key;
String? get pickledSession => session?.pickle(key);
String? get pickledSession => session?.toPickleEncrypted(key.toPickleKey());
bool get isValid => session != null;
@ -40,21 +43,25 @@ class OlmSession {
OlmSession.fromJson(Map<String, dynamic> dbEntry, this.key)
: identityKey = dbEntry['identity_key'] ?? '' {
session = olm.Session();
try {
session!.unpickle(key, dbEntry['pickle']);
try {
session = vod.Session.fromPickleEncrypted(
pickleKey: key.toPickleKey(),
pickle: dbEntry['pickle'],
);
} catch (_) {
Logs().d('Unable to unpickle Olm session. Try LibOlm format.');
session = vod.Session.fromOlmPickleEncrypted(
pickleKey: utf8.encode(key),
pickle: dbEntry['pickle'],
);
}
sessionId = dbEntry['session_id'];
lastReceived =
DateTime.fromMillisecondsSinceEpoch(dbEntry['last_received'] ?? 0);
assert(sessionId == session!.session_id());
assert(sessionId == session!.sessionId);
} catch (e, s) {
Logs().e('[LibOlm] Could not unpickle olm session', e, s);
dispose();
Logs().e('[Vodozemac] Could not unpickle olm session', e, s);
}
}
void dispose() {
session?.free();
session = null;
}
}

View File

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

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/>.
*/
import 'package:olm/olm.dart' as olm;
import 'dart:convert';
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/encryption/utils/pickle_key.dart';
import 'package:matrix/encryption/utils/stored_inbound_group_session.dart';
import 'package:matrix/matrix.dart';
@ -33,7 +36,7 @@ class SessionKey {
Map<String, Map<String, int>> allowedAtIndex;
/// Underlying olm [InboundGroupSession] object
olm.InboundGroupSession? inboundGroupSession;
vod.InboundGroupSession? inboundGroupSession;
/// Key for libolm pickle / unpickle
final String key;
@ -81,8 +84,7 @@ class SessionKey {
.catchMap((k, v) => MapEntry(k, Map<String, int>.from(v))),
roomId = dbEntry.roomId,
sessionId = dbEntry.sessionId,
senderKey = dbEntry.senderKey,
inboundGroupSession = olm.InboundGroupSession() {
senderKey = dbEntry.senderKey {
final parsedSenderClaimedKeys =
Event.getMapFromPayload(dbEntry.senderClaimedKeys)
.catchMap((k, v) => MapEntry<String, String>(k, v));
@ -99,15 +101,21 @@ class SessionKey {
: <String, String>{}));
try {
inboundGroupSession!.unpickle(key, dbEntry.pickle);
inboundGroupSession = vod.InboundGroupSession.fromPickleEncrypted(
pickle: dbEntry.pickle,
pickleKey: key.toPickleKey(),
);
} catch (e, s) {
dispose();
Logs().e('[LibOlm] Unable to unpickle inboundGroupSession', e, s);
try {
Logs().d('Unable to unpickle inboundGroupSession. Try LibOlm format.');
inboundGroupSession = vod.InboundGroupSession.fromOlmPickleEncrypted(
pickle: dbEntry.pickle,
pickleKey: utf8.encode(key),
);
} catch (_) {
Logs().e('[Vodozemac] Unable to unpickle inboundGroupSession', e, s);
rethrow;
}
}
}
void dispose() {
inboundGroupSession?.free();
inboundGroupSession = null;
}
}

View File

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

View File

@ -11,12 +11,3 @@ typedef ComputeRunner = Future<T> Function<T, U>(
FutureOr<T> Function(U arg) function,
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:collection/collection.dart' show IterableExtension;
import 'package:olm/olm.dart' as olm;
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/encryption.dart';
import 'package:matrix/matrix.dart';
@ -240,24 +240,17 @@ abstract class SignableKey extends MatrixSignableKey {
String signature, {
bool isSignatureWithoutLibolmValid = false,
}) {
olm.Utility olmutil;
try {
olmutil = olm.Utility();
} catch (e) {
// if no libolm is present we land in this catch block, and return the default
// set if no libolm is there. Some signatures should be assumed-valid while others
// should be assumed-invalid
return isSignatureWithoutLibolmValid;
}
var valid = false;
try {
olmutil.ed25519_verify(pubKey, signingContent, signature);
vod.Ed25519PublicKey.fromBase64(pubKey).verify(
message: signingContent,
signature: vod.Ed25519Signature.fromBase64(signature),
);
valid = true;
} catch (_) {
} catch (e) {
Logs().d('Invalid Ed25519 signature', e);
// bad signature
valid = false;
} finally {
olmutil.free();
}
return valid;
}

View File

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

View File

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

View File

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

View File

@ -1,35 +1,5 @@
#!/usr/bin/env bash
ENTRYPOINT="$(pwd)"
mkdir js
cd js
curl -O 'https://packages.matrix.org/npm/olm/olm-3.1.4.tgz'
tar xaf olm-3.1.4.tgz
cd ..
if [ -f /usr/lib/x86_64-linux-gnu/libolm.so.3 ]; then
mkdir -p ffi/olm/
ln -sf /usr/lib/x86_64-linux-gnu/libolm.so.3 ffi/olm/libolm.so
# alpine specific location
elif [ -f /usr/lib/libolm.so.3 ]; then
mkdir -p ffi/olm
ln -sf /usr/lib/libolm.so.3 ffi/olm/libolm.so
else
mkdir ffi
cd ffi
cd ..
git clone --depth 1 https://gitlab.matrix.org/matrix-org/olm.git
cd olm
cmake -DCMAKE_BUILD_TYPE=Release .
cmake --build .
cd ..
fi
cd "$ENTRYPOINT"
if which flutter >/dev/null; then
flutter pub get
else

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

View File

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

View File

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

View File

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

View File

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

View File

@ -16,8 +16,8 @@
* 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:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/matrix.dart';
import '../fake_client.dart';
@ -33,8 +33,11 @@ void main() {
final now = DateTime.now();
setUpAll(() async {
await olm.init();
olm.get_library_version();
await vod.init(
wasmPath: './pkg/',
libraryPath: './rust/target/debug/',
);
client = await getClient();
room = client.getRoomById(roomId)!;
});

View File

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

View File

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

View File

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

View File

@ -20,8 +20,8 @@ import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';
import 'package:olm/olm.dart' as olm;
import 'package:test/test.dart';
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/encryption.dart';
import 'package:matrix/matrix.dart';
@ -52,14 +52,16 @@ void main() async {
// key @othertest:fakeServer.notExisting
const otherPickledOlmAccount =
'VWhVApbkcilKAEGppsPDf9nNVjaK8/IxT3asSR0sYg0S5KgbfE8vXEPwoiKBX2cEvwX3OessOBOkk+ZE7TTbjlrh/KEd31p8Wo+47qj0AP+Ky+pabnhi+/rTBvZy+gfzTqUfCxZrkzfXI9Op4JnP6gYmy7dVX2lMYIIs9WCO1jcmIXiXum5jnfXu1WLfc7PZtO2hH+k9CDKosOFaXRBmsu8k/BGXPSoWqUpvu6WpEG9t5STk4FeAzA';
'0aFMkSgJhj0kVLxVnactRpl3L2kgIR8bAqICFtDkvp/mkinITZjr1Vh6Jy9FmJzvhLfFUtjU2j/2bqrFn61CSrvRbRaLP6rCFegGJHNGpVfw+c24NthCwGF/SN10aPjPo6yQ3er9bc42I6AmJz5HgyfU6C4bE+LdWrML93C0iEnmQN/SYHnS1KHPXNl6NpFGITggbZQ9jwHOFILWo8wzJ4iqlJtMrNaOOLAAB7By7Fbxl4xoNz2K+w';
late Client client1;
late Client client2;
setUpAll(() async {
await olm.init();
olm.get_library_version();
await vod.init(
wasmPath: './pkg/',
libraryPath: './rust/target/debug/',
);
});
setUp(() async {

View File

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

View File

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

View File

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

View File

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

View File

@ -17,10 +17,12 @@
*/
import 'dart:convert';
import 'dart:typed_data';
import 'package:test/test.dart';
import 'package:matrix/encryption/utils/base64_unpadded.dart';
import 'package:matrix/encryption/utils/pickle_key.dart';
import 'package:matrix/matrix.dart';
void main() {
@ -84,4 +86,27 @@ void main() {
);
});
});
group('toPickleKey', () {
test('toPickleKey', () {
const shortKey = 'abcd';
var pickleKey = shortKey.toPickleKey();
expect(pickleKey.length, 32, reason: 'Pickle key should be 32 bytes');
expect(
shortKey,
String.fromCharCodes(pickleKey.take(4)),
reason: 'Pickle key should match the first 32 bytes of the input',
);
const longKey =
'abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890';
pickleKey = longKey.toPickleKey();
expect(pickleKey.length, 32, reason: 'Pickle key should be 32 bytes');
expect(
pickleKey,
Uint8List.fromList(longKey.codeUnits.take(32).toList()),
reason: 'Pickle key should match the first 32 bytes of the input',
);
});
});
}

View File

@ -20,6 +20,7 @@ import 'dart:convert';
import 'dart:typed_data';
import 'package:test/test.dart';
import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/encryption.dart';
import 'package:matrix/matrix.dart';
@ -276,6 +277,15 @@ void main() async {
});
test('sendAgain', () async {
try {
vodInit ??= vod.init(
wasmPath: './pkg/',
libraryPath: './rust/target/debug/',
);
await vodInit;
} catch (_) {
Logs().d('Encryption via Vodozemac not enabled');
}
final matrix = Client(
'testclient',
httpClient: FakeMatrixApi(),

View File

@ -16,6 +16,8 @@
* 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 'fake_database.dart';
@ -24,13 +26,24 @@ const ssssKey = 'EsT9 RzbW VhPW yqNp cC7j ViiW 5TZB LuY4 ryyv 9guN Ysmr WDPH';
// key @test:fakeServer.notExisting
const pickledOlmAccount =
'N2v1MkIFGcl0mQpo2OCwSopxPQJ0wnl7oe7PKiT4141AijfdTIhRu+ceXzXKy3Kr00nLqXtRv7kid6hU4a+V0rfJWLL0Y51+3Rp/ORDVnQy+SSeo6Fn4FHcXrxifJEJ0djla5u98fBcJ8BSkhIDmtXRPi5/oJAvpiYn+8zMjFHobOeZUAxYR0VfQ9JzSYBsSovoQ7uFkNks1M4EDUvHtuyg3RxViwdNxs3718fyAqQ/VSwbXsY0Nl+qQbF+nlVGHenGqk5SuNl1P6e1PzZxcR0IfXA94Xij1Ob5gDv5YH4UCn9wRMG0abZsQP0YzpDM0FLaHSCyo9i5JD/vMlhH+nZWrgAzPPCTNGYewNV8/h3c+VyJh8ZTx/fVi6Yq46Fv+27Ga2ETRZ3Qn+Oyx6dLBjnBZ9iUvIhqpe2XqaGA1PopOz8iDnaZitw';
'huxcPifHlyiQsX7cZeMMITbka3hLeUT3ss6DLL6dV7knaD4wgAYK6gcWknkixnX8C5KMIyxzytxiNqAOhDFRE5NsET8hr2dQ8OvXX7M95eQ7/3dPi7FkPUIbvneTSGgJYNDxJdHsDJ8OBHZ3BoqUJFDbTzFfVJjEzN4G9XQwPDafZ2p5WyerOK8Twj/rvk5N+ERmkt1XgVLQl66we/BO1ugTeM3YpDHm5lTzFUitJGTIuuONsKG9mmzdAmVUJ9YIrSxwmOBdegbGA+LAl5acg5VOol3KxRgZUMJQRQ58zpBAs72oauHizv1QVoQ7uIUiCUeb9lym+TEjmApvhru/1CPHU90K5jHNZ57wb/4V9VsqBWuoNibzDWG35YTFLcx0o+1lrCIjm1QjuC0777G+L1HNw5wnppV3z/k0YujjuPS3wvOA30TjHg';
Future? vodInit;
/// only use `path` if you explicitly if you need a db on path instead of in mem
Future<Client> getClient({
Duration sendTimelineEventTimeout = const Duration(minutes: 1),
String? databasePath,
}) async {
try {
vodInit ??= vod.init(
wasmPath: './pkg/',
libraryPath: './rust/target/debug/',
);
await vodInit;
} catch (_) {
Logs().d('Encryption via Vodozemac not enabled');
}
final client = Client(
logLevel: Level.verbose,
'testclient',

View File

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

View File

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

View File

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

View File

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