Page MenuHomePhabricator

D12692.diff
No OneTemporary

D12692.diff

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

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)

Event Timeline