diff --git a/keyserver/package.json b/keyserver/package.json --- a/keyserver/package.json +++ b/keyserver/package.json @@ -49,6 +49,7 @@ "@babel/runtime": "^7.28.3", "@commapp/olm": "0.2.6", "@hono/node-server": "^1.13.7", + "vodozemac": "file:../../vodozemac/js", "@parse/node-apn": "^3.2.0", "@vingle/bmp-js": "^0.2.5", "bad-words": "^3.0.4", diff --git a/keyserver/src/keyserver.js b/keyserver/src/keyserver.js --- a/keyserver/src/keyserver.js +++ b/keyserver/src/keyserver.js @@ -1,6 +1,5 @@ // @flow -import olm from '@commapp/olm'; import cluster from 'cluster'; import compression from 'compression'; import cookieParser from 'cookie-parser'; @@ -13,6 +12,7 @@ import os from 'os'; import qrcode from 'qrcode'; import stoppable from 'stoppable'; +import initVodozemac from 'vodozemac'; import './cron/cron.js'; import { qrCodeLinkURL } from 'lib/facts/links.js'; @@ -72,7 +72,7 @@ void (async () => { const [webAppCorsConfig] = await Promise.all([ getWebAppCorsConfig(), - olm.init(), + initVodozemac(), prefetchAllURLFacts(), initENSCache(), initFCCache(), 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 @@ -41,6 +41,7 @@ +fontsURL: string, +cssInclude: string, +olmFilename: string, + +vodozemacFilename: string, +commQueryExecutorFilename: string, +backupClientFilename: string, +webworkersOpaqueFilename: string, @@ -57,6 +58,7 @@ fontsURL, cssInclude: '', olmFilename: '', + vodozemacFilename: '', commQueryExecutorFilename: '', backupClientFilename: '', webworkersOpaqueFilename: '', @@ -82,6 +84,7 @@ /> `, olmFilename: manifest['olm.wasm'], + vodozemacFilename: manifest['vodozemac.wasm'], commQueryExecutorFilename: webworkersManifest['comm-query-executor.wasm'], backupClientFilename: webworkersManifest['backup-client-wasm_bg.wasm'], webworkersOpaqueFilename: webworkersManifest['comm_opaque2_wasm_bg.wasm'], @@ -136,6 +139,7 @@ fontsURL, cssInclude, olmFilename, + vodozemacFilename, commQueryExecutorFilename, backupClientFilename, webworkersOpaqueFilename, @@ -195,6 +199,7 @@ var keyserverURL = "${keyserverURL}"; var baseURL = "${baseURL}"; var olmFilename = "${olmFilename}"; + var vodozemacFilename = "${vodozemacFilename}"; var commQueryExecutorFilename = "${commQueryExecutorFilename}"; var backupClientFilename = "${backupClientFilename}"; var webworkersOpaqueFilename = "${webworkersOpaqueFilename}" diff --git a/lib/flow-typed/vodozemac.js b/lib/flow-typed/vodozemac.js new file mode 100644 --- /dev/null +++ b/lib/flow-typed/vodozemac.js @@ -0,0 +1,89 @@ +// @flow + +declare module 'vodozemac' { + declare export default function init(opts?: Object): Promise; + declare export function initSync(options: { module: Buffer | Uint8Array }): void; + + declare export class Account { + constructor(): Account; + free(): void; + + +ed25519_key: string; + +curve25519_key: string; + sign(message: string): string; + + one_time_keys(): Map; + mark_keys_as_published(): void; + max_number_of_one_time_keys(): number; + generate_one_time_keys(count: number): void; + + generate_prekey(): void; + prekey(): ?string; + unpublished_prekey(): ?string; + prekey_signature(): ?string; + forget_old_prekey(): void; + + mark_prekey_as_published(): boolean; + last_prekey_publish_time(): bigint; + + pickle(pickle_key: Uint8Array): string; + static from_pickle(pickle: string, pickle_key: Uint8Array): Account; + static from_libolm_pickle(pickle: string, pickle_key: Uint8Array): Account; + + create_outbound_session( + identity_key: string, + signing_key: string, + one_time_key: ?string, + pre_key: string, + pre_key_signature: string, + olm_compatibility_mode: boolean, + ): Session; + + create_inbound_session( + identity_key: string, + message: OlmMessage, + ): InboundCreationResult; + } + + declare export class Session { + free(): void; + + pickle(pickle_key: Uint8Array): string; + static from_pickle(pickle: string, pickle_key: Uint8Array): Session; + static from_libolm_pickle(pickle: string, pickle_key: Uint8Array): Session; + + +session_id: string; + has_received_message(): boolean; + is_sender_chain_empty(): boolean; + session_matches(message: OlmMessage): boolean; + + encrypt(plaintext: string): OlmMessage; + decrypt(message: OlmMessage): string; + } + + declare export class OlmMessage { + constructor(message_type: number, ciphertext: string): OlmMessage; + free(): void; + + +ciphertext: string; + +message_type: 0 | 1, // 0: PreKey, 1: Message + } + + declare export class InboundCreationResult { + free(): void; + + +plaintext: string; + into_session(): Session; + } + + declare export class Utility { + constructor(): void; + free(): void; + sha256(input: string | Uint8Array): string; + ed25519_verify( + key: string, + message: string | Uint8Array, + signature: string, + ): void; + } +} diff --git a/lib/package.json b/lib/package.json --- a/lib/package.json +++ b/lib/package.json @@ -38,6 +38,7 @@ "dependencies": { "@commapp/olm": "0.2.6", "@ensdomains/ensjs": "^4.0.1", + "vodozemac": "file:../../vodozemac/js", "@khanacademy/simple-markdown": "^2.1.0", "@rainbow-me/rainbowkit": "^2.0.7", "base-64": "^0.1.0", 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 @@ -3,7 +3,10 @@ import { type OlmAPI } from 'lib/types/crypto-types.js'; import { getCommSharedWorker } from '../shared-worker/shared-worker-provider.js'; -import { getOlmWasmPath } from '../shared-worker/utils/constants.js'; +import { + getOlmWasmPath, + getVodozemacWasmPath, +} from '../shared-worker/utils/constants.js'; import { workerRequestMessageTypes, workerResponseMessageTypes, @@ -41,6 +44,7 @@ await sharedWorker.schedule({ type: workerRequestMessageTypes.INITIALIZE_CRYPTO_ACCOUNT, olmWasmPath: getOlmWasmPath(), + vodozemacWasmPath: getVodozemacWasmPath(), }); }, getUserPublicKey: proxyToWorker('getUserPublicKey'), diff --git a/web/package.json b/web/package.json --- a/web/package.json +++ b/web/package.json @@ -46,6 +46,7 @@ "@babel/runtime": "^7.28.3", "@commapp/olm": "0.2.6", "@commapp/opaque-ke-wasm": "npm:@commapp/opaque-ke-wasm@^0.0.4", + "vodozemac": "file:../../vodozemac/js", "@emoji-mart/data": "^1.1.2", "@emoji-mart/react": "^1.1.1", "@fortawesome/fontawesome-svg-core": "1.2.25", diff --git a/web/push-notif/notif-crypto-utils.js b/web/push-notif/notif-crypto-utils.js --- a/web/push-notif/notif-crypto-utils.js +++ b/web/push-notif/notif-crypto-utils.js @@ -5,6 +5,7 @@ import invariant from 'invariant'; import localforage from 'localforage'; import uuid from 'uuid'; +import initVodozemac from 'vodozemac'; import { olmEncryptedMessageTypes, @@ -52,6 +53,7 @@ export type WebNotifsServiceUtilsData = { +olmWasmPath: string, + +vodozemacWasmPath: string, +staffCanSee: boolean, }; @@ -369,7 +371,8 @@ if (!utilsData) { return { id, error: 'Necessary data not found in IndexedDB' }; } - const { olmWasmPath, staffCanSee } = (utilsData: WebNotifsServiceUtilsData); + const { vodozemacWasmPath, staffCanSee } = + (utilsData: WebNotifsServiceUtilsData); let notifsAccountWithOlmData; try { @@ -404,7 +407,7 @@ encryptedOlmAccount, accountEncryptionKey, ), - olm.init({ locateFile: () => olmWasmPath }), + initVodozemac(vodozemacWasmPath), ]); let decryptedNotification; 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 @@ -32,7 +32,10 @@ import PushNotifModal from '../modals/push-notif-modal.react.js'; import { updateNavInfoActionType } from '../redux/action-types.js'; import { useSelector } from '../redux/redux-utils.js'; -import { getOlmWasmPath } from '../shared-worker/utils/constants.js'; +import { + getOlmWasmPath, + getVodozemacWasmPath, +} from '../shared-worker/utils/constants.js'; import { useStaffCanSee } from '../utils/staff-utils.js'; function useCreateDesktopPushSubscription() { @@ -162,6 +165,7 @@ workerRegistration.active?.postMessage({ olmWasmPath: getOlmWasmPath(), + vodozemacWasmPath: getVodozemacWasmPath(), staffCanSee, authMetadata, }); diff --git a/web/push-notif/service-worker.js b/web/push-notif/service-worker.js --- a/web/push-notif/service-worker.js +++ b/web/push-notif/service-worker.js @@ -29,6 +29,7 @@ declare class CommAppMessage extends ExtendableEvent { +data: { +olmWasmPath?: string, + +vodozemacWasmPath?: string, +staffCanSee?: boolean, +authMetadata?: AuthMetadata, }; @@ -71,14 +72,21 @@ localforage.config(localforageConfig); event.waitUntil( (async () => { - const { olmWasmPath, staffCanSee, authMetadata } = event.data; - - if (!olmWasmPath || staffCanSee === undefined || !authMetadata) { + const { olmWasmPath, vodozemacWasmPath, staffCanSee, authMetadata } = + event.data; + + if ( + !olmWasmPath || + !vodozemacWasmPath || + staffCanSee === undefined || + !authMetadata + ) { return; } const webNotifsServiceUtils: WebNotifsServiceUtilsData = { olmWasmPath: olmWasmPath, + vodozemacWasmPath: vodozemacWasmPath, staffCanSee: staffCanSee, }; diff --git a/web/redux/persist.js b/web/redux/persist.js --- a/web/redux/persist.js +++ b/web/redux/persist.js @@ -86,7 +86,10 @@ import { legacyUnshimClientDB, unshimClientDB } from './unshim-utils.js'; import { authoritativeKeyserverID } from '../authoritative-keyserver.js'; import { getCommSharedWorker } from '../shared-worker/shared-worker-provider.js'; -import { getOlmWasmPath } from '../shared-worker/utils/constants.js'; +import { + getOlmWasmPath, + getVodozemacWasmPath, +} from '../shared-worker/utils/constants.js'; import { isSQLiteSupported } from '../shared-worker/utils/db-utils.js'; import { workerRequestMessageTypes } from '../types/worker-types.js'; @@ -341,6 +344,7 @@ await sharedWorker.schedule({ type: workerRequestMessageTypes.INITIALIZE_CRYPTO_ACCOUNT, olmWasmPath: getOlmWasmPath(), + vodozemacWasmPath: getVodozemacWasmPath(), initialCryptoStore: cryptoStore, }); return rest; diff --git a/web/shared-worker/shared-worker-provider.js b/web/shared-worker/shared-worker-provider.js --- a/web/shared-worker/shared-worker-provider.js +++ b/web/shared-worker/shared-worker-provider.js @@ -25,6 +25,7 @@ declare var baseURL: string; declare var commQueryExecutorFilename: string; declare var backupClientFilename: string; +declare var vodozemacFilename: string; const sharedWorkerStatuses = Object.freeze({ NOT_RUNNING: 'NOT_RUNNING', @@ -107,6 +108,7 @@ encryptionKey, commQueryExecutorFilename, backupClientFilename, + vodozemacFilename, }); this.status = { type: sharedWorkerStatuses.INIT_SUCCESS }; console.info('Database initialization success'); 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,7 @@ export const DEFAULT_BACKUP_CLIENT_FILENAME = 'backup-client-wasm_bg.wasm'; export const DEFAULT_OLM_FILENAME = 'olm.wasm'; +export const DEFAULT_VODOZEMAC_FILENAME = 'vodozemac_bg.wasm'; export const DEFAULT_WEBWORKERS_OPAQUE_FILENAME = 'comm_opaque2_wasm_bg.wasm'; @@ -69,3 +70,13 @@ : DEFAULT_WEBWORKERS_OPAQUE_FILENAME; return `${opaqueWasmDirPath}/${opaqueWasmFilename}`; } + +declare var vodozemacFilename: string; +export function getVodozemacWasmPath(): string { + const origin = window.location.origin; + const vodozemacWasmDirPath = `${origin}${baseURL}${WORKERS_MODULES_DIR_PATH}`; + const vodozemacWasmFilename = vodozemacFilename + ? vodozemacFilename + : DEFAULT_VODOZEMAC_FILENAME; + return `${vodozemacWasmDirPath}/${vodozemacWasmFilename}`; +} 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 @@ -1,6 +1,7 @@ // @flow import localforage from 'localforage'; +import initVodozemac from 'vodozemac'; import { databaseIdentifier, @@ -53,6 +54,7 @@ DEFAULT_BACKUP_CLIENT_FILENAME, SQLITE_RESTORE_DATABASE_PATH, RESTORED_SQLITE_CONTENT, + DEFAULT_VODOZEMAC_FILENAME, } from '../utils/constants.js'; import { clearSensitiveData, @@ -180,6 +182,20 @@ await initBackupClientModule(modulePath); } +async function initVodozemacModule( + webworkerModulesFilePath: string, + vodozemacFilename: ?string, +) { + let modulePath; + if (vodozemacFilename) { + modulePath = `${webworkerModulesFilePath}/${vodozemacFilename}`; + } else { + modulePath = `${webworkerModulesFilePath}/${DEFAULT_VODOZEMAC_FILENAME}`; + } + console.log(modulePath); + await initVodozemac(modulePath); +} + async function persist() { persistInProgress = true; const mainQueryExecutor = getSQLiteQueryExecutor(); @@ -286,6 +302,10 @@ message.commQueryExecutorFilename, message.encryptionKey, ), + initVodozemacModule( + message.webworkerModulesFilePath, + message.vodozemacFilename, + ), ]; if (message.backupClientFilename !== undefined) { promises.push( 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 @@ -7,6 +7,7 @@ import base64 from 'base-64'; import localforage from 'localforage'; import uuid from 'uuid'; +import initVodozemac from 'vodozemac'; import { initialEncryptedMessageContent } from 'lib/shared/crypto-utils.js'; import { hasMinCodeVersion } from 'lib/shared/version-utils.js'; @@ -354,6 +355,7 @@ async function initializeCryptoAccount( olmWasmPath: string, + vodozemacWasmPath: string, initialCryptoStore: ?LegacyCryptoStore, ) { const sqliteQueryExecutor = getSQLiteQueryExecutor(); @@ -361,7 +363,7 @@ throw new Error('Database not initialized'); } - await olm.init({ locateFile: () => olmWasmPath }); + await initVodozemac(vodozemacWasmPath); if (initialCryptoStore) { clearCryptoStore(); @@ -392,6 +394,7 @@ if (message.type === workerRequestMessageTypes.INITIALIZE_CRYPTO_ACCOUNT) { await initializeCryptoAccount( message.olmWasmPath, + message.vodozemacWasmPath, message.initialCryptoStore, ); verifyMemoryUsage('INITIALIZE_CRYPTO_ACCOUNT'); 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 @@ -104,6 +104,7 @@ +commQueryExecutorFilename: ?string, +encryptionKey?: ?SubtleCrypto$JsonWebKey, +backupClientFilename?: ?string, + +vodozemacFilename: string, }; export type GenerateDatabaseEncryptionKeyRequestMessage = { @@ -169,6 +170,7 @@ export type InitializeCryptoAccountRequestMessage = { +type: 12, +olmWasmPath: string, + +vodozemacWasmPath: string, +initialCryptoStore?: LegacyCryptoStore, }; diff --git a/web/webpack.config.cjs b/web/webpack.config.cjs --- a/web/webpack.config.cjs +++ b/web/webpack.config.cjs @@ -134,6 +134,14 @@ }, ], }), + new CopyPlugin({ + patterns: [ + { + from: 'node_modules/vodozemac/wasm/vodozemac_bg.wasm', + to: path.join(__dirname, 'dist', 'webworkers'), + }, + ], + }), new CopyPlugin({ patterns: [ { @@ -181,6 +189,19 @@ }, ], }), + new CopyPlugin({ + patterns: [ + { + from: 'node_modules/vodozemac/wasm/vodozemac_bg.wasm', + to: path.join( + __dirname, + 'dist', + 'webworkers', + 'vodozemac.[contenthash:12].wasm', + ), + }, + ], + }), new CopyPlugin({ patterns: [ { diff --git a/yarn.lock b/yarn.lock --- a/yarn.lock +++ b/yarn.lock @@ -27533,6 +27533,9 @@ resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== +"vodozemac@file:../vodozemac/js": + version "0.1.0" + void-elements@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09"