Page MenuHomePhorge

D10245.1767340703.diff
No OneTemporary

Size
11 KB
Referenced Files
None
Subscribers
None

D10245.1767340703.diff

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
@@ -39,6 +39,7 @@
export type NotificationsSessionCreatorContextType = {
+notificationsSessionCreator: (
+ cookie: ?string,
notificationsIdentityKeys: OLMIdentityKeys,
notificationsInitializationInfo: OlmSessionInitializationInfo,
) => Promise<string>,
diff --git a/lib/utils/cookie-utils.js b/lib/utils/cookie-utils.js
--- a/lib/utils/cookie-utils.js
+++ b/lib/utils/cookie-utils.js
@@ -11,4 +11,10 @@
return cookies;
}
-export { parseCookies };
+function getCookieIDFromCookie(cookie: string): string {
+ const cookieString = cookie.split('=').pop();
+ const [cookieID] = cookieString.split(':');
+ return cookieID;
+}
+
+export { parseCookies, getCookieIDFromCookie };
diff --git a/web/account/account-hooks.js b/web/account/account-hooks.js
--- a/web/account/account-hooks.js
+++ b/web/account/account-hooks.js
@@ -28,12 +28,12 @@
encryptData,
exportKeyToJWK,
} from '../crypto/aes-gcm-crypto-utils.js';
-import {
- NOTIFICATIONS_OLM_DATA_CONTENT,
- NOTIFICATIONS_OLM_DATA_ENCRYPTION_KEY,
-} from '../database/utils/constants.js';
import { isDesktopSafari } from '../database/utils/db-utils.js';
import { initOlm } from '../olm/olm-utils.js';
+import {
+ getOlmDataContentKeyForCookie,
+ getOlmEncryptionKeyDBLabelForCookie,
+} from '../push-notif/notif-crypto-utils.js';
import { setCryptoStore } from '../redux/crypto-store-reducer.js';
import { useSelector } from '../redux/redux-utils.js';
@@ -183,6 +183,7 @@
const createNewNotificationsSession = React.useCallback(
async (
+ cookie: ?string,
notificationsIdentityKeys: OLMIdentityKeys,
notificationsInitializationInfo: OlmSessionInitializationInfo,
) => {
@@ -228,6 +229,10 @@
encryptionKey,
);
+ const notifsOlmDataEncryptionKeyDBLabel =
+ getOlmEncryptionKeyDBLabelForCookie(cookie);
+ const notifsOlmDataContentKey = getOlmDataContentKeyForCookie(cookie);
+
const persistEncryptionKeyPromise = (async () => {
let cryptoKeyPersistentForm;
if (isDesktopSafari) {
@@ -239,13 +244,13 @@
}
await localforage.setItem(
- NOTIFICATIONS_OLM_DATA_ENCRYPTION_KEY,
+ notifsOlmDataEncryptionKeyDBLabel,
cryptoKeyPersistentForm,
);
})();
await Promise.all([
- localforage.setItem(NOTIFICATIONS_OLM_DATA_CONTENT, encryptedOlmData),
+ localforage.setItem(notifsOlmDataContentKey, encryptedOlmData),
persistEncryptionKeyPromise,
]);
@@ -257,6 +262,7 @@
const notificationsSessionPromise = React.useRef<?Promise<string>>(null);
const createNotificationsSession = React.useCallback(
async (
+ cookie: ?string,
notificationsIdentityKeys: OLMIdentityKeys,
notificationsInitializationInfo: OlmSessionInitializationInfo,
) => {
@@ -267,6 +273,7 @@
const newNotificationsSessionPromise = (async () => {
try {
return await createNewNotificationsSession(
+ cookie,
notificationsIdentityKeys,
notificationsInitializationInfo,
);
@@ -304,6 +311,7 @@
}
function useWebNotificationsSessionCreator(): (
+ cookie: ?string,
notificationsIdentityKeys: OLMIdentityKeys,
notificationsInitializationInfo: OlmSessionInitializationInfo,
) => Promise<string> {
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
@@ -11,6 +11,7 @@
PlainTextWebNotification,
EncryptedWebNotification,
} from 'lib/types/notif-types.js';
+import { getCookieIDFromCookie } from 'lib/utils/cookie-utils.js';
import {
type EncryptedData,
@@ -50,20 +51,31 @@
encryptedNotification: EncryptedWebNotification,
): Promise<PlainTextWebNotification | WebNotifDecryptionError> {
const { id, encryptedPayload } = encryptedNotification;
-
- const [encryptedOlmData, encryptionKey, utilsData] = await Promise.all([
- localforage.getItem<EncryptedData>(NOTIFICATIONS_OLM_DATA_CONTENT),
- retrieveEncryptionKey(),
- localforage.getItem<WebNotifsServiceUtilsData>(
- WEB_NOTIFS_SERVICE_UTILS_KEY,
- ),
- ]);
+ const utilsData = await localforage.getItem<WebNotifsServiceUtilsData>(
+ WEB_NOTIFS_SERVICE_UTILS_KEY,
+ );
if (!utilsData) {
return { id, error: 'Necessary data not found in IndexedDB' };
}
-
const { olmWasmPath, staffCanSee } = (utilsData: WebNotifsServiceUtilsData);
+
+ let olmDBKeys;
+ try {
+ olmDBKeys = await getNotifsOlmSessionDBKeys();
+ } catch (e) {
+ return {
+ id,
+ error: e.message,
+ displayErrorMessage: staffCanSee,
+ };
+ }
+ const { olmDataContentKey, encryptionKeyDBKey } = olmDBKeys;
+ const [encryptedOlmData, encryptionKey] = await Promise.all([
+ localforage.getItem<EncryptedData>(olmDataContentKey),
+ retrieveEncryptionKey(encryptionKeyDBKey),
+ ]);
+
if (!encryptionKey || !encryptedOlmData) {
return {
id,
@@ -77,6 +89,7 @@
const decryptedNotification = await commonDecrypt<PlainTextWebNotification>(
encryptedOlmData,
+ olmDataContentKey,
encryptionKey,
encryptedPayload,
);
@@ -95,11 +108,16 @@
encryptedPayload: string,
staffCanSee: boolean,
): Promise<{ +[string]: mixed }> {
- let encryptedOlmData, encryptionKey;
+ let encryptedOlmData, encryptionKey, olmDataContentKey;
try {
+ const { olmDataContentKey: olmDataContentKeyValue, encryptionKeyDBKey } =
+ await getNotifsOlmSessionDBKeys();
+
+ olmDataContentKey = olmDataContentKeyValue;
+
[encryptedOlmData, encryptionKey] = await Promise.all([
- localforage.getItem<EncryptedData>(NOTIFICATIONS_OLM_DATA_CONTENT),
- retrieveEncryptionKey(),
+ localforage.getItem<EncryptedData>(olmDataContentKey),
+ retrieveEncryptionKey(encryptionKeyDBKey),
initOlm(),
]);
} catch (e) {
@@ -119,6 +137,7 @@
try {
return await commonDecrypt(
encryptedOlmData,
+ olmDataContentKey,
encryptionKey,
encryptedPayload,
);
@@ -132,6 +151,7 @@
async function commonDecrypt<T>(
encryptedOlmData: EncryptedData,
+ olmDataContentKey: string,
encryptionKey: CryptoKey,
encryptedPayload: string,
): Promise<T> {
@@ -191,10 +211,7 @@
encryptionKey,
);
- await localforage.setItem(
- NOTIFICATIONS_OLM_DATA_CONTENT,
- updatedEncryptedSession,
- );
+ await localforage.setItem(olmDataContentKey, updatedEncryptedSession);
return decryptedNotification;
}
@@ -246,16 +263,16 @@
}
}
-async function retrieveEncryptionKey(): Promise<?CryptoKey> {
+async function retrieveEncryptionKey(
+ encryptionKeyDBLabel: string,
+): Promise<?CryptoKey> {
if (!isDesktopSafari) {
- return await localforage.getItem<CryptoKey>(
- NOTIFICATIONS_OLM_DATA_ENCRYPTION_KEY,
- );
+ return await localforage.getItem<CryptoKey>(encryptionKeyDBLabel);
}
// Safari doesn't support structured clone algorithm in service
// worker context so we have to store CryptoKey as JSON
const persistedCryptoKey = await localforage.getItem<SubtleCrypto$JsonWebKey>(
- NOTIFICATIONS_OLM_DATA_ENCRYPTION_KEY,
+ encryptionKeyDBLabel,
);
if (!persistedCryptoKey) {
return null;
@@ -263,4 +280,96 @@
return await importJWKKey(persistedCryptoKey);
}
-export { decryptWebNotification, decryptDesktopNotification };
+async function getNotifsOlmSessionDBKeys(): Promise<{
+ +olmDataContentKey: string,
+ +encryptionKeyDBKey: string,
+}> {
+ const dbKeys = await localforage.keys();
+ const olmDataContentKeys = sortOlmDBKeysArray(
+ dbKeys.filter(key => key.startsWith(NOTIFICATIONS_OLM_DATA_CONTENT)),
+ );
+ const encryptionKeyDBLabels = sortOlmDBKeysArray(
+ dbKeys.filter(key => key.startsWith(NOTIFICATIONS_OLM_DATA_ENCRYPTION_KEY)),
+ );
+
+ if (olmDataContentKeys.length === 0 || encryptionKeyDBLabels.length === 0) {
+ throw new Error(
+ 'Received encrypted notification but olm session was not created',
+ );
+ }
+
+ const latestDataContentKey =
+ olmDataContentKeys[olmDataContentKeys.length - 1];
+ const latestEncryptionKeyDBKey =
+ encryptionKeyDBLabels[encryptionKeyDBLabels.length - 1];
+
+ const latestDataContentCookieID =
+ getCookieIDFromOlmDBKey(latestDataContentKey);
+ const latestEncryptionKeyCookieID = getCookieIDFromOlmDBKey(
+ latestEncryptionKeyDBKey,
+ );
+
+ if (latestDataContentCookieID !== latestEncryptionKeyCookieID) {
+ throw new Error(
+ 'Olm sessions and their encryption keys out of sync. Latest cookie ' +
+ `id for olm sessions ${latestDataContentCookieID}. Latest cookie ` +
+ `id for olm session encryption keys ${latestEncryptionKeyCookieID}`,
+ );
+ }
+
+ const olmDBKeys = {
+ olmDataContentKey: latestDataContentKey,
+ encryptionKeyDBKey: latestEncryptionKeyDBKey,
+ };
+
+ const keysToDelete: $ReadOnlyArray<string> = [
+ ...olmDataContentKeys.slice(0, olmDataContentKeys.length - 1),
+ ...encryptionKeyDBLabels.slice(0, encryptionKeyDBLabels.length - 1),
+ ];
+
+ await Promise.all(keysToDelete.map(key => localforage.removeItem(key)));
+ return olmDBKeys;
+}
+
+function getOlmDataContentKeyForCookie(cookie: ?string): string {
+ if (!cookie) {
+ return NOTIFICATIONS_OLM_DATA_CONTENT;
+ }
+ const cookieID = getCookieIDFromCookie(cookie);
+ return `${NOTIFICATIONS_OLM_DATA_CONTENT}:${cookieID}`;
+}
+
+function getOlmEncryptionKeyDBLabelForCookie(cookie: ?string): string {
+ if (!cookie) {
+ return NOTIFICATIONS_OLM_DATA_ENCRYPTION_KEY;
+ }
+ const cookieID = getCookieIDFromCookie(cookie);
+ return `${NOTIFICATIONS_OLM_DATA_ENCRYPTION_KEY}:${cookieID}`;
+}
+
+function getCookieIDFromOlmDBKey(olmDBKey: string): string | '0' {
+ const cookieID = olmDBKey.split(':')[1];
+ return cookieID ?? '0';
+}
+
+function sortOlmDBKeysArray(
+ olmDBKeysArray: $ReadOnlyArray<string>,
+): $ReadOnlyArray<string> {
+ return olmDBKeysArray
+ .map(key => ({
+ cookieID: Number(getCookieIDFromOlmDBKey(key)),
+ key,
+ }))
+ .sort(
+ ({ cookieID: cookieID1 }, { cookieID: cookieID2 }) =>
+ cookieID1 - cookieID2,
+ )
+ .map(({ key }) => key);
+}
+
+export {
+ decryptWebNotification,
+ decryptDesktopNotification,
+ getOlmDataContentKeyForCookie,
+ getOlmEncryptionKeyDBLabelForCookie,
+};
diff --git a/web/socket.react.js b/web/socket.react.js
--- a/web/socket.react.js
+++ b/web/socket.react.js
@@ -13,6 +13,8 @@
} from 'lib/selectors/keyserver-selectors.js';
import { useInitialNotificationsEncryptedMessage } from 'lib/shared/crypto-utils.js';
import Socket, { type BaseSocketProps } from 'lib/socket/socket.react.js';
+import type { OLMIdentityKeys } from 'lib/types/crypto-types.js';
+import type { OlmSessionInitializationInfo } from 'lib/types/request-types.js';
import { useDispatchActionPromise } from 'lib/utils/action-utils.js';
import { useDispatch } from 'lib/utils/redux-utils.js';
import { ashoatKeyserverID } from 'lib/utils/validation-utils.js';
@@ -58,8 +60,20 @@
);
const getSignedIdentityKeysBlob = useGetSignedIdentityKeysBlob();
const webNotificationsSessionCreator = useWebNotificationsSessionCreator();
+ const webNotifsSessionCreatorForCookie = React.useCallback(
+ async (
+ notificationsIdentityKeys: OLMIdentityKeys,
+ notificationsInitializationInfo: OlmSessionInitializationInfo,
+ ) =>
+ webNotificationsSessionCreator(
+ cookie,
+ notificationsIdentityKeys,
+ notificationsInitializationInfo,
+ ),
+ [webNotificationsSessionCreator, cookie],
+ );
const getInitialNotificationsEncryptedMessage =
- useInitialNotificationsEncryptedMessage(webNotificationsSessionCreator);
+ useInitialNotificationsEncryptedMessage(webNotifsSessionCreatorForCookie);
const getClientResponses = useSelector(state =>
webGetClientResponsesSelector({
state,

File Metadata

Mime Type
text/plain
Expires
Fri, Jan 2, 7:58 AM (4 h, 11 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5878025
Default Alt Text
D10245.1767340703.diff (11 KB)

Event Timeline