From 4c60369b8d5d85e755c40f5af9b9d916b8e1082e Mon Sep 17 00:00:00 2001 From: Sorunome Date: Fri, 5 Jun 2020 22:03:28 +0200 Subject: [PATCH] migrate to new thingy! --- lib/{src => encryption}/cross_signing.dart | 49 ++- lib/encryption/encryption.dart | 16 + lib/encryption/key_manager.dart | 132 ++++++- lib/encryption/key_verification_manager.dart | 48 ++- lib/encryption/olm_manager.dart | 4 + lib/{src => encryption}/ssss.dart | 38 +- lib/encryption/utils/key_verification.dart | 21 +- lib/matrix_api.dart | 1 + lib/matrix_api/model/keys_query_response.dart | 54 +++ .../model/matrix_cross_signing_key.dart | 65 ++++ lib/src/client.dart | 105 +++--- lib/src/key_manager.dart | 349 ------------------ lib/src/utils/device_keys_list.dart | 115 ++---- test/client_test.dart | 2 +- test/device_keys_list_test.dart | 24 +- .../encrypt_decrypt_to_device_test.dart | 14 +- test/encryption/key_request_test.dart | 4 +- 17 files changed, 489 insertions(+), 552 deletions(-) rename lib/{src => encryption}/cross_signing.dart (77%) rename lib/{src => encryption}/ssss.dart (92%) create mode 100644 lib/matrix_api/model/matrix_cross_signing_key.dart delete mode 100644 lib/src/key_manager.dart diff --git a/lib/src/cross_signing.dart b/lib/encryption/cross_signing.dart similarity index 77% rename from lib/src/cross_signing.dart rename to lib/encryption/cross_signing.dart index 779e03ef..a163c510 100644 --- a/lib/src/cross_signing.dart +++ b/lib/encryption/cross_signing.dart @@ -1,19 +1,38 @@ +/* + * 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 'dart:typed_data'; import 'dart:convert'; import 'package:olm/olm.dart' as olm; +import 'package:famedlysdk/famedlysdk.dart'; -import 'client.dart'; -import 'utils/device_keys_list.dart'; +import 'encryption.dart'; const SELF_SIGNING_KEY = 'm.cross_signing.self_signing'; const USER_SIGNING_KEY = 'm.cross_signing.user_signing'; const MASTER_KEY = 'm.cross_signing.master'; class CrossSigning { - final Client client; - CrossSigning(this.client) { - client.ssss.setValidator(SELF_SIGNING_KEY, (String secret) async { + final Encryption encryption; + Client get client => encryption.client; + CrossSigning(this.encryption) { + encryption.ssss.setValidator(SELF_SIGNING_KEY, (String secret) async { final keyObj = olm.PkSigning(); try { return keyObj.init_with_seed(base64.decode(secret)) == @@ -24,7 +43,7 @@ class CrossSigning { keyObj.free(); } }); - client.ssss.setValidator(USER_SIGNING_KEY, (String secret) async { + encryption.ssss.setValidator(USER_SIGNING_KEY, (String secret) async { final keyObj = olm.PkSigning(); try { return keyObj.init_with_seed(base64.decode(secret)) == @@ -46,12 +65,12 @@ class CrossSigning { if (!enabled) { return false; } - return (await client.ssss.getCached(SELF_SIGNING_KEY)) != null && - (await client.ssss.getCached(USER_SIGNING_KEY)) != null; + return (await encryption.ssss.getCached(SELF_SIGNING_KEY)) != null && + (await encryption.ssss.getCached(USER_SIGNING_KEY)) != null; } Future selfSign({String password, String recoveryKey}) async { - final handle = client.ssss.open(MASTER_KEY); + final handle = encryption.ssss.open(MASTER_KEY); await handle.unlock(password: password, recoveryKey: recoveryKey); await handle.maybeCacheAll(); final masterPrivateKey = base64.decode(await handle.getStored(MASTER_KEY)); @@ -133,7 +152,7 @@ class CrossSigning { if (key is CrossSigningKey) { if (key.usage.contains('master')) { // okay, we'll sign our own master key - final signature = client.signString(key.signingContent); + final signature = encryption.olmManager.signString(key.signingContent); addSignature( key, client @@ -144,8 +163,8 @@ class CrossSigning { } else { // okay, we'll sign a device key with our self signing key selfSigningKey ??= base64 - .decode(await client.ssss.getCached(SELF_SIGNING_KEY) ?? ''); - if (selfSigningKey != null) { + .decode(await encryption.ssss.getCached(SELF_SIGNING_KEY) ?? ''); + if (selfSigningKey.isNotEmpty) { final signature = _sign(key.signingContent, selfSigningKey); addSignature(key, client.userDeviceKeys[client.userID].selfSigningKey, signature); @@ -154,8 +173,8 @@ class CrossSigning { } else if (key is CrossSigningKey && key.usage.contains('master')) { // we are signing someone elses master key userSigningKey ??= - base64.decode(await client.ssss.getCached(USER_SIGNING_KEY) ?? ''); - if (userSigningKey != null) { + base64.decode(await encryption.ssss.getCached(USER_SIGNING_KEY) ?? ''); + if (userSigningKey.isNotEmpty) { final signature = _sign(key.signingContent, userSigningKey); addSignature(key, client.userDeviceKeys[client.userID].userSigningKey, signature); @@ -165,7 +184,7 @@ class CrossSigning { if (signedKey) { // post our new keys! await client.jsonRequest( - type: HTTPType.POST, + type: RequestType.POST, action: '/client/r0/keys/signatures/upload', data: signatures, ); diff --git a/lib/encryption/encryption.dart b/lib/encryption/encryption.dart index 072d0c8f..4cd54f0f 100644 --- a/lib/encryption/encryption.dart +++ b/lib/encryption/encryption.dart @@ -24,6 +24,8 @@ import 'package:pedantic/pedantic.dart'; import 'key_manager.dart'; import 'olm_manager.dart'; import 'key_verification_manager.dart'; +import 'cross_signing.dart'; +import 'ssss.dart'; class Encryption { final Client client; @@ -42,15 +44,19 @@ class Encryption { KeyManager keyManager; OlmManager olmManager; KeyVerificationManager keyVerificationManager; + CrossSigning crossSigning; + SSSS ssss; Encryption({ this.client, this.debug, this.enableE2eeRecovery, }) { + ssss = SSSS(this); keyManager = KeyManager(this); olmManager = OlmManager(this); keyVerificationManager = KeyVerificationManager(this); + crossSigning = CrossSigning(this); } Future init(String olmAccount) async { @@ -79,6 +85,16 @@ class Encryption { } } + Future handleEventUpdate(EventUpdate update) async { + if (update.type == 'ephemeral') { + return; + } + if (update.eventType.startsWith('m.key.verification.') || (update.eventType == 'm.room.message' && (update.content['content']['msgtype'] is String) && update.content['content']['msgtype'].startsWith('m.key.verification.'))) { + // "just" key verification, no need to do this in sync + unawaited(keyVerificationManager.handleEventUpdate(update)); + } + } + Future decryptToDeviceEvent(ToDeviceEvent event) async { return await olmManager.decryptToDeviceEvent(event); } diff --git a/lib/encryption/key_manager.dart b/lib/encryption/key_manager.dart index 4121c11f..7855de31 100644 --- a/lib/encryption/key_manager.dart +++ b/lib/encryption/key_manager.dart @@ -27,6 +27,8 @@ import './encryption.dart'; import './utils/session_key.dart'; import './utils/outbound_group_session.dart'; +const MEGOLM_KEY = 'm.megolm_backup.v1'; + class KeyManager { final Encryption encryption; Client get client => encryption.client; @@ -37,7 +39,22 @@ class KeyManager { final Set _loadedOutboundGroupSessions = {}; final Set _requestedSessionIds = {}; - KeyManager(this.encryption); + KeyManager(this.encryption) { + encryption.ssss.setValidator(MEGOLM_KEY, (String secret) async { + final keyObj = olm.PkDecryption(); + try { + final info = await getRoomKeysInfo(); + return keyObj.init_with_private_key(base64.decode(secret)) == + info['auth_data']['public_key']; + } catch (_) { + return false; + } finally { + keyObj.free(); + } + }); + } + + bool get enabled => client.accountData[MEGOLM_KEY] != null; /// clear all cached inbound group sessions. useful for testing void clearInboundGroupSessions() { @@ -283,8 +300,120 @@ 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; + } + return (await encryption.ssss.getCached(MEGOLM_KEY)) != null; + } + + Future loadFromResponse(Map payload) 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(); + 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) { + 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']; + if (firstMessageIndex == null || + forwardedCount == null || + isVerified == null || + !(sessionData is Map)) { + continue; + } + Map decrypted; + try { + decrypted = json.decode(decryption.decrypt(sessionData['ephemeral'], + sessionData['mac'], sessionData['ciphertext'])); + } catch (err) { + print('[LibOlm] Error decrypting room key: ' + err.toString()); + } + if (decrypted != null) { + decrypted['session_id'] = sessionId; + decrypted['room_id'] = roomId; + setInboundGroupSession(roomId, sessionId, decrypted['sender_key'], decrypted, forwarded: true); + } + } + } + } finally { + decryption.free(); + } + } + + 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({ + 'rooms': { + roomId: { + 'sessions': { + sessionId: ret, + }, + }, + }, + }); + } + /// Request a certain key from another device Future request(Room room, String sessionId, String senderKey) async { + // let's first check our online key backup store thingy... + var hadPreviously = getInboundGroupSession(room.id, sessionId, senderKey) != null; + try { + await loadSingleKey(room.id, sessionId); + } catch (err, stacktrace) { + print('++++++++++++++++++'); + print(err.toString()); + print(stacktrace); + } + if (!hadPreviously && getInboundGroupSession(room.id, sessionId, senderKey) != null) { + 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 // devices themself to know where to send the cancel to after receiving a reply final devices = await room.getUserDeviceKeys(); @@ -500,7 +629,6 @@ class RoomKeyRequest extends ToDeviceEvent { for (final key in session.forwardingCurve25519KeyChain) { forwardedKeys.add(key); } - await requestingDevice.setVerified(true, keyManager.client); var message = session.content; message['forwarding_curve25519_key_chain'] = forwardedKeys; diff --git a/lib/encryption/key_verification_manager.dart b/lib/encryption/key_verification_manager.dart index f8720f4c..6170e24a 100644 --- a/lib/encryption/key_verification_manager.dart +++ b/lib/encryption/key_verification_manager.dart @@ -51,7 +51,7 @@ class KeyVerificationManager { } Future handleToDeviceEvent(ToDeviceEvent event) async { - if (!event.type.startsWith('m.key.verification')) { + if (!event.type.startsWith('m.key.verification') || client.verificationMethods.isEmpty) { return; } // we have key verification going on! @@ -75,6 +75,52 @@ class KeyVerificationManager { } } + Future handleEventUpdate(EventUpdate update) async { + final event = update.content; + final type = event['type'].startsWith('m.key.verification.') + ? event['type'] + : event['content']['msgtype']; + if (type == null || !type.startsWith('m.key.verification.') || client.verificationMethods.isEmpty) { + return; + } + if (type == 'm.key.verification.request') { + event['content']['timestamp'] = event['origin_server_ts']; + } + + final transactionId = + KeyVerification.getTransactionId(event['content']) ?? event['event_id']; + + if (_requests.containsKey(transactionId)) { + final req = _requests[transactionId]; + if (event['sender'] != client.userID) { + req.handlePayload(type, event['content'], event['event_id']); + } else if (req.userId == client.userID && req.deviceId == null) { + // okay, maybe another of our devices answered + await req.handlePayload(type, event['content'], event['event_id']); + if (req.deviceId != client.deviceID) { + req.otherDeviceAccepted(); + req.dispose(); + _requests.remove(transactionId); + } + } + } else if (event['sender'] != client.userID) { + final room = + client.getRoomById(update.roomID) ?? Room(id: update.roomID, client: client); + final newKeyRequest = + KeyVerification(encryption: encryption, userId: event['sender'], room: room); + await newKeyRequest + .handlePayload(type, event['content'], event['event_id']); + if (newKeyRequest.state != KeyVerificationState.askAccept) { + // something went wrong, let's just dispose the request + newKeyRequest.dispose(); + } else { + // new request! Let's notify it and stuff + _requests[transactionId] = newKeyRequest; + client.onKeyVerificationRequest.add(newKeyRequest); + } + } + } + void dispose() { for (final req in _requests.values) { req.dispose(); diff --git a/lib/encryption/olm_manager.dart b/lib/encryption/olm_manager.dart index e116dc7b..5db0b785 100644 --- a/lib/encryption/olm_manager.dart +++ b/lib/encryption/olm_manager.dart @@ -96,6 +96,10 @@ class OlmManager { return payload; } + String signString(String s) { + return _olmAccount.sign(s); + } + /// Checks the signature of a signed json object. bool checkJsonSignature(String key, Map signedJson, String userId, String deviceId) { diff --git a/lib/src/ssss.dart b/lib/encryption/ssss.dart similarity index 92% rename from lib/src/ssss.dart rename to lib/encryption/ssss.dart index a151ab4c..0d9bc0de 100644 --- a/lib/src/ssss.dart +++ b/lib/encryption/ssss.dart @@ -1,3 +1,21 @@ +/* + * 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 'dart:typed_data'; import 'dart:convert'; @@ -5,11 +23,10 @@ import 'package:encrypt/encrypt.dart'; import 'package:crypto/crypto.dart'; import 'package:base58check/base58.dart'; import 'package:password_hash/password_hash.dart'; +import 'package:famedlysdk/famedlysdk.dart'; +import 'package:famedlysdk/matrix_api.dart'; -import 'client.dart'; -import 'account_data.dart'; -import 'utils/device_keys_list.dart'; -import 'utils/to_device_event.dart'; +import 'encryption.dart'; const CACHE_TYPES = [ 'm.cross_signing.self_signing', @@ -25,10 +42,11 @@ const OLM_RECOVERY_KEY_PREFIX = [0x8B, 0x01]; const OLM_PRIVATE_KEY_LENGTH = 32; // TODO: fetch from dart-olm class SSSS { - final Client client; + final Encryption encryption; + Client get client => encryption.client; final pendingShareRequests = {}; final _validators = Function(String)>{}; - SSSS(this.client); + SSSS(this.encryption); static _DerivedKeys deriveKeys(Uint8List key, String name) { final zerosalt = Uint8List(8); @@ -129,11 +147,11 @@ class SSSS { return keyData.content['key']; } - AccountData getKey(String keyId) { + BasicEvent getKey(String keyId) { return client.accountData['m.secret_storage.key.${keyId}']; } - bool checkKey(Uint8List key, AccountData keyData) { + bool checkKey(Uint8List key, BasicEvent keyData) { final info = keyData.content; if (info['algorithm'] == 'm.secret_storage.v1.aes-hmac-sha2') { if ((info['mac'] is String) && (info['iv'] is String)) { @@ -200,7 +218,7 @@ class SSSS { }; // store the thing in your account data await client.jsonRequest( - type: HTTPType.PUT, + type: RequestType.PUT, action: '/client/r0/user/${client.userID}/account_data/${type}', data: content, ); @@ -421,7 +439,7 @@ class _PasswordInfo { class OpenSSSS { final SSSS ssss; final String keyId; - final AccountData keyData; + final BasicEvent keyData; OpenSSSS({this.ssss, this.keyId, this.keyData}); Uint8List privateKey; diff --git a/lib/encryption/utils/key_verification.dart b/lib/encryption/utils/key_verification.dart index 12247aa0..d21b6380 100644 --- a/lib/encryption/utils/key_verification.dart +++ b/lib/encryption/utils/key_verification.dart @@ -184,8 +184,8 @@ class KeyVerification { if (room == null) { transactionId = client.generateUniqueTransactionId(); } - if (client.crossSigning.enabled && - !(await client.crossSigning.isCached()) && + if (encryption.crossSigning.enabled && + !(await encryption.crossSigning.isCached()) && !client.isUnknownSession) { setState(KeyVerificationState.askSSSS); _nextAction = 'request'; @@ -241,7 +241,6 @@ class KeyVerification { print('Setting device id start: ' + _deviceId.toString()); transactionId ??= eventId ?? payload['transaction_id']; if (method != null) { - print('DUPLICATE START'); // the other side sent us a start, even though we already sent one if (payload['method'] == method.type) { // same method. Determine priority @@ -250,10 +249,8 @@ class KeyVerification { entries.sort(); if (entries.first == ourEntry) { // our start won, nothing to do - print('we won, nothing to do'); return; } else { - print('They won, handing off'); // the other start won, let's hand off startedVerification = false; // it is now as if they started lastStep = @@ -324,7 +321,7 @@ class KeyVerification { } else if (_nextAction == 'done') { if (_verifiedDevices != null) { // and now let's sign them all in the background - client.crossSigning.sign(_verifiedDevices); + encryption.crossSigning.sign(_verifiedDevices); } setState(KeyVerificationState.done); } @@ -333,7 +330,7 @@ class KeyVerification { next(); return; } - final handle = client.ssss.open('m.cross_signing.user_signing'); + final handle = encryption.ssss.open('m.cross_signing.user_signing'); await handle.unlock(password: password, recoveryKey: recoveryKey); await handle.maybeCacheAll(); next(); @@ -437,18 +434,18 @@ class KeyVerification { if (verifiedMasterKey && userId == client.userID) { // it was our own master key, let's request the cross signing keys // we do it in the background, thus no await needed here - unawaited(client.ssss + unawaited(encryption.ssss .maybeRequestAll(_verifiedDevices.whereType().toList())); } await send('m.key.verification.done', {}); var askingSSSS = false; - if (client.crossSigning.enabled && - client.crossSigning.signable(_verifiedDevices)) { + if (encryption.crossSigning.enabled && + encryption.crossSigning.signable(_verifiedDevices)) { // these keys can be signed! Let's do so - if (await client.crossSigning.isCached()) { + if (await encryption.crossSigning.isCached()) { // and now let's sign them all in the background - unawaited(client.crossSigning.sign(_verifiedDevices)); + unawaited(encryption.crossSigning.sign(_verifiedDevices)); } else if (!wasUnknownSession) { askingSSSS = true; } diff --git a/lib/matrix_api.dart b/lib/matrix_api.dart index d364cf86..b12702b9 100644 --- a/lib/matrix_api.dart +++ b/lib/matrix_api.dart @@ -31,6 +31,7 @@ export 'package:famedlysdk/matrix_api/model/filter.dart'; export 'package:famedlysdk/matrix_api/model/keys_query_response.dart'; export 'package:famedlysdk/matrix_api/model/login_response.dart'; export 'package:famedlysdk/matrix_api/model/login_types.dart'; +export 'package:famedlysdk/matrix_api/model/matrix_cross_signing_key.dart'; export 'package:famedlysdk/matrix_api/model/matrix_device_keys.dart'; export 'package:famedlysdk/matrix_api/model/matrix_exception.dart'; export 'package:famedlysdk/matrix_api/model/message_types.dart'; diff --git a/lib/matrix_api/model/keys_query_response.dart b/lib/matrix_api/model/keys_query_response.dart index 8c6bb326..3e4f2a1a 100644 --- a/lib/matrix_api/model/keys_query_response.dart +++ b/lib/matrix_api/model/keys_query_response.dart @@ -17,10 +17,14 @@ */ import 'matrix_device_keys.dart'; +import 'matrix_cross_signing_key.dart'; class KeysQueryResponse { Map failures; Map> deviceKeys; + Map masterKeys; + Map selfSigningKeys; + Map userSigningKeys; KeysQueryResponse.fromJson(Map json) { failures = Map.from(json['failures']); @@ -37,6 +41,32 @@ class KeysQueryResponse { ), ) : null; + masterKeys = json['master_keys'] != null ? + (json['master_keys'] as Map).map( + (k, v) => MapEntry( + k, + MatrixCrossSigningKey.fromJson(v), + ), + ) + : null; + + selfSigningKeys = json['self_signing_keys'] != null ? + (json['self_signing_keys'] as Map).map( + (k, v) => MapEntry( + k, + MatrixCrossSigningKey.fromJson(v), + ), + ) + : null; + + userSigningKeys = json['user_signing_keys'] != null ? + (json['user_signing_keys'] as Map).map( + (k, v) => MapEntry( + k, + MatrixCrossSigningKey.fromJson(v), + ), + ) + : null; } Map toJson() { @@ -57,6 +87,30 @@ class KeysQueryResponse { ), ); } + if (masterKeys != null) { + data['master_keys'] = masterKeys.map( + (k, v) => MapEntry( + k, + v.toJson(), + ), + ); + } + if (selfSigningKeys != null) { + data['self_signing_keys'] = selfSigningKeys.map( + (k, v) => MapEntry( + k, + v.toJson(), + ), + ); + } + if (userSigningKeys != null) { + data['user_signing_keys'] = userSigningKeys.map( + (k, v) => MapEntry( + k, + v.toJson(), + ), + ); + } return data; } } diff --git a/lib/matrix_api/model/matrix_cross_signing_key.dart b/lib/matrix_api/model/matrix_cross_signing_key.dart new file mode 100644 index 00000000..2a852a1a --- /dev/null +++ b/lib/matrix_api/model/matrix_cross_signing_key.dart @@ -0,0 +1,65 @@ +/* + * 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 MatrixCrossSigningKey { + String userId; + List usage; + Map keys; + Map> signatures; + Map unsigned; + String get publicKey => keys?.values?.first; + + MatrixCrossSigningKey( + this.userId, + this.usage, + this.keys, + this.signatures, { + this.unsigned, + }); + + // This object is used for signing so we need the raw json too + Map _json; + + MatrixCrossSigningKey.fromJson(Map json) { + _json = json; + userId = json['user_id']; + usage = List.from(json['usage']); + keys = Map.from(json['keys']); + signatures = Map>.from( + (json['signatures'] as Map) + .map((k, v) => MapEntry(k, Map.from(v)))); + unsigned = json['unsigned'] != null + ? Map.from(json['unsigned']) + : null; + } + + Map toJson() { + final data = _json ?? {}; + data['user_id'] = userId; + data['usage'] = usage; + data['keys'] = keys; + + if (signatures != null) { + data['signatures'] = signatures; + } + if (unsigned != null) { + data['unsigned'] = unsigned; + } + return data; + } +} diff --git a/lib/src/client.dart b/lib/src/client.dart index 935420d6..9102f556 100644 --- a/lib/src/client.dart +++ b/lib/src/client.dart @@ -41,11 +41,6 @@ typedef RoomSorter = int Function(Room a, Room b); enum LoginState { logged, loggedOut } -class GenericException implements Exception { - final dynamic content; - GenericException(this.content); -} - /// Represents a Matrix client to communicate with a /// [Matrix](https://matrix.org) homeserver and is the entry point for this /// SDK. @@ -61,6 +56,8 @@ class Client { Encryption encryption; + Set verificationMethods; + /// Create a client /// clientName = unique identifier of this client /// debug: Print debug output? @@ -73,7 +70,9 @@ class Client { {this.debug = false, this.database, this.enableE2eeRecovery = false, + this.verificationMethods, http.Client httpClient}) { + verificationMethods ??= {}; api = MatrixApi(debug: debug, httpClient: httpClient); onLoginStateChanged.stream.listen((loginState) { if (debug) { @@ -642,7 +641,7 @@ class Client { encryption?.pickledOlmAccount, ); } - _userDeviceKeys = await database.getUserDeviceKeys(id); + _userDeviceKeys = await database.getUserDeviceKeys(this); _rooms = await database.getRoomList(this, onlyLeft: false); _sortRooms(); accountData = await database.getAccountData(id); @@ -813,9 +812,6 @@ class Client { if (encryptionEnabled) { await encryption.handleToDeviceEvent(toDeviceEvent); } - if (toDeviceEvent.type.startsWith('m.secret.')) { - ssss.handleToDeviceEvent(toDeviceEvent); - } onToDeviceEvent.add(toDeviceEvent); } } @@ -969,11 +965,8 @@ class Client { await database.storeEventUpdate(id, update); } _updateRoomsByEventUpdate(update); - if (event['type'].startsWith('m.key.verification.') || - (event['type'] == 'm.room.message' && - (event['content']['msgtype'] is String) && - event['content']['msgtype'].startsWith('m.key.verification.'))) { - _handleRoomKeyVerificationRequest(update); + if (encryptionEnabled) { + await encryption.handleEventUpdate(update); } onEvent.add(update); @@ -1167,14 +1160,21 @@ class Client { final deviceId = rawDeviceKeyEntry.key; // Set the new device key for this device - - if (!oldKeys.containsKey(deviceId)) { - final entry = - DeviceKeys.fromMatrixDeviceKeys(rawDeviceKeyEntry.value); - if (entry.isValid) { + final entry = DeviceKeys.fromMatrixDeviceKeys(rawDeviceKeyEntry.value, this); + if (entry.isValid) { + // is this a new key or the same one as an old one? + // better store an update - the signatures might have changed! + if (!oldKeys.containsKey(deviceId) || + oldKeys[deviceId].ed25519Key == entry.ed25519Key) { + if (oldKeys.containsKey(deviceId)) { + // be sure to save the verified status + entry.setDirectVerified(oldKeys[deviceId].directVerified); + entry.blocked = oldKeys[deviceId].blocked; + entry.validSignatures = oldKeys[deviceId].validSignatures; + } _userDeviceKeys[userId].deviceKeys[deviceId] = entry; if (deviceId == deviceID && - entry.ed25519Key == encryption?.fingerprintKey) { + entry.ed25519Key == fingerprintKey) { // Always trust the own device entry.setDirectVerified(true); } @@ -1185,16 +1185,16 @@ class Client { _userDeviceKeys[userId].deviceKeys[deviceId] = oldKeys[deviceId]; } - if (database != null) { - dbActions.add(() => database.storeUserDeviceKey( - id, - userId, - deviceId, - json.encode(entry.toJson()), - entry.directVerified, - entry.blocked, - )); - } + } + if (database != null) { + dbActions.add(() => database.storeUserDeviceKey( + id, + userId, + deviceId, + json.encode(entry.toJson()), + entry.directVerified, + entry.blocked, + )); } } // delete old/unused entries @@ -1215,29 +1215,33 @@ class Client { } } // next we parse and persist the cross signing keys - for (final keyType in [ - 'master_keys', - 'self_signing_keys', - 'user_signing_keys' - ]) { - if (!(response[keyType] is Map)) { + final crossSigningTypes = { + 'master': response.masterKeys, + 'self_signing': response.selfSigningKeys, + 'user_signing': response.userSigningKeys, + }; + for (final crossSigningKeysEntry in crossSigningTypes.entries) { + final keyType = crossSigningKeysEntry.key; + final keys = crossSigningKeysEntry.value; + if (keys == null) { continue; } - for (final rawDeviceKeyListEntry in response[keyType].entries) { - final String userId = rawDeviceKeyListEntry.key; - final oldKeys = Map.from( - _userDeviceKeys[userId].crossSigningKeys); + for (final crossSigningKeyListEntry in keys.entries) { + final userId = crossSigningKeyListEntry.key; + if (!userDeviceKeys.containsKey(userId)) { + _userDeviceKeys[userId] = DeviceKeysList(userId); + } + final oldKeys = Map.from(_userDeviceKeys[userId].crossSigningKeys); _userDeviceKeys[userId].crossSigningKeys = {}; - // add the types we arne't handling atm back + // add the types we aren't handling atm back for (final oldEntry in oldKeys.entries) { - if (!oldEntry.value.usage.contains( - keyType.substring(0, keyType.length - '_keys'.length))) { + if (!oldEntry.value.usage.contains(keyType)) { _userDeviceKeys[userId].crossSigningKeys[oldEntry.key] = oldEntry.value; } } final entry = - CrossSigningKey.fromJson(rawDeviceKeyListEntry.value, this); + CrossSigningKey.fromMatrixCrossSigningKey(crossSigningKeyListEntry.value, this); if (entry.isValid) { final publicKey = entry.publicKey; if (!oldKeys.containsKey(publicKey) || @@ -1267,19 +1271,6 @@ class Client { )); } } - // delete old/unused entries - if (database != null) { - for (final oldCrossSigningKeyEntry in oldKeys.entries) { - final publicKey = oldCrossSigningKeyEntry.key; - if (!_userDeviceKeys[userId] - .crossSigningKeys - .containsKey(publicKey)) { - // we need to remove an old key - dbActions.add(() => database.removeUserCrossSigningKey( - id, userId, publicKey)); - } - } - } _userDeviceKeys[userId].outdated = false; if (database != null) { dbActions.add( diff --git a/lib/src/key_manager.dart b/lib/src/key_manager.dart deleted file mode 100644 index ef242338..00000000 --- a/lib/src/key_manager.dart +++ /dev/null @@ -1,349 +0,0 @@ -import 'dart:core'; -import 'dart:convert'; - -import 'package:olm/olm.dart' as olm; - -import 'client.dart'; -import 'room.dart'; -import 'utils/to_device_event.dart'; -import 'utils/device_keys_list.dart'; - -const MEGOLM_KEY = 'm.megolm_backup.v1'; - -class KeyManager { - final Client client; - final outgoingShareRequests = {}; - final incomingShareRequests = {}; - - KeyManager(this.client) { - client.ssss.setValidator(MEGOLM_KEY, (String secret) async { - final keyObj = olm.PkDecryption(); - try { - final info = await getRoomKeysInfo(); - return keyObj.init_with_private_key(base64.decode(secret)) == - info['auth_data']['public_key']; - } catch (_) { - return false; - } finally { - keyObj.free(); - } - }); - } - - bool get enabled => client.accountData[MEGOLM_KEY] != null; - - Future> getRoomKeysInfo() async { - return await client.jsonRequest( - type: HTTPType.GET, - action: '/client/r0/room_keys/version', - ); - } - - Future isCached() async { - if (!enabled) { - return false; - } - return (await client.ssss.getCached(MEGOLM_KEY)) != null; - } - - Future loadFromResponse(Map payload) async { - if (!(await isCached())) { - return; - } - if (!(payload['rooms'] is Map)) { - return; - } - final privateKey = base64.decode(await client.ssss.getCached(MEGOLM_KEY)); - final decryption = olm.PkDecryption(); - final info = await getRoomKeysInfo(); - 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) { - 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']; - if (firstMessageIndex == null || - forwardedCount == null || - isVerified == null || - !(sessionData is Map)) { - continue; - } - Map decrypted; - try { - decrypted = json.decode(decryption.decrypt(sessionData['ephemeral'], - sessionData['mac'], sessionData['ciphertext'])); - } catch (err) { - print('[LibOlm] Error decrypting room key: ' + err.toString()); - } - if (decrypted != null) { - decrypted['session_id'] = sessionId; - decrypted['room_id'] = roomId; - final room = - client.getRoomById(roomId) ?? Room(id: roomId, client: client); - room.setInboundGroupSession(sessionId, decrypted, forwarded: true); - } - } - } - } finally { - decryption.free(); - } - } - - Future loadSingleKey(String roomId, String sessionId) async { - final info = await getRoomKeysInfo(); - final ret = await client.jsonRequest( - type: HTTPType.GET, - action: - '/client/r0/room_keys/keys/${Uri.encodeComponent(roomId)}/${Uri.encodeComponent(sessionId)}?version=${info['version']}', - ); - await loadFromResponse({ - 'rooms': { - roomId: { - 'sessions': { - sessionId: ret, - }, - }, - }, - }); - } - - /// Request a certain key from another device - Future request(Room room, String sessionId, String senderKey) async { - // let's first check our online key backup store thingy... - var hadPreviously = room.inboundGroupSessions.containsKey(sessionId); - try { - await loadSingleKey(room.id, sessionId); - } catch (err, stacktrace) { - print('++++++++++++++++++'); - print(err.toString()); - print(stacktrace); - } - if (!hadPreviously && room.inboundGroupSessions.containsKey(sessionId)) { - 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 - // devices themself to know where to send the cancel to after receiving a reply - final devices = await room.getUserDeviceKeys(); - final requestId = client.generateUniqueTransactionId(); - final request = KeyManagerKeyShareRequest( - requestId: requestId, - devices: devices, - room: room, - sessionId: sessionId, - senderKey: senderKey, - ); - await client.sendToDevice( - [], - 'm.room_key_request', - { - 'action': 'request', - 'body': { - 'algorithm': 'm.megolm.v1.aes-sha2', - 'room_id': room.id, - 'sender_key': senderKey, - 'session_id': sessionId, - }, - 'request_id': requestId, - 'requesting_device_id': client.deviceID, - }, - encrypted: false, - toUsers: await room.requestParticipants()); - outgoingShareRequests[request.requestId] = request; - } - - /// Handle an incoming to_device event that is related to key sharing - Future handleToDeviceEvent(ToDeviceEvent event) async { - if (event.type == 'm.room_key_request') { - if (!event.content.containsKey('request_id')) { - return; // invalid event - } - if (event.content['action'] == 'request') { - // we are *receiving* a request - if (!event.content.containsKey('body')) { - return; // no body - } - if (!client.userDeviceKeys.containsKey(event.sender) || - !client.userDeviceKeys[event.sender].deviceKeys - .containsKey(event.content['requesting_device_id'])) { - return; // device not found - } - final device = client.userDeviceKeys[event.sender] - .deviceKeys[event.content['requesting_device_id']]; - if (device.userId == client.userID && - device.deviceId == client.deviceID) { - return; // ignore requests by ourself - } - final room = client.getRoomById(event.content['body']['room_id']); - if (room == null) { - return; // unknown room - } - final sessionId = event.content['body']['session_id']; - // okay, let's see if we have this session at all - await room.loadInboundGroupSessionKey(sessionId); - if (!room.inboundGroupSessions.containsKey(sessionId)) { - return; // we don't have this session anyways - } - final request = KeyManagerKeyShareRequest( - requestId: event.content['request_id'], - devices: [device], - room: room, - sessionId: event.content['body']['session_id'], - senderKey: event.content['body']['sender_key'], - ); - if (incomingShareRequests.containsKey(request.requestId)) { - return; // we don't want to process one and the same request multiple times - } - incomingShareRequests[request.requestId] = request; - final roomKeyRequest = - RoomKeyRequest.fromToDeviceEvent(event, this, request); - if (device.userId == client.userID && - device.verified && - !device.blocked) { - // alright, we can forward the key - await roomKeyRequest.forwardKey(); - } else { - client.onRoomKeyRequest - .add(roomKeyRequest); // let the client handle this - } - } else if (event.content['action'] == 'request_cancellation') { - // we got told to cancel an incoming request - if (!incomingShareRequests.containsKey(event.content['request_id'])) { - return; // we don't know this request anyways - } - // alright, let's just cancel this request - final request = incomingShareRequests[event.content['request_id']]; - request.canceled = true; - incomingShareRequests.remove(request.requestId); - } - } else if (event.type == 'm.forwarded_room_key') { - // we *received* an incoming key request - if (event.encryptedContent == null) { - return; // event wasn't encrypted, this is a security risk - } - final request = outgoingShareRequests.values.firstWhere( - (r) => - r.room.id == event.content['room_id'] && - r.sessionId == event.content['session_id'] && - r.senderKey == event.content['sender_key'], - orElse: () => null); - if (request == null || request.canceled) { - return; // no associated request found or it got canceled - } - final device = request.devices.firstWhere( - (d) => - d.userId == event.sender && - d.curve25519Key == event.encryptedContent['sender_key'], - orElse: () => null); - if (device == null) { - return; // someone we didn't send our request to replied....better ignore this - } - // TODO: verify that the keys work to decrypt a message - // alright, all checks out, let's go ahead and store this session - request.room.setInboundGroupSession(request.sessionId, event.content, - forwarded: true); - request.devices.removeWhere( - (k) => k.userId == device.userId && k.deviceId == device.deviceId); - outgoingShareRequests.remove(request.requestId); - // send cancel to all other devices - if (request.devices.isEmpty) { - return; // no need to send any cancellation - } - await client.sendToDevice( - request.devices, - 'm.room_key_request', - { - 'action': 'request_cancellation', - 'request_id': request.requestId, - 'requesting_device_id': client.deviceID, - }, - encrypted: false); - } - } -} - -class KeyManagerKeyShareRequest { - final String requestId; - final List devices; - final Room room; - final String sessionId; - final String senderKey; - bool canceled; - - KeyManagerKeyShareRequest( - {this.requestId, - this.devices, - this.room, - this.sessionId, - this.senderKey, - this.canceled = false}); -} - -class RoomKeyRequest extends ToDeviceEvent { - KeyManager keyManager; - KeyManagerKeyShareRequest request; - RoomKeyRequest.fromToDeviceEvent(ToDeviceEvent toDeviceEvent, - KeyManager keyManager, KeyManagerKeyShareRequest request) { - this.keyManager = keyManager; - this.request = request; - sender = toDeviceEvent.sender; - content = toDeviceEvent.content; - type = toDeviceEvent.type; - } - - Room get room => request.room; - - DeviceKeys get requestingDevice => request.devices.first; - - Future forwardKey() async { - if (request.canceled) { - keyManager.incomingShareRequests.remove(request.requestId); - return; // request is canceled, don't send anything - } - var room = this.room; - await room.loadInboundGroupSessionKey(request.sessionId); - final session = room.inboundGroupSessions[request.sessionId]; - var forwardedKeys = [keyManager.client.identityKey]; - for (final key in session.forwardingCurve25519KeyChain) { - forwardedKeys.add(key); - } - var message = session.content; - message['forwarding_curve25519_key_chain'] = forwardedKeys; - - message['session_key'] = session.inboundGroupSession - .export_session(session.inboundGroupSession.first_known_index()); - // send the actual reply of the key back to the requester - await keyManager.client.sendToDevice( - [requestingDevice], - 'm.forwarded_room_key', - message, - ); - keyManager.incomingShareRequests.remove(request.requestId); - } -} diff --git a/lib/src/utils/device_keys_list.dart b/lib/src/utils/device_keys_list.dart index bfc3b40f..6f31e811 100644 --- a/lib/src/utils/device_keys_list.dart +++ b/lib/src/utils/device_keys_list.dart @@ -60,7 +60,7 @@ class DeviceKeysList { throw 'Unable to start new room'; } final room = client.getRoomById(roomId) ?? Room(id: roomId, client: client); - final request = KeyVerification(client: client, room: room, userId: userId); + final request = KeyVerification(encryption: client.encryption, room: room, userId: userId); await request.start(); // no need to add to the request client object. As we are doing a room // verification request that'll happen automatically once we know the transaction id @@ -94,34 +94,6 @@ class DeviceKeysList { } } - DeviceKeysList.fromJson(Map json, Client cl) { - client = cl; - userId = json['user_id']; - outdated = json['outdated']; - deviceKeys = {}; - for (final rawDeviceKeyEntry in json['device_keys'].entries) { - deviceKeys[rawDeviceKeyEntry.key] = - DeviceKeys.fromJson(rawDeviceKeyEntry.value, client); - } - } - - Map toJson() { - var map = {}; - final data = map; - data['user_id'] = userId; - data['outdated'] = outdated ?? true; - - var rawDeviceKeys = {}; - for (final deviceKeyEntry in deviceKeys.entries) { - rawDeviceKeys[deviceKeyEntry.key] = deviceKeyEntry.value.toJson(); - } - data['device_keys'] = rawDeviceKeys; - return data; - } - - @override - String toString() => json.encode(toJson()); - DeviceKeysList(this.userId); } @@ -137,7 +109,6 @@ abstract class SignedKey { bool blocked; String get ed25519Key => keys['ed25519:$identifier']; - bool get verified => (directVerified || crossVerified) && !blocked; void setDirectVerified(bool v) { @@ -145,9 +116,7 @@ abstract class SignedKey { } bool get directVerified => _verified; - bool get crossVerified => hasValidSignatureChain(); - bool get signed => hasValidSignatureChain(verifiedOnly: false); String get signingContent { @@ -255,9 +224,9 @@ abstract class SignedKey { Future setVerified(bool newVerified, [bool sign = true]) { _verified = newVerified; - if (sign && client.crossSigning.signable([this])) { + if (sign && client.encryptionEnabled && client.encryption.crossSigning.signable([this])) { // sign the key! - client.crossSigning.sign([this]); + client.encryption.crossSigning.sign([this]); } return Future.value(); } @@ -297,6 +266,20 @@ class CrossSigningKey extends SignedKey { newBlocked, client.id, userId, publicKey); } + CrossSigningKey.fromMatrixCrossSigningKey(MatrixCrossSigningKey k, Client cl) { + client = cl; + content = Map.from(k.toJson()); + userId = k.userId; + identifier = k.publicKey; + usage = content['usage'].cast(); + keys = content['keys'] != null ? Map.from(content['keys']) : null; + signatures = content['signatures'] != null + ? Map.from(content['signatures']) + : null; + _verified = false; + blocked = false; + } + CrossSigningKey.fromDb(DbUserCrossSigningKey dbEntry, Client cl) { client = cl; final json = Event.getMapFromPayload(dbEntry.content); @@ -346,17 +329,36 @@ class DeviceKeys extends SignedKey { @override Future setVerified(bool newVerified, [bool sign = true]) { super.setVerified(newVerified, sign); - return client.database + return client?.database ?.setVerifiedUserDeviceKey(newVerified, client.id, userId, deviceId); } @override Future setBlocked(bool newBlocked) { blocked = newBlocked; - return client.database + return client?.database ?.setBlockedUserDeviceKey(newBlocked, client.id, userId, deviceId); } + DeviceKeys.fromMatrixDeviceKeys(MatrixDeviceKeys k, Client cl) { + client = cl; + content = Map.from(k.toJson()); + userId = k.userId; + identifier = k.deviceId; + algorithms = content['algorithms'].cast(); + keys = content['keys'] != null + ? Map.from(content['keys']) + : null; + signatures = content['signatures'] != null + ? Map.from(content['signatures']) + : null; + unsigned = content['unsigned'] != null + ? Map.from(content['unsigned']) + : null; + _verified = false; + blocked = false; + } + DeviceKeys.fromDb(DbUserDeviceKeysKey dbEntry, Client cl) { client = cl; final json = Event.getMapFromPayload(dbEntry.content); @@ -365,45 +367,10 @@ class DeviceKeys extends SignedKey { identifier = dbEntry.deviceId; algorithms = content['algorithms'].cast(); keys = content['keys'] != null - }) : super(userId, deviceId, algorithms, keys, signatures, - unsigned: unsigned); - - DeviceKeys({ - String userId, - String deviceId, - List algorithms, - Map keys, - Map> signatures, - Map unsigned, - this.verified, - this.blocked, - }) : super(userId, deviceId, algorithms, keys, signatures, - unsigned: unsigned); - - factory DeviceKeys.fromMatrixDeviceKeys(MatrixDeviceKeys matrixDeviceKeys) => - DeviceKeys( - userId: matrixDeviceKeys.userId, - deviceId: matrixDeviceKeys.deviceId, - algorithms: matrixDeviceKeys.algorithms, - keys: matrixDeviceKeys.keys, - signatures: matrixDeviceKeys.signatures, - unsigned: matrixDeviceKeys.unsigned, - verified: false, - blocked: false, - ); - - static DeviceKeys fromDb(DbUserDeviceKeysKey dbEntry) { - var deviceKeys = DeviceKeys(); - final content = Event.getMapFromPayload(dbEntry.content); - deviceKeys.userId = dbEntry.userId; - deviceKeys.deviceId = dbEntry.deviceId; - deviceKeys.algorithms = content['algorithms'].cast(); - deviceKeys.keys = content['keys'] != null ? Map.from(content['keys']) : null; - deviceKeys.signatures = content['signatures'] != null - ? Map>.from((content['signatures'] as Map) - .map((k, v) => MapEntry(k, Map.from(v)))) + signatures = content['signatures'] != null + ? Map.from(content['signatures']) : null; unsigned = json['unsigned'] != null ? Map.from(json['unsigned']) @@ -431,7 +398,7 @@ class DeviceKeys extends SignedKey { KeyVerification startVerification() { final request = - KeyVerification(client: client, userId: userId, deviceId: deviceId); + KeyVerification(encryption: client.encryption, userId: userId, deviceId: deviceId); request.start(); client.encryption.keyVerificationManager.addRequest(request); diff --git a/test/client_test.dart b/test/client_test.dart index 39634000..8c93b6db 100644 --- a/test/client_test.dart +++ b/test/client_test.dart @@ -388,7 +388,7 @@ void main() { 'dSO80A01XiigH3uBiDVx/EjzaoycHcjq9lfQX0uWsqxl2giMIiSPR8a4d291W1ihKJL/a+myXS367WT6NAIcBA' } } - }); + }, matrix); test('sendToDevice', () async { await matrix.sendToDevice( [deviceKeys], diff --git a/test/device_keys_list_test.dart b/test/device_keys_list_test.dart index b16e2f23..54e3e1f3 100644 --- a/test/device_keys_list_test.dart +++ b/test/device_keys_list_test.dart @@ -41,8 +41,6 @@ void main() { } }, 'unsigned': {'device_display_name': "Alice's mobile phone"}, - 'verified': false, - 'blocked': true, }; var rawListJson = { 'user_id': '@alice:example.com', @@ -50,28 +48,12 @@ void main() { 'device_keys': {'JLAFKJWSCS': rawJson}, }; - var userDeviceKeys = { - '@alice:example.com': DeviceKeysList.fromJson(rawListJson, null), - }; - var userDeviceKeyRaw = { - '@alice:example.com': rawListJson, - }; - final key = DeviceKeys.fromJson(rawJson, null); - rawJson.remove('verified'); - rawJson.remove('blocked'); + key.setVerified(false, false); + key.setBlocked(true); expect(json.encode(key.toJson()), json.encode(rawJson)); - expect(key.verified, false); + expect(key.directVerified, false); expect(key.blocked, true); - expect(json.encode(DeviceKeysList.fromJson(rawListJson, null).toJson()), - json.encode(rawListJson)); - - var mapFromRaw = {}; - for (final rawListEntry in userDeviceKeyRaw.entries) { - mapFromRaw[rawListEntry.key] = - DeviceKeysList.fromJson(rawListEntry.value, null); - } - expect(mapFromRaw.toString(), userDeviceKeys.toString()); }); }); } diff --git a/test/encryption/encrypt_decrypt_to_device_test.dart b/test/encryption/encrypt_decrypt_to_device_test.dart index 0be0a195..5636e8be 100644 --- a/test/encryption/encrypt_decrypt_to_device_test.dart +++ b/test/encryption/encrypt_decrypt_to_device_test.dart @@ -61,17 +61,15 @@ void main() { ); await Future.delayed(Duration(milliseconds: 10)); - device = DeviceKeys( - userId: client.userID, - deviceId: client.deviceID, - algorithms: ['m.olm.v1.curve25519-aes-sha2', 'm.megolm.v1.aes-sha2'], - keys: { + device = DeviceKeys.fromJson({ + 'user_id': client.userID, + 'device_id': client.deviceID, + 'algorithms': ['m.olm.v1.curve25519-aes-sha2', 'm.megolm.v1.aes-sha2'], + 'keys': { 'curve25519:${client.deviceID}': client.identityKey, 'ed25519:${client.deviceID}': client.fingerprintKey, }, - verified: true, - blocked: false, - ); + }, client); }); test('encryptToDeviceMessage', () async { diff --git a/test/encryption/key_request_test.dart b/test/encryption/key_request_test.dart index 1ccf5107..168a1677 100644 --- a/test/encryption/key_request_test.dart +++ b/test/encryption/key_request_test.dart @@ -85,10 +85,10 @@ void main() { FakeMatrixApi.calledEndpoints.clear(); await matrix .userDeviceKeys['@alice:example.com'].deviceKeys['OTHERDEVICE'] - .setBlocked(false, matrix); + .setBlocked(false); await matrix .userDeviceKeys['@alice:example.com'].deviceKeys['OTHERDEVICE'] - .setVerified(true, matrix); + .setVerified(true); // test a successful share var event = ToDeviceEvent( sender: '@alice:example.com',