Request-ify room key store stuff

This commit is contained in:
Sorunome 2020-06-12 16:17:00 +02:00
parent fc5400a30c
commit 2a6a19e2b0
No known key found for this signature in database
GPG key ID: B19471D07FC9BE9C
7 changed files with 382 additions and 63 deletions

View file

@ -43,9 +43,12 @@ class KeyManager {
encryption.ssss.setValidator(MEGOLM_KEY, (String secret) async { encryption.ssss.setValidator(MEGOLM_KEY, (String secret) async {
final keyObj = olm.PkDecryption(); final keyObj = olm.PkDecryption();
try { 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)) == return keyObj.init_with_private_key(base64.decode(secret)) ==
info['auth_data']['public_key']; (info.authData as RoomKeysAuthDataV1Curve25519AesSha2).publicKey;
} catch (_) { } catch (_) {
return false; return false;
} finally { } finally {
@ -313,13 +316,6 @@ class KeyManager {
_outboundGroupSessions[roomId] = sess; _outboundGroupSessions[roomId] = sess;
} }
Future<Map<String, dynamic>> getRoomKeysInfo() async {
return await client.jsonRequest(
type: RequestType.GET,
action: '/client/r0/room_keys/version',
);
}
Future<bool> isCached() async { Future<bool> isCached() async {
if (!enabled) { if (!enabled) {
return false; return false;
@ -327,50 +323,32 @@ class KeyManager {
return (await encryption.ssss.getCached(MEGOLM_KEY)) != null; return (await encryption.ssss.getCached(MEGOLM_KEY)) != null;
} }
Future<void> loadFromResponse(Map<String, dynamic> payload) async { Future<void> loadFromResponse(RoomKeys keys) async {
if (!(await isCached())) { if (!(await isCached())) {
return; return;
} }
if (!(payload['rooms'] is Map)) {
return;
}
final privateKey = final privateKey =
base64.decode(await encryption.ssss.getCached(MEGOLM_KEY)); base64.decode(await encryption.ssss.getCached(MEGOLM_KEY));
final decryption = olm.PkDecryption(); final decryption = olm.PkDecryption();
final info = await getRoomKeysInfo(); final info = await client.api.getRoomKeysBackup();
String backupPubKey; String backupPubKey;
try { try {
backupPubKey = decryption.init_with_private_key(privateKey); backupPubKey = decryption.init_with_private_key(privateKey);
if (backupPubKey == null || if (backupPubKey == null ||
!info.containsKey('auth_data') || !(info.authData is RoomKeysAuthDataV1Curve25519AesSha2) ||
!(info['auth_data'] is Map) || (info.authData as RoomKeysAuthDataV1Curve25519AesSha2).publicKey != backupPubKey) {
info['auth_data']['public_key'] != backupPubKey) {
return; return;
} }
for (final roomEntries in payload['rooms'].entries) { for (final roomEntry in keys.rooms.entries) {
final roomId = roomEntries.key; final roomId = roomEntry.key;
if (!(roomEntries.value is Map) || for (final sessionEntry in roomEntry.value.sessions.entries) {
!(roomEntries.value['sessions'] is Map)) { final sessionId = sessionEntry.key;
continue; final session = sessionEntry.value;
} final firstMessageIndex = session.firstMessageIndex;
for (final sessionEntries in roomEntries.value['sessions'].entries) { final forwardedCount = session.forwardedCount;
final sessionId = sessionEntries.key; final isVerified = session.isVerified;
final rawEncryptedSession = sessionEntries.value; final sessionData = session.sessionData;
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'];
if (firstMessageIndex == null || if (firstMessageIndex == null ||
forwardedCount == null || forwardedCount == null ||
isVerified == null || isVerified == null ||
@ -399,21 +377,18 @@ class KeyManager {
} }
Future<void> loadSingleKey(String roomId, String sessionId) async { Future<void> loadSingleKey(String roomId, String sessionId) async {
final info = await getRoomKeysInfo(); final info = await client.api.getRoomKeysBackup();
final ret = await client.jsonRequest( final ret = await client.api.getRoomKeysSingleKey(roomId, sessionId, info.version);
type: RequestType.GET, final keys = RoomKeys.fromJson({
action:
'/client/r0/room_keys/keys/${Uri.encodeComponent(roomId)}/${Uri.encodeComponent(sessionId)}?version=${info['version']}',
);
await loadFromResponse({
'rooms': { 'rooms': {
roomId: { roomId: {
'sessions': { 'sessions': {
sessionId: ret, sessionId: ret.toJson(),
}, },
}, },
}, },
}); });
await loadFromResponse(keys);
} }
/// Request a certain key from another device /// Request a certain key from another device
@ -422,6 +397,7 @@ class KeyManager {
var hadPreviously = var hadPreviously =
getInboundGroupSession(room.id, sessionId, senderKey) != null; getInboundGroupSession(room.id, sessionId, senderKey) != null;
try { try {
print('FETCHING FROM KEY STORE...');
await loadSingleKey(room.id, sessionId); await loadSingleKey(room.id, sessionId);
} catch (err, stacktrace) { } catch (err, stacktrace) {
print('++++++++++++++++++'); print('++++++++++++++++++');
@ -430,6 +406,7 @@ class KeyManager {
} }
if (!hadPreviously && if (!hadPreviously &&
getInboundGroupSession(room.id, sessionId, senderKey) != null) { 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 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 // while we just send the to-device event to '*', we still need to save the

View file

@ -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/pusher.dart';
export 'package:famedlysdk/matrix_api/model/request_token_response.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_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/room_summary.dart';
export 'package:famedlysdk/matrix_api/model/server_capabilities.dart'; export 'package:famedlysdk/matrix_api/model/server_capabilities.dart';
export 'package:famedlysdk/matrix_api/model/stripped_state_event.dart'; export 'package:famedlysdk/matrix_api/model/stripped_state_event.dart';

View file

@ -49,6 +49,8 @@ import 'model/public_rooms_response.dart';
import 'model/push_rule_set.dart'; import 'model/push_rule_set.dart';
import 'model/pusher.dart'; import 'model/pusher.dart';
import 'model/room_alias_informations.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/supported_protocol.dart';
import 'model/tag.dart'; import 'model/tag.dart';
import 'model/third_party_identifier.dart'; import 'model/third_party_identifier.dart';
@ -2036,4 +2038,148 @@ class MatrixApi {
); );
return; return;
} }
/// Create room keys backup
/// https://matrix.org/docs/spec/client_server/unstable#post-matrix-client-r0-room-keys-version
Future<String> 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<RoomKeysVersionResponse> 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<void> 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<void> 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<RoomKeysUpdateResponse> 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<RoomKeysSingleKey> 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<RoomKeysUpdateResponse> 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<RoomKeysUpdateResponse> 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<RoomKeysRoom> 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<RoomKeysUpdateResponse> 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<RoomKeysUpdateResponse> 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<RoomKeys> 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<RoomKeysUpdateResponse> deleteRoomKeys(String version) async {
final ret = await request(
RequestType.DELETE,
'/client/unstable/room_keys/keys?version=${Uri.encodeComponent(version)}',
);
return RoomKeysUpdateResponse.fromJson(ret);
}
} }

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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<String, dynamic> _json;
RoomKeysAuthData.fromJson(Map<String, dynamic> json) {
_json = json;
}
Map<String, dynamic> toJson() {
return _json;
}
}
class RoomKeysAuthDataV1Curve25519AesSha2 extends RoomKeysAuthData {
String publicKey;
Map<String, Map<String, String>> signatures;
RoomKeysAuthDataV1Curve25519AesSha2.fromJson(Map<String, dynamic> json) : super.fromJson(json) {
publicKey = json['public_key'];
signatures = json['signatures'] is Map
? Map<String, Map<String, String>>.from((json['signatures'] as Map)
.map((k, v) => MapEntry(k, Map<String, String>.from(v))))
: null;
}
@override
Map<String, dynamic> 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<String, dynamic> 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<String, dynamic> toJson() {
final data = <String, dynamic>{};
data['algorithm'] = algorithm?.algorithmString;
data['auth_data'] = authData?.toJson();
data['count'] = count;
data['etag'] = etag;
data['version'] = version;
return data;
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
class RoomKeysSingleKey {
int firstMessageIndex;
int forwardedCount;
bool isVerified;
Map<String, dynamic> sessionData;
RoomKeysSingleKey.fromJson(Map<String, dynamic> json) {
firstMessageIndex = json['first_message_index'];
forwardedCount = json['forwarded_count'];
isVerified = json['is_verified'];
sessionData = json['session_data'];
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
data['first_message_index'] = firstMessageIndex;
data['forwarded_count'] = forwardedCount;
data['is_verified'] = isVerified;
data['session_data'] = sessionData;
return data;
}
}
class RoomKeysRoom {
Map<String, RoomKeysSingleKey> sessions;
RoomKeysRoom.fromJson(Map<String, dynamic> json) {
sessions = (json['sessions'] as Map).map((k, v) => MapEntry(k, RoomKeysSingleKey.fromJson(v)));
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
data['sessions'] = sessions.map((k, v) => MapEntry(k, v.toJson()));
return data;
}
}
class RoomKeys {
Map<String, RoomKeysRoom> rooms;
RoomKeys.fromJson(Map<String, dynamic> json) {
rooms = (json['rooms'] as Map).map((k, v) => MapEntry(k, RoomKeysRoom.fromJson(v)));
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
data['rooms'] = rooms.map((k, v) => MapEntry(k, v.toJson()));
return data;
}
}
class RoomKeysUpdateResponse {
String etag;
int count;
RoomKeysUpdateResponse.fromJson(Map<String, dynamic> json) {
etag = json['etag']; // synapse replies an int but docs say string?
count = json['count'];
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
data['etag'] = etag;
data['count'] = count;
return data;
}
}

View file

@ -333,9 +333,11 @@ packages:
matrix_file_e2ee: matrix_file_e2ee:
dependency: "direct main" dependency: "direct main"
description: description:
path: "/home/sorunome/repos/famedly/matrix_file_e2ee" path: "."
relative: false ref: "1.x.y"
source: path resolved-ref: "32edeff765369a7a77a0822f4b19302ca24a017b"
url: "https://gitlab.com/famedly/libraries/matrix_file_e2ee.git"
source: git
version: "1.0.3" version: "1.0.3"
meta: meta:
dependency: transitive dependency: transitive
@ -410,10 +412,12 @@ packages:
olm: olm:
dependency: "direct main" dependency: "direct main"
description: description:
path: "/home/sorunome/repos/famedly/dart-olm" path: "."
relative: false ref: "1.x.y"
source: path resolved-ref: "8e4fcccff7a2d4d0bd5142964db092bf45061905"
version: "1.1.1" url: "https://gitlab.com/famedly/libraries/dart-olm.git"
source: git
version: "1.2.0"
package_config: package_config:
dependency: transitive dependency: transitive
description: description:

View file

@ -22,16 +22,14 @@ dependencies:
password_hash: ^2.0.0 password_hash: ^2.0.0
olm: olm:
path: /home/sorunome/repos/famedly/dart-olm git:
# git: url: https://gitlab.com/famedly/libraries/dart-olm.git
# url: https://gitlab.com/famedly/libraries/dart-olm.git ref: 1.x.y
# ref: 0c612a525511652a7760126b058de8c924fe8900
matrix_file_e2ee: matrix_file_e2ee:
path: /home/sorunome/repos/famedly/matrix_file_e2ee git:
# git: url: https://gitlab.com/famedly/libraries/matrix_file_e2ee.git
# url: https://gitlab.com/famedly/libraries/matrix_file_e2ee.git ref: 1.x.y
# ref: 1.x.y
dev_dependencies: dev_dependencies:
test: ^1.0.0 test: ^1.0.0