Skip to content

Commit

Permalink
feat: Encrypt file descriptions on E2EE rooms (#5599)
Browse files Browse the repository at this point in the history
  • Loading branch information
diegolmello authored Apr 19, 2024
1 parent 197f136 commit 94845cb
Show file tree
Hide file tree
Showing 9 changed files with 97 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -95,16 +95,21 @@ export const RecordAudio = (): ReactElement | null => {
try {
if (!rid) return;
setRecordingAudio(false);
const fileURI = recordingRef.current?.getURI();
const fileData = await getInfoAsync(fileURI as string);
const fileInfo = {
const fileURI = recordingRef.current?.getURI() as string;
const fileData = await getInfoAsync(fileURI);

if (!fileData.exists) {
return;
}

const fileInfo: IUpload = {
rid,
name: `${Date.now()}${RECORDING_EXTENSION}`,
mime: 'audio/aac',
type: 'audio/aac',
store: 'Uploads',
path: fileURI,
size: fileData.exists ? fileData.size : null
} as IUpload;
size: fileData.size
};

if (fileInfo) {
if (permissionToUpload) {
Expand Down
4 changes: 2 additions & 2 deletions app/containers/message/Attachments.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,14 @@ const AttachedActions = ({ attachment, getCustomEmoji }: { attachment: IAttachme

const Attachments: React.FC<IMessageAttachments> = React.memo(
({ attachments, timeFormat, showAttachment, style, getCustomEmoji, isReply, author }: IMessageAttachments) => {
const { translateLanguage } = useContext(MessageContext);
const { translateLanguage, isEncrypted } = useContext(MessageContext);

if (!attachments || attachments.length === 0) {
return null;
}

const attachmentsElements = attachments.map((file: IAttachment, index: number) => {
const msg = getMessageFromAttachment(file, translateLanguage);
const msg = isEncrypted ? '' : getMessageFromAttachment(file, translateLanguage);
if (file && file.image_url) {
return (
<Image
Expand Down
3 changes: 2 additions & 1 deletion app/containers/message/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -428,7 +428,8 @@ class MessageContainer extends React.Component<IMessageContainerProps, IMessageC
threadBadgeColor,
toggleFollowThread,
replies,
translateLanguage: canTranslateMessage ? autoTranslateLanguage : undefined
translateLanguage: canTranslateMessage ? autoTranslateLanguage : undefined,
isEncrypted: this.isEncrypted
}}
>
{/* @ts-ignore*/}
Expand Down
6 changes: 5 additions & 1 deletion app/definitions/IUpload.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import Model from '@nozbe/watermelondb/Model';

import { E2EType, MessageType } from './IMessage';

export interface IUpload {
id?: string;
rid?: string;
rid: string;
path: string;
name?: string;
tmid?: string;
Expand All @@ -14,6 +16,8 @@ export interface IUpload {
error?: boolean;
subscription?: { id: string };
msg?: string;
t?: MessageType;
e2e?: E2EType;
}

export type TUploadModel = IUpload & Model;
24 changes: 19 additions & 5 deletions app/lib/encryption/encryption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,15 @@ import log from '../methods/helpers/log';
import { store } from '../store/auxStore';
import { joinVectorData, randomPassword, splitVectorData, toString, utf8ToBuffer } from './utils';
import { EncryptionRoom } from './index';
import { IMessage, ISubscription, TMessageModel, TSubscriptionModel, TThreadMessageModel, TThreadModel } from '../../definitions';
import {
IMessage,
ISubscription,
IUpload,
TMessageModel,
TSubscriptionModel,
TThreadMessageModel,
TThreadModel
} from '../../definitions';
import {
E2E_BANNER_TYPE,
E2E_MESSAGE_TYPE,
Expand All @@ -34,6 +42,7 @@ class Encryption {
handshake: Function;
decrypt: Function;
encrypt: Function;
encryptUpload: Function;
importRoomKey: Function;
};
};
Expand Down Expand Up @@ -275,7 +284,7 @@ class Encryption {
];
toDecrypt = (await Promise.all(
toDecrypt.map(async message => {
const { t, msg, tmsg } = message;
const { t, msg, tmsg, attachments } = message;
let newMessage: TMessageModel = {} as TMessageModel;
if (message.subscription) {
const { id: rid } = message.subscription;
Expand All @@ -284,7 +293,8 @@ class Encryption {
t,
rid,
msg: msg as string,
tmsg
tmsg,
attachments
});
}

Expand Down Expand Up @@ -434,7 +444,7 @@ class Encryption {
};

// Encrypt a message
encryptMessage = async (message: IMessage) => {
encryptMessage = async (message: IMessage | IUpload) => {
const { rid } = message;
const db = database.active;
const subCollection = db.get('subscriptions');
Expand All @@ -456,6 +466,10 @@ class Encryption {
}

const roomE2E = await this.getRoomInstance(rid);

if ('path' in message) {
return roomE2E.encryptUpload(message);
}
return roomE2E.encrypt(message);
} catch {
// Subscription not found
Expand All @@ -467,7 +481,7 @@ class Encryption {
};

// Decrypt a message
decryptMessage = async (message: Pick<IMessage, 't' | 'e2e' | 'rid' | 'msg' | 'tmsg'>) => {
decryptMessage = async (message: Pick<IMessage, 't' | 'e2e' | 'rid' | 'msg' | 'tmsg' | 'attachments'>) => {
const { t, e2e } = message;

// Prevent create a new instance if this room was encrypted sometime ago
Expand Down
36 changes: 35 additions & 1 deletion app/lib/encryption/room.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import ByteBuffer from 'bytebuffer';
import parse from 'url-parse';

import getSingleMessage from '../methods/getSingleMessage';
import { IMessage, IUser } from '../../definitions';
import { IMessage, IUpload, IUser } from '../../definitions';
import Deferred from './helpers/deferred';
import { debounce } from '../methods/helpers';
import database from '../database';
Expand Down Expand Up @@ -243,8 +243,38 @@ export default class EncryptionRoom {
return message;
};

// Encrypt upload
encryptUpload = async (message: IUpload) => {
if (!this.ready) {
return message;
}

try {
let description = '';

if (message.description) {
description = await this.encryptText(EJSON.stringify({ text: message.description }));
}

return {
...message,
t: E2E_MESSAGE_TYPE,
e2e: E2E_STATUS.PENDING,
description
};
} catch {
// Do nothing
}

return message;
};

// Decrypt text
decryptText = async (msg: string | ArrayBuffer) => {
if (!msg) {
return null;
}

msg = b64ToBuffer(msg.slice(12) as string);
const [vector, cipherText] = splitVectorData(msg);

Expand Down Expand Up @@ -275,6 +305,10 @@ export default class EncryptionRoom {
tmsg = await this.decryptText(tmsg);
}

if (message.attachments?.length) {
message.attachments[0].description = await this.decryptText(message.attachments[0].description as string);
}

const decryptedMessage: IMessage = {
...message,
tmsg,
Expand Down
17 changes: 16 additions & 1 deletion app/lib/methods/sendFileMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import isEmpty from 'lodash/isEmpty';
import { FetchBlobResponse, StatefulPromise } from 'rn-fetch-blob';
import { Alert } from 'react-native';

import { Encryption } from '../encryption';
import { IUpload, IUser, TUploadModel } from '../../definitions';
import i18n from '../../i18n';
import database from '../database';
import FileUpload from './helpers/fileUpload';
import { IFileUpload } from './helpers/fileUpload/interfaces';
import log from './helpers/log';
import { E2E_MESSAGE_TYPE } from '../constants';

const uploadQueue: { [index: string]: StatefulPromise<FetchBlobResponse> } = {};

Expand Down Expand Up @@ -85,6 +87,8 @@ export function sendFileMessage(
}
}

const encryptedFileInfo = await Encryption.encryptMessage(fileInfo);

const formData: IFileUpload[] = [];
formData.push({
name: 'file',
Expand All @@ -96,7 +100,7 @@ export function sendFileMessage(
if (fileInfo.description) {
formData.push({
name: 'description',
data: fileInfo.description
data: encryptedFileInfo.description
});
}

Expand All @@ -114,6 +118,17 @@ export function sendFileMessage(
});
}

if (encryptedFileInfo.t === E2E_MESSAGE_TYPE) {
formData.push({
name: 't',
data: encryptedFileInfo.t
});
formData.push({
name: 'e2e',
data: encryptedFileInfo.e2e
});
}

const headers = {
...RocketChatSettings.customHeaders,
'Content-Type': 'multipart/form-data',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ describe('SwitchItemEncrypted', () => {
const component = screen.queryByTestId(testEncrypted.testSwitchID);
expect(component).toBeTruthy();
});

it('should change value of switch', () => {
render(
<SwitchItemEncrypted
Expand All @@ -62,7 +62,7 @@ describe('SwitchItemEncrypted', () => {
expect(onPressMock).toHaveReturnedWith({ value: !testEncrypted.encrypted });
}
});

it('label when encrypted and isTeam are false and is a public channel', () => {
render(
<SwitchItemEncrypted
Expand All @@ -76,7 +76,7 @@ describe('SwitchItemEncrypted', () => {
const component = screen.queryByTestId(testEncrypted.testLabelID);
expect(component?.props.children).toBe(i18n.t('Channel_hint_encrypted_not_available'));
});

it('label when encrypted and isTeam are true and is a private team', () => {
testEncrypted.isTeam = true;
testEncrypted.type = true;
Expand Down
5 changes: 4 additions & 1 deletion app/views/ShareView/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,9 @@ class ShareView extends Component<IShareViewProps, IShareViewState> {

// if is share extension show default back button
if (!this.isShareExtension) {
options.headerLeft = () => <HeaderButton.CloseModal navigation={navigation} color={themes[theme].surfaceTint} testID='share-view-close' />;
options.headerLeft = () => (
<HeaderButton.CloseModal navigation={navigation} color={themes[theme].surfaceTint} testID='share-view-close' />
);
}

if (!attachments.length && !readOnly) {
Expand Down Expand Up @@ -255,6 +257,7 @@ class ShareView extends Component<IShareViewProps, IShareViewState> {
return sendFileMessage(
room.rid,
{
rid: room.rid,
name,
description,
size,
Expand Down

0 comments on commit 94845cb

Please sign in to comment.