feat: Encrypt file descriptions on E2EE rooms (#5599)
This commit is contained in:
parent
197f13654f
commit
94845cbfd2
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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*/}
|
||||
|
|
|
@ -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;
|
||||
|
@ -14,6 +16,8 @@ export interface IUpload {
|
|||
error?: boolean;
|
||||
subscription?: { id: string };
|
||||
msg?: string;
|
||||
t?: MessageType;
|
||||
e2e?: E2EType;
|
||||
}
|
||||
|
||||
export type TUploadModel = IUpload & Model;
|
||||
|
|
|
@ -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,
|
||||
|
@ -34,6 +42,7 @@ class Encryption {
|
|||
handshake: Function;
|
||||
decrypt: Function;
|
||||
encrypt: Function;
|
||||
encryptUpload: Function;
|
||||
importRoomKey: Function;
|
||||
};
|
||||
};
|
||||
|
@ -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;
|
||||
|
@ -284,7 +293,8 @@ class Encryption {
|
|||
t,
|
||||
rid,
|
||||
msg: msg as string,
|
||||
tmsg
|
||||
tmsg,
|
||||
attachments
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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');
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
|
|
@ -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';
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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,
|
||||
|
|
|
@ -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> } = {};
|
||||
|
||||
|
@ -85,6 +87,8 @@ export function sendFileMessage(
|
|||
}
|
||||
}
|
||||
|
||||
const encryptedFileInfo = await Encryption.encryptMessage(fileInfo);
|
||||
|
||||
const formData: IFileUpload[] = [];
|
||||
formData.push({
|
||||
name: 'file',
|
||||
|
@ -96,7 +100,7 @@ export function sendFileMessage(
|
|||
if (fileInfo.description) {
|
||||
formData.push({
|
||||
name: 'description',
|
||||
data: fileInfo.description
|
||||
data: encryptedFileInfo.description
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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',
|
||||
|
|
|
@ -45,7 +45,7 @@ describe('SwitchItemEncrypted', () => {
|
|||
const component = screen.queryByTestId(testEncrypted.testSwitchID);
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
|
||||
it('should change value of switch', () => {
|
||||
render(
|
||||
<SwitchItemEncrypted
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
@ -255,6 +257,7 @@ class ShareView extends Component<IShareViewProps, IShareViewState> {
|
|||
return sendFileMessage(
|
||||
room.rid,
|
||||
{
|
||||
rid: room.rid,
|
||||
name,
|
||||
description,
|
||||
size,
|
||||
|
|
Loading…
Reference in New Issue