From b4e83caa89d87c0541e472ddb3813ffbc7ed5710 Mon Sep 17 00:00:00 2001 From: Sorunome Date: Sat, 6 Jun 2020 15:17:05 +0200 Subject: [PATCH] requestify cross-signing endpoints --- lib/encryption/cross_signing.dart | 39 ++++--------- lib/matrix_api.dart | 1 + lib/matrix_api/matrix_api.dart | 50 +++++++++++++++++ lib/matrix_api/model/matrix_keys.dart | 24 ++++---- .../model/upload_key_signatures_response.dart | 55 +++++++++++++++++++ lib/src/utils/device_keys_list.dart | 8 ++- 6 files changed, 139 insertions(+), 38 deletions(-) create mode 100644 lib/matrix_api/model/upload_key_signatures_response.dart diff --git a/lib/encryption/cross_signing.dart b/lib/encryption/cross_signing.dart index 164b183..e4d8bc9 100644 --- a/lib/encryption/cross_signing.dart +++ b/lib/encryption/cross_signing.dart @@ -117,34 +117,23 @@ class CrossSigning { Future sign(List keys) async { Uint8List selfSigningKey; Uint8List userSigningKey; - final signatures = {}; - var signedKey = false; + final signedKeys = []; final addSignature = (SignableKey key, SignableKey signedWith, String signature) { if (key == null || signedWith == null || signature == null) { return; } - if (!signatures.containsKey(key.userId)) { - signatures[key.userId] = {}; + final signedKey = signedKeys.firstWhere( + (k) => k.userId == key.userId && k.identifier == key.identifier, + orElse: () => null) ?? + key.cloneForSigning(); + signedKey.signatures ??= >{}; + if (!signedKey.signatures.containsKey(signedWith.userId)) { + signedKey.signatures[signedWith.userId] = {}; } - if (!signatures[key.userId].containsKey(key.identifier)) { - signatures[key.userId][key.identifier] = - Map.from(key.toJson()); - // we don't need to send all old signatures, so let's just remove them - signatures[key.userId][key.identifier].remove('signatures'); - } - if (!signatures[key.userId][key.identifier].containsKey('signatures')) { - signatures[key.userId][key.identifier] - ['signatures'] = {}; - } - if (!signatures[key.userId][key.identifier]['signatures'] - .containsKey(signedWith.userId)) { - signatures[key.userId][key.identifier]['signatures'] - [signedWith.userId] = {}; - } - signatures[key.userId][key.identifier]['signatures'][signedWith.userId] + signedKey.signatures[signedWith.userId] ['ed25519:${signedWith.identifier}'] = signature; - signedKey = true; + signedKeys.add(signedKey); }; for (final key in keys) { if (key.userId == client.userID) { @@ -182,13 +171,9 @@ class CrossSigning { } } } - if (signedKey) { + if (signedKeys.isNotEmpty) { // post our new keys! - await client.jsonRequest( - type: RequestType.POST, - action: '/client/r0/keys/signatures/upload', - data: signatures, - ); + await client.api.uploadKeySignatures(signedKeys); } } diff --git a/lib/matrix_api.dart b/lib/matrix_api.dart index 5832d4f..e1a22d5 100644 --- a/lib/matrix_api.dart +++ b/lib/matrix_api.dart @@ -58,6 +58,7 @@ export 'package:famedlysdk/matrix_api/model/third_party_location.dart'; export 'package:famedlysdk/matrix_api/model/third_party_user.dart'; export 'package:famedlysdk/matrix_api/model/timeline_history_response.dart'; export 'package:famedlysdk/matrix_api/model/turn_server_credentials.dart'; +export 'package:famedlysdk/matrix_api/model/upload_key_signatures_response.dart'; export 'package:famedlysdk/matrix_api/model/user_search_result.dart'; export 'package:famedlysdk/matrix_api/model/well_known_informations.dart'; export 'package:famedlysdk/matrix_api/model/who_is_info.dart'; diff --git a/lib/matrix_api/matrix_api.dart b/lib/matrix_api/matrix_api.dart index 4c3ddcb..60155fd 100644 --- a/lib/matrix_api/matrix_api.dart +++ b/lib/matrix_api/matrix_api.dart @@ -54,6 +54,7 @@ import 'model/tag.dart'; import 'model/third_party_identifier.dart'; import 'model/third_party_user.dart'; import 'model/turn_server_credentials.dart'; +import 'model/upload_key_signatures_response.dart'; import 'model/well_known_informations.dart'; import 'model/who_is_info.dart'; @@ -1503,6 +1504,55 @@ class MatrixApi { return DeviceListsUpdate.fromJson(response); } + /// Uploads your own cross-signing keys. + /// https://12682-24998719-gh.circle-artifacts.com/0/scripts/gen/client_server/unstable.html#post-matrix-client-r0-keys-device-signing-upload + Future uploadDeviceSigningKeys({ + MatrixCrossSigningKey masterKey, + MatrixCrossSigningKey selfSigningKey, + MatrixCrossSigningKey userSigningKey, + }) async { + await request( + RequestType.POST, + '/client/r0/keys/device_signing/upload', + data: { + 'master_key': masterKey.toJson(), + 'self_signing_key': selfSigningKey.toJson(), + 'user_signing_key': userSigningKey.toJson(), + }, + ); + } + + /// Uploads new signatures of keys + /// https://12682-24998719-gh.circle-artifacts.com/0/scripts/gen/client_server/unstable.html#post-matrix-client-r0-keys-signatures-upload + Future uploadKeySignatures( + List keys) async { + final payload = {}; + for (final key in keys) { + if (key.identifier == null || + key.signatures == null || + key.signatures.isEmpty) { + continue; + } + if (!payload.containsKey(key.userId)) { + payload[key.userId] = {}; + } + if (payload[key.userId].containsKey(key.identifier)) { + // we need to merge signature objects + payload[key.userId][key.identifier]['signatures'] + .addAll(key.signatures); + } else { + // we can just add signatures + payload[key.userId][key.identifier] = key.toJson(); + } + } + final response = await request( + RequestType.POST, + '/client/r0/keys/signatures/upload', + data: payload, + ); + return UploadKeySignaturesResponse.fromJson(response); + } + /// Gets all currently active pushers for the authenticated user. /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-pushers Future> requestPushers() async { diff --git a/lib/matrix_api/model/matrix_keys.dart b/lib/matrix_api/model/matrix_keys.dart index 05ffa28..c0f0038 100644 --- a/lib/matrix_api/model/matrix_keys.dart +++ b/lib/matrix_api/model/matrix_keys.dart @@ -18,11 +18,13 @@ class MatrixSignableKey { String userId; + String identifier; Map keys; Map> signatures; Map unsigned; - MatrixSignableKey(this.userId, this.keys, this.signatures, {this.unsigned}); + MatrixSignableKey(this.userId, this.identifier, this.keys, this.signatures, + {this.unsigned}); // This object is used for signing so we need the raw json too Map _json; @@ -31,9 +33,10 @@ class MatrixSignableKey { _json = json; userId = json['user_id']; keys = Map.from(json['keys']); - signatures = json['signatures'] is Map ? Map>.from( - (json['signatures'] as Map) - .map((k, v) => MapEntry(k, Map.from(v)))) : null; + signatures = json['signatures'] is Map + ? Map>.from((json['signatures'] as Map) + .map((k, v) => MapEntry(k, Map.from(v)))) + : null; unsigned = json['unsigned'] is Map ? Map.from(json['unsigned']) : null; @@ -56,7 +59,7 @@ class MatrixSignableKey { class MatrixCrossSigningKey extends MatrixSignableKey { List usage; - String get publicKey => keys?.values?.first; + String get publicKey => identifier; MatrixCrossSigningKey( String userId, @@ -64,12 +67,13 @@ class MatrixCrossSigningKey extends MatrixSignableKey { Map keys, Map> signatures, { Map unsigned, - }) : super(userId, keys, signatures, unsigned: unsigned); + }) : super(userId, keys?.values?.first, keys, signatures, unsigned: unsigned); @override MatrixCrossSigningKey.fromJson(Map json) : super.fromJson(json) { usage = List.from(json['usage']); + identifier = keys?.values?.first; } @override @@ -81,23 +85,23 @@ class MatrixCrossSigningKey extends MatrixSignableKey { } class MatrixDeviceKeys extends MatrixSignableKey { - String deviceId; + String get deviceId => identifier; List algorithms; String get deviceDisplayName => unsigned != null ? unsigned['device_display_name'] : null; MatrixDeviceKeys( String userId, - this.deviceId, + String deviceId, this.algorithms, Map keys, Map> signatures, { Map unsigned, - }) : super(userId, keys, signatures, unsigned: unsigned); + }) : super(userId, deviceId, keys, signatures, unsigned: unsigned); @override MatrixDeviceKeys.fromJson(Map json) : super.fromJson(json) { - deviceId = json['device_id']; + identifier = json['device_id']; algorithms = json['algorithms'].cast(); } diff --git a/lib/matrix_api/model/upload_key_signatures_response.dart b/lib/matrix_api/model/upload_key_signatures_response.dart new file mode 100644 index 0000000..325aa5e --- /dev/null +++ b/lib/matrix_api/model/upload_key_signatures_response.dart @@ -0,0 +1,55 @@ +/* + * 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 . + */ + +import 'matrix_exception.dart'; + +class UploadKeySignaturesResponse { + Map> failures; + + UploadKeySignaturesResponse.fromJson(Map json) { + failures = json['failures'] != null + ? (json['failures'] as Map).map( + (k, v) => MapEntry( + k, + (v as Map).map((k, v) => MapEntry( + k, + MatrixException.fromJson(v), + )), + ), + ) + : null; + } + + Map toJson() { + final data = {}; + if (failures != null) { + data['failures'] = failures.map( + (k, v) => MapEntry( + k, + v.map( + (k, v) => MapEntry( + k, + v.raw, + ), + ), + ), + ); + } + return data; + } +} diff --git a/lib/src/utils/device_keys_list.dart b/lib/src/utils/device_keys_list.dart index a39fda2..27ee9f7 100644 --- a/lib/src/utils/device_keys_list.dart +++ b/lib/src/utils/device_keys_list.dart @@ -100,7 +100,6 @@ class DeviceKeysList { abstract class SignableKey extends MatrixSignableKey { Client client; - String identifier; Map validSignatures; bool _verified; bool blocked; @@ -123,6 +122,13 @@ abstract class SignableKey extends MatrixSignableKey { blocked = false; } + MatrixSignableKey cloneForSigning() { + final newKey = + MatrixSignableKey.fromJson(Map.from(toJson())); + newKey.signatures.clear(); + return newKey; + } + String get signingContent { final data = Map.from(super.toJson()); // some old data might have the custom verified and blocked keys