From db0068b4298a615bd6a4661e19d3da5ba5698d83 Mon Sep 17 00:00:00 2001 From: sachaaaaa Date: Thu, 21 Nov 2019 11:25:03 +1100 Subject: [PATCH] Sending and handling of special UNPAIRING_REQUEST message --- js/background.js | 9 ++++ js/modules/loki_file_server_api.js | 4 ++ js/views/app_view.js | 3 ++ js/views/device_pairing_dialog_view.js | 5 +- libloki/api.js | 22 ++++++++ libtextsecure/message_receiver.js | 71 +++++++++++++++++++++++++- libtextsecure/outgoing_message.js | 2 + preload.js | 2 +- protos/SignalService.proto | 1 + 9 files changed, 113 insertions(+), 6 deletions(-) diff --git a/js/background.js b/js/background.js index b367d641b..d83615be7 100644 --- a/js/background.js +++ b/js/background.js @@ -1025,6 +1025,15 @@ pubKey ); }); + + Whisper.events.on('deviceUnpairingRequested', async pubKey => { + await libloki.storage.removePairingAuthorisationForSecondaryPubKey( + pubKey + ); + await window.lokiFileServerAPI.updateOurDeviceMapping(); + // TODO: we should ensure the message was sent and retry automatically if not + await libloki.api.sendUnpairingMessageToSecondary(pubKey); + }); } window.getSyncRequest = () => diff --git a/js/modules/loki_file_server_api.js b/js/modules/loki_file_server_api.js index 0721603f9..8acd119cc 100644 --- a/js/modules/loki_file_server_api.js +++ b/js/modules/loki_file_server_api.js @@ -224,6 +224,10 @@ class LokiFileServerAPI { uploadPrivateAttachment(data) { return this._server.uploadData(data); } + + clearOurDeviceMappingAnnotations() { + return this._server.setSelfAnnotation(DEVICE_MAPPING_ANNOTATION_KEY, null); + } } module.exports = LokiFileServerAPI; diff --git a/js/views/app_view.js b/js/views/app_view.js index 5a0a8fc6f..c78618f40 100644 --- a/js/views/app_view.js +++ b/js/views/app_view.js @@ -223,6 +223,9 @@ dialog.on('devicePairingRequestRejected', pubKey => Whisper.events.trigger('devicePairingRequestRejected', pubKey) ); + dialog.on('deviceUnpairingRequested', pubKey => + Whisper.events.trigger('deviceUnpairingRequested', pubKey) + ); dialog.once('close', () => { Whisper.events.off('devicePairingRequestReceived'); }); diff --git a/js/views/device_pairing_dialog_view.js b/js/views/device_pairing_dialog_view.js index e9a3294d0..e7c4edcbc 100644 --- a/js/views/device_pairing_dialog_view.js +++ b/js/views/device_pairing_dialog_view.js @@ -117,10 +117,7 @@ this.pubKey = this.pubKeyRequests.pop(); }, async confirmUnpairDevice() { - await libloki.storage.removePairingAuthorisationForSecondaryPubKey( - this.pubKeyToUnpair - ); - await lokiFileServerAPI.updateOurDeviceMapping(); + this.trigger('deviceUnpairingRequested', this.pubKeyToUnpair); this.reset(); this.showView(); }, diff --git a/libloki/api.js b/libloki/api.js index a78a0a218..b9ee06920 100644 --- a/libloki/api.js +++ b/libloki/api.js @@ -107,6 +107,27 @@ secondaryDevicePubKey, }); } + + function sendUnpairingMessageToSecondary(pubKey) { + const flags = textsecure.protobuf.DataMessage.Flags.UNPAIRING_REQUEST; + const dataMessage = new textsecure.protobuf.DataMessage({ + flags, + }); + const content = new textsecure.protobuf.Content({ + dataMessage, + }); + const options = { messageType: 'device-unpairing' }; + const outgoingMessage = new textsecure.OutgoingMessage( + null, // server + Date.now(), // timestamp, + [pubKey], // numbers + content, // message + true, // silent + () => null, // callback + options + ); + return outgoingMessage.sendToNumber(pubKey); + } // Serialise as ... // This is an implementation of the reciprocal of contacts_parser.js function serialiseByteBuffers(buffers) { @@ -226,6 +247,7 @@ broadcastOnlineStatus, sendPairingAuthorisation, createPairingAuthorisationProtoMessage, + sendUnpairingMessageToSecondary, createContactSyncProtoMessage, }; })(); diff --git a/libtextsecure/message_receiver.js b/libtextsecure/message_receiver.js index 1aa5154db..2e14a0e45 100644 --- a/libtextsecure/message_receiver.js +++ b/libtextsecure/message_receiver.js @@ -1286,7 +1286,8 @@ MessageReceiver.prototype.extend({ this.processDecrypted(envelope, msg).then(async message => { const groupId = message.group && message.group.id; const isBlocked = this.isGroupBlocked(groupId); - const isMe = envelope.source === textsecure.storage.user.getNumber(); + const ourPubKey = textsecure.storage.user.getNumber(); + const isMe = envelope.source === ourPubKey; const conversation = window.ConversationController.get(envelope.source); const isLeavingGroup = Boolean( message.group && @@ -1294,6 +1295,72 @@ MessageReceiver.prototype.extend({ ); const friendRequest = envelope.type === textsecure.protobuf.Envelope.Type.FRIEND_REQUEST; + const { UNPAIRING_REQUEST } = textsecure.protobuf.DataMessage.Flags; + // eslint-disable-next-line no-bitwise + const isUnpairingRequest = Boolean(message.flags & UNPAIRING_REQUEST); + + if (isUnpairingRequest) { + // TODO: move high-level pairing logic to libloki.multidevice.xx + + const unpairingRequestIsLegit = async () => { + const isSecondary = textsecure.storage.get('isSecondaryDevice'); + if (!isSecondary) { + return false; + } + const primaryPubKey = window.storage.get('primaryDevicePubKey'); + // TODO: allow unpairing from any paired device? + if (envelope.source !== primaryPubKey) { + return false; + } + + const primaryMapping = await lokiFileServerAPI.getUserDeviceMapping( + primaryPubKey + ); + + if (!primaryMapping) { + return false; + } + + // We expect the primary device to have updated its mapping + // before sending the unpairing request + const found = primaryMapping.authorisations.find( + authorisation => authorisation.secondaryDevicePubKey === ourPubKey + ); + + // our pubkey should NOT be in the primary device mapping + if (found) { + return false; + } + + return true; + }; + + const legit = await unpairingRequestIsLegit(); + + this.removeFromCache(envelope); + + if (legit) { + // remove our device mapping annotations from file server + await lokiFileServerAPI.clearOurDeviceMappingAnnotations(); + // Delete the account and restart + try { + await window.Signal.Logs.deleteAll(); + await window.Signal.Data.removeAll(); + await window.Signal.Data.close(); + await window.Signal.Data.removeDB(); + await window.Signal.Data.removeOtherData(); + // TODO generate an empty db with a flag + // to display a message about the unpairing + // after the app restarts + } catch (error) { + window.log.error( + 'Something went wrong deleting all data:', + error && error.stack ? error.stack : error + ); + } + window.restart(); + } + } // Check if we need to update any profile names if (!isMe && conversation) { @@ -1787,6 +1854,8 @@ MessageReceiver.prototype.extend({ decrypted.attachments = []; } else if (decrypted.flags & FLAGS.BACKGROUND_FRIEND_REQUEST) { // do nothing + } else if (decrypted.flags & FLAGS.UNPAIRING_REQUEST) { + // do nothing } else if (decrypted.flags !== 0) { throw new Error('Unknown flags in message'); } diff --git a/libtextsecure/outgoing_message.js b/libtextsecure/outgoing_message.js index b33ba8347..12b17a351 100644 --- a/libtextsecure/outgoing_message.js +++ b/libtextsecure/outgoing_message.js @@ -443,6 +443,8 @@ OutgoingMessage.prototype = { switch (type) { case 'friend-request': return 4 * 24 * 60 * 60 * 1000; // 4 days for friend request message + case 'device-unpairing': + return 4 * 24 * 60 * 60 * 1000; // 4 days for device unpairing case 'onlineBroadcast': return 60 * 1000; // 1 minute for online broadcast message case 'typing': diff --git a/preload.js b/preload.js index 6085bbd94..cc55e5aba 100644 --- a/preload.js +++ b/preload.js @@ -469,6 +469,6 @@ window.pubkeyPattern = /@[a-fA-F0-9]{64,66}\b/g; window.SMALL_GROUP_SIZE_LIMIT = 10; window.lokiFeatureFlags = { - multiDeviceUnpairing: false, + multiDeviceUnpairing: true, privateGroupChats: false, }; diff --git a/protos/SignalService.proto b/protos/SignalService.proto index f70505168..cd57885b0 100644 --- a/protos/SignalService.proto +++ b/protos/SignalService.proto @@ -105,6 +105,7 @@ message DataMessage { END_SESSION = 1; EXPIRATION_TIMER_UPDATE = 2; PROFILE_KEY_UPDATE = 4; + UNPAIRING_REQUEST = 128; BACKGROUND_FRIEND_REQUEST = 256; }