From 99d536b14fa42baf8ecd9b96523d5200238f125d Mon Sep 17 00:00:00 2001 From: Sorunome Date: Mon, 17 Aug 2020 14:25:48 +0200 Subject: [PATCH] feature: Upload to online key backup --- lib/encryption/encryption.dart | 17 ++ lib/encryption/key_manager.dart | 173 ++++++++++++++++++-- lib/encryption/ssss.dart | 26 ++- lib/encryption/utils/session_key.dart | 6 + lib/matrix_api/matrix_api.dart | 2 +- lib/matrix_api/model/room_keys_keys.dart | 14 ++ lib/src/client.dart | 15 ++ lib/src/database/database.g.dart | 21 +++ lib/src/database/database.moor | 2 + lib/src/utils/logs.dart | 18 ++ lib/src/utils/run_in_background.dart | 30 ++++ pubspec.lock | 9 +- pubspec.yaml | 3 +- test.sh | 4 +- test/encryption/key_verification_test.dart | 6 +- test/encryption/online_key_backup_test.dart | 47 ++++++ test/encryption/ssss_test.dart | 16 +- test/user_test.dart | 4 +- 18 files changed, 375 insertions(+), 38 deletions(-) create mode 100644 lib/src/utils/run_in_background.dart diff --git a/lib/encryption/encryption.dart b/lib/encryption/encryption.dart index 4b9e0fc..f6f3a3c 100644 --- a/lib/encryption/encryption.dart +++ b/lib/encryption/encryption.dart @@ -63,6 +63,8 @@ class Encryption { Future init(String olmAccount) async { await olmManager.init(olmAccount); + _backgroundTasksRunning = true; + _backgroundTasks(); // start the background tasks } void handleDeviceOneTimeKeysCount(Map countJson) { @@ -307,10 +309,25 @@ class Encryption { return await olmManager.encryptToDeviceMessage(deviceKeys, type, payload); } + // this method is responsible for all background tasks, such as uploading online key backups + bool _backgroundTasksRunning = true; + void _backgroundTasks() { + if (!_backgroundTasksRunning) { + return; + } + + keyManager.backgroundTasks(); + + if (_backgroundTasksRunning) { + Timer(Duration(seconds: 10), _backgroundTasks); + } + } + void dispose() { keyManager.dispose(); olmManager.dispose(); keyVerificationManager.dispose(); + _backgroundTasksRunning = false; } } diff --git a/lib/encryption/key_manager.dart b/lib/encryption/key_manager.dart index bb39da7..d032704 100644 --- a/lib/encryption/key_manager.dart +++ b/lib/encryption/key_manager.dart @@ -26,7 +26,9 @@ import './utils/outbound_group_session.dart'; import './utils/session_key.dart'; import '../famedlysdk.dart'; import '../matrix_api.dart'; +import '../src/database/database.dart'; import '../src/utils/logs.dart'; +import '../src/utils/run_in_background.dart'; const MEGOLM_KEY = 'm.megolm_backup.v1'; @@ -71,18 +73,14 @@ class KeyManager { void setInboundGroupSession(String roomId, String sessionId, String senderKey, Map content, - {bool forwarded = false, Map senderClaimedKeys}) { + {bool forwarded = false, + Map senderClaimedKeys, + bool uploaded = false}) { senderClaimedKeys ??= {}; if (!senderClaimedKeys.containsKey('ed25519')) { - DeviceKeys device; - for (final user in client.userDeviceKeys.values) { - device = user.deviceKeys.values.firstWhere( - (e) => e.curve25519Key == senderKey, - orElse: () => null); - if (device != null) { - senderClaimedKeys['ed25519'] = device.ed25519Key; - break; - } + final device = client.getUserDeviceKeysByCurve25519Key(senderKey); + if (device != null) { + senderClaimedKeys['ed25519'] = device.ed25519Key; } } final oldSession = @@ -109,6 +107,8 @@ class KeyManager { content: content, inboundGroupSession: inboundGroupSession, indexes: {}, + roomId: roomId, + sessionId: sessionId, key: client.userID, senderKey: senderKey, senderClaimedKeys: senderClaimedKeys, @@ -132,7 +132,8 @@ class KeyManager { _inboundGroupSessions[roomId] = {}; } _inboundGroupSessions[roomId][sessionId] = newSession; - client.database?.storeInboundGroupSession( + client.database + ?.storeInboundGroupSession( client.id, roomId, sessionId, @@ -141,9 +142,13 @@ class KeyManager { json.encode({}), senderKey, json.encode(senderClaimedKeys), - ); - // Note to self: When adding key-backup that needs to be unawaited(), else - // we might accidentally end up with http requests inside of the sync loop + ) + ?.then((_) { + if (uploaded) { + client.database + .markInboundGroupSessionAsUploaded(client.id, roomId, sessionId); + } + }); // TODO: somehow try to decrypt last message again final room = client.getRoomById(roomId); if (room != null) { @@ -410,7 +415,8 @@ class KeyManager { forwarded: true, senderClaimedKeys: decrypted['sender_claimed_keys'] != null ? Map.from(decrypted['sender_claimed_keys']) - : null); + : null, + uploaded: true); } } } @@ -492,6 +498,79 @@ class KeyManager { } } + bool _isUploadingKeys = false; + Future backgroundTasks() async { + if (_isUploadingKeys || client.database == null) { + return; + } + _isUploadingKeys = true; + try { + if (!(await isCached())) { + return; // we can't backup anyways + } + final dbSessions = + await client.database.getInboundGroupSessionsToUpload().get(); + if (dbSessions.isEmpty) { + return; // nothing to do + } + final privateKey = + base64.decode(await encryption.ssss.getCached(MEGOLM_KEY)); + // 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 client.getRoomKeysBackup(); + String backupPubKey; + try { + backupPubKey = decryption.init_with_private_key(privateKey); + + if (backupPubKey == null || + info.algorithm != RoomKeysAlgorithmType.v1Curve25519AesSha2 || + info.authData['public_key'] != backupPubKey) { + return; + } + final args = _GenerateUploadKeysArgs( + pubkey: backupPubKey, + dbSessions: <_DbInboundGroupSessionBundle>[], + userId: client.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, + )); + i++; + if (i > 10) { + await Future.delayed(Duration(milliseconds: 1)); + i = 0; + } + } + final roomKeys = + await runInBackground( + _generateUploadKeys, args); + Logs.info('[Key Manager] Uploading ${dbSessions.length} room keys...'); + // upload the payload... + await client.storeRoomKeys(info.version, roomKeys); + // and now finally mark all the keys as uploaded + // no need to optimze this, as we only run it so seldomly and almost never with many keys at once + for (final dbSession in dbSessions) { + await client.database.markInboundGroupSessionAsUploaded( + client.id, dbSession.roomId, dbSession.sessionId); + } + } finally { + decryption.free(); + } + } catch (e, s) { + Logs.error('[Key Manager] Error uploading room keys: ' + e.toString(), s); + } finally { + _isUploadingKeys = false; + } + } + /// Handle an incoming to_device event that is related to key sharing Future handleToDeviceEvent(ToDeviceEvent event) async { if (event.type == 'm.room_key_request') { @@ -725,3 +804,67 @@ class RoomKeyRequest extends ToDeviceEvent { keyManager.incomingShareRequests.remove(request.requestId); } } + +RoomKeys _generateUploadKeys(_GenerateUploadKeysArgs args) { + final enc = olm.PkEncryption(); + try { + enc.set_recipient_key(args.pubkey); + // first we generate the payload to upload all the session keys in this chunk + final roomKeys = RoomKeys(); + for (final dbSession in args.dbSessions) { + final sess = SessionKey.fromDb(dbSession.dbSession, args.userId); + if (!sess.isValid) { + continue; + } + // create the room if it doesn't exist + if (!roomKeys.rooms.containsKey(sess.roomId)) { + roomKeys.rooms[sess.roomId] = RoomKeysRoom(); + } + // generate the encrypted content + final payload = { + 'algorithm': 'm.megolm.v1.aes-sha2', + 'forwarding_curve25519_key_chain': sess.forwardingCurve25519KeyChain, + 'sender_key': sess.senderKey, + 'sender_clencaimed_keys': sess.senderClaimedKeys, + 'session_key': sess.inboundGroupSession + .export_session(sess.inboundGroupSession.first_known_index()), + }; + // encrypt the content + final encrypted = enc.encrypt(json.encode(payload)); + // fetch the device, if available... + //final device = args.client.getUserDeviceKeysByCurve25519Key(sess.senderKey); + // aaaand finally add the session key to our payload + roomKeys.rooms[sess.roomId].sessions[sess.sessionId] = RoomKeysSingleKey( + firstMessageIndex: sess.inboundGroupSession.first_known_index(), + forwardedCount: sess.forwardingCurve25519KeyChain.length, + isVerified: dbSession.verified, //device?.verified ?? false, + sessionData: { + 'ephemeral': encrypted.ephemeral, + 'ciphertext': encrypted.ciphertext, + 'mac': encrypted.mac, + }, + ); + } + return roomKeys; + } catch (e, s) { + Logs.error('[Key Manager] Error generating payload ' + e.toString(), s); + rethrow; + } finally { + enc.free(); + } +} + +class _DbInboundGroupSessionBundle { + _DbInboundGroupSessionBundle({this.dbSession, this.verified}); + + DbInboundGroupSession dbSession; + bool verified; +} + +class _GenerateUploadKeysArgs { + _GenerateUploadKeysArgs({this.pubkey, this.dbSessions, this.userId}); + + String pubkey; + List<_DbInboundGroupSessionBundle> dbSessions; + String userId; +} diff --git a/lib/encryption/ssss.dart b/lib/encryption/ssss.dart index 8386f03..f6be44d 100644 --- a/lib/encryption/ssss.dart +++ b/lib/encryption/ssss.dart @@ -27,6 +27,7 @@ import 'package:password_hash/password_hash.dart'; import '../famedlysdk.dart'; import '../matrix_api.dart'; +import '../src/database/database.dart'; import '../src/utils/logs.dart'; import 'encryption.dart'; @@ -48,8 +49,15 @@ class SSSS { Client get client => encryption.client; final pendingShareRequests = {}; final _validators = Function(String)>{}; + final Map _cache = {}; SSSS(this.encryption); + // for testing + Future clearCache() async { + await client.database?.clearSSSSCache(client.id); + _cache.clear(); + } + static _DerivedKeys deriveKeys(Uint8List key, String name) { final zerosalt = Uint8List(8); final prk = Hmac(sha256, zerosalt).convert(key); @@ -173,16 +181,22 @@ class SSSS { if (client.database == null) { return null; } + // check if it is still valid + final keys = keyIdsFromType(type); + final isValid = (dbEntry) => + keys.contains(dbEntry.keyId) && + client.accountData[type].content['encrypted'][dbEntry.keyId] + ['ciphertext'] == + dbEntry.ciphertext; + if (_cache.containsKey(type) && isValid(_cache[type])) { + return _cache[type].content; + } final ret = await client.database.getSSSSCache(client.id, type); if (ret == null) { return null; } - // check if it is still valid - final keys = keyIdsFromType(type); - if (keys.contains(ret.keyId) && - client.accountData[type].content['encrypted'][ret.keyId] - ['ciphertext'] == - ret.ciphertext) { + if (isValid(ret)) { + _cache[type] = ret; return ret.content; } return null; diff --git a/lib/encryption/utils/session_key.dart b/lib/encryption/utils/session_key.dart index 0477935..b54ca32 100644 --- a/lib/encryption/utils/session_key.dart +++ b/lib/encryption/utils/session_key.dart @@ -37,12 +37,16 @@ class SessionKey { Map senderClaimedKeys; String senderKey; bool get isValid => inboundGroupSession != null; + String roomId; + String sessionId; SessionKey( {this.content, this.inboundGroupSession, this.key, this.indexes, + this.roomId, + this.sessionId, String senderKey, Map senderClaimedKeys}) { _setSenderKey(senderKey); @@ -59,6 +63,8 @@ class SessionKey { indexes = parsedIndexes != null ? Map.from(parsedIndexes) : {}; + roomId = dbEntry.roomId; + sessionId = dbEntry.sessionId; _setSenderKey(dbEntry.senderKey); _setSenderClaimedKeys(Map.from(parsedSenderClaimedKeys)); diff --git a/lib/matrix_api/matrix_api.dart b/lib/matrix_api/matrix_api.dart index f738a83..4e372e1 100644 --- a/lib/matrix_api/matrix_api.dart +++ b/lib/matrix_api/matrix_api.dart @@ -2062,7 +2062,7 @@ class MatrixApi { return RoomKeysRoom.fromJson(ret); } - /// Deletes room ekys for a room + /// Deletes room keys for a room /// https://matrix.org/docs/spec/client_server/unstable#delete-matrix-client-r0-room-keys-keys-roomid Future deleteRoomKeysRoom( String roomId, String version) async { diff --git a/lib/matrix_api/model/room_keys_keys.dart b/lib/matrix_api/model/room_keys_keys.dart index 3b2c88e..6987066 100644 --- a/lib/matrix_api/model/room_keys_keys.dart +++ b/lib/matrix_api/model/room_keys_keys.dart @@ -22,6 +22,12 @@ class RoomKeysSingleKey { bool isVerified; Map sessionData; + RoomKeysSingleKey( + {this.firstMessageIndex, + this.forwardedCount, + this.isVerified, + this.sessionData}); + RoomKeysSingleKey.fromJson(Map json) { firstMessageIndex = json['first_message_index']; forwardedCount = json['forwarded_count']; @@ -42,6 +48,10 @@ class RoomKeysSingleKey { class RoomKeysRoom { Map sessions; + RoomKeysRoom({this.sessions}) { + sessions ??= {}; + } + RoomKeysRoom.fromJson(Map json) { sessions = (json['sessions'] as Map) .map((k, v) => MapEntry(k, RoomKeysSingleKey.fromJson(v))); @@ -57,6 +67,10 @@ class RoomKeysRoom { class RoomKeys { Map rooms; + RoomKeys({this.rooms}) { + rooms ??= {}; + } + RoomKeys.fromJson(Map json) { rooms = (json['rooms'] as Map) .map((k, v) => MapEntry(k, RoomKeysRoom.fromJson(v))); diff --git a/lib/src/client.dart b/lib/src/client.dart index 76b38b3..8e67d42 100644 --- a/lib/src/client.dart +++ b/lib/src/client.dart @@ -615,6 +615,7 @@ class Client extends MatrixApi { return; } + encryption?.dispose(); encryption = Encryption(client: this, enableE2eeRecovery: enableE2eeRecovery); await encryption.init(olmAccount); @@ -1165,6 +1166,18 @@ class Client extends MatrixApi { Map get userDeviceKeys => _userDeviceKeys; Map _userDeviceKeys = {}; + /// Gets user device keys by its curve25519 key. Returns null if it isn't found + DeviceKeys getUserDeviceKeysByCurve25519Key(String senderKey) { + for (final user in userDeviceKeys.values) { + final device = user.deviceKeys.values + .firstWhere((e) => e.curve25519Key == senderKey, orElse: () => null); + if (device != null) { + return device; + } + } + return null; + } + Future> _getUserIdsInEncryptedRooms() async { var userIds = {}; for (var i = 0; i < rooms.length; i++) { @@ -1493,6 +1506,8 @@ class Client extends MatrixApi { } if (closeDatabase) await database?.close(); database = null; + encryption?.dispose(); + encryption = null; return; } } diff --git a/lib/src/database/database.g.dart b/lib/src/database/database.g.dart index 2ee1edb..283679e 100644 --- a/lib/src/database/database.g.dart +++ b/lib/src/database/database.g.dart @@ -5873,6 +5873,27 @@ abstract class _$Database extends GeneratedDatabase { ); } + Selectable getInboundGroupSessionsToUpload() { + return customSelect( + 'SELECT * FROM inbound_group_sessions WHERE uploaded = false LIMIT 500', + variables: [], + readsFrom: {inboundGroupSessions}).map(_rowToDbInboundGroupSession); + } + + Future markInboundGroupSessionAsUploaded( + int client_id, String room_id, String session_id) { + return customUpdate( + 'UPDATE inbound_group_sessions SET uploaded = true WHERE client_id = :client_id AND room_id = :room_id AND session_id = :session_id', + variables: [ + Variable.withInt(client_id), + Variable.withString(room_id), + Variable.withString(session_id) + ], + updates: {inboundGroupSessions}, + updateKind: UpdateKind.update, + ); + } + Future storeUserDeviceKeysInfo( int client_id, String user_id, bool outdated) { return customInsert( diff --git a/lib/src/database/database.moor b/lib/src/database/database.moor index 13de47e..7ce4425 100644 --- a/lib/src/database/database.moor +++ b/lib/src/database/database.moor @@ -191,6 +191,8 @@ dbGetInboundGroupSessionKeys: SELECT * FROM inbound_group_sessions WHERE client_ getAllInboundGroupSessions: SELECT * FROM inbound_group_sessions WHERE client_id = :client_id; storeInboundGroupSession: INSERT OR REPLACE INTO inbound_group_sessions (client_id, room_id, session_id, pickle, content, indexes, sender_key, sender_claimed_keys) VALUES (:client_id, :room_id, :session_id, :pickle, :content, :indexes, :sender_key, :sender_claimed_keys); updateInboundGroupSessionIndexes: UPDATE inbound_group_sessions SET indexes = :indexes WHERE client_id = :client_id AND room_id = :room_id AND session_id = :session_id; +getInboundGroupSessionsToUpload: SELECT * FROM inbound_group_sessions WHERE uploaded = false LIMIT 500; +markInboundGroupSessionAsUploaded: UPDATE inbound_group_sessions SET uploaded = true WHERE client_id = :client_id AND room_id = :room_id AND session_id = :session_id; storeUserDeviceKeysInfo: INSERT OR REPLACE INTO user_device_keys (client_id, user_id, outdated) VALUES (:client_id, :user_id, :outdated); setVerifiedUserDeviceKey: UPDATE user_device_keys_key SET verified = :verified WHERE client_id = :client_id AND user_id = :user_id AND device_id = :device_id; setBlockedUserDeviceKey: UPDATE user_device_keys_key SET blocked = :blocked WHERE client_id = :client_id AND user_id = :user_id AND device_id = :device_id; diff --git a/lib/src/utils/logs.dart b/lib/src/utils/logs.dart index f774de3..fa2609a 100644 --- a/lib/src/utils/logs.dart +++ b/lib/src/utils/logs.dart @@ -1,3 +1,21 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + import 'package:ansicolor/ansicolor.dart'; abstract class Logs { diff --git a/lib/src/utils/run_in_background.dart b/lib/src/utils/run_in_background.dart new file mode 100644 index 0000000..cbc5a40 --- /dev/null +++ b/lib/src/utils/run_in_background.dart @@ -0,0 +1,30 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import 'package:isolate/isolate.dart'; +import 'dart:async'; + +Future runInBackground( + FutureOr Function(U arg) function, U arg) async { + final isolate = await IsolateRunner.spawn(); + try { + return await isolate.run(function, arg); + } finally { + await isolate.close(); + } +} diff --git a/pubspec.lock b/pubspec.lock index 25d83fe..ebc2d2f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -281,6 +281,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.3.4" + isolate: + dependency: "direct main" + description: + name: isolate + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.3" js: dependency: transitive description: @@ -609,7 +616,7 @@ packages: name: test_coverage url: "https://pub.dartlang.org" source: hosted - version: "0.4.1" + version: "0.4.3" timing: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index d594d4c..8880a3c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -22,10 +22,11 @@ dependencies: olm: ^1.2.1 matrix_file_e2ee: ^1.0.4 ansicolor: ^1.0.2 + isolate: ^2.0.3 dev_dependencies: test: ^1.0.0 - test_coverage: ^0.4.1 + test_coverage: ^0.4.3 moor_generator: ^3.0.0 build_runner: ^1.5.2 pedantic: ^1.9.0 diff --git a/test.sh b/test.sh index 2496656..f6300dd 100644 --- a/test.sh +++ b/test.sh @@ -1,6 +1,6 @@ #!/bin/sh -e -pub run test -p vm -pub run test_coverage +# pub run test -p vm +pub run test_coverage --print-test-output pub global activate remove_from_coverage pub global run remove_from_coverage:remove_from_coverage -f coverage/lcov.info -r '\.g\.dart$' genhtml -o coverage coverage/lcov.info || true diff --git a/test/encryption/key_verification_test.dart b/test/encryption/key_verification_test.dart index 2612207..319b8f5 100644 --- a/test/encryption/key_verification_test.dart +++ b/test/encryption/key_verification_test.dart @@ -207,7 +207,7 @@ void main() { test('ask SSSS start', () async { client1.userDeviceKeys[client1.userID].masterKey.setDirectVerified(true); - await client1.database.clearSSSSCache(client1.id); + await client1.encryption.ssss.clearCache(); final req1 = await client1.userDeviceKeys[client2.userID].startVerification(); expect(req1.state, KeyVerificationState.askSSSS); @@ -288,7 +288,7 @@ void main() { // alright, they match client1.userDeviceKeys[client1.userID].masterKey.setDirectVerified(true); - await client1.database.clearSSSSCache(client1.id); + await client1.encryption.ssss.clearCache(); // send mac FakeMatrixApi.calledEndpoints.clear(); @@ -312,7 +312,7 @@ void main() { client1.encryption.ssss = MockSSSS(client1.encryption); (client1.encryption.ssss as MockSSSS).requestedSecrets = false; - await client1.database.clearSSSSCache(client1.id); + await client1.encryption.ssss.clearCache(); await req1.maybeRequestSSSSSecrets(); await Future.delayed(Duration(milliseconds: 10)); expect((client1.encryption.ssss as MockSSSS).requestedSecrets, true); diff --git a/test/encryption/online_key_backup_test.dart b/test/encryption/online_key_backup_test.dart index 12b9ae0..9218466 100644 --- a/test/encryption/online_key_backup_test.dart +++ b/test/encryption/online_key_backup_test.dart @@ -16,12 +16,16 @@ * along with this program. If not, see . */ +import 'dart:convert'; + import 'package:famedlysdk/famedlysdk.dart'; import 'package:famedlysdk/src/utils/logs.dart'; +import 'package:famedlysdk/matrix_api.dart'; import 'package:test/test.dart'; import 'package:olm/olm.dart' as olm; import '../fake_client.dart'; +import '../fake_matrix_api.dart'; void main() { group('Online Key Backup', () { @@ -67,6 +71,49 @@ void main() { true); }); + test('upload key', () async { + final session = olm.OutboundGroupSession(); + session.create(); + final inbound = olm.InboundGroupSession(); + inbound.create(session.session_key()); + final senderKey = client.identityKey; + final roomId = '!someroom:example.org'; + final sessionId = inbound.session_id(); + // set a payload... + var sessionPayload = { + 'algorithm': 'm.megolm.v1.aes-sha2', + 'room_id': roomId, + 'forwarding_curve25519_key_chain': [client.identityKey], + 'session_id': sessionId, + 'session_key': inbound.export_session(1), + 'sender_key': senderKey, + 'sender_claimed_ed25519_key': client.fingerprintKey, + }; + FakeMatrixApi.calledEndpoints.clear(); + client.encryption.keyManager.setInboundGroupSession( + roomId, sessionId, senderKey, sessionPayload, + forwarded: true); + var dbSessions = + await client.database.getInboundGroupSessionsToUpload().get(); + expect(dbSessions.isNotEmpty, true); + await client.encryption.keyManager.backgroundTasks(); + final payload = FakeMatrixApi + .calledEndpoints['/client/unstable/room_keys/keys?version=5'].first; + dbSessions = + await client.database.getInboundGroupSessionsToUpload().get(); + expect(dbSessions.isEmpty, true); + + final onlineKeys = RoomKeys.fromJson(json.decode(payload)); + client.encryption.keyManager.clearInboundGroupSessions(); + var ret = client.encryption.keyManager + .getInboundGroupSession(roomId, sessionId, senderKey); + expect(ret, null); + await client.encryption.keyManager.loadFromResponse(onlineKeys); + ret = client.encryption.keyManager + .getInboundGroupSession(roomId, sessionId, senderKey); + expect(ret != null, true); + }); + test('dispose client', () async { await client.dispose(closeDatabase: true); }); diff --git a/test/encryption/ssss_test.dart b/test/encryption/ssss_test.dart index d213248..e698e09 100644 --- a/test/encryption/ssss_test.dart +++ b/test/encryption/ssss_test.dart @@ -248,7 +248,7 @@ void main() { client.encryption.ssss.open('m.cross_signing.self_signing'); handle.unlock(recoveryKey: SSSS_KEY); - await client.database.clearSSSSCache(client.id); + await client.encryption.ssss.clearCache(); client.encryption.ssss.pendingShareRequests.clear(); await client.encryption.ssss.request('best animal', [key]); var event = ToDeviceEvent( @@ -272,7 +272,7 @@ void main() { 'm.megolm_backup.v1' ]) { final secret = await handle.getStored(type); - await client.database.clearSSSSCache(client.id); + await client.encryption.ssss.clearCache(); client.encryption.ssss.pendingShareRequests.clear(); await client.encryption.ssss.request(type, [key]); event = ToDeviceEvent( @@ -294,7 +294,7 @@ void main() { // test different fail scenarios // not encrypted - await client.database.clearSSSSCache(client.id); + await client.encryption.ssss.clearCache(); client.encryption.ssss.pendingShareRequests.clear(); await client.encryption.ssss.request('best animal', [key]); event = ToDeviceEvent( @@ -309,7 +309,7 @@ void main() { expect(await client.encryption.ssss.getCached('best animal'), null); // unknown request id - await client.database.clearSSSSCache(client.id); + await client.encryption.ssss.clearCache(); client.encryption.ssss.pendingShareRequests.clear(); await client.encryption.ssss.request('best animal', [key]); event = ToDeviceEvent( @@ -327,7 +327,7 @@ void main() { expect(await client.encryption.ssss.getCached('best animal'), null); // not from a device we sent the request to - await client.database.clearSSSSCache(client.id); + await client.encryption.ssss.clearCache(); client.encryption.ssss.pendingShareRequests.clear(); await client.encryption.ssss.request('best animal', [key]); event = ToDeviceEvent( @@ -345,7 +345,7 @@ void main() { expect(await client.encryption.ssss.getCached('best animal'), null); // secret not a string - await client.database.clearSSSSCache(client.id); + await client.encryption.ssss.clearCache(); client.encryption.ssss.pendingShareRequests.clear(); await client.encryption.ssss.request('best animal', [key]); event = ToDeviceEvent( @@ -363,7 +363,7 @@ void main() { expect(await client.encryption.ssss.getCached('best animal'), null); // validator doesn't check out - await client.database.clearSSSSCache(client.id); + await client.encryption.ssss.clearCache(); client.encryption.ssss.pendingShareRequests.clear(); await client.encryption.ssss.request('m.megolm_backup.v1', [key]); event = ToDeviceEvent( @@ -386,7 +386,7 @@ void main() { final key = client.userDeviceKeys[client.userID].deviceKeys['OTHERDEVICE']; key.setDirectVerified(true); - await client.database.clearSSSSCache(client.id); + await client.encryption.ssss.clearCache(); client.encryption.ssss.pendingShareRequests.clear(); await client.encryption.ssss.maybeRequestAll([key]); expect(client.encryption.ssss.pendingShareRequests.length, 3); diff --git a/test/user_test.dart b/test/user_test.dart index 0e7de24..ea2d7fe 100644 --- a/test/user_test.dart +++ b/test/user_test.dart @@ -132,6 +132,8 @@ void main() { await client.checkServer('https://fakeserver.notexisting'); expect(user1.canChangePowerLevel, false); }); - client.dispose(); + test('dispose client', () async { + await client.dispose(); + }); }); }