From 5924e57cf1acf18af80b97b49db51784b7845693 Mon Sep 17 00:00:00 2001 From: Sorunome Date: Fri, 30 Oct 2020 11:36:08 +0100 Subject: [PATCH] feat: Add broadcast to-device verification --- lib/encryption/key_manager.dart | 1 + lib/encryption/key_verification_manager.dart | 9 +++-- lib/encryption/utils/key_verification.dart | 41 ++++++++++++++++++-- lib/src/utils/device_keys_list.dart | 33 ++++++++++------ 4 files changed, 67 insertions(+), 17 deletions(-) diff --git a/lib/encryption/key_manager.dart b/lib/encryption/key_manager.dart index 2b4da5d7..814f2ba4 100644 --- a/lib/encryption/key_manager.dart +++ b/lib/encryption/key_manager.dart @@ -563,6 +563,7 @@ class KeyManager { stacktrace); } } + // TODO: also don't request from others if we have an index of 0 now 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 diff --git a/lib/encryption/key_verification_manager.dart b/lib/encryption/key_verification_manager.dart index 387836dd..7a599304 100644 --- a/lib/encryption/key_verification_manager.dart +++ b/lib/encryption/key_verification_manager.dart @@ -65,9 +65,12 @@ class KeyVerificationManager { return; // TODO: send cancel with unknown transaction id } if (_requests.containsKey(transactionId)) { - await _requests[transactionId].handlePayload(event.type, event.content); + // make sure that new requests can't come from ourself + if (!{'m.key.verification.request'}.contains(event.type)) { + await _requests[transactionId].handlePayload(event.type, event.content); + } } else { - if (!['m.key.verification.request', 'm.key.verification.start'] + if (!{'m.key.verification.request', 'm.key.verification.start'} .contains(event.type)) { return; // we can only start on these } @@ -115,7 +118,7 @@ class KeyVerificationManager { _requests.remove(transactionId); } } else if (event['sender'] != client.userID) { - if (!['m.key.verification.request', 'm.key.verification.start'] + if (!{'m.key.verification.request', 'm.key.verification.start'} .contains(type)) { return; // we can only start on these } diff --git a/lib/encryption/utils/key_verification.dart b/lib/encryption/utils/key_verification.dart index 8075a216..74b416e2 100644 --- a/lib/encryption/utils/key_verification.dart +++ b/lib/encryption/utils/key_verification.dart @@ -140,6 +140,9 @@ class KeyVerification { bool canceled = false; String canceledCode; String canceledReason; + bool get isDone => + canceled || + {KeyVerificationState.error, KeyVerificationState.done}.contains(state); KeyVerification( {this.encryption, @@ -200,6 +203,9 @@ class KeyVerification { Future handlePayload(String type, Map payload, [String eventId]) async { + if (isDone) { + return; // no need to do anything with already canceled requests + } while (_handlePayloadLock) { await Future.delayed(Duration(milliseconds: 50)); } @@ -235,6 +241,21 @@ class KeyVerification { setState(KeyVerificationState.askAccept); break; case 'm.key.verification.ready': + if (deviceId == '*') { + _deviceId = payload['from_device']; // gotta set the real device id + // and broadcast the cancel to the other devices + final devices = List.from( + client.userDeviceKeys[userId].deviceKeys.values); + devices.removeWhere( + (d) => {deviceId, client.deviceID}.contains(d.deviceId)); + final cancelPayload = { + 'reason': 'Another device accepted the request', + 'code': 'm.accepted', + }; + makePayload(cancelPayload); + await client.sendToDeviceEncrypted( + devices, 'm.key.verification.cancel', cancelPayload); + } _deviceId ??= payload['from_device']; possibleMethods = _intersect(knownVerificationMethods, payload['methods']); @@ -383,6 +404,9 @@ class KeyVerification { /// called when the user rejects an incoming verification Future rejectVerification() async { + if (isDone) { + return; + } if (!(await verifyLastStep( ['m.key.verification.request', 'm.key.verification.start']))) { return; @@ -557,7 +581,7 @@ class KeyVerification { if (room != null) { Logs.info( '[Key Verification] Sending to ${userId} in room ${room.id}...'); - if (['m.key.verification.request'].contains(type)) { + if ({'m.key.verification.request'}.contains(type)) { payload['msgtype'] = type; payload['to'] = userId; payload['body'] = @@ -572,8 +596,19 @@ class KeyVerification { } else { Logs.info( '[Key Verification] Sending to ${userId} device ${deviceId}...'); - await client.sendToDeviceEncrypted( - [client.userDeviceKeys[userId].deviceKeys[deviceId]], type, payload); + if (deviceId == '*') { + if ({'m.key.verification.request'}.contains(type)) { + await client.sendToDevicesOfUserIds({userId}, type, payload); + } else { + Logs.error( + '[Key Verification] Tried to broadcast and un-broadcastable type: ${type}'); + } + } else { + await client.sendToDeviceEncrypted( + [client.userDeviceKeys[userId].deviceKeys[deviceId]], + type, + payload); + } } } diff --git a/lib/src/utils/device_keys_list.dart b/lib/src/utils/device_keys_list.dart index 9fb8f8be..b4d3693a 100644 --- a/lib/src/utils/device_keys_list.dart +++ b/lib/src/utils/device_keys_list.dart @@ -54,18 +54,29 @@ class DeviceKeysList { } Future startVerification() async { - final roomId = - await User(userId, room: Room(client: client)).startDirectChat(); - if (roomId == null) { - throw 'Unable to start new room'; + if (userId != client.userID) { + // in-room verification with someone else + final roomId = + await User(userId, room: Room(client: client)).startDirectChat(); + if (roomId == null) { + throw 'Unable to start new room'; + } + final room = + client.getRoomById(roomId) ?? Room(id: roomId, client: client); + 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 + return request; + } else { + // broadcast self-verification + final request = KeyVerification( + encryption: client.encryption, userId: userId, deviceId: '*'); + await request.start(); + client.encryption.keyVerificationManager.addRequest(request); + return request; } - final room = client.getRoomById(roomId) ?? Room(id: roomId, client: client); - 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 - return request; } DeviceKeysList.fromDb(