LCOV - code coverage report
Current view: top level - lib/src/utils - device_keys_list.dart (source / functions) Coverage Total Hit
Test: merged.info Lines: 93.9 % 247 232
Test Date: 2025-04-10 12:13:24 Functions: - 0 0

            Line data    Source code
       1              : /*
       2              :  *   Famedly Matrix SDK
       3              :  *   Copyright (C) 2020, 2021 Famedly GmbH
       4              :  *
       5              :  *   This program is free software: you can redistribute it and/or modify
       6              :  *   it under the terms of the GNU Affero General Public License as
       7              :  *   published by the Free Software Foundation, either version 3 of the
       8              :  *   License, or (at your option) any later version.
       9              :  *
      10              :  *   This program is distributed in the hope that it will be useful,
      11              :  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
      12              :  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
      13              :  *   GNU Affero General Public License for more details.
      14              :  *
      15              :  *   You should have received a copy of the GNU Affero General Public License
      16              :  *   along with this program.  If not, see <https://www.gnu.org/licenses/>.
      17              :  */
      18              : 
      19              : import 'dart:convert';
      20              : 
      21              : import 'package:canonical_json/canonical_json.dart';
      22              : import 'package:collection/collection.dart' show IterableExtension;
      23              : import 'package:olm/olm.dart' as olm;
      24              : 
      25              : import 'package:matrix/encryption.dart';
      26              : import 'package:matrix/matrix.dart';
      27              : 
      28              : enum UserVerifiedStatus { verified, unknown, unknownDevice }
      29              : 
      30              : class DeviceKeysList {
      31              :   Client client;
      32              :   String userId;
      33              :   bool outdated = true;
      34              :   Map<String, DeviceKeys> deviceKeys = {};
      35              :   Map<String, CrossSigningKey> crossSigningKeys = {};
      36              : 
      37           13 :   SignableKey? getKey(String id) => deviceKeys[id] ?? crossSigningKeys[id];
      38              : 
      39           36 :   CrossSigningKey? getCrossSigningKey(String type) => crossSigningKeys.values
      40           42 :       .firstWhereOrNull((key) => key.usage.contains(type));
      41              : 
      42           24 :   CrossSigningKey? get masterKey => getCrossSigningKey('master');
      43           12 :   CrossSigningKey? get selfSigningKey => getCrossSigningKey('self_signing');
      44            8 :   CrossSigningKey? get userSigningKey => getCrossSigningKey('user_signing');
      45              : 
      46            3 :   UserVerifiedStatus get verified {
      47            3 :     if (masterKey == null) {
      48              :       return UserVerifiedStatus.unknown;
      49              :     }
      50            6 :     if (masterKey!.verified) {
      51            3 :       for (final key in deviceKeys.values) {
      52            1 :         if (!key.verified) {
      53              :           return UserVerifiedStatus.unknownDevice;
      54              :         }
      55              :       }
      56              :       return UserVerifiedStatus.verified;
      57              :     } else {
      58            9 :       for (final key in deviceKeys.values) {
      59            3 :         if (!key.verified) {
      60              :           return UserVerifiedStatus.unknown;
      61              :         }
      62              :       }
      63              :       return UserVerifiedStatus.verified;
      64              :     }
      65              :   }
      66              : 
      67              :   /// Starts a verification with this device. This might need to create a new
      68              :   /// direct chat to send the verification request over this room. For this you
      69              :   /// can set parameters here.
      70            3 :   Future<KeyVerification> startVerification({
      71              :     bool? newDirectChatEnableEncryption,
      72              :     List<StateEvent>? newDirectChatInitialState,
      73              :   }) async {
      74            6 :     final encryption = client.encryption;
      75              :     if (encryption == null) {
      76            0 :       throw Exception('Encryption not enabled');
      77              :     }
      78           12 :     if (userId != client.userID) {
      79              :       // in-room verification with someone else
      80              :       Room? room;
      81              :       // we check if there's already a direct chat with the user
      82           13 :       for (final directChatRoomId in client.directChats[userId] ?? []) {
      83            2 :         final tempRoom = client.getRoomById(directChatRoomId);
      84              :         if (tempRoom != null &&
      85              :             // check if the room is a direct chat and has less than 2 members
      86              :             // (including the invited users)
      87            3 :             (tempRoom.summary.mInvitedMemberCount ?? 0) +
      88            3 :                     (tempRoom.summary.mJoinedMemberCount ?? 1) <=
      89              :                 2) {
      90              :           // Now we check if the users in the room are none other than the current
      91              :           // user and the user we want to verify
      92            2 :           final members = tempRoom.getParticipants([
      93              :             Membership.invite,
      94              :             Membership.join,
      95              :           ]);
      96            7 :           if (members.every((m) => {userId, client.userID}.contains(m.id))) {
      97              :             // if so, we use that room
      98              :             room = tempRoom;
      99              :             break;
     100              :           }
     101              :         }
     102              :       }
     103              :       // if there's no direct chat that satisfies the conditions, we create a new one
     104              :       if (room == null) {
     105            4 :         final newRoomId = await client.startDirectChat(
     106            2 :           userId,
     107              :           enableEncryption: newDirectChatEnableEncryption,
     108              :           initialState: newDirectChatInitialState,
     109              :           waitForSync: false,
     110              :           skipExistingChat: true, // to create a new room directly
     111              :         );
     112            4 :         room = client.getRoomById(newRoomId) ??
     113            4 :             Room(id: newRoomId, client: client);
     114              :       }
     115              : 
     116              :       final request =
     117            4 :           KeyVerification(encryption: encryption, room: room, userId: userId);
     118            2 :       await request.start();
     119              :       // no need to add to the request client object. As we are doing a room
     120              :       // verification request that'll happen automatically once we know the transaction id
     121              :       return request;
     122              :     } else {
     123              :       // start verification with verified devices
     124            1 :       final request = KeyVerification(
     125              :         encryption: encryption,
     126            1 :         userId: userId,
     127              :         deviceId: '*',
     128              :       );
     129            1 :       await request.start();
     130            2 :       encryption.keyVerificationManager.addRequest(request);
     131              :       return request;
     132              :     }
     133              :   }
     134              : 
     135            1 :   DeviceKeysList.fromDbJson(
     136              :     Map<String, dynamic> dbEntry,
     137              :     List<Map<String, dynamic>> childEntries,
     138              :     List<Map<String, dynamic>> crossSigningEntries,
     139              :     this.client,
     140            1 :   ) : userId = dbEntry['user_id'] ?? '' {
     141            2 :     outdated = dbEntry['outdated'];
     142            2 :     deviceKeys = {};
     143            2 :     for (final childEntry in childEntries) {
     144              :       try {
     145            2 :         final entry = DeviceKeys.fromDb(childEntry, client);
     146            1 :         if (!entry.isValid) throw Exception('Invalid device keys');
     147            3 :         deviceKeys[childEntry['device_id']] = entry;
     148              :       } catch (e, s) {
     149            0 :         Logs().w('Skipping invalid user device key', e, s);
     150            0 :         outdated = true;
     151              :       }
     152              :     }
     153            2 :     for (final crossSigningEntry in crossSigningEntries) {
     154              :       try {
     155            2 :         final entry = CrossSigningKey.fromDbJson(crossSigningEntry, client);
     156            1 :         if (!entry.isValid) throw Exception('Invalid device keys');
     157            3 :         crossSigningKeys[crossSigningEntry['public_key']] = entry;
     158              :       } catch (e, s) {
     159            0 :         Logs().w('Skipping invalid cross siging key', e, s);
     160            0 :         outdated = true;
     161              :       }
     162              :     }
     163              :   }
     164              : 
     165           31 :   DeviceKeysList(this.userId, this.client);
     166              : }
     167              : 
     168              : class SimpleSignableKey extends MatrixSignableKey {
     169              :   @override
     170              :   String? identifier;
     171              : 
     172            7 :   SimpleSignableKey.fromJson(Map<String, dynamic> super.json)
     173            7 :       : super.fromJson();
     174              : }
     175              : 
     176              : abstract class SignableKey extends MatrixSignableKey {
     177              :   Client client;
     178              :   Map<String, dynamic>? validSignatures;
     179              :   bool? _verified;
     180              :   bool? _blocked;
     181              : 
     182          155 :   String? get ed25519Key => keys['ed25519:$identifier'];
     183            9 :   bool get verified =>
     184           33 :       identifier != null && (directVerified || crossVerified) && !(blocked);
     185           62 :   bool get blocked => _blocked ?? false;
     186            6 :   set blocked(bool isBlocked) => _blocked = isBlocked;
     187              : 
     188            5 :   bool get encryptToDevice {
     189            5 :     if (blocked) return false;
     190              : 
     191           10 :     if (identifier == null || ed25519Key == null) return false;
     192              : 
     193           10 :     switch (client.shareKeysWith) {
     194            5 :       case ShareKeysWith.all:
     195              :         return true;
     196            5 :       case ShareKeysWith.crossVerifiedIfEnabled:
     197           24 :         if (client.userDeviceKeys[userId]?.masterKey == null) return true;
     198            2 :         return hasValidSignatureChain(verifiedByTheirMasterKey: true);
     199            1 :       case ShareKeysWith.crossVerified:
     200            1 :         return hasValidSignatureChain(verifiedByTheirMasterKey: true);
     201            1 :       case ShareKeysWith.directlyVerifiedOnly:
     202            1 :         return directVerified;
     203              :     }
     204              :   }
     205              : 
     206           23 :   void setDirectVerified(bool isVerified) {
     207           23 :     _verified = isVerified;
     208              :   }
     209              : 
     210           62 :   bool get directVerified => _verified ?? false;
     211           16 :   bool get crossVerified => hasValidSignatureChain();
     212           20 :   bool get signed => hasValidSignatureChain(verifiedOnly: false);
     213              : 
     214           31 :   SignableKey.fromJson(Map<String, dynamic> super.json, this.client)
     215           31 :       : super.fromJson() {
     216           31 :     _verified = false;
     217           31 :     _blocked = false;
     218              :   }
     219              : 
     220            7 :   SimpleSignableKey cloneForSigning() {
     221           21 :     final newKey = SimpleSignableKey.fromJson(toJson().copy());
     222           14 :     newKey.identifier = identifier;
     223           14 :     (newKey.signatures ??= {}).clear();
     224              :     return newKey;
     225              :   }
     226              : 
     227           23 :   String get signingContent {
     228           46 :     final data = super.toJson().copy();
     229              :     // some old data might have the custom verified and blocked keys
     230           23 :     data.remove('verified');
     231           23 :     data.remove('blocked');
     232              :     // remove the keys not needed for signing
     233           23 :     data.remove('unsigned');
     234           23 :     data.remove('signatures');
     235           46 :     return String.fromCharCodes(canonicalJson.encode(data));
     236              :   }
     237              : 
     238           31 :   bool _verifySignature(
     239              :     String pubKey,
     240              :     String signature, {
     241              :     bool isSignatureWithoutLibolmValid = false,
     242              :   }) {
     243              :     olm.Utility olmutil;
     244              :     try {
     245           31 :       olmutil = olm.Utility();
     246              :     } catch (e) {
     247              :       // if no libolm is present we land in this catch block, and return the default
     248              :       // set if no libolm is there. Some signatures should be assumed-valid while others
     249              :       // should be assumed-invalid
     250              :       return isSignatureWithoutLibolmValid;
     251              :     }
     252              :     var valid = false;
     253              :     try {
     254           46 :       olmutil.ed25519_verify(pubKey, signingContent, signature);
     255              :       valid = true;
     256              :     } catch (_) {
     257              :       // bad signature
     258              :       valid = false;
     259              :     } finally {
     260           23 :       olmutil.free();
     261              :     }
     262              :     return valid;
     263              :   }
     264              : 
     265           12 :   bool hasValidSignatureChain({
     266              :     bool verifiedOnly = true,
     267              :     Set<String>? visited,
     268              :     Set<String>? onlyValidateUserIds,
     269              : 
     270              :     /// Only check if this key is verified by their Master key.
     271              :     bool verifiedByTheirMasterKey = false,
     272              :   }) {
     273           24 :     if (!client.encryptionEnabled) {
     274              :       return false;
     275              :     }
     276              : 
     277              :     final visited_ = visited ?? <String>{};
     278              :     final onlyValidateUserIds_ = onlyValidateUserIds ?? <String>{};
     279              : 
     280           33 :     final setKey = '$userId;$identifier';
     281           11 :     if (visited_.contains(setKey) ||
     282           11 :         (onlyValidateUserIds_.isNotEmpty &&
     283            0 :             !onlyValidateUserIds_.contains(userId))) {
     284              :       return false; // prevent recursion & validate hasValidSignatureChain
     285              :     }
     286           11 :     visited_.add(setKey);
     287              : 
     288           11 :     if (signatures == null) return false;
     289              : 
     290           33 :     for (final signatureEntries in signatures!.entries) {
     291           11 :       final otherUserId = signatureEntries.key;
     292           33 :       if (!client.userDeviceKeys.containsKey(otherUserId)) {
     293              :         continue;
     294              :       }
     295              :       // we don't allow transitive trust unless it is for ourself
     296           22 :       if (otherUserId != userId && otherUserId != client.userID) {
     297              :         continue;
     298              :       }
     299           33 :       for (final signatureEntry in signatureEntries.value.entries) {
     300           11 :         final fullKeyId = signatureEntry.key;
     301           11 :         final signature = signatureEntry.value;
     302           22 :         final keyId = fullKeyId.substring('ed25519:'.length);
     303              :         // we ignore self-signatures here
     304           44 :         if (otherUserId == userId && keyId == identifier) {
     305              :           continue;
     306              :         }
     307              : 
     308           55 :         final key = client.userDeviceKeys[otherUserId]?.deviceKeys[keyId] ??
     309           55 :             client.userDeviceKeys[otherUserId]?.crossSigningKeys[keyId];
     310              :         if (key == null) {
     311              :           continue;
     312              :         }
     313              : 
     314           10 :         if (onlyValidateUserIds_.isNotEmpty &&
     315            0 :             !onlyValidateUserIds_.contains(key.userId)) {
     316              :           // we don't want to verify keys from this user
     317              :           continue;
     318              :         }
     319              : 
     320           10 :         if (key.blocked) {
     321              :           continue; // we can't be bothered about this keys signatures
     322              :         }
     323              :         var haveValidSignature = false;
     324              :         var gotSignatureFromCache = false;
     325           10 :         final fullKeyIdBool = validSignatures
     326            7 :             ?.tryGetMap<String, Object?>(otherUserId)
     327            7 :             ?.tryGet<bool>(fullKeyId);
     328           10 :         if (fullKeyIdBool == true) {
     329              :           haveValidSignature = true;
     330              :           gotSignatureFromCache = true;
     331           10 :         } else if (fullKeyIdBool == false) {
     332              :           haveValidSignature = false;
     333              :           gotSignatureFromCache = true;
     334              :         }
     335              : 
     336           10 :         if (!gotSignatureFromCache && key.ed25519Key != null) {
     337              :           // validate the signature manually
     338           20 :           haveValidSignature = _verifySignature(key.ed25519Key!, signature);
     339           20 :           final validSignatures = this.validSignatures ??= <String, dynamic>{};
     340           10 :           if (!validSignatures.containsKey(otherUserId)) {
     341           20 :             validSignatures[otherUserId] = <String, dynamic>{};
     342              :           }
     343           20 :           validSignatures[otherUserId][fullKeyId] = haveValidSignature;
     344              :         }
     345              :         if (!haveValidSignature) {
     346              :           // no valid signature, this key is useless
     347              :           continue;
     348              :         }
     349              : 
     350            5 :         if ((verifiedOnly && key.directVerified) ||
     351           10 :             (key is CrossSigningKey &&
     352           20 :                 key.usage.contains('master') &&
     353              :                 (verifiedByTheirMasterKey ||
     354           25 :                     (key.directVerified && key.userId == client.userID)))) {
     355              :           return true; // we verified this key and it is valid...all checks out!
     356              :         }
     357              :         // or else we just recurse into that key and check if it works out
     358           10 :         final haveChain = key.hasValidSignatureChain(
     359              :           verifiedOnly: verifiedOnly,
     360              :           visited: visited_,
     361              :           onlyValidateUserIds: onlyValidateUserIds,
     362              :           verifiedByTheirMasterKey: verifiedByTheirMasterKey,
     363              :         );
     364              :         if (haveChain) {
     365              :           return true;
     366              :         }
     367              :       }
     368              :     }
     369              :     return false;
     370              :   }
     371              : 
     372            7 :   Future<void> setVerified(bool newVerified, [bool sign = true]) async {
     373            7 :     _verified = newVerified;
     374           14 :     final encryption = client.encryption;
     375              :     if (newVerified &&
     376              :         sign &&
     377              :         encryption != null &&
     378            4 :         client.encryptionEnabled &&
     379            6 :         encryption.crossSigning.signable([this])) {
     380              :       // sign the key!
     381              :       // ignore: unawaited_futures
     382            6 :       encryption.crossSigning.sign([this]);
     383              :     }
     384              :   }
     385              : 
     386              :   Future<void> setBlocked(bool newBlocked);
     387              : 
     388           31 :   @override
     389              :   Map<String, dynamic> toJson() {
     390           62 :     final data = super.toJson().copy();
     391              :     // some old data may have the verified and blocked keys which are unneeded now
     392           31 :     data.remove('verified');
     393           31 :     data.remove('blocked');
     394              :     return data;
     395              :   }
     396              : 
     397            0 :   @override
     398            0 :   String toString() => json.encode(toJson());
     399              : 
     400            9 :   @override
     401            9 :   bool operator ==(Object other) => (other is SignableKey &&
     402           27 :       other.userId == userId &&
     403           27 :       other.identifier == identifier);
     404              : 
     405            9 :   @override
     406           27 :   int get hashCode => Object.hash(userId, identifier);
     407              : }
     408              : 
     409              : class CrossSigningKey extends SignableKey {
     410              :   @override
     411              :   String? identifier;
     412              : 
     413           62 :   String? get publicKey => identifier;
     414              :   late List<String> usage;
     415              : 
     416           31 :   bool get isValid =>
     417           62 :       userId.isNotEmpty &&
     418           31 :       publicKey != null &&
     419           62 :       keys.isNotEmpty &&
     420           31 :       ed25519Key != null;
     421              : 
     422            5 :   @override
     423              :   Future<void> setVerified(bool newVerified, [bool sign = true]) async {
     424            5 :     if (!isValid) {
     425            0 :       throw Exception('setVerified called on invalid key');
     426              :     }
     427            5 :     await super.setVerified(newVerified, sign);
     428           10 :     await client.database
     429           15 :         ?.setVerifiedUserCrossSigningKey(newVerified, userId, publicKey!);
     430              :   }
     431              : 
     432            2 :   @override
     433              :   Future<void> setBlocked(bool newBlocked) async {
     434            2 :     if (!isValid) {
     435            0 :       throw Exception('setBlocked called on invalid key');
     436              :     }
     437            2 :     _blocked = newBlocked;
     438            4 :     await client.database
     439            6 :         ?.setBlockedUserCrossSigningKey(newBlocked, userId, publicKey!);
     440              :   }
     441              : 
     442           31 :   CrossSigningKey.fromMatrixCrossSigningKey(
     443              :     MatrixCrossSigningKey key,
     444              :     Client client,
     445           93 :   ) : super.fromJson(key.toJson().copy(), client) {
     446           31 :     final json = toJson();
     447           62 :     identifier = key.publicKey;
     448           93 :     usage = json['usage'].cast<String>();
     449              :   }
     450              : 
     451            1 :   CrossSigningKey.fromDbJson(Map<String, dynamic> dbEntry, Client client)
     452            3 :       : super.fromJson(Event.getMapFromPayload(dbEntry['content']), client) {
     453            1 :     final json = toJson();
     454            2 :     identifier = dbEntry['public_key'];
     455            3 :     usage = json['usage'].cast<String>();
     456            2 :     _verified = dbEntry['verified'];
     457            2 :     _blocked = dbEntry['blocked'];
     458              :   }
     459              : 
     460            2 :   CrossSigningKey.fromJson(Map<String, dynamic> json, Client client)
     461            4 :       : super.fromJson(json.copy(), client) {
     462            2 :     final json = toJson();
     463            6 :     usage = json['usage'].cast<String>();
     464            4 :     if (keys.isNotEmpty) {
     465            8 :       identifier = keys.values.first;
     466              :     }
     467              :   }
     468              : }
     469              : 
     470              : class DeviceKeys extends SignableKey {
     471              :   @override
     472              :   String? identifier;
     473              : 
     474           62 :   String? get deviceId => identifier;
     475              :   late List<String> algorithms;
     476              :   late DateTime lastActive;
     477              : 
     478          155 :   String? get curve25519Key => keys['curve25519:$deviceId'];
     479            0 :   String? get deviceDisplayName =>
     480            0 :       unsigned?.tryGet<String>('device_display_name');
     481              : 
     482              :   bool? _validSelfSignature;
     483           31 :   bool get selfSigned =>
     484           31 :       _validSelfSignature ??
     485           62 :       (_validSelfSignature = deviceId != null &&
     486           31 :           signatures
     487           62 :                   ?.tryGetMap<String, Object?>(userId)
     488           93 :                   ?.tryGet<String>('ed25519:$deviceId') !=
     489              :               null &&
     490              :           // without libolm we still want to be able to add devices. In that case we ofc just can't
     491              :           // verify the signature
     492           31 :           _verifySignature(
     493           31 :             ed25519Key!,
     494          186 :             signatures![userId]!['ed25519:$deviceId']!,
     495              :             isSignatureWithoutLibolmValid: true,
     496              :           ));
     497              : 
     498           31 :   @override
     499           62 :   bool get blocked => super.blocked || !selfSigned;
     500              : 
     501           31 :   bool get isValid =>
     502           31 :       deviceId != null &&
     503           62 :       keys.isNotEmpty &&
     504           31 :       curve25519Key != null &&
     505           31 :       ed25519Key != null &&
     506           31 :       selfSigned;
     507              : 
     508            3 :   @override
     509              :   Future<void> setVerified(bool newVerified, [bool sign = true]) async {
     510            3 :     if (!isValid) {
     511              :       //throw Exception('setVerified called on invalid key');
     512              :       return;
     513              :     }
     514            3 :     await super.setVerified(newVerified, sign);
     515            6 :     await client.database
     516            9 :         ?.setVerifiedUserDeviceKey(newVerified, userId, deviceId!);
     517              :   }
     518              : 
     519            2 :   @override
     520              :   Future<void> setBlocked(bool newBlocked) async {
     521            2 :     if (!isValid) {
     522              :       //throw Exception('setBlocked called on invalid key');
     523              :       return;
     524              :     }
     525            2 :     _blocked = newBlocked;
     526            4 :     await client.database
     527            6 :         ?.setBlockedUserDeviceKey(newBlocked, userId, deviceId!);
     528              :   }
     529              : 
     530           31 :   DeviceKeys.fromMatrixDeviceKeys(
     531              :     MatrixDeviceKeys keys,
     532              :     Client client, [
     533              :     DateTime? lastActiveTs,
     534           93 :   ]) : super.fromJson(keys.toJson().copy(), client) {
     535           31 :     final json = toJson();
     536           62 :     identifier = keys.deviceId;
     537           93 :     algorithms = json['algorithms'].cast<String>();
     538           62 :     lastActive = lastActiveTs ?? DateTime.now();
     539              :   }
     540              : 
     541            1 :   DeviceKeys.fromDb(Map<String, dynamic> dbEntry, Client client)
     542            3 :       : super.fromJson(Event.getMapFromPayload(dbEntry['content']), client) {
     543            1 :     final json = toJson();
     544            2 :     identifier = dbEntry['device_id'];
     545            3 :     algorithms = json['algorithms'].cast<String>();
     546            2 :     _verified = dbEntry['verified'];
     547            2 :     _blocked = dbEntry['blocked'];
     548            1 :     lastActive =
     549            2 :         DateTime.fromMillisecondsSinceEpoch(dbEntry['last_active'] ?? 0);
     550              :   }
     551              : 
     552            4 :   DeviceKeys.fromJson(Map<String, dynamic> json, Client client)
     553            8 :       : super.fromJson(json.copy(), client) {
     554            4 :     final json = toJson();
     555            8 :     identifier = json['device_id'];
     556           12 :     algorithms = json['algorithms'].cast<String>();
     557            8 :     lastActive = DateTime.fromMillisecondsSinceEpoch(0);
     558              :   }
     559              : 
     560            1 :   Future<KeyVerification> startVerification() async {
     561            1 :     if (!isValid) {
     562            0 :       throw Exception('setVerification called on invalid key');
     563              :     }
     564            2 :     final encryption = client.encryption;
     565              :     if (encryption == null) {
     566            0 :       throw Exception('setVerification called with disabled encryption');
     567              :     }
     568              : 
     569            1 :     final request = KeyVerification(
     570              :       encryption: encryption,
     571            1 :       userId: userId,
     572            1 :       deviceId: deviceId!,
     573              :     );
     574              : 
     575            1 :     await request.start();
     576            2 :     encryption.keyVerificationManager.addRequest(request);
     577              :     return request;
     578              :   }
     579              : }
        

Generated by: LCOV version 2.0-1