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 'package:matrix/encryption/encryption.dart'; 20 : import 'package:matrix/encryption/utils/key_verification.dart'; 21 : import 'package:matrix/matrix.dart'; 22 : 23 : class KeyVerificationManager { 24 : final Encryption encryption; 25 9 : Client get client => encryption.client; 26 : 27 24 : KeyVerificationManager(this.encryption); 28 : 29 : final Map<String, KeyVerification> _requests = {}; 30 : 31 24 : Future<void> cleanup() async { 32 : final Set entriesToDispose = <String>{}; 33 50 : for (final entry in _requests.entries) { 34 4 : var dispose = entry.value.canceled || 35 6 : entry.value.state == KeyVerificationState.done || 36 6 : entry.value.state == KeyVerificationState.error; 37 : if (!dispose) { 38 4 : dispose = !(await entry.value.verifyActivity()); 39 : } 40 : if (dispose) { 41 4 : entry.value.dispose(); 42 4 : entriesToDispose.add(entry.key); 43 : } 44 : } 45 72 : entriesToDispose.forEach(_requests.remove); 46 : } 47 : 48 3 : void addRequest(KeyVerification request) { 49 3 : if (request.transactionId == null) { 50 : return; 51 : } 52 9 : _requests[request.transactionId!] = request; 53 : } 54 : 55 6 : KeyVerification? getRequest(String requestId) => _requests[requestId]; 56 : 57 1 : Future<void> handleToDeviceEvent(ToDeviceEvent event) async { 58 2 : if (!event.type.startsWith('m.key.verification.') || 59 3 : client.verificationMethods.isEmpty) { 60 : return; 61 : } 62 : // we have key verification going on! 63 2 : final transactionId = KeyVerification.getTransactionId(event.content); 64 : if (transactionId == null) { 65 : return; // TODO: send cancel with unknown transaction id 66 : } 67 2 : final request = _requests[transactionId]; 68 : if (request != null) { 69 : // make sure that new requests can't come from ourself 70 3 : if (!{EventTypes.KeyVerificationRequest}.contains(event.type)) { 71 3 : await request.handlePayload(event.type, event.content); 72 : } 73 : } else { 74 2 : if (!{EventTypes.KeyVerificationRequest, EventTypes.KeyVerificationStart} 75 2 : .contains(event.type)) { 76 : return; // we can only start on these 77 : } 78 : final newKeyRequest = 79 3 : KeyVerification(encryption: encryption, userId: event.sender); 80 3 : await newKeyRequest.handlePayload(event.type, event.content); 81 2 : if (newKeyRequest.state != KeyVerificationState.askAccept) { 82 : // okay, something went wrong (unknown transaction id?), just dispose it 83 0 : newKeyRequest.dispose(); 84 : } else { 85 2 : _requests[transactionId] = newKeyRequest; 86 3 : client.onKeyVerificationRequest.add(newKeyRequest); 87 : } 88 : } 89 : } 90 : 91 2 : Future<void> handleEventUpdate(EventUpdate update) async { 92 2 : final event = update.content; 93 4 : final type = event['type'].startsWith('m.key.verification.') 94 1 : ? event['type'] 95 4 : : event['content']['msgtype']; 96 : if (type == null || 97 2 : !type.startsWith('m.key.verification.') || 98 6 : client.verificationMethods.isEmpty) { 99 : return; 100 : } 101 1 : if (type == EventTypes.KeyVerificationRequest) { 102 3 : event['content']['timestamp'] = event['origin_server_ts']; 103 : } 104 : 105 : final transactionId = 106 3 : KeyVerification.getTransactionId(event['content']) ?? event['event_id']; 107 : 108 2 : final req = _requests[transactionId]; 109 : if (req != null) { 110 2 : final otherDeviceId = event['content']['from_device']; 111 4 : if (event['sender'] != client.userID) { 112 3 : await req.handlePayload(type, event['content'], event['event_id']); 113 4 : } else if (event['sender'] == client.userID && 114 : otherDeviceId != null && 115 3 : otherDeviceId != client.deviceID) { 116 : // okay, another of our devices answered 117 1 : req.otherDeviceAccepted(); 118 1 : req.dispose(); 119 2 : _requests.remove(transactionId); 120 : } 121 4 : } else if (event['sender'] != client.userID) { 122 2 : if (!{EventTypes.KeyVerificationRequest, EventTypes.KeyVerificationStart} 123 1 : .contains(type)) { 124 : return; // we can only start on these 125 : } 126 3 : final room = client.getRoomById(update.roomID) ?? 127 3 : Room(id: update.roomID, client: client); 128 1 : final newKeyRequest = KeyVerification( 129 1 : encryption: encryption, 130 1 : userId: event['sender'], 131 : room: room, 132 : ); 133 1 : await newKeyRequest.handlePayload( 134 : type, 135 1 : event['content'], 136 1 : event['event_id'], 137 : ); 138 2 : if (newKeyRequest.state != KeyVerificationState.askAccept) { 139 : // something went wrong, let's just dispose the request 140 0 : newKeyRequest.dispose(); 141 : } else { 142 : // new request! Let's notify it and stuff 143 2 : _requests[transactionId] = newKeyRequest; 144 3 : client.onKeyVerificationRequest.add(newKeyRequest); 145 : } 146 : } 147 : } 148 : 149 21 : void dispose() { 150 45 : for (final req in _requests.values) { 151 3 : req.dispose(); 152 : } 153 : } 154 : }