Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save marcinwasowicz/442f91ecb3e9781ab4e6e997692e3a46 to your computer and use it in GitHub Desktop.
Save marcinwasowicz/442f91ecb3e9781ab4e6e997692e3a46 to your computer and use it in GitHub Desktop.
test ASE in NSE
#import "NotificationService.h"
#import "Logger.h"
#import "NotificationsCryptoModule.h"
#import "TemporaryMessageStorage.h"
#import <CommExpoPackage/CommExpoPackageObjCCompat.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);
CFStringRef newMessageInfosDarwinNotification =
CFSTR("app.comm.darwin_new_message_infos");
@interface NotificationService ()
@property(nonatomic, strong) void (^contentHandler)
(UNNotificationContent *contentToDeliver);
@property(nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;
@end
@implementation NotificationService
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request
withContentHandler:
(void (^)(UNNotificationContent *_Nonnull))
contentHandler {
AESCryptoModuleObjCCompat *obj = [[AESCryptoModuleObjCCompat alloc] init];
NSError *keygenError = nil;
NSMutableData *rawKey = [NSMutableData dataWithLength:32];
[obj generateKey:rawKey withError:&keygenError];
if (keygenError) {
comm::Logger::log(
"Failed to generate key: " +
std::string([keygenError.localizedDescription UTF8String]));
}
NSError *encryptError = nil;
NSData *plaintext =
[@"test_data!" dataUsingEncoding:NSUTF8StringEncoding];
NSMutableData *sealedData =
[NSMutableData dataWithLength:plaintext.length + 28];
[obj encryptWithKey:rawKey
plaintext:plaintext
destination:sealedData
withError:&encryptError];
if (encryptError) {
comm::Logger::log(
"Failed to encrypt data: " +
std::string([encryptError.localizedDescription UTF8String]));
}
NSError *decryptError = nil;
NSMutableData *destination = [NSMutableData dataWithLength:plaintext.length];
[obj decryptWithKey:rawKey
sealedData:sealedData
destination:destination
withError:&decryptError];
if (decryptError) {
comm::Logger::log(
"Failed to decrypt data: " +
std::string([decryptError.localizedDescription UTF8String]));
}
comm::Logger::log(
"Decrypted data: " +
std::string(
[[[NSString alloc] initWithData:destination
encoding:NSUTF8StringEncoding] UTF8String]));
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.
if ([self isRescind:self.bestAttemptContent.userInfo]) {
[self removeNotificationWithIdentifier:self.bestAttemptContent
.userInfo[@"notificationId"]];
self.contentHandler([[UNNotificationContent alloc] init]);
return;
}
[self sendNewMessageInfosNotification];
// TODO modify self.bestAttemptContent here
self.contentHandler(self.bestAttemptContent);
}
- (void)serviceExtensionTimeWillExpire {
// Called just before the extension will be terminated by the system.
// Use this as an opportunity to deliver your "best attempt" at modified
// content, otherwise the original push payload will be used.
if ([self isRescind:self.bestAttemptContent.userInfo]) {
// If we get to this place it means we were unable to
// remove relevant notification from notification center in
// in time given to NSE to process notification.
// 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);
}
- (void)removeNotificationWithIdentifier:(NSString *)identifier {
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
void (^delayedSemaphorePostCallback)() = ^() {
dispatch_time_t timeToPostSemaphore =
dispatch_time(DISPATCH_TIME_NOW, notificationRemovalDelay);
dispatch_after(timeToPostSemaphore, dispatch_get_main_queue(), ^{
dispatch_semaphore_signal(semaphore);
});
};
[UNUserNotificationCenter.currentNotificationCenter
getDeliveredNotificationsWithCompletionHandler:^(
NSArray<UNNotification *> *_Nonnull notifications) {
for (UNNotification *notif in notifications) {
if ([identifier isEqual:notif.request.content.userInfo[@"id"]]) {
[UNUserNotificationCenter.currentNotificationCenter
removeDeliveredNotificationsWithIdentifiers:@[
notif.request.identifier
]];
}
}
delayedSemaphorePostCallback();
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
- (void)persistMessagePayload:(NSDictionary *)payload {
if (payload[messageInfosKey]) {
TemporaryMessageStorage *temporaryStorage =
[[TemporaryMessageStorage alloc] init];
[temporaryStorage writeMessage:payload[messageInfosKey]];
return;
}
if (![self isRescind:payload]) {
return;
}
NSError *jsonError = nil;
NSData *binarySerializedRescindPayload =
[NSJSONSerialization dataWithJSONObject:payload
options:0
error:&jsonError];
if (jsonError) {
comm::Logger::log(
"NSE: Failed to serialize rescind payload. Details: " +
std::string([jsonError.localizedDescription UTF8String]));
return;
}
NSString *serializedRescindPayload =
[[NSString alloc] initWithData:binarySerializedRescindPayload
encoding:NSUTF8StringEncoding];
TemporaryMessageStorage *temporaryRescindsStorage =
[[TemporaryMessageStorage alloc] initForRescinds];
[temporaryRescindsStorage writeMessage:serializedRescindPayload];
}
- (BOOL)isRescind:(NSDictionary *)payload {
return payload[backgroundNotificationTypeKey] &&
[payload[backgroundNotificationTypeKey] isEqualToString:@"CLEAR"];
}
- (void)sendNewMessageInfosNotification {
CFNotificationCenterPostNotification(
CFNotificationCenterGetDarwinNotifyCenter(),
newMessageInfosDarwinNotification,
(__bridge const void *)(self),
nil,
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<NSString *> *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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment