Skip to content

Commit

Permalink
feat: Reset room key (#6030)
Browse files Browse the repository at this point in the history
  • Loading branch information
diegolmello authored Dec 11, 2024
1 parent d6aee8e commit fdc4b7e
Show file tree
Hide file tree
Showing 23 changed files with 473 additions and 91 deletions.
1 change: 1 addition & 0 deletions app/containers/List/ListInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const styles = StyleSheet.create({
},
text: {
fontSize: 14,
lineHeight: 20,
...sharedStyles.textRegular
}
});
Expand Down
4 changes: 4 additions & 0 deletions app/definitions/ISubscription.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ export enum ERoomTypes {

type RelationModified<T extends Model> = { fetch(): Promise<T[]> } & Relation<T>;

type OldKey = { e2eKeyId: string; ts: Date; E2EKey: string };

export interface ISubscription {
_id: string;
id: string;
Expand Down Expand Up @@ -93,6 +95,7 @@ export interface ISubscription {
livechatData?: any;
tags?: string[];
E2EKey?: string;
oldRoomKeys?: OldKey[];
E2ESuggestedKey?: string | null;
encrypted?: boolean;
e2eKeyId?: string;
Expand Down Expand Up @@ -154,6 +157,7 @@ export interface IServerSubscription extends IRocketChatRecord {
onHold?: boolean;
encrypted?: boolean;
E2EKey?: string;
oldRoomKeys?: OldKey[];
E2ESuggestedKey?: string | null;
usersWaitingForE2EKeys?: TUserWaitingForE2EKeys[];
unreadAlert?: 'default' | 'all' | 'mentions' | 'nothing';
Expand Down
3 changes: 3 additions & 0 deletions app/definitions/rest/v1/e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,7 @@ export type E2eEndpoints = {
'e2e.fetchMyKeys': {
GET: () => { public_key: string; private_key: string };
};
'e2e.resetRoomKey': {
POST: (params: { rid: string; e2eKey: string; e2eKeyId: string }) => void;
};
};
13 changes: 11 additions & 2 deletions app/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@
"Dont_activate": "Don't activate now",
"Dont_Have_An_Account": "Don't you have an account?",
"Downloaded_file": "Downloaded file",
"E2E_Encryption": "E2E encryption",
"E2E_Encryption": "End-to-end encryption",
"E2E_encryption_change_password_confirmation": "Yes, change it",
"E2E_encryption_change_password_description": "You can now create encrypted private groups and direct messages. You may also change existing private groups or DMs to encrypted. \nThis is end to end encryption so the key to encode/decode your messages will not be saved on the workspace. 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_encryption_change_password_error": "Error while changing E2E key password!",
Expand Down Expand Up @@ -866,5 +866,14 @@
"Your_invite_link_will_expire_on__date__or_after__usesLeft__uses": "Your invite link will expire on {{date}} or after {{usesLeft}} uses.",
"Your_invite_link_will_never_expire": "Your invite link will never expire.",
"Your_password_is": "Your password is",
"Your_push_was_sent_to_s_devices": "Your push was sent to {{s}} devices"
"Your_push_was_sent_to_s_devices": "Your push was sent to {{s}} devices",
"Reset_room_key_title": "Reset encryption key",
"Reset_room_key_message": "All members may lose access to previously encrypted content.",
"Encryption_keys_reset": "Encryption keys reset",
"Encryption_keys_reset_failed": "Encryption keys reset failed",
"Reset": "Reset",
"Encrypt__room_type__": "Encrypt {{room_type}}",
"Encrypt__room_type__info__room_name__": "Ensure only intended recipients can access messages and files in {{room_name}}.",
"Reset_encryption_keys": "Reset encryption keys",
"Reset_encryption_keys_info__room_type__": "Resetting E2EE keys is only recommend if no {{room_type}} member has a valid key to regain access to the previously encrypted content."
}
3 changes: 3 additions & 0 deletions app/lib/database/model/Subscription.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ export default class Subscription extends Model {

@field('e2e_key') E2EKey;

@json('old_room_keys', sanitizer) oldRoomKeys;

@field('e2e_suggested_key') E2ESuggestedKey;

@field('encrypted') encrypted;
Expand Down Expand Up @@ -203,6 +205,7 @@ export default class Subscription extends Model {
livechatData: this.livechatData,
tags: this.tags,
E2EKey: this.E2EKey,
oldKeys: this.oldKeys,
E2ESuggestedKey: this.E2ESuggestedKey,
encrypted: this.encrypted,
e2eKeyId: this.e2eKeyId,
Expand Down
5 changes: 4 additions & 1 deletion app/lib/database/model/migrations.js
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,10 @@ export default schemaMigrations({
steps: [
addColumns({
table: 'subscriptions',
columns: [{ name: 'users_waiting_for_e2e_keys', type: 'string', isOptional: true }]
columns: [
{ name: 'users_waiting_for_e2e_keys', type: 'string', isOptional: true },
{ name: 'old_room_keys', type: 'string', isOptional: true }
]
})
]
}
Expand Down
1 change: 1 addition & 0 deletions app/lib/database/schema/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export default appSchema({
{ name: 'livechat_data', type: 'string', isOptional: true },
{ name: 'tags', type: 'string', isOptional: true },
{ name: 'e2e_key', type: 'string', isOptional: true },
{ name: 'old_room_keys', type: 'string', isOptional: true },
{ name: 'e2e_suggested_key', type: 'string', isOptional: true },
{ name: 'encrypted', type: 'boolean', isOptional: true },
{ name: 'e2e_key_id', type: 'string', isOptional: true },
Expand Down
7 changes: 6 additions & 1 deletion app/lib/encryption/encryption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const ROOM_KEY_EXCHANGE_SIZE = 10;
class Encryption {
ready: boolean;
privateKey: string | null;
publicKey: string | null;
readyPromise: Deferred;
userId: string | null;
roomInstances: {
Expand All @@ -55,6 +56,7 @@ class Encryption {
encryptFile: TEncryptFile;
encryptUpload: Function;
importRoomKey: Function;
resetRoomKey: Function;
hasSessionKey: () => boolean;
encryptGroupKeyForParticipantsWaitingForTheKeys: (params: any) => Promise<any>;
};
Expand All @@ -67,6 +69,7 @@ class Encryption {
this.userId = '';
this.ready = false;
this.privateKey = null;
this.publicKey = null;
this.roomInstances = {};
this.readyPromise = new Deferred();
this.readyPromise
Expand Down Expand Up @@ -112,6 +115,7 @@ class Encryption {
stop = () => {
this.userId = null;
this.privateKey = null;
this.publicKey = null;
this.roomInstances = {};
// Cancel ongoing encryption/decryption requests
this.readyPromise.reject();
Expand Down Expand Up @@ -155,7 +159,8 @@ class Encryption {
// Persist keys on UserPreferences
persistKeys = async (server: string, publicKey: string, privateKey: string) => {
this.privateKey = await SimpleCrypto.RSA.importKey(EJSON.parse(privateKey));
UserPreferences.setString(`${server}-${E2E_PUBLIC_KEY}`, EJSON.stringify(publicKey));
this.publicKey = EJSON.stringify(publicKey);
UserPreferences.setString(`${server}-${E2E_PUBLIC_KEY}`, this.publicKey);
UserPreferences.setString(`${server}-${E2E_PRIVATE_KEY}`, privateKey);
};

Expand Down
52 changes: 52 additions & 0 deletions app/lib/encryption/helpers/hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { compareServerVersion } from '../../methods/helpers';
import { useAppSelector } from '../../hooks';
import { TSubscriptionModel } from '../../../definitions';

const isMissingRoomE2EEKey = ({
encryptionEnabled,
roomEncrypted,
E2EKey
}: {
encryptionEnabled: boolean;
roomEncrypted: TSubscriptionModel['encrypted'];
E2EKey: TSubscriptionModel['E2EKey'];
}) => (encryptionEnabled && roomEncrypted && !E2EKey) ?? false;

const isE2EEDisabledEncryptedRoom = ({
encryptionEnabled,
roomEncrypted
}: {
encryptionEnabled: boolean;
roomEncrypted: TSubscriptionModel['encrypted'];
}) => (!encryptionEnabled && roomEncrypted) ?? false;

export const useIsMissingRoomE2EEKey = (roomEncrypted: TSubscriptionModel['encrypted'], E2EKey: TSubscriptionModel['E2EKey']) => {
const serverVersion = useAppSelector(state => state.server.version);
const e2eeEnabled = useAppSelector(state => state.settings.E2E_Enable);
const encryptionEnabled = useAppSelector(state => state.encryption.enabled);
if (!e2eeEnabled) {
return false;
}
if (compareServerVersion(serverVersion, 'lowerThan', '6.10.0')) {
return false;
}

return isMissingRoomE2EEKey({ encryptionEnabled, roomEncrypted, E2EKey });
};

export const useHasE2EEWarning = (roomEncrypted: TSubscriptionModel['encrypted'], E2EKey: TSubscriptionModel['E2EKey']) => {
const serverVersion = useAppSelector(state => state.server.version);
const e2eeEnabled = useAppSelector(state => state.settings.E2E_Enable);
const encryptionEnabled = useAppSelector(state => state.encryption.enabled);
if (!e2eeEnabled) {
return false;
}
if (compareServerVersion(serverVersion, 'lowerThan', '6.10.0')) {
return false;
}

return (
isMissingRoomE2EEKey({ encryptionEnabled, roomEncrypted, E2EKey }) ||
isE2EEDisabledEncryptedRoom({ encryptionEnabled, roomEncrypted })
);
};
48 changes: 29 additions & 19 deletions app/lib/encryption/helpers/toggleRoomE2EE.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,22 @@ import database from '../../database';
import { getSubscriptionByRoomId } from '../../database/services/Subscription';
import log from '../../methods/helpers/log';
import I18n from '../../../i18n';
import { TSubscriptionModel } from '../../../definitions';

const optimisticUpdate = async (room: TSubscriptionModel, value: TSubscriptionModel['encrypted']) => {
try {
const db = database.active;

// Instantly feedback to the user
await db.write(async () => {
await room.update(r => {
r.encrypted = value;
});
});
} catch {
// do nothing
}
};

export const toggleRoomE2EE = async (rid: string): Promise<void> => {
const room = await getSubscriptionByRoomId(rid);
Expand All @@ -17,34 +33,32 @@ export const toggleRoomE2EE = async (rid: string): Promise<void> => {
const message = I18n.t(isEncrypted ? 'Disable_encryption_description' : 'Enable_encryption_description');
const confirmationText = I18n.t(isEncrypted ? 'Disable' : 'Enable');

// Toggle encrypted value
const newValue = !room.encrypted;

// Instantly feedback to the user
await optimisticUpdate(room, newValue);

Alert.alert(
title,
message,
[
{
text: I18n.t('Cancel'),
style: 'cancel'
style: 'cancel',
onPress: async () => {
// Revert to original value
await optimisticUpdate(room, !newValue);
}
},
{
text: confirmationText,
style: isEncrypted ? 'destructive' : 'default',
onPress: async () => {
try {
const db = database.active;

// Toggle encrypted value
const encrypted = !room.encrypted;

// Instantly feedback to the user
await db.write(async () => {
await room.update(r => {
r.encrypted = encrypted;
});
});

try {
// Send new room setting value to server
const { result } = await Services.saveRoomSettings(rid, { encrypted });
const { result } = await Services.saveRoomSettings(rid, { encrypted: newValue });
// If it was saved successfully
if (result) {
return;
Expand All @@ -54,11 +68,7 @@ export const toggleRoomE2EE = async (rid: string): Promise<void> => {
}

// If something goes wrong we go back to the previous value
await db.write(async () => {
await room.update(r => {
r.encrypted = room.encrypted;
});
});
await optimisticUpdate(room, !newValue);
} catch (e) {
log(e);
}
Expand Down
Loading

0 comments on commit fdc4b7e

Please sign in to comment.