diff --git a/native/cpp/CommonCpp/Notifications/BackgroundDataStorage/NotificationsCryptoModule.h b/native/cpp/CommonCpp/Notifications/BackgroundDataStorage/NotificationsCryptoModule.h --- a/native/cpp/CommonCpp/Notifications/BackgroundDataStorage/NotificationsCryptoModule.h +++ b/native/cpp/CommonCpp/Notifications/BackgroundDataStorage/NotificationsCryptoModule.h @@ -24,6 +24,7 @@ const std::string &callingProcessName); public: + const static int olmEncryptedTypeMessage; static void initializeNotificationsCryptoAccount(const std::string &callingProcessName); static void clearSensitiveData(); diff --git a/native/cpp/CommonCpp/Notifications/BackgroundDataStorage/NotificationsCryptoModule.cpp b/native/cpp/CommonCpp/Notifications/BackgroundDataStorage/NotificationsCryptoModule.cpp --- a/native/cpp/CommonCpp/Notifications/BackgroundDataStorage/NotificationsCryptoModule.cpp +++ b/native/cpp/CommonCpp/Notifications/BackgroundDataStorage/NotificationsCryptoModule.cpp @@ -23,6 +23,7 @@ "keyserverHostedNotificationsID"; const std::string NotificationsCryptoModule::initialEncryptedMessageContent = "{\"type\": \"init\"}"; +const int NotificationsCryptoModule::olmEncryptedTypeMessage = 1; crypto::CryptoModule NotificationsCryptoModule::deserializeCryptoModule( const std::string &path, diff --git a/native/ios/Comm.xcodeproj/project.pbxproj b/native/ios/Comm.xcodeproj/project.pbxproj --- a/native/ios/Comm.xcodeproj/project.pbxproj +++ b/native/ios/Comm.xcodeproj/project.pbxproj @@ -58,6 +58,7 @@ CB38B48628771CDD00171182 /* TemporaryMessageStorage.mm in Sources */ = {isa = PBXBuildFile; fileRef = CB38B47F28771A3B00171182 /* TemporaryMessageStorage.mm */; }; CB38B48728771CE500171182 /* TemporaryMessageStorage.mm in Sources */ = {isa = PBXBuildFile; fileRef = CB38B47F28771A3B00171182 /* TemporaryMessageStorage.mm */; }; CB38F2B1286C6C870010535C /* MessageOperationsUtilities.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CB38F2AF286C6C870010535C /* MessageOperationsUtilities.cpp */; }; + CB3C0A3B2A125C8F009BD4DA /* NotificationsCryptoModule.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CB24361729A39A2500FEC4E1 /* NotificationsCryptoModule.cpp */; }; CB3C621127CE4A320054F24C /* Logger.mm in Sources */ = {isa = PBXBuildFile; fileRef = 71CA4A63262DA8E500835C89 /* Logger.mm */; }; CB3C621227CE65030054F24C /* CommSecureStoreIOSWrapper.mm in Sources */ = {isa = PBXBuildFile; fileRef = 71142A7626C2650A0039DCBD /* CommSecureStoreIOSWrapper.mm */; }; CB4821A927CFB153001AB7E1 /* WorkerThread.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 718DE99C2653D41C00365824 /* WorkerThread.cpp */; }; @@ -1028,6 +1029,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + CB3C0A3B2A125C8F009BD4DA /* NotificationsCryptoModule.cpp in Sources */, CB90951F29534B32002F2A7F /* CommSecureStore.mm in Sources */, CB38B48728771CE500171182 /* TemporaryMessageStorage.mm in Sources */, CB38B48528771CB800171182 /* EncryptedFileUtils.mm in Sources */, diff --git a/native/ios/NotificationService/NotificationService.mm b/native/ios/NotificationService/NotificationService.mm --- a/native/ios/NotificationService/NotificationService.mm +++ b/native/ios/NotificationService/NotificationService.mm @@ -1,8 +1,11 @@ #import "NotificationService.h" #import "Logger.h" +#import "NotificationsCryptoModule.h" #import "TemporaryMessageStorage.h" NSString *const backgroundNotificationTypeKey = @"backgroundNotifType"; +NSString *const messageInfosKey = @"messageInfos"; +const std::string callingProcessName = "NSE"; // The context for this constant can be found here: // https://linear.app/comm/issue/ENG-3074#comment-bd2f5e28 int64_t const notificationRemovalDelay = (int64_t)(0.1 * NSEC_PER_SEC); @@ -26,6 +29,10 @@ self.contentHandler = contentHandler; self.bestAttemptContent = [request.content mutableCopy]; + if ([self shouldBeDecrypted:self.bestAttemptContent.userInfo]) { + [self decryptBestAttemptContent]; + } + [self persistMessagePayload:self.bestAttemptContent.userInfo]; // Message payload persistence is a higher priority task, so it has // to happen prior to potential notification center clearing. @@ -52,6 +59,16 @@ // It is an extremely unlikely to happen. comm::Logger::log("NSE: Exceeded time limit to rescind a notification."); self.contentHandler([[UNNotificationContent alloc] init]); + return; + } + if ([self shouldBeDecrypted:self.bestAttemptContent.userInfo] && + !self.bestAttemptContent.userInfo[@"succesfullyDecrypted"]) { + // If we get to this place it means we were unable to + // decrypt encrypted notification content in time + // given to NSE to process notification. + comm::Logger::log("NSE: Exceeded time limit to decrypt a notification."); + self.contentHandler([[UNNotificationContent alloc] init]); + return; } self.contentHandler(self.bestAttemptContent); } @@ -85,10 +102,10 @@ } - (void)persistMessagePayload:(NSDictionary *)payload { - if (payload[@"messageInfos"]) { + if (payload[messageInfosKey]) { TemporaryMessageStorage *temporaryStorage = [[TemporaryMessageStorage alloc] init]; - [temporaryStorage writeMessage:payload[@"messageInfos"]]; + [temporaryStorage writeMessage:payload[messageInfosKey]]; return; } @@ -131,4 +148,85 @@ TRUE); } +- (BOOL)shouldBeDecrypted:(NSDictionary *)payload { + return payload[@"encrypted"] && [payload[@"encrypted"] isEqualToNumber:@(1)]; +} + +- (NSString *)singleDecrypt:(NSString *)data { + std::string encryptedData = std::string([data UTF8String]); + return [NSString + stringWithUTF8String: + (comm::NotificationsCryptoModule::decrypt( + encryptedData, + comm::NotificationsCryptoModule::olmEncryptedTypeMessage, + callingProcessName)) + .c_str()]; +} + +- (void)decryptBestAttemptContent { + NSMutableDictionary *mutableUserInfo = + [self.bestAttemptContent.userInfo mutableCopy]; + + NSMutableDictionary *mutableAps = nil; + if (mutableUserInfo[@"aps"]) { + mutableAps = [mutableUserInfo[@"aps"] mutableCopy]; + } + + if (self.bestAttemptContent.body) { + NSString *decryptedBody = [self singleDecrypt:self.bestAttemptContent.body]; + self.bestAttemptContent.body = decryptedBody; + + if (mutableAps && mutableAps[@"alert"]) { + mutableAps[@"alert"] = decryptedBody; + } + } + + if (self.bestAttemptContent.threadIdentifier) { + NSString *decryptedThreadID = + [self singleDecrypt:self.bestAttemptContent.threadIdentifier]; + self.bestAttemptContent.threadIdentifier = decryptedThreadID; + mutableUserInfo[@"threadID"] = decryptedThreadID; + + if (mutableAps) { + mutableAps[@"thread-id"] = decryptedThreadID; + } + } + + if (self.bestAttemptContent.userInfo[@"badge"]) { + NSNumber *decryptedBadge = @([[self + singleDecrypt:self.bestAttemptContent.userInfo[@"badge"]] intValue]); + self.bestAttemptContent.badge = decryptedBadge; + + if (mutableAps) { + mutableAps[@"badge"] = decryptedBadge; + } + } + + // Properties 'id' and 'encrypted are not encrypted. + // The rest have been already decrypted and handled. + static NSArray *keysToOmit = + @[ @"id", @"badge", @"threadID", @"aps", @"encrypted" ]; + + for (NSString *payloadKey in self.bestAttemptContent.userInfo) { + if ([keysToOmit containsObject:payloadKey]) { + continue; + } + + NSString *decryptedPayloadValue = + [self singleDecrypt:mutableUserInfo[payloadKey]]; + mutableUserInfo[payloadKey] = decryptedPayloadValue; + + if (mutableAps && mutableAps[payloadKey]) { + mutableAps[payloadKey] = decryptedPayloadValue; + } + } + + if (mutableAps) { + mutableUserInfo[@"aps"] = mutableAps; + } + + mutableUserInfo[@"succesfullyDecrypted"] = @(YES); + self.bestAttemptContent.userInfo = mutableUserInfo; +} + @end