diff --git a/lib/types/crypto-types.js b/lib/types/crypto-types.js --- a/lib/types/crypto-types.js +++ b/lib/types/crypto-types.js @@ -150,6 +150,12 @@ contentIdentityKeys: OLMIdentityKeys, contentInitializationInfo: OlmSessionInitializationInfo, ) => Promise, + +notificationsSessionCreator: ( + cookie: ?string, + notificationsIdentityKeys: OLMIdentityKeys, + notificationsInitializationInfo: OlmSessionInitializationInfo, + keyserverID: string, + ) => Promise, +getOneTimeKeys: (numberOfKeys: number) => Promise, +validateAndUploadPrekeys: (authMetadata: AuthMetadata) => Promise, }; diff --git a/lib/utils/__mocks__/config.js b/lib/utils/__mocks__/config.js --- a/lib/utils/__mocks__/config.js +++ b/lib/utils/__mocks__/config.js @@ -19,6 +19,7 @@ decrypt: jest.fn(), contentInboundSessionCreator: jest.fn(), contentOutboundSessionCreator: jest.fn(), + notificationsSessionCreator: jest.fn(), getOneTimeKeys: jest.fn(), validateAndUploadPrekeys: jest.fn(), }, diff --git a/native/crypto/olm-api.js b/native/crypto/olm-api.js --- a/native/crypto/olm-api.js +++ b/native/crypto/olm-api.js @@ -10,7 +10,10 @@ import type { OlmSessionInitializationInfo } from 'lib/types/request-types.js'; import { commCoreModule } from '../native-modules.js'; -import { nativeOutboundContentSessionCreator } from '../utils/crypto-utils.js'; +import { + nativeOutboundContentSessionCreator, + nativeNotificationsSessionCreator, +} from '../utils/crypto-utils.js'; const olmAPI: OlmAPI = { async initializeCryptoAccount(): Promise { @@ -43,6 +46,18 @@ contentIdentityKeys.ed25519, ); }, + notificationsSessionCreator( + cookie: ?string, + notificationsIdentityKeys: OLMIdentityKeys, + notificationsInitializationInfo: OlmSessionInitializationInfo, + keyserverID: string, + ): Promise { + return nativeNotificationsSessionCreator( + notificationsIdentityKeys, + notificationsInitializationInfo, + keyserverID, + ); + }, async getOneTimeKeys(numberOfKeys: number): Promise { const { contentOneTimeKeys, notificationsOneTimeKeys } = await commCoreModule.getOneTimeKeys(numberOfKeys); 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 @@ -56,6 +56,7 @@ decrypt: proxyToWorker('decrypt'), contentInboundSessionCreator: proxyToWorker('contentInboundSessionCreator'), contentOutboundSessionCreator: proxyToWorker('contentOutboundSessionCreator'), + notificationsSessionCreator: proxyToWorker('notificationsSessionCreator'), getOneTimeKeys: proxyToWorker('getOneTimeKeys'), validateAndUploadPrekeys: proxyToWorker('validateAndUploadPrekeys'), }; 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 @@ -1,6 +1,7 @@ // @flow import olm from '@commapp/olm'; +import localforage from 'localforage'; import uuid from 'uuid'; import { @@ -17,6 +18,7 @@ type OlmAPI, type OneTimeKeysResultValues, type ClientPublicKeys, + type NotificationsOlmDataType, } from 'lib/types/crypto-types.js'; import type { IdentityDeviceKeyUpload } from 'lib/types/identity-service-types.js'; import type { OlmSessionInitializationInfo } from 'lib/types/request-types.js'; @@ -32,6 +34,15 @@ import { getIdentityClient } from './identity-client.js'; import { getProcessingStoreOpsExceptionMessage } from './process-operations.js'; import { getDBModule, getSQLiteQueryExecutor } from './worker-database.js'; +import { + encryptData, + exportKeyToJWK, + generateCryptoKey, +} from '../../crypto/aes-gcm-crypto-utils.js'; +import { + getOlmDataContentKeyForCookie, + getOlmEncryptionKeyDBLabelForCookie, +} from '../../push-notif/notif-crypto-utils.js'; import { type WorkerRequestMessage, type WorkerResponseMessage, @@ -39,6 +50,7 @@ workerResponseMessageTypes, } from '../../types/worker-types.js'; import type { OlmPersistSession } from '../types/sqlite-query-executor.js'; +import { isDesktopSafari } from '../utils/db-utils.js'; type WorkerCryptoStore = { +contentAccountPickleKey: string, @@ -408,6 +420,80 @@ return initialContentEncryptedMessage; }, + async notificationsSessionCreator( + cookie: ?string, + notificationsIdentityKeys: OLMIdentityKeys, + notificationsInitializationInfo: OlmSessionInitializationInfo, + keyserverID: string, + ): Promise { + if (!cryptoStore) { + throw new Error('Crypto account not initialized'); + } + + const { notificationAccountPickleKey, notificationAccount } = cryptoStore; + const encryptionKey = await generateCryptoKey({ + extractable: isDesktopSafari, + }); + + const notificationsPrekey = getPrekeyValueFromBlob( + notificationsInitializationInfo.prekey, + ); + + const session = new olm.Session(); + session.create_outbound( + notificationAccount, + notificationsIdentityKeys.curve25519, + notificationsIdentityKeys.ed25519, + notificationsPrekey, + notificationsInitializationInfo.prekeySignature, + notificationsInitializationInfo.oneTimeKey, + ); + const { body: initialNotificationsEncryptedMessage } = session.encrypt( + JSON.stringify(initialEncryptedMessageContent), + ); + + const mainSession = session.pickle(notificationAccountPickleKey); + const notificationsOlmData: NotificationsOlmDataType = { + mainSession, + pendingSessionUpdate: mainSession, + updateCreationTimestamp: Date.now(), + picklingKey: notificationAccountPickleKey, + }; + const encryptedOlmData = await encryptData( + new TextEncoder().encode(JSON.stringify(notificationsOlmData)), + encryptionKey, + ); + + const notifsOlmDataEncryptionKeyDBLabel = + getOlmEncryptionKeyDBLabelForCookie(cookie); + const notifsOlmDataContentKey = getOlmDataContentKeyForCookie( + cookie, + keyserverID, + ); + + const persistEncryptionKeyPromise = (async () => { + let cryptoKeyPersistentForm; + if (isDesktopSafari) { + // Safari doesn't support structured clone algorithm in service + // worker context so we have to store CryptoKey as JSON + cryptoKeyPersistentForm = await exportKeyToJWK(encryptionKey); + } else { + cryptoKeyPersistentForm = encryptionKey; + } + + await localforage.setItem( + notifsOlmDataEncryptionKeyDBLabel, + cryptoKeyPersistentForm, + ); + })(); + + await Promise.all([ + localforage.setItem(notifsOlmDataContentKey, encryptedOlmData), + persistEncryptionKeyPromise, + ]); + + return initialNotificationsEncryptedMessage; + }, async getOneTimeKeys(numberOfKeys: number): Promise { if (!cryptoStore) { throw new Error('Crypto account not initialized');