diff --git a/lib/encryption/encryption.dart b/lib/encryption/encryption.dart index bb76912..5e343e4 100644 --- a/lib/encryption/encryption.dart +++ b/lib/encryption/encryption.dart @@ -111,6 +111,7 @@ class Encryption { if (event.type != EventTypes.Encrypted || event.content['ciphertext'] == null) return event; Map decryptedPayload; + var canRequestSession = false; try { if (event.content['algorithm'] != 'm.megolm.v1.aes-sha2') { throw (DecryptError.UNKNOWN_ALGORITHM); @@ -120,6 +121,7 @@ class Encryption { final inboundGroupSession = keyManager.getInboundGroupSession(roomId, sessionId, senderKey); if (inboundGroupSession == null) { + canRequestSession = true; throw (DecryptError.UNKNOWN_SESSION); } final decryptResult = inboundGroupSession.inboundGroupSession @@ -146,6 +148,8 @@ class Encryption { roomId, sessionId); } + // decrypt errors here may mean we have a bad session key - others might have a better one + canRequestSession = true; decryptedPayload = json.decode(decryptResult.plaintext); } catch (exception) { // alright, if this was actually by our own outbound group session, we might as well clear it @@ -158,13 +162,14 @@ class Encryption { event.content['session_id']) { keyManager.clearOutboundGroupSession(roomId, wipe: true); } - if (exception.toString() == DecryptError.UNKNOWN_SESSION) { + if (canRequestSession) { decryptedPayload = { 'content': event.content, 'type': EventTypes.Encrypted, }; decryptedPayload['content']['body'] = exception.toString(); decryptedPayload['content']['msgtype'] = 'm.bad.encrypted'; + decryptedPayload['content']['can_request_session'] = true; } else { decryptedPayload = { 'content': { diff --git a/lib/encryption/key_manager.dart b/lib/encryption/key_manager.dart index 3409b94..08ed064 100644 --- a/lib/encryption/key_manager.dart +++ b/lib/encryption/key_manager.dart @@ -66,9 +66,6 @@ class KeyManager { {bool forwarded = false}) { final oldSession = getInboundGroupSession(roomId, sessionId, senderKey, otherRooms: false); - if (oldSession != null) { - return; - } if (content['algorithm'] != 'm.megolm.v1.aes-sha2') { return; } @@ -86,15 +83,31 @@ class KeyManager { '[LibOlm] Could not create new InboundGroupSession: ' + e.toString()); return; } - if (!_inboundGroupSessions.containsKey(roomId)) { - _inboundGroupSessions[roomId] = {}; - } - _inboundGroupSessions[roomId][sessionId] = SessionKey( + final newSession = SessionKey( content: content, inboundGroupSession: inboundGroupSession, indexes: {}, key: client.userID, ); + final oldFirstIndex = + oldSession?.inboundGroupSession?.first_known_index() ?? 0; + final newFirstIndex = newSession.inboundGroupSession.first_known_index(); + if (oldSession == null || + newFirstIndex < oldFirstIndex || + (oldFirstIndex == newFirstIndex && + newSession.forwardingCurve25519KeyChain.length < + oldSession.forwardingCurve25519KeyChain.length)) { + // use new session + oldSession?.dispose(); + } else { + // we are gonna keep our old session + newSession.dispose(); + return; + } + if (!_inboundGroupSessions.containsKey(roomId)) { + _inboundGroupSessions[roomId] = {}; + } + _inboundGroupSessions[roomId][sessionId] = newSession; client.database?.storeInboundGroupSession( client.id, roomId, diff --git a/lib/matrix_api/matrix_api.dart b/lib/matrix_api/matrix_api.dart index aac4081..6f3bb81 100644 --- a/lib/matrix_api/matrix_api.dart +++ b/lib/matrix_api/matrix_api.dart @@ -1577,6 +1577,7 @@ class MatrixApi { await request( RequestType.POST, '/client/r0/pushers/set', + data: data, ); return; } diff --git a/lib/matrix_api/model/open_id_credentials.dart b/lib/matrix_api/model/open_id_credentials.dart index 6d9f6f0..1d2902b 100644 --- a/lib/matrix_api/model/open_id_credentials.dart +++ b/lib/matrix_api/model/open_id_credentials.dart @@ -20,7 +20,7 @@ class OpenIdCredentials { String accessToken; String tokenType; String matrixServerName; - int expiresIn; + double expiresIn; OpenIdCredentials.fromJson(Map json) { accessToken = json['access_token']; diff --git a/lib/src/event.dart b/lib/src/event.dart index fd2ef27..bb6a637 100644 --- a/lib/src/event.dart +++ b/lib/src/event.dart @@ -341,8 +341,8 @@ class Event extends MatrixEvent { Future requestKey() async { if (type != EventTypes.Encrypted || messageType != MessageTypes.BadEncrypted || - content['body'] != DecryptError.UNKNOWN_SESSION) { - throw ('Session key not unknown'); + content['can_request_session'] != true) { + throw ('Session key not requestable'); } await room.requestSessionKey(content['session_id'], content['sender_key']); return; diff --git a/lib/src/room.dart b/lib/src/room.dart index b0af9b1..6a1c2e1 100644 --- a/lib/src/room.dart +++ b/lib/src/room.dart @@ -19,7 +19,6 @@ import 'dart:async'; import 'package:famedlysdk/matrix_api.dart'; -import 'package:famedlysdk/encryption.dart'; import 'package:famedlysdk/famedlysdk.dart'; import 'package:famedlysdk/src/client.dart'; import 'package:famedlysdk/src/event.dart'; @@ -998,7 +997,7 @@ class Room { await client.database.transaction(() async { for (var i = 0; i < events.length; i++) { if (events[i].type == EventTypes.Encrypted && - events[i].content['body'] == DecryptError.UNKNOWN_SESSION) { + events[i].content['can_request_session'] == true) { events[i] = await client.encryption .decryptRoomEvent(id, events[i], store: true); } diff --git a/lib/src/timeline.dart b/lib/src/timeline.dart index e7497c1..63388dd 100644 --- a/lib/src/timeline.dart +++ b/lib/src/timeline.dart @@ -19,7 +19,6 @@ import 'dart:async'; import 'package:famedlysdk/matrix_api.dart'; -import 'package:famedlysdk/encryption.dart'; import 'event.dart'; import 'room.dart'; @@ -104,7 +103,7 @@ class Timeline { for (var i = 0; i < events.length; i++) { if (events[i].type == EventTypes.Encrypted && events[i].messageType == MessageTypes.BadEncrypted && - events[i].content['body'] == DecryptError.UNKNOWN_SESSION && + events[i].content['can_request_session'] == true && events[i].content['session_id'] == sessionId) { events[i] = await room.client.encryption .decryptRoomEvent(room.id, events[i], store: true); diff --git a/test/encryption/key_manager_test.dart b/test/encryption/key_manager_test.dart index aba2c34..5b3025f 100644 --- a/test/encryption/key_manager_test.dart +++ b/test/encryption/key_manager_test.dart @@ -216,6 +216,148 @@ void main() { true); }); + test('setInboundGroupSession', () 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, + }; + client.encryption.keyManager.setInboundGroupSession( + roomId, sessionId, senderKey, sessionPayload, + forwarded: true); + expect( + client.encryption.keyManager + .getInboundGroupSession(roomId, sessionId, senderKey) + .inboundGroupSession + .first_known_index(), + 1); + expect( + client.encryption.keyManager + .getInboundGroupSession(roomId, sessionId, senderKey) + .forwardingCurve25519KeyChain + .length, + 1); + + // not set one with a higher first known index + sessionPayload = { + 'algorithm': 'm.megolm.v1.aes-sha2', + 'room_id': roomId, + 'forwarding_curve25519_key_chain': [client.identityKey], + 'session_id': sessionId, + 'session_key': inbound.export_session(2), + 'sender_key': senderKey, + 'sender_claimed_ed25519_key': client.fingerprintKey, + }; + client.encryption.keyManager.setInboundGroupSession( + roomId, sessionId, senderKey, sessionPayload, + forwarded: true); + expect( + client.encryption.keyManager + .getInboundGroupSession(roomId, sessionId, senderKey) + .inboundGroupSession + .first_known_index(), + 1); + expect( + client.encryption.keyManager + .getInboundGroupSession(roomId, sessionId, senderKey) + .forwardingCurve25519KeyChain + .length, + 1); + + // set one with a lower first known index + sessionPayload = { + 'algorithm': 'm.megolm.v1.aes-sha2', + 'room_id': roomId, + 'forwarding_curve25519_key_chain': [client.identityKey], + 'session_id': sessionId, + 'session_key': inbound.export_session(0), + 'sender_key': senderKey, + 'sender_claimed_ed25519_key': client.fingerprintKey, + }; + client.encryption.keyManager.setInboundGroupSession( + roomId, sessionId, senderKey, sessionPayload, + forwarded: true); + expect( + client.encryption.keyManager + .getInboundGroupSession(roomId, sessionId, senderKey) + .inboundGroupSession + .first_known_index(), + 0); + expect( + client.encryption.keyManager + .getInboundGroupSession(roomId, sessionId, senderKey) + .forwardingCurve25519KeyChain + .length, + 1); + + // not set one with a longer forwarding chain + sessionPayload = { + 'algorithm': 'm.megolm.v1.aes-sha2', + 'room_id': roomId, + 'forwarding_curve25519_key_chain': [client.identityKey, 'beep'], + 'session_id': sessionId, + 'session_key': inbound.export_session(0), + 'sender_key': senderKey, + 'sender_claimed_ed25519_key': client.fingerprintKey, + }; + client.encryption.keyManager.setInboundGroupSession( + roomId, sessionId, senderKey, sessionPayload, + forwarded: true); + expect( + client.encryption.keyManager + .getInboundGroupSession(roomId, sessionId, senderKey) + .inboundGroupSession + .first_known_index(), + 0); + expect( + client.encryption.keyManager + .getInboundGroupSession(roomId, sessionId, senderKey) + .forwardingCurve25519KeyChain + .length, + 1); + + // set one with a shorter forwarding chain + sessionPayload = { + 'algorithm': 'm.megolm.v1.aes-sha2', + 'room_id': roomId, + 'forwarding_curve25519_key_chain': [], + 'session_id': sessionId, + 'session_key': inbound.export_session(0), + 'sender_key': senderKey, + 'sender_claimed_ed25519_key': client.fingerprintKey, + }; + client.encryption.keyManager.setInboundGroupSession( + roomId, sessionId, senderKey, sessionPayload, + forwarded: true); + expect( + client.encryption.keyManager + .getInboundGroupSession(roomId, sessionId, senderKey) + .inboundGroupSession + .first_known_index(), + 0); + expect( + client.encryption.keyManager + .getInboundGroupSession(roomId, sessionId, senderKey) + .forwardingCurve25519KeyChain + .length, + 0); + + inbound.free(); + session.free(); + }); + test('dispose client', () async { await client.dispose(closeDatabase: true); }); diff --git a/test/event_test.dart b/test/event_test.dart index 90b3223..035b55b 100644 --- a/test/event_test.dart +++ b/test/event_test.dart @@ -237,9 +237,9 @@ void main() { try { await event.requestKey(); } catch (e) { - exception = e; + exception = e.toString(); } - expect(exception, 'Session key not unknown'); + expect(exception, 'Session key not requestable'); var event2 = Event.fromJson({ 'event_id': id, @@ -251,6 +251,7 @@ void main() { 'content': json.encode({ 'msgtype': 'm.bad.encrypted', 'body': DecryptError.UNKNOWN_SESSION, + 'can_request_session': true, 'algorithm': 'm.megolm.v1.aes-sha2', 'ciphertext': 'AwgAEnACgAkLmt6qF84IK++J7UDH2Za1YVchHyprqTqsg...', 'device_id': 'RJYKSTBOIE', diff --git a/test/fake_matrix_api.dart b/test/fake_matrix_api.dart index f2b2d54..4e3eb08 100644 --- a/test/fake_matrix_api.dart +++ b/test/fake_matrix_api.dart @@ -1707,7 +1707,7 @@ class FakeMatrixApi extends MockClient { 'access_token': 'SomeT0kenHere', 'token_type': 'Bearer', 'matrix_server_name': 'example.com', - 'expires_in': 3600 + 'expires_in': 3600.0 }, '/client/r0/user/@test:fakeServer.notExisting/openid/request_token': (var req) => {