diff --git a/native/cpp/CommonCpp/DatabaseManagers/DatabaseManager.cpp b/native/cpp/CommonCpp/DatabaseManagers/DatabaseManager.cpp --- a/native/cpp/CommonCpp/DatabaseManagers/DatabaseManager.cpp +++ b/native/cpp/CommonCpp/DatabaseManagers/DatabaseManager.cpp @@ -1,5 +1,8 @@ #include "DatabaseManager.h" #include "../Notifications/BackgroundDataStorage/NotificationsCryptoModule.h" +#ifdef __APPLE__ +#include "../Tools/CommMMKV.h" +#endif #include "../Tools/CommSecureStore.h" #include "Logger.h" #include "PlatformSpecificTools.h" @@ -36,6 +39,9 @@ CommSecureStore::set(CommSecureStore::commServicesAccessToken, ""); SQLiteQueryExecutor::clearSensitiveData(); PlatformSpecificTools::removeBackupDirectory(); +#ifdef __APPLE__ + CommMMKV::clearSensitiveData(); +#endif NotificationsCryptoModule::clearSensitiveData(); DatabaseManager::setDatabaseStatusAsWorkable(); } diff --git a/native/cpp/CommonCpp/Tools/CommMMKV.h b/native/cpp/CommonCpp/Tools/CommMMKV.h new file mode 100644 --- /dev/null +++ b/native/cpp/CommonCpp/Tools/CommMMKV.h @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + +namespace comm { +class CommMMKV { + +public: + static void initialize(); + static void clearSensitiveData(); + static bool setString(std::string key, std::string value); + static std::optional getString(std::string key); +}; +} // namespace comm 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 @@ -85,6 +85,8 @@ CB90951F29534B32002F2A7F /* CommSecureStore.mm in Sources */ = {isa = PBXBuildFile; fileRef = 71D4D7CB26C50B1000FCDBCD /* CommSecureStore.mm */; }; CBA5F8852B6979F7005BE700 /* SQLiteConnectionManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CBA5F8842B6979ED005BE700 /* SQLiteConnectionManager.cpp */; }; CBAAA4702B459181007599DA /* BackupOperationsExecutor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CBAAA46E2B459181007599DA /* BackupOperationsExecutor.cpp */; }; + CBB0DF602B768007008E22FF /* CommMMKV.mm in Sources */ = {isa = PBXBuildFile; fileRef = CBB0DF5F2B768007008E22FF /* CommMMKV.mm */; }; + 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 */; }; CBDEC69B28ED867000C17588 /* GlobalDBSingleton.mm in Sources */ = {isa = PBXBuildFile; fileRef = CBDEC69A28ED867000C17588 /* GlobalDBSingleton.mm */; }; @@ -295,6 +297,8 @@ CBA784382B28AC4300E9F419 /* CommServicesAuthMetadataEmitter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CommServicesAuthMetadataEmitter.h; sourceTree = ""; }; CBAAA46E2B459181007599DA /* BackupOperationsExecutor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = BackupOperationsExecutor.cpp; path = PersistentStorageUtilities/BackupOperationsUtilities/BackupOperationsExecutor.cpp; sourceTree = ""; }; CBAAA46F2B459181007599DA /* BackupOperationsExecutor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BackupOperationsExecutor.h; path = PersistentStorageUtilities/BackupOperationsUtilities/BackupOperationsExecutor.h; sourceTree = ""; }; + CBB0DF5E2B767FDF008E22FF /* CommMMKV.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CommMMKV.h; sourceTree = ""; }; + CBB0DF5F2B768007008E22FF /* CommMMKV.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = CommMMKV.mm; path = Comm/CommMMKV.mm; sourceTree = ""; }; 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 = ""; }; @@ -396,6 +400,7 @@ 71B8CCB626BD30EC0040C0A2 /* CommCoreImplementations */ = { isa = PBXGroup; children = ( + CBB0DF5F2B768007008E22FF /* CommMMKV.mm */, CB74AB1B2B2AFF6E00CBB494 /* CommServicesAuthMetadataEmitter.mm */, DFD5E77D2B05264000C32B6A /* AESCrypto.mm */, CBCF57A92B05091D00EC4BC0 /* CommAESCryptoUtils */, @@ -440,6 +445,7 @@ 71BE84382636A944002849D2 /* Tools */ = { isa = PBXGroup; children = ( + CBB0DF5E2B767FDF008E22FF /* CommMMKV.h */, DFD5E7802B05264F00C32B6A /* AESCrypto.h */, CBCA09052A8E0E6B00F75B3E /* StaffUtils.cpp */, CBCA09042A8E0E6B00F75B3E /* StaffUtils.h */, @@ -1142,6 +1148,7 @@ 718DE99E2653D41C00365824 /* WorkerThread.cpp in Sources */, 8B99BAAE28D511FF00EB5ADB /* lib.rs.cc in Sources */, 71CA4AEC262F236100835C89 /* Tools.mm in Sources */, + CBB0DF602B768007008E22FF /* CommMMKV.mm in Sources */, 71762A75270D8AAE00F565ED /* PlatformSpecificTools.mm in Sources */, 71BF5B7126B3FF0900EDE27D /* Session.cpp in Sources */, 8EF7756E2A7513F40046A385 /* MessageStore.cpp in Sources */, @@ -1193,6 +1200,7 @@ CB1648AF27CFBE6A00394D9D /* CryptoModule.cpp in Sources */, CB4821AE27CFB187001AB7E1 /* Tools.cpp in Sources */, CB4821AC27CFB17C001AB7E1 /* Session.cpp in Sources */, + CBB0DF612B768007008E22FF /* CommMMKV.mm in Sources */, CB4821A927CFB153001AB7E1 /* WorkerThread.cpp in Sources */, CB4821AA27CFB153001AB7E1 /* Tools.mm in Sources */, CB3C621227CE65030054F24C /* CommSecureStoreIOSWrapper.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,6 +40,7 @@ #import "CommConstants.h" #import "CommCoreModule.h" +#import "CommMMKV.h" #import "CommRustModule.h" #import "CommUtilsModule.h" #import "GlobalDBSingleton.h" @@ -88,6 +89,7 @@ willFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [self attemptDatabaseInitialization]; [self registerForNewMessageInfosNotifications]; + comm::CommMMKV::initialize(); return YES; } diff --git a/native/ios/Comm/CommMMKV.mm b/native/ios/Comm/CommMMKV.mm new file mode 100644 --- /dev/null +++ b/native/ios/Comm/CommMMKV.mm @@ -0,0 +1,133 @@ +#import "CommMMKV.h" +#import "../../cpp/CommonCpp/CryptoTools/Tools.h" +#import "CommSecureStore.h" +#import "Logger.h" +#import "Tools.h" + +#import +#import + +namespace comm { + +const int mmkvEncryptionKeySize = 16; +const int mmkvIDsize = 8; + +const std::string secureStoreMMKVEncryptionKeyID = "comm.mmkvEncryptionKey"; +const std::string secureStoreMMKVIdentifierKeyID = "comm.mmkvID"; + +static NSString *mmkvEncryptionKey; +static NSString *mmkvIdentifier; + +MMKV *getMMKVInstance(NSString *mmkvID, NSString *encryptionKey) { + MMKV *mmkv = + [MMKV mmkvWithID:mmkvID + cryptKey:[encryptionKey dataUsingEncoding:NSUTF8StringEncoding] + mode:MMKVMultiProcess]; + if (!mmkv) { + throw std::runtime_error("Failed to instantiate MMKV object."); + } + return mmkv; +} + +void assignInitializationData() { + std::string encryptionKey = + crypto::Tools::generateRandomString(mmkvEncryptionKeySize); + std::string identifier = crypto::Tools::generateRandomString(mmkvIDsize); + CommSecureStore::set(secureStoreMMKVEncryptionKeyID, encryptionKey); + CommSecureStore::set(secureStoreMMKVIdentifierKeyID, identifier); + mmkvEncryptionKey = [NSString stringWithCString:encryptionKey.c_str() + encoding:NSUTF8StringEncoding]; + mmkvIdentifier = [NSString stringWithCString:identifier.c_str() + encoding:NSUTF8StringEncoding]; +} + +void CommMMKV::initialize() { + // This way of checking if we are running in app extension is + // taken from MMKV implementation. See the code linked below: + // https://github.com/Tencent/MMKV/blob/master/iOS/MMKV/MMKV/libMMKV.mm#L109 + bool isRunningInAppExtension = + [[[NSBundle mainBundle] bundlePath] hasSuffix:@".appex"]; + + void (^initializeBlock)(void) = ^{ + auto maybeEncryptionKey = + CommSecureStore::get(secureStoreMMKVEncryptionKeyID); + auto maybeIdentifier = CommSecureStore::get(secureStoreMMKVIdentifierKeyID); + + if (maybeEncryptionKey.hasValue() && maybeIdentifier.hasValue()) { + mmkvEncryptionKey = + [NSString stringWithCString:maybeEncryptionKey.value().c_str() + encoding:NSUTF8StringEncoding]; + mmkvIdentifier = + [NSString stringWithCString:maybeIdentifier.value().c_str() + encoding:NSUTF8StringEncoding]; + } else if (!isRunningInAppExtension) { + assignInitializationData(); + } else { + throw std::runtime_error("NSE can't initialize MMKV encryption key."); + } + + [MMKV initializeMMKV:nil + groupDir:[Tools getAppGroupDirectoryPath] + logLevel:MMKVLogNone]; + + getMMKVInstance(mmkvIdentifier, mmkvEncryptionKey); + }; + + if (isRunningInAppExtension) { + initializeBlock(); + return; + } + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, initializeBlock); +} + +void CommMMKV::clearSensitiveData() { + CommMMKV::initialize(); + + @synchronized(mmkvEncryptionKey) { + MMKV *mmkv = getMMKVInstance(mmkvIdentifier, mmkvEncryptionKey); + [mmkv clearAll]; + BOOL storageRemoved = [MMKV removeStorage:mmkvIdentifier + mode:MMKVMultiProcess]; + if (!storageRemoved) { + throw std::runtime_error("Failed to remove mmkv storage."); + } + + assignInitializationData(); + [MMKV initializeMMKV:nil + groupDir:[Tools getAppGroupDirectoryPath] + logLevel:MMKVLogNone]; + getMMKVInstance(mmkvIdentifier, mmkvEncryptionKey); + } +} + +bool CommMMKV::setString(std::string key, std::string value) { + CommMMKV::initialize(); + MMKV *mmkv = getMMKVInstance(mmkvIdentifier, mmkvEncryptionKey); + + BOOL result = + [mmkv setString:[NSString stringWithCString:value.c_str() + encoding:NSUTF8StringEncoding] + forKey:[NSString stringWithCString:key.c_str() + encoding:NSUTF8StringEncoding]]; + if (!result) { + Logger::log("Attempt to write in background or failure during write."); + } + return result; +} + +std::optional CommMMKV::getString(std::string key) { + CommMMKV::initialize(); + MMKV *mmkv = getMMKVInstance(mmkvIdentifier, mmkvEncryptionKey); + + NSString *value = + [mmkv getStringForKey:[NSString stringWithCString:key.c_str() + encoding:NSUTF8StringEncoding]]; + if (!value) { + return std::nullopt; + } + return std::string([value UTF8String]); +} + +} // namespace comm diff --git a/native/ios/Comm/Tools.h b/native/ios/Comm/Tools.h --- a/native/ios/Comm/Tools.h +++ b/native/ios/Comm/Tools.h @@ -4,5 +4,6 @@ @interface Tools : NSObject + (NSString *)getSQLiteFilePath; ++ (NSString *)getAppGroupDirectoryPath; + (NSString *)getAppGroupSQLiteFilePath; @end diff --git a/native/ios/Comm/Tools.mm b/native/ios/Comm/Tools.mm --- a/native/ios/Comm/Tools.mm +++ b/native/ios/Comm/Tools.mm @@ -2,6 +2,10 @@ #import #import +@interface Tools () ++ (NSURL *)getAppGroupDirectoryURL; +@end + @implementation Tools + (NSString *)getSQLiteFilePath { @@ -22,13 +26,23 @@ return [documentsUrl URLByAppendingPathComponent:@"comm.sqlite"].path; } -+ (NSString *)getAppGroupSQLiteFilePath { ++ (NSURL *)getAppGroupDirectoryURL { NSURL *groupUrl = [NSFileManager.defaultManager containerURLForSecurityApplicationGroupIdentifier:@"group.app.comm"]; if (groupUrl == nil) { throw std::runtime_error( - "Failed to resolve database path - could not find groupUrl"); + "Failed to resolve app group path - could not find groupUrl"); } + return groupUrl; +} + ++ (NSString *)getAppGroupDirectoryPath { + NSURL *groupUrl = [Tools getAppGroupDirectoryURL]; + return groupUrl.path; +} + ++ (NSString *)getAppGroupSQLiteFilePath { + NSURL *groupUrl = [Tools getAppGroupDirectoryURL]; return [groupUrl URLByAppendingPathComponent:@"comm.sqlite"].path; } diff --git a/native/ios/Podfile b/native/ios/Podfile --- a/native/ios/Podfile +++ b/native/ios/Podfile @@ -46,6 +46,7 @@ common_comm_target_pods pod 'ReactNativeKeyboardTrackingView', :path => '../../node_modules/react-native-keyboard-tracking-view' pod 'ReactNativeKeyboardInput', :path => '../node_modules/react-native-keyboard-input' + pod 'MMKV' react_native_config end @@ -55,6 +56,7 @@ pod 'OLMKit', :path => "../node_modules/olm" pod 'RCT-Folly', :podspec => "../../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec" pod 'CommExpoPackage', :path => "../expo-modules/comm-expo-package/ios" + pod 'MMKVAppExtension' react_native_config end diff --git a/native/ios/Podfile.lock b/native/ios/Podfile.lock --- a/native/ios/Podfile.lock +++ b/native/ios/Podfile.lock @@ -158,6 +158,11 @@ - lottie-react-native (5.1.4): - lottie-ios (~> 3.4.0) - React-Core + - MMKV (1.3.3): + - MMKVCore (~> 1.3.3) + - MMKVAppExtension (1.3.3): + - MMKVCore (~> 1.3.3) + - MMKVCore (1.3.3) - mobile-ffmpeg-min (4.3.1.LTS) - OLMKit (3.2.14): - OLMKit/olmc (= 3.2.14) @@ -604,6 +609,8 @@ - hermes-engine (from `../../node_modules/react-native/sdks/hermes/hermes-engine.podspec`) - libevent (~> 2.1.12) - lottie-react-native (from `../node_modules/lottie-react-native`) + - MMKV + - MMKVAppExtension - OLMKit (from `../node_modules/olm`) - RCT-Folly (from `../../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) - RCTRequired (from `../../node_modules/react-native/Libraries/RCTRequired`) @@ -668,6 +675,9 @@ - libvmaf - libwebp - lottie-ios + - MMKV + - MMKVAppExtension + - MMKVCore - mobile-ffmpeg-min - OpenSSL-Universal - SDWebImage @@ -885,6 +895,9 @@ libwebp: f62cb61d0a484ba548448a4bd52aabf150ff6eef lottie-ios: 8f97d3271e155c2d688875c29cd3c74908aef5f8 lottie-react-native: b702fab740cdb952a8e2354713d3beda63ff97b0 + MMKV: f902fb6719da13c2ab0965233d8963a59416f911 + MMKVAppExtension: fcf23c6b250cc87db63507bc57be8e6ed378168d + MMKVCore: d26e4d3edd5cb8588c2569222cbd8be4231374e9 mobile-ffmpeg-min: d5d22dcef5c8ec56f771258f1f5be245d914f193 OLMKit: a13e1a20579e88d03971c5821360be24949a1a09 OpenSSL-Universal: 84efb8a29841f2764ac5403e0c4119a28b713346 @@ -946,6 +959,6 @@ Yoga: dc109b79db907f0f589fc423e991b09ec42d2295 ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb -PODFILE CHECKSUM: 8b75c667b4679ba1a92a975823401ba7614ea7a1 +PODFILE CHECKSUM: fa3cbc5d9308882a5b32f37839d5ed4c00a41eab COCOAPODS: 1.14.3