Page MenuHomePhorge

D12838.1765222044.diff
No OneTemporary

Size
25 KB
Referenced Files
None
Subscribers
None

D12838.1765222044.diff

diff --git a/desktop/src/main.js b/desktop/src/main.js
--- a/desktop/src/main.js
+++ b/desktop/src/main.js
@@ -307,11 +307,13 @@
const handleEncryptedNotification = (
encryptedPayload: string,
keyserverID: string,
+ type: string,
) => {
if (mainWindow) {
mainWindow.webContents.send('on-encrypted-notification', {
encryptedPayload,
keyserverID,
+ type,
});
}
};
diff --git a/desktop/src/push-notifications.js b/desktop/src/push-notifications.js
--- a/desktop/src/push-notifications.js
+++ b/desktop/src/push-notifications.js
@@ -132,28 +132,31 @@
handleEncryptedNotification: (
encryptedPayload: string,
keyserverID: string,
+ type: string,
) => void,
) {
if (process.platform === 'darwin') {
pushNotifications.on('received-apns-notification', (event, userInfo) => {
- const { keyserverID, encryptedPayload } = userInfo;
+ const { keyserverID, encryptedPayload, type } = userInfo;
if (
typeof keyserverID === 'string' &&
- typeof encryptedPayload === 'string'
+ typeof encryptedPayload === 'string' &&
+ typeof type === 'string'
) {
- handleEncryptedNotification(encryptedPayload, keyserverID);
+ handleEncryptedNotification(encryptedPayload, keyserverID, type);
return;
}
showNewNotification(userInfo, handleClick);
});
} else if (process.platform === 'win32') {
windowsPushNotifEventEmitter.on('received-wns-notification', payload => {
- const { keyserverID, encryptedPayload } = payload;
+ const { keyserverID, encryptedPayload, type } = payload;
if (
typeof keyserverID === 'string' &&
- typeof encryptedPayload === 'string'
+ typeof encryptedPayload === 'string' &&
+ typeof type === 'string'
) {
- handleEncryptedNotification(encryptedPayload, keyserverID);
+ handleEncryptedNotification(encryptedPayload, keyserverID, type);
return;
}
showNewNotification(payload, handleClick);
diff --git a/lib/types/electron-types.js b/lib/types/electron-types.js
--- a/lib/types/electron-types.js
+++ b/lib/types/electron-types.js
@@ -13,7 +13,8 @@
type OnEncryptedNotificationListener = (data: {
encryptedPayload: string,
- keyserverID?: string,
+ type: string,
+ keyserverID: string,
}) => mixed;
export type ElectronBridge = {
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
@@ -14,10 +14,16 @@
import type {
PlainTextWebNotification,
EncryptedWebNotification,
+ SenderDeviceDescriptor,
} 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 {
+ fetchAuthMetadata,
+ getNotifsInboundKeysForDeviceID,
+} from './services-client.js';
import {
type EncryptedData,
decryptData,
@@ -130,11 +136,199 @@
return await exportKeyToJWK(cryptoKey);
}
+async function getOlmDataForKeyserverSession(keyserverID?: string): Promise<{
+ +encryptionKey: ?CryptoKey,
+ +encryptedOlmData: ?EncryptedData,
+ +olmDataKey: string,
+ +encryptionKeyDBLabel: string,
+}> {
+ const olmDBKeys = await getNotifsOlmSessionDBKeys(keyserverID);
+ const { olmDataKey, encryptionKeyDBKey } = olmDBKeys;
+ const [encryptedOlmData, encryptionKey] = await Promise.all([
+ localforage.getItem<EncryptedData>(olmDataKey),
+ retrieveEncryptionKey(encryptionKeyDBKey),
+ ]);
+ return {
+ encryptedOlmData,
+ encryptionKey,
+ olmDataKey,
+ encryptionKeyDBLabel: encryptionKeyDBKey,
+ };
+}
+
+async function getOlmDataForSessionWithDevice(senderDeviceID: string): Promise<{
+ +encryptedOlmData: ?EncryptedData,
+ +encryptionKey: ?CryptoKey,
+ +olmDataKey: string,
+ +encryptionKeyDBLabel: string,
+ +encryptedOlmAccount: ?EncryptedData,
+ +accountEncryptionKey: ?CryptoKey,
+ +synchronizationValue: ?string,
+}> {
+ const olmDataKey = getOlmDataKeyForDeviceID(senderDeviceID);
+ const olmDataEncryptionKeyDBLabel =
+ getOlmEncryptionKeyDBLabelForDeviceID(senderDeviceID);
+
+ const queryResult = await localforage.getMultipleItems<{
+ notificationAccount: ?EncryptedData,
+ notificationAccountEncryptionKey: ?CryptoKey,
+ synchronizationValue: ?number,
+ [string]: ?CryptoKey | ?EncryptedData,
+ }>(
+ [
+ INDEXED_DB_NOTIFS_ACCOUNT_KEY,
+ INDEXED_DB_NOTIFS_ACCOUNT_ENCRYPTION_KEY_DB_LABEL,
+ olmDataEncryptionKeyDBLabel,
+ olmDataKey,
+ ],
+ INDEXED_DB_NOTIFS_SYNC_KEY,
+ );
+
+ const {
+ notificationAccount,
+ notificationAccountEncryptionKey,
+ synchronizationValue,
+ } = queryResult;
+
+ if (!notificationAccount || !notificationAccountEncryptionKey) {
+ throw new Error(
+ 'Attempt to decrypt notification but olm account not initialized.',
+ );
+ }
+
+ const olmData = queryResult[olmDataKey];
+ const olmDataEncryptionKey = queryResult[olmDataEncryptionKeyDBLabel];
+
+ // type refinement
+ if (
+ (olmData && !olmData.ciphertext) ||
+ (olmDataEncryptionKey && !olmDataEncryptionKey.algorithm)
+ ) {
+ throw new Error(
+ 'IndexedDB returned invalid data types for olm data and olm data encryption key',
+ );
+ }
+
+ const [encryptionKey, accountEncryptionKey] = await Promise.all([
+ validateCryptoKey(olmDataEncryptionKey),
+ validateCryptoKey(notificationAccountEncryptionKey),
+ ]);
+
+ return {
+ encryptedOlmData: olmData,
+ encryptionKey,
+ encryptionKeyDBLabel: olmDataEncryptionKeyDBLabel,
+ encryptedOlmAccount: notificationAccount,
+ olmDataKey,
+ accountEncryptionKey,
+ synchronizationValue,
+ };
+}
+
+async function persistKeyserverOlmData(
+ olmDataKey: string,
+ encryptionKey: CryptoKey,
+ olmData: NotificationsOlmDataType,
+): Promise<void> {
+ const updatedEncryptedSession =
+ await serializeUnencryptedData<NotificationsOlmDataType>(
+ olmData,
+ encryptionKey,
+ );
+
+ await localforage.setItem(olmDataKey, updatedEncryptedSession);
+}
+
+async function persistPeerOlmData(input: {
+ olmDataKey: string,
+ olmEncryptionKeyDBLabel: string,
+ encryptionKey: ?CryptoKey,
+ olmData: ?NotificationsOlmDataType,
+ accountEncryptionKey: ?CryptoKey,
+ accountWithPicklingKey?: PickledOLMAccount,
+ synchronizationValue: ?string,
+}): Promise<void> {
+ const {
+ olmData,
+ olmDataKey,
+ accountEncryptionKey,
+ accountWithPicklingKey,
+ encryptionKey,
+ synchronizationValue,
+ olmEncryptionKeyDBLabel,
+ } = input;
+
+ const shouldPersistOlmData = olmData && encryptionKey;
+ const shouldPersistAccount = accountWithPicklingKey && accountEncryptionKey;
+
+ if (!shouldPersistOlmData && !shouldPersistAccount) {
+ return;
+ }
+
+ const serializationPromises: {
+ [string]: Promise<EncryptedData | CryptoKey | SubtleCrypto$JsonWebKey>,
+ } = {};
+
+ if (!olmData && !accountWithPicklingKey) {
+ return;
+ }
+
+ if (olmData && encryptionKey) {
+ serializationPromises[olmDataKey] =
+ serializeUnencryptedData<NotificationsOlmDataType>(
+ olmData,
+ encryptionKey,
+ );
+ } else if (olmData) {
+ 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,
+ );
+ }
+
+ const setMultipleItemsInput = await promiseAll(serializationPromises);
+ const newSynchronizationValue = uuid.v4();
+ try {
+ await localforage.setMultipleItems(
+ setMultipleItemsInput,
+ INDEXED_DB_NOTIFS_SYNC_KEY,
+ synchronizationValue,
+ newSynchronizationValue,
+ false,
+ );
+ } catch (e) {
+ // likely worker crypt persisted its own data
+ console.log(e);
+ }
+}
+
async function decryptWebNotification(
encryptedNotification: EncryptedWebNotification,
): Promise<PlainTextWebNotification | WebNotifDecryptionError> {
- const { id, keyserverID, encryptedPayload } = encryptedNotification;
- invariant(keyserverID, 'KeyserverID must be present to decrypt a notif');
+ const {
+ id,
+ keyserverID,
+ senderDeviceID,
+ encryptedPayload,
+ type: messageType,
+ } = encryptedNotification;
+
const utilsData = await localforage.getItem<WebNotifsServiceUtilsData>(
WEB_NOTIFS_SERVICE_UTILS_KEY,
);
@@ -144,9 +338,43 @@
}
const { olmWasmPath, staffCanSee } = (utilsData: WebNotifsServiceUtilsData);
- let olmDBKeys;
+ let encryptionKey;
+ let olmEncryptionKeyDBLabel;
+ let encryptedOlmData;
+ let olmDataKey;
+ let accountEncryptionKey;
+ let encryptedOlmAccount;
+ let synchronizationValue;
+
+ let getOlmDataPromise;
+ if (keyserverID) {
+ getOlmDataPromise = getOlmDataForKeyserverSession(keyserverID);
+ } else if (senderDeviceID) {
+ getOlmDataPromise = getOlmDataForSessionWithDevice(senderDeviceID);
+ } else {
+ // we will never reach this branch
+ throw new Error(
+ 'keyserverID or SenderDeviceID must be present to decrypt a notif',
+ );
+ }
+
try {
- olmDBKeys = await getNotifsOlmSessionDBKeys(keyserverID);
+ const {
+ encryptionKey: fetchedEncryptionKey,
+ encryptedOlmData: fetchedEncryptedOlmData,
+ olmDataKey: fetchedOlmDataKey,
+ encryptionKeyDBLabel: fetchedOlmEncryptionKeyDBLabel,
+ accountEncryptionKey: fetchedAccountEncryptionKey,
+ encryptedOlmAccount: fetchedEncryptedOlmAccount,
+ synchronizationValue: fetchedSynchronizationValue,
+ } = await getOlmDataPromise;
+ encryptionKey = fetchedEncryptionKey;
+ encryptedOlmData = fetchedEncryptedOlmData;
+ olmDataKey = fetchedOlmDataKey;
+ accountEncryptionKey = fetchedAccountEncryptionKey;
+ encryptedOlmAccount = fetchedEncryptedOlmAccount;
+ synchronizationValue = fetchedSynchronizationValue;
+ olmEncryptionKeyDBLabel = fetchedOlmEncryptionKeyDBLabel;
} catch (e) {
return {
id,
@@ -154,38 +382,90 @@
displayErrorMessage: staffCanSee,
};
}
- const { olmDataKey, encryptionKeyDBKey } = olmDBKeys;
- const [encryptedOlmData, encryptionKey] = await Promise.all([
- localforage.getItem<EncryptedData>(olmDataKey),
- retrieveEncryptionKey(encryptionKeyDBKey),
- ]);
-
- if (!encryptionKey || !encryptedOlmData) {
- return {
- id,
- error: 'Received encrypted notification but olm session was not created',
- displayErrorMessage: staffCanSee,
- };
- }
try {
- await olm.init({ locateFile: () => olmWasmPath });
+ const [notificationsOlmData, accountWithPicklingKey] = await Promise.all([
+ deserializeEncryptedData<NotificationsOlmDataType>(
+ encryptedOlmData,
+ encryptionKey,
+ ),
+ deserializeEncryptedData<PickledOLMAccount>(
+ encryptedOlmAccount,
+ accountEncryptionKey,
+ ),
+ olm.init({ locateFile: () => olmWasmPath }),
+ ]);
- const decryptedNotification = await commonDecrypt<PlainTextWebNotification>(
- encryptedOlmData,
- olmDataKey,
- encryptionKey,
- encryptedPayload,
- );
+ let decryptedNotification;
+ let updatedOlmData;
+ let updatedNotifsAccount;
- const { unreadCount } = decryptedNotification;
+ if (keyserverID) {
+ invariant(
+ notificationsOlmData && encryptionKey,
+ 'Received encrypted notification but keyserver olm session was not created',
+ );
- invariant(keyserverID, 'Keyserver ID must be set to update badge counts');
- await updateNotifsUnreadCountStorage({
- [keyserverID]: unreadCount,
- });
+ const {
+ decryptedNotification: resultDecryptedNotification,
+ updatedOlmData: resultUpdatedOlmData,
+ } = await commonDecrypt<PlainTextWebNotification>(
+ notificationsOlmData,
+ encryptedPayload,
+ );
+
+ decryptedNotification = resultDecryptedNotification;
+ updatedOlmData = resultUpdatedOlmData;
+ const { unreadCount } = decryptedNotification;
+
+ invariant(keyserverID, 'Keyserver ID must be set to update badge counts');
+ await Promise.all([
+ persistKeyserverOlmData(olmDataKey, encryptionKey, updatedOlmData),
+ updateNotifsUnreadCountStorage({
+ [keyserverID]: unreadCount,
+ }),
+ ]);
+
+ return { id, ...decryptedNotification };
+ } else if (senderDeviceID) {
+ invariant(
+ accountWithPicklingKey,
+ 'Received encrypted notification but notifs olm account not created',
+ );
- return { id, ...decryptedNotification };
+ const {
+ decryptedNotification: resultDecryptedNotification,
+ updatedOlmData: resultUpdatedOlmData,
+ updatedNotifsAccount: resultUpdatedNotifsAccount,
+ } = await commonPeerDecrypt<PlainTextWebNotification>(
+ senderDeviceID,
+ notificationsOlmData,
+ accountWithPicklingKey,
+ messageType,
+ encryptedPayload,
+ );
+
+ decryptedNotification = resultDecryptedNotification;
+ updatedOlmData = resultUpdatedOlmData;
+ updatedNotifsAccount = resultUpdatedNotifsAccount;
+
+ await persistPeerOlmData({
+ accountWithPicklingKey: updatedNotifsAccount,
+ accountEncryptionKey,
+ encryptionKey,
+ olmData: updatedOlmData,
+ olmDataKey,
+ olmEncryptionKeyDBLabel,
+ synchronizationValue,
+ });
+
+ return { id, ...decryptedNotification };
+ } else {
+ // we will never reach this branch
+ throw new Error(
+ 'keyserverID or SenderDeviceID must be present to decrypt a notif',
+ );
+ }
} catch (e) {
return {
id,
@@ -197,87 +477,163 @@
async function decryptDesktopNotification(
encryptedPayload: string,
+ messageType: string,
staffCanSee: boolean,
- keyserverID?: string,
+ senderDeviceDescriptor: SenderDeviceDescriptor,
): Promise<{ +[string]: mixed }> {
- let encryptedOlmData, encryptionKey, olmDataKey;
- try {
- const { olmDataKey: olmDataKeyValue, encryptionKeyDBKey } =
- await getNotifsOlmSessionDBKeys(keyserverID);
+ const { keyserverID, senderDeviceID } = senderDeviceDescriptor;
- olmDataKey = olmDataKeyValue;
+ let encryptionKey;
+ let olmEncryptionKeyDBLabel;
+ let encryptedOlmData;
+ let olmDataKey;
+ let accountEncryptionKey;
+ let encryptedOlmAccount;
+ let synchronizationValue;
- [encryptedOlmData, encryptionKey] = await Promise.all([
- localforage.getItem<EncryptedData>(olmDataKey),
- retrieveEncryptionKey(encryptionKeyDBKey),
- initOlm(),
- ]);
- } catch (e) {
- return {
- error: e.message,
- displayErrorMessage: staffCanSee,
- };
+ let getOlmDataPromise;
+ if (keyserverID) {
+ getOlmDataPromise = getOlmDataForKeyserverSession(keyserverID);
+ } else if (senderDeviceID) {
+ getOlmDataPromise = getOlmDataForSessionWithDevice(senderDeviceID);
+ } else {
+ // we will never reach this branch
+ throw new Error(
+ 'keyserverID or SenderDeviceID must be present to decrypt a notif',
+ );
}
- if (!encryptionKey || !encryptedOlmData) {
+ try {
+ const {
+ encryptionKey: fetchedEncryptionKey,
+ encryptedOlmData: fetchedEncryptedOlmData,
+ olmDataKey: fetchedOlmDataKey,
+ encryptionKeyDBLabel: fetchedOlmEncryptionKeyDBLabel,
+ accountEncryptionKey: fetchedAccountEncryptionKey,
+ encryptedOlmAccount: fetchedEncryptedOlmAccount,
+ synchronizationValue: fetchedSynchronizationValue,
+ } = await getOlmDataPromise;
+ await initOlm();
+
+ encryptionKey = fetchedEncryptionKey;
+ encryptedOlmData = fetchedEncryptedOlmData;
+ olmDataKey = fetchedOlmDataKey;
+ accountEncryptionKey = fetchedAccountEncryptionKey;
+ encryptedOlmAccount = fetchedEncryptedOlmAccount;
+ synchronizationValue = fetchedSynchronizationValue;
+ olmEncryptionKeyDBLabel = fetchedOlmEncryptionKeyDBLabel;
+ } catch (e) {
return {
- error: 'Received encrypted notification but olm session was not created',
+ error: e.message,
displayErrorMessage: staffCanSee,
};
}
- let decryptedNotification;
try {
- decryptedNotification = await commonDecrypt<{ +[string]: mixed }>(
- encryptedOlmData,
- olmDataKey,
- encryptionKey,
- encryptedPayload,
- );
+ const [notificationsOlmData, accountWithPicklingKey] = await Promise.all([
+ deserializeEncryptedData<NotificationsOlmDataType>(
+ encryptedOlmData,
+ encryptionKey,
+ ),
+ deserializeEncryptedData<PickledOLMAccount>(
+ encryptedOlmAccount,
+ accountEncryptionKey,
+ ),
+ ]);
+
+ if (keyserverID) {
+ invariant(
+ notificationsOlmData && encryptionKey,
+ 'Received encrypted notification but keyserver olm session was not created',
+ );
+
+ const { decryptedNotification, updatedOlmData } = await commonDecrypt<{
+ +[string]: mixed,
+ }>(notificationsOlmData, encryptedPayload);
+
+ const updatedOlmDataPersistencePromise = persistKeyserverOlmData(
+ olmDataKey,
+ encryptionKey,
+ updatedOlmData,
+ );
+ // iOS notifications require that unread count is set under
+ // `badge` key. Since MacOS notifications are created by the
+ // same function the unread count is also set under `badge` key
+ const { badge } = decryptedNotification;
+ if (typeof badge === 'number') {
+ await Promise.all([
+ updateNotifsUnreadCountStorage({ [(keyserverID: string)]: badge }),
+ updatedOlmDataPersistencePromise,
+ ]);
+ return decryptedNotification;
+ }
+
+ const { unreadCount } = decryptedNotification;
+ if (typeof unreadCount === 'number') {
+ await Promise.all([
+ updateNotifsUnreadCountStorage({
+ [(keyserverID: string)]: unreadCount,
+ }),
+ updatedOlmDataPersistencePromise,
+ ]);
+ }
+
+ return decryptedNotification;
+ } else if (senderDeviceID) {
+ invariant(
+ accountWithPicklingKey,
+ 'Received encrypted notification but notifs olm account not created',
+ );
+
+ const { decryptedNotification, updatedOlmData, updatedNotifsAccount } =
+ await commonPeerDecrypt<{
+ +[string]: mixed,
+ }>(
+ senderDeviceID,
+ notificationsOlmData,
+ accountWithPicklingKey,
+ messageType,
+ encryptedPayload,
+ );
+
+ await persistPeerOlmData({
+ accountWithPicklingKey: updatedNotifsAccount,
+ accountEncryptionKey,
+ encryptionKey,
+ olmData: updatedOlmData,
+ olmDataKey,
+ olmEncryptionKeyDBLabel,
+ synchronizationValue,
+ });
+
+ return decryptedNotification;
+ } else {
+ // we will never reach this branch
+ throw new Error(
+ 'keyserverID or SenderDeviceID must be present to decrypt a notif',
+ );
+ }
} catch (e) {
return {
error: e.message,
staffCanSee,
};
}
-
- if (!keyserverID) {
- return decryptedNotification;
- }
-
- // iOS notifications require that unread count is set under
- // `badge` key. Since MacOS notifications are created by the
- // same function the unread count is also set under `badge` key
- const { badge } = decryptedNotification;
- if (typeof badge === 'number') {
- await updateNotifsUnreadCountStorage({ [(keyserverID: string)]: badge });
- return decryptedNotification;
- }
-
- const { unreadCount } = decryptedNotification;
- if (typeof unreadCount === 'number') {
- await updateNotifsUnreadCountStorage({
- [(keyserverID: string)]: unreadCount,
- });
- }
- return decryptedNotification;
}
async function commonDecrypt<T>(
- encryptedOlmData: EncryptedData,
- olmDataKey: string,
- encryptionKey: CryptoKey,
+ notificationsOlmData: NotificationsOlmDataType,
encryptedPayload: string,
-): Promise<T> {
- const serializedOlmData = await decryptData(encryptedOlmData, encryptionKey);
+): Promise<{
+ +decryptedNotification: T,
+ +updatedOlmData: NotificationsOlmDataType,
+}> {
const {
mainSession,
picklingKey,
pendingSessionUpdate,
updateCreationTimestamp,
- }: NotificationsOlmDataType = JSON.parse(
- new TextDecoder().decode(serializedOlmData),
- );
+ } = notificationsOlmData;
let updatedOlmData: NotificationsOlmDataType;
let decryptedNotification: T;
@@ -320,14 +676,134 @@
};
}
- const updatedEncryptedSession = await encryptData(
- new TextEncoder().encode(JSON.stringify(updatedOlmData)),
- encryptionKey,
+ return { decryptedNotification, updatedOlmData };
+}
+
+async function commonPeerDecrypt<T>(
+ senderDeviceID: string,
+ notificationsOlmData: ?NotificationsOlmDataType,
+ notificationAccount: PickledOLMAccount,
+ messageType: string,
+ encryptedPayload: string,
+): Promise<{
+ +decryptedNotification: T,
+ +updatedOlmData?: NotificationsOlmDataType,
+ +updatedNotifsAccount?: PickledOLMAccount,
+}> {
+ if (
+ messageType !== olmEncryptedMessageTypes.PREKEY.toString() &&
+ messageType !== olmEncryptedMessageTypes.TEXT.toString()
+ ) {
+ throw new Error(
+ `Received message of invalid type from device: ${senderDeviceID}`,
+ );
+ }
+
+ let isSenderChainEmpty = true;
+ let hasReceivedMessage = false;
+ const sessionExists = !!notificationsOlmData;
+
+ if (notificationsOlmData) {
+ const session = new olm.Session();
+ session.unpickle(
+ notificationsOlmData.picklingKey,
+ notificationsOlmData.pendingSessionUpdate,
+ );
+
+ isSenderChainEmpty = session.is_sender_chain_empty();
+ hasReceivedMessage = session.has_received_message();
+ }
+
+ // regular message
+ const isRegularMessage =
+ !!notificationsOlmData &&
+ messageType === olmEncryptedMessageTypes.TEXT.toString();
+
+ const isRegularPrekeyMessage =
+ !!notificationsOlmData &&
+ messageType === olmEncryptedMessageTypes.PREKEY.toString() &&
+ isSenderChainEmpty &&
+ hasReceivedMessage;
+
+ if (!!notificationsOlmData && (isRegularMessage || isRegularPrekeyMessage)) {
+ return await commonDecrypt<T>(notificationsOlmData, encryptedPayload);
+ }
+
+ // At this point we either face race condition or session reset attempt or
+ // session initialization attempt. For each of this scenario new inbound
+ // session must be created in order to decrypt message
+ const authMetadata = await fetchAuthMetadata();
+ const notifInboundKeys = await getNotifsInboundKeysForDeviceID(
+ senderDeviceID,
+ authMetadata,
);
- await localforage.setItem(olmDataKey, updatedEncryptedSession);
+ const account = new olm.Account();
+ const session = new olm.Session();
+
+ account.unpickle(
+ notificationAccount.picklingKey,
+ notificationAccount.pickledAccount,
+ );
+
+ if (notifInboundKeys.error) {
+ throw new Error(notifInboundKeys.error);
+ }
+
+ invariant(
+ notifInboundKeys.curve25519,
+ 'curve25519 must be present in notifs inbound keys',
+ );
+
+ session.create_inbound_from(
+ account,
+ notifInboundKeys.curve25519,
+ encryptedPayload,
+ );
+
+ const decryptedNotification: T = JSON.parse(
+ session.decrypt(Number(messageType), encryptedPayload),
+ );
+
+ // session reset attempt or session initialization - handled the same
+ const sessionResetAttempt =
+ sessionExists && !isSenderChainEmpty && hasReceivedMessage;
+
+ // race condition
+ const raceCondition =
+ sessionExists && !isSenderChainEmpty && !hasReceivedMessage;
+ const { deviceID: ourDeviceID } = authMetadata;
+ invariant(ourDeviceID, 'Session creation attempt but no device id');
+
+ const thisDeviceWinsRaceCondition = ourDeviceID > senderDeviceID;
+
+ if (
+ !sessionExists ||
+ sessionResetAttempt ||
+ (raceCondition && !thisDeviceWinsRaceCondition)
+ ) {
+ const pickledOlmSession = session.pickle(notificationAccount.picklingKey);
+ const updatedOlmData = {
+ mainSession: pickledOlmSession,
+ pendingSessionUpdate: pickledOlmSession,
+ updateCreationTimestamp: Date.now(),
+ picklingKey: notificationAccount.picklingKey,
+ };
+ const updatedNotifsAccount = {
+ pickledAccount: account.pickle(notificationAccount.picklingKey),
+ picklingKey: notificationAccount.picklingKey,
+ };
+ return {
+ decryptedNotification,
+ updatedOlmData,
+ updatedNotifsAccount,
+ };
+ }
- return decryptedNotification;
+ // If there is a race condition but we win device id comparison
+ // we return object that carries decrypted data but won't persist
+ // any session state
+ return { decryptedNotification };
}
function decryptWithSession<T>(
diff --git a/web/push-notif/push-notifs-handler.js b/web/push-notif/push-notifs-handler.js
--- a/web/push-notif/push-notifs-handler.js
+++ b/web/push-notif/push-notifs-handler.js
@@ -85,14 +85,17 @@
async ({
encryptedPayload,
keyserverID,
+ type: messageType,
}: {
encryptedPayload: string,
- keyserverID?: string,
+ type: string,
+ keyserverID: string,
}) => {
const decryptedPayload = await decryptDesktopNotification(
encryptedPayload,
+ messageType,
staffCanSee,
- keyserverID,
+ { keyserverID },
);
electron?.showDecryptedNotification(decryptedPayload);
},

File Metadata

Mime Type
text/plain
Expires
Mon, Dec 8, 7:27 PM (1 h, 41 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5850114
Default Alt Text
D12838.1765222044.diff (25 KB)

Event Timeline