Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[NEW] Support for end to end encryption #10094

Merged
merged 98 commits into from
Sep 21, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
98 commits
Select commit Hold shift + click to select a range
832a813
Adding initial files
mrinaldhar Jun 7, 2017
8492023
Generating registration keys on device
mrinaldhar Jun 7, 2017
20fd64b
Adding ability to detect if device previously registered, code cleanup
mrinaldhar Jun 7, 2017
7c3df67
Changes made to confirm to ESLINT
mrinaldhar Jun 7, 2017
370a7db
Changes made to conform to ESLINT
mrinaldhar Jun 7, 2017
840273f
Merge branch 'develop' of https://github.com/mrinaldhar/Rocket.Chat i…
mrinaldhar Jun 7, 2017
d44b4e6
Updated flextab labels. Needs internationalization
mrinaldhar Jun 9, 2017
e93b67e
Defined datastructure for public keys in database, added method for u…
mrinaldhar Jun 9, 2017
1f66b1b
Enables clients to download each others keys from server
mrinaldhar Jun 9, 2017
569dbf7
Added code for signal session establishment
mrinaldhar Jun 9, 2017
b761361
Removing unnecessary code
mrinaldhar Jun 9, 2017
59524da
Ability to encrypt and decrypt messages, fixed session bug
mrinaldhar Jun 22, 2017
6e8c793
Online requirement removed from flextab
mrinaldhar Jul 12, 2017
7e8b2e4
Fixing eslint errors
mrinaldhar Jul 12, 2017
7a6ea25
adding libsignal to eslint ignore, fixing other eslint errors
mrinaldhar Jul 12, 2017
2d1067f
adding libsignal to eslint ignore, fixing other eslint errors
mrinaldhar Jul 12, 2017
b16c83e
Merge branch 'develop' of https://github.com/mrinaldhar/Rocket.Chat i…
mrinaldhar Jul 12, 2017
19323ea
Fixing issue with group chats
mrinaldhar Jul 18, 2017
83c819b
Encryption and decryption working on same client group messaging
mrinaldhar Jul 20, 2017
e49b1bd
fixed encoding of ciphertext
mrinaldhar Jul 20, 2017
a8fb726
Fetch group members public keys for pairwise group key encryption
mrinaldhar Jul 20, 2017
5141222
RSA asymmetric keys added to user model
mrinaldhar Jul 21, 2017
35f18b1
Make RSA key extractable
mrinaldhar Jul 21, 2017
5e01e83
Pairwise encrypt session key for everyone in group
mrinaldhar Jul 21, 2017
e5441d0
Successful group chat encryption
mrinaldhar Jul 21, 2017
8015987
Fixing ESLINT errors
mrinaldhar Jul 21, 2017
7374806
Fixing ESLINT errors
mrinaldhar Jul 21, 2017
2777c92
Merge branch 'develop' of https://github.com/mrinaldhar/Rocket.Chat i…
mrinaldhar Jul 21, 2017
3e1c6a1
Fetch existing group encryption key, do not generate new
mrinaldhar Jul 23, 2017
2cb3e8c
Add UI feature: Clear group key across all clients
mrinaldhar Jul 23, 2017
d923861
Merge branch 'develop' of https://github.com/RocketChat/Rocket.Chat i…
mrinaldhar Jul 23, 2017
5457cd2
Run init after checking user id
mrinaldhar Aug 10, 2017
2f3fde5
Fixed issues with group and direct message encryption
mrinaldhar Aug 13, 2017
b7d2dc8
Fixed issues with group and direct message encryption
mrinaldhar Aug 13, 2017
2d73a2d
Merge branch 'develop' of https://github.com/mrinaldhar/Rocket.Chat i…
mrinaldhar Aug 13, 2017
2565fb4
Decrypt encrypted messages from chat history
mrinaldhar Aug 13, 2017
142185a
Adding functionality for encrypted file uploads and downloads.
mrinaldhar Aug 26, 2017
dbb0c4f
Fixed some issues with file upload
mrinaldhar Aug 27, 2017
e5cd5eb
Minor UI fixes
mrinaldhar Aug 27, 2017
cfe2771
Added documentation where necessary
mrinaldhar Aug 27, 2017
23831af
Fixed ESLINT errors.
mrinaldhar Aug 27, 2017
612bb19
Merge branch 'develop' into develop
mrinaldhar Aug 28, 2017
913dbd6
Fixed ESLINT errors.
mrinaldhar Aug 27, 2017
5440c28
Merge branch 'develop' of https://github.com/mrinaldhar/Rocket.Chat i…
mrinaldhar Aug 28, 2017
f95ee73
Merge branch 'develop' of https://github.com/mrinaldhar/Rocket.Chat i…
mrinaldhar Aug 28, 2017
636363b
Merge branch 'develop' of https://github.com/mrinaldhar/Rocket.Chat i…
mrinaldhar Aug 28, 2017
55afde6
Merge branch 'develop' into develop
mrinaldhar Nov 24, 2017
6598573
Update packages
mrinaldhar Nov 24, 2017
629f50e
Merge branch 'develop' into develop
mrinaldhar Dec 5, 2017
7aafd32
Removing all signal related code and files. libsignal.js exempted fro…
mrinaldhar Dec 15, 2017
20c0eb9
Adding password based key generation
mrinaldhar Dec 16, 2017
4c5bab9
Implementation of password based public key protection complete
mrinaldhar Dec 16, 2017
2237f1d
Fixing ESLint errors
mrinaldhar Dec 16, 2017
89c0e49
Merge pull request #7181 from mrinaldhar/develop
mrinaldhar Dec 16, 2017
349e4bd
Adding API methods for E2E
mrinaldhar Jan 19, 2018
3113d10
Fixing typo
mrinaldhar Jan 19, 2018
c8d6b09
Fix E2E DDP method in API file
mrinaldhar Feb 13, 2018
0cf7f55
Merge branch 'develop' into end-to-end-encryption
mrinaldhar Mar 10, 2018
723e682
Fixing ESLINT errors
mrinaldhar Mar 12, 2018
66f5cff
Fixing ESLINT errors
mrinaldhar Mar 12, 2018
d1b77c9
Merge branch 'end-to-end-encryption' of https://github.com/RocketChat…
mrinaldhar Mar 13, 2018
8c15e94
Merge branch 'develop' into end-to-end-encryption
mrinaldhar Apr 10, 2018
2364111
Merge branch 'develop' into end-to-end-encryption
marceloschmidt Sep 13, 2018
b448a43
RSA-EPrivKey may also be undefined at this point
marceloschmidt Sep 13, 2018
85e99b6
replace swal for modal.open
marceloschmidt Sep 13, 2018
36c10ba
Fix the use of self
marceloschmidt Sep 13, 2018
24727d3
Don't use EJSON.parse on the plain text string (but have to check if …
marceloschmidt Sep 13, 2018
0395888
Fix lint errors
marceloschmidt Sep 13, 2018
97ea6e0
Simplifying code by using es6
marceloschmidt Sep 14, 2018
9897280
Fix codacy issues
marceloschmidt Sep 14, 2018
a74d153
Merge branch 'develop' into end-to-end-encryption
marceloschmidt Sep 14, 2018
afbe903
Merge remote-tracking branch 'origin/develop' into end-to-end-encryption
rodrigok Sep 14, 2018
48aaa1b
Fix lint
rodrigok Sep 14, 2018
58383ac
Remove uncessary changes
rodrigok Sep 14, 2018
d977a7a
Initial code improvements
rodrigok Sep 18, 2018
43b9e34
Remove libsignal and e2e tab
rodrigok Sep 19, 2018
e8ff866
Move files to the import style
rodrigok Sep 19, 2018
9c4d8db
Fix less import
rodrigok Sep 19, 2018
f38dd61
Change E2E setting’s group name
rodrigok Sep 19, 2018
c5fac10
Fix import of TimeSync
rodrigok Sep 19, 2018
6586481
DRY crypto methods
rodrigok Sep 19, 2018
680c756
Reduce code duplication
rodrigok Sep 19, 2018
a306843
Reduce code duplication
rodrigok Sep 19, 2018
6d70651
Unused code removal
rodrigok Sep 19, 2018
93bd69d
Fix wrong usage of splitVectorAndEcryptedData
rodrigok Sep 19, 2018
c2c7fa2
Move some methods to a helper file
rodrigok Sep 19, 2018
0949cf4
Remove unused code
rodrigok Sep 19, 2018
c5bafe5
Alert user about the encryption password
rodrigok Sep 20, 2018
3839c37
Disable E2E by default
rodrigok Sep 20, 2018
43be0b5
Handle wrong password for decryption of private key
rodrigok Sep 20, 2018
7f4d88a
Do not encrypt files
rodrigok Sep 20, 2018
5b904d6
Remove uncessary changes
rodrigok Sep 20, 2018
32fa1ea
Fix notification for encrypted messages
rodrigok Sep 20, 2018
88e200d
Add switch to create room already encrypted
rodrigok Sep 20, 2018
0a9627b
Prevent some errors
rodrigok Sep 20, 2018
a67ebba
Some adjustments
rodrigok Sep 20, 2018
189099e
Merge remote-tracking branch 'origin/develop' into end-to-end-encryption
rodrigok Sep 20, 2018
b14aa73
Fix typo
sampaiodiego Sep 21, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .meteor/packages
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ steffo:meteor-accounts-saml
todda00:friendly-slugs
yasaricli:slugify
yasinuslu:blaze-meta
rocketchat:e2e

rocketchat:blockstack
rocketchat:version-check
Expand Down
1 change: 1 addition & 0 deletions .meteor/versions
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ rocketchat:custom-oauth@1.0.0
rocketchat:custom-sounds@1.0.0
rocketchat:dolphin@0.0.2
rocketchat:drupal@0.0.1
rocketchat:e2e@0.0.1
rocketchat:emoji@1.0.0
rocketchat:emoji-custom@1.0.0
rocketchat:emoji-emojione@0.0.1
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@
"bugsnag": "^2.4.0",
"bunyan": "^1.8.12",
"busboy": "^0.2.14",
"bytebuffer": "5.0.1",
"cas": "https://github.com/kcbanner/node-cas/tarball/fcd27dad333223b3b75a048bce27973fb3ca0f62",
"chart.js": "^2.7.2",
"clipboard": "^2.0.1",
Expand Down
46 changes: 46 additions & 0 deletions packages/rocketchat-api/server/v1/e2e.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
RocketChat.API.v1.addRoute('e2e.fetchKeychain', { authRequired: true }, {
get() {
const { uid } = this.queryParams;

let result;
Meteor.runAsUser(this.userId, () => result = Meteor.call('fetchKeychain', uid));

return RocketChat.API.v1.success(result);
},
});

RocketChat.API.v1.addRoute('e2e.fetchMyKeys', { authRequired: true }, {
get() {
let result;
Meteor.runAsUser(this.userId, () => result = Meteor.call('fetchMyKeys'));

return RocketChat.API.v1.success(result);
},
});

RocketChat.API.v1.addRoute('e2e.addKeyToChain', { authRequired: true }, {
post() {
const { RSAPubKey, RSAEPrivKey } = this.bodyParams;

Meteor.runAsUser(this.userId, () => {
RocketChat.API.v1.success(Meteor.call('addKeyToChain', {
public_key: RSAPubKey,
private_key: RSAEPrivKey,
}));
});

return RocketChat.API.v1.success();
},
});

RocketChat.API.v1.addRoute('e2e.updateGroupE2EKey', { authRequired: true }, {
post() {
const { uid, rid, key } = this.bodyParams;

Meteor.runAsUser(this.userId, () => {
RocketChat.API.v1.success(Meteor.call('updateGroupE2EKey', rid, uid, key));
});

return RocketChat.API.v1.success();
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,24 @@
{{/if}}
{{/with}}

{{#with settings.encrypted}}
{{#if canView}}
<div class="rc-user-info__row">
<div class="rc-switch rc-switch--blue">
<label class="rc-switch__label">
<span class="rc-switch__text">
{{_ label}}{{equal default value '*'}}
</span>
<input type="checkbox" class="rc-switch__input js-input-check" name="encrypted" checked="{{checked}}" disabled="{{./disabled}}">
<span class="rc-switch__button">
<span class="rc-switch__button-inside"></span>
</span>
</label>
</div>
</div>
{{/if}}
{{/with}}

{{#with settings.broadcast}}
{{#if canView}}
<div class="rc-user-info__row">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,25 @@ Template.channelSettingsEditing.onCreated(function() {
);
},
},
encrypted: {
type: 'boolean',
label: 'Encrypted',
isToggle: true,
processing: new ReactiveVar(false),
canView() {
return RocketChat.roomTypes.roomTypes[room.t].allowRoomSettingChange(room, RoomSettingsEnum.E2E);
},
canEdit() {
return RocketChat.authz.hasAllPermission('edit-room', room._id);
},
save(value) {
return call('saveRoomSettings', room._id, 'encrypted', value).then(() => {
toastr.success(
t('Encrypted_setting_changed_successfully')
);
});
},
},
};
Object.keys(this.settings).forEach((key) => {
const setting = this.settings[key];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const fields = ['roomName', 'roomTopic', 'roomAnnouncement', 'roomCustomFields', 'roomDescription', 'roomType', 'readOnly', 'reactWhenReadOnly', 'systemMessages', 'default', 'joinCode', 'tokenpass', 'streamingOptions', 'retentionEnabled', 'retentionMaxAge', 'retentionExcludePinned', 'retentionFilesOnly', 'retentionOverrideGlobal'];
const fields = ['roomName', 'roomTopic', 'roomAnnouncement', 'roomCustomFields', 'roomDescription', 'roomType', 'readOnly', 'reactWhenReadOnly', 'systemMessages', 'default', 'joinCode', 'tokenpass', 'streamingOptions', 'retentionEnabled', 'retentionMaxAge', 'retentionExcludePinned', 'retentionFilesOnly', 'retentionOverrideGlobal', 'encrypted'];
Meteor.methods({
saveRoomSettings(rid, settings, value) {
const userId = Meteor.userId();
Expand Down Expand Up @@ -72,6 +72,12 @@ Meteor.methods({
action: 'Change_Room_Type',
});
}
if (setting === 'encrypted' && value !== room.encrypted && (room.t !== 'd' && room.t !== 'p')) {
throw new Meteor.Error('error-action-not-allowed', 'Only groups or direct channels can enable encryption', {
method: 'saveRoomSettings',
action: 'Change_Room_Encrypted',
});
}

if (setting === 'retentionEnabled' && !RocketChat.authz.hasPermission(userId, 'edit-room-retention-policy', rid) && value !== room.retention.enabled) {
throw new Meteor.Error('error-action-not-allowed', 'Editing room retention policy is not allowed', {
Expand Down Expand Up @@ -184,6 +190,9 @@ Meteor.methods({
case 'retentionOverrideGlobal':
RocketChat.models.Rooms.saveRetentionOverrideGlobalById(rid, value);
break;
case 'encrypted':
RocketChat.models.Rooms.saveEncryptedById(rid, value);
break;
}
});

Expand Down
4 changes: 4 additions & 0 deletions packages/rocketchat-e2e/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"extends": ["@rocket.chat/eslint-config"],
"root": true
}
117 changes: 117 additions & 0 deletions packages/rocketchat-e2e/client/helper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/* eslint-disable new-cap, no-proto */

import ByteBuffer from 'bytebuffer';

const StaticArrayBufferProto = new ArrayBuffer().__proto__;

export function toString(thing) {
if (typeof thing === 'string') {
return thing;
}
return new ByteBuffer.wrap(thing).toString('binary');
}

export function toArrayBuffer(thing) {
if (thing === undefined) {
return undefined;
}
if (thing === Object(thing)) {
if (thing.__proto__ === StaticArrayBufferProto) {
return thing;
}
}

if (typeof thing !== 'string') {
throw new Error(`Tried to convert a non-string of type ${ typeof thing } to an array buffer`);
}
return new ByteBuffer.wrap(thing, 'binary').toArrayBuffer();
}

export function joinVectorAndEcryptedData(vector, encryptedData) {
const cipherText = new Uint8Array(encryptedData);
const output = new Uint8Array(vector.length + cipherText.length);
output.set(vector, 0);
output.set(cipherText, vector.length);
return output;
}

export function splitVectorAndEcryptedData(cipherText) {
const vector = cipherText.slice(0, 16);
const encryptedData = cipherText.slice(16);

return [vector, encryptedData];
}

export async function encryptRSA(key, data) {
return await crypto.subtle.encrypt({ name: 'RSA-OAEP' }, key, data);
}

export async function encryptAES(vector, key, data) {
return await crypto.subtle.encrypt({ name: 'AES-CBC', iv: vector }, key, data);
}

export async function decryptRSA(key, data) {
return await crypto.subtle.decrypt({ name: 'RSA-OAEP' }, key, data);
}

export async function decryptAES(vector, key, data) {
return await crypto.subtle.decrypt({ name: 'AES-CBC', iv: vector }, key, data);
}

export async function generateAESKey() {
return await crypto.subtle.generateKey({ name: 'AES-CBC', length: 128 }, true, ['encrypt', 'decrypt']);
}

export async function generateRSAKey() {
return await crypto.subtle.generateKey({ name: 'RSA-OAEP', modulusLength: 2048, publicExponent: new Uint8Array([0x01, 0x00, 0x01]), hash: { name: 'SHA-256' } }, true, ['encrypt', 'decrypt']);
}

export async function exportJWKKey(key) {
return await crypto.subtle.exportKey('jwk', key);
}

export async function importRSAKey(keyData, keyUsages = ['encrypt', 'decrypt']) {
return await crypto.subtle.importKey('jwk', keyData, { name: 'RSA-OAEP', modulusLength: 2048, publicExponent: new Uint8Array([0x01, 0x00, 0x01]), hash: { name: 'SHA-256' } }, true, keyUsages);
}

export async function importAESKey(keyData, keyUsages = ['encrypt', 'decrypt']) {
return await crypto.subtle.importKey('jwk', keyData, { name: 'AES-CBC' }, true, keyUsages);
}

export async function importRawKey(keyData, keyUsages = ['deriveKey']) {
return await crypto.subtle.importKey('raw', keyData, { name: 'PBKDF2' }, false, keyUsages);
}

export async function deriveKey(salt, baseKey, keyUsages = ['encrypt', 'decrypt']) {
const iterations = 1000;
const hash = 'SHA-256';

return await crypto.subtle.deriveKey({ name: 'PBKDF2', salt, iterations, hash }, baseKey, { name: 'AES-CBC', length: 256 }, true, keyUsages);
}

export async function readFileAsArrayBuffer(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = function(evt) {
resolve(evt.target.result);
};
reader.onerror = function(evt) {
reject(evt);
};
reader.readAsArrayBuffer(file);
});
}

export class Deferred {
constructor() {
const p = new Promise((resolve, reject) => {
this.resolve = resolve;
this.reject = reject;
});

p.resolve = this.resolve;
p.reject = this.reject;

return p;
}
}
Loading