Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F32562512
D10245.1767340703.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
11 KB
Referenced Files
None
Subscribers
None
D10245.1767340703.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D10245: Suffix web olm notifs session with cookieID to avoid race condition during multiple simultaneous log-in processes.
Attached
Detach File
Event Timeline
Log In to Comment