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 @@ -97,8 +97,8 @@ CBB0DF612B768007008E22FF /* CommMMKV.mm in Sources */ = {isa = PBXBuildFile; fileRef = CBB0DF5F2B768007008E22FF /* CommMMKV.mm */; }; CBCA09062A8E0E7400F75B3E /* StaffUtils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CBCA09052A8E0E6B00F75B3E /* StaffUtils.cpp */; }; CBCA09072A8E0E7D00F75B3E /* StaffUtils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CBCA09052A8E0E6B00F75B3E /* StaffUtils.cpp */; }; - CBCF984F2BA499DA00DBC3D9 /* CommIOSBlobClient.mm in Sources */ = {isa = PBXBuildFile; fileRef = CBCF984D2BA499DA00DBC3D9 /* CommIOSBlobClient.mm */; }; - CBCF98502BA49A0500DBC3D9 /* CommIOSBlobClient.mm in Sources */ = {isa = PBXBuildFile; fileRef = CBCF984D2BA499DA00DBC3D9 /* CommIOSBlobClient.mm */; }; + CBCF984F2BA499DA00DBC3D9 /* CommIOSServicesClient.mm in Sources */ = {isa = PBXBuildFile; fileRef = CBCF984D2BA499DA00DBC3D9 /* CommIOSServicesClient.mm */; }; + CBCF98502BA49A0500DBC3D9 /* CommIOSServicesClient.mm in Sources */ = {isa = PBXBuildFile; fileRef = CBCF984D2BA499DA00DBC3D9 /* CommIOSServicesClient.mm */; }; CBDEC69B28ED867000C17588 /* GlobalDBSingleton.mm in Sources */ = {isa = PBXBuildFile; fileRef = CBDEC69A28ED867000C17588 /* GlobalDBSingleton.mm */; }; CBFBEEBA2B4ED90600729F1D /* RustBackupExecutor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CBFBEEB82B4ED90600729F1D /* RustBackupExecutor.cpp */; }; CBFE58292885852B003B94C9 /* ThreadOperations.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CBFE58282885852B003B94C9 /* ThreadOperations.cpp */; }; @@ -332,8 +332,8 @@ CBCA09042A8E0E6B00F75B3E /* StaffUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StaffUtils.h; sourceTree = ""; }; CBCA09052A8E0E6B00F75B3E /* StaffUtils.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = StaffUtils.cpp; sourceTree = ""; }; CBCF57AB2B05096F00EC4BC0 /* AESCryptoModuleObjCCompat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AESCryptoModuleObjCCompat.h; path = Comm/CommAESCryptoUtils/AESCryptoModuleObjCCompat.h; sourceTree = ""; }; - CBCF984D2BA499DA00DBC3D9 /* CommIOSBlobClient.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = CommIOSBlobClient.mm; path = Comm/CommIOSServices/CommIOSBlobClient.mm; sourceTree = ""; }; - CBCF984E2BA499DA00DBC3D9 /* CommIOSBlobClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CommIOSBlobClient.h; path = Comm/CommIOSServices/CommIOSBlobClient.h; sourceTree = ""; }; + CBCF984D2BA499DA00DBC3D9 /* CommIOSServicesClient.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = CommIOSServicesClient.mm; path = Comm/CommIOSServices/CommIOSServicesClient.mm; sourceTree = ""; }; + CBCF984E2BA499DA00DBC3D9 /* CommIOSServicesClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CommIOSServicesClient.h; path = Comm/CommIOSServices/CommIOSServicesClient.h; sourceTree = ""; }; CBDEC69928ED859600C17588 /* GlobalDBSingleton.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GlobalDBSingleton.h; sourceTree = ""; }; CBDEC69A28ED867000C17588 /* GlobalDBSingleton.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = GlobalDBSingleton.mm; path = Comm/GlobalDBSingleton.mm; sourceTree = ""; }; CBF9DAE22B595934000EE771 /* EntityQueryHelpers.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EntityQueryHelpers.h; sourceTree = ""; }; @@ -816,8 +816,8 @@ CBCF984C2BA499C200DBC3D9 /* CommIOSServices */ = { isa = PBXGroup; children = ( - CBCF984E2BA499DA00DBC3D9 /* CommIOSBlobClient.h */, - CBCF984D2BA499DA00DBC3D9 /* CommIOSBlobClient.mm */, + CBCF984E2BA499DA00DBC3D9 /* CommIOSServicesClient.h */, + CBCF984D2BA499DA00DBC3D9 /* CommIOSServicesClient.mm */, ); name = CommIOSServices; sourceTree = ""; @@ -1201,7 +1201,7 @@ 8EF775682A74032C0046A385 /* CommRustModule.cpp in Sources */, 34055C152BAD31AC0008E713 /* SyncedMetadataStore.cpp in Sources */, 8E43C32C291E5B4A009378F5 /* TerminateApp.mm in Sources */, - CBCF984F2BA499DA00DBC3D9 /* CommIOSBlobClient.mm in Sources */, + CBCF984F2BA499DA00DBC3D9 /* CommIOSServicesClient.mm in Sources */, B3B02EBF2B8538980020D118 /* CommunityStore.cpp in Sources */, 8BC9568529FC49B00060AE4A /* JSIRust.cpp in Sources */, 8EA59BD92A73DAB000EB4F53 /* rustJSI-generated.cpp in Sources */, @@ -1262,7 +1262,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - CBCF98502BA49A0500DBC3D9 /* CommIOSBlobClient.mm in Sources */, + CBCF98502BA49A0500DBC3D9 /* CommIOSServicesClient.mm in Sources */, CBCA09072A8E0E7D00F75B3E /* StaffUtils.cpp in Sources */, CB3C0A3B2A125C8F009BD4DA /* NotificationsCryptoModule.cpp in Sources */, CB90951F29534B32002F2A7F /* CommSecureStore.mm in Sources */, diff --git a/native/ios/Comm/AppDelegate.mm b/native/ios/Comm/AppDelegate.mm --- a/native/ios/Comm/AppDelegate.mm +++ b/native/ios/Comm/AppDelegate.mm @@ -40,7 +40,7 @@ #import "CommConstants.h" #import "CommCoreModule.h" -#import "CommIOSBlobClient.h" +#import "CommIOSServicesClient.h" #import "CommMMKV.h" #import "CommRustModule.h" #import "CommUtilsModule.h" @@ -415,12 +415,12 @@ - (void)scheduleNSEBlobsDeletion { dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ - [CommIOSBlobClient.sharedInstance deleteStoredBlobs]; + [CommIOSServicesClient.sharedInstance deleteStoredBlobs]; }); } - (void)applicationWillResignActive:(UIApplication *)application { - [[CommIOSBlobClient sharedInstance] cancelOngoingRequests]; + [[CommIOSServicesClient sharedInstance] cancelOngoingRequests]; } // Copied from diff --git a/native/ios/Comm/CommIOSServices/CommIOSBlobClient.h b/native/ios/Comm/CommIOSServices/CommIOSServicesClient.h rename from native/ios/Comm/CommIOSServices/CommIOSBlobClient.h rename to native/ios/Comm/CommIOSServices/CommIOSServicesClient.h --- a/native/ios/Comm/CommIOSServices/CommIOSBlobClient.h +++ b/native/ios/Comm/CommIOSServices/CommIOSServicesClient.h @@ -1,8 +1,10 @@ #import -@interface CommIOSBlobClient : NSObject +@interface CommIOSServicesClient : NSObject + (id)sharedInstance; - (NSData *)getBlobSync:(NSString *)blobHash orSetError:(NSError **)error; +- (NSDictionary *)getNotifsIdentityKeysFor:(NSString *)deviceID + orSetError:(NSError **)error; - (void)deleteBlobAsyncWithHash:(NSString *)blobHash andHolder:(NSString *)blobHolder withSuccessHandler:(void (^)())successHandler diff --git a/native/ios/Comm/CommIOSServices/CommIOSBlobClient.mm b/native/ios/Comm/CommIOSServices/CommIOSServicesClient.mm rename from native/ios/Comm/CommIOSServices/CommIOSBlobClient.mm rename to native/ios/Comm/CommIOSServices/CommIOSServicesClient.mm --- a/native/ios/Comm/CommIOSServices/CommIOSBlobClient.mm +++ b/native/ios/Comm/CommIOSServices/CommIOSServicesClient.mm @@ -1,4 +1,4 @@ -#import "CommIOSBlobClient.h" +#import "CommIOSServicesClient.h" #import "CommMMKV.h" #import "CommSecureStore.h" #import "Logger.h" @@ -6,46 +6,50 @@ #ifdef DEBUG NSString const *blobServiceAddress = @"https://blob.staging.commtechnologies.org"; +NSString const *identityServiceAddress = + @"https://identity.staging.commtechnologies.org:51004"; #else NSString const *blobServiceAddress = @"https://blob.commtechnologies.org"; +NSString const *identityServiceAddress = + @"https://identity.commtechnologies.org:51004"; #endif -int const blobServiceQueryTimeLimit = 15; +int const servicesQueryTimeLimit = 15; const std::string mmkvBlobHolderPrefix = "BLOB_HOLDER."; // The blob service expects slightly different keys in // delete reuqest payload than we use in notif payload NSString *const blobServiceHashKey = @"blob_hash"; NSString *const blobServiceHolderKey = @"holder"; -@interface CommIOSBlobClient () -@property(nonatomic, strong) NSURLSession *sharedBlobServiceSession; +@interface CommIOSServicesClient () +@property(nonatomic, strong) NSURLSession *sharedServicesSession; @end -@implementation CommIOSBlobClient +@implementation CommIOSServicesClient + (id)sharedInstance { - static CommIOSBlobClient *sharedBlobServiceClient = nil; + static CommIOSServicesClient *sharedServicesClient = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSURLSessionConfiguration *config = [NSURLSessionConfiguration ephemeralSessionConfiguration]; - [config setTimeoutIntervalForRequest:blobServiceQueryTimeLimit]; + [config setTimeoutIntervalForRequest:servicesQueryTimeLimit]; NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:nil delegateQueue:[NSOperationQueue mainQueue]]; - sharedBlobServiceClient = [[self alloc] init]; - sharedBlobServiceClient.sharedBlobServiceSession = session; + sharedServicesClient = [[self alloc] init]; + sharedServicesClient.sharedServicesSession = session; }); - return sharedBlobServiceClient; + return sharedServicesClient; } - (NSData *)getBlobSync:(NSString *)blobHash orSetError:(NSError **)error { NSError *authTokenError = nil; NSString *authToken = - [CommIOSBlobClient _getAuthTokenOrSetError:&authTokenError]; + [CommIOSServicesClient _getAuthTokenOrSetError:&authTokenError]; if (authTokenError) { *error = authTokenError; @@ -71,7 +75,7 @@ __block NSData *blobContent = nil; dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); - NSURLSessionDataTask *task = [self.sharedBlobServiceSession + NSURLSessionDataTask *task = [self.sharedServicesSession dataTaskWithRequest:blobRequest completionHandler:^( NSData *_Nullable data, @@ -109,8 +113,7 @@ dispatch_semaphore_wait( semaphore, dispatch_time( - DISPATCH_TIME_NOW, - (int64_t)(blobServiceQueryTimeLimit * NSEC_PER_SEC))); + DISPATCH_TIME_NOW, (int64_t)(servicesQueryTimeLimit * NSEC_PER_SEC))); if (requestError) { *error = requestError; return nil; @@ -118,13 +121,156 @@ return blobContent; } +- (NSDictionary *)getNotifsIdentityKeysFor:(NSString *)deviceID + orSetError:(NSError *__autoreleasing *)error { + NSError *authTokenError = nil; + NSString *authToken = + [CommIOSServicesClient _getAuthTokenOrSetError:&authTokenError]; + + if (authTokenError) { + *error = authTokenError; + return nil; + } + + NSString *base64URLEncodedDeviceID = + [[deviceID stringByReplacingOccurrencesOfString:@"+" withString:@"-"] + stringByReplacingOccurrencesOfString:@"/" + withString:@"_"]; + + NSString *urlString = + [NSString stringWithFormat:@"%@/device_inbound_keys?device_id=%@", + identityServiceAddress, + base64URLEncodedDeviceID]; + NSURL *url = [NSURL URLWithString:urlString]; + + NSMutableURLRequest *identityRequest = + [[NSMutableURLRequest alloc] initWithURL:url]; + [identityRequest setHTTPMethod:@"GET"]; + + // This is slightly against Apple docs: + // https://developer.apple.com/documentation/foundation/nsurlrequest?language=objc#1776617 + // but apparently there is no other way to + // do this and even Apple staff members + // advice to set this field manually to + // achieve token based authentication: + // https://developer.apple.com/forums/thread/89811 + [identityRequest setValue:authToken forHTTPHeaderField:@"Authorization"]; + + __block NSError *requestError = nil; + __block NSDictionary *notifIdentityKeys = nil; + + dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); + NSURLSessionDataTask *task = [self.sharedServicesSession + dataTaskWithRequest:identityRequest + completionHandler:^( + NSData *_Nullable data, + NSURLResponse *_Nullable response, + NSError *_Nullable error) { + @try { + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; + if (httpResponse.statusCode > 299) { + NSString *errorMessage = + [@"Fetching notifs identity key failed with the following " + @"reason: " + stringByAppendingString:[NSHTTPURLResponse + localizedStringForStatusCode: + httpResponse.statusCode]]; + requestError = [NSError + errorWithDomain:@"app.comm" + code:httpResponse.statusCode + userInfo:@{NSLocalizedDescriptionKey : errorMessage}]; + return; + } + if (error) { + requestError = error; + return; + } + + NSError *jsonError; + NSDictionary *responseObject = + [NSJSONSerialization JSONObjectWithData:data + options:0 + error:&jsonError]; + + if (jsonError) { + requestError = jsonError; + return; + } + + if (!responseObject[@"identityKeyInfo"] || + !responseObject[@"identityKeyInfo"][@"keyPayload"]) { + NSString *errorMessage = + [@"identityKeyInfo or keyPayload missing in identity service " + @"response" + stringByAppendingString:[NSHTTPURLResponse + localizedStringForStatusCode: + httpResponse.statusCode]]; + requestError = [NSError + errorWithDomain:@"app.comm" + code:httpResponse.statusCode + userInfo:@{NSLocalizedDescriptionKey : errorMessage}]; + return; + } + + NSData *keyPayload = + [responseObject[@"identityKeyInfo"][@"keyPayload"] + dataUsingEncoding:NSUTF8StringEncoding]; + NSDictionary *identityKeys = + [NSJSONSerialization JSONObjectWithData:keyPayload + options:0 + error:&jsonError]; + + if (jsonError) { + requestError = jsonError; + return; + } + + if (!identityKeys[@"notificationIdentityPublicKeys"]) { + NSString *errorMessage = + [@"notificationIdentityPublicKeys missing in identity " + @"service response" + stringByAppendingString:[NSHTTPURLResponse + localizedStringForStatusCode: + httpResponse.statusCode]]; + requestError = [NSError + errorWithDomain:@"app.comm" + code:httpResponse.statusCode + userInfo:@{NSLocalizedDescriptionKey : errorMessage}]; + return; + } + + notifIdentityKeys = identityKeys[@"notificationIdentityPublicKeys"]; + + } @catch (NSException *exception) { + comm::Logger::log( + "Received exception when fetching notifs identity key. " + "Details: " + + std::string([exception.reason UTF8String])); + } @finally { + dispatch_semaphore_signal(semaphore); + } + }]; + + [task resume]; + dispatch_semaphore_wait( + semaphore, + dispatch_time( + DISPATCH_TIME_NOW, (int64_t)(servicesQueryTimeLimit * NSEC_PER_SEC))); + if (requestError) { + *error = requestError; + return nil; + } + + return notifIdentityKeys; +} + - (void)deleteBlobAsyncWithHash:(NSString *)blobHash andHolder:(NSString *)blobHolder withSuccessHandler:(void (^)())successHandler andFailureHandler:(void (^)(NSError *))failureHandler { NSError *authTokenError = nil; NSString *authToken = - [CommIOSBlobClient _getAuthTokenOrSetError:&authTokenError]; + [CommIOSServicesClient _getAuthTokenOrSetError:&authTokenError]; if (authTokenError) { comm::Logger::log( @@ -151,7 +297,7 @@ options:0 error:nil]; - NSURLSessionDataTask *task = [self.sharedBlobServiceSession + NSURLSessionDataTask *task = [self.sharedServicesSession dataTaskWithRequest:deleteRequest completionHandler:^( NSData *_Nullable data, @@ -232,7 +378,7 @@ } - (void)cancelOngoingRequests { - [self.sharedBlobServiceSession + [self.sharedServicesSession getAllTasksWithCompletionHandler:^( NSArray<__kindof NSURLSessionTask *> *_Nonnull tasks) { for (NSURLSessionTask *task in tasks) { 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,6 +1,6 @@ #import "NotificationService.h" #import "AESCryptoModuleObjCCompat.h" -#import "CommIOSBlobClient.h" +#import "CommIOSServicesClient.h" #import "CommMMKV.h" #import "Logger.h" #import "NotificationsCryptoModule.h" @@ -550,8 +550,8 @@ __block NSError *fetchError = nil; NSData *largePayloadBinary = - [CommIOSBlobClient.sharedInstance getBlobSync:blobHash - orSetError:&fetchError]; + [CommIOSServicesClient.sharedInstance getBlobSync:blobHash + orSetError:&fetchError]; if (fetchError) { comm::Logger::log( @@ -564,7 +564,7 @@ [NotificationService aesDecryptAndParse:largePayloadBinary withKey:encryptionKey]; [self persistMessagePayload:largePayload]; - [CommIOSBlobClient.sharedInstance + [CommIOSServicesClient.sharedInstance storeBlobForDeletionWithHash:blobHash andHolder:content.userInfo[blobHolderKey]]; }