diff --git a/native/android/app/src/main/java/app/comm/android/fbjni/CommMMKV.java b/native/android/app/src/main/java/app/comm/android/fbjni/CommMMKV.java --- a/native/android/app/src/main/java/app/comm/android/fbjni/CommMMKV.java +++ b/native/android/app/src/main/java/app/comm/android/fbjni/CommMMKV.java @@ -20,8 +20,7 @@ private static String mmkvIdentifier; private static MMKV getMMKVInstance(String mmkvID, String encryptionKey) { - MMKV mmkv = - MMKV.mmkvWithID(mmkvID, MMKV.SINGLE_PROCESS_MODE, encryptionKey); + MMKV mmkv = MMKV.mmkvWithID(mmkvID, MMKV.MULTI_PROCESS_MODE, encryptionKey); if (mmkv == null) { throw new RuntimeException("Failed to instantiate MMKV object."); } diff --git a/native/cpp/CommonCpp/NativeModules/CommCoreModule.h b/native/cpp/CommonCpp/NativeModules/CommCoreModule.h --- a/native/cpp/CommonCpp/NativeModules/CommCoreModule.h +++ b/native/cpp/CommonCpp/NativeModules/CommCoreModule.h @@ -36,7 +36,7 @@ const std::string publicCryptoAccountID = "publicCryptoAccountID"; std::unique_ptr contentCryptoModule; const std::string notifsCryptoAccountID = "notifsCryptoAccountID"; - std::unique_ptr notifsCryptoModule; + DraftStore draftStore; ThreadStore threadStore; MessageStore messageStore; @@ -51,8 +51,11 @@ EntryStore entryStore; MessageSearchStore messageSearchStore; - void - persistCryptoModules(bool persistContentModule, bool persistNotifsModule); + void persistCryptoModules( + bool persistContentModule, + std::optional< + std::pair, std::string>> + maybeUpdatedNotifsCryptoModule); jsi::Value createNewBackupInternal( jsi::Runtime &rt, std::string backupSecret, diff --git a/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp b/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp --- a/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp +++ b/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp @@ -561,10 +561,11 @@ void CommCoreModule::persistCryptoModules( bool persistContentModule, - bool persistNotifsModule) { + std::optional, std::string>> + maybeUpdatedNotifsCryptoModule) { std::string storedSecretKey = getAccountDataKey(secureStoreAccountDataKey); - if (!persistContentModule && !persistNotifsModule) { + if (!persistContentModule && !maybeUpdatedNotifsCryptoModule.has_value()) { return; } @@ -573,11 +574,6 @@ newContentPersist = this->contentCryptoModule->storeAsB64(storedSecretKey); } - crypto::Persist newNotifsPersist; - if (persistNotifsModule) { - newNotifsPersist = this->notifsCryptoModule->storeAsB64(storedSecretKey); - } - std::promise persistencePromise; std::future persistenceFuture = persistencePromise.get_future(); GlobalDBSingleton::instance.scheduleOrRunCancellable( @@ -589,10 +585,10 @@ DatabaseManager::getQueryExecutor().getContentAccountID(), newContentPersist); } - if (persistNotifsModule) { - DatabaseManager::getQueryExecutor().storeOlmPersistData( - DatabaseManager::getQueryExecutor().getNotifsAccountID(), - newNotifsPersist); + if (maybeUpdatedNotifsCryptoModule.has_value()) { + NotificationsCryptoModule::persistNotificationsAccount( + maybeUpdatedNotifsCryptoModule.value().first, + maybeUpdatedNotifsCryptoModule.value().second); } DatabaseManager::getQueryExecutor().commitTransaction(); persistencePromise.set_value(); @@ -644,6 +640,7 @@ std::optional notifsAccountData = DatabaseManager::getQueryExecutor().getOlmPersistAccountData( DatabaseManager::getQueryExecutor().getNotifsAccountID()); + if (notifsAccountData.has_value()) { notifsPersist.account = crypto::OlmBuffer( notifsAccountData->begin(), notifsAccountData->end()); @@ -660,14 +657,23 @@ storedSecretKey.value(), contentPersist)); - this->notifsCryptoModule.reset(new crypto::CryptoModule( - this->notifsCryptoAccountID, - storedSecretKey.value(), - notifsPersist)); + std::optional< + std::pair, std::string>> + maybeNotifsCryptoAccountToPersist; + + if (!NotificationsCryptoModule:: + isNotificationsAccountInitialized()) { + maybeNotifsCryptoAccountToPersist = { + std::make_shared( + this->notifsCryptoAccountID, + storedSecretKey.value(), + notifsPersist), + storedSecretKey.value()}; + } try { this->persistCryptoModules( - contentPersist.isEmpty(), notifsPersist.isEmpty()); + contentPersist.isEmpty(), maybeNotifsCryptoAccountToPersist); } catch (const std::exception &e) { error = e.what(); } @@ -696,12 +702,12 @@ std::string primaryKeysResult; std::string notificationsKeysResult; if (this->contentCryptoModule == nullptr || - this->notifsCryptoModule == nullptr) { + !NotificationsCryptoModule::isNotificationsAccountInitialized()) { error = "user has not been initialized"; } else { primaryKeysResult = this->contentCryptoModule->getIdentityKeys(); notificationsKeysResult = - this->notifsCryptoModule->getIdentityKeys(); + NotificationsCryptoModule::getIdentityKeys(); } std::string notificationsCurve25519Cpp, notificationsEd25519Cpp, @@ -873,7 +879,7 @@ std::string contentResult; std::string notifResult; if (this->contentCryptoModule == nullptr || - this->notifsCryptoModule == nullptr) { + !NotificationsCryptoModule::isNotificationsAccountInitialized()) { this->jsInvoker_->invokeAsync([=, &innerRt]() { promise->reject("user has not been initialized"); }); @@ -883,9 +889,13 @@ contentResult = this->contentCryptoModule->getOneTimeKeysForPublishing( oneTimeKeysAmount); - notifResult = this->notifsCryptoModule->getOneTimeKeysForPublishing( - oneTimeKeysAmount); - this->persistCryptoModules(true, true); + std::pair, std::string> + notifsCryptoModuleWithPicklingKey = + NotificationsCryptoModule::fetchNotificationsAccount() + .value(); + notifResult = notifsCryptoModuleWithPicklingKey.first + ->getOneTimeKeysForPublishing(oneTimeKeysAmount); + this->persistCryptoModules(true, notifsCryptoModuleWithPicklingKey); } catch (const std::exception &e) { error = e.what(); } @@ -918,19 +928,25 @@ std::optional maybeNotifsPrekeyToUpload; if (this->contentCryptoModule == nullptr || - this->notifsCryptoModule == nullptr) { + !NotificationsCryptoModule::isNotificationsAccountInitialized()) { this->jsInvoker_->invokeAsync([=, &innerRt]() { promise->reject("user has not been initialized"); }); return; } + std::optional< + std::pair, std::string>> + notifsCryptoModuleWithPicklingKey; try { + notifsCryptoModuleWithPicklingKey = + NotificationsCryptoModule::fetchNotificationsAccount(); maybeContentPrekeyToUpload = this->contentCryptoModule->validatePrekey(); maybeNotifsPrekeyToUpload = - this->notifsCryptoModule->validatePrekey(); - this->persistCryptoModules(true, true); + notifsCryptoModuleWithPicklingKey.value() + .first->validatePrekey(); + this->persistCryptoModules(true, notifsCryptoModuleWithPicklingKey); if (!maybeContentPrekeyToUpload.has_value()) { maybeContentPrekeyToUpload = @@ -938,7 +954,8 @@ } if (!maybeNotifsPrekeyToUpload.has_value()) { maybeNotifsPrekeyToUpload = - this->notifsCryptoModule->getUnpublishedPrekey(); + notifsCryptoModuleWithPicklingKey.value() + .first->getUnpublishedPrekey(); } } catch (const std::exception &e) { error = e.what(); @@ -968,7 +985,8 @@ if (maybeNotifsPrekeyToUpload.has_value()) { notifsPrekeyToUpload = maybeNotifsPrekeyToUpload.value(); } else { - notifsPrekeyToUpload = this->notifsCryptoModule->getPrekey(); + notifsPrekeyToUpload = + notifsCryptoModuleWithPicklingKey.value().first->getPrekey(); } std::string prekeyUploadError; @@ -977,7 +995,8 @@ std::string contentPrekeySignature = this->contentCryptoModule->getPrekeySignature(); std::string notifsPrekeySignature = - this->notifsCryptoModule->getPrekeySignature(); + notifsCryptoModuleWithPicklingKey.value() + .first->getPrekeySignature(); try { std::promise prekeyPromise; @@ -1010,8 +1029,10 @@ if (!prekeyUploadError.size()) { this->contentCryptoModule->markPrekeyAsPublished(); - this->notifsCryptoModule->markPrekeyAsPublished(); - this->persistCryptoModules(true, true); + notifsCryptoModuleWithPicklingKey.value() + .first->markPrekeyAsPublished(); + this->persistCryptoModules( + true, notifsCryptoModuleWithPicklingKey); } } catch (std::exception &e) { error = e.what(); @@ -1044,13 +1065,19 @@ std::optional notifPrekeyBlob; if (this->contentCryptoModule == nullptr || - this->notifsCryptoModule == nullptr) { + !NotificationsCryptoModule::isNotificationsAccountInitialized()) { this->jsInvoker_->invokeAsync([=, &innerRt]() { promise->reject("user has not been initialized"); }); return; } + + std::optional< + std::pair, std::string>> + notifsCryptoModuleWithPicklingKey; try { + notifsCryptoModuleWithPicklingKey = + NotificationsCryptoModule::fetchNotificationsAccount(); contentPrekeyBlob = this->contentCryptoModule->validatePrekey(); if (!contentPrekeyBlob) { contentPrekeyBlob = @@ -1060,20 +1087,22 @@ contentPrekeyBlob = this->contentCryptoModule->getPrekey(); } - notifPrekeyBlob = this->notifsCryptoModule->validatePrekey(); + notifPrekeyBlob = notifsCryptoModuleWithPicklingKey.value() + .first->validatePrekey(); if (!notifPrekeyBlob) { - notifPrekeyBlob = - this->notifsCryptoModule->getUnpublishedPrekey(); + notifPrekeyBlob = notifsCryptoModuleWithPicklingKey.value() + .first->getUnpublishedPrekey(); } if (!notifPrekeyBlob) { - notifPrekeyBlob = this->notifsCryptoModule->getPrekey(); + notifPrekeyBlob = + notifsCryptoModuleWithPicklingKey.value().first->getPrekey(); } - this->persistCryptoModules(true, true); + this->persistCryptoModules(true, notifsCryptoModuleWithPicklingKey); contentPrekeySignature = this->contentCryptoModule->getPrekeySignature(); - notifPrekeySignature = - this->notifsCryptoModule->getPrekeySignature(); + notifPrekeySignature = notifsCryptoModuleWithPicklingKey.value() + .first->getPrekeySignature(); contentPrekey = parseOLMPrekey(contentPrekeyBlob.value()); notifPrekey = parseOLMPrekey(notifPrekeyBlob.value()); @@ -1135,36 +1164,45 @@ taskType job = [=, &innerRt]() { std::string error; crypto::EncryptedData result; + std::optional< + std::pair, std::string>> + notifsCryptoModuleWithPicklingKey; try { + notifsCryptoModuleWithPicklingKey = + NotificationsCryptoModule::fetchNotificationsAccount(); std::optional oneTimeKeyBuffer; if (oneTimeKeyCpp) { oneTimeKeyBuffer = crypto::OlmBuffer( oneTimeKeyCpp->begin(), oneTimeKeyCpp->end()); } - this->notifsCryptoModule->initializeOutboundForSendingSession( - keyserverIDCpp, - std::vector( - identityKeysCpp.begin(), identityKeysCpp.end()), - std::vector(prekeyCpp.begin(), prekeyCpp.end()), - std::vector( - prekeySignatureCpp.begin(), prekeySignatureCpp.end()), - oneTimeKeyBuffer); + notifsCryptoModuleWithPicklingKey.value() + .first->initializeOutboundForSendingSession( + keyserverIDCpp, + std::vector( + identityKeysCpp.begin(), identityKeysCpp.end()), + std::vector(prekeyCpp.begin(), prekeyCpp.end()), + std::vector( + prekeySignatureCpp.begin(), prekeySignatureCpp.end()), + oneTimeKeyBuffer); - result = this->notifsCryptoModule->encrypt( + result = notifsCryptoModuleWithPicklingKey.value().first->encrypt( keyserverIDCpp, NotificationsCryptoModule::initialEncryptedMessageContent); std::shared_ptr keyserverNotificationsSession = - this->notifsCryptoModule->getSessionByDeviceId(keyserverIDCpp); + notifsCryptoModuleWithPicklingKey.value() + .first->getSessionByDeviceId(keyserverIDCpp); NotificationsCryptoModule::persistNotificationsSession( keyserverIDCpp, keyserverNotificationsSession); // Session is removed from the account since it is persisted // at different location that the account after serialization - this->notifsCryptoModule->removeSessionByDeviceId(keyserverIDCpp); - this->persistCryptoModules(false, true); + notifsCryptoModuleWithPicklingKey.value() + .first->removeSessionByDeviceId(keyserverIDCpp); + this->persistCryptoModules( + false, notifsCryptoModuleWithPicklingKey); } catch (const std::exception &e) { error = e.what(); } @@ -1215,7 +1253,7 @@ rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=, &innerRt]() { if (this->contentCryptoModule == nullptr || - this->notifsCryptoModule == nullptr) { + !NotificationsCryptoModule::isNotificationsAccountInitialized()) { this->jsInvoker_->invokeAsync([=, &innerRt]() { promise->reject("user has not been initialized"); }); @@ -1257,7 +1295,7 @@ rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=, &innerRt]() { if (this->contentCryptoModule == nullptr || - this->notifsCryptoModule == nullptr) { + !NotificationsCryptoModule::isNotificationsAccountInitialized()) { this->jsInvoker_->invokeAsync([=, &innerRt]() { promise->reject("user has not been initialized"); }); @@ -1469,7 +1507,7 @@ initialEncryptedData = contentCryptoModule->encrypt(deviceIDCpp, initMessage); - this->persistCryptoModules(true, false); + this->persistCryptoModules(true, std::nullopt); } catch (const std::exception &e) { error = e.what(); } @@ -1526,7 +1564,7 @@ messageType}; decryptedMessage = this->contentCryptoModule->decrypt(deviceIDCpp, encryptedData); - this->persistCryptoModules(true, false); + this->persistCryptoModules(true, std::nullopt); } catch (const std::exception &e) { error = e.what(); } @@ -1554,7 +1592,7 @@ bool result; if (this->contentCryptoModule == nullptr || - this->notifsCryptoModule == nullptr) { + !NotificationsCryptoModule::isNotificationsAccountInitialized()) { this->jsInvoker_->invokeAsync([=, &innerRt]() { promise->reject("user has not been initialized"); }); @@ -1601,35 +1639,44 @@ taskType job = [=, &innerRt]() { std::string error; crypto::EncryptedData result; + std::optional< + std::pair, std::string>> + notifsCryptoModuleWithPicklingKey; try { + notifsCryptoModuleWithPicklingKey = + NotificationsCryptoModule::fetchNotificationsAccount(); std::optional oneTimeKeyBuffer; if (oneTimeKeyCpp) { oneTimeKeyBuffer = crypto::OlmBuffer( oneTimeKeyCpp->begin(), oneTimeKeyCpp->end()); } - this->notifsCryptoModule->initializeOutboundForSendingSession( - deviceIDCpp, - std::vector( - identityKeysCpp.begin(), identityKeysCpp.end()), - std::vector(prekeyCpp.begin(), prekeyCpp.end()), - std::vector( - prekeySignatureCpp.begin(), prekeySignatureCpp.end()), - oneTimeKeyBuffer); + notifsCryptoModuleWithPicklingKey.value() + .first->initializeOutboundForSendingSession( + deviceIDCpp, + std::vector( + identityKeysCpp.begin(), identityKeysCpp.end()), + std::vector(prekeyCpp.begin(), prekeyCpp.end()), + std::vector( + prekeySignatureCpp.begin(), prekeySignatureCpp.end()), + oneTimeKeyBuffer); - result = this->notifsCryptoModule->encrypt( + result = notifsCryptoModuleWithPicklingKey.value().first->encrypt( deviceIDCpp, NotificationsCryptoModule::initialEncryptedMessageContent); std::shared_ptr peerNotificationsSession = - this->notifsCryptoModule->getSessionByDeviceId(deviceIDCpp); + notifsCryptoModuleWithPicklingKey.value() + .first->getSessionByDeviceId(deviceIDCpp); NotificationsCryptoModule::persistDeviceNotificationsSession( deviceIDCpp, peerNotificationsSession); // Session is removed from the account since it is persisted // at different location that the account after serialization - this->notifsCryptoModule->removeSessionByDeviceId(deviceIDCpp); - this->persistCryptoModules(false, true); + notifsCryptoModuleWithPicklingKey.value() + .first->removeSessionByDeviceId(deviceIDCpp); + this->persistCryptoModules( + false, notifsCryptoModuleWithPicklingKey); } catch (const std::exception &e) { error = e.what(); } @@ -1660,7 +1707,7 @@ try { encryptedMessage = contentCryptoModule->encrypt(deviceIDCpp, messageCpp); - this->persistCryptoModules(true, false); + this->persistCryptoModules(true, std::nullopt); } catch (const std::exception &e) { error = e.what(); } @@ -1802,7 +1849,7 @@ messageType}; decryptedMessage = this->contentCryptoModule->decrypt(deviceIDCpp, encryptedData); - this->persistCryptoModules(true, false); + this->persistCryptoModules(true, std::nullopt); } catch (const std::exception &e) { error = e.what(); } @@ -2849,17 +2896,23 @@ std::string error; if (this->contentCryptoModule == nullptr || - this->notifsCryptoModule == nullptr) { + !NotificationsCryptoModule::isNotificationsAccountInitialized()) { this->jsInvoker_->invokeAsync([=, &innerRt]() { promise->reject("user has not been initialized"); }); return; } + std::optional< + std::pair, std::string>> + notifsCryptoModuleWithPicklingKey; try { + notifsCryptoModuleWithPicklingKey = + NotificationsCryptoModule::fetchNotificationsAccount(); this->contentCryptoModule->markPrekeyAsPublished(); - this->notifsCryptoModule->markPrekeyAsPublished(); - this->persistCryptoModules(true, true); + notifsCryptoModuleWithPicklingKey.value() + .first->markPrekeyAsPublished(); + this->persistCryptoModules(true, notifsCryptoModuleWithPicklingKey); } catch (std::exception &e) { error = e.what(); } 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 @@ -45,6 +45,8 @@ const static int olmEncryptedTypeMessage; static void clearSensitiveData(); + + // notifications sessions static void persistNotificationsSession( const std::string &keyserverID, std::shared_ptr keyserverNotificationsSession); @@ -58,6 +60,16 @@ isNotificationsSessionInitializedWithDevices( const std::vector &deviceIDs); + // notifications account + static void persistNotificationsAccount( + const std::shared_ptr cryptoModule, + const std::string &picklingKey); + static std::optional< + std::pair, std::string>> + fetchNotificationsAccount(); + static bool isNotificationsAccountInitialized(); + static std::string getIdentityKeys(); + class BaseStatefulDecryptResult { BaseStatefulDecryptResult( std::string picklingKey, 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 @@ -38,6 +38,7 @@ const std::string ashoatKeyserverIDUsedOnlyForMigrationFromLegacyNotifStorage = "256"; const int temporaryFilePathRandomSuffixLength = 32; +const std::string notificationsAccountKey = "NOTIFS.ACCOUNT"; std::unique_ptr NotificationsCryptoModule::deserializeCryptoModule( @@ -268,6 +269,62 @@ serializedSession.value()); } +void NotificationsCryptoModule::persistNotificationsAccount( + const std::shared_ptr cryptoModule, + const std::string &picklingKey) { + crypto::Persist serializedCryptoModule = + cryptoModule->storeAsB64(picklingKey); + crypto::OlmBuffer serializedAccount = serializedCryptoModule.account; + std::string serializedAccountString{ + serializedAccount.begin(), serializedAccount.end()}; + + folly::dynamic serializedAccountObject = folly::dynamic::object( + "account", serializedAccountString)("picklingKey", picklingKey); + std::string serializedAccountJson = folly::toJson(serializedAccountObject); + + bool accountPersisted = + CommMMKV::setString(notificationsAccountKey, serializedAccountJson); + + if (!accountPersisted) { + throw std::runtime_error("Failed to persist notifications crypto account."); + } +} + +std::optional, std::string>> +NotificationsCryptoModule::fetchNotificationsAccount() { + std::optional serializedAccountJson; + try { + serializedAccountJson = CommMMKV::getString(notificationsAccountKey); + } catch (const CommMMKV::InitFromNSEForbiddenError &e) { + serializedAccountJson = std::nullopt; + } + + if (!serializedAccountJson.has_value()) { + return std::nullopt; + } + + folly::dynamic serializedAccountObject; + try { + serializedAccountObject = folly::parseJson(serializedAccountJson.value()); + } catch (const folly::json::parse_error &e) { + throw std::runtime_error( + "Notifications account deserialization failed with reason: " + + std::string(e.what())); + } + + std::string picklingKey = serializedAccountObject["picklingKey"].asString(); + std::string accountString = serializedAccountObject["account"].asString(); + crypto::OlmBuffer account = + crypto::OlmBuffer{accountString.begin(), accountString.end()}; + crypto::Persist serializedCryptoModule{account, {}}; + + std::shared_ptr cryptoModule = + std::make_shared( + notificationsCryptoAccountID, picklingKey, serializedCryptoModule); + + return {{cryptoModule, picklingKey}}; +} + void NotificationsCryptoModule::persistNotificationsSession( const std::string &keyserverID, std::shared_ptr keyserverNotificationsSession) { @@ -317,6 +374,20 @@ return result; } +// notifications account + +bool NotificationsCryptoModule::isNotificationsAccountInitialized() { + return fetchNotificationsAccount().has_value(); +} + +std::string NotificationsCryptoModule::getIdentityKeys() { + auto cryptoModuleWithPicklingKey = + NotificationsCryptoModule::fetchNotificationsAccount(); + if (!cryptoModuleWithPicklingKey.has_value()) { + throw std::runtime_error("Notifications crypto account not initialized."); + } + return cryptoModuleWithPicklingKey.value().first->getIdentityKeys(); +} NotificationsCryptoModule::BaseStatefulDecryptResult::BaseStatefulDecryptResult( std::string picklingKey,