From 2a6a19e2b00fdb9da2b80726200693d1d5ae2865 Mon Sep 17 00:00:00 2001 From: Sorunome Date: Fri, 12 Jun 2020 16:17:00 +0200 Subject: [PATCH] Request-ify room key store stuff --- lib/encryption/key_manager.dart | 73 ++++-------- lib/matrix_api.dart | 2 + lib/matrix_api/matrix_api.dart | 146 +++++++++++++++++++++++ lib/matrix_api/model/room_keys_info.dart | 107 +++++++++++++++++ lib/matrix_api/model/room_keys_keys.dart | 85 +++++++++++++ pubspec.lock | 18 +-- pubspec.yaml | 14 +-- 7 files changed, 382 insertions(+), 63 deletions(-) create mode 100644 lib/matrix_api/model/room_keys_info.dart create mode 100644 lib/matrix_api/model/room_keys_keys.dart diff --git a/lib/encryption/key_manager.dart b/lib/encryption/key_manager.dart index 99314e8..bd87275 100644 --- a/lib/encryption/key_manager.dart +++ b/lib/encryption/key_manager.dart @@ -43,9 +43,12 @@ class KeyManager { encryption.ssss.setValidator(MEGOLM_KEY, (String secret) async { final keyObj = olm.PkDecryption(); try { - final info = await getRoomKeysInfo(); + final info = await client.api.getRoomKeysBackup(); + if (!(info.authData is RoomKeysAuthDataV1Curve25519AesSha2)) { + return false; + } return keyObj.init_with_private_key(base64.decode(secret)) == - info['auth_data']['public_key']; + (info.authData as RoomKeysAuthDataV1Curve25519AesSha2).publicKey; } catch (_) { return false; } finally { @@ -313,13 +316,6 @@ class KeyManager { _outboundGroupSessions[roomId] = sess; } - Future> getRoomKeysInfo() async { - return await client.jsonRequest( - type: RequestType.GET, - action: '/client/r0/room_keys/version', - ); - } - Future isCached() async { if (!enabled) { return false; @@ -327,50 +323,32 @@ class KeyManager { return (await encryption.ssss.getCached(MEGOLM_KEY)) != null; } - Future loadFromResponse(Map payload) async { + Future loadFromResponse(RoomKeys keys) async { if (!(await isCached())) { return; } - if (!(payload['rooms'] is Map)) { - return; - } final privateKey = base64.decode(await encryption.ssss.getCached(MEGOLM_KEY)); final decryption = olm.PkDecryption(); - final info = await getRoomKeysInfo(); + final info = await client.api.getRoomKeysBackup(); String backupPubKey; try { backupPubKey = decryption.init_with_private_key(privateKey); if (backupPubKey == null || - !info.containsKey('auth_data') || - !(info['auth_data'] is Map) || - info['auth_data']['public_key'] != backupPubKey) { + !(info.authData is RoomKeysAuthDataV1Curve25519AesSha2) || + (info.authData as RoomKeysAuthDataV1Curve25519AesSha2).publicKey != backupPubKey) { return; } - for (final roomEntries in payload['rooms'].entries) { - final roomId = roomEntries.key; - if (!(roomEntries.value is Map) || - !(roomEntries.value['sessions'] is Map)) { - continue; - } - for (final sessionEntries in roomEntries.value['sessions'].entries) { - final sessionId = sessionEntries.key; - final rawEncryptedSession = sessionEntries.value; - if (!(rawEncryptedSession is Map)) { - continue; - } - final firstMessageIndex = - rawEncryptedSession['first_message_index'] is int - ? rawEncryptedSession['first_message_index'] - : null; - final forwardedCount = rawEncryptedSession['forwarded_count'] is int - ? rawEncryptedSession['forwarded_count'] - : null; - final isVerified = rawEncryptedSession['is_verified'] is bool - ? rawEncryptedSession['is_verified'] - : null; - final sessionData = rawEncryptedSession['session_data']; + for (final roomEntry in keys.rooms.entries) { + final roomId = roomEntry.key; + for (final sessionEntry in roomEntry.value.sessions.entries) { + final sessionId = sessionEntry.key; + final session = sessionEntry.value; + final firstMessageIndex = session.firstMessageIndex; + final forwardedCount = session.forwardedCount; + final isVerified = session.isVerified; + final sessionData = session.sessionData; if (firstMessageIndex == null || forwardedCount == null || isVerified == null || @@ -399,21 +377,18 @@ class KeyManager { } Future loadSingleKey(String roomId, String sessionId) async { - final info = await getRoomKeysInfo(); - final ret = await client.jsonRequest( - type: RequestType.GET, - action: - '/client/r0/room_keys/keys/${Uri.encodeComponent(roomId)}/${Uri.encodeComponent(sessionId)}?version=${info['version']}', - ); - await loadFromResponse({ + final info = await client.api.getRoomKeysBackup(); + final ret = await client.api.getRoomKeysSingleKey(roomId, sessionId, info.version); + final keys = RoomKeys.fromJson({ 'rooms': { roomId: { 'sessions': { - sessionId: ret, + sessionId: ret.toJson(), }, }, }, }); + await loadFromResponse(keys); } /// Request a certain key from another device @@ -422,6 +397,7 @@ class KeyManager { var hadPreviously = getInboundGroupSession(room.id, sessionId, senderKey) != null; try { + print('FETCHING FROM KEY STORE...'); await loadSingleKey(room.id, sessionId); } catch (err, stacktrace) { print('++++++++++++++++++'); @@ -430,6 +406,7 @@ class KeyManager { } if (!hadPreviously && getInboundGroupSession(room.id, sessionId, senderKey) != null) { + print('GOT FROM KEY STORE, SUCCESS!!!!!'); return; // we managed to load the session from online backup, no need to care about it now } // while we just send the to-device event to '*', we still need to save the diff --git a/lib/matrix_api.dart b/lib/matrix_api.dart index e1a22d5..be120a3 100644 --- a/lib/matrix_api.dart +++ b/lib/matrix_api.dart @@ -46,6 +46,8 @@ export 'package:famedlysdk/matrix_api/model/push_rule_set.dart'; export 'package:famedlysdk/matrix_api/model/pusher.dart'; export 'package:famedlysdk/matrix_api/model/request_token_response.dart'; export 'package:famedlysdk/matrix_api/model/room_alias_informations.dart'; +export 'package:famedlysdk/matrix_api/model/room_keys_info.dart'; +export 'package:famedlysdk/matrix_api/model/room_keys_keys.dart'; export 'package:famedlysdk/matrix_api/model/room_summary.dart'; export 'package:famedlysdk/matrix_api/model/server_capabilities.dart'; export 'package:famedlysdk/matrix_api/model/stripped_state_event.dart'; diff --git a/lib/matrix_api/matrix_api.dart b/lib/matrix_api/matrix_api.dart index 6f3bb81..081d779 100644 --- a/lib/matrix_api/matrix_api.dart +++ b/lib/matrix_api/matrix_api.dart @@ -49,6 +49,8 @@ import 'model/public_rooms_response.dart'; import 'model/push_rule_set.dart'; import 'model/pusher.dart'; import 'model/room_alias_informations.dart'; +import 'model/room_keys_info.dart'; +import 'model/room_keys_keys.dart'; import 'model/supported_protocol.dart'; import 'model/tag.dart'; import 'model/third_party_identifier.dart'; @@ -2036,4 +2038,148 @@ class MatrixApi { ); return; } + + /// Create room keys backup + /// https://matrix.org/docs/spec/client_server/unstable#post-matrix-client-r0-room-keys-version + Future createRoomKeysBackup(RoomKeysAlgorithmType algorithm, RoomKeysAuthData authData) async { + final ret = await request( + RequestType.POST, + '/client/unstable/room_keys/version', + data: { + 'algorithm': algorithm.algorithmString, + 'auth_data': authData.toJson(), + }, + ); + return ret['version']; + } + + /// Gets a room key backup + /// https://matrix.org/docs/spec/client_server/unstable#get-matrix-client-r0-room-keys-version + Future getRoomKeysBackup([String version]) async { + var url = '/client/unstable/room_keys/version'; + if (version != null) { + url += '/${Uri.encodeComponent(version)}'; + } + final ret = await request( + RequestType.GET, + url, + ); + return RoomKeysVersionResponse.fromJson(ret); + } + + /// Updates a room key backup + /// https://matrix.org/docs/spec/client_server/unstable#put-matrix-client-r0-room-keys-version-version + Future updateRoomKeysBackup(String version, RoomKeysAlgorithmType algorithm, RoomKeysAuthData authData) async { + await request( + RequestType.PUT, + '/client/unstable/room_keys/version/${Uri.encodeComponent(version)}', + data: { + 'algorithm': algorithm.algorithmString, + 'auth_data': authData.toJson, + 'version': version, + }, + ); + } + + /// Deletes a room key backup + /// https://matrix.org/docs/spec/client_server/unstable#delete-matrix-client-r0-room-keys-version-version + Future deleteRoomKeysBackup(String version) async { + await request( + RequestType.DELETE, + '/client/unstable/room_keys/version/${Uri.encodeComponent(version)}', + ); + } + + /// Stores a single room key + /// https://matrix.org/docs/spec/client_server/unstable#put-matrix-client-r0-room-keys-keys-roomid-sessionid + Future storeRoomKeysSingleKey(String roomId, String sessionId, String version, RoomKeysSingleKey session) async { + final ret = await request( + RequestType.PUT, + '/client/unstable/room_keys/keys/${Uri.encodeComponent(roomId)}/${Uri.encodeComponent(sessionId)}?version=${Uri.encodeComponent(version)}', + data: session.toJson(), + ); + return RoomKeysUpdateResponse.fromJson(ret); + } + + /// Gets a single room key + /// https://matrix.org/docs/spec/client_server/unstable#get-matrix-client-r0-room-keys-keys-roomid-sessionid + Future getRoomKeysSingleKey(String roomId, String sessionId, String version) async { + final ret = await request( + RequestType.GET, + '/client/unstable/room_keys/keys/${Uri.encodeComponent(roomId)}/${Uri.encodeComponent(sessionId)}?version=${Uri.encodeComponent(version)}', + ); + return RoomKeysSingleKey.fromJson(ret); + } + + /// Deletes a single room key + /// https://matrix.org/docs/spec/client_server/unstable#delete-matrix-client-r0-room-keys-keys-roomid-sessionid + Future deleteRoomKeysSingleKey(String roomId, String sessionId, String version) async { + final ret = await request( + RequestType.DELETE, + '/client/unstable/room_keys/keys/${Uri.encodeComponent(roomId)}/${Uri.encodeComponent(sessionId)}?version=${Uri.encodeComponent(version)}', + ); + return RoomKeysUpdateResponse.fromJson(ret); + } + + /// Stores room keys for a room + /// https://matrix.org/docs/spec/client_server/unstable#put-matrix-client-r0-room-keys-keys-roomid + Future storeRoomKeysRoom(String roomId, String version, RoomKeysRoom keys) async { + final ret = await request( + RequestType.PUT, + '/client/unstable/room_keys/keys/${Uri.encodeComponent(roomId)}?version=${Uri.encodeComponent(version)}', + data: keys.toJson(), + ); + return RoomKeysUpdateResponse.fromJson(ret); + } + + /// Gets room keys for a room + /// https://matrix.org/docs/spec/client_server/unstable#get-matrix-client-r0-room-keys-keys-roomid + Future getRoomKeysRoom(String roomId, String version) async { + final ret = await request( + RequestType.GET, + '/client/unstable/room_keys/keys/${Uri.encodeComponent(roomId)}?version=${Uri.encodeComponent(version)}', + ); + return RoomKeysRoom.fromJson(ret); + } + + /// Deletes room ekys for a room + /// https://matrix.org/docs/spec/client_server/unstable#delete-matrix-client-r0-room-keys-keys-roomid + Future deleteRoomKeysRoom(String roomId, String version) async { + final ret = await request( + RequestType.DELETE, + '/client/unstable/room_keys/keys/${Uri.encodeComponent(roomId)}?version=${Uri.encodeComponent(version)}', + ); + return RoomKeysUpdateResponse.fromJson(ret); + } + + /// Store multiple room keys + /// https://matrix.org/docs/spec/client_server/unstable#put-matrix-client-r0-room-keys-keys + Future storeRoomKeys(String version, RoomKeys keys) async { + final ret = await request( + RequestType.PUT, + '/client/unstable/room_keys/keys?version=${Uri.encodeComponent(version)}', + data: keys.toJson(), + ); + return RoomKeysUpdateResponse.fromJson(ret); + } + + /// get all room keys + /// https://matrix.org/docs/spec/client_server/unstable#get-matrix-client-r0-room-keys-keys + Future getRoomKeys(String version) async { + final ret = await request( + RequestType.GET, + '/client/unstable/room_keys/keys?version=${Uri.encodeComponent(version)}', + ); + return RoomKeys.fromJson(ret); + } + + /// delete all room keys + /// https://matrix.org/docs/spec/client_server/unstable#delete-matrix-client-r0-room-keys-keys + Future deleteRoomKeys(String version) async { + final ret = await request( + RequestType.DELETE, + '/client/unstable/room_keys/keys?version=${Uri.encodeComponent(version)}', + ); + return RoomKeysUpdateResponse.fromJson(ret); + } } diff --git a/lib/matrix_api/model/room_keys_info.dart b/lib/matrix_api/model/room_keys_info.dart new file mode 100644 index 0000000..7ed4513 --- /dev/null +++ b/lib/matrix_api/model/room_keys_info.dart @@ -0,0 +1,107 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +enum RoomKeysAlgorithmType { v1Curve25519AesSha2 } + +extension RoomKeysAlgorithmTypeExtension on RoomKeysAlgorithmType { + String get algorithmString { + switch (this) { + case RoomKeysAlgorithmType.v1Curve25519AesSha2: + return 'm.megolm_backup.v1.curve25519-aes-sha2'; + default: + return null; + } + } + + static RoomKeysAlgorithmType fromAlgorithmString(String s) { + switch (s) { + case 'm.megolm_backup.v1.curve25519-aes-sha2': + return RoomKeysAlgorithmType.v1Curve25519AesSha2; + default: + return null; + } + } +} + +abstract class RoomKeysAuthData { + // This object is used for signing so we need the raw json too + Map _json; + + RoomKeysAuthData.fromJson(Map json) { + _json = json; + } + + Map toJson() { + return _json; + } +} + +class RoomKeysAuthDataV1Curve25519AesSha2 extends RoomKeysAuthData { + String publicKey; + Map> signatures; + + RoomKeysAuthDataV1Curve25519AesSha2.fromJson(Map json) : super.fromJson(json) { + publicKey = json['public_key']; + signatures = json['signatures'] is Map + ? Map>.from((json['signatures'] as Map) + .map((k, v) => MapEntry(k, Map.from(v)))) + : null; + } + + @override + Map toJson() { + final data = super.toJson(); + data['public_key'] = publicKey; + if (signatures != null) { + data['signatures'] = signatures; + } + return data; + } +} + +class RoomKeysVersionResponse { + RoomKeysAlgorithmType algorithm; + RoomKeysAuthData authData; + int count; + String etag; + String version; + + RoomKeysVersionResponse.fromJson(Map json) { + algorithm = RoomKeysAlgorithmTypeExtension.fromAlgorithmString(json['algorithm']); + switch (algorithm) { + case RoomKeysAlgorithmType.v1Curve25519AesSha2: + authData = RoomKeysAuthDataV1Curve25519AesSha2.fromJson(json['auth_data']); + break; + default: + authData = null; + } + count = json['count']; + etag = json['etag'].toString(); // synapse replies an int but docs say string? + version = json['version']; + } + + Map toJson() { + final data = {}; + data['algorithm'] = algorithm?.algorithmString; + data['auth_data'] = authData?.toJson(); + data['count'] = count; + data['etag'] = etag; + data['version'] = version; + return data; + } +} diff --git a/lib/matrix_api/model/room_keys_keys.dart b/lib/matrix_api/model/room_keys_keys.dart new file mode 100644 index 0000000..7170f0e --- /dev/null +++ b/lib/matrix_api/model/room_keys_keys.dart @@ -0,0 +1,85 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +class RoomKeysSingleKey { + int firstMessageIndex; + int forwardedCount; + bool isVerified; + Map sessionData; + + RoomKeysSingleKey.fromJson(Map json) { + firstMessageIndex = json['first_message_index']; + forwardedCount = json['forwarded_count']; + isVerified = json['is_verified']; + sessionData = json['session_data']; + } + + Map toJson() { + final data = {}; + data['first_message_index'] = firstMessageIndex; + data['forwarded_count'] = forwardedCount; + data['is_verified'] = isVerified; + data['session_data'] = sessionData; + return data; + } +} + +class RoomKeysRoom { + Map sessions; + + RoomKeysRoom.fromJson(Map json) { + sessions = (json['sessions'] as Map).map((k, v) => MapEntry(k, RoomKeysSingleKey.fromJson(v))); + } + + Map toJson() { + final data = {}; + data['sessions'] = sessions.map((k, v) => MapEntry(k, v.toJson())); + return data; + } +} + +class RoomKeys { + Map rooms; + + RoomKeys.fromJson(Map json) { + rooms = (json['rooms'] as Map).map((k, v) => MapEntry(k, RoomKeysRoom.fromJson(v))); + } + + Map toJson() { + final data = {}; + data['rooms'] = rooms.map((k, v) => MapEntry(k, v.toJson())); + return data; + } +} + +class RoomKeysUpdateResponse { + String etag; + int count; + + RoomKeysUpdateResponse.fromJson(Map json) { + etag = json['etag']; // synapse replies an int but docs say string? + count = json['count']; + } + + Map toJson() { + final data = {}; + data['etag'] = etag; + data['count'] = count; + return data; + } +} diff --git a/pubspec.lock b/pubspec.lock index 232c25e..5c8e154 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -333,9 +333,11 @@ packages: matrix_file_e2ee: dependency: "direct main" description: - path: "/home/sorunome/repos/famedly/matrix_file_e2ee" - relative: false - source: path + path: "." + ref: "1.x.y" + resolved-ref: "32edeff765369a7a77a0822f4b19302ca24a017b" + url: "https://gitlab.com/famedly/libraries/matrix_file_e2ee.git" + source: git version: "1.0.3" meta: dependency: transitive @@ -410,10 +412,12 @@ packages: olm: dependency: "direct main" description: - path: "/home/sorunome/repos/famedly/dart-olm" - relative: false - source: path - version: "1.1.1" + path: "." + ref: "1.x.y" + resolved-ref: "8e4fcccff7a2d4d0bd5142964db092bf45061905" + url: "https://gitlab.com/famedly/libraries/dart-olm.git" + source: git + version: "1.2.0" package_config: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index c37e405..324dcd4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -22,16 +22,14 @@ dependencies: password_hash: ^2.0.0 olm: - path: /home/sorunome/repos/famedly/dart-olm -# git: -# url: https://gitlab.com/famedly/libraries/dart-olm.git -# ref: 0c612a525511652a7760126b058de8c924fe8900 + git: + url: https://gitlab.com/famedly/libraries/dart-olm.git + ref: 1.x.y matrix_file_e2ee: - path: /home/sorunome/repos/famedly/matrix_file_e2ee -# git: -# url: https://gitlab.com/famedly/libraries/matrix_file_e2ee.git -# ref: 1.x.y + git: + url: https://gitlab.com/famedly/libraries/matrix_file_e2ee.git + ref: 1.x.y dev_dependencies: test: ^1.0.0