From 7c52a1d038fb6f5a6dd78c9ee580df477b80a734 Mon Sep 17 00:00:00 2001 From: sachaaaaa Date: Fri, 29 Nov 2019 15:00:33 +1100 Subject: [PATCH] encrypt and decrypt images before upload and after download --- js/background.js | 30 ++++++++++++++------ libloki/api.js | 6 ++++ libtextsecure/message_receiver.js | 46 +++++++++++++++++++++++++------ libtextsecure/sendmessage.js | 18 ++++++------ 4 files changed, 72 insertions(+), 28 deletions(-) diff --git a/js/background.js b/js/background.js index 294bddfd6..1f9a5bd22 100644 --- a/js/background.js +++ b/js/background.js @@ -933,7 +933,8 @@ avatarColor: conversation.getColor(), onOk: async (newName, avatar) => { let newAvatarPath = ''; - + let url = null; + let profileKey = null; if (avatar) { const data = await readFile({ file: avatar }); @@ -945,19 +946,30 @@ conversation.setLokiProfile({ displayName: newName }); conversation.set('avatar', tempUrl); - const avatarPointer = await textsecure.messaging.uploadAvatar( - data + // Encrypt with a new key every time + profileKey = libsignal.crypto.getRandomBytes(32); + const encryptedData = await textsecure.crypto.encryptProfile( + data.data, + profileKey ); - conversation.set('avatarPointer', avatarPointer.url); + const avatarPointer = await textsecure.messaging.uploadAvatar({ + ...data, + data: encryptedData, + size: encryptedData.byteLength, + }); + + ({ url } = avatarPointer); - const downloaded = await messageReceiver.downloadAttachment({ - url: avatarPointer.url, + storage.put('profileKey', profileKey); + + conversation.set('avatarPointer', url); + + const upgraded = await Signal.Migrations.processNewAttachment({ isRaw: true, + data: data.data, + url, }); - const upgraded = await Signal.Migrations.processNewAttachment( - downloaded - ); newAvatarPath = upgraded.path; } diff --git a/libloki/api.js b/libloki/api.js index b9ee06920..bd252b4b4 100644 --- a/libloki/api.js +++ b/libloki/api.js @@ -202,11 +202,17 @@ if (isGrant) { // Send profile name to secondary device const lokiProfile = ourConversation.getLokiProfile(); + // profile.avatar is the path to the local image + // replace with the avatar URL + const avatarPointer = ourConversation.get('avatarPointer'); + lokiProfile.avatar = avatarPointer; const profile = new textsecure.protobuf.DataMessage.LokiProfile( lokiProfile ); + const profileKey = window.storage.get('profileKey'); const dataMessage = new textsecure.protobuf.DataMessage({ profile, + profileKey, }); // Attach contact list const conversations = await window.Signal.Data.getConversationsWithFriendStatus( diff --git a/libtextsecure/message_receiver.js b/libtextsecure/message_receiver.js index f98acbb53..39a241676 100644 --- a/libtextsecure/message_receiver.js +++ b/libtextsecure/message_receiver.js @@ -1127,12 +1127,13 @@ MessageReceiver.prototype.extend({ ); primaryConversation.trigger('change'); Whisper.events.trigger('secondaryDeviceRegistration'); - // Update profile name - if (dataMessage && dataMessage.profile) { + // Update profile + if (dataMessage) { + const { profile, profileKey } = dataMessage; const ourNumber = window.storage.get('primaryDevicePubKey'); const me = window.ConversationController.get(ourNumber); if (me) { - me.setLokiProfile(dataMessage.profile); + this.updateProfile(me, profile, profileKey); } } // Update contact list @@ -1238,7 +1239,7 @@ MessageReceiver.prototype.extend({ return true; }, - async updateProfile(conversation, profile) { + async updateProfile(conversation, profile, profileKey) { // Retain old values unless changed: const newProfile = conversation.get('profile') || {}; @@ -1252,17 +1253,40 @@ MessageReceiver.prototype.extend({ if (needsUpdate) { conversation.set('avatarPointer', profile.avatar); + conversation.set('profileKey', profileKey); const downloaded = await this.downloadAttachment({ url: profile.avatar, isRaw: true, }); - const upgraded = await Signal.Migrations.processNewAttachment( - downloaded - ); - newProfile.avatar = upgraded.path; + // null => use jazzicon + let path = null; + if (profileKey) { + // Convert profileKey to ArrayBuffer, if needed + const encoding = typeof profileKey === 'string' ? 'base64' : null; + try { + const profileKeyArrayBuffer = dcodeIO.ByteBuffer.wrap( + profileKey, + encoding + ).toArrayBuffer(); + const decryptedData = await textsecure.crypto.decryptProfile( + downloaded.data, + profileKeyArrayBuffer + ); + const upgraded = await Signal.Migrations.processNewAttachment({ + ...downloaded, + data: decryptedData, + }); + ({ path } = upgraded); + } catch (e) { + window.log.error(`Could not decrypt profile image: ${e}`); + } } + newProfile.avatar = path; + } + } else { + newProfile.avatar = null; } await conversation.setLokiProfile(newProfile); @@ -1361,7 +1385,11 @@ MessageReceiver.prototype.extend({ // Check if we need to update any profile names if (!isMe && conversation) { if (message.profile) { - await this.updateProfile(conversation, message.profile); + await this.updateProfile( + conversation, + message.profile, + message.profileKey + ); } } diff --git a/libtextsecure/sendmessage.js b/libtextsecure/sendmessage.js index a021fd9df..be5be6c97 100644 --- a/libtextsecure/sendmessage.js +++ b/libtextsecure/sendmessage.js @@ -191,8 +191,7 @@ MessageSender.prototype = { async makeAttachmentPointer( attachment, publicServer = null, - isRaw = false, - isAvatar = false + { isRaw = false, isAvatar = false } ) { if (typeof attachment !== 'object' || attachment == null) { return Promise.resolve(undefined); @@ -211,13 +210,7 @@ MessageSender.prototype = { const proto = new textsecure.protobuf.AttachmentPointer(); let attachmentData; - let server; - - if (publicServer) { - server = publicServer; - } else { - ({ server } = this); - } + const server = publicServer || this.server; if (publicServer || isRaw) { attachmentData = attachment.data; @@ -572,7 +565,12 @@ MessageSender.prototype = { }, uploadAvatar(attachment) { - return this.makeAttachmentPointer(attachment, null, true, true); + // isRaw is true since the data is already encrypted + // and doesn't need to be encrypted again + return this.makeAttachmentPointer(attachment, null, { + isRaw: true, + isAvatar: true, + }); }, sendRequestConfigurationSyncMessage(options) {