Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F3361401
D12692.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
29 KB
Referenced Files
None
Subscribers
None
D12692.diff
View Options
diff --git a/web/crypto/aes-gcm-crypto-utils.js b/web/crypto/aes-gcm-crypto-utils.js
--- a/web/crypto/aes-gcm-crypto-utils.js
+++ b/web/crypto/aes-gcm-crypto-utils.js
@@ -1,5 +1,9 @@
// @flow
+import t, { type TInterface, type TUnion } from 'tcomb';
+
+import { tShape } from 'lib/utils/validation-utils.js';
+
const ENCRYPTION_ALGORITHM = 'AES-GCM';
const ENCRYPTION_KEY_USAGES: $ReadOnlyArray<CryptoKey$Usages> = [
'encrypt',
@@ -11,6 +15,45 @@
+ciphertext: Uint8Array,
};
+export const encryptedAESDataValidator: TInterface<EncryptedData> =
+ tShape<EncryptedData>({
+ iv: t.irreducible('Uint8Array', x => x instanceof Uint8Array),
+ ciphertext: t.irreducible('Uint8Array', x => x instanceof Uint8Array),
+ });
+
+export const cryptoKeyValidator: TInterface<CryptoKey> = tShape<CryptoKey>({
+ algorithm: t.Object,
+ extractable: t.Boolean,
+ type: t.String,
+ usages: t.list(t.String),
+});
+
+export const subtleCrypto$JsonWebKeyValidator: TInterface<SubtleCrypto$JsonWebKey> =
+ tShape({
+ alg: t.maybe(t.String),
+ crv: t.maybe(t.String),
+ d: t.maybe(t.String),
+ dp: t.maybe(t.String),
+ dq: t.maybe(t.String),
+ e: t.maybe(t.String),
+ ext: t.maybe(t.Boolean),
+ k: t.maybe(t.String),
+ key_ops: t.maybe(t.list(t.String)),
+ kty: t.maybe(t.String),
+ n: t.maybe(t.String),
+ oth: t.maybe(t.list(t.Object)),
+ p: t.maybe(t.String),
+ q: t.maybe(t.String),
+ qi: t.maybe(t.String),
+ use: t.maybe(t.String),
+ x: t.maybe(t.String),
+ y: t.maybe(t.String),
+ });
+
+export const extendedCryptoKeyValidator: TUnion<
+ CryptoKey | SubtleCrypto$JsonWebKey,
+> = t.union([cryptoKeyValidator, subtleCrypto$JsonWebKeyValidator]);
+
function generateCryptoKey({
extractable,
}: {
@@ -26,7 +69,7 @@
);
}
-function generateIV(): BufferSource {
+function generateIV(): Uint8Array {
return crypto.getRandomValues(new Uint8Array(12));
}
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
@@ -4,10 +4,12 @@
import type { EncryptResult } from '@commapp/olm';
import invariant from 'invariant';
import localforage from 'localforage';
+import uuid from 'uuid';
import {
olmEncryptedMessageTypes,
type NotificationsOlmDataType,
+ type PickledOLMAccount,
} from 'lib/types/crypto-types.js';
import type {
PlainTextWebNotification,
@@ -15,12 +17,18 @@
} from 'lib/types/notif-types.js';
import { getCookieIDFromCookie } from 'lib/utils/cookie-utils.js';
import { getMessageForException } from 'lib/utils/errors.js';
+import { promiseAll } from 'lib/utils/promises.js';
+import { assertWithValidator } from 'lib/utils/validation-utils.js';
import {
type EncryptedData,
decryptData,
encryptData,
importJWKKey,
+ exportKeyToJWK,
+ generateCryptoKey,
+ encryptedAESDataValidator,
+ extendedCryptoKeyValidator,
} from '../crypto/aes-gcm-crypto-utils.js';
import { initOlm } from '../olm/olm-utils.js';
import {
@@ -40,6 +48,13 @@
+staffCanSee: boolean,
};
+export type NotificationAccountWithPicklingKey = {
+ +notificationAccount: olm.Account,
+ +picklingKey: string,
+ +synchronizationValue: ?string,
+ +accountEncryptionKey?: CryptoKey,
+};
+
type DecryptionResult<T> = {
+newPendingSessionUpdate: string,
+newUpdateCreationTimestamp: number,
@@ -52,6 +67,7 @@
const INDEXED_DB_KEYSERVER_PREFIX = 'keyserver';
const INDEXED_DB_KEY_SEPARATOR = ':';
const INDEXED_DB_DEVICE_PREFIX = 'device';
+const INDEXED_DB_NOTIFS_SYNC_KEY = 'notifsSyncKey';
// This constant is only used to migrate the existing notifications
// session with production keyserver to new IndexedDB key format. This
@@ -63,6 +79,146 @@
'256';
const INDEXED_DB_UNREAD_COUNT_SUFFIX = 'unreadCount';
+const INDEXED_DB_NOTIFS_ACCOUNT_KEY = 'notificationAccount';
+const INDEXED_DB_NOTIFS_ACCOUNT_ENCRYPTION_KEY_DB_LABEL =
+ 'notificationAccountEncryptionKey';
+
+async function deserializeEncryptedData<T>(
+ encryptedData: EncryptedData,
+ encryptionKey: CryptoKey,
+): Promise<T> {
+ const serializedData = await decryptData(encryptedData, encryptionKey);
+ const data: T = JSON.parse(new TextDecoder().decode(serializedData));
+ return data;
+}
+
+async function serializeUnencryptedData<T>(
+ data: T,
+ encryptionKey: CryptoKey,
+): Promise<EncryptedData> {
+ const dataAsString = JSON.stringify(data);
+ invariant(
+ dataAsString,
+ 'Attempt to serialize null or undefined is forbidden',
+ );
+ return await encryptData(
+ new TextEncoder().encode(dataAsString),
+ encryptionKey,
+ );
+}
+
+async function validateCryptoKey(
+ cryptoKey: CryptoKey | SubtleCrypto$JsonWebKey,
+): Promise<CryptoKey> {
+ if (!isDesktopSafari) {
+ return ((cryptoKey: any): CryptoKey);
+ }
+ return await importJWKKey(((cryptoKey: any): SubtleCrypto$JsonWebKey));
+}
+
+async function getCryptoKeyPersistentForm(
+ cryptoKey: CryptoKey,
+): Promise<CryptoKey | SubtleCrypto$JsonWebKey> {
+ if (!isDesktopSafari) {
+ return cryptoKey;
+ }
+
+ // Safari doesn't support structured clone algorithm in service
+ // worker context so we have to store CryptoKey as JSON
+ return await exportKeyToJWK(cryptoKey);
+}
+
+async function persistNotifsAccountWithOlmData(input: {
+ +olmDataKey?: string,
+ +olmEncryptionKeyDBLabel?: string,
+ +olmData?: ?NotificationsOlmDataType,
+ +encryptionKey?: ?CryptoKey,
+ +accountEncryptionKey?: ?CryptoKey,
+ +accountWithPicklingKey?: PickledOLMAccount,
+ +synchronizationValue: ?string,
+ +forceWrite: boolean,
+}): Promise<void> {
+ const {
+ olmData,
+ olmDataKey,
+ accountEncryptionKey,
+ accountWithPicklingKey,
+ encryptionKey,
+ synchronizationValue,
+ olmEncryptionKeyDBLabel,
+ forceWrite,
+ } = input;
+
+ const shouldPersistOlmData =
+ olmDataKey && olmData && (encryptionKey || olmEncryptionKeyDBLabel);
+ const shouldPersistAccount = !!accountWithPicklingKey;
+
+ if (!shouldPersistOlmData && !shouldPersistAccount) {
+ return;
+ }
+
+ const serializationPromises: {
+ [string]: Promise<EncryptedData | CryptoKey | SubtleCrypto$JsonWebKey>,
+ } = {};
+
+ if (olmDataKey && olmData && encryptionKey) {
+ serializationPromises[olmDataKey] =
+ serializeUnencryptedData<NotificationsOlmDataType>(
+ olmData,
+ encryptionKey,
+ );
+ } else if (olmData && olmDataKey && olmEncryptionKeyDBLabel) {
+ const newEncryptionKey = await generateCryptoKey({
+ extractable: isDesktopSafari,
+ });
+
+ serializationPromises[olmDataKey] =
+ serializeUnencryptedData<NotificationsOlmDataType>(
+ olmData,
+ newEncryptionKey,
+ );
+
+ serializationPromises[olmEncryptionKeyDBLabel] =
+ getCryptoKeyPersistentForm(newEncryptionKey);
+ }
+
+ if (accountWithPicklingKey && accountEncryptionKey) {
+ serializationPromises[INDEXED_DB_NOTIFS_ACCOUNT_KEY] =
+ serializeUnencryptedData<PickledOLMAccount>(
+ accountWithPicklingKey,
+ accountEncryptionKey,
+ );
+ } else if (accountWithPicklingKey) {
+ const newEncryptionKey = await generateCryptoKey({
+ extractable: isDesktopSafari,
+ });
+
+ serializationPromises[INDEXED_DB_NOTIFS_ACCOUNT_KEY] =
+ serializeUnencryptedData<PickledOLMAccount>(
+ accountWithPicklingKey,
+ newEncryptionKey,
+ );
+
+ serializationPromises[INDEXED_DB_NOTIFS_ACCOUNT_ENCRYPTION_KEY_DB_LABEL] =
+ getCryptoKeyPersistentForm(newEncryptionKey);
+ }
+
+ const setMultipleItemsInput = await promiseAll(serializationPromises);
+ const newSynchronizationValue = uuid.v4();
+
+ try {
+ await localforage.setMultipleItems(
+ setMultipleItemsInput,
+ INDEXED_DB_NOTIFS_SYNC_KEY,
+ synchronizationValue,
+ newSynchronizationValue,
+ forceWrite,
+ );
+ } catch (e) {
+ // likely shared worker persisted its own data
+ console.log(e);
+ }
+}
async function decryptWebNotification(
encryptedNotification: EncryptedWebNotification,
@@ -319,13 +475,21 @@
const olmEncryptionKeyDBLabel =
getOlmEncryptionKeyDBLabelForDeviceID(deviceID);
- let encryptedOlmData, encryptionKey;
+ let encryptedOlmData, encryptionKey, synchronizationValue;
try {
- [encryptedOlmData, encryptionKey] = await Promise.all([
- localforage.getItem<EncryptedData>(olmDataKey),
- retrieveEncryptionKey(olmEncryptionKeyDBLabel),
- initOlm(),
- ]);
+ const {
+ values: {
+ [olmDataKey]: fetchedEncryptedOlmData,
+ [olmEncryptionKeyDBLabel]: fetchedEncryptionKey,
+ },
+ synchronizationValue: fetchedSynchronizationValue,
+ } = await localforage.getMultipleItems<{
+ +[string]: ?EncryptedData | ?CryptoKey | ?SubtleCrypto$JsonWebKey,
+ }>([olmDataKey, olmEncryptionKeyDBLabel], INDEXED_DB_NOTIFS_SYNC_KEY);
+
+ encryptedOlmData = fetchedEncryptedOlmData;
+ encryptionKey = fetchedEncryptionKey;
+ synchronizationValue = fetchedSynchronizationValue;
} catch (e) {
throw new Error(
`Failed to fetch olm session from IndexedDB for device: ${deviceID}. Details: ${
@@ -338,13 +502,22 @@
throw new Error(`Session with device: ${deviceID} not initialized.`);
}
+ const validatedEncryptedOlmData = assertWithValidator(
+ encryptedOlmData,
+ encryptedAESDataValidator,
+ );
+ const validatedEncryptionKey = await validateCryptoKey(
+ assertWithValidator(encryptionKey, extendedCryptoKeyValidator),
+ );
+
let encryptedNotification;
try {
encryptedNotification = await encryptNotificationWithOlmSession(
payload,
- encryptedOlmData,
+ validatedEncryptedOlmData,
olmDataKey,
- encryptionKey,
+ validatedEncryptionKey,
+ synchronizationValue,
);
} catch (e) {
throw new Error(
@@ -361,6 +534,7 @@
encryptedOlmData: EncryptedData,
olmDataKey: string,
encryptionKey: CryptoKey,
+ synchronizationValue: ?string,
): Promise<EncryptResult> {
const serializedOlmData = await decryptData(encryptedOlmData, encryptionKey);
const {
@@ -388,10 +562,70 @@
encryptionKey,
);
- await localforage.setItem(olmDataKey, updatedEncryptedSession);
+ const newSynchronizationValue = uuid.v4();
+ await localforage.setMultipleItems(
+ { [olmDataKey]: updatedEncryptedSession },
+ INDEXED_DB_NOTIFS_SYNC_KEY,
+ synchronizationValue,
+ newSynchronizationValue,
+ // This method (encryptNotification) is expected to be called
+ // exclusively from the shared worker which must always win race
+ // condition against push notifications service-worker.
+ true,
+ );
+
return encryptedNotification;
}
+// notifications account manipulation
+
+async function getNotifsCryptoAccount(): Promise<NotificationAccountWithPicklingKey> {
+ const {
+ values: {
+ [INDEXED_DB_NOTIFS_ACCOUNT_KEY]: encryptedNotifsAccount,
+ [INDEXED_DB_NOTIFS_ACCOUNT_ENCRYPTION_KEY_DB_LABEL]:
+ notifsAccountEncryptionKey,
+ },
+ synchronizationValue,
+ } = await localforage.getMultipleItems<{
+ +notificationAccount: ?EncryptedData,
+ +notificationAccountEncryptionKey: ?CryptoKey | ?SubtleCrypto$JsonWebKey,
+ }>(
+ [
+ INDEXED_DB_NOTIFS_ACCOUNT_KEY,
+ INDEXED_DB_NOTIFS_ACCOUNT_ENCRYPTION_KEY_DB_LABEL,
+ ],
+ INDEXED_DB_NOTIFS_SYNC_KEY,
+ );
+
+ if (!encryptedNotifsAccount || !notifsAccountEncryptionKey) {
+ throw new Error(
+ 'Attempt to retrieve notifs olm account but account not created.',
+ );
+ }
+
+ const validatedNotifsAccountEncryptionKey = await validateCryptoKey(
+ notifsAccountEncryptionKey,
+ );
+
+ const pickledOLMAccount = await deserializeEncryptedData<PickledOLMAccount>(
+ encryptedNotifsAccount,
+ validatedNotifsAccountEncryptionKey,
+ );
+
+ const { pickledAccount, picklingKey } = pickledOLMAccount;
+
+ const notificationAccount = new olm.Account();
+ notificationAccount.unpickle(picklingKey, pickledAccount);
+
+ return {
+ notificationAccount,
+ picklingKey,
+ synchronizationValue,
+ accountEncryptionKey: validatedNotifsAccountEncryptionKey,
+ };
+}
+
async function retrieveEncryptionKey(
encryptionKeyDBLabel: string,
): Promise<?CryptoKey> {
@@ -408,6 +642,22 @@
return await importJWKKey(persistedCryptoKey);
}
+async function persistEncryptionKey(
+ encryptionKeyDBLabel: string,
+ encryptionKey: CryptoKey,
+): Promise<void> {
+ 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(encryptionKeyDBLabel, cryptoKeyPersistentForm);
+}
+
async function getNotifsOlmSessionDBKeys(keyserverID?: string): Promise<{
+olmDataKey: string,
+encryptionKeyDBKey: string,
@@ -643,4 +893,8 @@
migrateLegacyOlmNotificationsSessions,
updateNotifsUnreadCountStorage,
queryNotifsUnreadCountStorage,
+ getNotifsCryptoAccount,
+ persistEncryptionKey,
+ retrieveEncryptionKey,
+ persistNotifsAccountWithOlmData,
};
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
@@ -44,17 +44,15 @@
getSQLiteQueryExecutor,
getPlatformDetails,
} from './worker-database.js';
-import {
- encryptData,
- exportKeyToJWK,
- generateCryptoKey,
-} from '../../crypto/aes-gcm-crypto-utils.js';
import {
getOlmDataKeyForCookie,
getOlmEncryptionKeyDBLabelForCookie,
getOlmDataKeyForDeviceID,
getOlmEncryptionKeyDBLabelForDeviceID,
encryptNotification,
+ type NotificationAccountWithPicklingKey,
+ getNotifsCryptoAccount,
+ persistNotifsAccountWithOlmData,
} from '../../push-notif/notif-crypto-utils.js';
import {
type WorkerRequestMessage,
@@ -64,7 +62,6 @@
type LegacyCryptoStore,
} from '../../types/worker-types.js';
import type { OlmPersistSession } from '../types/sqlite-query-executor.js';
-import { isDesktopSafari } from '../utils/db-utils.js';
type OlmSession = { +session: olm.Session, +version: number };
type OlmSessions = {
@@ -75,8 +72,6 @@
+contentAccountPickleKey: string,
+contentAccount: olm.Account,
+contentSessions: OlmSessions,
- +notificationAccountPickleKey: string,
- +notificationAccount: olm.Account,
};
let cryptoStore: ?WorkerCryptoStore = null;
@@ -86,7 +81,10 @@
cryptoStore = null;
}
-function persistCryptoStore(withoutTransaction: boolean = false) {
+async function persistCryptoStore(
+ notifsCryptoAccount?: NotificationAccountWithPicklingKey,
+ withoutTransaction: boolean = false,
+) {
const sqliteQueryExecutor = getSQLiteQueryExecutor();
const dbModule = getDBModule();
if (!sqliteQueryExecutor || !dbModule) {
@@ -98,13 +96,8 @@
throw new Error("Couldn't persist crypto store because it doesn't exist");
}
- const {
- contentAccountPickleKey,
- contentAccount,
- contentSessions,
- notificationAccountPickleKey,
- notificationAccount,
- } = cryptoStore;
+ const { contentAccountPickleKey, contentAccount, contentSessions } =
+ cryptoStore;
const pickledContentAccount: PickledOLMAccount = {
picklingKey: contentAccountPickleKey,
@@ -119,11 +112,6 @@
version: sessionData.version,
}));
- const pickledNotificationAccount: PickledOLMAccount = {
- picklingKey: notificationAccountPickleKey,
- pickledAccount: notificationAccount.pickle(notificationAccountPickleKey),
- };
-
try {
if (!withoutTransaction) {
sqliteQueryExecutor.beginTransaction();
@@ -135,10 +123,27 @@
for (const pickledSession of pickledContentSessions) {
sqliteQueryExecutor.storeOlmPersistSession(pickledSession);
}
- sqliteQueryExecutor.storeOlmPersistAccount(
- sqliteQueryExecutor.getNotifsAccountID(),
- JSON.stringify(pickledNotificationAccount),
- );
+ if (notifsCryptoAccount) {
+ const {
+ notificationAccount,
+ picklingKey,
+ synchronizationValue,
+ accountEncryptionKey,
+ } = notifsCryptoAccount;
+
+ const pickledAccount = notificationAccount.pickle(picklingKey);
+ const accountWithPicklingKey: PickledOLMAccount = {
+ pickledAccount,
+ picklingKey,
+ };
+
+ await persistNotifsAccountWithOlmData({
+ accountEncryptionKey,
+ accountWithPicklingKey,
+ synchronizationValue,
+ forceWrite: true,
+ });
+ }
if (!withoutTransaction) {
sqliteQueryExecutor.commitTransaction();
}
@@ -160,10 +165,13 @@
throw new Error('Crypto account not initialized');
}
- const { notificationAccountPickleKey, notificationAccount } = cryptoStore;
- const encryptionKey = await generateCryptoKey({
- extractable: isDesktopSafari,
- });
+ const notificationAccountWithPicklingKey = await getNotifsCryptoAccount();
+ const {
+ notificationAccount,
+ picklingKey,
+ synchronizationValue,
+ accountEncryptionKey,
+ } = notificationAccountWithPicklingKey;
const notificationsPrekey = notificationsInitializationInfo.prekey;
const session = new olm.Session();
@@ -189,46 +197,40 @@
JSON.stringify(initialEncryptedMessageContent),
);
- const mainSession = session.pickle(notificationAccountPickleKey);
+ const mainSession = session.pickle(
+ notificationAccountWithPicklingKey.picklingKey,
+ );
const notificationsOlmData: NotificationsOlmDataType = {
mainSession,
pendingSessionUpdate: mainSession,
updateCreationTimestamp: Date.now(),
- picklingKey: notificationAccountPickleKey,
+ picklingKey,
};
- const encryptedOlmData = await encryptData(
- new TextEncoder().encode(JSON.stringify(notificationsOlmData)),
- encryptionKey,
- );
-
- 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(
- dataEncryptionKeyDBLabel,
- cryptoKeyPersistentForm,
- );
- })();
+ const pickledAccount = notificationAccount.pickle(picklingKey);
+ const accountWithPicklingKey: PickledOLMAccount = {
+ pickledAccount,
+ picklingKey,
+ };
- await Promise.all([
- localforage.setItem(dataPersistenceKey, encryptedOlmData),
- persistEncryptionKeyPromise,
- ]);
+ await persistNotifsAccountWithOlmData({
+ accountEncryptionKey,
+ accountWithPicklingKey,
+ olmDataKey: dataPersistenceKey,
+ olmData: notificationsOlmData,
+ olmEncryptionKeyDBLabel: dataEncryptionKeyDBLabel,
+ synchronizationValue,
+ forceWrite: true,
+ });
return { message, messageType };
}
-function getOrCreateOlmAccount(accountIDInDB: number): {
+async function getOrCreateOlmAccount(accountIDInDB: number): Promise<{
+picklingKey: string,
+account: olm.Account,
-} {
+ +synchronizationValue?: ?string,
+}> {
const sqliteQueryExecutor = getSQLiteQueryExecutor();
const dbModule = getDBModule();
if (!sqliteQueryExecutor || !dbModule) {
@@ -246,6 +248,31 @@
throw new Error(getProcessingStoreOpsExceptionMessage(err, dbModule));
}
+ const maybeNotifsCryptoAccount: ?NotificationAccountWithPicklingKey =
+ await (async () => {
+ if (accountIDInDB !== sqliteQueryExecutor.getNotifsAccountID()) {
+ return undefined;
+ }
+ try {
+ return await getNotifsCryptoAccount();
+ } catch (e) {
+ return undefined;
+ }
+ })();
+
+ if (maybeNotifsCryptoAccount) {
+ const {
+ notificationAccount,
+ picklingKey: notificationAccountPicklingKey,
+ synchronizationValue,
+ } = maybeNotifsCryptoAccount;
+ return {
+ account: notificationAccount,
+ picklingKey: notificationAccountPicklingKey,
+ synchronizationValue,
+ };
+ }
+
if (accountDBString.isNull) {
picklingKey = uuid.v4();
account.create();
@@ -255,6 +282,10 @@
account.unpickle(picklingKey, dbAccount.pickledAccount);
}
+ if (accountIDInDB === sqliteQueryExecutor.getNotifsAccountID()) {
+ return { picklingKey, account, synchronizationValue: uuid.v4() };
+ }
+
return { picklingKey, account };
}
@@ -316,13 +347,15 @@
initialCryptoStore.primaryAccount,
),
contentSessions: {},
- notificationAccountPickleKey:
- initialCryptoStore.notificationAccount.picklingKey,
+ };
+ const notifsCryptoAccount = {
+ picklingKey: initialCryptoStore.notificationAccount.picklingKey,
notificationAccount: unpickleInitialCryptoStoreAccount(
initialCryptoStore.notificationAccount,
),
+ synchronizationValue: uuid.v4(),
};
- persistCryptoStore();
+ await persistCryptoStore(notifsCryptoAccount);
return;
}
@@ -352,12 +385,13 @@
return undefined;
}
-function getSignedIdentityKeysBlob(): SignedIdentityKeysBlob {
+async function getSignedIdentityKeysBlob(): Promise<SignedIdentityKeysBlob> {
if (!cryptoStore) {
throw new Error('Crypto account not initialized');
}
- const { contentAccount, notificationAccount } = cryptoStore;
+ const { contentAccount } = cryptoStore;
+ const { notificationAccount } = await getNotifsCryptoAccount();
const identityKeysBlob: IdentityKeysBlob = {
notificationIdentityPublicKeys: JSON.parse(
@@ -375,22 +409,25 @@
return signedIdentityKeysBlob;
}
-function getNewDeviceKeyUpload(): IdentityNewDeviceKeyUpload {
+async function getNewDeviceKeyUpload(): Promise<IdentityNewDeviceKeyUpload> {
if (!cryptoStore) {
throw new Error('Crypto account not initialized');
}
- const { contentAccount, notificationAccount } = cryptoStore;
-
- const signedIdentityKeysBlob = getSignedIdentityKeysBlob();
+ const { contentAccount } = cryptoStore;
+ const [notifsCryptoAccount, signedIdentityKeysBlob] = await Promise.all([
+ getNotifsCryptoAccount(),
+ getSignedIdentityKeysBlob(),
+ ]);
const primaryAccountKeysSet = retrieveAccountKeysSet(contentAccount);
- const notificationAccountKeysSet =
- retrieveAccountKeysSet(notificationAccount);
+ const notificationAccountKeysSet = retrieveAccountKeysSet(
+ notifsCryptoAccount.notificationAccount,
+ );
contentAccount.mark_keys_as_published();
- notificationAccount.mark_keys_as_published();
+ notifsCryptoAccount.notificationAccount.mark_keys_as_published();
- persistCryptoStore();
+ await persistCryptoStore(notifsCryptoAccount);
return {
keyPayload: signedIdentityKeysBlob.payload,
@@ -404,20 +441,22 @@
};
}
-function getExistingDeviceKeyUpload(): IdentityExistingDeviceKeyUpload {
+async function getExistingDeviceKeyUpload(): Promise<IdentityExistingDeviceKeyUpload> {
if (!cryptoStore) {
throw new Error('Crypto account not initialized');
}
- const { contentAccount, notificationAccount } = cryptoStore;
-
- const signedIdentityKeysBlob = getSignedIdentityKeysBlob();
+ const { contentAccount } = cryptoStore;
+ const [notifsCryptoAccount, signedIdentityKeysBlob] = await Promise.all([
+ getNotifsCryptoAccount(),
+ getSignedIdentityKeysBlob(),
+ ]);
const { prekey: contentPrekey, prekeySignature: contentPrekeySignature } =
retrieveIdentityKeysAndPrekeys(contentAccount);
const { prekey: notifPrekey, prekeySignature: notifPrekeySignature } =
- retrieveIdentityKeysAndPrekeys(notificationAccount);
+ retrieveIdentityKeysAndPrekeys(notifsCryptoAccount.notificationAccount);
- persistCryptoStore();
+ await persistCryptoStore(notifsCryptoAccount);
return {
keyPayload: signedIdentityKeysBlob.payload,
@@ -470,30 +509,36 @@
throw new Error('Database not initialized');
}
- const contentAccountResult = getOrCreateOlmAccount(
- sqliteQueryExecutor.getContentAccountID(),
- );
- const notificationAccountResult = getOrCreateOlmAccount(
- sqliteQueryExecutor.getNotifsAccountID(),
+ const [contentAccountResult, notificationAccountResult] = await Promise.all(
+ [
+ getOrCreateOlmAccount(sqliteQueryExecutor.getContentAccountID()),
+ getOrCreateOlmAccount(sqliteQueryExecutor.getNotifsAccountID()),
+ ],
);
+
const contentSessions = getOlmSessions(contentAccountResult.picklingKey);
cryptoStore = {
contentAccountPickleKey: contentAccountResult.picklingKey,
contentAccount: contentAccountResult.account,
contentSessions,
- notificationAccountPickleKey: notificationAccountResult.picklingKey,
+ };
+ const notifsCryptoAccount = {
+ picklingKey: notificationAccountResult.picklingKey,
notificationAccount: notificationAccountResult.account,
+ synchronizationValue: notificationAccountResult.synchronizationValue,
};
- persistCryptoStore();
+ await persistCryptoStore(notifsCryptoAccount);
},
async getUserPublicKey(): Promise<ClientPublicKeys> {
if (!cryptoStore) {
throw new Error('Crypto account not initialized');
}
- const { contentAccount, notificationAccount } = cryptoStore;
- const { payload, signature } = getSignedIdentityKeysBlob();
+ const { contentAccount } = cryptoStore;
+ const [{ notificationAccount }, { payload, signature }] = await Promise.all(
+ [getNotifsCryptoAccount(), getSignedIdentityKeysBlob()],
+ );
return {
primaryIdentityPublicKeys: JSON.parse(contentAccount.identity_keys()),
@@ -514,7 +559,7 @@
}
const encryptedContent = olmSession.session.encrypt(content);
- persistCryptoStore();
+ await persistCryptoStore();
return {
message: encryptedContent.body,
@@ -556,7 +601,7 @@
deviceID,
JSON.stringify(result),
);
- persistCryptoStore(true);
+ await persistCryptoStore(undefined, true);
sqliteQueryExecutor.commitTransaction();
} catch (e) {
sqliteQueryExecutor.rollbackTransaction();
@@ -593,7 +638,7 @@
encryptedData.message,
);
- persistCryptoStore();
+ await persistCryptoStore();
return result;
},
@@ -634,7 +679,7 @@
sqliteQueryExecutor.beginTransaction();
try {
sqliteQueryExecutor.addInboundP2PMessage(receivedMessage);
- persistCryptoStore(true);
+ await persistCryptoStore(undefined, true);
sqliteQueryExecutor.commitTransaction();
} catch (e) {
sqliteQueryExecutor.rollbackTransaction();
@@ -680,7 +725,7 @@
session,
version: sessionVersion,
};
- persistCryptoStore();
+ await persistCryptoStore();
return initialEncryptedMessage;
},
@@ -722,7 +767,7 @@
session,
version: newSessionVersion,
};
- persistCryptoStore();
+ await persistCryptoStore();
const encryptedData: EncryptedData = {
message: initialEncryptedData.body,
@@ -844,7 +889,8 @@
if (!cryptoStore) {
throw new Error('Crypto account not initialized');
}
- const { contentAccount, notificationAccount } = cryptoStore;
+ const { contentAccount } = cryptoStore;
+ const notifsCryptoAccount = await getNotifsCryptoAccount();
const contentOneTimeKeys = getAccountOneTimeKeys(
contentAccount,
@@ -853,12 +899,12 @@
contentAccount.mark_keys_as_published();
const notificationsOneTimeKeys = getAccountOneTimeKeys(
- notificationAccount,
+ notifsCryptoAccount.notificationAccount,
numberOfKeys,
);
- notificationAccount.mark_keys_as_published();
+ notifsCryptoAccount.notificationAccount.mark_keys_as_published();
- persistCryptoStore();
+ await persistCryptoStore(notifsCryptoAccount);
return { contentOneTimeKeys, notificationsOneTimeKeys };
},
@@ -876,26 +922,27 @@
if (!cryptoStore) {
throw new Error('Crypto account not initialized');
}
- const { contentAccount, notificationAccount } = cryptoStore;
+ const { contentAccount } = cryptoStore;
+ const notifsCryptoAccount = await getNotifsCryptoAccount();
// Content and notification accounts' keys are always rotated at the same
// time so we only need to check one of them.
if (shouldRotatePrekey(contentAccount)) {
contentAccount.generate_prekey();
- notificationAccount.generate_prekey();
+ notifsCryptoAccount.notificationAccount.generate_prekey();
}
if (shouldForgetPrekey(contentAccount)) {
contentAccount.forget_old_prekey();
- notificationAccount.forget_old_prekey();
+ notifsCryptoAccount.notificationAccount.forget_old_prekey();
}
- persistCryptoStore();
+ await persistCryptoStore(notifsCryptoAccount);
if (!contentAccount.unpublished_prekey()) {
return;
}
const { prekey: notifPrekey, prekeySignature: notifPrekeySignature } =
- getAccountPrekeysSet(notificationAccount);
+ getAccountPrekeysSet(notifsCryptoAccount.notificationAccount);
const { prekey: contentPrekey, prekeySignature: contentPrekeySignature } =
getAccountPrekeysSet(contentAccount);
@@ -910,9 +957,9 @@
notifPrekeySignature,
});
contentAccount.mark_prekey_as_published();
- notificationAccount.mark_prekey_as_published();
+ notifsCryptoAccount.notificationAccount.mark_prekey_as_published();
- persistCryptoStore();
+ await persistCryptoStore(notifsCryptoAccount);
},
async signMessage(message: string): Promise<string> {
if (!cryptoStore) {
@@ -945,12 +992,13 @@
if (!cryptoStore) {
throw new Error('Crypto account not initialized');
}
- const { contentAccount, notificationAccount } = cryptoStore;
+ const { contentAccount } = cryptoStore;
+ const notifsCryptoAccount = await getNotifsCryptoAccount();
contentAccount.mark_prekey_as_published();
- notificationAccount.mark_prekey_as_published();
+ notifsCryptoAccount.notificationAccount.mark_prekey_as_published();
- persistCryptoStore();
+ await persistCryptoStore(notifsCryptoAccount);
},
};
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Mon, Nov 25, 5:09 PM (21 h, 14 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2580502
Default Alt Text
D12692.diff (29 KB)
Attached To
Mode
D12692: Migrate notification account to IndexedDB on web
Attached
Detach File
Event Timeline
Log In to Comment