feat: Migrate to vodozemac

This commit is contained in:
Christian Kußowski 2025-05-28 11:18:24 +02:00
parent 6cf6171b35
commit 31a32b0145
No known key found for this signature in database
GPG Key ID: E067ECD60F1A0652
25 changed files with 359 additions and 306 deletions

View File

@ -18,6 +18,11 @@ 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}}
@ -27,6 +32,7 @@ jobs:
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 libolm3 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

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

@ -21,6 +21,7 @@ import 'dart:convert';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:olm/olm.dart' as olm; 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';
@ -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) {
@ -669,54 +669,57 @@ 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);
if (info.algorithm != BackupAlgorithm.mMegolmBackupV1Curve25519AesSha2 || final decryption = vod.PkDecryption.fromSecretKey(
info.authData['public_key'] != backupPubKey) { vod.Curve25519PublicKey.fromBytes(privateKey),
return; );
} backupPubKey = decryption.publicKey;
for (final roomEntry in keys.rooms.entries) {
final roomId = roomEntry.key; if (info.algorithm != BackupAlgorithm.mMegolmBackupV1Curve25519AesSha2 ||
for (final sessionEntry in roomEntry.value.sessions.entries) { info.authData['public_key'] != backupPubKey) {
final sessionId = sessionEntry.key; return;
final session = sessionEntry.value; }
final sessionData = session.sessionData; for (final roomEntry in keys.rooms.entries) {
Map<String, Object?>? decrypted; final roomId = roomEntry.key;
try { for (final sessionEntry in roomEntry.value.sessions.entries) {
decrypted = json.decode( final sessionId = sessionEntry.key;
decryption.decrypt( final session = sessionEntry.value;
sessionData['ephemeral'] as String, final sessionData = session.sessionData;
sessionData['mac'] as String, Map<String, Object?>? decrypted;
sessionData['ciphertext'] as String, try {
decrypted = json.decode(
decryption.decrypt(
vod.PkMessage(
base64decodeUnpadded(sessionData['ciphertext'] as String),
base64decodeUnpadded(sessionData['mac'] as String),
vod.Curve25519PublicKey.fromBase64(
sessionData['ephemeral'] as String,
),
), ),
); ),
} catch (e, s) { );
Logs().e('[LibOlm] Error decrypting room key', e, s); } catch (e, s) {
} Logs().e('[LibOlm] Error decrypting room key', e, s);
final senderKey = decrypted?.tryGet<String>('sender_key'); }
if (decrypted != null && senderKey != null) { final senderKey = decrypted?.tryGet<String>('sender_key');
decrypted['session_id'] = sessionId; if (decrypted != null && senderKey != null) {
decrypted['room_id'] = roomId; decrypted['session_id'] = sessionId;
await setInboundGroupSession( decrypted['room_id'] = roomId;
roomId, await setInboundGroupSession(
sessionId, roomId,
senderKey, sessionId,
decrypted, senderKey,
forwarded: true, decrypted,
senderClaimedKeys: forwarded: true,
decrypted.tryGetMap<String, String>('sender_claimed_keys') ?? senderClaimedKeys:
<String, String>{}, decrypted.tryGetMap<String, String>('sender_claimed_keys') ??
uploaded: true, <String, String>{},
); uploaded: true,
} );
} }
} }
} finally {
decryption.free();
} }
} }
@ -875,57 +878,56 @@ 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);
if (info.algorithm != final decryption = vod.PkDecryption.fromSecretKey(
BackupAlgorithm.mMegolmBackupV1Curve25519AesSha2 || vod.Curve25519PublicKey.fromBytes(privateKey),
info.authData['public_key'] != backupPubKey) { );
decryption.free(); backupPubKey = decryption.publicKey;
return;
} if (info.algorithm !=
final args = GenerateUploadKeysArgs( BackupAlgorithm.mMegolmBackupV1Curve25519AesSha2 ||
pubkey: backupPubKey, info.authData['public_key'] != backupPubKey) {
dbSessions: <DbInboundGroupSessionBundle>[], return;
userId: userID, }
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 i++;
// with 500 keys they do, however, noticably block the UI, which is why we give brief async suspentions in here if (i > 10) {
// so that the event loop can progress await Future.delayed(Duration(milliseconds: 1));
var i = 0; 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;
}
} }
final roomKeys = }
await client.nativeImplementations.generateUploadKeys(args); final roomKeys =
Logs().i('[Key Manager] Uploading ${dbSessions.length} room keys...'); await client.nativeImplementations.generateUploadKeys(args);
// upload the payload... Logs().i('[Key Manager] Uploading ${dbSessions.length} room keys...');
await client.putRoomKeys(info.version, roomKeys); // upload the payload...
// and now finally mark all the keys as uploaded await client.putRoomKeys(info.version, roomKeys);
// no need to optimze this, as we only run it so seldomly and almost never with many keys at once // and now finally mark all the keys as uploaded
for (final dbSession in dbSessions) { // no need to optimze this, as we only run it so seldomly and almost never with many keys at once
await database.markInboundGroupSessionAsUploaded( for (final dbSession in dbSessions) {
dbSession.roomId, await database.markInboundGroupSessionAsUploaded(
dbSession.sessionId, dbSession.roomId,
); 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);
@ -1247,9 +1249,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) {
@ -1279,17 +1282,15 @@ RoomKeys generateUploadKeysImplementation(GenerateUploadKeysArgs args) {
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': encrypted.ephemeralKey.toBase64(),
'ciphertext': encrypted.ciphertext, 'ciphertext': base64Encode(encrypted.ciphertext),
'mac': encrypted.mac, 'mac': base64Encode(encrypted.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

@ -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,106 +370,82 @@ 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'], 'keys': <String, dynamic>{
'keys': <String, dynamic>{ 'ed25519:$masterPub': masterPub,
'ed25519:$masterPub': masterPub, },
}, };
}; masterKey = MatrixCrossSigningKey.fromJson(json);
masterKey = MatrixCrossSigningKey.fromJson(json); secretsToStore[EventTypes.CrossSigningMasterKey] = masterSigningKey;
secretsToStore[EventTypes.CrossSigningMasterKey] =
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 { return keyObj
keyObj.init_with_seed(masterSigningKey); .sign(String.fromCharCodes(canonicalJson.encode(object)))
return keyObj .toBase64();
.sign(String.fromCharCodes(canonicalJson.encode(object)));
} finally {
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'], 'keys': <String, dynamic>{
'keys': <String, dynamic>{ 'ed25519:$selfSigningPub': selfSigningPub,
'ed25519:$selfSigningPub': selfSigningPub, },
}, };
}; final signature = sign(json);
final signature = sign(json); json['signatures'] = <String, dynamic>{
json['signatures'] = <String, dynamic>{ userID: <String, dynamic>{
userID: <String, dynamic>{ 'ed25519:$masterPub': signature,
'ed25519:$masterPub': signature, },
}, };
}; selfSigningKey = MatrixCrossSigningKey.fromJson(json);
selfSigningKey = MatrixCrossSigningKey.fromJson(json); secretsToStore[EventTypes.CrossSigningSelfSigning] = selfSigningPriv;
secretsToStore[EventTypes.CrossSigningSelfSigning] =
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'], 'keys': <String, dynamic>{
'keys': <String, dynamic>{ 'ed25519:$userSigningPub': userSigningPub,
'ed25519:$userSigningPub': userSigningPub, },
}, };
}; final signature = sign(json);
final signature = sign(json); json['signatures'] = <String, dynamic>{
json['signatures'] = <String, dynamic>{ userID: <String, dynamic>{
userID: <String, dynamic>{ 'ed25519:$masterPub': signature,
'ed25519:$masterPub': signature, },
}, };
}; userSigningKey = MatrixCrossSigningKey.fromJson(json);
userSigningKey = MatrixCrossSigningKey.fromJson(json); secretsToStore[EventTypes.CrossSigningUserSigning] = userSigningPriv;
secretsToStore[EventTypes.CrossSigningUserSigning] =
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('[LibOlm] 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,31 @@ 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']); 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 +1485,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 +1551,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.encode(pubKey + canonicalJson);
final ret = olmutil.sha256(pubKey + canonicalJson); final digest = crypto.sha256.convert(bytes);
olmutil.free(); return base64.encode(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

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

@ -31,6 +31,11 @@ dependencies:
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:
git:
url: https://github.com/famedly/dart-vodozemac.git
path: dart
ref: main
webrtc_interface: ^1.2.0 webrtc_interface: ^1.2.0
dev_dependencies: dev_dependencies:

8
scripts/prepare_vodozemac.sh Executable file
View File

@ -0,0 +1,8 @@
#!/usr/bin/env bash
git clone https://github.com/famedly/dart-vodozemac.git
mv ./dart-vodozemac/rust ./
rm -rf dart-vodozemac
cd ./rust
cargo build
cd ..

View File

@ -26,6 +26,7 @@ import 'package:collection/collection.dart';
import 'package:olm/olm.dart' as olm; 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';
@ -67,11 +68,15 @@ void main() {
group('client mem', tags: 'olm', () { group('client mem', tags: 'olm', () {
late Client matrix; late Client matrix;
Logs().level = Level.error; Future? vodInit;
/// Check if all Elements get created /// Check if all Elements get created
setUp(() async { setUp(() async {
vodInit ??= vod.init(
wasmPath: './pkg/',
libraryPath: './rust/target/debug/',
);
await vodInit;
matrix = await getClient(); matrix = await getClient();
}); });

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

@ -21,6 +21,7 @@ import 'dart:convert';
import 'package:olm/olm.dart' as olm; 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,6 +36,10 @@ void main() {
late String origKeyId; late String origKeyId;
setUpAll(() async { setUpAll(() async {
await vod.init(
wasmPath: './pkg/',
libraryPath: './rust/target/debug/',
);
await olm.init(); await olm.init();
olm.get_library_version(); olm.get_library_version();
client = await getClient(); client = await getClient();
@ -75,20 +80,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 +134,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 +189,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

@ -20,6 +20,7 @@ import 'dart:convert';
import 'package:olm/olm.dart' as olm; 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,6 +32,10 @@ void main() {
late Client client; late Client client;
setUpAll(() async { setUpAll(() async {
await vod.init(
wasmPath: './pkg/',
libraryPath: './rust/target/debug/',
);
await olm.init(); await olm.init();
olm.get_library_version(); olm.get_library_version();
client = await getClient(); client = await getClient();

View File

@ -18,6 +18,7 @@
import 'package:olm/olm.dart' as olm; 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,6 +34,10 @@ void main() {
final now = DateTime.now(); final now = DateTime.now();
setUpAll(() async { setUpAll(() async {
await vod.init(
wasmPath: './pkg/',
libraryPath: './rust/target/debug/',
);
await olm.init(); await olm.init();
olm.get_library_version(); olm.get_library_version();
client = await getClient(); client = await getClient();

View File

@ -18,6 +18,7 @@
import 'package:olm/olm.dart' as olm; 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';
@ -41,6 +42,10 @@ void main() async {
late Map<String, dynamic> payload; late Map<String, dynamic> payload;
setUpAll(() async { setUpAll(() async {
await vod.init(
wasmPath: './pkg/',
libraryPath: './rust/target/debug/',
);
await olm.init(); await olm.init();
olm.get_library_version(); olm.get_library_version();
client = await getClient(); client = await getClient();

View File

@ -20,6 +20,7 @@ import 'dart:convert';
import 'package:olm/olm.dart' as olm; 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,6 +31,10 @@ void main() {
late Client client; late Client client;
setUpAll(() async { setUpAll(() async {
await vod.init(
wasmPath: './pkg/',
libraryPath: './rust/target/debug/',
);
await olm.init(); await olm.init();
olm.get_library_version(); olm.get_library_version();
client = await getClient(); client = await getClient();

View File

@ -20,6 +20,7 @@ import 'dart:convert';
import 'package:olm/olm.dart' as olm; 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,6 +43,10 @@ void main() {
Logs().level = Level.error; Logs().level = Level.error;
setUpAll(() async { setUpAll(() async {
await vod.init(
wasmPath: './pkg/',
libraryPath: './rust/target/debug/',
);
await olm.init(); await olm.init();
olm.get_library_version(); olm.get_library_version();
}); });

View File

@ -22,6 +22,7 @@ import 'dart:typed_data';
import 'package:olm/olm.dart' as olm; 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';
@ -58,6 +59,10 @@ void main() async {
late Client client2; late Client client2;
setUpAll(() async { setUpAll(() async {
await vod.init(
wasmPath: './pkg/',
libraryPath: './rust/target/debug/',
);
await olm.init(); await olm.init();
olm.get_library_version(); olm.get_library_version();
}); });

View File

@ -20,6 +20,7 @@ import 'dart:convert';
import 'package:olm/olm.dart' as olm; 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,6 +33,10 @@ void main() {
late Client client; late Client client;
setUpAll(() async { setUpAll(() async {
await vod.init(
wasmPath: './pkg/',
libraryPath: './rust/target/debug/',
);
await olm.init(); await olm.init();
olm.get_library_version(); olm.get_library_version();
client = await getClient(); client = await getClient();

View File

@ -20,6 +20,7 @@ import 'dart:convert';
import 'package:olm/olm.dart' as olm; 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,6 +36,10 @@ void main() {
final senderKey = 'JBG7ZaPn54OBC7TuIEiylW3BZ+7WcGQhFBPB9pogbAg'; final senderKey = 'JBG7ZaPn54OBC7TuIEiylW3BZ+7WcGQhFBPB9pogbAg';
setUpAll(() async { setUpAll(() async {
await vod.init(
wasmPath: './pkg/',
libraryPath: './rust/target/debug/',
);
await olm.init(); await olm.init();
olm.get_library_version(); olm.get_library_version();
client = await getClient(); client = await getClient();

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

@ -22,6 +22,7 @@ import 'dart:typed_data';
import 'package:olm/olm.dart' as olm; 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,6 +55,10 @@ void main() {
late Client client; late Client client;
setUpAll(() async { setUpAll(() async {
await vod.init(
wasmPath: './pkg/',
libraryPath: './rust/target/debug/',
);
await olm.init(); await olm.init();
olm.get_library_version(); olm.get_library_version();
client = await getClient(); client = await getClient();

View File

@ -22,6 +22,7 @@ import 'dart:math';
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';
@ -671,12 +672,20 @@ void main() {
expect(fetchedParticipants.length, newParticipants.length); expect(fetchedParticipants.length, newParticipants.length);
}); });
test('calcEncryptionHealthState', () async { test(
expect( 'calcEncryptionHealthState',
await room.calcEncryptionHealthState(), () async {
EncryptionHealthState.unverifiedDevices, await vod.init(
); wasmPath: './pkg/',
}); libraryPath: './rust/target/debug/',
);
expect(
await room.calcEncryptionHealthState(),
EncryptionHealthState.allVerified,
);
},
tags: 'olm',
);
test('getEventByID', () async { test('getEventByID', () async {
final event = await room.getEventById('1234'); final event = await room.getEventById('1234');

View File

@ -20,6 +20,7 @@ import 'dart:io';
import 'package:olm/olm.dart' as olm; 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,6 +40,10 @@ void main() => group(
Client? testClientA, testClientB; Client? testClientA, testClientB;
try { try {
await vod.init(
wasmPath: './pkg/',
libraryPath: './rust/target/debug/',
);
await olm.init(); await olm.init();
olm.Account(); olm.Account();
Logs().i('[LibOlm] Enabled'); Logs().i('[LibOlm] Enabled');