[Room] Clear outbound session only if devices changed

This commit is contained in:
Christian Pauly 2020-02-27 08:41:49 +00:00
parent 73841bd2f6
commit 31b64a6631
5 changed files with 64 additions and 30 deletions

View File

@ -755,7 +755,7 @@ class Client {
sessions.forEach((olm.Session session) => session?.free()); sessions.forEach((olm.Session session) => session?.free());
}); });
rooms.forEach((Room room) { rooms.forEach((Room room) {
room.clearOutboundGroupSession(); room.clearOutboundGroupSession(wipe: true);
room.sessionKeys.values.forEach((SessionKey sessionKey) { room.sessionKeys.values.forEach((SessionKey sessionKey) {
sessionKey.inboundGroupSession?.free(); sessionKey.inboundGroupSession?.free();
}); });
@ -986,22 +986,6 @@ class Client {
} }
} }
/// Clears the outboundGroupSession from all rooms where this user is
/// participating. Should be called when the user's devices list has changed.
void _clearOutboundGroupSessionsByUserId(String userId) {
for (Room room in rooms) {
if (!room.encrypted) continue;
room.requestParticipants().then((List<User> users) {
if (users.indexWhere((u) =>
u.id == userId &&
[Membership.join, Membership.invite].contains(u.membership)) !=
-1) {
room.clearOutboundGroupSession();
}
});
}
}
void _handleDeviceListsEvents(Map<String, dynamic> deviceLists) { void _handleDeviceListsEvents(Map<String, dynamic> deviceLists) {
if (deviceLists["changed"] is List) { if (deviceLists["changed"] is List) {
for (final userId in deviceLists["changed"]) { for (final userId in deviceLists["changed"]) {
@ -1010,7 +994,6 @@ class Client {
} }
} }
for (final userId in deviceLists["left"]) { for (final userId in deviceLists["left"]) {
_clearOutboundGroupSessionsByUserId(userId);
if (_userDeviceKeys.containsKey(userId)) { if (_userDeviceKeys.containsKey(userId)) {
_userDeviceKeys.remove(userId); _userDeviceKeys.remove(userId);
} }
@ -1489,13 +1472,14 @@ class Client {
} }
} }
_userDeviceKeys[userId].outdated = false; _userDeviceKeys[userId].outdated = false;
if (_userDeviceKeys[userId].deviceKeys.toString() !=
oldKeys.toString()) {
_clearOutboundGroupSessionsByUserId(userId);
}
} }
} }
await this.storeAPI?.storeUserDeviceKeys(userDeviceKeys); await this.storeAPI?.storeUserDeviceKeys(userDeviceKeys);
rooms.forEach((Room room) {
if (room.encrypted) {
room.clearOutboundGroupSession();
}
});
} catch (e) { } catch (e) {
print("[LibOlm] Unable to update user device keys: " + e.toString()); print("[LibOlm] Unable to update user device keys: " + e.toString());
} }

View File

@ -84,12 +84,20 @@ class Room {
olm.OutboundGroupSession get outboundGroupSession => _outboundGroupSession; olm.OutboundGroupSession get outboundGroupSession => _outboundGroupSession;
olm.OutboundGroupSession _outboundGroupSession; olm.OutboundGroupSession _outboundGroupSession;
List<String> _outboundGroupSessionDevices;
/// Clears the existing outboundGroupSession, tries to create a new one and /// Clears the existing outboundGroupSession, tries to create a new one and
/// stores it as an ingoingGroupSession in the [sessionKeys]. Then sends the /// stores it as an ingoingGroupSession in the [sessionKeys]. Then sends the
/// new session encrypted with olm to all non-blocked devices using /// new session encrypted with olm to all non-blocked devices using
/// to-device-messaging. /// to-device-messaging.
Future<void> createOutboundGroupSession() async { Future<void> createOutboundGroupSession() async {
await clearOutboundGroupSession(); await clearOutboundGroupSession(wipe: true);
List<DeviceKeys> deviceKeys = await getUserDeviceKeys();
_outboundGroupSessionDevices = [];
for (DeviceKeys keys in deviceKeys) {
_outboundGroupSessionDevices.add(keys.deviceId);
}
_outboundGroupSessionDevices.sort();
try { try {
_outboundGroupSession = olm.OutboundGroupSession(); _outboundGroupSession = olm.OutboundGroupSession();
_outboundGroupSession.create(); _outboundGroupSession.create();
@ -110,7 +118,6 @@ class Room {
"session_key": _outboundGroupSession.session_key(), "session_key": _outboundGroupSession.session_key(),
}; };
setSessionKey(rawSession["session_id"], rawSession); setSessionKey(rawSession["session_id"], rawSession);
List<DeviceKeys> deviceKeys = await getUserDeviceKeys();
try { try {
await client.sendToDevice(deviceKeys, "m.room_key", rawSession); await client.sendToDevice(deviceKeys, "m.room_key", rawSession);
} catch (e) { } catch (e) {
@ -127,17 +134,35 @@ class Room {
await client.storeAPI?.setItem( await client.storeAPI?.setItem(
"/clients/${client.deviceID}/rooms/${this.id}/outbound_group_session", "/clients/${client.deviceID}/rooms/${this.id}/outbound_group_session",
_outboundGroupSession.pickle(client.userID)); _outboundGroupSession.pickle(client.userID));
await client.storeAPI?.setItem(
"/clients/${client.deviceID}/rooms/${this.id}/outbound_group_session_devices",
json.encode(_outboundGroupSessionDevices));
return; return;
} }
/// Clears the existing outboundGroupSession. /// Clears the existing outboundGroupSession but first checks if the participating
Future<void> clearOutboundGroupSession() async { /// devices have been changed. Returns false if the session has not been cleared because
/// it wasn't necessary.
Future<bool> clearOutboundGroupSession({bool wipe = false}) async {
if (!wipe && this._outboundGroupSessionDevices != null) {
List<DeviceKeys> deviceKeys = await getUserDeviceKeys();
List<String> outboundGroupSessionDevices = [];
for (DeviceKeys keys in deviceKeys) {
outboundGroupSessionDevices.add(keys.deviceId);
}
outboundGroupSessionDevices.sort();
if (outboundGroupSessionDevices.toString() ==
this._outboundGroupSessionDevices.toString()) {
return false;
}
}
this._outboundGroupSessionDevices == null;
await client.storeAPI?.setItem( await client.storeAPI?.setItem(
"/clients/${client.deviceID}/rooms/${this.id}/outbound_group_session", "/clients/${client.deviceID}/rooms/${this.id}/outbound_group_session",
null); null);
this._outboundGroupSession?.free(); this._outboundGroupSession?.free();
this._outboundGroupSession = null; this._outboundGroupSession = null;
return; return true;
} }
/// Key-Value store of session ids to the session keys. Only m.megolm.v1.aes-sha2 /// Key-Value store of session ids to the session keys. Only m.megolm.v1.aes-sha2
@ -861,6 +886,13 @@ class Room {
e.toString()); e.toString());
} }
} }
final String outboundGroupSessionDevicesString = await client.storeAPI
.getItem(
"/clients/${client.deviceID}/rooms/${this.id}/outbound_group_session_devices");
if (outboundGroupSessionDevicesString != null) {
this._outboundGroupSessionDevices =
List<String>.from(json.decode(outboundGroupSessionDevicesString));
}
final String sessionKeysPickle = await client.storeAPI final String sessionKeysPickle = await client.storeAPI
.getItem("/clients/${client.deviceID}/rooms/${this.id}/session_keys"); .getItem("/clients/${client.deviceID}/rooms/${this.id}/session_keys");
if (sessionKeysPickle?.isNotEmpty ?? false) { if (sessionKeysPickle?.isNotEmpty ?? false) {

View File

@ -563,7 +563,7 @@ void main() {
}); });
test('Test invalidate outboundGroupSessions', () async { test('Test invalidate outboundGroupSessions', () async {
if (matrix.encryptionEnabled) { if (matrix.encryptionEnabled) {
await matrix.rooms[1].clearOutboundGroupSession(); await matrix.rooms[1].clearOutboundGroupSession(wipe: true);
expect(matrix.rooms[1].outboundGroupSession == null, true); expect(matrix.rooms[1].outboundGroupSession == null, true);
await matrix.rooms[1].createOutboundGroupSession(); await matrix.rooms[1].createOutboundGroupSession();
expect(matrix.rooms[1].outboundGroupSession != null, true); expect(matrix.rooms[1].outboundGroupSession != null, true);
@ -589,7 +589,7 @@ void main() {
} }
}); });
await Future.delayed(Duration(milliseconds: 50)); await Future.delayed(Duration(milliseconds: 50));
expect(matrix.rooms[1].outboundGroupSession == null, true); expect(matrix.rooms[1].outboundGroupSession != null, true);
} }
}); });
DeviceKeys deviceKeys = DeviceKeys.fromJson({ DeviceKeys deviceKeys = DeviceKeys.fromJson({

View File

@ -603,6 +603,24 @@ class FakeMatrixApi extends MockClient {
{"type": "m.login.password"} {"type": "m.login.password"}
] ]
}, },
"/client/r0/rooms/!696r7674:example.com/members": (var req) => {
"chunk": [
{
"content": {
"membership": "join",
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
"displayname": "Alice Margatroid"
},
"type": "m.room.member",
"event_id": "§143273582443PhrSn:example.org",
"room_id": "!636q39766251:example.com",
"sender": "@alice:example.com",
"origin_server_ts": 1432735824653,
"unsigned": {"age": 1234},
"state_key": "@alice:example.com"
}
]
},
"/client/r0/rooms/!726s6s6q:example.com/members": (var req) => { "/client/r0/rooms/!726s6s6q:example.com/members": (var req) => {
"chunk": [ "chunk": [
{ {

View File

@ -406,7 +406,7 @@ void main() {
test('clearOutboundGroupSession', () async { test('clearOutboundGroupSession', () async {
if (!room.client.encryptionEnabled) return; if (!room.client.encryptionEnabled) return;
await room.clearOutboundGroupSession(); await room.clearOutboundGroupSession(wipe: true);
expect(room.outboundGroupSession == null, true); expect(room.outboundGroupSession == null, true);
}); });