diff --git a/lib/encryption/key_manager.dart b/lib/encryption/key_manager.dart index a17944ed..a8038cf7 100644 --- a/lib/encryption/key_manager.dart +++ b/lib/encryption/key_manager.dart @@ -271,7 +271,7 @@ class KeyManager { /// devices have been changed. Returns false if the session has not been cleared because /// it wasn't necessary. Otherwise returns true. Future clearOrUseOutboundGroupSession(String roomId, - {bool wipe = false}) async { + {bool wipe = false, bool use = true}) async { final room = client.getRoomById(roomId); final sess = getOutboundGroupSession(roomId); if (room == null || sess == null) { @@ -334,8 +334,13 @@ class KeyManager { break; } // and now add all the new devices! - final oldDeviceIds = Set.from(sess.devices[userId].keys); - final newDeviceIds = Set.from(newDeviceKeyIds[userId].keys); + final oldDeviceIds = Set.from(sess.devices[userId].entries + .where((e) => !e.value) + .map((e) => e.key)); + final newDeviceIds = Set.from(newDeviceKeyIds[userId] + .entries + .where((e) => !e.value) + .map((e) => e.key)); final newDevices = newDeviceIds.difference(oldDeviceIds); if (newDeviceIds.isNotEmpty) { devicesToReceive.addAll(newDeviceKeys.where( @@ -345,6 +350,9 @@ class KeyManager { } if (!wipe) { + if (!use) { + return false; + } // okay, we use the outbound group session! sess.sentMessages++; sess.devices = newDeviceKeyIds; @@ -355,7 +363,7 @@ class KeyManager { 'session_key': sess.outboundGroupSession.session_key(), }; try { - devicesToReceive.removeWhere((k) => k.blocked); + devicesToReceive.removeWhere((k) => !k.encryptToDevice); if (devicesToReceive.isNotEmpty) { // update allowedAtIndex for (final device in devicesToReceive) { @@ -394,6 +402,7 @@ class KeyManager { return true; } + /// Store an outbound group session in the database Future storeOutboundGroupSession( String roomId, OutboundGroupSession sess) async { if (sess == null) { @@ -411,6 +420,7 @@ class KeyManager { final Map> _pendingNewOutboundGroupSessions = {}; + /// Creates an outbound group session for a given room id Future createOutboundGroupSession(String roomId) async { if (_pendingNewOutboundGroupSessions.containsKey(roomId)) { return _pendingNewOutboundGroupSessions[roomId]; @@ -421,6 +431,18 @@ class KeyManager { return _pendingNewOutboundGroupSessions.remove(roomId); } + /// Prepares an outbound group session for a given room ID. That is, load it from + /// the database, cycle it if needed and create it if absent. + Future prepareOutboundGroupSession(String roomId) async { + if (getOutboundGroupSession(roomId) == null) { + await loadOutboundGroupSession(roomId); + } + await clearOrUseOutboundGroupSession(roomId, use: false); + if (getOutboundGroupSession(roomId) == null) { + await createOutboundGroupSession(roomId); + } + } + Future _createOutboundGroupSession( String roomId) async { await clearOrUseOutboundGroupSession(roomId, wipe: true); @@ -477,10 +499,12 @@ class KeyManager { return sess; } + /// Get an outbound group session for a room id OutboundGroupSession getOutboundGroupSession(String roomId) { return _outboundGroupSessions[roomId]; } + /// Load an outbound group session from database Future loadOutboundGroupSession(String roomId) async { if (_loadedOutboundGroupSessions.contains(roomId) || _outboundGroupSessions.containsKey(roomId) || diff --git a/lib/src/client.dart b/lib/src/client.dart index dd601fb6..06f710af 100644 --- a/lib/src/client.dart +++ b/lib/src/client.dart @@ -22,6 +22,7 @@ import 'dart:core'; import 'dart:typed_data'; import 'package:http/http.dart' as http; +import 'package:pedantic/pedantic.dart'; import '../encryption.dart'; import '../famedlysdk.dart'; @@ -542,6 +543,22 @@ class Client extends MatrixApi { return mxc; } + /// Sends a typing notification and initiates a megolm session, if needed + @override + Future sendTypingNotification( + String userId, + String roomId, + bool typing, { + int timeout, + }) async { + await super + .sendTypingNotification(userId, roomId, typing, timeout: timeout); + final room = getRoomById(roomId); + if (typing && room != null && encryptionEnabled && room.encrypted) { + unawaited(encryption.keyManager.prepareOutboundGroupSession(roomId)); + } + } + /// Uploads a new user avatar for this user. Future setAvatar(MatrixFile file) async { final uploadResp = await upload(file.bytes, file.name); diff --git a/test/encryption/key_manager_test.dart b/test/encryption/key_manager_test.dart index 3de5fe89..4d51c2d5 100644 --- a/test/encryption/key_manager_test.dart +++ b/test/encryption/key_manager_test.dart @@ -133,6 +133,26 @@ void main() { client.userDeviceKeys['@alice:example.com'].deviceKeys['JLAFKJWSCS'] .blocked = false; + // lazy-create if it would rotate + sess = + await client.encryption.keyManager.createOutboundGroupSession(roomId); + final oldSessKey = sess.outboundGroupSession.session_key(); + client.userDeviceKeys['@alice:example.com'].deviceKeys['JLAFKJWSCS'] + .blocked = true; + await client.encryption.keyManager.prepareOutboundGroupSession(roomId); + expect( + client.encryption.keyManager.getOutboundGroupSession(roomId) != null, + true); + expect( + client.encryption.keyManager + .getOutboundGroupSession(roomId) + .outboundGroupSession + .session_key() != + oldSessKey, + true); + client.userDeviceKeys['@alice:example.com'].deviceKeys['JLAFKJWSCS'] + .blocked = false; + // rotate if too far in the past sess = await client.encryption.keyManager.createOutboundGroupSession(roomId);