diff --git a/packages/rocketchat-e2e/client/accountEncryption.html b/packages/rocketchat-e2e/client/accountEncryption.html new file mode 100644 index 000000000000..4f70ef444625 --- /dev/null +++ b/packages/rocketchat-e2e/client/accountEncryption.html @@ -0,0 +1,51 @@ + diff --git a/packages/rocketchat-e2e/client/accountEncryption.js b/packages/rocketchat-e2e/client/accountEncryption.js new file mode 100644 index 000000000000..9b09ebbf6d41 --- /dev/null +++ b/packages/rocketchat-e2e/client/accountEncryption.js @@ -0,0 +1,84 @@ +/* globals Template, t, ReactiveVar */ +import toastr from 'toastr'; +import s from 'underscore.string'; +import { RocketChat } from 'meteor/rocketchat:lib'; +import { e2e } from 'meteor/rocketchat:e2e'; + +Template.accountEncryption.helpers({ + isEnabled() { + return RocketChat.settings.get('E2E_Enable'); + }, + allowKeyChange() { + return localStorage.getItem('public_key') && localStorage.getItem('private_key'); + }, + canConfirmNewKey() { + const encryptionKey = Template.instance().encryptionKey.get(); + return encryptionKey && encryptionKey !== ''; + }, + ifThenElse(condition, val, not = '') { + return condition ? val : not; + }, + canSave(ret) { + const instance = Template.instance(); + + const encryptionKey = instance.encryptionKey.get(); + const confirmationEncryptionKey = instance.confirmationEncryptionKey.get(); + + if ((!encryptionKey || encryptionKey !== confirmationEncryptionKey)) { + return ret; + } + }, +}); + +Template.accountEncryption.events({ + 'input [name=encryptionKey]'(e, instance) { + instance.encryptionKey.set(e.target.value); + + if (e.target.value.length === 0) { + instance.confirmationEncryptionKey.set(''); + } + }, + 'input [name=confirmation-encryptionKey]'(e, instance) { + instance.confirmationEncryptionKey.set(e.target.value); + }, + 'submit form'(e, instance) { + e.preventDefault(); + + return instance.save(); + }, +}); + +Template.accountEncryption.onCreated(function() { + const self = this; + + this.encryptionKey = new ReactiveVar; + this.confirmationEncryptionKey = new ReactiveVar; + + this.save = function(cb) { + const instance = this; + const data = {}; + + if (s.trim(self.encryptionKey.get())) { + data.newEncryptionKey = self.encryptionKey.get(); + } + + if (Object.keys(data).length === 0) { + return cb && cb(); + } + + e2e.changePassword(data.newEncryptionKey); + + instance.clearForm(); + toastr.remove(); + this.encryptionKey.set(''); + this.confirmationEncryptionKey.set(''); + + toastr.success(t('Encryption_key_saved_successfully')); + }; + + this.clearForm = function() { + this.find('[name=encryptionKey]').value = ''; + this.find('[name=confirmation-encryptionKey]').value = ''; + }; + +}); diff --git a/packages/rocketchat-e2e/client/rocketchat.e2e.js b/packages/rocketchat-e2e/client/rocketchat.e2e.js index 61c32ca29c72..34fecd99afb3 100644 --- a/packages/rocketchat-e2e/client/rocketchat.e2e.js +++ b/packages/rocketchat-e2e/client/rocketchat.e2e.js @@ -130,7 +130,7 @@ class E2E { if (!this.db_public_key || !this.db_private_key) { await call('addKeyToChain', { public_key: localStorage.getItem('public_key'), - private_key: await this.encodePrivateKey(localStorage.getItem('private_key')), + private_key: await this.encodePrivateKey(localStorage.getItem('private_key'), this.createRandomPassword()), }); } @@ -202,6 +202,17 @@ class E2E { }); } + async changePassword(newPassword) { + await call('addKeyToChain', { + public_key: localStorage.getItem('public_key'), + private_key: await this.encodePrivateKey(localStorage.getItem('private_key'), newPassword), + }); + + if (localStorage.getItem('e2e.randomPassword')) { + localStorage.setItem('e2e.randomPassword', newPassword); + } + } + async loadKeysFromDB() { try { const { public_key, private_key } = await call('fetchMyKeys'); @@ -251,11 +262,14 @@ class E2E { } } - async encodePrivateKey(private_key) { + createRandomPassword() { const randomPassword = `${ Random.id(3) }-${ Random.id(3) }-${ Random.id(3) }`.toLowerCase(); localStorage.setItem('e2e.randomPassword', randomPassword); + return randomPassword; + } - const masterKey = await this.getMasterKey(randomPassword); + async encodePrivateKey(private_key, password) { + const masterKey = await this.getMasterKey(password); const vector = crypto.getRandomValues(new Uint8Array(16)); try { diff --git a/packages/rocketchat-e2e/package.js b/packages/rocketchat-e2e/package.js index 84ff43a160fe..a0b7658f1e79 100644 --- a/packages/rocketchat-e2e/package.js +++ b/packages/rocketchat-e2e/package.js @@ -8,11 +8,18 @@ Package.describe({ }); Package.onUse(function(api) { - api.use('ecmascript'); - api.use('less'); - api.use('mizzao:timesync'); + api.use([ + 'ecmascript', + 'less', + 'mizzao:timesync', + 'rocketchat:lib', + 'templating', + 'sha', + ]); api.mainModule('client/rocketchat.e2e.js', 'client'); + api.addFiles('client/accountEncryption.html', 'client'); + api.addFiles('client/accountEncryption.js', 'client'); api.mainModule('server/index.js', 'server'); }); diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index 2280d10cb87a..dbe490e9dc93 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -217,6 +217,7 @@ "Additional_Feedback": "Additional Feedback", "additional_integrations_Bots": "If you are looking for how to integrate your own bot, then look no further than our Hubot adapter. https://github.com/RocketChat/hubot-rocketchat", "additional_integrations_Zapier": "Are you looking to integrate other software and applications with Rocket.Chat but you don't have the time to manually do it? Then we suggest using Zapier which we fully support. Read more about it on our documentation. https://rocket.chat/docs/administrator-guides/integrations/zapier/using-zaps/", + "Admin_disabled_encryption": "Your administrator did not enable E2E encryption.", "Admin_Info": "Admin Info", "Administration": "Administration", "Adult_images_are_not_allowed": "Adult images are not allowed", @@ -572,6 +573,7 @@ "Compact": "Compact", "Condensed": "Condensed", "Computer": "Computer", + "Confirm_new_encryption_key": "Confirm new encryption key", "Confirm_password": "Confirm your password", "Connection_Closed": "Connection closed", "Connection_Reset": "Connection reset", @@ -978,6 +980,7 @@ "Duplicate_private_group_name": "A Private Group with name '%s' exists", "Duration": "Duration", "E2E_Enable_description": "This feature is still on BETA state.
Encrypted messages will not be found by search operations.
Notifications may also not work.", + "E2E_Encryption_Password_Explanation": "You can now create encrypted private groups and direct messages. You may also change existing private groups or DMs to encrypted.

This is end to end encryption so the key to encode/decode your messages will not be saved on the server. For that reason you need to store your password somewhere safe. You will be required to enter it on other devices you wish to use e2e encryption on.", "E2E_password_reveal_text": "You can now create encrypted private groups and direct messages. You may also change existing private groups or DMs to encrypted.
This is end to end encryption so the key to encode/decode your messages will not be saved on the server. For that reason you need to store this password somewhere safe. You will be required to enter it on other devices you wish to use e2e encryption on.

Your password is: %s

This is an auto generated password, you can setup a new password for your encryption key any time from any browser you have entered the existing password.
This password is only stored on this browser until you store the password and dismiss this message.", "Edit": "Edit", "edit-message": "Edit Message", @@ -1030,6 +1033,8 @@ "Encrypted": "Encrypted", "Encrypted_channel_Description": "End to end encrypted channel. Search will not work with encrypted channels and notifications may not show the messages content.", "Encrypted_message": "Encrypted message", + "EncryptionKey_Change_Disabled": "You can't set a password for your encryption key because your private key is not present on this client. In order to set a new password you need load your private key using your existing password or use a client where the key is already loaded.", + "Encryption_key_saved_successfully": "Your encryption key was saved successfully.", "End_OTR": "End OTR", "Enter_a_name": "Enter a name", "Enter_a_regex": "Enter a regex", @@ -1886,6 +1891,7 @@ "New_Password_Placeholder": "Please enter new password...", "Confirm_new_password": "Confirm New Password", "Confirm_New_Password_Placeholder": "Please re-enter new password...", + "New_encryption_key": "New encryption key", "New_role": "New role", "New_Room_Notification": "New Room Notification", "New_Trigger": "New Trigger", diff --git a/packages/rocketchat-ui-account/client/accountFlex.html b/packages/rocketchat-ui-account/client/accountFlex.html index 68a5d6209fa0..3033f12a8804 100644 --- a/packages/rocketchat-ui-account/client/accountFlex.html +++ b/packages/rocketchat-ui-account/client/accountFlex.html @@ -17,6 +17,10 @@

{{_ "My_Account"}}

{{/if}} {{> sidebarItem menuItem "Security" "lock" "account" "security" }} + + {{#if encryptionEnabled}} + {{> sidebarItem menuItem "Encryption" "key" "account" "encryption" }} + {{/if}} {{#if accessTokensEnabled}} {{> sidebarItem menuItem "Personal_Access_Tokens" "key" "account" "tokens" }} diff --git a/packages/rocketchat-ui-account/client/accountFlex.js b/packages/rocketchat-ui-account/client/accountFlex.js index d7a9059e6cb2..40fca5532992 100644 --- a/packages/rocketchat-ui-account/client/accountFlex.js +++ b/packages/rocketchat-ui-account/client/accountFlex.js @@ -15,6 +15,9 @@ Template.accountFlex.helpers({ accessTokensEnabled() { return RocketChat.settings.get('API_Enable_Personal_Access_Tokens'); }, + encryptionEnabled() { + return RocketChat.settings.get('E2E_Enable'); + }, menuItem(name, icon, section, group) { return { name: t(name),