diff --git a/keyserver/src/responders/website-responders.js b/keyserver/src/responders/website-responders.js --- a/keyserver/src/responders/website-responders.js +++ b/keyserver/src/responders/website-responders.js @@ -45,6 +45,7 @@ +commQueryExecutorFilename: string, +opaqueURL: string, +backupClientFilename: string, + +webworkersOpaqueFilename: string, }; let assetInfo: ?AssetInfo = null; async function getAssetInfo() { @@ -61,6 +62,7 @@ commQueryExecutorFilename: '', opaqueURL: 'http://localhost:8080/opaque-ke.wasm', backupClientFilename: '', + webworkersOpaqueFilename: '', }; return assetInfo; } @@ -86,6 +88,7 @@ commQueryExecutorFilename: webworkersManifest['comm_query_executor.wasm'], opaqueURL: `compiled/${manifest['comm_opaque2_wasm_bg.wasm']}`, backupClientFilename: webworkersManifest['backup-client-wasm_bg.wasm'], + webworkersOpaqueFilename: webworkersManifest['comm_opaque2_wasm_bg.wasm'], }; return assetInfo; } catch { @@ -138,6 +141,7 @@ opaqueURL, commQueryExecutorFilename, backupClientFilename, + webworkersOpaqueFilename, } = await assetInfoPromise; // prettier-ignore @@ -190,6 +194,7 @@ var olmFilename = "${olmFilename}"; var commQueryExecutorFilename = "${commQueryExecutorFilename}"; var backupClientFilename = "${backupClientFilename}"; + var webworkersOpaqueFilename = "${webworkersOpaqueFilename}" var opaqueURL = "${opaqueURL}"; 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 @@ -15,6 +15,8 @@ export const DEFAULT_OLM_FILENAME = 'olm.wasm'; +export const DEFAULT_WEBWORKERS_OPAQUE_FILENAME = 'comm_opaque2_wasm_bg.wasm'; + export const COMM_SQLITE_DATABASE_PATH = 'comm.sqlite'; export const COMM_SQLITE_BACKUP_RESTORE_DATABASE_PATH = 'comm_backup_restore.sqlite'; @@ -55,3 +57,13 @@ const olmWasmFilename = olmFilename ? olmFilename : DEFAULT_OLM_FILENAME; return `${olmWasmDirPath}/${olmWasmFilename}`; } + +declare var webworkersOpaqueFilename: string; +export function getOpaqueWasmPath(): string { + const origin = window.location.origin; + const opaqueWasmDirPath = `${origin}${baseURL}${WORKERS_MODULES_DIR_PATH}`; + const opaqueWasmFilename = webworkersOpaqueFilename + ? webworkersOpaqueFilename + : DEFAULT_WEBWORKERS_OPAQUE_FILENAME; + return `${opaqueWasmDirPath}/${opaqueWasmFilename}`; +} diff --git a/web/shared-worker/worker/identity-client.js b/web/shared-worker/worker/identity-client.js new file mode 100644 --- /dev/null +++ b/web/shared-worker/worker/identity-client.js @@ -0,0 +1,36 @@ +// @flow + +import { getDeviceKeyUpload } from './worker-crypto.js'; +import { IdentityServiceClientWrapper } from '../../grpc/identity-service-client-wrapper.js'; +import { workerRequestMessageTypes } from '../../types/worker-types.js'; +import type { + WorkerResponseMessage, + WorkerRequestMessage, +} from '../../types/worker-types.js'; +import type { EmscriptenModule } from '../types/module'; +import type { SQLiteQueryExecutor } from '../types/sqlite-query-executor'; + +let identityClient: ?IdentityServiceClientWrapper = null; + +async function processAppIdentityClientRequest( + sqliteQueryExecutor: SQLiteQueryExecutor, + dbModule: EmscriptenModule, + message: WorkerRequestMessage, +): Promise { + if ( + message.type === workerRequestMessageTypes.CREATE_IDENTITY_SERVICE_CLIENT + ) { + identityClient = new IdentityServiceClientWrapper( + message.platformDetails, + message.opaqueWasmPath, + message.authLayer, + async () => getDeviceKeyUpload(), + ); + } +} + +function getIdentityClient(): ?IdentityServiceClientWrapper { + return identityClient; +} + +export { processAppIdentityClientRequest, getIdentityClient }; 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 @@ -3,6 +3,7 @@ import localforage from 'localforage'; import { restoreBackup } from './backup.js'; +import { processAppIdentityClientRequest } from './identity-client.js'; import { getClientStoreFromQueryExecutor, processDBStoreOperations, @@ -32,6 +33,7 @@ workerWriteRequests, workerOlmAPIRequests, } from '../../types/worker-types.js'; +import { workerIdentityClientRequests } from '../../types/worker-types.js'; import { getDatabaseModule } from '../db-module.js'; import { COMM_SQLITE_DATABASE_PATH, @@ -233,7 +235,14 @@ // write operations const isOlmAPIRequest = workerOlmAPIRequests.includes(message.type); - if (!workerWriteRequests.includes(message.type) && !isOlmAPIRequest) { + const isIdentityClientRequest = workerIdentityClientRequests.includes( + message.type, + ); + if ( + !workerWriteRequests.includes(message.type) && + !isOlmAPIRequest && + !isIdentityClientRequest + ) { throw new Error(`Request type ${message.type} not supported`); } if (!sqliteQueryExecutor || !dbModule) { @@ -242,8 +251,15 @@ ); } + let result; if (isOlmAPIRequest) { await processAppOlmApiRequest(message); + } else if (isIdentityClientRequest) { + result = await processAppIdentityClientRequest( + sqliteQueryExecutor, + dbModule, + message, + ); } else if ( message.type === workerRequestMessageTypes.PROCESS_STORE_OPERATIONS ) { @@ -278,7 +294,7 @@ void persist(); } - return undefined; + return result; } function connectHandler(event: SharedWorkerMessageEvent) { diff --git a/web/shared-worker/worker/worker-crypto.js b/web/shared-worker/worker/worker-crypto.js --- a/web/shared-worker/worker/worker-crypto.js +++ b/web/shared-worker/worker/worker-crypto.js @@ -3,7 +3,14 @@ import olm from '@commapp/olm'; import uuid from 'uuid'; -import type { CryptoStore, PickledOLMAccount } from 'lib/types/crypto-types.js'; +import type { + CryptoStore, + PickledOLMAccount, + IdentityKeysBlob, + SignedIdentityKeysBlob, +} from 'lib/types/crypto-types.js'; +import type { IdentityDeviceKeyUpload } from 'lib/types/identity-service-types.js'; +import { retrieveAccountKeysSet } from 'lib/utils/olm-utils.js'; import { getProcessingStoreOpsExceptionMessage } from './process-operations.js'; import { getDBModule, getSQLiteQueryExecutor } from './worker-database.js'; @@ -164,4 +171,58 @@ } } -export { clearCryptoStore, processAppOlmApiRequest }; +function getSignedIdentityKeysBlob(): SignedIdentityKeysBlob { + if (!cryptoStore) { + throw new Error('Crypto account not initialized'); + } + + const { contentAccount, notificationAccount } = cryptoStore; + + const identityKeysBlob: IdentityKeysBlob = { + primaryIdentityPublicKeys: JSON.parse(contentAccount.identity_keys()), + notificationIdentityPublicKeys: JSON.parse( + notificationAccount.identity_keys(), + ), + }; + + const payloadToBeSigned: string = JSON.stringify(identityKeysBlob); + const signedIdentityKeysBlob: SignedIdentityKeysBlob = { + payload: payloadToBeSigned, + signature: contentAccount.sign(payloadToBeSigned), + }; + + return signedIdentityKeysBlob; +} + +function getDeviceKeyUpload(): IdentityDeviceKeyUpload { + if (!cryptoStore) { + throw new Error('Crypto account not initialized'); + } + const { contentAccount, notificationAccount } = cryptoStore; + + const signedIdentityKeysBlob = getSignedIdentityKeysBlob(); + + const primaryAccountKeysSet = retrieveAccountKeysSet(contentAccount); + const notificationAccountKeysSet = + retrieveAccountKeysSet(notificationAccount); + + persistCryptoStore(); + + return { + keyPayload: signedIdentityKeysBlob.payload, + keyPayloadSignature: signedIdentityKeysBlob.signature, + contentPrekey: primaryAccountKeysSet.prekey, + contentPrekeySignature: primaryAccountKeysSet.prekeySignature, + notifPrekey: notificationAccountKeysSet.prekey, + notifPrekeySignature: notificationAccountKeysSet.prekeySignature, + contentOneTimeKeys: primaryAccountKeysSet.oneTimeKeys, + notifOneTimeKeys: notificationAccountKeysSet.oneTimeKeys, + }; +} + +export { + clearCryptoStore, + processAppOlmApiRequest, + getSignedIdentityKeysBlob, + getDeviceKeyUpload, +}; 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 @@ -2,6 +2,8 @@ import type { AuthMetadata } from 'lib/shared/identity-client-context.js'; import type { CryptoStore } from 'lib/types/crypto-types.js'; +import type { PlatformDetails } from 'lib/types/device-types.js'; +import type { IdentityServiceAuthLayer } from 'lib/types/identity-service-types.js'; import type { ClientDBStore, ClientDBStoreOperations, @@ -22,6 +24,7 @@ CLEAR_SENSITIVE_DATA: 10, BACKUP_RESTORE: 11, INITIALIZE_CRYPTO_ACCOUNT: 12, + CREATE_IDENTITY_SERVICE_CLIENT: 13, }); export const workerWriteRequests: $ReadOnlyArray = [ @@ -37,6 +40,10 @@ workerRequestMessageTypes.INITIALIZE_CRYPTO_ACCOUNT, ]; +export const workerIdentityClientRequests: $ReadOnlyArray = [ + workerRequestMessageTypes.CREATE_IDENTITY_SERVICE_CLIENT, +]; + export type PingWorkerRequestMessage = { +type: 0, +text: string, @@ -107,6 +114,13 @@ +initialCryptoStore?: CryptoStore, }; +export type CreateIdentityServiceClientRequestMessage = { + +type: 13, + +opaqueWasmPath: string, + +platformDetails: PlatformDetails, + +authLayer: ?IdentityServiceAuthLayer, +}; + export type WorkerRequestMessage = | PingWorkerRequestMessage | InitWorkerRequestMessage @@ -120,7 +134,8 @@ | RemovePersistStorageItemRequestMessage | ClearSensitiveDataRequestMessage | BackupRestoreRequestMessage - | InitializeCryptoAccountRequestMessage; + | InitializeCryptoAccountRequestMessage + | CreateIdentityServiceClientRequestMessage; export type WorkerRequestProxyMessage = { +id: number, diff --git a/web/webpack.config.cjs b/web/webpack.config.cjs --- a/web/webpack.config.cjs +++ b/web/webpack.config.cjs @@ -160,6 +160,16 @@ }, ], }), + new CopyPlugin({ + patterns: [ + { + from: + 'node_modules/@commapp/opaque-ke-wasm' + + '/pkg/comm_opaque2_wasm_bg.wasm', + to: path.join(__dirname, 'dist', 'webworkers'), + }, + ], + }), ]; const prodWebWorkersPlugins = [ @@ -202,6 +212,21 @@ }, ], }), + new CopyPlugin({ + patterns: [ + { + from: + 'node_modules/@commapp/opaque-ke-wasm' + + '/pkg/comm_opaque2_wasm_bg.wasm', + to: path.join( + __dirname, + 'dist', + 'webworkers', + 'opaque-ke.[contenthash:12].wasm', + ), + }, + ], + }), new WebpackManifestPlugin({ publicPath: '', }),