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(Event update) async {
92 4 : final type = update.type.startsWith('m.key.verification.')
93 1 : ? update.type
94 4 : : update.content.tryGet<String>('msgtype');
95 : if (type == null ||
96 2 : !type.startsWith('m.key.verification.') ||
97 6 : client.verificationMethods.isEmpty) {
98 : return;
99 : }
100 1 : if (type == EventTypes.KeyVerificationRequest) {
101 2 : update.content['timestamp'] =
102 2 : update.originServerTs.millisecondsSinceEpoch;
103 : }
104 :
105 : final transactionId =
106 3 : KeyVerification.getTransactionId(update.content) ?? update.eventId;
107 :
108 2 : final req = _requests[transactionId];
109 : if (req != null) {
110 2 : final otherDeviceId = update.content.tryGet<String>('from_device');
111 4 : if (update.senderId != client.userID) {
112 3 : await req.handlePayload(type, update.content, update.eventId);
113 4 : } else if (update.senderId == 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 (update.senderId != 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: update.senderId,
131 : room: room,
132 : );
133 1 : await newKeyRequest.handlePayload(
134 : type,
135 1 : update.content,
136 1 : update.eventId,
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 : }
|