diff --git a/web/crypto/olm-api.js b/web/crypto/olm-api.js --- a/web/crypto/olm-api.js +++ b/web/crypto/olm-api.js @@ -17,6 +17,11 @@ shouldRotatePrekey, } from 'lib/utils/olm-utils.js'; +import { getCommSharedWorker } from '../shared-worker/shared-worker-provider.js'; +import { getOlmWasmPath } from '../shared-worker/utils/constants.js'; +import { workerRequestMessageTypes } from '../types/worker-types.js'; + +const usingSharedWorker = false; // methods below are just mocks to SQLite API // implement proper methods tracked in ENG-6462 @@ -36,7 +41,15 @@ const olmAPI: OlmAPI = { async initializeCryptoAccount(): Promise { - await olm.init(); + if (usingSharedWorker) { + const sharedWorker = await getCommSharedWorker(); + await sharedWorker.schedule({ + type: workerRequestMessageTypes.INITIALIZE_CRYPTO_ACCOUNT, + olmWasmPath: getOlmWasmPath(), + }); + } else { + await olm.init(); + } }, async encrypt(content: string, deviceID: string): Promise { const session = getOlmSession(deviceID); diff --git a/web/push-notif/push-notifs-handler.js b/web/push-notif/push-notifs-handler.js --- a/web/push-notif/push-notifs-handler.js +++ b/web/push-notif/push-notifs-handler.js @@ -22,15 +22,9 @@ import PushNotifModal from '../modals/push-notif-modal.react.js'; import { updateNavInfoActionType } from '../redux/action-types.js'; import { useSelector } from '../redux/redux-utils.js'; -import { - WORKERS_MODULES_DIR_PATH, - DEFAULT_OLM_FILENAME, -} from '../shared-worker/utils/constants.js'; +import { getOlmWasmPath } from '../shared-worker/utils/constants.js'; import { useStaffCanSee } from '../utils/staff-utils.js'; -declare var baseURL: string; -declare var olmFilename: string; - function useCreateDesktopPushSubscription() { const dispatchActionPromise = useDispatchActionPromise(); const callSetDeviceToken = useSetDeviceTokenFanout(); @@ -106,11 +100,10 @@ return; } - const origin = window.location.origin; - const olmWasmDirPath = `${origin}${baseURL}${WORKERS_MODULES_DIR_PATH}`; - const olmWasmFilename = olmFilename ? olmFilename : DEFAULT_OLM_FILENAME; - const olmWasmPath = `${olmWasmDirPath}/${olmWasmFilename}`; - workerRegistration.active?.postMessage({ olmWasmPath, staffCanSee }); + workerRegistration.active?.postMessage({ + olmWasmPath: getOlmWasmPath(), + staffCanSee, + }); const subscription = await workerRegistration.pushManager.subscribe({ userVisibleOnly: true, diff --git a/web/shared-worker/utils/constants.js b/web/shared-worker/utils/constants.js --- a/web/shared-worker/utils/constants.js +++ b/web/shared-worker/utils/constants.js @@ -46,3 +46,12 @@ description: 'Comm encrypted database storage', version: '1.0', }; + +declare var baseURL: string; +declare var olmFilename: string; +export function getOlmWasmPath(): string { + const origin = window.location.origin; + const olmWasmDirPath = `${origin}${baseURL}${WORKERS_MODULES_DIR_PATH}`; + const olmWasmFilename = olmFilename ? olmFilename : DEFAULT_OLM_FILENAME; + return `${olmWasmDirPath}/${olmWasmFilename}`; +} diff --git a/web/shared-worker/worker/shared-worker.js b/web/shared-worker/worker/shared-worker.js --- a/web/shared-worker/worker/shared-worker.js +++ b/web/shared-worker/worker/shared-worker.js @@ -7,6 +7,7 @@ getClientStoreFromQueryExecutor, processDBStoreOperations, } from './process-operations.js'; +import { clearCryptoStore, processAppOlmApiRequest } from './worker-crypto.js'; import { getDBModule, getSQLiteQueryExecutor, @@ -29,6 +30,7 @@ workerResponseMessageTypes, type WorkerRequestProxyMessage, workerWriteRequests, + workerOlmAPIRequests, } from '../../types/worker-types.js'; import { getDatabaseModule } from '../db-module.js'; import { @@ -189,6 +191,7 @@ await Promise.all(promises); return undefined; } else if (message.type === workerRequestMessageTypes.CLEAR_SENSITIVE_DATA) { + clearCryptoStore(); encryptionKey = null; await localforage.clear(); if (dbModule && sqliteQueryExecutor) { @@ -229,8 +232,9 @@ } // write operations - if (!workerWriteRequests.includes(message.type)) { - throw new Error('Request type not supported'); + const isOlmAPIRequest = workerOlmAPIRequests.includes(message.type); + if (!workerWriteRequests.includes(message.type) && !isOlmAPIRequest) { + throw new Error(`Request type ${message.type} not supported`); } if (!sqliteQueryExecutor || !dbModule) { throw new Error( @@ -238,7 +242,11 @@ ); } - if (message.type === workerRequestMessageTypes.PROCESS_STORE_OPERATIONS) { + if (isOlmAPIRequest) { + await processAppOlmApiRequest(message); + } else if ( + message.type === workerRequestMessageTypes.PROCESS_STORE_OPERATIONS + ) { processDBStoreOperations( sqliteQueryExecutor, message.storeOperations, diff --git a/web/shared-worker/worker/worker-crypto.js b/web/shared-worker/worker/worker-crypto.js new file mode 100644 --- /dev/null +++ b/web/shared-worker/worker/worker-crypto.js @@ -0,0 +1,167 @@ +// @flow + +import olm from '@commapp/olm'; +import uuid from 'uuid'; + +import type { CryptoStore, PickledOLMAccount } from 'lib/types/crypto-types.js'; + +import { getProcessingStoreOpsExceptionMessage } from './process-operations.js'; +import { getDBModule, getSQLiteQueryExecutor } from './worker-database.js'; +import { + type WorkerRequestMessage, + type WorkerResponseMessage, + workerRequestMessageTypes, +} from '../../types/worker-types.js'; + +type WorkerCryptoStore = { + +contentAccountPickleKey: string, + +contentAccount: olm.Account, + +notificationAccountPickleKey: string, + +notificationAccount: olm.Account, +}; + +let cryptoStore: ?WorkerCryptoStore = null; + +function clearCryptoStore() { + cryptoStore = null; +} + +function persistCryptoStore() { + const sqliteQueryExecutor = getSQLiteQueryExecutor(); + const dbModule = getDBModule(); + if (!sqliteQueryExecutor || !dbModule) { + throw new Error('Database not initialized'); + } + if (!cryptoStore) { + throw new Error("CryptoStore doesn't exist"); + } + + const { + contentAccountPickleKey, + contentAccount, + notificationAccountPickleKey, + notificationAccount, + } = cryptoStore; + + const pickledContentAccount: PickledOLMAccount = { + picklingKey: contentAccountPickleKey, + pickledAccount: contentAccount.pickle(contentAccountPickleKey), + }; + + const pickledNotificationAccount: PickledOLMAccount = { + picklingKey: notificationAccountPickleKey, + pickledAccount: notificationAccount.pickle(notificationAccountPickleKey), + }; + + try { + sqliteQueryExecutor.storeOlmPersistAccount( + sqliteQueryExecutor.getContentAccountID(), + JSON.stringify(pickledContentAccount), + ); + sqliteQueryExecutor.storeOlmPersistAccount( + sqliteQueryExecutor.getNotifsAccountID(), + JSON.stringify(pickledNotificationAccount), + ); + } catch (err) { + throw new Error(getProcessingStoreOpsExceptionMessage(err, dbModule)); + } +} + +function getOrCreateOlmAccount(accountIDInDB: number): { + +picklingKey: string, + +account: olm.Account, +} { + const sqliteQueryExecutor = getSQLiteQueryExecutor(); + const dbModule = getDBModule(); + if (!sqliteQueryExecutor || !dbModule) { + throw new Error('Database not initialized'); + } + + const account = new olm.Account(); + let picklingKey; + + let accountDBString; + try { + accountDBString = + sqliteQueryExecutor.getOlmPersistAccountDataWeb(accountIDInDB); + } catch (err) { + throw new Error(getProcessingStoreOpsExceptionMessage(err, dbModule)); + } + + if (accountDBString.isNull) { + picklingKey = uuid.v4(); + account.create(); + } else { + const dbAccount: PickledOLMAccount = JSON.parse(accountDBString.value); + picklingKey = dbAccount.picklingKey; + account.unpickle(picklingKey, dbAccount.pickledAccount); + } + + return { picklingKey, account }; +} + +function unpickleInitialCryptoStoreAccount( + account: PickledOLMAccount, +): olm.Account { + const { picklingKey, pickledAccount } = account; + const olmAccount = new olm.Account(); + olmAccount.unpickle(picklingKey, pickledAccount); + return olmAccount; +} + +async function initializeCryptoAccount( + olmWasmPath: string, + initialCryptoStore: ?CryptoStore, +) { + const sqliteQueryExecutor = getSQLiteQueryExecutor(); + if (!sqliteQueryExecutor) { + throw new Error('Database not initialized'); + } + + await olm.init({ locateFile: () => olmWasmPath }); + + if (initialCryptoStore) { + cryptoStore = { + contentAccountPickleKey: initialCryptoStore.primaryAccount.picklingKey, + contentAccount: unpickleInitialCryptoStoreAccount( + initialCryptoStore.primaryAccount, + ), + notificationAccountPickleKey: + initialCryptoStore.notificationAccount.picklingKey, + notificationAccount: unpickleInitialCryptoStoreAccount( + initialCryptoStore.notificationAccount, + ), + }; + persistCryptoStore(); + return; + } + + const contentAccountResult = getOrCreateOlmAccount( + sqliteQueryExecutor.getContentAccountID(), + ); + const notificationAccountResult = getOrCreateOlmAccount( + sqliteQueryExecutor.getNotifsAccountID(), + ); + + cryptoStore = { + contentAccountPickleKey: contentAccountResult.picklingKey, + contentAccount: contentAccountResult.account, + notificationAccountPickleKey: notificationAccountResult.picklingKey, + notificationAccount: notificationAccountResult.account, + }; + + persistCryptoStore(); +} + +async function processAppOlmApiRequest( + message: WorkerRequestMessage, +): Promise { + if (message.type === workerRequestMessageTypes.INITIALIZE_CRYPTO_ACCOUNT) { + await initializeCryptoAccount( + message.olmWasmPath, + message.initialCryptoStore, + ); + } +} + +export { clearCryptoStore, processAppOlmApiRequest }; diff --git a/web/types/worker-types.js b/web/types/worker-types.js --- a/web/types/worker-types.js +++ b/web/types/worker-types.js @@ -1,6 +1,7 @@ // @flow import type { AuthMetadata } from 'lib/shared/identity-client-context.js'; +import type { CryptoStore } from 'lib/types/crypto-types.js'; import type { ClientDBStore, ClientDBStoreOperations, @@ -20,6 +21,7 @@ REMOVE_PERSIST_STORAGE_ITEM: 9, CLEAR_SENSITIVE_DATA: 10, BACKUP_RESTORE: 11, + INITIALIZE_CRYPTO_ACCOUNT: 12, }); export const workerWriteRequests: $ReadOnlyArray = [ @@ -28,6 +30,11 @@ workerRequestMessageTypes.SET_PERSIST_STORAGE_ITEM, workerRequestMessageTypes.REMOVE_PERSIST_STORAGE_ITEM, workerRequestMessageTypes.BACKUP_RESTORE, + workerRequestMessageTypes.INITIALIZE_CRYPTO_ACCOUNT, +]; + +export const workerOlmAPIRequests: $ReadOnlyArray = [ + workerRequestMessageTypes.INITIALIZE_CRYPTO_ACCOUNT, ]; export type PingWorkerRequestMessage = { @@ -41,6 +48,7 @@ +commQueryExecutorFilename: ?string, +encryptionKey?: ?SubtleCrypto$JsonWebKey, +backupClientFilename?: ?string, + +initialCryptoStore?: CryptoStore, }; export type GenerateDatabaseEncryptionKeyRequestMessage = { @@ -93,6 +101,12 @@ +backupLogDataKey: string, }; +export type InitializeCryptoAccountRequestMessage = { + +type: 12, + +olmWasmPath: string, + +initialCryptoStore?: CryptoStore, +}; + export type WorkerRequestMessage = | PingWorkerRequestMessage | InitWorkerRequestMessage @@ -105,7 +119,8 @@ | SetPersistStorageItemRequestMessage | RemovePersistStorageItemRequestMessage | ClearSensitiveDataRequestMessage - | BackupRestoreRequestMessage; + | BackupRestoreRequestMessage + | InitializeCryptoAccountRequestMessage; export type WorkerRequestProxyMessage = { +id: number,