diff --git a/lib/encryption/key_manager.dart b/lib/encryption/key_manager.dart index 69340cb9..a17944ed 100644 --- a/lib/encryption/key_manager.dart +++ b/lib/encryption/key_manager.dart @@ -30,6 +30,7 @@ import '../matrix_api/utils/logs.dart'; import '../src/utils/run_in_background.dart'; import '../src/utils/run_in_root.dart'; import '../matrix_api/utils/try_get_map_extension.dart'; +import '../matrix_api/utils/map_copy_extension.dart'; const MEGOLM_KEY = EventTypes.MegolmBackup; @@ -743,7 +744,8 @@ class KeyManager { } if (event.content['action'] == 'request') { // we are *receiving* a request - Logs().i('[KeyManager] Received key sharing request...'); + Logs().i( + '[KeyManager] Received key sharing request from ${event.sender}:${event.content['requesting_device_id']}...'); if (!event.content.containsKey('body')) { Logs().i('[KeyManager] No body, doing nothing'); return; // no body @@ -954,10 +956,10 @@ class RoomKeyRequest extends ToDeviceEvent { keyManager.incomingShareRequests.remove(request.requestId); return; // request is canceled, don't send anything } - var room = this.room; + final room = this.room; final session = await keyManager.loadInboundGroupSession( room.id, request.sessionId, request.senderKey); - var message = session.content; + final message = session.content.copy(); message['forwarding_curve25519_key_chain'] = List.from(session.forwardingCurve25519KeyChain); diff --git a/lib/encryption/olm_manager.dart b/lib/encryption/olm_manager.dart index 4a2fddf6..b3130381 100644 --- a/lib/encryption/olm_manager.dart +++ b/lib/encryption/olm_manager.dart @@ -227,9 +227,7 @@ class OlmManager { if (client.database == null) { return; } - if (!_olmSessions.containsKey(session.identityKey)) { - _olmSessions[session.identityKey] = []; - } + _olmSessions[session.identityKey] ??= []; final ix = _olmSessions[session.identityKey] .indexWhere((s) => s.sessionId == session.sessionId); if (ix == -1) { @@ -264,19 +262,21 @@ class OlmManager { if (type != 0 && type != 1) { throw ('Unknown message type'); } - var existingSessions = olmSessions[senderKey]; + final existingSessions = olmSessions[senderKey]; + final updateSessionUsage = (OlmSession session) => runInRoot(() { + session.lastReceived = DateTime.now(); + storeOlmSession(session); + }); if (existingSessions != null) { for (var session in existingSessions) { if (type == 0 && session.session.matches_inbound(body) == true) { plaintext = session.session.decrypt(type, body); - session.lastReceived = DateTime.now(); - storeOlmSession(session); + updateSessionUsage(session); break; } else if (type == 1) { try { plaintext = session.session.decrypt(type, body); - session.lastReceived = DateTime.now(); - storeOlmSession(session); + updateSessionUsage(session); break; } catch (_) { plaintext = null; @@ -295,13 +295,13 @@ class OlmManager { _olmAccount.remove_one_time_keys(newSession); client.database?.updateClientKeys(pickledOlmAccount, client.id); plaintext = newSession.decrypt(type, body); - storeOlmSession(OlmSession( - key: client.userID, - identityKey: senderKey, - sessionId: newSession.session_id(), - session: newSession, - lastReceived: DateTime.now(), - )); + runInRoot(() => storeOlmSession(OlmSession( + key: client.userID, + identityKey: senderKey, + sessionId: newSession.session_id(), + session: newSession, + lastReceived: DateTime.now(), + ))); } catch (_) { newSession?.free(); rethrow; @@ -345,6 +345,21 @@ class OlmManager { return res; } + Future> getOlmSessions(String senderKey) async { + var sess = olmSessions[senderKey]; + if (sess == null || sess.isEmpty) { + final sessions = await getOlmSessionsFromDatabase(senderKey); + if (sessions.isEmpty) { + return []; + } + sess = _olmSessions[senderKey] = sessions; + } + sess.sort((a, b) => a.lastReceived == b.lastReceived + ? a.sessionId.compareTo(b.sessionId) + : b.lastReceived.compareTo(a.lastReceived)); + return sess; + } + final Map _restoredOlmSessionsTime = {}; Future restoreOlmSession(String userId, String senderKey) async { @@ -375,15 +390,8 @@ class OlmManager { } final senderKey = event.content['sender_key']; final loadFromDb = () async { - if (client.database == null) { - return false; - } - final sessions = await getOlmSessionsFromDatabase(senderKey); - if (sessions.isEmpty) { - return false; // okay, can't do anything - } - _olmSessions[senderKey] = sessions; - return true; + final sessions = await getOlmSessions(senderKey); + return sessions.isNotEmpty; }; if (!_olmSessions.containsKey(senderKey)) { await loadFromDb(); @@ -406,6 +414,9 @@ class OlmManager { } Future startOutgoingOlmSessions(List deviceKeys) async { + Logs().v( + '[OlmManager] Starting session with ${deviceKeys.length} devices...'); + var requestingKeysFrom = >{}; for (var device in deviceKeys) { if (requestingKeysFrom[device.userId] == null) { @@ -429,6 +440,7 @@ class OlmManager { if (!deviceKey.checkJsonSignature(fingerprintKey, userId, deviceId)) { continue; } + Logs().v('[OlmManager] Starting session with ${userId}:${deviceId}'); var session = olm.Session(); try { session.create_outbound(_olmAccount, identityKey, deviceKey['key']); @@ -452,17 +464,11 @@ class OlmManager { Future> encryptToDeviceMessagePayload( DeviceKeys device, String type, Map payload) async { - var sess = olmSessions[device.curve25519Key]; - if (sess == null || sess.isEmpty) { - final sessions = await getOlmSessionsFromDatabase(device.curve25519Key); - if (sessions.isEmpty) { - throw ('No olm session found'); - } - sess = _olmSessions[device.curve25519Key] = sessions; + final sess = await getOlmSessions(device.curve25519Key); + if (sess.isEmpty) { + throw ('No olm session found for ${device.userId}:${device.deviceId}'); } - sess.sort((a, b) => a.lastReceived == b.lastReceived - ? a.sessionId.compareTo(b.sessionId) - : b.lastReceived.compareTo(a.lastReceived)); + final fullPayload = { 'type': type, 'content': payload, @@ -493,18 +499,13 @@ class OlmManager { // first check if any of our sessions we want to encrypt for are in the database if (client.database != null) { for (final device in deviceKeys) { - if (!olmSessions.containsKey(device.curve25519Key)) { - final sessions = - await getOlmSessionsFromDatabase(device.curve25519Key); - if (sessions.isNotEmpty) { - _olmSessions[device.curve25519Key] = sessions; - } - } + await getOlmSessions(device.curve25519Key); } } final deviceKeysWithoutSession = List.from(deviceKeys); deviceKeysWithoutSession.removeWhere((DeviceKeys deviceKeys) => - olmSessions.containsKey(deviceKeys.curve25519Key)); + olmSessions.containsKey(deviceKeys.curve25519Key) && + olmSessions[deviceKeys.curve25519Key].isNotEmpty); if (deviceKeysWithoutSession.isNotEmpty) { await startOutgoingOlmSessions(deviceKeysWithoutSession); } diff --git a/lib/src/client.dart b/lib/src/client.dart index 67586dfc..dd601fb6 100644 --- a/lib/src/client.dart +++ b/lib/src/client.dart @@ -1428,7 +1428,17 @@ sort order of ${prevState.sortOrder}. This should never happen...'''); // Always trust the own device entry.setDirectVerified(true); } - } else { + if (database != null) { + dbActions.add(() => database.storeUserDeviceKey( + id, + userId, + deviceId, + json.encode(entry.toJson()), + entry.directVerified, + entry.blocked, + )); + } + } else if (oldKeys.containsKey(deviceId)) { // This shouldn't ever happen. The same device ID has gotten // a new public key. So we ignore the update. TODO: ask krille // if we should instead use the new key with unknown verified / blocked status @@ -1438,16 +1448,6 @@ sort order of ${prevState.sortOrder}. This should never happen...'''); } else { Logs().w('Invalid device ${entry.userId}:${entry.deviceId}'); } - if (database != null) { - dbActions.add(() => database.storeUserDeviceKey( - id, - userId, - deviceId, - json.encode(entry.toJson()), - entry.directVerified, - entry.blocked, - )); - } } // delete old/unused entries if (database != null) { @@ -1571,8 +1571,7 @@ sort order of ${prevState.sortOrder}. This should never happen...'''); return; } - /// Sends an encrypted [message] of this [type] to these [deviceKeys]. To send - /// the request to all devices of the current user, pass an empty list to [deviceKeys]. + /// Sends an encrypted [message] of this [type] to these [deviceKeys]. Future sendToDeviceEncrypted( List deviceKeys, String eventType, @@ -1586,7 +1585,7 @@ sort order of ${prevState.sortOrder}. This should never happen...'''); if (deviceKeys.isNotEmpty) { deviceKeys.removeWhere((DeviceKeys deviceKeys) => deviceKeys.blocked || - deviceKeys.deviceId == deviceID || + (deviceKeys.userId == userID && deviceKeys.deviceId == deviceID) || (onlyVerified && !deviceKeys.verified)); if (deviceKeys.isEmpty) return; }