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,13 @@ #import "NotificationService.h" #import "Logger.h" +#import "NotificationsCryptoModule.h" #import "TemporaryMessageStorage.h" NSString *const backgroundNotificationTypeKey = @"backgroundNotifType"; +NSString *const messageInfosKey = @"messageInfos"; +NSString *const encryptedPayloadKey = @"encryptedPayload"; +NSString *const encryptionFailureKey = @"encryptionFailure"; +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 +31,24 @@ self.contentHandler = contentHandler; self.bestAttemptContent = [request.content mutableCopy]; + if ([self shouldBeDecrypted:self.bestAttemptContent.userInfo]) { + @try { + [self decryptBestAttemptContent]; + } @catch (NSException *e) { + comm::Logger::log( + "NSE: Received exception: " + std::string([e.name UTF8String]) + + " with reason: " + std::string([e.reason UTF8String]) + + " during notification decryption"); + self.contentHandler([[UNNotificationContent alloc] init]); + return; + } + } else if ([self shouldAlertUnencryptedNotification:self.bestAttemptContent + .userInfo]) { + // In future this will be replaced by notification content + // modification for DEV environment and staff members + comm::Logger::log("NSE: Received erroneously unencrypted notitication."); + } + [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 +75,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 +118,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 +164,85 @@ TRUE); } +- (BOOL)shouldBeDecrypted:(NSDictionary *)payload { + return payload[encryptedPayloadKey]; +} + +- (BOOL)shouldAlertUnencryptedNotification:(NSDictionary *)payload { + return payload[encryptionFailureKey] && + [payload[encryptionFailureKey] 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 { + NSString *decryptedSerializedPayload = [self + singleDecrypt:self.bestAttemptContent.userInfo[encryptedPayloadKey]]; + NSDictionary *decryptedPayload = [NSJSONSerialization + JSONObjectWithData:[decryptedSerializedPayload + dataUsingEncoding:NSUTF8StringEncoding] + options:0 + error:nil]; + + NSMutableDictionary *mutableUserInfo = + [self.bestAttemptContent.userInfo mutableCopy]; + + NSMutableDictionary *mutableAps = nil; + if (mutableUserInfo[@"aps"]) { + mutableAps = [mutableUserInfo[@"aps"] mutableCopy]; + } + + NSString *body = decryptedPayload[@"merged"]; + if (body) { + self.bestAttemptContent.body = body; + if (mutableAps && mutableAps[@"alert"]) { + mutableAps[@"alert"] = body; + } + } + + NSString *threadID = decryptedPayload[@"threadID"]; + if (threadID) { + self.bestAttemptContent.threadIdentifier = threadID; + mutableUserInfo[@"threadID"] = threadID; + if (mutableAps) { + mutableAps[@"thread-id"] = threadID; + } + } + + NSString *badgeStr = decryptedPayload[@"badge"]; + if (badgeStr) { + NSNumber *badge = @([badgeStr intValue]); + self.bestAttemptContent.badge = badge; + if (mutableAps) { + mutableAps[@"badge"] = badge; + } + } + + // The rest have been already decrypted and handled. + static NSArray *handledKeys = + @[ @"merged", @"badge", @"threadID" ]; + + for (NSString *payloadKey in decryptedPayload) { + if ([handledKeys containsObject:payloadKey]) { + continue; + } + mutableUserInfo[payloadKey] = decryptedPayload[payloadKey]; + } + + if (mutableAps) { + mutableUserInfo[@"aps"] = mutableAps; + } + [mutableUserInfo removeObjectForKey:encryptedPayloadKey]; + self.bestAttemptContent.userInfo = mutableUserInfo; +} + @end