From df1c249011acf8c9299291a99468103ab8acdf52 Mon Sep 17 00:00:00 2001 From: Sorunome Date: Fri, 18 Dec 2020 15:09:03 +0100 Subject: [PATCH] feat: Auto-Share megolm sessions with other users we know for a fact are allowed to see said message --- lib/encryption/key_manager.dart | 61 ++++++++++++++++++++--- lib/encryption/utils/session_key.dart | 53 +++++++++++++------- lib/src/database/database.dart | 8 ++- lib/src/database/database.g.dart | 71 +++++++++++++++++++++++++-- lib/src/database/database.moor | 4 +- test/encryption/key_manager_test.dart | 17 +++++-- test/encryption/key_request_test.dart | 53 ++++++++++++++++++++ 7 files changed, 231 insertions(+), 36 deletions(-) diff --git a/lib/encryption/key_manager.dart b/lib/encryption/key_manager.dart index e507a387..b7f451d5 100644 --- a/lib/encryption/key_manager.dart +++ b/lib/encryption/key_manager.dart @@ -29,6 +29,7 @@ import '../src/database/database.dart'; 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'; const MEGOLM_KEY = EventTypes.MegolmBackup; @@ -88,7 +89,8 @@ class KeyManager { Map content, {bool forwarded = false, Map senderClaimedKeys, - bool uploaded = false}) { + bool uploaded = false, + Map> allowedAtIndex}) { senderClaimedKeys ??= {}; if (!senderClaimedKeys.containsKey('ed25519')) { final device = client.getUserDeviceKeysByCurve25519Key(senderKey); @@ -123,6 +125,7 @@ class KeyManager { key: client.userID, senderKey: senderKey, senderClaimedKeys: senderClaimedKeys, + allowedAtIndex: allowedAtIndex, ); final oldFirstIndex = oldSession?.inboundGroupSession?.first_known_index() ?? 0; @@ -151,6 +154,7 @@ class KeyManager { inboundGroupSession.pickle(client.userID), json.encode(content), json.encode({}), + json.encode(allowedAtIndex ?? {}), senderKey, json.encode(senderClaimedKeys), ) @@ -290,6 +294,8 @@ class KeyManager { wipe = true; } } + final inboundSess = await loadInboundGroupSession(room.id, + sess.outboundGroupSession.session_id(), encryption.identityKey); if (!wipe) { // next check if the devices in the room changed final devicesToReceive = []; @@ -350,6 +356,25 @@ class KeyManager { try { devicesToReceive.removeWhere((k) => k.blocked); if (devicesToReceive.isNotEmpty) { + // update allowedAtIndex + for (final device in devicesToReceive) { + inboundSess.allowedAtIndex[device.userId] ??= {}; + if (!inboundSess.allowedAtIndex[device.userId] + .containsKey(device.deviceId) || + inboundSess.allowedAtIndex[device.userId][device.deviceId] > + sess.outboundGroupSession.message_index()) { + inboundSess.allowedAtIndex[device.userId][device.deviceId] = + sess.outboundGroupSession.message_index(); + } + } + if (client.database != null) { + await client.database.updateInboundGroupSessionAllowedAtIndex( + json.encode(inboundSess.allowedAtIndex), + client.id, + room.id, + sess.outboundGroupSession.session_id()); + } + // send out the key await client.sendToDeviceEncrypted( devicesToReceive, 'm.room_key', rawSession); } @@ -404,6 +429,7 @@ class KeyManager { } final deviceKeys = await room.getUserDeviceKeys(); final deviceKeyIds = _getDeviceKeyIdMap(deviceKeys); + deviceKeys.removeWhere((k) => k.blocked); final outboundGroupSession = olm.OutboundGroupSession(); try { outboundGroupSession.create(); @@ -418,8 +444,15 @@ class KeyManager { 'session_id': outboundGroupSession.session_id(), 'session_key': outboundGroupSession.session_key(), }; + final allowedAtIndex = >{}; + for (final device in deviceKeys) { + allowedAtIndex[device.userId] ??= {}; + allowedAtIndex[device.userId][device.deviceId] = + outboundGroupSession.message_index(); + } setInboundGroupSession( - roomId, rawSession['session_id'], encryption.identityKey, rawSession); + roomId, rawSession['session_id'], encryption.identityKey, rawSession, + allowedAtIndex: allowedAtIndex); final sess = OutboundGroupSession( devices: deviceKeyIds, creationTime: DateTime.now(), @@ -428,7 +461,6 @@ class KeyManager { key: client.userID, ); try { - deviceKeys.removeWhere((k) => k.blocked); await client.sendToDeviceEncrypted(deviceKeys, 'm.room_key', rawSession); await storeOutboundGroupSession(roomId, sess); _outboundGroupSessions[roomId] = sess; @@ -736,8 +768,9 @@ class KeyManager { final sessionId = event.content['body']['session_id']; final senderKey = event.content['body']['sender_key']; // okay, let's see if we have this session at all - if ((await loadInboundGroupSession(room.id, sessionId, senderKey)) == - null) { + final session = + await loadInboundGroupSession(room.id, sessionId, senderKey); + if (session == null) { Logs().i('[KeyManager] Unknown session, ignoring'); return; // we don't have this session anyways } @@ -761,6 +794,18 @@ class KeyManager { Logs().i('[KeyManager] All checks out, forwarding key...'); // alright, we can forward the key await roomKeyRequest.forwardKey(); + } else if (!device.blocked && + session.allowedAtIndex + .tryGet>(device.userId) + ?.tryGet(device.deviceId) != + null) { + // if we know the user may see the message, then we can just forward the key. + // we do not need to check if the device is verified, just if it is not blocked, + // as that is the logic we already initially try to send out the room keys. + final index = session.allowedAtIndex[device.userId][device.deviceId]; + Logs().i( + '[KeyManager] Valid foreign request, forwarding key at index $index...'); + await roomKeyRequest.forwardKey(index); } else { Logs() .i('[KeyManager] Asking client, if the key should be forwarded'); @@ -903,7 +948,7 @@ class RoomKeyRequest extends ToDeviceEvent { DeviceKeys get requestingDevice => request.devices.first; - Future forwardKey() async { + Future forwardKey([int index]) async { if (request.canceled) { keyManager.incomingShareRequests.remove(request.requestId); return; // request is canceled, don't send anything @@ -924,8 +969,8 @@ class RoomKeyRequest extends ToDeviceEvent { (session.forwardingCurve25519KeyChain.isEmpty ? keyManager.encryption.fingerprintKey : null); - message['session_key'] = session.inboundGroupSession - .export_session(session.inboundGroupSession.first_known_index()); + message['session_key'] = session.inboundGroupSession.export_session( + index ?? session.inboundGroupSession.first_known_index()); // send the actual reply of the key back to the requester await keyManager.client.sendToDeviceEncrypted( [requestingDevice], diff --git a/lib/encryption/utils/session_key.dart b/lib/encryption/utils/session_key.dart index 6ffc9bf5..f2f18e1b 100644 --- a/lib/encryption/utils/session_key.dart +++ b/lib/encryption/utils/session_key.dart @@ -16,8 +16,6 @@ * along with this program. If not, see . */ -import 'dart:convert'; - import 'package:olm/olm.dart' as olm; import '../../famedlysdk.dart'; @@ -25,19 +23,42 @@ import '../../src/database/database.dart' show DbInboundGroupSession; import '../../matrix_api/utils/logs.dart'; class SessionKey { + /// The raw json content of the key Map content; + + /// Map of stringified-index to event id, so that we can detect replay attacks Map indexes; + + /// Map of userId to map of deviceId to index, that we know that device receivied, e.g. sending it ourself. + /// Used for automatically answering key requests + Map> allowedAtIndex; + + /// Underlying olm [InboundGroupSession] object olm.InboundGroupSession inboundGroupSession; + + /// Key for libolm pickle / unpickle final String key; + + /// Forwarding keychain List get forwardingCurve25519KeyChain => (content['forwarding_curve25519_key_chain'] != null ? List.from(content['forwarding_curve25519_key_chain']) : null) ?? []; + + /// Claimed keys of the original sender Map senderClaimedKeys; + + /// Sender curve25519 key String senderKey; + + /// Is this session valid? bool get isValid => inboundGroupSession != null; + + /// roomId for this session String roomId; + + /// Id of this session String sessionId; SessionKey( @@ -45,17 +66,22 @@ class SessionKey { this.inboundGroupSession, this.key, this.indexes, + this.allowedAtIndex, this.roomId, this.sessionId, String senderKey, Map senderClaimedKeys}) { _setSenderKey(senderKey); _setSenderClaimedKeys(senderClaimedKeys); + indexes ??= {}; + allowedAtIndex ??= >{}; } SessionKey.fromDb(DbInboundGroupSession dbEntry, String key) : key = key { final parsedContent = Event.getMapFromPayload(dbEntry.content); final parsedIndexes = Event.getMapFromPayload(dbEntry.indexes); + final parsedAllowedAtIndex = + Event.getMapFromPayload(dbEntry.allowedAtIndex); final parsedSenderClaimedKeys = Event.getMapFromPayload(dbEntry.senderClaimedKeys); content = @@ -68,6 +94,14 @@ class SessionKey { } catch (e) { indexes = {}; } + try { + allowedAtIndex = parsedAllowedAtIndex != null + ? Map>.from(parsedAllowedAtIndex + .map((k, v) => MapEntry(k, Map.from(v)))) + : >{}; + } catch (e) { + allowedAtIndex = >{}; + } roomId = dbEntry.roomId; sessionId = dbEntry.sessionId; _setSenderKey(dbEntry.senderKey); @@ -98,23 +132,8 @@ class SessionKey { : {})); } - Map toJson() { - final data = {}; - if (content != null) { - data['content'] = content; - } - if (indexes != null) { - data['indexes'] = indexes; - } - data['inboundGroupSession'] = inboundGroupSession.pickle(key); - return data; - } - void dispose() { inboundGroupSession?.free(); inboundGroupSession = null; } - - @override - String toString() => json.encode(toJson()); } diff --git a/lib/src/database/database.dart b/lib/src/database/database.dart index caa7b387..294d7120 100644 --- a/lib/src/database/database.dart +++ b/lib/src/database/database.dart @@ -54,7 +54,7 @@ class Database extends _$Database { Database.connect(DatabaseConnection connection) : super.connect(connection); @override - int get schemaVersion => 7; + int get schemaVersion => 8; int get maxFileSize => 1 * 1024 * 1024; @@ -136,6 +136,12 @@ class Database extends _$Database { await delete(rooms).go(); await delete(outboundGroupSessions).go(); await customStatement('UPDATE clients SET prev_batch = null'); + from++; + } + if (from == 7) { + await m.addColumnIfNotExists( + inboundGroupSessions, inboundGroupSessions.allowedAtIndex); + from++; } } catch (e, s) { Logs().e('Database migration failed', e, s); diff --git a/lib/src/database/database.g.dart b/lib/src/database/database.g.dart index fba8593b..85cc7538 100644 --- a/lib/src/database/database.g.dart +++ b/lib/src/database/database.g.dart @@ -2262,6 +2262,7 @@ class DbInboundGroupSession extends DataClass final String pickle; final String content; final String indexes; + final String allowedAtIndex; final bool uploaded; final String senderKey; final String senderClaimedKeys; @@ -2272,6 +2273,7 @@ class DbInboundGroupSession extends DataClass @required this.pickle, this.content, this.indexes, + this.allowedAtIndex, this.uploaded, this.senderKey, this.senderClaimedKeys}); @@ -2295,6 +2297,8 @@ class DbInboundGroupSession extends DataClass stringType.mapFromDatabaseResponse(data['${effectivePrefix}content']), indexes: stringType.mapFromDatabaseResponse(data['${effectivePrefix}indexes']), + allowedAtIndex: stringType + .mapFromDatabaseResponse(data['${effectivePrefix}allowed_at_index']), uploaded: boolType.mapFromDatabaseResponse(data['${effectivePrefix}uploaded']), senderKey: stringType @@ -2324,6 +2328,9 @@ class DbInboundGroupSession extends DataClass if (!nullToAbsent || indexes != null) { map['indexes'] = Variable(indexes); } + if (!nullToAbsent || allowedAtIndex != null) { + map['allowed_at_index'] = Variable(allowedAtIndex); + } if (!nullToAbsent || uploaded != null) { map['uploaded'] = Variable(uploaded); } @@ -2354,6 +2361,9 @@ class DbInboundGroupSession extends DataClass indexes: indexes == null && nullToAbsent ? const Value.absent() : Value(indexes), + allowedAtIndex: allowedAtIndex == null && nullToAbsent + ? const Value.absent() + : Value(allowedAtIndex), uploaded: uploaded == null && nullToAbsent ? const Value.absent() : Value(uploaded), @@ -2376,6 +2386,7 @@ class DbInboundGroupSession extends DataClass pickle: serializer.fromJson(json['pickle']), content: serializer.fromJson(json['content']), indexes: serializer.fromJson(json['indexes']), + allowedAtIndex: serializer.fromJson(json['allowed_at_index']), uploaded: serializer.fromJson(json['uploaded']), senderKey: serializer.fromJson(json['sender_key']), senderClaimedKeys: @@ -2392,6 +2403,7 @@ class DbInboundGroupSession extends DataClass 'pickle': serializer.toJson(pickle), 'content': serializer.toJson(content), 'indexes': serializer.toJson(indexes), + 'allowed_at_index': serializer.toJson(allowedAtIndex), 'uploaded': serializer.toJson(uploaded), 'sender_key': serializer.toJson(senderKey), 'sender_claimed_keys': serializer.toJson(senderClaimedKeys), @@ -2405,6 +2417,7 @@ class DbInboundGroupSession extends DataClass String pickle, String content, String indexes, + String allowedAtIndex, bool uploaded, String senderKey, String senderClaimedKeys}) => @@ -2415,6 +2428,7 @@ class DbInboundGroupSession extends DataClass pickle: pickle ?? this.pickle, content: content ?? this.content, indexes: indexes ?? this.indexes, + allowedAtIndex: allowedAtIndex ?? this.allowedAtIndex, uploaded: uploaded ?? this.uploaded, senderKey: senderKey ?? this.senderKey, senderClaimedKeys: senderClaimedKeys ?? this.senderClaimedKeys, @@ -2428,6 +2442,7 @@ class DbInboundGroupSession extends DataClass ..write('pickle: $pickle, ') ..write('content: $content, ') ..write('indexes: $indexes, ') + ..write('allowedAtIndex: $allowedAtIndex, ') ..write('uploaded: $uploaded, ') ..write('senderKey: $senderKey, ') ..write('senderClaimedKeys: $senderClaimedKeys') @@ -2449,9 +2464,11 @@ class DbInboundGroupSession extends DataClass $mrjc( indexes.hashCode, $mrjc( - uploaded.hashCode, - $mrjc(senderKey.hashCode, - senderClaimedKeys.hashCode))))))))); + allowedAtIndex.hashCode, + $mrjc( + uploaded.hashCode, + $mrjc(senderKey.hashCode, + senderClaimedKeys.hashCode)))))))))); @override bool operator ==(dynamic other) => identical(this, other) || @@ -2462,6 +2479,7 @@ class DbInboundGroupSession extends DataClass other.pickle == this.pickle && other.content == this.content && other.indexes == this.indexes && + other.allowedAtIndex == this.allowedAtIndex && other.uploaded == this.uploaded && other.senderKey == this.senderKey && other.senderClaimedKeys == this.senderClaimedKeys); @@ -2475,6 +2493,7 @@ class InboundGroupSessionsCompanion final Value pickle; final Value content; final Value indexes; + final Value allowedAtIndex; final Value uploaded; final Value senderKey; final Value senderClaimedKeys; @@ -2485,6 +2504,7 @@ class InboundGroupSessionsCompanion this.pickle = const Value.absent(), this.content = const Value.absent(), this.indexes = const Value.absent(), + this.allowedAtIndex = const Value.absent(), this.uploaded = const Value.absent(), this.senderKey = const Value.absent(), this.senderClaimedKeys = const Value.absent(), @@ -2496,6 +2516,7 @@ class InboundGroupSessionsCompanion @required String pickle, this.content = const Value.absent(), this.indexes = const Value.absent(), + this.allowedAtIndex = const Value.absent(), this.uploaded = const Value.absent(), this.senderKey = const Value.absent(), this.senderClaimedKeys = const Value.absent(), @@ -2510,6 +2531,7 @@ class InboundGroupSessionsCompanion Expression pickle, Expression content, Expression indexes, + Expression allowedAtIndex, Expression uploaded, Expression senderKey, Expression senderClaimedKeys, @@ -2521,6 +2543,7 @@ class InboundGroupSessionsCompanion if (pickle != null) 'pickle': pickle, if (content != null) 'content': content, if (indexes != null) 'indexes': indexes, + if (allowedAtIndex != null) 'allowed_at_index': allowedAtIndex, if (uploaded != null) 'uploaded': uploaded, if (senderKey != null) 'sender_key': senderKey, if (senderClaimedKeys != null) 'sender_claimed_keys': senderClaimedKeys, @@ -2534,6 +2557,7 @@ class InboundGroupSessionsCompanion Value pickle, Value content, Value indexes, + Value allowedAtIndex, Value uploaded, Value senderKey, Value senderClaimedKeys}) { @@ -2544,6 +2568,7 @@ class InboundGroupSessionsCompanion pickle: pickle ?? this.pickle, content: content ?? this.content, indexes: indexes ?? this.indexes, + allowedAtIndex: allowedAtIndex ?? this.allowedAtIndex, uploaded: uploaded ?? this.uploaded, senderKey: senderKey ?? this.senderKey, senderClaimedKeys: senderClaimedKeys ?? this.senderClaimedKeys, @@ -2571,6 +2596,9 @@ class InboundGroupSessionsCompanion if (indexes.present) { map['indexes'] = Variable(indexes.value); } + if (allowedAtIndex.present) { + map['allowed_at_index'] = Variable(allowedAtIndex.value); + } if (uploaded.present) { map['uploaded'] = Variable(uploaded.value); } @@ -2592,6 +2620,7 @@ class InboundGroupSessionsCompanion ..write('pickle: $pickle, ') ..write('content: $content, ') ..write('indexes: $indexes, ') + ..write('allowedAtIndex: $allowedAtIndex, ') ..write('uploaded: $uploaded, ') ..write('senderKey: $senderKey, ') ..write('senderClaimedKeys: $senderClaimedKeys') @@ -2653,6 +2682,16 @@ class InboundGroupSessions extends Table $customConstraints: ''); } + final VerificationMeta _allowedAtIndexMeta = + const VerificationMeta('allowedAtIndex'); + GeneratedTextColumn _allowedAtIndex; + GeneratedTextColumn get allowedAtIndex => + _allowedAtIndex ??= _constructAllowedAtIndex(); + GeneratedTextColumn _constructAllowedAtIndex() { + return GeneratedTextColumn('allowed_at_index', $tableName, true, + $customConstraints: ''); + } + final VerificationMeta _uploadedMeta = const VerificationMeta('uploaded'); GeneratedBoolColumn _uploaded; GeneratedBoolColumn get uploaded => _uploaded ??= _constructUploaded(); @@ -2688,6 +2727,7 @@ class InboundGroupSessions extends Table pickle, content, indexes, + allowedAtIndex, uploaded, senderKey, senderClaimedKeys @@ -2736,6 +2776,12 @@ class InboundGroupSessions extends Table context.handle(_indexesMeta, indexes.isAcceptableOrUnknown(data['indexes'], _indexesMeta)); } + if (data.containsKey('allowed_at_index')) { + context.handle( + _allowedAtIndexMeta, + allowedAtIndex.isAcceptableOrUnknown( + data['allowed_at_index'], _allowedAtIndexMeta)); + } if (data.containsKey('uploaded')) { context.handle(_uploadedMeta, uploaded.isAcceptableOrUnknown(data['uploaded'], _uploadedMeta)); @@ -6293,10 +6339,11 @@ abstract class _$Database extends GeneratedDatabase { String pickle, String content, String indexes, + String allowed_at_index, String sender_key, String sender_claimed_keys) { return customInsert( - '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)', + 'INSERT OR REPLACE INTO inbound_group_sessions (client_id, room_id, session_id, pickle, content, indexes, allowed_at_index, sender_key, sender_claimed_keys) VALUES (:client_id, :room_id, :session_id, :pickle, :content, :indexes, :allowed_at_index, :sender_key, :sender_claimed_keys)', variables: [ Variable.withInt(client_id), Variable.withString(room_id), @@ -6304,6 +6351,7 @@ abstract class _$Database extends GeneratedDatabase { Variable.withString(pickle), Variable.withString(content), Variable.withString(indexes), + Variable.withString(allowed_at_index), Variable.withString(sender_key), Variable.withString(sender_claimed_keys) ], @@ -6326,6 +6374,21 @@ abstract class _$Database extends GeneratedDatabase { ); } + Future updateInboundGroupSessionAllowedAtIndex(String allowed_at_index, + int client_id, String room_id, String session_id) { + return customUpdate( + 'UPDATE inbound_group_sessions SET allowed_at_index = :allowed_at_index WHERE client_id = :client_id AND room_id = :room_id AND session_id = :session_id', + variables: [ + Variable.withString(allowed_at_index), + Variable.withInt(client_id), + Variable.withString(room_id), + Variable.withString(session_id) + ], + updates: {inboundGroupSessions}, + updateKind: UpdateKind.update, + ); + } + Selectable getInboundGroupSessionsToUpload() { return customSelect( 'SELECT * FROM inbound_group_sessions WHERE uploaded = false LIMIT 500', diff --git a/lib/src/database/database.moor b/lib/src/database/database.moor index 32d9eb01..427d7b60 100644 --- a/lib/src/database/database.moor +++ b/lib/src/database/database.moor @@ -71,6 +71,7 @@ CREATE TABLE inbound_group_sessions ( pickle TEXT NOT NULL, content TEXT, indexes TEXT, + allowed_at_index TEXT, uploaded BOOLEAN DEFAULT false, sender_key TEXT, sender_claimed_keys TEXT, @@ -189,8 +190,9 @@ removeOutboundGroupSession: DELETE FROM outbound_group_sessions WHERE client_id dbGetInboundGroupSessionKey: SELECT * FROM inbound_group_sessions WHERE client_id = :client_id AND room_id = :room_id AND session_id = :session_id; dbGetInboundGroupSessionKeys: SELECT * FROM inbound_group_sessions WHERE client_id = :client_id AND room_id = :room_id; 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); +storeInboundGroupSession: INSERT OR REPLACE INTO inbound_group_sessions (client_id, room_id, session_id, pickle, content, indexes, allowed_at_index, sender_key, sender_claimed_keys) VALUES (:client_id, :room_id, :session_id, :pickle, :content, :indexes, :allowed_at_index, :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; +updateInboundGroupSessionAllowedAtIndex: UPDATE inbound_group_sessions SET allowed_at_index = :allowed_at_index 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; markInboundGroupSessionsAsNeedingUpload: UPDATE inbound_group_sessions SET uploaded = false WHERE client_id = :client_id; diff --git a/test/encryption/key_manager_test.dart b/test/encryption/key_manager_test.dart index e57ba6ca..9405020d 100644 --- a/test/encryption/key_manager_test.dart +++ b/test/encryption/key_manager_test.dart @@ -106,11 +106,11 @@ void main() { expect( client.encryption.keyManager.getOutboundGroupSession(roomId) != null, true); - expect( - client.encryption.keyManager.getInboundGroupSession(roomId, - sess.outboundGroupSession.session_id(), client.identityKey) != - null, - true); + var inbound = client.encryption.keyManager.getInboundGroupSession( + roomId, sess.outboundGroupSession.session_id(), client.identityKey); + expect(inbound != null, true); + expect(inbound.allowedAtIndex['@alice:example.com']['JLAFKJWSCS'], 0); + expect(inbound.allowedAtIndex['@alice:example.com']['OTHERDEVICE'], 0); // rotate after too many messages sess.sentMessages = 300; @@ -157,6 +157,8 @@ void main() { // do not rotate if new device is added sess = await client.encryption.keyManager.createOutboundGroupSession(roomId); + sess.outboundGroupSession.encrypt( + 'foxies'); // so that the new device will have a different index client.userDeviceKeys['@alice:example.com'].deviceKeys['NEWDEVICE'] = DeviceKeys.fromJson({ 'user_id': '@alice:example.com', @@ -175,6 +177,11 @@ void main() { expect( client.encryption.keyManager.getOutboundGroupSession(roomId) != null, true); + inbound = client.encryption.keyManager.getInboundGroupSession( + roomId, sess.outboundGroupSession.session_id(), client.identityKey); + expect(inbound.allowedAtIndex['@alice:example.com']['JLAFKJWSCS'], 0); + expect(inbound.allowedAtIndex['@alice:example.com']['OTHERDEVICE'], 0); + expect(inbound.allowedAtIndex['@alice:example.com']['NEWDEVICE'], 1); // do not rotate if new user is added member.content['membership'] = 'leave'; diff --git a/test/encryption/key_request_test.dart b/test/encryption/key_request_test.dart index 132426f1..3213cec0 100644 --- a/test/encryption/key_request_test.dart +++ b/test/encryption/key_request_test.dart @@ -91,6 +91,9 @@ void main() { await matrix .userDeviceKeys['@alice:example.com'].deviceKeys['OTHERDEVICE'] .setVerified(true); + final session = await matrix.encryption.keyManager + .loadInboundGroupSession( + '!726s6s6q:example.com', validSessionId, validSenderKey); // test a successful share var event = ToDeviceEvent( sender: '@alice:example.com', @@ -113,8 +116,58 @@ void main() { (k) => k.startsWith('/client/r0/sendToDevice/m.room.encrypted')), true); + // test a successful foreign share + FakeMatrixApi.calledEndpoints.clear(); + session.allowedAtIndex['@test:fakeServer.notExisting'] = { + 'OTHERDEVICE': 0, + }; + event = ToDeviceEvent( + sender: '@test:fakeServer.notExisting', + type: 'm.room_key_request', + content: { + 'action': 'request', + 'body': { + 'algorithm': AlgorithmTypes.megolmV1AesSha2, + 'room_id': '!726s6s6q:example.com', + 'sender_key': validSenderKey, + 'session_id': validSessionId, + }, + 'request_id': 'request_a1', + 'requesting_device_id': 'OTHERDEVICE', + }); + await matrix.encryption.keyManager.handleToDeviceEvent(event); + Logs().i(FakeMatrixApi.calledEndpoints.keys.toString()); + expect( + FakeMatrixApi.calledEndpoints.keys.any( + (k) => k.startsWith('/client/r0/sendToDevice/m.room.encrypted')), + true); + session.allowedAtIndex.remove('@test:fakeServer.notExisting'); + // test various fail scenarios + // unknown person + FakeMatrixApi.calledEndpoints.clear(); + event = ToDeviceEvent( + sender: '@test:fakeServer.notExisting', + type: 'm.room_key_request', + content: { + 'action': 'request', + 'body': { + 'algorithm': AlgorithmTypes.megolmV1AesSha2, + 'room_id': '!726s6s6q:example.com', + 'sender_key': validSenderKey, + 'session_id': validSessionId, + }, + 'request_id': 'request_a2', + 'requesting_device_id': 'OTHERDEVICE', + }); + await matrix.encryption.keyManager.handleToDeviceEvent(event); + Logs().i(FakeMatrixApi.calledEndpoints.keys.toString()); + expect( + FakeMatrixApi.calledEndpoints.keys.any( + (k) => k.startsWith('/client/r0/sendToDevice/m.room.encrypted')), + false); + // no body FakeMatrixApi.calledEndpoints.clear(); event = ToDeviceEvent(