Skip to content

Commit

Permalink
New: Option to change E2E key (#12169)
Browse files Browse the repository at this point in the history
* Removed e2e password from alert message

* Added strings to i18n

* Adjusted i18n strings

* Added a screen to allow users to change their encryption keys

* Removed unused import

* Declared global variables

* Added e2e explanation on the password change screen

* Hide Encryption area off the account page when encryption is disabled

* Removed unused code
  • Loading branch information
Hudell authored and rodrigok committed Sep 26, 2018
1 parent 59de13b commit 062af91
Show file tree
Hide file tree
Showing 7 changed files with 175 additions and 6 deletions.
51 changes: 51 additions & 0 deletions packages/rocketchat-e2e/client/accountEncryption.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<template name="accountEncryption">
<section class="preferences-page preferences-page--new">
{{#header sectionName="Encryption" buttons=true fullpage=true}}
<div class="rc-header__section-button">
<button class="rc-button rc-button--primary" name="send" type="submit" data-button="create" form="encryption" {{canSave 'disabled'}}>{{_ "Save_changes"}}</button>
</div>
{{/header}}

<div class="preferences-page__content">
<form id="encryption" autocomplete="off" class="container">
{{# if isEnabled}}
<fieldset>
<div class="section">
<div class="section-content border-component-color">

<div class="alert">
{{{_ "E2E_Encryption_Password_Explanation" }}}
</div>

<div class="rc-input{{#if confirmationKeyInvalid}} rc-input--error{{/if}} rc-w50 padded">
{{#with canChange=allowKeyChange}}
<label class="rc-input__label">
<div class="rc-input__title">{{_ "New_encryption_key"}}</div>
<div class="rc-input__wrapper">
<input name="encryptionKey" type="password" class="rc-input__element" placeholder="{{_ "New_Password_Placeholder"}}" autocomplete="new-password" {{ifThenElse canChange '' 'disabled'}}>
</div>
</label>
{{#if canConfirmNewKey}}
<label class="rc-input__label">
<div class="rc-input__title">{{_ "Confirm_new_encryption_key"}}</div>
<div class="rc-input__wrapper">
<input name="confirmation-encryptionKey" type="password" class="rc-input__element" placeholder="{{_ "Confirm_New_Password_Placeholder"}}" autocomplete="confirm-new-password">
</div>
</label>
{{/if}}
{{# unless canChange}}
<div class="rc-input__description">{{_ 'EncryptionKey_Change_Disabled'}}</div>
{{/unless}}
{{/with}}
</div>
</div>
</div>
</fieldset>

{{else}}
{{_ "Admin_disabled_encryption"}}
{{/if}}
</form>
</div>
</section>
</template>
84 changes: 84 additions & 0 deletions packages/rocketchat-e2e/client/accountEncryption.js
Original file line number Diff line number Diff line change
@@ -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 = '';
};

});
20 changes: 17 additions & 3 deletions packages/rocketchat-e2e/client/rocketchat.e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()),
});
}

Expand Down Expand Up @@ -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');
Expand Down Expand Up @@ -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 {
Expand Down
13 changes: 10 additions & 3 deletions packages/rocketchat-e2e/package.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});
6 changes: 6 additions & 0 deletions packages/rocketchat-i18n/i18n/en.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -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. <a href='https://github.com/RocketChat/hubot-rocketchat' target='_blank'>https://github.com/RocketChat/hubot-rocketchat</a>",
"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. <a href='https://rocket.chat/docs/administrator-guides/integrations/zapier/using-zaps/' target='_blank'>https://rocket.chat/docs/administrator-guides/integrations/zapier/using-zaps/</a>",
"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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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.<br/>Encrypted messages will not be found by search operations.<br/> 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.<br/><br/>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.<br/>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.<br/><br/>Your password is: <span style=\"font-weight: bold;\">%s</span><br/><br/>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.<br/>This password is only stored on this browser until you store the password and dismiss this message.",
"Edit": "Edit",
"edit-message": "Edit Message",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
4 changes: 4 additions & 0 deletions packages/rocketchat-ui-account/client/accountFlex.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ <h1 class="sidebar-flex__title">{{_ "My_Account"}}</h1>
{{/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" }}
Expand Down
3 changes: 3 additions & 0 deletions packages/rocketchat-ui-account/client/accountFlex.js
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down

0 comments on commit 062af91

Please sign in to comment.