refactor: Migrate olm account to vodozemac
This commit is contained in:
parent
98fcd683a6
commit
5fdcbf8006
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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,33 +71,31 @@ class OlmManager {
|
||||||
}) async {
|
}) async {
|
||||||
ourDeviceId = deviceId;
|
ourDeviceId = deviceId;
|
||||||
if (olmAccount == null) {
|
if (olmAccount == null) {
|
||||||
try {
|
_olmAccount = vod.Account();
|
||||||
await olm.init();
|
if (!await uploadKeys(
|
||||||
_olmAccount = olm.Account();
|
uploadDeviceKeys: true,
|
||||||
_olmAccount!.create();
|
updateDatabase: false,
|
||||||
if (!await uploadKeys(
|
dehydratedDeviceAlgorithm: dehydratedDeviceAlgorithm,
|
||||||
uploadDeviceKeys: true,
|
dehydratedDevicePickleKey:
|
||||||
updateDatabase: false,
|
dehydratedDeviceAlgorithm != null ? pickleKey : null,
|
||||||
dehydratedDeviceAlgorithm: dehydratedDeviceAlgorithm,
|
)) {
|
||||||
dehydratedDevicePickleKey:
|
throw ('Upload key failed');
|
||||||
dehydratedDeviceAlgorithm != null ? pickleKey : null,
|
|
||||||
)) {
|
|
||||||
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,38 +201,32 @@ 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.entries) {
|
||||||
for (final entry in fallbackKey['curve25519'].entries) {
|
final key = entry.key;
|
||||||
final key = entry.key;
|
final value = entry.value.toBase64();
|
||||||
final value = entry.value;
|
signedFallbackKeys['signed_curve25519:$key'] = signJson({
|
||||||
signedFallbackKeys['signed_curve25519:$key'] = signJson({
|
'key': value,
|
||||||
'key': value,
|
'fallback': true,
|
||||||
'fallback': true,
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (signedFallbackKeys.isEmpty &&
|
if (signedFallbackKeys.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 =
|
final key = otk.tryGet<String>('key');
|
||||||
json.decode(olmAccount.identity_keys())['curve25519'];
|
if (key != null) {
|
||||||
final key = otk.tryGet<String>('key');
|
olmAccount.createOutboundSession(
|
||||||
if (key != null) {
|
identityKey: vod.Curve25519PublicKey.fromBase64(identity),
|
||||||
session.create_outbound(_olmAccount!, identity, key);
|
oneTimeKey: vod.Curve25519PublicKey.fromBase64(key),
|
||||||
olmAccount.remove_one_time_keys(session);
|
);
|
||||||
}
|
|
||||||
} 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: unusedFallbackKey,
|
||||||
unusedFallbackKey: haveFallbackKeys ? unusedFallbackKey : null,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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';
|
||||||
|
|
|
||||||
|
|
@ -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');
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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';
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue