Chore: Migrate lib/encryption folder to TypeScript (#3639)
* Initial commit * add types/bytebuffer, add type definitions to params and update interfaces * add more types and type assertions * update types * change bang operator by type assertion and update class variables definitions * add types for deferred class * minor tweaks on types definitions * add ts-ignore * Update encryption.ts * update deferred and encryption * update encryption.ts * Update room.ts * update toDecrypt type * initialize sessionKeyExportedString * remove return types
This commit is contained in:
parent
97f8271127
commit
352a718631
|
@ -3,7 +3,8 @@ import { MarkdownAST } from '@rocket.chat/message-parser';
|
|||
|
||||
import { IAttachment } from './IAttachment';
|
||||
import { IReaction } from './IReaction';
|
||||
import { SubscriptionType } from './ISubscription';
|
||||
|
||||
export type MessageType = 'jitsi_call_started' | 'discussion-created' | 'e2e' | 'load_more' | 'rm' | 'uj';
|
||||
|
||||
export interface IUserMessage {
|
||||
_id: string;
|
||||
|
@ -34,12 +35,16 @@ export interface ITranslations {
|
|||
value: string;
|
||||
}
|
||||
|
||||
export type E2EType = 'pending' | 'done';
|
||||
|
||||
export interface ILastMessage {
|
||||
_id: string;
|
||||
rid: string;
|
||||
tshow: boolean;
|
||||
t: MessageType;
|
||||
tmid: string;
|
||||
msg: string;
|
||||
e2e: E2EType;
|
||||
ts: Date;
|
||||
u: IUserMessage;
|
||||
_updatedAt: Date;
|
||||
|
@ -55,8 +60,9 @@ export interface ILastMessage {
|
|||
|
||||
export interface IMessage {
|
||||
_id: string;
|
||||
rid: string;
|
||||
msg?: string;
|
||||
t?: SubscriptionType;
|
||||
t?: MessageType;
|
||||
ts: Date;
|
||||
u: IUserMessage;
|
||||
alias: string;
|
||||
|
|
|
@ -12,6 +12,7 @@ export enum SubscriptionType {
|
|||
DIRECT = 'd',
|
||||
CHANNEL = 'c',
|
||||
OMNICHANNEL = 'l',
|
||||
E2E = 'e2e',
|
||||
THREAD = 'thread' // FIXME: this is not a type of subscription
|
||||
}
|
||||
|
||||
|
|
|
@ -2,9 +2,8 @@ import Model from '@nozbe/watermelondb/Model';
|
|||
import { MarkdownAST } from '@rocket.chat/message-parser';
|
||||
|
||||
import { IAttachment } from './IAttachment';
|
||||
import { IEditedBy, IUserChannel, IUserMention, IUserMessage } from './IMessage';
|
||||
import { IEditedBy, IUserChannel, IUserMention, IUserMessage, MessageType } from './IMessage';
|
||||
import { IReaction } from './IReaction';
|
||||
import { SubscriptionType } from './ISubscription';
|
||||
import { IUrl } from './IUrl';
|
||||
|
||||
interface IFileThread {
|
||||
|
@ -35,8 +34,9 @@ export interface IThreadResult {
|
|||
|
||||
export interface IThread {
|
||||
id: string;
|
||||
tmsg?: string;
|
||||
msg?: string;
|
||||
t?: SubscriptionType;
|
||||
t?: MessageType;
|
||||
rid: string;
|
||||
_updatedAt?: Date;
|
||||
ts?: Date;
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import Model from '@nozbe/watermelondb/Model';
|
||||
|
||||
import { IAttachment } from './IAttachment';
|
||||
import { IEditedBy, ITranslations, IUserChannel, IUserMention, IUserMessage } from './IMessage';
|
||||
import { IEditedBy, ITranslations, IUserChannel, IUserMention, IUserMessage, MessageType } from './IMessage';
|
||||
import { IReaction } from './IReaction';
|
||||
import { SubscriptionType } from './ISubscription';
|
||||
|
||||
export interface IThreadMessage {
|
||||
tmsg?: string;
|
||||
msg?: string;
|
||||
t?: SubscriptionType;
|
||||
t?: MessageType;
|
||||
rid: string;
|
||||
ts: Date;
|
||||
u: IUserMessage;
|
||||
|
|
|
@ -99,8 +99,8 @@ export interface IUser extends IRocketChatRecord {
|
|||
roles: string[];
|
||||
type: string;
|
||||
active: boolean;
|
||||
username?: string;
|
||||
name?: string;
|
||||
username: string;
|
||||
services?: IUserServices;
|
||||
emails?: IUserEmail[];
|
||||
status?: UserStatus;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import EJSON from 'ejson';
|
||||
import SimpleCrypto from 'react-native-simple-crypto';
|
||||
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||
import { Q } from '@nozbe/watermelondb';
|
||||
import { Q, Model } from '@nozbe/watermelondb';
|
||||
|
||||
import RocketChat from '../rocketchat';
|
||||
import UserPreferences from '../userPreferences';
|
||||
|
@ -20,9 +20,25 @@ import {
|
|||
} from './constants';
|
||||
import { joinVectorData, randomPassword, splitVectorData, toString, utf8ToBuffer } from './utils';
|
||||
import { EncryptionRoom } from './index';
|
||||
import { IMessage, ISubscription, TMessageModel, TSubscriptionModel, TThreadMessageModel, TThreadModel } from '../../definitions';
|
||||
|
||||
class Encryption {
|
||||
ready: boolean;
|
||||
privateKey: string | null;
|
||||
readyPromise: Deferred;
|
||||
userId: string | null;
|
||||
roomInstances: {
|
||||
[rid: string]: {
|
||||
ready: boolean;
|
||||
provideKeyToUser: Function;
|
||||
handshake: Function;
|
||||
decrypt: Function;
|
||||
encrypt: Function;
|
||||
};
|
||||
};
|
||||
|
||||
constructor() {
|
||||
this.userId = '';
|
||||
this.ready = false;
|
||||
this.privateKey = null;
|
||||
this.roomInstances = {};
|
||||
|
@ -37,7 +53,7 @@ class Encryption {
|
|||
}
|
||||
|
||||
// Initialize Encryption client
|
||||
initialize = userId => {
|
||||
initialize = (userId: string) => {
|
||||
this.userId = userId;
|
||||
this.roomInstances = {};
|
||||
|
||||
|
@ -82,7 +98,7 @@ class Encryption {
|
|||
};
|
||||
|
||||
// When a new participant join and request a new room encryption key
|
||||
provideRoomKeyToUser = async (keyId, rid) => {
|
||||
provideRoomKeyToUser = async (keyId: string, rid: string) => {
|
||||
// If the client is not ready
|
||||
if (!this.ready) {
|
||||
try {
|
||||
|
@ -100,14 +116,14 @@ class Encryption {
|
|||
};
|
||||
|
||||
// Persist keys on UserPreferences
|
||||
persistKeys = async (server, publicKey, privateKey) => {
|
||||
persistKeys = async (server: string, publicKey: string, privateKey: string) => {
|
||||
this.privateKey = await SimpleCrypto.RSA.importKey(EJSON.parse(privateKey));
|
||||
await UserPreferences.setStringAsync(`${server}-${E2E_PUBLIC_KEY}`, EJSON.stringify(publicKey));
|
||||
await UserPreferences.setStringAsync(`${server}-${E2E_PRIVATE_KEY}`, privateKey);
|
||||
};
|
||||
|
||||
// Could not obtain public-private keypair from server.
|
||||
createKeys = async (userId, server) => {
|
||||
createKeys = async (userId: string, server: string) => {
|
||||
// Generate new keys
|
||||
const key = await SimpleCrypto.RSA.generateKeys(2048);
|
||||
|
||||
|
@ -132,7 +148,7 @@ class Encryption {
|
|||
};
|
||||
|
||||
// Encode a private key before send it to the server
|
||||
encodePrivateKey = async (privateKey, password, userId) => {
|
||||
encodePrivateKey = async (privateKey: string, password: string, userId: string) => {
|
||||
const masterKey = await this.generateMasterKey(password, userId);
|
||||
|
||||
const vector = await SimpleCrypto.utils.randomBytes(16);
|
||||
|
@ -142,7 +158,7 @@ class Encryption {
|
|||
};
|
||||
|
||||
// Decode a private key fetched from server
|
||||
decodePrivateKey = async (privateKey, password, userId) => {
|
||||
decodePrivateKey = async (privateKey: string, password: string, userId: string) => {
|
||||
const masterKey = await this.generateMasterKey(password, userId);
|
||||
const [vector, cipherText] = splitVectorData(EJSON.parse(privateKey));
|
||||
|
||||
|
@ -152,7 +168,7 @@ class Encryption {
|
|||
};
|
||||
|
||||
// Generate a user master key, this is based on userId and a password
|
||||
generateMasterKey = async (password, userId) => {
|
||||
generateMasterKey = async (password: string, userId: string) => {
|
||||
const iterations = 1000;
|
||||
const hash = 'SHA256';
|
||||
const keyLen = 32;
|
||||
|
@ -166,18 +182,18 @@ class Encryption {
|
|||
};
|
||||
|
||||
// Create a random password to local created keys
|
||||
createRandomPassword = async server => {
|
||||
createRandomPassword = async (server: string) => {
|
||||
const password = randomPassword();
|
||||
await UserPreferences.setStringAsync(`${server}-${E2E_RANDOM_PASSWORD_KEY}`, password);
|
||||
return password;
|
||||
};
|
||||
|
||||
changePassword = async (server, password) => {
|
||||
changePassword = async (server: string, password: string) => {
|
||||
// Cast key to the format server is expecting
|
||||
const privateKey = await SimpleCrypto.RSA.exportKey(this.privateKey);
|
||||
const privateKey = await SimpleCrypto.RSA.exportKey(this.privateKey as string);
|
||||
|
||||
// Encode the private key
|
||||
const encodedPrivateKey = await this.encodePrivateKey(EJSON.stringify(privateKey), password, this.userId);
|
||||
const encodedPrivateKey = await this.encodePrivateKey(EJSON.stringify(privateKey), password, this.userId as string);
|
||||
const publicKey = await UserPreferences.getStringAsync(`${server}-${E2E_PUBLIC_KEY}`);
|
||||
|
||||
// Send the new keys to the server
|
||||
|
@ -185,7 +201,7 @@ class Encryption {
|
|||
};
|
||||
|
||||
// get a encryption room instance
|
||||
getRoomInstance = async rid => {
|
||||
getRoomInstance = async (rid: string) => {
|
||||
// Prevent handshake again
|
||||
if (this.roomInstances[rid]?.ready) {
|
||||
return this.roomInstances[rid];
|
||||
|
@ -193,7 +209,7 @@ class Encryption {
|
|||
|
||||
// If doesn't have a instance of this room
|
||||
if (!this.roomInstances[rid]) {
|
||||
this.roomInstances[rid] = new EncryptionRoom(rid, this.userId);
|
||||
this.roomInstances[rid] = new EncryptionRoom(rid, this.userId as string);
|
||||
}
|
||||
|
||||
const roomE2E = this.roomInstances[rid];
|
||||
|
@ -206,7 +222,7 @@ class Encryption {
|
|||
|
||||
// Logic to decrypt all pending messages/threads/threadMessages
|
||||
// after initialize the encryption client
|
||||
decryptPendingMessages = async roomId => {
|
||||
decryptPendingMessages = async (roomId?: string) => {
|
||||
const db = database.active;
|
||||
|
||||
const messagesCollection = db.get('messages');
|
||||
|
@ -228,8 +244,12 @@ class Encryption {
|
|||
const threadMessagesToDecrypt = await threadMessagesCollection.query(...whereClause).fetch();
|
||||
|
||||
// Concat messages/threads/threadMessages
|
||||
let toDecrypt = [...messagesToDecrypt, ...threadsToDecrypt, ...threadMessagesToDecrypt];
|
||||
toDecrypt = await Promise.all(
|
||||
let toDecrypt: (TThreadModel | TThreadMessageModel)[] = [
|
||||
...messagesToDecrypt,
|
||||
...threadsToDecrypt,
|
||||
...threadMessagesToDecrypt
|
||||
];
|
||||
toDecrypt = (await Promise.all(
|
||||
toDecrypt.map(async message => {
|
||||
const { t, msg, tmsg } = message;
|
||||
const { id: rid } = message.subscription;
|
||||
|
@ -240,9 +260,10 @@ class Encryption {
|
|||
msg,
|
||||
tmsg
|
||||
});
|
||||
|
||||
try {
|
||||
return message.prepareUpdate(
|
||||
protectedFunction(m => {
|
||||
protectedFunction((m: TMessageModel) => {
|
||||
Object.assign(m, newMessage);
|
||||
})
|
||||
);
|
||||
|
@ -250,9 +271,9 @@ class Encryption {
|
|||
return null;
|
||||
}
|
||||
})
|
||||
);
|
||||
)) as (TThreadModel | TThreadMessageModel)[];
|
||||
|
||||
await db.action(async () => {
|
||||
await db.write(async () => {
|
||||
await db.batch(...toDecrypt);
|
||||
});
|
||||
} catch (e) {
|
||||
|
@ -271,19 +292,19 @@ class Encryption {
|
|||
const subsEncrypted = await subCollection.query(Q.where('e2e_key_id', Q.notEq(null))).fetch();
|
||||
// We can't do this on database level since lastMessage is not a database object
|
||||
const subsToDecrypt = subsEncrypted.filter(
|
||||
sub =>
|
||||
(sub: ISubscription) =>
|
||||
// Encrypted message
|
||||
sub?.lastMessage?.t === E2E_MESSAGE_TYPE &&
|
||||
// Message pending decrypt
|
||||
sub?.lastMessage?.e2e === E2E_STATUS.PENDING
|
||||
);
|
||||
await Promise.all(
|
||||
subsToDecrypt.map(async sub => {
|
||||
subsToDecrypt.map(async (sub: TSubscriptionModel) => {
|
||||
const { rid, lastMessage } = sub;
|
||||
const newSub = await this.decryptSubscription({ rid, lastMessage });
|
||||
try {
|
||||
return sub.prepareUpdate(
|
||||
protectedFunction(m => {
|
||||
protectedFunction((m: TSubscriptionModel) => {
|
||||
Object.assign(m, newSub);
|
||||
})
|
||||
);
|
||||
|
@ -293,7 +314,7 @@ class Encryption {
|
|||
})
|
||||
);
|
||||
|
||||
await db.action(async () => {
|
||||
await db.write(async () => {
|
||||
await db.batch(...subsToDecrypt);
|
||||
});
|
||||
} catch (e) {
|
||||
|
@ -302,7 +323,7 @@ class Encryption {
|
|||
};
|
||||
|
||||
// Decrypt a subscription lastMessage
|
||||
decryptSubscription = async subscription => {
|
||||
decryptSubscription = async (subscription: Partial<ISubscription>) => {
|
||||
// If the subscription doesn't have a lastMessage just return
|
||||
if (!subscription?.lastMessage) {
|
||||
return subscription;
|
||||
|
@ -334,18 +355,18 @@ class Encryption {
|
|||
|
||||
let subRecord;
|
||||
try {
|
||||
subRecord = await subCollection.find(rid);
|
||||
subRecord = await subCollection.find(rid as string);
|
||||
} catch {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
try {
|
||||
const batch = [];
|
||||
const batch: (Model | null | void | false | Promise<void>)[] = [];
|
||||
// If the subscription doesn't exists yet
|
||||
if (!subRecord) {
|
||||
// Let's create the subscription with the data received
|
||||
batch.push(
|
||||
subCollection.prepareCreate(s => {
|
||||
subCollection.prepareCreate((s: TSubscriptionModel) => {
|
||||
s._raw = sanitizedRaw({ id: rid }, subCollection.schema);
|
||||
Object.assign(s, subscription);
|
||||
})
|
||||
|
@ -355,7 +376,7 @@ class Encryption {
|
|||
try {
|
||||
// Let's update the subscription with the received E2EKey
|
||||
batch.push(
|
||||
subRecord.prepareUpdate(s => {
|
||||
subRecord.prepareUpdate((s: TSubscriptionModel) => {
|
||||
s.E2EKey = subscription.E2EKey;
|
||||
})
|
||||
);
|
||||
|
@ -366,7 +387,7 @@ class Encryption {
|
|||
|
||||
// If batch has some operation
|
||||
if (batch.length) {
|
||||
await db.action(async () => {
|
||||
await db.write(async () => {
|
||||
await db.batch(...batch);
|
||||
});
|
||||
}
|
||||
|
@ -377,7 +398,7 @@ class Encryption {
|
|||
}
|
||||
|
||||
// Get a instance using the subscription
|
||||
const roomE2E = await this.getRoomInstance(rid);
|
||||
const roomE2E = await this.getRoomInstance(rid as string);
|
||||
const decryptedMessage = await roomE2E.decrypt(lastMessage);
|
||||
return {
|
||||
...subscription,
|
||||
|
@ -386,7 +407,7 @@ class Encryption {
|
|||
};
|
||||
|
||||
// Encrypt a message
|
||||
encryptMessage = async message => {
|
||||
encryptMessage = async (message: IMessage) => {
|
||||
const { rid } = message;
|
||||
const db = database.active;
|
||||
const subCollection = db.get('subscriptions');
|
||||
|
@ -419,7 +440,7 @@ class Encryption {
|
|||
};
|
||||
|
||||
// Decrypt a message
|
||||
decryptMessage = async message => {
|
||||
decryptMessage = async (message: Partial<IMessage>) => {
|
||||
const { t, e2e } = message;
|
||||
|
||||
// Prevent create a new instance if this room was encrypted sometime ago
|
||||
|
@ -440,15 +461,15 @@ class Encryption {
|
|||
}
|
||||
|
||||
const { rid } = message;
|
||||
const roomE2E = await this.getRoomInstance(rid);
|
||||
const roomE2E = await this.getRoomInstance(rid as string);
|
||||
return roomE2E.decrypt(message);
|
||||
};
|
||||
|
||||
// Decrypt multiple messages
|
||||
decryptMessages = messages => Promise.all(messages.map(m => this.decryptMessage(m)));
|
||||
decryptMessages = (messages: IMessage[]) => Promise.all(messages.map((m: IMessage) => this.decryptMessage(m)));
|
||||
|
||||
// Decrypt multiple subscriptions
|
||||
decryptSubscriptions = subscriptions => Promise.all(subscriptions.map(s => this.decryptSubscription(s)));
|
||||
decryptSubscriptions = (subscriptions: ISubscription[]) => Promise.all(subscriptions.map(s => this.decryptSubscription(s)));
|
||||
}
|
||||
|
||||
const encryption = new Encryption();
|
|
@ -1,7 +1,9 @@
|
|||
import EJSON from 'ejson';
|
||||
import { Base64 } from 'js-base64';
|
||||
import SimpleCrypto from 'react-native-simple-crypto';
|
||||
import ByteBuffer from 'bytebuffer';
|
||||
|
||||
import { IMessage } from '../../definitions';
|
||||
import RocketChat from '../rocketchat';
|
||||
import Deferred from '../../utils/deferred';
|
||||
import debounce from '../../utils/debounce';
|
||||
|
@ -19,13 +21,26 @@ import {
|
|||
utf8ToBuffer
|
||||
} from './utils';
|
||||
import { Encryption } from './index';
|
||||
import { IUser } from '../../definitions/IUser';
|
||||
|
||||
export default class EncryptionRoom {
|
||||
constructor(roomId, userId) {
|
||||
ready: boolean;
|
||||
roomId: string;
|
||||
userId: string;
|
||||
establishing: boolean;
|
||||
readyPromise: Deferred;
|
||||
sessionKeyExportedString: string | ByteBuffer;
|
||||
keyID: string;
|
||||
roomKey: ArrayBuffer;
|
||||
|
||||
constructor(roomId: string, userId: string) {
|
||||
this.ready = false;
|
||||
this.roomId = roomId;
|
||||
this.userId = userId;
|
||||
this.establishing = false;
|
||||
this.keyID = '';
|
||||
this.sessionKeyExportedString = '';
|
||||
this.roomKey = new ArrayBuffer(0);
|
||||
this.readyPromise = new Deferred();
|
||||
this.readyPromise.then(() => {
|
||||
// Mark as ready
|
||||
|
@ -57,7 +72,7 @@ export default class EncryptionRoom {
|
|||
const { E2EKey, e2eKeyId } = subscription;
|
||||
|
||||
// If this room has a E2EKey, we import it
|
||||
if (E2EKey) {
|
||||
if (E2EKey && Encryption.privateKey) {
|
||||
// We're establishing a new room encryption client
|
||||
this.establishing = true;
|
||||
await this.importRoomKey(E2EKey, Encryption.privateKey);
|
||||
|
@ -82,25 +97,25 @@ export default class EncryptionRoom {
|
|||
};
|
||||
|
||||
// Import roomKey as an AES Decrypt key
|
||||
importRoomKey = async (E2EKey, privateKey) => {
|
||||
importRoomKey = async (E2EKey: string, privateKey: string) => {
|
||||
const roomE2EKey = E2EKey.slice(12);
|
||||
|
||||
const decryptedKey = await SimpleCrypto.RSA.decrypt(roomE2EKey, privateKey);
|
||||
this.sessionKeyExportedString = toString(decryptedKey);
|
||||
|
||||
this.keyID = Base64.encode(this.sessionKeyExportedString).slice(0, 12);
|
||||
this.keyID = Base64.encode(this.sessionKeyExportedString as string).slice(0, 12);
|
||||
|
||||
// Extract K from Web Crypto Secret Key
|
||||
// K is a base64URL encoded array of bytes
|
||||
// Web Crypto API uses this as a private key to decrypt/encrypt things
|
||||
// Reference: https://www.javadoc.io/doc/com.nimbusds/nimbus-jose-jwt/5.1/com/nimbusds/jose/jwk/OctetSequenceKey.html
|
||||
const { k } = EJSON.parse(this.sessionKeyExportedString);
|
||||
const { k } = EJSON.parse(this.sessionKeyExportedString as string);
|
||||
this.roomKey = b64ToBuffer(k);
|
||||
};
|
||||
|
||||
// Create a key to a room
|
||||
createRoomKey = async () => {
|
||||
const key = await SimpleCrypto.utils.randomBytes(16);
|
||||
const key = (await SimpleCrypto.utils.randomBytes(16)) as Uint8Array;
|
||||
this.roomKey = key;
|
||||
|
||||
// Web Crypto format of a Secret Key
|
||||
|
@ -131,7 +146,7 @@ export default class EncryptionRoom {
|
|||
// Each time you see a encrypted message of a room that you don't have a key
|
||||
// this will be called again and run once in 5 seconds
|
||||
requestRoomKey = debounce(
|
||||
async e2eKeyId => {
|
||||
async (e2eKeyId: string) => {
|
||||
await RocketChat.e2eRequestRoomKey(this.roomId, e2eKeyId);
|
||||
},
|
||||
5000,
|
||||
|
@ -143,22 +158,22 @@ export default class EncryptionRoom {
|
|||
const result = await RocketChat.e2eGetUsersOfRoomWithoutKey(this.roomId);
|
||||
if (result.success) {
|
||||
const { users } = result;
|
||||
await Promise.all(users.map(user => this.encryptRoomKeyForUser(user)));
|
||||
await Promise.all(users.map((user: IUser) => this.encryptRoomKeyForUser(user)));
|
||||
}
|
||||
};
|
||||
|
||||
// Encrypt the room key to each user in
|
||||
encryptRoomKeyForUser = async user => {
|
||||
encryptRoomKeyForUser = async (user: IUser) => {
|
||||
if (user?.e2e?.public_key) {
|
||||
const { public_key: publicKey } = user.e2e;
|
||||
const userKey = await SimpleCrypto.RSA.importKey(EJSON.parse(publicKey));
|
||||
const encryptedUserKey = await SimpleCrypto.RSA.encrypt(this.sessionKeyExportedString, userKey);
|
||||
const encryptedUserKey = await SimpleCrypto.RSA.encrypt(this.sessionKeyExportedString as string, userKey);
|
||||
await RocketChat.e2eUpdateGroupKey(user?._id, this.roomId, this.keyID + encryptedUserKey);
|
||||
}
|
||||
};
|
||||
|
||||
// Provide this room key to a user
|
||||
provideKeyToUser = async keyId => {
|
||||
provideKeyToUser = async (keyId: string) => {
|
||||
// Don't provide a key if the keyId received
|
||||
// is different than the current one
|
||||
if (this.keyID !== keyId) {
|
||||
|
@ -169,16 +184,16 @@ export default class EncryptionRoom {
|
|||
};
|
||||
|
||||
// Encrypt text
|
||||
encryptText = async text => {
|
||||
text = utf8ToBuffer(text);
|
||||
encryptText = async (text: string | ArrayBuffer) => {
|
||||
text = utf8ToBuffer(text as string);
|
||||
const vector = await SimpleCrypto.utils.randomBytes(16);
|
||||
const data = await SimpleCrypto.AES.encrypt(text, this.roomKey, vector);
|
||||
const data = await SimpleCrypto.AES.encrypt(text, this.roomKey as ArrayBuffer, vector);
|
||||
|
||||
return this.keyID + bufferToB64(joinVectorData(vector, data));
|
||||
};
|
||||
|
||||
// Encrypt messages
|
||||
encrypt = async message => {
|
||||
encrypt = async (message: IMessage) => {
|
||||
if (!this.ready) {
|
||||
return message;
|
||||
}
|
||||
|
@ -207,8 +222,8 @@ export default class EncryptionRoom {
|
|||
};
|
||||
|
||||
// Decrypt text
|
||||
decryptText = async msg => {
|
||||
msg = b64ToBuffer(msg.slice(12));
|
||||
decryptText = async (msg: string | ArrayBuffer) => {
|
||||
msg = b64ToBuffer(msg.slice(12) as string);
|
||||
const [vector, cipherText] = splitVectorData(msg);
|
||||
|
||||
const decrypted = await SimpleCrypto.AES.decrypt(cipherText, this.roomKey, vector);
|
||||
|
@ -219,7 +234,7 @@ export default class EncryptionRoom {
|
|||
};
|
||||
|
||||
// Decrypt messages
|
||||
decrypt = async message => {
|
||||
decrypt = async (message: IMessage) => {
|
||||
if (!this.ready) {
|
||||
return message;
|
||||
}
|
||||
|
@ -231,7 +246,7 @@ export default class EncryptionRoom {
|
|||
if (t === E2E_MESSAGE_TYPE && e2e !== E2E_STATUS.DONE) {
|
||||
let { msg, tmsg } = message;
|
||||
// Decrypt msg
|
||||
msg = await this.decryptText(msg);
|
||||
msg = await this.decryptText(msg as string);
|
||||
|
||||
// Decrypt tmsg
|
||||
if (tmsg) {
|
|
@ -1,4 +1,3 @@
|
|||
/* eslint-disable no-bitwise */
|
||||
import ByteBuffer from 'bytebuffer';
|
||||
import SimpleCrypto from 'react-native-simple-crypto';
|
||||
|
||||
|
@ -7,12 +6,13 @@ import { fromByteArray, toByteArray } from '../../utils/base64-js';
|
|||
|
||||
const BASE64URI = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
|
||||
|
||||
export const b64ToBuffer = base64 => toByteArray(base64).buffer;
|
||||
// @ts-ignore
|
||||
export const b64ToBuffer = (base64: string): ArrayBuffer => toByteArray(base64).buffer;
|
||||
export const utf8ToBuffer = SimpleCrypto.utils.convertUtf8ToArrayBuffer;
|
||||
export const bufferToB64 = arrayBuffer => fromByteArray(new Uint8Array(arrayBuffer));
|
||||
export const bufferToB64 = (arrayBuffer: ArrayBuffer): string => fromByteArray(new Uint8Array(arrayBuffer));
|
||||
// ArrayBuffer -> Base64 URI Safe
|
||||
// https://github.com/herrjemand/Base64URL-ArrayBuffer/blob/master/lib/base64url-arraybuffer.js
|
||||
export const bufferToB64URI = buffer => {
|
||||
export const bufferToB64URI = (buffer: ArrayBuffer): string => {
|
||||
const uintArray = new Uint8Array(buffer);
|
||||
const len = uintArray.length;
|
||||
let base64 = '';
|
||||
|
@ -33,28 +33,28 @@ export const bufferToB64URI = buffer => {
|
|||
return base64;
|
||||
};
|
||||
// SimpleCrypto.utils.convertArrayBufferToUtf8 is not working with unicode emoji
|
||||
export const bufferToUtf8 = buffer => {
|
||||
const uintArray = new Uint8Array(buffer);
|
||||
export const bufferToUtf8 = (buffer: ArrayBuffer): string => {
|
||||
const uintArray = new Uint8Array(buffer) as number[] & Uint8Array;
|
||||
const encodedString = String.fromCharCode.apply(null, uintArray);
|
||||
const decodedString = decodeURIComponent(escape(encodedString));
|
||||
return decodedString;
|
||||
return decodeURIComponent(escape(encodedString));
|
||||
};
|
||||
export const splitVectorData = text => {
|
||||
export const splitVectorData = (text: ArrayBuffer): ArrayBuffer[] => {
|
||||
const vector = text.slice(0, 16);
|
||||
const data = text.slice(16);
|
||||
return [vector, data];
|
||||
};
|
||||
export const joinVectorData = (vector, data) => {
|
||||
|
||||
export const joinVectorData = (vector: ArrayBuffer, data: ArrayBuffer): ArrayBufferLike => {
|
||||
const output = new Uint8Array(vector.byteLength + data.byteLength);
|
||||
output.set(new Uint8Array(vector), 0);
|
||||
output.set(new Uint8Array(data), vector.byteLength);
|
||||
return output.buffer;
|
||||
};
|
||||
export const toString = thing => {
|
||||
export const toString = (thing: string | ByteBuffer | Buffer | ArrayBuffer | Uint8Array): string | ByteBuffer => {
|
||||
if (typeof thing === 'string') {
|
||||
return thing;
|
||||
}
|
||||
// eslint-disable-next-line new-cap
|
||||
// @ts-ignore
|
||||
return new ByteBuffer.wrap(thing).toString('binary');
|
||||
};
|
||||
export const randomPassword = () => `${random(3)}-${random(3)}-${random(3)}`.toLowerCase();
|
||||
export const randomPassword = (): string => `${random(3)}-${random(3)}-${random(3)}`.toLowerCase();
|
|
@ -37,7 +37,7 @@ const getLens = (b64: string) => {
|
|||
};
|
||||
|
||||
// base64 is 4/3 + up to two characters of the original data
|
||||
export const byteLength = (b64: string) => {
|
||||
export const byteLength = (b64: string): number => {
|
||||
const lens = getLens(b64);
|
||||
const validLen = lens[0];
|
||||
const placeHoldersLen = lens[1];
|
||||
|
@ -47,7 +47,7 @@ export const byteLength = (b64: string) => {
|
|||
const _byteLength = (b64: string, validLen: number, placeHoldersLen: number) =>
|
||||
((validLen + placeHoldersLen) * 3) / 4 - placeHoldersLen;
|
||||
|
||||
export const toByteArray = (b64: string) => {
|
||||
export const toByteArray = (b64: string): any[] | Uint8Array => {
|
||||
let tmp;
|
||||
const lens = getLens(b64);
|
||||
const validLen = lens[0];
|
||||
|
@ -106,7 +106,7 @@ const encodeChunk = (uint8: number[] | Uint8Array, start: number, end: number) =
|
|||
return output.join('');
|
||||
};
|
||||
|
||||
export const fromByteArray = (uint8: number[] | Uint8Array) => {
|
||||
export const fromByteArray = (uint8: number[] | Uint8Array): string => {
|
||||
let tmp;
|
||||
const len = uint8.length;
|
||||
const extraBytes = len % 3; // if we have 1 byte left, pad 2 bytes
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
// https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/Promise.jsm/Deferred
|
||||
export default class Deferred {
|
||||
constructor() {
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
this.resolve = resolve;
|
||||
this.reject = reject;
|
||||
});
|
||||
|
||||
promise.resolve = this.resolve;
|
||||
promise.reject = this.reject;
|
||||
|
||||
return promise;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
export default class Deferred {
|
||||
[Symbol.toStringTag]: 'Promise';
|
||||
|
||||
private promise: Promise<unknown>;
|
||||
private _resolve: (value?: unknown) => void;
|
||||
private _reject: (reason?: any) => void;
|
||||
|
||||
constructor() {
|
||||
this._resolve = () => {};
|
||||
this._reject = () => {};
|
||||
this.promise = new Promise((resolve, reject) => {
|
||||
this._resolve = resolve as (value?: unknown) => void;
|
||||
this._reject = reject;
|
||||
});
|
||||
}
|
||||
|
||||
public then<TResult1, TResult2>(
|
||||
onfulfilled?: ((value: unknown) => TResult1 | PromiseLike<TResult1>) | undefined | null,
|
||||
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null
|
||||
): Promise<TResult1 | TResult2> {
|
||||
return this.promise.then(onfulfilled, onrejected);
|
||||
}
|
||||
|
||||
public catch<TResult>(
|
||||
onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null
|
||||
): Promise<unknown | TResult> {
|
||||
return this.promise.catch(onrejected);
|
||||
}
|
||||
|
||||
public finally(onfinally?: (() => void) | null | undefined): Promise<unknown> {
|
||||
return this.promise.finally(onfinally);
|
||||
}
|
||||
|
||||
public resolve(value?: unknown): void {
|
||||
this._resolve(value);
|
||||
}
|
||||
|
||||
public reject(reason?: any): void {
|
||||
this._reject(reason);
|
||||
}
|
||||
}
|
|
@ -143,6 +143,7 @@
|
|||
"@rocket.chat/eslint-config": "^0.4.0",
|
||||
"@storybook/addon-storyshots": "5.3.21",
|
||||
"@storybook/react-native": "5.3.25",
|
||||
"@types/bytebuffer": "^5.0.43",
|
||||
"@testing-library/react-native": "^9.0.0",
|
||||
"@types/ejson": "^2.1.3",
|
||||
"@types/jest": "^26.0.24",
|
||||
|
|
13
yarn.lock
13
yarn.lock
|
@ -4200,6 +4200,14 @@
|
|||
dependencies:
|
||||
"@babel/types" "^7.3.0"
|
||||
|
||||
"@types/bytebuffer@^5.0.43":
|
||||
version "5.0.43"
|
||||
resolved "https://registry.yarnpkg.com/@types/bytebuffer/-/bytebuffer-5.0.43.tgz#b5259fca1412106bcee0cabfbf7c104846d06738"
|
||||
integrity sha512-vQnTYvy4LpSojHjKdmg4nXFI1BAiYPvZ/k3ouczZAQnbDprk1xqxJiFmFHyy8y6MuUq3slz5erNMtn6n87uVKw==
|
||||
dependencies:
|
||||
"@types/long" "*"
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/color-name@^1.1.1":
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
|
||||
|
@ -4341,6 +4349,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.172.tgz#aad774c28e7bfd7a67de25408e03ee5a8c3d028a"
|
||||
integrity sha512-/BHF5HAx3em7/KkzVKm3LrsD6HZAXuXO1AJZQ3cRRBZj4oHZDviWPYu0aEplAqDFNHZPW6d3G7KN+ONcCCC7pw==
|
||||
|
||||
"@types/long@*":
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9"
|
||||
integrity sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==
|
||||
|
||||
"@types/minimatch@*":
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
|
||||
|
|
Loading…
Reference in New Issue