diff --git a/keyserver/src/push/crypto.js b/keyserver/src/push/crypto.js --- a/keyserver/src/push/crypto.js +++ b/keyserver/src/push/crypto.js @@ -1,6 +1,5 @@ // @flow -import type { EncryptResult } from '@commapp/olm'; import apn from '@parse/node-apn'; import crypto from 'crypto'; import invariant from 'invariant'; @@ -18,16 +17,17 @@ AndroidNotificationRescind, NotificationTargetDevice, SenderDeviceDescriptor, + EncryptedNotifUtilsAPI, } from 'lib/types/notif-types.js'; import { toBase64URL } from 'lib/utils/base64.js'; -import { encryptAndUpdateOlmSession } from '../updaters/olm-session-updater.js'; import { encrypt, generateKey } from '../utils/aes-crypto-utils.js'; import { getOlmUtility } from '../utils/olm-utils.js'; async function encryptAPNsNotification( + encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI, cookieID: string, - senderDeviceID: SenderDeviceDescriptor, + senderDeviceDescriptor: SenderDeviceDescriptor, notification: apn.Notification, codeVersion?: ?number, notificationSizeValidator?: apn.Notification => boolean, @@ -71,32 +71,25 @@ let dbPersistCondition; if (notificationSizeValidator) { - dbPersistCondition = ({ - serializedPayload, - }: { - +[string]: EncryptResult, - }) => { + dbPersistCondition = (serializedPayload: string) => { const notifCopy = _cloneDeep(encryptedNotification); - notifCopy.payload.encryptedPayload = serializedPayload.body; + notifCopy.payload.encryptedPayload = serializedPayload; return notificationSizeValidator(notifCopy); }; } const { - encryptedMessages: { serializedPayload }, - dbPersistConditionViolated, + encryptedData: serializedPayload, + sizeLimitViolated: dbPersistConditionViolated, encryptionOrder, - } = await encryptAndUpdateOlmSession( + } = await encryptedNotifUtilsAPI.encryptSerializedNotifPayload( cookieID, - 'notifications', - { - serializedPayload: unencryptedSerializedPayload, - }, + unencryptedSerializedPayload, dbPersistCondition, ); encryptedNotification.payload.encryptedPayload = serializedPayload.body; encryptedNotification.payload = { - ...senderDeviceID, + ...senderDeviceDescriptor, ...encryptedNotification.payload, }; @@ -140,8 +133,9 @@ } async function encryptAndroidNotificationPayload( + encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI, cookieID: string, - senderDeviceID: SenderDeviceDescriptor, + senderDeviceDescriptor: SenderDeviceDescriptor, unencryptedPayload: T, payloadSizeValidator?: ( T | $ReadOnly<{ ...SenderDeviceDescriptor, +encryptedPayload: string }>, @@ -166,33 +160,27 @@ let dbPersistCondition; if (payloadSizeValidator) { - dbPersistCondition = ({ - serializedPayload, - }: { - +[string]: EncryptResult, - }) => + dbPersistCondition = (serializedPayload: string) => payloadSizeValidator({ - encryptedPayload: serializedPayload.body, - ...senderDeviceID, + encryptedPayload: serializedPayload, + ...senderDeviceDescriptor, }); } const { - encryptedMessages: { serializedPayload }, - dbPersistConditionViolated, + encryptedData: serializedPayload, + sizeLimitViolated: dbPersistConditionViolated, encryptionOrder, - } = await encryptAndUpdateOlmSession( + } = await encryptedNotifUtilsAPI.encryptSerializedNotifPayload( cookieID, - 'notifications', - { - serializedPayload: unencryptedSerializedPayload, - }, + unencryptedSerializedPayload, dbPersistCondition, ); + return { resultPayload: { encryptedPayload: serializedPayload.body, - ...senderDeviceID, + ...senderDeviceDescriptor, }, payloadSizeExceeded: !!dbPersistConditionViolated, encryptionOrder, @@ -213,7 +201,8 @@ } async function encryptAndroidVisualNotification( - senderDeviceID: SenderDeviceDescriptor, + encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI, + senderDeviceDescriptor: SenderDeviceDescriptor, cookieID: string, notification: AndroidVisualNotification, notificationSizeValidator?: AndroidVisualNotification => boolean, @@ -249,8 +238,9 @@ } const { resultPayload, payloadSizeExceeded, encryptionOrder } = await encryptAndroidNotificationPayload( + encryptedNotifUtilsAPI, cookieID, - senderDeviceID, + senderDeviceDescriptor, unencryptedPayload, payloadSizeValidator, ); @@ -267,8 +257,9 @@ } async function encryptAndroidSilentNotification( + encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI, cookieID: string, - senderDeviceID: SenderDeviceDescriptor, + senderDeviceDescriptor: SenderDeviceDescriptor, notification: AndroidNotificationRescind | AndroidBadgeOnlyNotification, ): Promise { // We don't validate payload size for rescind @@ -276,8 +267,9 @@ // never exceed any FCM limit const { ...unencryptedPayload } = notification.data; const { resultPayload } = await encryptAndroidNotificationPayload( + encryptedNotifUtilsAPI, cookieID, - senderDeviceID, + senderDeviceDescriptor, unencryptedPayload, ); if (resultPayload.encryptedPayload) { @@ -300,8 +292,9 @@ } async function encryptBasicPayload( + encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI, cookieID: string, - senderDeviceID: SenderDeviceDescriptor, + senderDeviceDescriptor: SenderDeviceDescriptor, basicPayload: T, ): Promise< | $ReadOnly<{ @@ -318,15 +311,14 @@ } try { - const { - encryptedMessages: { serializedPayload }, - encryptionOrder, - } = await encryptAndUpdateOlmSession(cookieID, 'notifications', { - serializedPayload: unencryptedSerializedPayload, - }); + const { encryptedData: serializedPayload, encryptionOrder } = + await encryptedNotifUtilsAPI.encryptSerializedNotifPayload( + cookieID, + unencryptedSerializedPayload, + ); return { - ...senderDeviceID, + ...senderDeviceDescriptor, encryptedPayload: serializedPayload.body, encryptionOrder, }; @@ -340,15 +332,17 @@ } async function encryptWebNotification( + encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI, cookieID: string, - senderDeviceID: SenderDeviceDescriptor, + senderDeviceDescriptor: SenderDeviceDescriptor, notification: PlainTextWebNotification, ): Promise<{ +notification: WebNotification, +encryptionOrder?: number }> { const { id, ...payloadSansId } = notification; const { encryptionOrder, ...encryptionResult } = await encryptBasicPayload( + encryptedNotifUtilsAPI, cookieID, - senderDeviceID, + senderDeviceDescriptor, payloadSansId, ); @@ -359,14 +353,16 @@ } async function encryptWNSNotification( + encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI, cookieID: string, - senderDeviceID: SenderDeviceDescriptor, + senderDeviceDescriptor: SenderDeviceDescriptor, notification: PlainTextWNSNotification, ): Promise<{ +notification: WNSNotification, +encryptionOrder?: number }> { const { encryptionOrder, ...encryptionResult } = await encryptBasicPayload( + encryptedNotifUtilsAPI, cookieID, - senderDeviceID, + senderDeviceDescriptor, notification, ); return { @@ -376,7 +372,8 @@ } function prepareEncryptedAPNsNotifications( - senderDeviceID: SenderDeviceDescriptor, + encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI, + senderDeviceDescriptor: SenderDeviceDescriptor, devices: $ReadOnlyArray, notification: apn.Notification, codeVersion?: ?number, @@ -394,8 +391,9 @@ const notificationPromises = devices.map( async ({ cookieID, deviceToken, blobHolder }) => { const notif = await encryptAPNsNotification( + encryptedNotifUtilsAPI, cookieID, - senderDeviceID, + senderDeviceDescriptor, notification, codeVersion, notificationSizeValidator, @@ -408,7 +406,8 @@ } function prepareEncryptedIOSNotificationRescind( - senderDeviceID: SenderDeviceDescriptor, + encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI, + senderDeviceDescriptor: SenderDeviceDescriptor, devices: $ReadOnlyArray, notification: apn.Notification, codeVersion?: ?number, @@ -422,8 +421,9 @@ const notificationPromises = devices.map( async ({ deviceToken, cookieID }) => { const { notification: notif } = await encryptAPNsNotification( + encryptedNotifUtilsAPI, cookieID, - senderDeviceID, + senderDeviceDescriptor, notification, codeVersion, ); @@ -434,7 +434,8 @@ } function prepareEncryptedAndroidVisualNotifications( - senderDeviceID: SenderDeviceDescriptor, + encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI, + senderDeviceDescriptor: SenderDeviceDescriptor, devices: $ReadOnlyArray, notification: AndroidVisualNotification, notificationSizeValidator?: ( @@ -452,7 +453,8 @@ const notificationPromises = devices.map( async ({ deviceToken, cookieID, blobHolder }) => { const notif = await encryptAndroidVisualNotification( - senderDeviceID, + encryptedNotifUtilsAPI, + senderDeviceDescriptor, cookieID, notification, notificationSizeValidator, @@ -465,7 +467,8 @@ } function prepareEncryptedAndroidSilentNotifications( - senderDeviceID: SenderDeviceDescriptor, + encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI, + senderDeviceDescriptor: SenderDeviceDescriptor, devices: $ReadOnlyArray, notification: AndroidNotificationRescind | AndroidBadgeOnlyNotification, ): Promise< @@ -479,8 +482,9 @@ const notificationPromises = devices.map( async ({ deviceToken, cookieID }) => { const notif = await encryptAndroidSilentNotification( + encryptedNotifUtilsAPI, cookieID, - senderDeviceID, + senderDeviceDescriptor, notification, ); return { deviceToken, cookieID, notification: notif }; @@ -490,7 +494,8 @@ } function prepareEncryptedWebNotifications( - senderDeviceID: SenderDeviceDescriptor, + encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI, + senderDeviceDescriptor: SenderDeviceDescriptor, devices: $ReadOnlyArray, notification: PlainTextWebNotification, ): Promise< @@ -503,8 +508,9 @@ const notificationPromises = devices.map( async ({ deviceToken, cookieID }) => { const notif = await encryptWebNotification( + encryptedNotifUtilsAPI, cookieID, - senderDeviceID, + senderDeviceDescriptor, notification, ); return { ...notif, deviceToken }; @@ -514,7 +520,8 @@ } function prepareEncryptedWNSNotifications( - senderDeviceID: SenderDeviceDescriptor, + encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI, + senderDeviceDescriptor: SenderDeviceDescriptor, devices: $ReadOnlyArray, notification: PlainTextWNSNotification, ): Promise< @@ -527,8 +534,9 @@ const notificationPromises = devices.map( async ({ deviceToken, cookieID }) => { const notif = await encryptWNSNotification( + encryptedNotifUtilsAPI, cookieID, - senderDeviceID, + senderDeviceDescriptor, notification, ); return { ...notif, deviceToken }; diff --git a/keyserver/src/push/encrypted-notif-utils-api.js b/keyserver/src/push/encrypted-notif-utils-api.js new file mode 100644 --- /dev/null +++ b/keyserver/src/push/encrypted-notif-utils-api.js @@ -0,0 +1,49 @@ +// @flow + +import type { EncryptResult } from '@commapp/olm'; + +import type { EncryptedNotifUtilsAPI } from 'lib/types/notif-types.js'; + +import { blobServiceUpload } from './utils.js'; +import { encryptAndUpdateOlmSession } from '../updaters/olm-session-updater.js'; + +const encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI = { + encryptSerializedNotifPayload: async ( + cryptoID: string, + unencryptedPayload: string, + encryptedPayloadSizeValidator?: (encryptedPayload: string) => boolean, + ) => { + let dbPersistCondition; + if (encryptedPayloadSizeValidator) { + dbPersistCondition = ({ + serializedPayload, + }: { + +[string]: EncryptResult, + }) => encryptedPayloadSizeValidator(serializedPayload.body); + } + + const { + encryptedMessages: { serializedPayload }, + dbPersistConditionViolated, + encryptionOrder, + } = await encryptAndUpdateOlmSession( + cryptoID, + 'notifications', + { + serializedPayload: unencryptedPayload, + }, + dbPersistCondition, + ); + + return { + encryptedData: serializedPayload, + sizeLimitViolated: dbPersistConditionViolated, + encryptionOrder, + }; + }, + uploadLargeNotifPayload: blobServiceUpload, + getNotifByteSize: (serializedPayload: string) => + Buffer.byteLength(serializedPayload), +}; + +export default encryptedNotifUtilsAPI; diff --git a/keyserver/src/push/rescind.js b/keyserver/src/push/rescind.js --- a/keyserver/src/push/rescind.js +++ b/keyserver/src/push/rescind.js @@ -10,6 +10,7 @@ NotificationTargetDevice, TargetedAndroidNotification, SenderDeviceDescriptor, + EncryptedNotifUtilsAPI, } from 'lib/types/notif-types.js'; import { threadSubscriptions } from 'lib/types/subscription-types.js'; import { threadPermissions } from 'lib/types/thread-permission-types.js'; @@ -20,6 +21,7 @@ prepareEncryptedAndroidSilentNotifications, prepareEncryptedIOSNotificationRescind, } from './crypto.js'; +import encryptedNotifUtilsAPI from './encrypted-notif-utils-api.js'; import { getAPNsNotificationTopic } from './providers.js'; import type { TargetedAPNsNotification } from './types.js'; import { @@ -275,12 +277,14 @@ } async function conditionallyEncryptNotification( - senderDeviceID: SenderDeviceDescriptor, + encryptedNotifUtilsAPIInstance: EncryptedNotifUtilsAPI, + senderDeviceDescriptor: SenderDeviceDescriptor, notification: T, codeVersion: ?number, devices: $ReadOnlyArray, encryptCallback: ( - senderDeviceID: SenderDeviceDescriptor, + encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI, + senderDeviceDescriptor: SenderDeviceDescriptor, devices: $ReadOnlyArray, notification: T, codeVersion?: ?number, @@ -301,7 +305,8 @@ })); } const notifications = await encryptCallback( - senderDeviceID, + encryptedNotifUtilsAPI, + senderDeviceDescriptor, devices, notification, codeVersion, @@ -354,6 +359,7 @@ }, }; return await conditionallyEncryptNotification( + encryptedNotifUtilsAPI, { keyserverID }, notification, codeVersion, @@ -383,6 +389,7 @@ }, }; const targetedRescinds = await conditionallyEncryptNotification( + encryptedNotifUtilsAPI, { keyserverID }, notification, codeVersion, diff --git a/keyserver/src/push/send.js b/keyserver/src/push/send.js --- a/keyserver/src/push/send.js +++ b/keyserver/src/push/send.js @@ -56,6 +56,7 @@ prepareEncryptedWebNotifications, prepareEncryptedWNSNotifications, } from './crypto.js'; +import encryptedNotifUtilsAPI from './encrypted-notif-utils-api.js'; import { getAPNsNotificationTopic } from './providers.js'; import { rescindPushNotifs } from './rescind.js'; import type { TargetedAPNsNotification } from './types.js'; @@ -70,7 +71,6 @@ wnsMaxNotificationPayloadByteSize, wnsPush, type WNSPushError, - blobServiceUpload, } from './utils.js'; import createIDs from '../creators/id-creator.js'; import { createUpdates } from '../creators/update-creator.js'; @@ -1039,6 +1039,7 @@ if (platformDetails.platform === 'macos') { const macOSNotifsWithoutMessageInfos = await prepareEncryptedAPNsNotifications( + encryptedNotifUtilsAPI, { keyserverID }, devices, notification, @@ -1053,6 +1054,7 @@ } const notifsWithMessageInfos = await prepareEncryptedAPNsNotifications( + encryptedNotifUtilsAPI, { keyserverID }, devices, copyWithMessageInfos, @@ -1090,7 +1092,7 @@ let blobHash, blobHolders, encryptionKey, blobUploadError; if (canQueryBlobService) { ({ blobHash, blobHolders, encryptionKey, blobUploadError } = - await blobServiceUpload( + await encryptedNotifUtilsAPI.uploadLargeNotifPayload( copyWithMessageInfos.compile(), devicesWithExcessiveSizeNoHolders.length, )); @@ -1123,6 +1125,7 @@ } const notifsWithoutMessageInfos = await prepareEncryptedAPNsNotifications( + encryptedNotifUtilsAPI, { keyserverID }, devicesWithExcessiveSize, notification, @@ -1247,8 +1250,9 @@ const priority = 'high'; if (!shouldBeEncrypted) { const notificationToSend = - Buffer.byteLength(JSON.stringify(copyWithMessageInfos)) <= - fcmMaxNotificationPayloadByteSize + encryptedNotifUtilsAPI.getNotifByteSize( + JSON.stringify(copyWithMessageInfos), + ) <= fcmMaxNotificationPayloadByteSize ? copyWithMessageInfos : notification; @@ -1263,12 +1267,14 @@ const serializedNotif = JSON.stringify(notif); return ( !serializedNotif || - Buffer.byteLength(serializedNotif) <= fcmMaxNotificationPayloadByteSize + encryptedNotifUtilsAPI.getNotifByteSize(serializedNotif) <= + fcmMaxNotificationPayloadByteSize ); }; const notifsWithMessageInfos = await prepareEncryptedAndroidVisualNotifications( + encryptedNotifUtilsAPI, { keyserverID }, devices, copyWithMessageInfos, @@ -1297,7 +1303,7 @@ let blobHash, blobHolders, encryptionKey, blobUploadError; if (canQueryBlobService) { ({ blobHash, blobHolders, encryptionKey, blobUploadError } = - await blobServiceUpload( + await encryptedNotifUtilsAPI.uploadLargeNotifPayload( JSON.stringify(copyWithMessageInfos.data), devicesWithExcessiveSizeNoHolders.length, )); @@ -1331,6 +1337,7 @@ const notifsWithoutMessageInfos = await prepareEncryptedAndroidVisualNotifications( + encryptedNotifUtilsAPI, { keyserverID }, devicesWithExcessiveSize, notification, @@ -1402,6 +1409,7 @@ } return prepareEncryptedWebNotifications( + encryptedNotifUtilsAPI, { keyserverID }, devices, notification, @@ -1440,7 +1448,7 @@ }; if ( - Buffer.byteLength(JSON.stringify(notification)) > + encryptedNotifUtilsAPI.getNotifByteSize(JSON.stringify(notification)) > wnsMaxNotificationPayloadByteSize ) { console.warn('WNS notification exceeds size limit'); @@ -1457,6 +1465,7 @@ })); } return await prepareEncryptedWNSNotifications( + encryptedNotifUtilsAPI, { keyserverID }, devices, notification, @@ -1813,6 +1822,7 @@ let targetedNotifications: $ReadOnlyArray; if (codeVersion > 222) { const notificationsArray = await prepareEncryptedAPNsNotifications( + encryptedNotifUtilsAPI, { keyserverID }, deviceInfos, notification, @@ -1865,6 +1875,7 @@ if (codeVersion > 222) { const notificationsArray = await prepareEncryptedAndroidSilentNotifications( + encryptedNotifUtilsAPI, { keyserverID }, deviceInfos, notification, @@ -1923,6 +1934,7 @@ let targetedNotifications: $ReadOnlyArray; if (shouldBeEncrypted) { const notificationsArray = await prepareEncryptedAPNsNotifications( + encryptedNotifUtilsAPI, { keyserverID }, deviceInfos, notification, diff --git a/lib/types/notif-types.js b/lib/types/notif-types.js --- a/lib/types/notif-types.js +++ b/lib/types/notif-types.js @@ -1,5 +1,6 @@ // @flow +import type { EncryptResult } from '@commapp/olm'; import t, { type TInterface } from 'tcomb'; import type { EntityText, ThreadEntity } from '../utils/entity-text.js'; @@ -185,3 +186,27 @@ +deviceToken: string, +blobHolder?: string, }; + +export type EncryptedNotifUtilsAPI = { + +encryptSerializedNotifPayload: ( + cryptoID: string, + unencryptedPayload: string, + encryptedPayloadSizeValidator?: (encryptedPayload: string) => boolean, + ) => Promise<{ + +encryptedData: EncryptResult, + +sizeLimitViolated?: boolean, + +encryptionOrder?: number, + }>, + +uploadLargeNotifPayload: ( + payload: string, + numberOfHolders: number, + ) => Promise< + | { + +blobHolders: $ReadOnlyArray, + +blobHash: string, + +encryptionKey: string, + } + | { +blobUploadError: string }, + >, + +getNotifByteSize: (serializedNotification: string) => number, +};