Skip to content

Commit

Permalink
fix: Prevent E2EE key reset on startup (#32653)
Browse files Browse the repository at this point in the history
  • Loading branch information
rodrigok authored Jun 22, 2024
1 parent 865a5aa commit 495628b
Show file tree
Hide file tree
Showing 7 changed files with 40 additions and 6 deletions.
6 changes: 6 additions & 0 deletions .changeset/nice-zebras-admire.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@rocket.chat/meteor": patch
"@rocket.chat/rest-typings": patch
---

Prevent E2EE key reset on startup due to possible race conditions
5 changes: 4 additions & 1 deletion apps/meteor/app/api/server/v1/e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ API.v1.addRoute(
* type: string
* private_key:
* type: string
* force:
* type: boolean
* responses:
* 200:
* content:
Expand All @@ -135,11 +137,12 @@ API.v1.addRoute(
{
async post() {
// eslint-disable-next-line @typescript-eslint/naming-convention
const { public_key, private_key } = this.bodyParams;
const { public_key, private_key, force } = this.bodyParams;

await Meteor.callAsync('e2e.setUserPublicAndPrivateKeys', {
public_key,
private_key,
force,
});

return API.v1.success();
Expand Down
14 changes: 11 additions & 3 deletions apps/meteor/app/e2e/client/rocketchat.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,11 @@ class E2E extends Emitter {
delete this.instancesByRoomId[rid];
}

async persistKeys({ public_key, private_key }: KeyPair, password: string): Promise<void> {
async persistKeys(
{ public_key, private_key }: KeyPair,
password: string,
{ force }: { force: boolean } = { force: false },
): Promise<void> {
if (typeof public_key !== 'string' || typeof private_key !== 'string') {
throw new Error('Failed to persist keys as they are not strings.');
}
Expand All @@ -170,6 +174,7 @@ class E2E extends Emitter {
await sdk.rest.post('/v1/e2e.setUserPublicAndPrivateKeys', {
public_key,
private_key: encodedPrivateKey,
force,
});
}

Expand Down Expand Up @@ -300,7 +305,7 @@ class E2E extends Emitter {
}

async changePassword(newPassword: string): Promise<void> {
await this.persistKeys(this.getKeysFromLocalStorage(), newPassword);
await this.persistKeys(this.getKeysFromLocalStorage(), newPassword, { force: true });

if (Meteor._localStorage.getItem('e2e.randomPassword')) {
Meteor._localStorage.setItem('e2e.randomPassword', newPassword);
Expand All @@ -316,7 +321,10 @@ class E2E extends Emitter {
this.db_private_key = private_key;
} catch (error) {
this.setState(E2EEState.ERROR);
return this.error('Error fetching RSA keys: ', error);
this.error('Error fetching RSA keys: ', error);
// Stop any process since we can't communicate with the server
// to get the keys. This prevents new key generation
throw error;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Meteor } from 'meteor/meteor';
declare module '@rocket.chat/ui-contexts' {
// eslint-disable-next-line @typescript-eslint/naming-convention
interface ServerMethods {
'e2e.setUserPublicAndPrivateKeys'({ public_key, private_key }: { public_key: string; private_key: string }): void;
'e2e.setUserPublicAndPrivateKeys'({ public_key, private_key }: { public_key: string; private_key: string; force?: boolean }): void;
}
}

Expand All @@ -19,6 +19,16 @@ Meteor.methods<ServerMethods>({
});
}

if (!keyPair.force) {
const keys = await Users.fetchKeysByUserId(userId);

if (keys.private_key && keys.public_key) {
throw new Meteor.Error('error-keys-already-set', 'Keys already set', {
method: 'e2e.setUserPublicAndPrivateKeys',
});
}
}

await Users.setE2EPublicAndPrivateKeysByUserId(userId, {
private_key: keyPair.private_key,
public_key: keyPair.public_key,
Expand Down
4 changes: 3 additions & 1 deletion apps/meteor/app/file-upload/client/lib/fileUploadHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import { Tracker } from 'meteor/tracker';
Tracker.autorun(() => {
const userId = Meteor.userId();

if (userId) {
// Check for Meteor.loggingIn to be reactive and ensure it will process only after login finishes
// preventing race condition setting the rc_token as null forever
if (userId && Meteor.loggingIn() === false) {
const secure = location.protocol === 'https:' ? '; secure' : '';

document.cookie = `rc_uid=${escape(userId)}; path=/${secure}`;
Expand Down
1 change: 1 addition & 0 deletions packages/rest-typings/src/v1/e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const ajv = new Ajv({
type E2eSetUserPublicAndPrivateKeysProps = {
public_key: string;
private_key: string;
force?: boolean;
};

const E2eSetUserPublicAndPrivateKeysSchema = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const ajv = new Ajv({
export type e2eSetUserPublicAndPrivateKeysParamsPOST = {
public_key: string;
private_key: string;
force?: boolean;
};

const e2eSetUserPublicAndPrivateKeysParamsPOSTSchema = {
Expand All @@ -18,6 +19,9 @@ const e2eSetUserPublicAndPrivateKeysParamsPOSTSchema = {
private_key: {
type: 'string',
},
force: {
type: 'boolean',
},
},
additionalProperties: false,
required: ['public_key', 'private_key'],
Expand Down

0 comments on commit 495628b

Please sign in to comment.