refactor: Migrate olm account to vodozemac

This commit is contained in:
Christian Kußowski 2025-05-30 14:22:53 +02:00
parent 98fcd683a6
commit 5fdcbf8006
No known key found for this signature in database
GPG Key ID: E067ECD60F1A0652
13 changed files with 153 additions and 151 deletions

View File

@ -19,7 +19,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'package:olm/olm.dart' as olm; import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/encryption/cross_signing.dart'; import 'package:matrix/encryption/cross_signing.dart';
import 'package:matrix/encryption/key_manager.dart'; import 'package:matrix/encryption/key_manager.dart';
@ -87,18 +87,6 @@ class Encryption {
if (!isDehydratedDevice) keyManager.startAutoUploadKeys(); if (!isDehydratedDevice) keyManager.startAutoUploadKeys();
} }
bool isMinOlmVersion(int major, int minor, int patch) {
try {
final version = olm.get_library_version();
return version[0] > major ||
(version[0] == major &&
(version[1] > minor ||
(version[1] == minor && version[2] >= patch)));
} catch (_) {
return false;
}
}
Bootstrap bootstrap({void Function(Bootstrap)? onUpdate}) => Bootstrap( Bootstrap bootstrap({void Function(Bootstrap)? onUpdate}) => Bootstrap(
encryption: this, encryption: this,
onUpdate: onUpdate, onUpdate: onUpdate,

View File

@ -21,11 +21,12 @@ import 'dart:convert';
import 'package:async/async.dart'; import 'package:async/async.dart';
import 'package:canonical_json/canonical_json.dart'; import 'package:canonical_json/canonical_json.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:olm/olm.dart' as olm; import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/encryption/encryption.dart'; import 'package:matrix/encryption/encryption.dart';
import 'package:matrix/encryption/utils/json_signature_check_extension.dart'; import 'package:matrix/encryption/utils/json_signature_check_extension.dart';
import 'package:matrix/encryption/utils/olm_session.dart'; import 'package:matrix/encryption/utils/olm_session.dart';
import 'package:matrix/encryption/utils/pickle_key.dart';
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
import 'package:matrix/msc_extensions/msc_3814_dehydrated_devices/api.dart'; import 'package:matrix/msc_extensions/msc_3814_dehydrated_devices/api.dart';
import 'package:matrix/src/utils/run_benchmarked.dart'; import 'package:matrix/src/utils/run_benchmarked.dart';
@ -34,20 +35,24 @@ import 'package:matrix/src/utils/run_in_root.dart';
class OlmManager { class OlmManager {
final Encryption encryption; final Encryption encryption;
Client get client => encryption.client; Client get client => encryption.client;
olm.Account? _olmAccount; vod.Account? _olmAccount;
String? ourDeviceId; String? ourDeviceId;
/// Returns the base64 encoded keys to store them in a store. /// Returns the base64 encoded keys to store them in a store.
/// This String should **never** leave the device! /// This String should **never** leave the device!
String? get pickledOlmAccount => String? get pickledOlmAccount {
enabled ? _olmAccount!.pickle(client.userID!) : null; return enabled
? _olmAccount!.toPickleEncrypted(client.userID!.toPickleKey())
: null;
}
String? get fingerprintKey => String? get fingerprintKey =>
enabled ? json.decode(_olmAccount!.identity_keys())['ed25519'] : null; enabled ? _olmAccount!.identityKeys.ed25519.toBase64() : null;
String? get identityKey => String? get identityKey =>
enabled ? json.decode(_olmAccount!.identity_keys())['curve25519'] : null; enabled ? _olmAccount!.identityKeys.curve25519.toBase64() : null;
String? pickleOlmAccountWithKey(String key) => String? pickleOlmAccountWithKey(String key) =>
enabled ? _olmAccount!.pickle(key) : null; enabled ? _olmAccount!.toPickleEncrypted(key.toPickleKey()) : null;
bool get enabled => _olmAccount != null; bool get enabled => _olmAccount != null;
@ -66,10 +71,7 @@ class OlmManager {
}) async { }) async {
ourDeviceId = deviceId; ourDeviceId = deviceId;
if (olmAccount == null) { if (olmAccount == null) {
try { _olmAccount = vod.Account();
await olm.init();
_olmAccount = olm.Account();
_olmAccount!.create();
if (!await uploadKeys( if (!await uploadKeys(
uploadDeviceKeys: true, uploadDeviceKeys: true,
updateDatabase: false, updateDatabase: false,
@ -79,20 +81,21 @@ class OlmManager {
)) { )) {
throw ('Upload key failed'); throw ('Upload key failed');
} }
} catch (_) {
_olmAccount?.free();
_olmAccount = null;
rethrow;
}
} else { } else {
try { try {
await olm.init(); _olmAccount = vod.Account.fromPickleEncrypted(
_olmAccount = olm.Account(); pickle: olmAccount,
_olmAccount!.unpickle(pickleKey ?? client.userID!, olmAccount); pickleKey: (pickleKey ?? client.userID!).toPickleKey(),
} catch (_) { );
_olmAccount?.free(); } catch (e) {
_olmAccount = null; Logs().d(
rethrow; 'Unable to unpickle account in vodozemac format. Trying Olm format...',
e,
);
_olmAccount = vod.Account.fromOlmPickleEncrypted(
pickle: olmAccount,
pickleKey: utf8.encode(pickleKey ?? client.userID!),
);
} }
} }
} }
@ -115,7 +118,8 @@ class OlmManager {
if (!payload['signatures'].containsKey(client.userID)) { if (!payload['signatures'].containsKey(client.userID)) {
payload['signatures'][client.userID] = <String, dynamic>{}; payload['signatures'][client.userID] = <String, dynamic>{};
} }
payload['signatures'][client.userID]['ed25519:$ourDeviceId'] = signature; payload['signatures'][client.userID]['ed25519:$ourDeviceId'] =
signature.toBase64();
if (unsigned != null) { if (unsigned != null) {
payload['unsigned'] = unsigned; payload['unsigned'] = unsigned;
} }
@ -123,13 +127,13 @@ class OlmManager {
} }
String signString(String s) { String signString(String s) {
return _olmAccount!.sign(s); return _olmAccount!.sign(s).toBase64();
} }
bool _uploadKeysLock = false; bool _uploadKeysLock = false;
CancelableOperation<Map<String, int>>? currentUpload; CancelableOperation<Map<String, int>>? currentUpload;
int? get maxNumberOfOneTimeKeys => _olmAccount?.max_number_of_one_time_keys(); int? get maxNumberOfOneTimeKeys => _olmAccount?.maxNumberOfOneTimeKeys;
/// Generates new one time keys, signs everything and upload it to the server. /// Generates new one time keys, signs everything and upload it to the server.
/// If `retry` is > 0, the request will be retried with new OTKs on upload failure. /// If `retry` is > 0, the request will be retried with new OTKs on upload failure.
@ -158,26 +162,24 @@ class OlmManager {
if (oldKeyCount != null) { if (oldKeyCount != null) {
// check if we have OTKs that still need uploading. If we do, we don't try to generate new ones, // check if we have OTKs that still need uploading. If we do, we don't try to generate new ones,
// instead we try to upload the old ones first // instead we try to upload the old ones first
final oldOTKsNeedingUpload = json final oldOTKsNeedingUpload = olmAccount.oneTimeKeys.length;
.decode(olmAccount.one_time_keys())['curve25519']
.entries
.length as int;
// generate one-time keys // generate one-time keys
// we generate 2/3rds of max, so that other keys people may still have can // we generate 2/3rds of max, so that other keys people may still have can
// still be used // still be used
final oneTimeKeysCount = final oneTimeKeysCount =
(olmAccount.max_number_of_one_time_keys() * 2 / 3).floor() - (olmAccount.maxNumberOfOneTimeKeys * 2 / 3).floor() -
oldKeyCount - oldKeyCount -
oldOTKsNeedingUpload; oldOTKsNeedingUpload;
if (oneTimeKeysCount > 0) { if (oneTimeKeysCount > 0) {
olmAccount.generate_one_time_keys(oneTimeKeysCount); olmAccount.generateOneTimeKeys(oneTimeKeysCount);
} }
uploadedOneTimeKeysCount = oneTimeKeysCount + oldOTKsNeedingUpload; uploadedOneTimeKeysCount = oneTimeKeysCount + oldOTKsNeedingUpload;
} }
if (encryption.isMinOlmVersion(3, 2, 7) && unusedFallbackKey == false) { if (unusedFallbackKey == false) {
// we don't have an unused fallback key uploaded....so let's change that! // we don't have an unused fallback key uploaded....so let's change that!
olmAccount.generate_fallback_key(); olmAccount.generateFallbackKey();
} }
// we save the generated OTKs into the database. // we save the generated OTKs into the database.
@ -199,39 +201,33 @@ class OlmManager {
}; };
if (uploadDeviceKeys) { if (uploadDeviceKeys) {
final Map<String, dynamic> keys = final keys = olmAccount.identityKeys;
json.decode(olmAccount.identity_keys()); deviceKeys['keys']['curve25519:$ourDeviceId'] =
for (final entry in keys.entries) { keys.curve25519.toBase64();
final algorithm = entry.key; deviceKeys['keys']['ed25519:$ourDeviceId'] = keys.ed25519.toBase64();
final value = entry.value;
deviceKeys['keys']['$algorithm:$ourDeviceId'] = value;
}
deviceKeys = signJson(deviceKeys); deviceKeys = signJson(deviceKeys);
} }
// now sign all the one-time keys // now sign all the one-time keys
for (final entry for (final entry in olmAccount.oneTimeKeys.entries) {
in json.decode(olmAccount.one_time_keys())['curve25519'].entries) {
final key = entry.key; final key = entry.key;
final value = entry.value; final value = entry.value.toBase64();
signedOneTimeKeys['signed_curve25519:$key'] = signJson({ signedOneTimeKeys['signed_curve25519:$key'] = signJson({
'key': value, 'key': value,
}); });
} }
final signedFallbackKeys = <String, dynamic>{}; final signedFallbackKeys = <String, dynamic>{};
if (encryption.isMinOlmVersion(3, 2, 7)) { final fallbackKey = olmAccount.fallbackKey;
final fallbackKey = json.decode(olmAccount.unpublished_fallback_key());
// now sign all the fallback keys // now sign all the fallback keys
for (final entry in fallbackKey['curve25519'].entries) { for (final entry in fallbackKey.entries) {
final key = entry.key; final key = entry.key;
final value = entry.value; final value = entry.value.toBase64();
signedFallbackKeys['signed_curve25519:$key'] = signJson({ signedFallbackKeys['signed_curve25519:$key'] = signJson({
'key': value, 'key': value,
'fallback': true, 'fallback': true,
}); });
} }
}
if (signedFallbackKeys.isEmpty && if (signedFallbackKeys.isEmpty &&
signedOneTimeKeys.isEmpty && signedOneTimeKeys.isEmpty &&
@ -281,7 +277,7 @@ class OlmManager {
} }
// mark the OTKs as published and save that to datbase // mark the OTKs as published and save that to datbase
olmAccount.mark_keys_as_published(); olmAccount.markKeysAsPublished();
if (updateDatabase) { if (updateDatabase) {
await encryption.olmDatabase?.updateClientKeys(pickledOlmAccount!); await encryption.olmDatabase?.updateClientKeys(pickledOlmAccount!);
} }
@ -301,17 +297,14 @@ class OlmManager {
Logs().w('Rotating otks because upload failed', exception); Logs().w('Rotating otks because upload failed', exception);
for (final otk in signedOneTimeKeys.values) { for (final otk in signedOneTimeKeys.values) {
// Keys can only be removed by creating a session... // Keys can only be removed by creating a session...
final session = olm.Session();
try { final identity = olmAccount.identityKeys.curve25519.toBase64();
final String identity =
json.decode(olmAccount.identity_keys())['curve25519'];
final key = otk.tryGet<String>('key'); final key = otk.tryGet<String>('key');
if (key != null) { if (key != null) {
session.create_outbound(_olmAccount!, identity, key); olmAccount.createOutboundSession(
olmAccount.remove_one_time_keys(session); identityKey: vod.Curve25519PublicKey.fromBase64(identity),
} oneTimeKey: vod.Curve25519PublicKey.fromBase64(key),
} finally { );
session.free();
} }
} }
@ -342,7 +335,6 @@ class OlmManager {
await _otkUpdateDedup.fetch( await _otkUpdateDedup.fetch(
() => runBenchmarked('handleOtkUpdate', () async { () => runBenchmarked('handleOtkUpdate', () async {
final haveFallbackKeys = encryption.isMinOlmVersion(3, 2, 0);
// Check if there are at least half of max_number_of_one_time_keys left on the server // Check if there are at least half of max_number_of_one_time_keys left on the server
// and generate and upload more if not. // and generate and upload more if not.
@ -357,7 +349,7 @@ class OlmManager {
} }
// fixup accidental too many uploads. We delete only one of them so that the server has time to update the counts and because we will get rate limited anyway. // fixup accidental too many uploads. We delete only one of them so that the server has time to update the counts and because we will get rate limited anyway.
if (keyCount > _olmAccount!.max_number_of_one_time_keys()) { if (keyCount > _olmAccount!.maxNumberOfOneTimeKeys) {
final requestingKeysFrom = { final requestingKeysFrom = {
client.userID!: {ourDeviceId!: 'signed_curve25519'}, client.userID!: {ourDeviceId!: 'signed_curve25519'},
}; };
@ -365,14 +357,13 @@ class OlmManager {
} }
// Only upload keys if they are less than half of the max or we have no unused fallback key // Only upload keys if they are less than half of the max or we have no unused fallback key
if (keyCount < (_olmAccount!.max_number_of_one_time_keys() / 2) || if (keyCount < (_olmAccount!.maxNumberOfOneTimeKeys / 2) ||
!unusedFallbackKey) { !unusedFallbackKey) {
await uploadKeys( await uploadKeys(
oldKeyCount: oldKeyCount: keyCount < (_olmAccount!.maxNumberOfOneTimeKeys / 2)
keyCount < (_olmAccount!.max_number_of_one_time_keys() / 2)
? keyCount ? keyCount
: null, : null,
unusedFallbackKey: haveFallbackKeys ? unusedFallbackKey : null, unusedFallbackKey: unusedFallbackKey,
); );
} }
}), }),
@ -449,9 +440,12 @@ class OlmManager {
if (session.session == null) { if (session.session == null) {
continue; continue;
} }
if (type == 0 && session.session!.matches_inbound(body)) { if (type == 0) {
try { try {
plaintext = session.session!.decrypt(type, body); plaintext = session.session!.decrypt(
messageType: type,
ciphertext: body,
);
} catch (e) { } catch (e) {
// The message was encrypted during this session, but is unable to decrypt // The message was encrypted during this session, but is unable to decrypt
throw DecryptException( throw DecryptException(
@ -463,7 +457,10 @@ class OlmManager {
break; break;
} else if (type == 1) { } else if (type == 1) {
try { try {
plaintext = session.session!.decrypt(type, body); plaintext = session.session!.decrypt(
messageType: type,
ciphertext: body,
);
await updateSessionUsage(session); await updateSessionUsage(session);
break; break;
} catch (_) { } catch (_) {
@ -477,26 +474,27 @@ class OlmManager {
} }
if (plaintext == null) { if (plaintext == null) {
final newSession = olm.Session();
try { try {
newSession.create_inbound_from(_olmAccount!, senderKey, body); final result = _olmAccount!.createInboundSession(
_olmAccount!.remove_one_time_keys(newSession); theirIdentityKey: vod.Curve25519PublicKey.fromBase64(senderKey),
await encryption.olmDatabase?.updateClientKeys(pickledOlmAccount!); preKeyMessageBase64: body,
);
plaintext = result.plaintext;
final newSession = result.session;
plaintext = newSession.decrypt(type, body); await encryption.olmDatabase?.updateClientKeys(pickledOlmAccount!);
await storeOlmSession( await storeOlmSession(
OlmSession( OlmSession(
key: client.userID!, key: client.userID!,
identityKey: senderKey, identityKey: senderKey,
sessionId: newSession.session_id(), sessionId: newSession.sessionId,
session: newSession, session: newSession,
lastReceived: DateTime.now(), lastReceived: DateTime.now(),
), ),
); );
await updateSessionUsage(); await updateSessionUsage();
} catch (e) { } catch (e) {
newSession.free();
throw DecryptException(DecryptException.decryptionFailed, e.toString()); throw DecryptException(DecryptException.decryptionFailed, e.toString());
} }
} }
@ -660,25 +658,25 @@ class OlmManager {
continue; continue;
} }
Logs().v('[OlmManager] Starting session with $userId:$deviceId'); Logs().v('[OlmManager] Starting session with $userId:$deviceId');
final session = olm.Session();
try { try {
session.create_outbound( final session = _olmAccount!.createOutboundSession(
_olmAccount!, identityKey: vod.Curve25519PublicKey.fromBase64(identityKey),
identityKey, oneTimeKey: vod.Curve25519PublicKey.fromBase64(
deviceKey.tryGet<String>('key')!, deviceKey.tryGet<String>('key')!,
),
); );
await storeOlmSession( await storeOlmSession(
OlmSession( OlmSession(
key: client.userID!, key: client.userID!,
identityKey: identityKey, identityKey: identityKey,
sessionId: session.session_id(), sessionId: session.sessionId,
session: session, session: session,
lastReceived: lastReceived:
DateTime.now(), // we want to use a newly created session DateTime.now(), // we want to use a newly created session
), ),
); );
} catch (e, s) { } catch (e, s) {
session.free();
Logs() Logs()
.e('[LibOlm] Could not create new outbound olm session', e, s); .e('[LibOlm] Could not create new outbound olm session', e, s);
} }
@ -733,8 +731,8 @@ class OlmManager {
'ciphertext': <String, dynamic>{}, 'ciphertext': <String, dynamic>{},
}; };
encryptedBody['ciphertext'][device.curve25519Key] = { encryptedBody['ciphertext'][device.curve25519Key] = {
'type': encryptResult.type, 'type': encryptResult.messageType,
'body': encryptResult.body, 'body': encryptResult.ciphertext,
}; };
return encryptedBody; return encryptedBody;
} }
@ -820,13 +818,6 @@ class OlmManager {
Future<void> dispose() async { Future<void> dispose() async {
await currentUpload?.cancel(); await currentUpload?.cancel();
for (final sessions in olmSessions.values) {
for (final sess in sessions) {
sess.dispose();
}
}
_olmAccount?.free();
_olmAccount = null;
} }
} }

View File

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

View File

@ -1,4 +1,5 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:developer';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:http/http.dart'; import 'package:http/http.dart';

View File

@ -26,7 +26,6 @@ import 'package:async/async.dart';
import 'package:collection/collection.dart' show IterableExtension; import 'package:collection/collection.dart' show IterableExtension;
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:mime/mime.dart'; import 'package:mime/mime.dart';
import 'package:olm/olm.dart' as olm;
import 'package:random_string/random_string.dart'; import 'package:random_string/random_string.dart';
import 'package:matrix/encryption.dart'; import 'package:matrix/encryption.dart';
@ -2102,9 +2101,6 @@ class Client extends MatrixApi {
await encryption?.dispose(); await encryption?.dispose();
try { try {
// make sure to throw an exception if libolm doesn't exist
await olm.init();
olm.get_library_version();
_encryption = Encryption(client: this); _encryption = Encryption(client: this);
} catch (e) { } catch (e) {
Logs().e('Error initializing encryption $e'); Logs().e('Error initializing encryption $e');

View File

@ -26,7 +26,6 @@ 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';
@ -36,7 +35,7 @@ import 'fake_database.dart';
void main() { void main() {
// key @test:fakeServer.notExisting // key @test:fakeServer.notExisting
const pickledOlmAccount = const pickledOlmAccount =
'N2v1MkIFGcl0mQpo2OCwSopxPQJ0wnl7oe7PKiT4141AijfdTIhRu+ceXzXKy3Kr00nLqXtRv7kid6hU4a+V0rfJWLL0Y51+3Rp/ORDVnQy+SSeo6Fn4FHcXrxifJEJ0djla5u98fBcJ8BSkhIDmtXRPi5/oJAvpiYn+8zMjFHobOeZUAxYR0VfQ9JzSYBsSovoQ7uFkNks1M4EDUvHtuyg3RxViwdNxs3718fyAqQ/VSwbXsY0Nl+qQbF+nlVGHenGqk5SuNl1P6e1PzZxcR0IfXA94Xij1Ob5gDv5YH4UCn9wRMG0abZsQP0YzpDM0FLaHSCyo9i5JD/vMlhH+nZWrgAzPPCTNGYewNV8/h3c+VyJh8ZTx/fVi6Yq46Fv+27Ga2ETRZ3Qn+Oyx6dLBjnBZ9iUvIhqpe2XqaGA1PopOz8iDnaZitw'; 'huxcPifHlyiQsX7cZeMMITbka3hLeUT3ss6DLL6dV7knaD4wgAYK6gcWknkixnX8C5KMIyxzytxiNqAOhDFRE5NsET8hr2dQ8OvXX7M95eQ7/3dPi7FkPUIbvneTSGgJYNDxJdHsDJ8OBHZ3BoqUJFDbTzFfVJjEzN4G9XQwPDafZ2p5WyerOK8Twj/rvk5N+ERmkt1XgVLQl66we/BO1ugTeM3YpDHm5lTzFUitJGTIuuONsKG9mmzdAmVUJ9YIrSxwmOBdegbGA+LAl5acg5VOol3KxRgZUMJQRQ58zpBAs72oauHizv1QVoQ7uIUiCUeb9lym+TEjmApvhru/1CPHU90K5jHNZ57wb/4V9VsqBWuoNibzDWG35YTFLcx0o+1lrCIjm1QjuC0777G+L1HNw5wnppV3z/k0YujjuPS3wvOA30TjHg';
const identityKey = '7rvl3jORJkBiK4XX1e5TnGnqz068XfYJ0W++Ml63rgk'; const identityKey = '7rvl3jORJkBiK4XX1e5TnGnqz068XfYJ0W++Ml63rgk';
const fingerprintKey = 'gjL//fyaFHADt9KBADGag8g7F8Up78B/K1zXeiEPLJo'; const fingerprintKey = 'gjL//fyaFHADt9KBADGag8g7F8Up78B/K1zXeiEPLJo';
@ -1752,7 +1751,10 @@ void main() {
expect(error.homeserver, Uri.parse('https://test.server')); expect(error.homeserver, Uri.parse('https://test.server'));
expect(error.olmAccount, 'abcd'); expect(error.olmAccount, 'abcd');
expect(error.userId, '@user:server'); expect(error.userId, '@user:server');
expect(error.toString(), 'Exception: BAD_ACCOUNT_KEY'); expect(
error.originalException.runtimeType.toString(),
'AnyhowException',
);
} }
await customClient.dispose(closeDatabase: true); await customClient.dispose(closeDatabase: true);
}, },

View File

@ -25,10 +25,8 @@ import '../fake_client.dart';
import '../fake_database.dart'; import '../fake_database.dart';
void main() async { void main() async {
// key @othertest:fakeServer.notExisting
const otherPickledOlmAccount =
'VWhVApbkcilKAEGppsPDf9nNVjaK8/IxT3asSR0sYg0S5KgbfE8vXEPwoiKBX2cEvwX3OessOBOkk+ZE7TTbjlrh/KEd31p8Wo+47qj0AP+Ky+pabnhi+/rTBvZy+gfzTqUfCxZrkzfXI9Op4JnP6gYmy7dVX2lMYIIs9WCO1jcmIXiXum5jnfXu1WLfc7PZtO2hH+k9CDKosOFaXRBmsu8k/BGXPSoWqUpvu6WpEG9t5STk4FeAzA';
final database = await getDatabase(); final database = await getDatabase();
group('Encrypt/Decrypt to-device messages', tags: 'olm', () { group('Encrypt/Decrypt to-device messages', tags: 'olm', () {
Logs().level = Level.error; Logs().level = Level.error;
@ -64,7 +62,6 @@ void main() async {
newHomeserver: otherClient.homeserver, newHomeserver: otherClient.homeserver,
newDeviceName: 'Text Matrix Client', newDeviceName: 'Text Matrix Client',
newDeviceID: 'FOXDEVICE', newDeviceID: 'FOXDEVICE',
newOlmAccount: otherPickledOlmAccount,
); );
await otherClient.abortSync(); await otherClient.abortSync();

View File

@ -53,7 +53,7 @@ void main() async {
// key @othertest:fakeServer.notExisting // key @othertest:fakeServer.notExisting
const otherPickledOlmAccount = const otherPickledOlmAccount =
'VWhVApbkcilKAEGppsPDf9nNVjaK8/IxT3asSR0sYg0S5KgbfE8vXEPwoiKBX2cEvwX3OessOBOkk+ZE7TTbjlrh/KEd31p8Wo+47qj0AP+Ky+pabnhi+/rTBvZy+gfzTqUfCxZrkzfXI9Op4JnP6gYmy7dVX2lMYIIs9WCO1jcmIXiXum5jnfXu1WLfc7PZtO2hH+k9CDKosOFaXRBmsu8k/BGXPSoWqUpvu6WpEG9t5STk4FeAzA'; '0aFMkSgJhj0kVLxVnactRpl3L2kgIR8bAqICFtDkvp/mkinITZjr1Vh6Jy9FmJzvhLfFUtjU2j/2bqrFn61CSrvRbRaLP6rCFegGJHNGpVfw+c24NthCwGF/SN10aPjPo6yQ3er9bc42I6AmJz5HgyfU6C4bE+LdWrML93C0iEnmQN/SYHnS1KHPXNl6NpFGITggbZQ9jwHOFILWo8wzJ4iqlJtMrNaOOLAAB7By7Fbxl4xoNz2K+w';
late Client client1; late Client client1;
late Client client2; late Client client2;

View File

@ -67,7 +67,7 @@ void main() {
); );
expect(sent['device_keys'] != null, true); expect(sent['device_keys'] != null, true);
expect(sent['one_time_keys'] != null, true); expect(sent['one_time_keys'] != null, true);
expect(sent['one_time_keys'].keys.length, 66); expect(sent['one_time_keys'].keys.length, 33);
expect(sent['fallback_keys'] != null, true); expect(sent['fallback_keys'] != null, true);
expect(sent['fallback_keys'].keys.length, 1); expect(sent['fallback_keys'].keys.length, 1);
FakeMatrixApi.calledEndpoints.clear(); FakeMatrixApi.calledEndpoints.clear();
@ -83,7 +83,7 @@ void main() {
sent = json.decode( sent = json.decode(
FakeMatrixApi.calledEndpoints['/client/v3/keys/upload']!.first, FakeMatrixApi.calledEndpoints['/client/v3/keys/upload']!.first,
); );
expect(sent['one_time_keys'].keys.length, 46); expect(sent['one_time_keys'].keys.length, 13);
expect(sent['fallback_keys'].keys.length, 0); expect(sent['fallback_keys'].keys.length, 0);
}); });

View File

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

View File

@ -16,8 +16,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import 'package:matrix/matrix.dart';
import 'package:vodozemac/vodozemac.dart' as vod; import 'package:vodozemac/vodozemac.dart' as vod;
import 'package:matrix/matrix.dart';
import 'fake_database.dart'; import 'fake_database.dart';
const ssssPassphrase = 'nae7ahDiequ7ohniufah3ieS2je1thohX4xeeka7aixohsho9O'; const ssssPassphrase = 'nae7ahDiequ7ohniufah3ieS2je1thohX4xeeka7aixohsho9O';
@ -25,7 +26,7 @@ const ssssKey = 'EsT9 RzbW VhPW yqNp cC7j ViiW 5TZB LuY4 ryyv 9guN Ysmr WDPH';
// key @test:fakeServer.notExisting // key @test:fakeServer.notExisting
const pickledOlmAccount = const pickledOlmAccount =
'N2v1MkIFGcl0mQpo2OCwSopxPQJ0wnl7oe7PKiT4141AijfdTIhRu+ceXzXKy3Kr00nLqXtRv7kid6hU4a+V0rfJWLL0Y51+3Rp/ORDVnQy+SSeo6Fn4FHcXrxifJEJ0djla5u98fBcJ8BSkhIDmtXRPi5/oJAvpiYn+8zMjFHobOeZUAxYR0VfQ9JzSYBsSovoQ7uFkNks1M4EDUvHtuyg3RxViwdNxs3718fyAqQ/VSwbXsY0Nl+qQbF+nlVGHenGqk5SuNl1P6e1PzZxcR0IfXA94Xij1Ob5gDv5YH4UCn9wRMG0abZsQP0YzpDM0FLaHSCyo9i5JD/vMlhH+nZWrgAzPPCTNGYewNV8/h3c+VyJh8ZTx/fVi6Yq46Fv+27Ga2ETRZ3Qn+Oyx6dLBjnBZ9iUvIhqpe2XqaGA1PopOz8iDnaZitw'; 'huxcPifHlyiQsX7cZeMMITbka3hLeUT3ss6DLL6dV7knaD4wgAYK6gcWknkixnX8C5KMIyxzytxiNqAOhDFRE5NsET8hr2dQ8OvXX7M95eQ7/3dPi7FkPUIbvneTSGgJYNDxJdHsDJ8OBHZ3BoqUJFDbTzFfVJjEzN4G9XQwPDafZ2p5WyerOK8Twj/rvk5N+ERmkt1XgVLQl66we/BO1ugTeM3YpDHm5lTzFUitJGTIuuONsKG9mmzdAmVUJ9YIrSxwmOBdegbGA+LAl5acg5VOol3KxRgZUMJQRQ58zpBAs72oauHizv1QVoQ7uIUiCUeb9lym+TEjmApvhru/1CPHU90K5jHNZ57wb/4V9VsqBWuoNibzDWG35YTFLcx0o+1lrCIjm1QjuC0777G+L1HNw5wnppV3z/k0YujjuPS3wvOA30TjHg';
Future? vodInit; Future? vodInit;

View File

@ -22,7 +22,6 @@ 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';

View File

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