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 index 07fc9b6ae..23f81caca 100644 --- 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 @@ -1,130 +1,129 @@ package app.comm.android.fbjni; import android.util.Log; import app.comm.android.MainApplication; import app.comm.android.fbjni.CommSecureStore; import app.comm.android.fbjni.PlatformSpecificTools; import com.tencent.mmkv.MMKV; import java.util.Base64; public class CommMMKV { private static final int MMKV_ENCRYPTION_KEY_SIZE = 16; private static final int MMKV_ID_SIZE = 8; private static final String SECURE_STORE_MMKV_ENCRYPTION_KEY_ID = "comm.mmkvEncryptionKey"; private static final String SECURE_STORE_MMKV_IDENTIFIER_KEY_ID = "comm.mmkvID"; private static String mmkvEncryptionKey; 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."); } return mmkv; } private static void assignInitializationData() { byte[] encryptionKeyBytes = PlatformSpecificTools.generateSecureRandomBytes( MMKV_ENCRYPTION_KEY_SIZE); byte[] identifierBytes = PlatformSpecificTools.generateSecureRandomBytes(MMKV_ID_SIZE); String encryptionKey = Base64.getEncoder() .encodeToString(encryptionKeyBytes) .substring(0, MMKV_ENCRYPTION_KEY_SIZE); String identifier = Base64.getEncoder() .encodeToString(identifierBytes) .substring(0, MMKV_ID_SIZE); CommSecureStore.set(SECURE_STORE_MMKV_ENCRYPTION_KEY_ID, encryptionKey); CommSecureStore.set(SECURE_STORE_MMKV_IDENTIFIER_KEY_ID, identifier); mmkvEncryptionKey = encryptionKey; mmkvIdentifier = identifier; } public static void initialize() { if (mmkvEncryptionKey != null && mmkvIdentifier != null) { return; } synchronized (CommMMKV.class) { if (mmkvEncryptionKey != null && mmkvIdentifier != null) { return; } String encryptionKey = null, identifier = null; try { encryptionKey = CommSecureStore.get(SECURE_STORE_MMKV_ENCRYPTION_KEY_ID); identifier = CommSecureStore.get(SECURE_STORE_MMKV_IDENTIFIER_KEY_ID); } catch (Exception e) { Log.w("COMM", "Failed to get MMKV keys from CommSecureStore", e); } if (encryptionKey == null || identifier == null) { assignInitializationData(); } else { mmkvEncryptionKey = encryptionKey; mmkvIdentifier = identifier; } MMKV.initialize(MainApplication.getMainApplicationContext()); getMMKVInstance(mmkvIdentifier, mmkvEncryptionKey); } } public static void clearSensitiveData() { initialize(); synchronized (mmkvEncryptionKey) { getMMKVInstance(mmkvIdentifier, mmkvEncryptionKey).clearAll(); boolean storageRemoved = MMKV.removeStorage(mmkvIdentifier); if (!storageRemoved) { throw new RuntimeException("Failed to remove MMKV storage."); } assignInitializationData(); MMKV.initialize(MainApplication.getMainApplicationContext()); getMMKVInstance(mmkvIdentifier, mmkvEncryptionKey); } } public static boolean setString(String key, String value) { initialize(); return getMMKVInstance(mmkvIdentifier, mmkvEncryptionKey) .encode(key, value); } public static String getString(String key) { initialize(); return getMMKVInstance(mmkvIdentifier, mmkvEncryptionKey).decodeString(key); } public static boolean setInt(String key, int value) { initialize(); return getMMKVInstance(mmkvIdentifier, mmkvEncryptionKey) .encode(key, value); } public static Integer getInt(String key, int noValue) { initialize(); int value = getMMKVInstance(mmkvIdentifier, mmkvEncryptionKey) .decodeInt(key, noValue); if (value == noValue) { return null; } return value; } public static String[] getAllKeys() { initialize(); return getMMKVInstance(mmkvIdentifier, mmkvEncryptionKey).allKeys(); } public static void removeKeys(String[] keys) { initialize(); getMMKVInstance(mmkvIdentifier, mmkvEncryptionKey) .removeValuesForKeys(keys); } } diff --git a/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp b/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp index 388f8cf14..a29559ee4 100644 --- a/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp +++ b/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp @@ -1,2971 +1,3024 @@ #include "CommCoreModule.h" #include "../Notifications/BackgroundDataStorage/NotificationsCryptoModule.h" #include "BaseDataStore.h" #include "CommServicesAuthMetadataEmitter.h" #include "DatabaseManager.h" #include "InternalModules/GlobalDBSingleton.h" #include "InternalModules/RustPromiseManager.h" #include "NativeModuleUtils.h" #include "TerminateApp.h" #include #include #include #include #include "JSIRust.h" #include "lib.rs.h" #include #include namespace comm { using namespace facebook::react; jsi::Value CommCoreModule::getDraft(jsi::Runtime &rt, jsi::String key) { std::string keyStr = key.utf8(rt); return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=, &innerRt]() { std::string error; std::string draftStr; try { draftStr = DatabaseManager::getQueryExecutor().getDraft(keyStr); } catch (std::system_error &e) { error = e.what(); } this->jsInvoker_->invokeAsync([=, &innerRt]() { if (error.size()) { promise->reject(error); return; } jsi::String draft = jsi::String::createFromUtf8(innerRt, draftStr); promise->resolve(std::move(draft)); }); }; GlobalDBSingleton::instance.scheduleOrRunCancellable( job, promise, this->jsInvoker_); }); } jsi::Value CommCoreModule::updateDraft( jsi::Runtime &rt, jsi::String key, jsi::String text) { std::string keyStr = key.utf8(rt); std::string textStr = text.utf8(rt); return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=]() { std::string error; try { DatabaseManager::getQueryExecutor().updateDraft(keyStr, textStr); } catch (std::system_error &e) { error = e.what(); } this->jsInvoker_->invokeAsync([=]() { if (error.size()) { promise->reject(error); } else { promise->resolve(true); } }); }; GlobalDBSingleton::instance.scheduleOrRunCancellable( job, promise, this->jsInvoker_); }); } jsi::Value CommCoreModule::moveDraft( jsi::Runtime &rt, jsi::String oldKey, jsi::String newKey) { std::string oldKeyStr = oldKey.utf8(rt); std::string newKeyStr = newKey.utf8(rt); return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=]() { std::string error; bool result = false; try { result = DatabaseManager::getQueryExecutor().moveDraft( oldKeyStr, newKeyStr); } catch (std::system_error &e) { error = e.what(); } this->jsInvoker_->invokeAsync([=]() { if (error.size()) { promise->reject(error); } else { promise->resolve(result); } }); }; GlobalDBSingleton::instance.scheduleOrRunCancellable( job, promise, this->jsInvoker_); }); } jsi::Value CommCoreModule::getClientDBStore(jsi::Runtime &rt) { return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=, &innerRt]() { std::string error; std::vector draftsVector; std::vector threadsVector; std::vector messagesVector; std::vector messageStoreThreadsVector; std::vector reportStoreVector; std::vector userStoreVector; std::vector keyserverStoreVector; std::vector communityStoreVector; std::vector integrityStoreVector; std::vector syncedMetadataStoreVector; std::vector auxUserStoreVector; std::vector threadActivityStoreVector; std::vector entryStoreVector; std::vector messageStoreLocalMessageInfosVector; try { draftsVector = DatabaseManager::getQueryExecutor().getAllDrafts(); messagesVector = DatabaseManager::getQueryExecutor().getAllMessages(); threadsVector = DatabaseManager::getQueryExecutor().getAllThreads(); messageStoreThreadsVector = DatabaseManager::getQueryExecutor().getAllMessageStoreThreads(); reportStoreVector = DatabaseManager::getQueryExecutor().getAllReports(); userStoreVector = DatabaseManager::getQueryExecutor().getAllUsers(); keyserverStoreVector = DatabaseManager::getQueryExecutor().getAllKeyservers(); communityStoreVector = DatabaseManager::getQueryExecutor().getAllCommunities(); integrityStoreVector = DatabaseManager::getQueryExecutor() .getAllIntegrityThreadHashes(); syncedMetadataStoreVector = DatabaseManager::getQueryExecutor().getAllSyncedMetadata(); auxUserStoreVector = DatabaseManager::getQueryExecutor().getAllAuxUserInfos(); threadActivityStoreVector = DatabaseManager::getQueryExecutor() .getAllThreadActivityEntries(); entryStoreVector = DatabaseManager::getQueryExecutor().getAllEntries(); messageStoreLocalMessageInfosVector = DatabaseManager::getQueryExecutor() .getAllMessageStoreLocalMessageInfos(); } catch (std::system_error &e) { error = e.what(); } auto draftsVectorPtr = std::make_shared>(std::move(draftsVector)); auto messagesVectorPtr = std::make_shared>( std::move(messagesVector)); auto threadsVectorPtr = std::make_shared>(std::move(threadsVector)); auto messageStoreThreadsVectorPtr = std::make_shared>( std::move(messageStoreThreadsVector)); auto reportStoreVectorPtr = std::make_shared>( std::move(reportStoreVector)); auto userStoreVectorPtr = std::make_shared>( std::move(userStoreVector)); auto keyserveStoreVectorPtr = std::make_shared>( std::move(keyserverStoreVector)); auto communityStoreVectorPtr = std::make_shared>( std::move(communityStoreVector)); auto integrityStoreVectorPtr = std::make_shared>( std::move(integrityStoreVector)); auto syncedMetadataStoreVectorPtr = std::make_shared>( std::move(syncedMetadataStoreVector)); auto auxUserStoreVectorPtr = std::make_shared>( std::move(auxUserStoreVector)); auto threadActivityStoreVectorPtr = std::make_shared>( std::move(threadActivityStoreVector)); auto entryStoreVectorPtr = std::make_shared>( std::move(entryStoreVector)); auto messageStoreLocalMessageInfosVectorPtr = std::make_shared>( std::move(messageStoreLocalMessageInfosVector)); this->jsInvoker_->invokeAsync([&innerRt, draftsVectorPtr, messagesVectorPtr, threadsVectorPtr, messageStoreThreadsVectorPtr, reportStoreVectorPtr, userStoreVectorPtr, keyserveStoreVectorPtr, communityStoreVectorPtr, integrityStoreVectorPtr, syncedMetadataStoreVectorPtr, auxUserStoreVectorPtr, threadActivityStoreVectorPtr, entryStoreVectorPtr, messageStoreLocalMessageInfosVectorPtr, error, promise, draftStore = this->draftStore, threadStore = this->threadStore, messageStore = this->messageStore, reportStore = this->reportStore, userStore = this->userStore, keyserverStore = this->keyserverStore, communityStore = this->communityStore, integrityStore = this->integrityStore, syncedMetadataStore = this->syncedMetadataStore, auxUserStore = this->auxUserStore, threadActivityStore = this->threadActivityStore, entryStore = this->entryStore]() { if (error.size()) { promise->reject(error); return; } jsi::Array jsiDrafts = draftStore.parseDBDataStore(innerRt, draftsVectorPtr); jsi::Array jsiMessages = messageStore.parseDBDataStore(innerRt, messagesVectorPtr); jsi::Array jsiThreads = threadStore.parseDBDataStore(innerRt, threadsVectorPtr); jsi::Array jsiMessageStoreThreads = messageStore.parseDBMessageStoreThreads( innerRt, messageStoreThreadsVectorPtr); jsi::Array jsiReportStore = reportStore.parseDBDataStore(innerRt, reportStoreVectorPtr); jsi::Array jsiUserStore = userStore.parseDBDataStore(innerRt, userStoreVectorPtr); jsi::Array jsiKeyserverStore = keyserverStore.parseDBDataStore( innerRt, keyserveStoreVectorPtr); jsi::Array jsiCommunityStore = communityStore.parseDBDataStore( innerRt, communityStoreVectorPtr); jsi::Array jsiIntegrityStore = integrityStore.parseDBDataStore( innerRt, integrityStoreVectorPtr); jsi::Array jsiSyncedMetadataStore = syncedMetadataStore.parseDBDataStore( innerRt, syncedMetadataStoreVectorPtr); jsi::Array jsiAuxUserStore = auxUserStore.parseDBDataStore(innerRt, auxUserStoreVectorPtr); jsi::Array jsiThreadActivityStore = threadActivityStore.parseDBDataStore( innerRt, threadActivityStoreVectorPtr); jsi::Array jsiEntryStore = entryStore.parseDBDataStore(innerRt, entryStoreVectorPtr); jsi::Array jsiMessageStoreLocalMessageInfos = messageStore.parseDBMessageStoreLocalMessageInfos( innerRt, messageStoreLocalMessageInfosVectorPtr); auto jsiClientDBStore = jsi::Object(innerRt); jsiClientDBStore.setProperty(innerRt, "messages", jsiMessages); jsiClientDBStore.setProperty(innerRt, "threads", jsiThreads); jsiClientDBStore.setProperty(innerRt, "drafts", jsiDrafts); jsiClientDBStore.setProperty( innerRt, "messageStoreThreads", jsiMessageStoreThreads); jsiClientDBStore.setProperty(innerRt, "reports", jsiReportStore); jsiClientDBStore.setProperty(innerRt, "users", jsiUserStore); jsiClientDBStore.setProperty( innerRt, "keyservers", jsiKeyserverStore); jsiClientDBStore.setProperty( innerRt, "communities", jsiCommunityStore); jsiClientDBStore.setProperty( innerRt, "integrityThreadHashes", jsiIntegrityStore); jsiClientDBStore.setProperty( innerRt, "syncedMetadata", jsiSyncedMetadataStore); jsiClientDBStore.setProperty( innerRt, "auxUserInfos", jsiAuxUserStore); jsiClientDBStore.setProperty( innerRt, "threadActivityEntries", jsiThreadActivityStore); jsiClientDBStore.setProperty(innerRt, "entries", jsiEntryStore); jsiClientDBStore.setProperty( innerRt, "messageStoreLocalMessageInfos", jsiMessageStoreLocalMessageInfos); promise->resolve(std::move(jsiClientDBStore)); }); }; GlobalDBSingleton::instance.scheduleOrRunCancellable( job, promise, this->jsInvoker_); }); } jsi::Value CommCoreModule::removeAllDrafts(jsi::Runtime &rt) { return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=]() { std::string error; try { DatabaseManager::getQueryExecutor().removeAllDrafts(); } catch (std::system_error &e) { error = e.what(); } this->jsInvoker_->invokeAsync([=]() { if (error.size()) { promise->reject(error); return; } promise->resolve(jsi::Value::undefined()); }); }; GlobalDBSingleton::instance.scheduleOrRunCancellable( job, promise, this->jsInvoker_); }); } jsi::Array CommCoreModule::getAllMessagesSync(jsi::Runtime &rt) { auto messagesVector = NativeModuleUtils::runSyncOrThrowJSError>( rt, []() { return DatabaseManager::getQueryExecutor().getAllMessages(); }); auto messagesVectorPtr = std::make_shared>(std::move(messagesVector)); jsi::Array jsiMessages = this->messageStore.parseDBDataStore(rt, messagesVectorPtr); return jsiMessages; } void CommCoreModule::processMessageStoreOperationsSync( jsi::Runtime &rt, jsi::Array operations) { return this->messageStore.processStoreOperationsSync( rt, std::move(operations)); } jsi::Array CommCoreModule::getAllThreadsSync(jsi::Runtime &rt) { auto threadsVector = NativeModuleUtils::runSyncOrThrowJSError>(rt, []() { return DatabaseManager::getQueryExecutor().getAllThreads(); }); auto threadsVectorPtr = std::make_shared>(std::move(threadsVector)); jsi::Array jsiThreads = this->threadStore.parseDBDataStore(rt, threadsVectorPtr); return jsiThreads; } void CommCoreModule::processThreadStoreOperationsSync( jsi::Runtime &rt, jsi::Array operations) { this->threadStore.processStoreOperationsSync(rt, std::move(operations)); } void CommCoreModule::processReportStoreOperationsSync( jsi::Runtime &rt, jsi::Array operations) { this->reportStore.processStoreOperationsSync(rt, std::move(operations)); } template void CommCoreModule::appendDBStoreOps( jsi::Runtime &rt, jsi::Object &operations, const char *key, T &store, std::shared_ptr>> &destination) { auto opsObject = operations.getProperty(rt, key); if (opsObject.isObject()) { auto ops = store.createOperations(rt, opsObject.asObject(rt).asArray(rt)); std::move( std::make_move_iterator(ops.begin()), std::make_move_iterator(ops.end()), std::back_inserter(*destination)); } } jsi::Value CommCoreModule::processDBStoreOperations( jsi::Runtime &rt, jsi::Object operations) { std::string createOperationsError; auto storeOpsPtr = std::make_shared>>(); try { this->appendDBStoreOps( rt, operations, "draftStoreOperations", this->draftStore, storeOpsPtr); this->appendDBStoreOps( rt, operations, "threadStoreOperations", this->threadStore, storeOpsPtr); this->appendDBStoreOps( rt, operations, "messageStoreOperations", this->messageStore, storeOpsPtr); this->appendDBStoreOps( rt, operations, "reportStoreOperations", this->reportStore, storeOpsPtr); this->appendDBStoreOps( rt, operations, "userStoreOperations", this->userStore, storeOpsPtr); this->appendDBStoreOps( rt, operations, "keyserverStoreOperations", this->keyserverStore, storeOpsPtr); this->appendDBStoreOps( rt, operations, "communityStoreOperations", this->communityStore, storeOpsPtr); this->appendDBStoreOps( rt, operations, "integrityStoreOperations", this->integrityStore, storeOpsPtr); this->appendDBStoreOps( rt, operations, "syncedMetadataStoreOperations", this->syncedMetadataStore, storeOpsPtr); this->appendDBStoreOps( rt, operations, "auxUserStoreOperations", this->auxUserStore, storeOpsPtr); this->appendDBStoreOps( rt, operations, "threadActivityStoreOperations", this->threadActivityStore, storeOpsPtr); this->appendDBStoreOps( rt, operations, "entryStoreOperations", this->entryStore, storeOpsPtr); this->appendDBStoreOps( rt, operations, "messageSearchStoreOperations", this->messageSearchStore, storeOpsPtr); } catch (std::runtime_error &e) { createOperationsError = e.what(); } std::vector messages; try { auto messagesJSIObj = operations.getProperty(rt, "outboundP2PMessages"); if (messagesJSIObj.isObject()) { auto messagesJSI = messagesJSIObj.asObject(rt).asArray(rt); for (size_t idx = 0; idx < messagesJSI.size(rt); idx++) { jsi::Object msgObj = messagesJSI.getValueAtIndex(rt, idx).asObject(rt); std::string messageID = msgObj.getProperty(rt, "messageID").asString(rt).utf8(rt); std::string deviceID = msgObj.getProperty(rt, "deviceID").asString(rt).utf8(rt); std::string userID = msgObj.getProperty(rt, "userID").asString(rt).utf8(rt); std::string timestamp = msgObj.getProperty(rt, "timestamp").asString(rt).utf8(rt); std::string plaintext = msgObj.getProperty(rt, "plaintext").asString(rt).utf8(rt); std::string ciphertext = msgObj.getProperty(rt, "ciphertext").asString(rt).utf8(rt); std::string status = msgObj.getProperty(rt, "status").asString(rt).utf8(rt); bool supports_auto_retry = msgObj.getProperty(rt, "supportsAutoRetry").asBool(); OutboundP2PMessage outboundMessage{ messageID, deviceID, userID, timestamp, plaintext, ciphertext, status, supports_auto_retry}; messages.push_back(outboundMessage); } } } catch (std::runtime_error &e) { createOperationsError = e.what(); } return facebook::react::createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=]() { std::string error = createOperationsError; if (!error.size()) { try { DatabaseManager::getQueryExecutor().beginTransaction(); for (const auto &operation : *storeOpsPtr) { operation->execute(); } if (messages.size() > 0) { DatabaseManager::getQueryExecutor().addOutboundP2PMessages( messages); } DatabaseManager::getQueryExecutor().captureBackupLogs(); DatabaseManager::getQueryExecutor().commitTransaction(); } catch (std::system_error &e) { error = e.what(); DatabaseManager::getQueryExecutor().rollbackTransaction(); } } if (!error.size()) { ::triggerBackupFileUpload(); } this->jsInvoker_->invokeAsync([=]() { if (error.size()) { promise->reject(error); } else { promise->resolve(jsi::Value::undefined()); } }); }; GlobalDBSingleton::instance.scheduleOrRunCancellable( job, promise, this->jsInvoker_); }); } void CommCoreModule::terminate(jsi::Runtime &rt) { TerminateApp::terminate(); } const std::string getAccountDataKey(const std::string secureStoreAccountDataKey) { folly::Optional storedSecretKey = CommSecureStore::get(secureStoreAccountDataKey); if (!storedSecretKey.hasValue()) { storedSecretKey = crypto::Tools::generateRandomString(64); CommSecureStore::set(secureStoreAccountDataKey, storedSecretKey.value()); } return storedSecretKey.value(); } 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; } crypto::Persist newContentPersist; if (persistContentModule) { 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( [=, &persistencePromise]() { try { DatabaseManager::getQueryExecutor().beginTransaction(); if (persistContentModule) { DatabaseManager::getQueryExecutor().storeOlmPersistData( 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(); } catch (std::system_error &e) { DatabaseManager::getQueryExecutor().rollbackTransaction(); persistencePromise.set_exception(std::make_exception_ptr(e)); } }); persistenceFuture.get(); } jsi::Value CommCoreModule::initializeCryptoAccount(jsi::Runtime &rt) { folly::Optional storedSecretKey = CommSecureStore::get(this->secureStoreAccountDataKey); if (!storedSecretKey.hasValue()) { storedSecretKey = crypto::Tools::generateRandomString(64); CommSecureStore::set( this->secureStoreAccountDataKey, storedSecretKey.value()); } return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=]() { crypto::Persist contentPersist; crypto::Persist notifsPersist; std::string error; try { std::optional contentAccountData = DatabaseManager::getQueryExecutor().getOlmPersistAccountData( DatabaseManager::getQueryExecutor().getContentAccountID()); if (contentAccountData.has_value()) { contentPersist.account = crypto::OlmBuffer( contentAccountData->begin(), contentAccountData->end()); // handle sessions data std::vector sessionsData = DatabaseManager::getQueryExecutor() .getOlmPersistSessionsData(); for (OlmPersistSession &sessionsDataItem : sessionsData) { crypto::OlmBuffer sessionDataBuffer( sessionsDataItem.session_data.begin(), sessionsDataItem.session_data.end()); crypto::SessionPersist sessionPersist{ sessionDataBuffer, sessionsDataItem.version}; contentPersist.sessions.insert(std::make_pair( sessionsDataItem.target_device_id, sessionPersist)); } } std::optional notifsAccountData = DatabaseManager::getQueryExecutor().getOlmPersistAccountData( DatabaseManager::getQueryExecutor().getNotifsAccountID()); + if (notifsAccountData.has_value()) { notifsPersist.account = crypto::OlmBuffer( notifsAccountData->begin(), notifsAccountData->end()); } } catch (std::system_error &e) { error = e.what(); } this->cryptoThread->scheduleTask([=]() { std::string error; this->contentCryptoModule.reset(new crypto::CryptoModule( this->publicCryptoAccountID, 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(); } this->jsInvoker_->invokeAsync([=]() { if (error.size()) { promise->reject(error); return; } }); this->jsInvoker_->invokeAsync( [=]() { promise->resolve(jsi::Value::undefined()); }); }); }; GlobalDBSingleton::instance.scheduleOrRunCancellable( job, promise, this->jsInvoker_); }); } jsi::Value CommCoreModule::getUserPublicKey(jsi::Runtime &rt) { return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=, &innerRt]() { std::string error; 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, blobPayloadCpp, signatureCpp, primaryCurve25519Cpp, primaryEd25519Cpp; if (!error.size()) { folly::dynamic parsedPrimary; try { parsedPrimary = folly::parseJson(primaryKeysResult); } catch (const folly::json::parse_error &e) { error = "parsing identity keys failed with: " + std::string(e.what()); } if (!error.size()) { primaryCurve25519Cpp = parsedPrimary["curve25519"].asString(); primaryEd25519Cpp = parsedPrimary["ed25519"].asString(); folly::dynamic parsedNotifications; try { parsedNotifications = folly::parseJson(notificationsKeysResult); } catch (const folly::json::parse_error &e) { error = "parsing notifications keys failed with: " + std::string(e.what()); } if (!error.size()) { notificationsCurve25519Cpp = parsedNotifications["curve25519"].asString(); notificationsEd25519Cpp = parsedNotifications["ed25519"].asString(); folly::dynamic blobPayloadJSON = folly::dynamic::object( "primaryIdentityPublicKeys", folly::dynamic::object("ed25519", primaryEd25519Cpp)( "curve25519", primaryCurve25519Cpp))( "notificationIdentityPublicKeys", folly::dynamic::object("ed25519", notificationsEd25519Cpp)( "curve25519", notificationsCurve25519Cpp)); blobPayloadCpp = folly::toJson(blobPayloadJSON); signatureCpp = this->contentCryptoModule->signMessage(blobPayloadCpp); } } } this->jsInvoker_->invokeAsync([=, &innerRt]() { if (error.size()) { promise->reject(error); return; } auto primaryCurve25519{ jsi::String::createFromUtf8(innerRt, primaryCurve25519Cpp)}; auto primaryEd25519{ jsi::String::createFromUtf8(innerRt, primaryEd25519Cpp)}; auto jsiPrimaryIdentityPublicKeys = jsi::Object(innerRt); jsiPrimaryIdentityPublicKeys.setProperty( innerRt, "ed25519", primaryEd25519); jsiPrimaryIdentityPublicKeys.setProperty( innerRt, "curve25519", primaryCurve25519); auto notificationsCurve25519{jsi::String::createFromUtf8( innerRt, notificationsCurve25519Cpp)}; auto notificationsEd25519{ jsi::String::createFromUtf8(innerRt, notificationsEd25519Cpp)}; auto jsiNotificationIdentityPublicKeys = jsi::Object(innerRt); jsiNotificationIdentityPublicKeys.setProperty( innerRt, "ed25519", notificationsEd25519); jsiNotificationIdentityPublicKeys.setProperty( innerRt, "curve25519", notificationsCurve25519); auto blobPayload{ jsi::String::createFromUtf8(innerRt, blobPayloadCpp)}; auto signature{jsi::String::createFromUtf8(innerRt, signatureCpp)}; auto jsiClientPublicKeys = jsi::Object(innerRt); jsiClientPublicKeys.setProperty( innerRt, "primaryIdentityPublicKeys", jsiPrimaryIdentityPublicKeys); jsiClientPublicKeys.setProperty( innerRt, "notificationIdentityPublicKeys", jsiNotificationIdentityPublicKeys); jsiClientPublicKeys.setProperty( innerRt, "blobPayload", blobPayload); jsiClientPublicKeys.setProperty(innerRt, "signature", signature); promise->resolve(std::move(jsiClientPublicKeys)); }); }; this->cryptoThread->scheduleTask(job); }); } jsi::Object parseOLMOneTimeKeys(jsi::Runtime &rt, std::string oneTimeKeysBlob) { folly::dynamic parsedOneTimeKeys = folly::parseJson(oneTimeKeysBlob); auto jsiOneTimeKeysInner = jsi::Object(rt); for (auto &kvPair : parsedOneTimeKeys["curve25519"].items()) { jsiOneTimeKeysInner.setProperty( rt, kvPair.first.asString().c_str(), jsi::String::createFromUtf8(rt, kvPair.second.asString())); } auto jsiOneTimeKeys = jsi::Object(rt); jsiOneTimeKeys.setProperty(rt, "curve25519", jsiOneTimeKeysInner); return jsiOneTimeKeys; } std::string parseOLMPrekey(std::string prekeyBlob) { folly::dynamic parsedPrekey; try { parsedPrekey = folly::parseJson(prekeyBlob); } catch (const folly::json::parse_error &e) { throw std::runtime_error( "parsing prekey failed with: " + std::string(e.what())); } folly::dynamic innerObject = parsedPrekey["curve25519"]; if (!innerObject.isObject()) { throw std::runtime_error("parsing prekey failed: inner object malformed"); } if (innerObject.values().begin() == innerObject.values().end()) { throw std::runtime_error("parsing prekey failed: prekey missing"); } return parsedPrekey["curve25519"].values().begin()->asString(); } jsi::Object parseOneTimeKeysResult( jsi::Runtime &rt, std::string contentOneTimeKeysBlob, std::string notifOneTimeKeysBlob) { auto contentOneTimeKeys = parseOLMOneTimeKeys(rt, contentOneTimeKeysBlob); auto notifOneTimeKeys = parseOLMOneTimeKeys(rt, notifOneTimeKeysBlob); auto jsiOneTimeKeysResult = jsi::Object(rt); jsiOneTimeKeysResult.setProperty( rt, "contentOneTimeKeys", contentOneTimeKeys); jsiOneTimeKeysResult.setProperty( rt, "notificationsOneTimeKeys", notifOneTimeKeys); return jsiOneTimeKeysResult; } jsi::Object parseEncryptedData( jsi::Runtime &rt, const crypto::EncryptedData &encryptedData) { auto encryptedDataJSI = jsi::Object(rt); auto message = std::string{encryptedData.message.begin(), encryptedData.message.end()}; auto messageJSI = jsi::String::createFromUtf8(rt, message); encryptedDataJSI.setProperty(rt, "message", messageJSI); encryptedDataJSI.setProperty( rt, "messageType", static_cast(encryptedData.messageType)); return encryptedDataJSI; } jsi::Value CommCoreModule::getOneTimeKeys(jsi::Runtime &rt, double oneTimeKeysAmount) { return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=, &innerRt]() { std::string error; 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"); }); return; } try { 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(); } this->jsInvoker_->invokeAsync([=, &innerRt]() { if (error.size()) { promise->reject(error); return; } promise->resolve( parseOneTimeKeysResult(innerRt, contentResult, notifResult)); }); }; this->cryptoThread->scheduleTask(job); }); } jsi::Value CommCoreModule::validateAndUploadPrekeys( jsi::Runtime &rt, jsi::String authUserID, jsi::String authDeviceID, jsi::String authAccessToken) { auto authUserIDRust = jsiStringToRustString(authUserID, rt); auto authDeviceIDRust = jsiStringToRustString(authDeviceID, rt); auto authAccessTokenRust = jsiStringToRustString(authAccessToken, rt); return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=, &innerRt]() { std::string error; std::optional maybeContentPrekeyToUpload; 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 = this->contentCryptoModule->getUnpublishedPrekey(); } if (!maybeNotifsPrekeyToUpload.has_value()) { maybeNotifsPrekeyToUpload = - this->notifsCryptoModule->getUnpublishedPrekey(); + notifsCryptoModuleWithPicklingKey.value() + .first->getUnpublishedPrekey(); } } catch (const std::exception &e) { error = e.what(); } if (error.size()) { this->jsInvoker_->invokeAsync( [=, &innerRt]() { promise->reject(error); }); return; } if (!maybeContentPrekeyToUpload.has_value() && !maybeNotifsPrekeyToUpload.has_value()) { this->jsInvoker_->invokeAsync( [=]() { promise->resolve(jsi::Value::undefined()); }); return; } std::string contentPrekeyToUpload; if (maybeContentPrekeyToUpload.has_value()) { contentPrekeyToUpload = maybeContentPrekeyToUpload.value(); } else { contentPrekeyToUpload = this->contentCryptoModule->getPrekey(); } std::string notifsPrekeyToUpload; if (maybeNotifsPrekeyToUpload.has_value()) { notifsPrekeyToUpload = maybeNotifsPrekeyToUpload.value(); } else { - notifsPrekeyToUpload = this->notifsCryptoModule->getPrekey(); + notifsPrekeyToUpload = + notifsCryptoModuleWithPicklingKey.value().first->getPrekey(); } std::string prekeyUploadError; try { std::string contentPrekeySignature = this->contentCryptoModule->getPrekeySignature(); std::string notifsPrekeySignature = - this->notifsCryptoModule->getPrekeySignature(); + notifsCryptoModuleWithPicklingKey.value() + .first->getPrekeySignature(); try { std::promise prekeyPromise; std::future prekeyFuture = prekeyPromise.get_future(); RustPromiseManager::CPPPromiseInfo promiseInfo = { std::move(prekeyPromise)}; auto currentID = RustPromiseManager::instance.addPromise( std::move(promiseInfo)); auto contentPrekeyToUploadRust = rust::String(parseOLMPrekey(contentPrekeyToUpload)); auto prekeySignatureRust = rust::string(contentPrekeySignature); auto notifsPrekeyToUploadRust = rust::String(parseOLMPrekey(notifsPrekeyToUpload)); auto notificationsPrekeySignatureRust = rust::string(notifsPrekeySignature); ::identityRefreshUserPrekeys( authUserIDRust, authDeviceIDRust, authAccessTokenRust, contentPrekeyToUploadRust, prekeySignatureRust, notifsPrekeyToUploadRust, notificationsPrekeySignatureRust, currentID); prekeyFuture.get(); } catch (const std::exception &e) { prekeyUploadError = e.what(); } 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(); } this->jsInvoker_->invokeAsync([=]() { if (error.size()) { promise->reject(error); return; } if (prekeyUploadError.size()) { promise->reject(prekeyUploadError); return; } promise->resolve(jsi::Value::undefined()); }); }; this->cryptoThread->scheduleTask(job); }); } jsi::Value CommCoreModule::validateAndGetPrekeys(jsi::Runtime &rt) { return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=, &innerRt]() { std::string error; std::string contentPrekey, notifPrekey, contentPrekeySignature, notifPrekeySignature; std::optional contentPrekeyBlob; 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 = this->contentCryptoModule->getUnpublishedPrekey(); } if (!contentPrekeyBlob) { 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()); } catch (const std::exception &e) { error = e.what(); } this->jsInvoker_->invokeAsync([=, &innerRt]() { if (error.size()) { promise->reject(error); return; } auto contentPrekeyJSI = jsi::String::createFromUtf8(innerRt, contentPrekey); auto contentPrekeySignatureJSI = jsi::String::createFromUtf8(innerRt, contentPrekeySignature); auto notifPrekeyJSI = jsi::String::createFromUtf8(innerRt, notifPrekey); auto notifPrekeySignatureJSI = jsi::String::createFromUtf8(innerRt, notifPrekeySignature); auto signedPrekeysJSI = jsi::Object(innerRt); signedPrekeysJSI.setProperty( innerRt, "contentPrekey", contentPrekeyJSI); signedPrekeysJSI.setProperty( innerRt, "contentPrekeySignature", contentPrekeySignatureJSI); signedPrekeysJSI.setProperty( innerRt, "notifPrekey", notifPrekeyJSI); signedPrekeysJSI.setProperty( innerRt, "notifPrekeySignature", notifPrekeySignatureJSI); promise->resolve(std::move(signedPrekeysJSI)); }); }; this->cryptoThread->scheduleTask(job); }); } jsi::Value CommCoreModule::initializeNotificationsSession( jsi::Runtime &rt, jsi::String identityKeys, jsi::String prekey, jsi::String prekeySignature, std::optional oneTimeKey, jsi::String keyserverID) { auto identityKeysCpp{identityKeys.utf8(rt)}; auto prekeyCpp{prekey.utf8(rt)}; auto prekeySignatureCpp{prekeySignature.utf8(rt)}; auto keyserverIDCpp{keyserverID.utf8(rt)}; std::optional oneTimeKeyCpp; if (oneTimeKey) { oneTimeKeyCpp = oneTimeKey->utf8(rt); } return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { 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(); } this->jsInvoker_->invokeAsync([=, &innerRt]() { if (error.size()) { promise->reject(error); return; } promise->resolve(jsi::String::createFromUtf8( innerRt, std::string{result.message.begin(), result.message.end()})); }); }; this->cryptoThread->scheduleTask(job); }); } jsi::Value CommCoreModule::isNotificationsSessionInitialized(jsi::Runtime &rt) { return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=, &innerRt]() { std::string error; bool result; try { result = NotificationsCryptoModule::isNotificationsSessionInitialized( "Comm"); } catch (const std::exception &e) { error = e.what(); } this->jsInvoker_->invokeAsync([=, &innerRt]() { if (error.size()) { promise->reject(error); return; } promise->resolve(result); }); }; this->cryptoThread->scheduleTask(job); }); } jsi::Value CommCoreModule::isDeviceNotificationsSessionInitialized( jsi::Runtime &rt, jsi::String deviceID) { auto deviceIDCpp{deviceID.utf8(rt)}; return createPromiseAsJSIValue( 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"); }); return; } std::string error; bool result; try { result = NotificationsCryptoModule:: isDeviceNotificationsSessionInitialized(deviceIDCpp); } catch (const std::exception &e) { error = e.what(); } this->jsInvoker_->invokeAsync([=, &innerRt]() { if (error.size()) { promise->reject(error); return; } promise->resolve(result); }); }; this->cryptoThread->scheduleTask(job); }); } jsi::Value CommCoreModule::isNotificationsSessionInitializedWithDevices( jsi::Runtime &rt, jsi::Array deviceIDs) { std::vector deviceIDsCpp; for (auto idx = 0; idx < deviceIDs.size(rt); idx++) { std::string deviceIDCpp = deviceIDs.getValueAtIndex(rt, idx).asString(rt).utf8(rt); deviceIDsCpp.push_back(deviceIDCpp); } return createPromiseAsJSIValue( 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"); }); return; } std::string error; std::vector> result; try { result = NotificationsCryptoModule:: isNotificationsSessionInitializedWithDevices(deviceIDsCpp); } catch (const std::exception &e) { error = e.what(); } auto resultPtr = std::make_shared>>( std::move(result)); this->jsInvoker_->invokeAsync( [&innerRt, resultPtr, error, promise]() { if (error.size()) { promise->reject(error); return; } jsi::Object jsiResult = jsi::Object(innerRt); for (const auto &deviceResult : *resultPtr) { jsiResult.setProperty( innerRt, deviceResult.first.c_str(), deviceResult.second); } promise->resolve(std::move(jsiResult)); }); }; this->cryptoThread->scheduleTask(job); }); } jsi::Value CommCoreModule::updateKeyserverDataInNotifStorage( jsi::Runtime &rt, jsi::Array keyserversData) { std::vector> keyserversDataCpp; for (auto idx = 0; idx < keyserversData.size(rt); idx++) { auto data = keyserversData.getValueAtIndex(rt, idx).asObject(rt); std::string keyserverID = data.getProperty(rt, "id").asString(rt).utf8(rt); std::string keyserverUnreadCountKey = "KEYSERVER." + keyserverID + ".UNREAD_COUNT"; int unreadCount = data.getProperty(rt, "unreadCount").asNumber(); keyserversDataCpp.push_back({keyserverUnreadCountKey, unreadCount}); } return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { std::string error; try { for (const auto &keyserverData : keyserversDataCpp) { CommMMKV::setInt(keyserverData.first, keyserverData.second); } } catch (const std::exception &e) { error = e.what(); } this->jsInvoker_->invokeAsync([=, &innerRt]() { if (error.size()) { promise->reject(error); return; } promise->resolve(jsi::Value::undefined()); }); }); } jsi::Value CommCoreModule::removeKeyserverDataFromNotifStorage( jsi::Runtime &rt, jsi::Array keyserverIDsToDelete) { std::vector keyserverIDsToDeleteCpp{}; for (auto idx = 0; idx < keyserverIDsToDelete.size(rt); idx++) { std::string keyserverID = keyserverIDsToDelete.getValueAtIndex(rt, idx).asString(rt).utf8(rt); std::string keyserverUnreadCountKey = "KEYSERVER." + keyserverID + ".UNREAD_COUNT"; keyserverIDsToDeleteCpp.push_back(keyserverUnreadCountKey); } return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { std::string error; try { CommMMKV::removeKeys(keyserverIDsToDeleteCpp); } catch (const std::exception &e) { error = e.what(); } this->jsInvoker_->invokeAsync([=, &innerRt]() { if (error.size()) { promise->reject(error); return; } promise->resolve(jsi::Value::undefined()); }); }); } jsi::Value CommCoreModule::getKeyserverDataFromNotifStorage( jsi::Runtime &rt, jsi::Array keyserverIDs) { std::vector keyserverIDsCpp{}; for (auto idx = 0; idx < keyserverIDs.size(rt); idx++) { std::string keyserverID = keyserverIDs.getValueAtIndex(rt, idx).asString(rt).utf8(rt); keyserverIDsCpp.push_back(keyserverID); } return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { std::string error; std::vector> keyserversDataVector{}; try { for (const auto &keyserverID : keyserverIDsCpp) { std::string keyserverUnreadCountKey = "KEYSERVER." + keyserverID + ".UNREAD_COUNT"; std::optional unreadCount = CommMMKV::getInt(keyserverUnreadCountKey, -1); if (!unreadCount.has_value()) { continue; } keyserversDataVector.push_back({keyserverID, unreadCount.value()}); } } catch (const std::exception &e) { error = e.what(); } auto keyserversDataVectorPtr = std::make_shared>>( std::move(keyserversDataVector)); this->jsInvoker_->invokeAsync( [&innerRt, keyserversDataVectorPtr, error, promise]() { if (error.size()) { promise->reject(error); return; } size_t numKeyserversData = keyserversDataVectorPtr->size(); jsi::Array jsiKeyserversData = jsi::Array(innerRt, numKeyserversData); size_t writeIdx = 0; for (const auto &keyserverData : *keyserversDataVectorPtr) { jsi::Object jsiKeyserverData = jsi::Object(innerRt); jsiKeyserverData.setProperty( innerRt, "id", keyserverData.first); jsiKeyserverData.setProperty( innerRt, "unreadCount", keyserverData.second); jsiKeyserversData.setValueAtIndex( innerRt, writeIdx++, jsiKeyserverData); } promise->resolve(std::move(jsiKeyserversData)); }); }); } jsi::Value CommCoreModule::initializeContentOutboundSession( jsi::Runtime &rt, jsi::String identityKeys, jsi::String prekey, jsi::String prekeySignature, std::optional oneTimeKey, jsi::String deviceID) { auto identityKeysCpp{identityKeys.utf8(rt)}; auto prekeyCpp{prekey.utf8(rt)}; auto prekeySignatureCpp{prekeySignature.utf8(rt)}; auto deviceIDCpp{deviceID.utf8(rt)}; std::optional oneTimeKeyCpp; if (oneTimeKey) { oneTimeKeyCpp = oneTimeKey->utf8(rt); } return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=, &innerRt]() { std::string error; crypto::EncryptedData initialEncryptedData; int sessionVersion; try { std::optional oneTimeKeyBuffer; if (oneTimeKeyCpp) { oneTimeKeyBuffer = crypto::OlmBuffer( oneTimeKeyCpp->begin(), oneTimeKeyCpp->end()); } sessionVersion = this->contentCryptoModule->initializeOutboundForSendingSession( deviceIDCpp, std::vector( identityKeysCpp.begin(), identityKeysCpp.end()), std::vector(prekeyCpp.begin(), prekeyCpp.end()), std::vector( prekeySignatureCpp.begin(), prekeySignatureCpp.end()), oneTimeKeyBuffer); const std::string initMessage = "{\"type\": \"init\"}"; initialEncryptedData = contentCryptoModule->encrypt(deviceIDCpp, initMessage); - this->persistCryptoModules(true, false); + this->persistCryptoModules(true, std::nullopt); } catch (const std::exception &e) { error = e.what(); } this->jsInvoker_->invokeAsync([=, &innerRt]() { if (error.size()) { promise->reject(error); return; } auto initialEncryptedDataJSI = parseEncryptedData(innerRt, initialEncryptedData); auto outboundSessionCreationResultJSI = jsi::Object(innerRt); outboundSessionCreationResultJSI.setProperty( innerRt, "encryptedData", initialEncryptedDataJSI); outboundSessionCreationResultJSI.setProperty( innerRt, "sessionVersion", sessionVersion); promise->resolve(std::move(outboundSessionCreationResultJSI)); }); }; this->cryptoThread->scheduleTask(job); }); } jsi::Value CommCoreModule::initializeContentInboundSession( jsi::Runtime &rt, jsi::String identityKeys, jsi::Object encryptedDataJSI, jsi::String deviceID, double sessionVersion, bool overwrite) { auto identityKeysCpp{identityKeys.utf8(rt)}; size_t messageType = std::lround(encryptedDataJSI.getProperty(rt, "messageType").asNumber()); std::string encryptedMessageCpp = encryptedDataJSI.getProperty(rt, "message").asString(rt).utf8(rt); auto deviceIDCpp{deviceID.utf8(rt)}; return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=, &innerRt]() { std::string error; std::string decryptedMessage; try { this->contentCryptoModule->initializeInboundForReceivingSession( deviceIDCpp, std::vector( encryptedMessageCpp.begin(), encryptedMessageCpp.end()), std::vector( identityKeysCpp.begin(), identityKeysCpp.end()), static_cast(sessionVersion), overwrite); crypto::EncryptedData encryptedData{ std::vector( encryptedMessageCpp.begin(), encryptedMessageCpp.end()), messageType}; decryptedMessage = this->contentCryptoModule->decrypt(deviceIDCpp, encryptedData); - this->persistCryptoModules(true, false); + this->persistCryptoModules(true, std::nullopt); } catch (const std::exception &e) { error = e.what(); } this->jsInvoker_->invokeAsync([=, &innerRt]() { if (error.size()) { promise->reject(error); return; } promise->resolve( jsi::String::createFromUtf8(innerRt, decryptedMessage)); }); }; this->cryptoThread->scheduleTask(job); }); } jsi::Value CommCoreModule::isContentSessionInitialized( jsi::Runtime &rt, jsi::String deviceID) { auto deviceIDCpp{deviceID.utf8(rt)}; return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=, &innerRt]() { std::string error; bool result; if (this->contentCryptoModule == nullptr || - this->notifsCryptoModule == nullptr) { + !NotificationsCryptoModule::isNotificationsAccountInitialized()) { this->jsInvoker_->invokeAsync([=, &innerRt]() { promise->reject("user has not been initialized"); }); return; } try { result = this->contentCryptoModule->hasSessionFor(deviceIDCpp); } catch (const std::exception &e) { error = e.what(); } this->jsInvoker_->invokeAsync([=, &innerRt]() { if (error.size()) { promise->reject(error); return; } promise->resolve(result); }); }; this->cryptoThread->scheduleTask(job); }); } jsi::Value CommCoreModule::initializeNotificationsOutboundSession( jsi::Runtime &rt, jsi::String identityKeys, jsi::String prekey, jsi::String prekeySignature, std::optional oneTimeKey, jsi::String deviceID) { auto identityKeysCpp{identityKeys.utf8(rt)}; auto prekeyCpp{prekey.utf8(rt)}; auto prekeySignatureCpp{prekeySignature.utf8(rt)}; auto deviceIDCpp{deviceID.utf8(rt)}; std::optional oneTimeKeyCpp; if (oneTimeKey) { oneTimeKeyCpp = oneTimeKey->utf8(rt); } return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { 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(); } this->jsInvoker_->invokeAsync([=, &innerRt]() { if (error.size()) { promise->reject(error); return; } auto initialEncryptedDataJSI = parseEncryptedData(innerRt, result); promise->resolve(std::move(initialEncryptedDataJSI)); }); }; this->cryptoThread->scheduleTask(job); }); } jsi::Value CommCoreModule::encrypt( jsi::Runtime &rt, jsi::String message, jsi::String deviceID) { auto messageCpp{message.utf8(rt)}; auto deviceIDCpp{deviceID.utf8(rt)}; return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=, &innerRt]() { std::string error; crypto::EncryptedData encryptedMessage; try { encryptedMessage = contentCryptoModule->encrypt(deviceIDCpp, messageCpp); - this->persistCryptoModules(true, false); + this->persistCryptoModules(true, std::nullopt); } catch (const std::exception &e) { error = e.what(); } this->jsInvoker_->invokeAsync([=, &innerRt]() { if (error.size()) { promise->reject(error); return; } auto encryptedDataJSI = parseEncryptedData(innerRt, encryptedMessage); promise->resolve(std::move(encryptedDataJSI)); }); }; this->cryptoThread->scheduleTask(job); }); } jsi::Value CommCoreModule::encryptNotification( jsi::Runtime &rt, jsi::String payload, jsi::String deviceID) { auto payloadCpp{payload.utf8(rt)}; auto deviceIDCpp{deviceID.utf8(rt)}; return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=, &innerRt]() { std::string error; crypto::EncryptedData result; try { result = NotificationsCryptoModule::encrypt(deviceIDCpp, payloadCpp); } catch (const std::exception &e) { error = e.what(); } this->jsInvoker_->invokeAsync([=, &innerRt]() { if (error.size()) { promise->reject(error); return; } auto encryptedDataJSI = parseEncryptedData(innerRt, result); promise->resolve(std::move(encryptedDataJSI)); }); }; this->cryptoThread->scheduleTask(job); }); } jsi::Value CommCoreModule::encryptAndPersist( jsi::Runtime &rt, jsi::String message, jsi::String deviceID, jsi::String messageID) { auto messageCpp{message.utf8(rt)}; auto deviceIDCpp{deviceID.utf8(rt)}; auto messageIDCpp{messageID.utf8(rt)}; return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=, &innerRt]() { std::string error; crypto::EncryptedData encryptedMessage; try { encryptedMessage = contentCryptoModule->encrypt(deviceIDCpp, messageCpp); std::string storedSecretKey = getAccountDataKey(secureStoreAccountDataKey); crypto::Persist newContentPersist = this->contentCryptoModule->storeAsB64(storedSecretKey); std::promise persistencePromise; std::future persistenceFuture = persistencePromise.get_future(); GlobalDBSingleton::instance.scheduleOrRunCancellable( [=, &persistencePromise]() { try { folly::dynamic jsonObject = folly::dynamic::object; std::string messageStr( encryptedMessage.message.begin(), encryptedMessage.message.end()); jsonObject["message"] = messageStr; jsonObject["messageType"] = encryptedMessage.messageType; std::string ciphertext = folly::toJson(jsonObject); DatabaseManager::getQueryExecutor().beginTransaction(); DatabaseManager::getQueryExecutor() .setCiphertextForOutboundP2PMessage( messageIDCpp, deviceIDCpp, ciphertext); DatabaseManager::getQueryExecutor().storeOlmPersistData( DatabaseManager::getQueryExecutor() .getContentAccountID(), newContentPersist); DatabaseManager::getQueryExecutor().commitTransaction(); persistencePromise.set_value(); } catch (std::system_error &e) { DatabaseManager::getQueryExecutor().rollbackTransaction(); persistencePromise.set_exception( std::make_exception_ptr(e)); } }); persistenceFuture.get(); } catch (const std::exception &e) { error = e.what(); } this->jsInvoker_->invokeAsync([=, &innerRt]() { if (error.size()) { promise->reject(error); return; } auto encryptedDataJSI = parseEncryptedData(innerRt, encryptedMessage); promise->resolve(std::move(encryptedDataJSI)); }); }; this->cryptoThread->scheduleTask(job); }); } jsi::Value CommCoreModule::decrypt( jsi::Runtime &rt, jsi::Object encryptedDataJSI, jsi::String deviceID) { size_t messageType = std::lround(encryptedDataJSI.getProperty(rt, "messageType").asNumber()); std::string message = encryptedDataJSI.getProperty(rt, "message").asString(rt).utf8(rt); auto deviceIDCpp{deviceID.utf8(rt)}; return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=, &innerRt]() { std::string error; std::string decryptedMessage; try { crypto::EncryptedData encryptedData{ std::vector(message.begin(), message.end()), messageType}; decryptedMessage = this->contentCryptoModule->decrypt(deviceIDCpp, encryptedData); - this->persistCryptoModules(true, false); + this->persistCryptoModules(true, std::nullopt); } catch (const std::exception &e) { error = e.what(); } this->jsInvoker_->invokeAsync([=, &innerRt]() { if (error.size()) { promise->reject(error); return; } promise->resolve( jsi::String::createFromUtf8(innerRt, decryptedMessage)); }); }; this->cryptoThread->scheduleTask(job); }); } jsi::Value CommCoreModule::decryptAndPersist( jsi::Runtime &rt, jsi::Object encryptedDataJSI, jsi::String deviceID, jsi::String messageID) { size_t messageType = std::lround(encryptedDataJSI.getProperty(rt, "messageType").asNumber()); std::string message = encryptedDataJSI.getProperty(rt, "message").asString(rt).utf8(rt); auto deviceIDCpp{deviceID.utf8(rt)}; auto messageIDCpp{messageID.utf8(rt)}; return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=, &innerRt]() { std::string error; std::string decryptedMessage; try { crypto::EncryptedData encryptedData{ std::vector(message.begin(), message.end()), messageType}; decryptedMessage = this->contentCryptoModule->decrypt(deviceIDCpp, encryptedData); std::string storedSecretKey = getAccountDataKey(secureStoreAccountDataKey); crypto::Persist newContentPersist = this->contentCryptoModule->storeAsB64(storedSecretKey); std::promise persistencePromise; std::future persistenceFuture = persistencePromise.get_future(); GlobalDBSingleton::instance.scheduleOrRunCancellable( [=, &persistencePromise]() { try { InboundP2PMessage message{ messageIDCpp, deviceIDCpp, decryptedMessage, "decrypted"}; DatabaseManager::getQueryExecutor().beginTransaction(); DatabaseManager::getQueryExecutor().addInboundP2PMessage( message); DatabaseManager::getQueryExecutor().storeOlmPersistData( DatabaseManager::getQueryExecutor() .getContentAccountID(), newContentPersist); DatabaseManager::getQueryExecutor().commitTransaction(); persistencePromise.set_value(); } catch (std::system_error &e) { DatabaseManager::getQueryExecutor().rollbackTransaction(); persistencePromise.set_exception( std::make_exception_ptr(e)); } }); persistenceFuture.get(); } catch (const std::exception &e) { error = e.what(); } this->jsInvoker_->invokeAsync([=, &innerRt]() { if (error.size()) { promise->reject(error); return; } promise->resolve( jsi::String::createFromUtf8(innerRt, decryptedMessage)); }); }; this->cryptoThread->scheduleTask(job); }); } jsi::Value CommCoreModule::signMessage(jsi::Runtime &rt, jsi::String message) { std::string messageStr = message.utf8(rt); return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=, &innerRt]() { std::string error; std::string signature; try { signature = this->contentCryptoModule->signMessage(messageStr); } catch (const std::exception &e) { error = "signing message failed with: " + std::string(e.what()); } this->jsInvoker_->invokeAsync([=, &innerRt]() { if (error.size()) { promise->reject(error); return; } auto jsiSignature{jsi::String::createFromUtf8(innerRt, signature)}; promise->resolve(std::move(jsiSignature)); }); }; this->cryptoThread->scheduleTask(job); }); } jsi::Value CommCoreModule::verifySignature( jsi::Runtime &rt, jsi::String publicKey, jsi::String message, jsi::String signature) { std::string keyStr = publicKey.utf8(rt); std::string messageStr = message.utf8(rt); std::string signatureStr = signature.utf8(rt); return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=, &innerRt]() { std::string error; try { crypto::CryptoModule::verifySignature( keyStr, messageStr, signatureStr); } catch (const std::exception &e) { error = "verifying signature failed with: " + std::string(e.what()); } this->jsInvoker_->invokeAsync([=, &innerRt]() { if (error.size()) { promise->reject(error); return; } promise->resolve(jsi::Value::undefined()); }); }; this->cryptoThread->scheduleTask(job); }); } CommCoreModule::CommCoreModule( std::shared_ptr jsInvoker) : facebook::react::CommCoreModuleSchemaCxxSpecJSI(jsInvoker), cryptoThread(std::make_unique("crypto")), draftStore(jsInvoker), threadStore(jsInvoker), messageStore(jsInvoker), reportStore(jsInvoker), userStore(jsInvoker), keyserverStore(jsInvoker), communityStore(jsInvoker), integrityStore(jsInvoker), syncedMetadataStore(jsInvoker), auxUserStore(jsInvoker), threadActivityStore(jsInvoker), entryStore(jsInvoker), messageSearchStore(jsInvoker) { GlobalDBSingleton::instance.enableMultithreading(); } double CommCoreModule::getCodeVersion(jsi::Runtime &rt) { return this->codeVersion; } jsi::Value CommCoreModule::setNotifyToken(jsi::Runtime &rt, jsi::String token) { auto notifyToken{token.utf8(rt)}; return createPromiseAsJSIValue( rt, [this, notifyToken](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [this, notifyToken, promise]() { std::string error; try { DatabaseManager::getQueryExecutor().setNotifyToken(notifyToken); } catch (std::system_error &e) { error = e.what(); } this->jsInvoker_->invokeAsync([error, promise]() { if (error.size()) { promise->reject(error); } else { promise->resolve(jsi::Value::undefined()); } }); }; GlobalDBSingleton::instance.scheduleOrRunCancellable( job, promise, this->jsInvoker_); }); } jsi::Value CommCoreModule::clearNotifyToken(jsi::Runtime &rt) { return createPromiseAsJSIValue( rt, [this](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [this, promise]() { std::string error; try { DatabaseManager::getQueryExecutor().clearNotifyToken(); } catch (std::system_error &e) { error = e.what(); } this->jsInvoker_->invokeAsync([error, promise]() { if (error.size()) { promise->reject(error); } else { promise->resolve(jsi::Value::undefined()); } }); }; GlobalDBSingleton::instance.scheduleOrRunCancellable( job, promise, this->jsInvoker_); }); }; jsi::Value CommCoreModule::stampSQLiteDBUserID(jsi::Runtime &rt, jsi::String userID) { auto currentUserID{userID.utf8(rt)}; return createPromiseAsJSIValue( rt, [this, currentUserID](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [this, promise, currentUserID]() { std::string error; try { DatabaseManager::getQueryExecutor().stampSQLiteDBUserID( currentUserID); } catch (const std::exception &e) { error = e.what(); } this->jsInvoker_->invokeAsync([error, promise]() { if (error.size()) { promise->reject(error); } else { promise->resolve(jsi::Value::undefined()); } }); }; GlobalDBSingleton::instance.scheduleOrRunCancellable( job, promise, this->jsInvoker_); }); } jsi::Value CommCoreModule::getSQLiteStampedUserID(jsi::Runtime &rt) { return createPromiseAsJSIValue( rt, [this](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [this, &innerRt, promise]() { std::string error; std::string result; try { result = DatabaseManager::getQueryExecutor().getSQLiteStampedUserID(); } catch (const std::exception &e) { error = e.what(); } this->jsInvoker_->invokeAsync([&innerRt, error, result, promise]() { if (error.size()) { promise->reject(error); } else { promise->resolve(jsi::String::createFromUtf8(innerRt, result)); } }); }; GlobalDBSingleton::instance.scheduleOrRunCancellable( job, promise, this->jsInvoker_); }); } jsi::Value CommCoreModule::clearSensitiveData(jsi::Runtime &rt) { return createPromiseAsJSIValue( rt, [this](jsi::Runtime &innerRt, std::shared_ptr promise) { GlobalDBSingleton::instance.setTasksCancelled(true); taskType job = [this, promise]() { std::string error; try { this->innerClearCommServicesAuthMetadata(); DatabaseManager::clearSensitiveData(); } catch (const std::exception &e) { error = e.what(); } this->jsInvoker_->invokeAsync([error, promise]() { if (error.size()) { promise->reject(error); } else { promise->resolve(jsi::Value::undefined()); } }); GlobalDBSingleton::instance.scheduleOrRun( []() { GlobalDBSingleton::instance.setTasksCancelled(false); }); }; GlobalDBSingleton::instance.scheduleOrRun(job); }); } bool CommCoreModule::checkIfDatabaseNeedsDeletion(jsi::Runtime &rt) { return DatabaseManager::checkIfDatabaseNeedsDeletion(); } void CommCoreModule::reportDBOperationsFailure(jsi::Runtime &rt) { DatabaseManager::reportDBOperationsFailure(); } jsi::Value CommCoreModule::computeBackupKey( jsi::Runtime &rt, jsi::String password, jsi::String backupID) { std::string passwordStr = password.utf8(rt); std::string backupIDStr = backupID.utf8(rt); return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=, &innerRt]() { std::string error; std::array<::std::uint8_t, 32> backupKey; try { backupKey = compute_backup_key(passwordStr, backupIDStr); } catch (const std::exception &e) { error = std::string{"Failed to compute backup key: "} + e.what(); } this->jsInvoker_->invokeAsync([=, &innerRt]() { if (error.size()) { promise->reject(error); return; } auto size = backupKey.size(); auto arrayBuffer = innerRt.global() .getPropertyAsFunction(innerRt, "ArrayBuffer") .callAsConstructor(innerRt, {static_cast(size)}) .asObject(innerRt) .getArrayBuffer(innerRt); auto bufferPtr = arrayBuffer.data(innerRt); memcpy(bufferPtr, backupKey.data(), size); promise->resolve(std::move(arrayBuffer)); }); }; this->cryptoThread->scheduleTask(job); }); } jsi::Value CommCoreModule::generateRandomString(jsi::Runtime &rt, double size) { return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=, &innerRt]() { std::string error; std::string randomString; try { randomString = crypto::Tools::generateRandomString(static_cast(size)); } catch (const std::exception &e) { error = "Failed to generate random string for size " + std::to_string(size) + ": " + e.what(); } this->jsInvoker_->invokeAsync( [&innerRt, error, randomString, promise]() { if (error.size()) { promise->reject(error); } else { jsi::String jsiRandomString = jsi::String::createFromUtf8(innerRt, randomString); promise->resolve(std::move(jsiRandomString)); } }); }; this->cryptoThread->scheduleTask(job); }); } jsi::Value CommCoreModule::setCommServicesAuthMetadata( jsi::Runtime &rt, jsi::String userID, jsi::String deviceID, jsi::String accessToken) { auto userIDStr{userID.utf8(rt)}; auto deviceIDStr{deviceID.utf8(rt)}; auto accessTokenStr{accessToken.utf8(rt)}; return createPromiseAsJSIValue( rt, [this, userIDStr, deviceIDStr, accessTokenStr]( jsi::Runtime &innerRt, std::shared_ptr promise) { std::string error; try { this->innerSetCommServicesAuthMetadata( userIDStr, deviceIDStr, accessTokenStr); } catch (const std::exception &e) { error = e.what(); } this->jsInvoker_->invokeAsync([error, promise]() { if (error.size()) { promise->reject(error); } else { promise->resolve(jsi::Value::undefined()); } }); }); } void CommCoreModule::innerSetCommServicesAuthMetadata( std::string userID, std::string deviceID, std::string accessToken) { CommSecureStore::set(CommSecureStore::userID, userID); CommSecureStore::set(CommSecureStore::deviceID, deviceID); CommSecureStore::set(CommSecureStore::commServicesAccessToken, accessToken); CommServicesAuthMetadataEmitter::sendAuthMetadataToJS(accessToken, userID); } jsi::Value CommCoreModule::getCommServicesAuthMetadata(jsi::Runtime &rt) { return createPromiseAsJSIValue( rt, [this](jsi::Runtime &innerRt, std::shared_ptr promise) { std::string error; std::string userID; std::string deviceID; std::string accessToken; try { folly::Optional userIDOpt = CommSecureStore::get(CommSecureStore::userID); if (userIDOpt.hasValue()) { userID = userIDOpt.value(); } folly::Optional deviceIDOpt = CommSecureStore::get(CommSecureStore::deviceID); if (deviceIDOpt.hasValue()) { deviceID = deviceIDOpt.value(); } folly::Optional accessTokenOpt = CommSecureStore::get(CommSecureStore::commServicesAccessToken); if (accessTokenOpt.hasValue()) { accessToken = accessTokenOpt.value(); } } catch (const std::exception &e) { error = e.what(); } this->jsInvoker_->invokeAsync( [&innerRt, error, userID, deviceID, accessToken, promise]() { if (error.size()) { promise->reject(error); } else { auto authMetadata = jsi::Object(innerRt); if (!userID.empty()) { authMetadata.setProperty( innerRt, "userID", jsi::String::createFromUtf8(innerRt, userID)); } if (!deviceID.empty()) { authMetadata.setProperty( innerRt, "deviceID", jsi::String::createFromUtf8(innerRt, deviceID)); } if (!accessToken.empty()) { authMetadata.setProperty( innerRt, "accessToken", jsi::String::createFromUtf8(innerRt, accessToken)); } promise->resolve(std::move(authMetadata)); } }); }); } jsi::Value CommCoreModule::clearCommServicesAuthMetadata(jsi::Runtime &rt) { return this->setCommServicesAuthMetadata( rt, jsi::String::createFromUtf8(rt, ""), jsi::String::createFromUtf8(rt, ""), jsi::String::createFromUtf8(rt, "")); } void CommCoreModule::innerClearCommServicesAuthMetadata() { return this->innerSetCommServicesAuthMetadata("", "", ""); } jsi::Value CommCoreModule::setCommServicesAccessToken( jsi::Runtime &rt, jsi::String accessToken) { auto accessTokenStr{accessToken.utf8(rt)}; return createPromiseAsJSIValue( rt, [this, accessTokenStr]( jsi::Runtime &innerRt, std::shared_ptr promise) { std::string error; try { CommSecureStore::set( CommSecureStore::commServicesAccessToken, accessTokenStr); } catch (const std::exception &e) { error = e.what(); } this->jsInvoker_->invokeAsync([error, promise]() { if (error.size()) { promise->reject(error); } else { promise->resolve(jsi::Value::undefined()); } }); }); } jsi::Value CommCoreModule::clearCommServicesAccessToken(jsi::Runtime &rt) { return createPromiseAsJSIValue( rt, [this](jsi::Runtime &innerRt, std::shared_ptr promise) { std::string error; try { CommSecureStore::set(CommSecureStore::commServicesAccessToken, ""); } catch (const std::exception &e) { error = e.what(); } this->jsInvoker_->invokeAsync([error, promise]() { if (error.size()) { promise->reject(error); } else { promise->resolve(jsi::Value::undefined()); } }); }); } void CommCoreModule::startBackupHandler(jsi::Runtime &rt) { try { ::startBackupHandler(); } catch (const std::exception &e) { throw jsi::JSError(rt, e.what()); } } void CommCoreModule::stopBackupHandler(jsi::Runtime &rt) { try { ::stopBackupHandler(); } catch (const std::exception &e) { throw jsi::JSError(rt, e.what()); } } jsi::Value CommCoreModule::createNewBackupInternal( jsi::Runtime &rt, std::string backupSecret, std::string backupMessage) { return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { this->cryptoThread->scheduleTask([=, &innerRt]() { std::string error; std::string backupID; try { backupID = crypto::Tools::generateRandomString(32); } catch (const std::exception &e) { error = "Failed to generate backupID"; } std::string pickleKey; std::string pickledAccount; if (!error.size()) { try { pickleKey = crypto::Tools::generateRandomString(64); crypto::Persist persist = this->contentCryptoModule->storeAsB64(pickleKey); pickledAccount = std::string(persist.account.begin(), persist.account.end()); } catch (const std::exception &e) { error = "Failed to pickle crypto account"; } } if (!error.size()) { auto currentID = RustPromiseManager::instance.addPromise( {promise, this->jsInvoker_, innerRt}); ::createBackup( rust::string(backupID), rust::string(backupSecret), rust::string(pickleKey), rust::string(pickledAccount), rust::string(backupMessage), currentID); } else { this->jsInvoker_->invokeAsync( [=, &innerRt]() { promise->reject(error); }); } }); }); } jsi::Value CommCoreModule::createNewBackup(jsi::Runtime &rt, jsi::String backupSecret) { std::string backupSecretStr = backupSecret.utf8(rt); return createNewBackupInternal(rt, backupSecretStr, ""); } jsi::Value CommCoreModule::createNewSIWEBackup( jsi::Runtime &rt, jsi::String backupSecret, jsi::String siweBackupMsg) { std::string backupSecretStr = backupSecret.utf8(rt); std::string siweBackupMsgStr = siweBackupMsg.utf8(rt); return createNewBackupInternal(rt, backupSecretStr, siweBackupMsgStr); } jsi::Value CommCoreModule::restoreBackupInternal( jsi::Runtime &rt, std::string backupSecret, std::string backupID, std::string maxVersion) { return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { auto currentID = RustPromiseManager::instance.addPromise( {promise, this->jsInvoker_, innerRt}); ::restoreBackup( rust::string(backupSecret), rust::string(backupID), rust::string(maxVersion), currentID); }); } jsi::Value CommCoreModule::restoreBackup( jsi::Runtime &rt, jsi::String backupSecret, jsi::String maxVersion) { std::string backupSecretStr = backupSecret.utf8(rt); std::string maxVersionStr = maxVersion.utf8(rt); return restoreBackupInternal(rt, backupSecretStr, "", maxVersionStr); } jsi::Value CommCoreModule::restoreSIWEBackup( jsi::Runtime &rt, jsi::String backupSecret, jsi::String backupID, jsi::String maxVersion) { std::string backupSecretStr = backupSecret.utf8(rt); std::string backupIDStr = backupID.utf8(rt); std::string maxVersionStr = maxVersion.utf8(rt); return restoreBackupInternal(rt, backupSecretStr, backupIDStr, maxVersionStr); } jsi::Value CommCoreModule::restoreBackupData( jsi::Runtime &rt, jsi::String backupID, jsi::String backupDataKey, jsi::String backupLogDataKey, jsi::String maxVersion) { std::string backupIDStr = backupID.utf8(rt); std::string backupDataKeyStr = backupDataKey.utf8(rt); std::string backupLogDataKeyStr = backupLogDataKey.utf8(rt); std::string maxVersionStr = maxVersion.utf8(rt); return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { auto currentID = RustPromiseManager::instance.addPromise( {promise, this->jsInvoker_, innerRt}); ::restoreBackupData( rust::string(backupIDStr), rust::string(backupDataKeyStr), rust::string(backupLogDataKeyStr), rust::string(maxVersionStr), currentID); }); } jsi::Value CommCoreModule::retrieveBackupKeys(jsi::Runtime &rt, jsi::String backupSecret) { std::string backupSecretStr = backupSecret.utf8(rt); return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { auto currentID = RustPromiseManager::instance.addPromise( {promise, this->jsInvoker_, innerRt}); ::retrieveBackupKeys(rust::string(backupSecretStr), currentID); }); } jsi::Value CommCoreModule::retrieveLatestSIWEBackupData(jsi::Runtime &rt) { return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { auto currentID = RustPromiseManager::instance.addPromise( {promise, this->jsInvoker_, innerRt}); ::retrieveLatestSIWEBackupData(currentID); }); } jsi::Value CommCoreModule::setSIWEBackupSecrets( jsi::Runtime &rt, jsi::Object siweBackupSecrets) { std::string message = siweBackupSecrets.getProperty(rt, "message").asString(rt).utf8(rt); std::string signature = siweBackupSecrets.getProperty(rt, "signature").asString(rt).utf8(rt); folly::dynamic backupSecretsJSON = folly::dynamic::object("message", message)("signature", signature); std::string backupSecrets = folly::toJson(backupSecretsJSON); return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [this, promise, backupSecrets]() { std::string error; try { DatabaseManager::getQueryExecutor().setMetadata( "siweBackupSecrets", backupSecrets); } catch (const std::exception &e) { error = e.what(); } this->jsInvoker_->invokeAsync([error, promise]() { if (error.size()) { promise->reject(error); } else { promise->resolve(jsi::Value::undefined()); } }); }; GlobalDBSingleton::instance.scheduleOrRunCancellable( job, promise, this->jsInvoker_); }); } jsi::Value CommCoreModule::getSIWEBackupSecrets(jsi::Runtime &rt) { return createPromiseAsJSIValue( rt, [this](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [this, &innerRt, promise]() { std::string error; std::string backupSecrets; try { backupSecrets = DatabaseManager::getQueryExecutor().getMetadata( "siweBackupSecrets"); } catch (const std::exception &e) { error = e.what(); } this->jsInvoker_->invokeAsync( [&innerRt, error, backupSecrets, promise]() { if (error.size()) { promise->reject(error); } else if (!backupSecrets.size()) { promise->resolve(jsi::Value::undefined()); } else { folly::dynamic backupSecretsJSON = folly::parseJson(backupSecrets); std::string message = backupSecretsJSON["message"].asString(); std::string signature = backupSecretsJSON["signature"].asString(); auto siweBackupSecrets = jsi::Object(innerRt); siweBackupSecrets.setProperty( innerRt, "message", jsi::String::createFromUtf8(innerRt, message)); siweBackupSecrets.setProperty( innerRt, "signature", jsi::String::createFromUtf8(innerRt, signature)); promise->resolve(std::move(siweBackupSecrets)); } }); }; GlobalDBSingleton::instance.scheduleOrRunCancellable( job, promise, this->jsInvoker_); }); } jsi::Value CommCoreModule::getAllInboundP2PMessages(jsi::Runtime &rt) { return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=, &innerRt]() { std::string error; std::vector messages; try { messages = DatabaseManager::getQueryExecutor().getAllInboundP2PMessage(); } catch (std::system_error &e) { error = e.what(); } auto messagesPtr = std::make_shared>( std::move(messages)); this->jsInvoker_->invokeAsync( [&innerRt, messagesPtr, error, promise]() { if (error.size()) { promise->reject(error); return; } jsi::Array jsiMessages = jsi::Array(innerRt, messagesPtr->size()); size_t writeIdx = 0; for (const InboundP2PMessage &msg : *messagesPtr) { jsi::Object jsiMsg = jsi::Object(innerRt); jsiMsg.setProperty(innerRt, "messageID", msg.message_id); jsiMsg.setProperty( innerRt, "senderDeviceID", msg.sender_device_id); jsiMsg.setProperty(innerRt, "plaintext", msg.plaintext); jsiMsg.setProperty(innerRt, "status", msg.status); jsiMessages.setValueAtIndex(innerRt, writeIdx++, jsiMsg); } promise->resolve(std::move(jsiMessages)); }); }; GlobalDBSingleton::instance.scheduleOrRunCancellable( job, promise, this->jsInvoker_); }); } jsi::Value CommCoreModule::removeInboundP2PMessages(jsi::Runtime &rt, jsi::Array ids) { std::vector msgIDsCPP{}; for (auto idx = 0; idx < ids.size(rt); idx++) { std::string msgID = ids.getValueAtIndex(rt, idx).asString(rt).utf8(rt); msgIDsCPP.push_back(msgID); } return createPromiseAsJSIValue( rt, [this, msgIDsCPP](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [this, promise, msgIDsCPP]() { std::string error; try { DatabaseManager::getQueryExecutor().removeInboundP2PMessages( msgIDsCPP); } catch (std::system_error &e) { error = e.what(); } this->jsInvoker_->invokeAsync([error, promise]() { if (error.size()) { promise->reject(error); } else { promise->resolve(jsi::Value::undefined()); } }); }; GlobalDBSingleton::instance.scheduleOrRunCancellable( job, promise, this->jsInvoker_); }); } jsi::Value CommCoreModule::getOutboundP2PMessagesByID(jsi::Runtime &rt, jsi::Array ids) { std::vector msgIDsCPP{}; for (auto idx = 0; idx < ids.size(rt); idx++) { std::string msgID = ids.getValueAtIndex(rt, idx).asString(rt).utf8(rt); msgIDsCPP.push_back(msgID); } return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=, &innerRt]() { std::string error; std::vector messages; try { messages = DatabaseManager::getQueryExecutor().getOutboundP2PMessagesByID( msgIDsCPP); } catch (std::system_error &e) { error = e.what(); } auto messagesPtr = std::make_shared>( std::move(messages)); this->jsInvoker_->invokeAsync( [&innerRt, messagesPtr, error, promise]() { if (error.size()) { promise->reject(error); return; } jsi::Array jsiMessages = jsi::Array(innerRt, messagesPtr->size()); size_t writeIdx = 0; for (const OutboundP2PMessage &msg : *messagesPtr) { jsi::Object jsiMsg = jsi::Object(innerRt); jsiMsg.setProperty(innerRt, "messageID", msg.message_id); jsiMsg.setProperty(innerRt, "deviceID", msg.device_id); jsiMsg.setProperty(innerRt, "userID", msg.user_id); jsiMsg.setProperty(innerRt, "timestamp", msg.timestamp); jsiMsg.setProperty(innerRt, "plaintext", msg.plaintext); jsiMsg.setProperty(innerRt, "ciphertext", msg.ciphertext); jsiMsg.setProperty(innerRt, "status", msg.status); jsiMsg.setProperty( innerRt, "supportsAutoRetry", msg.supports_auto_retry); jsiMessages.setValueAtIndex(innerRt, writeIdx++, jsiMsg); } promise->resolve(std::move(jsiMessages)); }); }; GlobalDBSingleton::instance.scheduleOrRunCancellable( job, promise, this->jsInvoker_); }); } jsi::Value CommCoreModule::getAllOutboundP2PMessages(jsi::Runtime &rt) { return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=, &innerRt]() { std::string error; std::vector messages; try { messages = DatabaseManager::getQueryExecutor().getAllOutboundP2PMessages(); } catch (std::system_error &e) { error = e.what(); } auto messagesPtr = std::make_shared>( std::move(messages)); this->jsInvoker_->invokeAsync( [&innerRt, messagesPtr, error, promise]() { if (error.size()) { promise->reject(error); return; } jsi::Array jsiMessages = jsi::Array(innerRt, messagesPtr->size()); size_t writeIdx = 0; for (const OutboundP2PMessage &msg : *messagesPtr) { jsi::Object jsiMsg = jsi::Object(innerRt); jsiMsg.setProperty(innerRt, "messageID", msg.message_id); jsiMsg.setProperty(innerRt, "deviceID", msg.device_id); jsiMsg.setProperty(innerRt, "userID", msg.user_id); jsiMsg.setProperty(innerRt, "timestamp", msg.timestamp); jsiMsg.setProperty(innerRt, "plaintext", msg.plaintext); jsiMsg.setProperty(innerRt, "ciphertext", msg.ciphertext); jsiMsg.setProperty(innerRt, "status", msg.status); jsiMsg.setProperty( innerRt, "supportsAutoRetry", msg.supports_auto_retry); jsiMessages.setValueAtIndex(innerRt, writeIdx++, jsiMsg); } promise->resolve(std::move(jsiMessages)); }); }; GlobalDBSingleton::instance.scheduleOrRunCancellable( job, promise, this->jsInvoker_); }); } jsi::Value CommCoreModule::markOutboundP2PMessageAsSent( jsi::Runtime &rt, jsi::String messageID, jsi::String deviceID) { auto messageIDCpp{messageID.utf8(rt)}; auto deviceIDCpp{deviceID.utf8(rt)}; return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=]() { std::string error; try { DatabaseManager::getQueryExecutor().markOutboundP2PMessageAsSent( messageIDCpp, deviceIDCpp); } catch (std::system_error &e) { error = e.what(); } this->jsInvoker_->invokeAsync([error, promise]() { if (error.size()) { promise->reject(error); } else { promise->resolve(jsi::Value::undefined()); } }); }; GlobalDBSingleton::instance.scheduleOrRunCancellable( job, promise, this->jsInvoker_); }); } jsi::Value CommCoreModule::removeOutboundP2PMessagesOlderThan( jsi::Runtime &rt, jsi::String messageID, jsi::String deviceID) { auto messageIDCpp{messageID.utf8(rt)}; auto deviceIDCpp{deviceID.utf8(rt)}; return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=]() { std::string error; try { DatabaseManager::getQueryExecutor() .removeOutboundP2PMessagesOlderThan(messageIDCpp, deviceIDCpp); } catch (std::system_error &e) { error = e.what(); } this->jsInvoker_->invokeAsync([error, promise]() { if (error.size()) { promise->reject(error); } else { promise->resolve(jsi::Value::undefined()); } }); }; GlobalDBSingleton::instance.scheduleOrRunCancellable( job, promise, this->jsInvoker_); }); } jsi::Value CommCoreModule::getSyncedDatabaseVersion(jsi::Runtime &rt) { return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=, &innerRt]() { std::string error; std::vector syncedMetadataStoreVector; try { syncedMetadataStoreVector = DatabaseManager::getQueryExecutor().getAllSyncedMetadata(); } catch (std::system_error &e) { error = e.what(); } std::string version; for (auto &entry : syncedMetadataStoreVector) { if (entry.name == "db_version") { version = entry.data; } } this->jsInvoker_->invokeAsync([&innerRt, error, promise, version]() { if (error.size()) { promise->reject(error); return; } jsi::String jsiVersion = jsi::String::createFromUtf8(innerRt, version); promise->resolve(std::move(jsiVersion)); }); }; GlobalDBSingleton::instance.scheduleOrRunCancellable( job, promise, this->jsInvoker_); }); } jsi::Value CommCoreModule::markPrekeysAsPublished(jsi::Runtime &rt) { return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=, &innerRt]() { 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(); } this->jsInvoker_->invokeAsync([=]() { if (error.size()) { promise->reject(error); return; } promise->resolve(jsi::Value::undefined()); }); }; this->cryptoThread->scheduleTask(job); }); } jsi::Value CommCoreModule::getRelatedMessages(jsi::Runtime &rt, jsi::String messageID) { std::string messageIDStr = messageID.utf8(rt); return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=, &innerRt]() { std::string error; std::shared_ptr> messages; try { messages = std::make_shared>( DatabaseManager::getQueryExecutor().getRelatedMessages( messageIDStr)); } catch (std::system_error &e) { error = e.what(); } this->jsInvoker_->invokeAsync([&innerRt, error, promise, messages, messageStore = this->messageStore]() { if (error.size()) { promise->reject(error); return; } jsi::Array jsiMessages = messageStore.parseDBDataStore(innerRt, messages); promise->resolve(std::move(jsiMessages)); }); }; GlobalDBSingleton::instance.scheduleOrRunCancellable( job, promise, this->jsInvoker_); }); } jsi::Value CommCoreModule::searchMessages( jsi::Runtime &rt, jsi::String query, jsi::String threadID, std::optional timestampCursor, std::optional messageIDCursor) { std::string queryStr = query.utf8(rt); std::string threadIDStr = threadID.utf8(rt); std::optional timestampCursorCpp; if (timestampCursor) { timestampCursorCpp = timestampCursor->utf8(rt); } std::optional messageIDCursorCpp; if (messageIDCursor) { messageIDCursorCpp = messageIDCursor->utf8(rt); } return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=, &innerRt]() { std::string error; std::shared_ptr> messages; try { messages = std::make_shared>( DatabaseManager::getQueryExecutor().searchMessages( queryStr, threadIDStr, timestampCursorCpp, messageIDCursorCpp)); } catch (std::system_error &e) { error = e.what(); } this->jsInvoker_->invokeAsync([&innerRt, error, promise, messages, messageStore = this->messageStore]() { if (error.size()) { promise->reject(error); return; } jsi::Array jsiMessages = messageStore.parseDBDataStore(innerRt, messages); promise->resolve(std::move(jsiMessages)); }); }; GlobalDBSingleton::instance.scheduleOrRunCancellable( job, promise, this->jsInvoker_); }); }; } // namespace comm diff --git a/native/cpp/CommonCpp/NativeModules/CommCoreModule.h b/native/cpp/CommonCpp/NativeModules/CommCoreModule.h index cfd090868..79dc3bba2 100644 --- a/native/cpp/CommonCpp/NativeModules/CommCoreModule.h +++ b/native/cpp/CommonCpp/NativeModules/CommCoreModule.h @@ -1,270 +1,273 @@ #pragma once #include "../CryptoTools/CryptoModule.h" #include "../DatabaseManagers/entities/Message.h" #include "../Tools/CommMMKV.h" #include "../Tools/CommSecureStore.h" #include "../Tools/WorkerThread.h" #include "../_generated/commJSI.h" #include "PersistentStorageUtilities/DataStores/AuxUserStore.h" #include "PersistentStorageUtilities/DataStores/CommunityStore.h" #include "PersistentStorageUtilities/DataStores/DraftStore.h" #include "PersistentStorageUtilities/DataStores/EntryStore.h" #include "PersistentStorageUtilities/DataStores/IntegrityStore.h" #include "PersistentStorageUtilities/DataStores/KeyserverStore.h" #include "PersistentStorageUtilities/DataStores/MessageSearchStore.h" #include "PersistentStorageUtilities/DataStores/MessageStore.h" #include "PersistentStorageUtilities/DataStores/ReportStore.h" #include "PersistentStorageUtilities/DataStores/SyncedMetadataStore.h" #include "PersistentStorageUtilities/DataStores/ThreadActivityStore.h" #include "PersistentStorageUtilities/DataStores/ThreadStore.h" #include "PersistentStorageUtilities/DataStores/UserStore.h" #include #include #include #include namespace comm { namespace jsi = facebook::jsi; class CommCoreModule : public facebook::react::CommCoreModuleSchemaCxxSpecJSI { const int codeVersion{380}; std::unique_ptr cryptoThread; const std::string secureStoreAccountDataKey = "cryptoAccountDataKey"; const std::string publicCryptoAccountID = "publicCryptoAccountID"; std::unique_ptr contentCryptoModule; const std::string notifsCryptoAccountID = "notifsCryptoAccountID"; - std::unique_ptr notifsCryptoModule; + DraftStore draftStore; ThreadStore threadStore; MessageStore messageStore; ReportStore reportStore; UserStore userStore; KeyserverStore keyserverStore; CommunityStore communityStore; IntegrityStore integrityStore; SyncedMetadataStore syncedMetadataStore; AuxUserStore auxUserStore; ThreadActivityStore threadActivityStore; 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, std::string backupMessage); jsi::Value restoreBackupInternal( jsi::Runtime &rt, std::string backupSecret, std::string backupID, std::string maxVersion); virtual jsi::Value getDraft(jsi::Runtime &rt, jsi::String key) override; virtual jsi::Value updateDraft(jsi::Runtime &rt, jsi::String key, jsi::String text) override; virtual jsi::Value moveDraft(jsi::Runtime &rt, jsi::String oldKey, jsi::String newKey) override; virtual jsi::Value getClientDBStore(jsi::Runtime &rt) override; virtual jsi::Value removeAllDrafts(jsi::Runtime &rt) override; virtual jsi::Array getAllMessagesSync(jsi::Runtime &rt) override; virtual void processReportStoreOperationsSync( jsi::Runtime &rt, jsi::Array operations) override; virtual void processMessageStoreOperationsSync( jsi::Runtime &rt, jsi::Array operations) override; virtual jsi::Array getAllThreadsSync(jsi::Runtime &rt) override; virtual void processThreadStoreOperationsSync( jsi::Runtime &rt, jsi::Array operations) override; virtual jsi::Value processDBStoreOperations(jsi::Runtime &rt, jsi::Object operations) override; template void appendDBStoreOps( jsi::Runtime &rt, jsi::Object &operations, const char *key, T &store, std::shared_ptr>> &destination); virtual jsi::Value initializeCryptoAccount(jsi::Runtime &rt) override; virtual jsi::Value getUserPublicKey(jsi::Runtime &rt) override; virtual jsi::Value getOneTimeKeys(jsi::Runtime &rt, double oneTimeKeysAmount) override; virtual jsi::Value validateAndUploadPrekeys( jsi::Runtime &rt, jsi::String authUserID, jsi::String authDeviceID, jsi::String authAccessToken) override; virtual jsi::Value validateAndGetPrekeys(jsi::Runtime &rt) override; virtual jsi::Value initializeNotificationsSession( jsi::Runtime &rt, jsi::String identityKeys, jsi::String prekey, jsi::String prekeySignature, std::optional oneTimeKey, jsi::String keyserverID) override; virtual jsi::Value isNotificationsSessionInitialized(jsi::Runtime &rt) override; virtual jsi::Value isDeviceNotificationsSessionInitialized( jsi::Runtime &rt, jsi::String deviceID) override; virtual jsi::Value isNotificationsSessionInitializedWithDevices( jsi::Runtime &rt, jsi::Array deviceIDs) override; virtual jsi::Value updateKeyserverDataInNotifStorage( jsi::Runtime &rt, jsi::Array keyserversData) override; virtual jsi::Value removeKeyserverDataFromNotifStorage( jsi::Runtime &rt, jsi::Array keyserverIDsToDelete) override; virtual jsi::Value getKeyserverDataFromNotifStorage( jsi::Runtime &rt, jsi::Array keyserverIDs) override; virtual jsi::Value initializeContentOutboundSession( jsi::Runtime &rt, jsi::String identityKeys, jsi::String prekey, jsi::String prekeySignature, std::optional oneTimeKey, jsi::String deviceID) override; virtual jsi::Value initializeContentInboundSession( jsi::Runtime &rt, jsi::String identityKeys, jsi::Object encryptedDataJSI, jsi::String deviceID, double sessionVersion, bool overwrite) override; virtual jsi::Value isContentSessionInitialized(jsi::Runtime &rt, jsi::String deviceID) override; virtual jsi::Value initializeNotificationsOutboundSession( jsi::Runtime &rt, jsi::String identityKeys, jsi::String prekey, jsi::String prekeySignature, std::optional oneTimeKey, jsi::String deviceID) override; virtual jsi::Value encrypt(jsi::Runtime &rt, jsi::String message, jsi::String deviceID) override; virtual jsi::Value encryptNotification( jsi::Runtime &rt, jsi::String payload, jsi::String deviceID) override; virtual jsi::Value encryptAndPersist( jsi::Runtime &rt, jsi::String message, jsi::String deviceID, jsi::String messageID) override; virtual jsi::Value decrypt( jsi::Runtime &rt, jsi::Object encryptedDataJSI, jsi::String deviceID) override; virtual jsi::Value decryptAndPersist( jsi::Runtime &rt, jsi::Object encryptedDataJSI, jsi::String deviceID, jsi::String messageID) override; virtual jsi::Value signMessage(jsi::Runtime &rt, jsi::String message) override; virtual jsi::Value verifySignature( jsi::Runtime &rt, jsi::String publicKey, jsi::String message, jsi::String signature) override; virtual void terminate(jsi::Runtime &rt) override; virtual double getCodeVersion(jsi::Runtime &rt) override; virtual jsi::Value setNotifyToken(jsi::Runtime &rt, jsi::String token) override; virtual jsi::Value clearNotifyToken(jsi::Runtime &rt) override; virtual jsi::Value stampSQLiteDBUserID(jsi::Runtime &rt, jsi::String userID) override; virtual jsi::Value getSQLiteStampedUserID(jsi::Runtime &rt) override; virtual jsi::Value clearSensitiveData(jsi::Runtime &rt) override; virtual bool checkIfDatabaseNeedsDeletion(jsi::Runtime &rt) override; virtual void reportDBOperationsFailure(jsi::Runtime &rt) override; virtual jsi::Value computeBackupKey( jsi::Runtime &rt, jsi::String password, jsi::String backupID) override; virtual jsi::Value generateRandomString(jsi::Runtime &rt, double size) override; virtual jsi::Value setCommServicesAuthMetadata( jsi::Runtime &rt, jsi::String userID, jsi::String deviceID, jsi::String accessToken) override; virtual void innerSetCommServicesAuthMetadata( std::string userID, std::string deviceID, std::string accessToken); virtual jsi::Value getCommServicesAuthMetadata(jsi::Runtime &rt) override; virtual jsi::Value clearCommServicesAuthMetadata(jsi::Runtime &rt) override; virtual void innerClearCommServicesAuthMetadata(); virtual jsi::Value setCommServicesAccessToken( jsi::Runtime &rt, jsi::String accessToken) override; virtual jsi::Value clearCommServicesAccessToken(jsi::Runtime &rt) override; virtual void startBackupHandler(jsi::Runtime &rt) override; virtual void stopBackupHandler(jsi::Runtime &rt) override; virtual jsi::Value createNewBackup(jsi::Runtime &rt, jsi::String backupSecret) override; virtual jsi::Value createNewSIWEBackup( jsi::Runtime &rt, jsi::String backupSecret, jsi::String siweBackupMsg) override; virtual jsi::Value restoreBackup( jsi::Runtime &rt, jsi::String backupSecret, jsi::String maxVersion) override; virtual jsi::Value restoreSIWEBackup( jsi::Runtime &rt, jsi::String backupSecret, jsi::String backupID, jsi::String maxVersion) override; virtual jsi::Value restoreBackupData( jsi::Runtime &rt, jsi::String backupID, jsi::String backupDataKey, jsi::String backupLogDataKey, jsi::String maxVersion) override; virtual jsi::Value retrieveBackupKeys(jsi::Runtime &rt, jsi::String backupSecret) override; virtual jsi::Value setSIWEBackupSecrets( jsi::Runtime &rt, jsi::Object siweBackupSecrets) override; virtual jsi::Value retrieveLatestSIWEBackupData(jsi::Runtime &rt) override; virtual jsi::Value getSIWEBackupSecrets(jsi::Runtime &rt) override; virtual jsi::Value getAllInboundP2PMessages(jsi::Runtime &rt) override; virtual jsi::Value removeInboundP2PMessages(jsi::Runtime &rt, jsi::Array ids) override; virtual jsi::Value getOutboundP2PMessagesByID(jsi::Runtime &rt, jsi::Array ids) override; virtual jsi::Value getAllOutboundP2PMessages(jsi::Runtime &rt) override; virtual jsi::Value markOutboundP2PMessageAsSent( jsi::Runtime &rt, jsi::String messageID, jsi::String deviceID) override; virtual jsi::Value removeOutboundP2PMessagesOlderThan( jsi::Runtime &rt, jsi::String messageID, jsi::String deviceID) override; virtual jsi::Value getSyncedDatabaseVersion(jsi::Runtime &rt) override; virtual jsi::Value markPrekeysAsPublished(jsi::Runtime &rt) override; virtual jsi::Value getRelatedMessages(jsi::Runtime &rt, jsi::String messageID) override; virtual jsi::Value searchMessages( jsi::Runtime &rt, jsi::String query, jsi::String threadID, std::optional timestampCursor, std::optional messageIDCursor) override; public: CommCoreModule(std::shared_ptr jsInvoker); }; } // namespace comm diff --git a/native/cpp/CommonCpp/Notifications/BackgroundDataStorage/NotificationsCryptoModule.cpp b/native/cpp/CommonCpp/Notifications/BackgroundDataStorage/NotificationsCryptoModule.cpp index a38b52836..9f33742f1 100644 --- a/native/cpp/CommonCpp/Notifications/BackgroundDataStorage/NotificationsCryptoModule.cpp +++ b/native/cpp/CommonCpp/Notifications/BackgroundDataStorage/NotificationsCryptoModule.cpp @@ -1,486 +1,557 @@ #include "NotificationsCryptoModule.h" #include "../../CryptoTools/Persist.h" #include "../../CryptoTools/Tools.h" #include "../../Tools/CommMMKV.h" #include "../../Tools/CommSecureStore.h" #include "../../Tools/Logger.h" #include "../../Tools/PlatformSpecificTools.h" #include "Logger.h" #include #include #include #include #include #include #include #include #include namespace comm { const std::string NotificationsCryptoModule::secureStoreNotificationsAccountDataKey = "notificationsCryptoAccountDataKey"; const std::string NotificationsCryptoModule::notificationsCryptoAccountID = "notificationsCryptoAccountDataID"; const std::string NotificationsCryptoModule::keyserverHostedNotificationsID = "keyserverHostedNotificationsID"; const std::string NotificationsCryptoModule::initialEncryptedMessageContent = "{\"type\": \"init\"}"; const int NotificationsCryptoModule::olmEncryptedTypeMessage = 1; // This constant is only used to migrate the existing notifications // session with production keyserver from flat file to MMKV. This // migration will fire when user updates the app. It will also fire // on dev env provided old keyserver set up is used. Developers willing // to use new keyserver set up must log out before installing updated // app version. Do not introduce new usages of this constant in the code!!! const std::string ashoatKeyserverIDUsedOnlyForMigrationFromLegacyNotifStorage = "256"; const int temporaryFilePathRandomSuffixLength = 32; +const std::string notificationsAccountKey = "NOTIFS.ACCOUNT"; std::unique_ptr NotificationsCryptoModule::deserializeCryptoModule( const std::string &path, const std::string &picklingKey) { std::ifstream pickledPersistStream(path, std::ifstream::in); if (!pickledPersistStream.good()) { throw std::runtime_error( "Attempt to deserialize non-existing notifications crypto account"); } std::stringstream pickledPersistStringStream; pickledPersistStringStream << pickledPersistStream.rdbuf(); pickledPersistStream.close(); std::string pickledPersist = pickledPersistStringStream.str(); folly::dynamic persistJSON; try { persistJSON = folly::parseJson(pickledPersist); } catch (const folly::json::parse_error &e) { throw std::runtime_error( "Notifications crypto account JSON deserialization failed with " "reason: " + std::string(e.what())); } std::string accountString = persistJSON["account"].asString(); crypto::OlmBuffer account = std::vector(accountString.begin(), accountString.end()); std::unordered_map sessions; if (persistJSON["sessions"].isNull()) { return std::make_unique( notificationsCryptoAccountID, picklingKey, crypto::Persist({account, sessions})); } for (auto &sessionKeyValuePair : persistJSON["sessions"].items()) { std::string targetUserID = sessionKeyValuePair.first.asString(); std::string sessionData = sessionKeyValuePair.second.asString(); sessions[targetUserID] = { std::vector(sessionData.begin(), sessionData.end()), 1}; } return std::make_unique( notificationsCryptoAccountID, picklingKey, crypto::Persist({account, sessions})); } void NotificationsCryptoModule::serializeAndFlushCryptoModule( std::unique_ptr cryptoModule, const std::string &path, const std::string &picklingKey) { crypto::Persist persist = cryptoModule->storeAsB64(picklingKey); folly::dynamic sessions = folly::dynamic::object; for (auto &sessionKeyValuePair : persist.sessions) { std::string targetUserID = sessionKeyValuePair.first; crypto::OlmBuffer sessionData = sessionKeyValuePair.second.buffer; sessions[targetUserID] = std::string(sessionData.begin(), sessionData.end()); } std::string account = std::string(persist.account.begin(), persist.account.end()); folly::dynamic persistJSON = folly::dynamic::object("account", account)("sessions", sessions); std::string pickledPersist = folly::toJson(persistJSON); std::string temporaryFilePathRandomSuffix = crypto::Tools::generateRandomHexString( temporaryFilePathRandomSuffixLength); std::string temporaryPath = path + temporaryFilePathRandomSuffix; mode_t readWritePermissionsMode = 0666; int temporaryFD = open(temporaryPath.c_str(), O_CREAT | O_WRONLY, readWritePermissionsMode); if (temporaryFD == -1) { throw std::runtime_error( "Failed to create temporary file. Unable to atomically update " "notifications crypto account. Details: " + std::string(strerror(errno))); } ssize_t bytesWritten = write(temporaryFD, pickledPersist.c_str(), pickledPersist.length()); if (bytesWritten == -1 || bytesWritten != pickledPersist.length()) { remove(temporaryPath.c_str()); throw std::runtime_error( "Failed to write all data to temporary file. Unable to atomically " "update notifications crypto account. Details: " + std::string(strerror(errno))); } if (fsync(temporaryFD) == -1) { remove(temporaryPath.c_str()); throw std::runtime_error( "Failed to synchronize temporary file data with hardware storage. " "Unable to atomically update notifications crypto account. Details: " + std::string(strerror(errno))); }; close(temporaryFD); if (rename(temporaryPath.c_str(), path.c_str()) == -1) { remove(temporaryPath.c_str()); throw std::runtime_error( "Failed to replace temporary file content with notifications crypto " "account. Unable to atomically update notifications crypto account. " "Details: " + std::string(strerror(errno))); } remove(temporaryPath.c_str()); } std::string NotificationsCryptoModule::getKeyserverNotificationsSessionKey( const std::string &keyserverID) { return "KEYSERVER." + keyserverID + ".NOTIFS_SESSION"; } std::string NotificationsCryptoModule::getDeviceNotificationsSessionKey( const std::string &deviceID) { return "DEVICE." + deviceID + ".NOTIFS_SESSION"; } std::string NotificationsCryptoModule::serializeNotificationsSession( std::shared_ptr session, std::string picklingKey) { crypto::OlmBuffer pickledSessionBytes = session->storeAsB64(picklingKey); std::string pickledSession = std::string{pickledSessionBytes.begin(), pickledSessionBytes.end()}; folly::dynamic serializedSessionJson = folly::dynamic::object( "session", pickledSession)("picklingKey", picklingKey); return folly::toJson(serializedSessionJson); } std::pair, std::string> NotificationsCryptoModule::deserializeNotificationsSession( const std::string &serializedSession) { folly::dynamic serializedSessionJson; try { serializedSessionJson = folly::parseJson(serializedSession); } catch (const folly::json::parse_error &e) { throw std::runtime_error( "Notifications session deserialization failed with reason: " + std::string(e.what())); } std::string pickledSession = serializedSessionJson["session"].asString(); crypto::OlmBuffer pickledSessionBytes = crypto::OlmBuffer{pickledSession.begin(), pickledSession.end()}; std::string picklingKey = serializedSessionJson["picklingKey"].asString(); std::unique_ptr session = crypto::Session::restoreFromB64(picklingKey, pickledSessionBytes); return {std::move(session), picklingKey}; } void NotificationsCryptoModule::clearSensitiveData() { std::string notificationsCryptoAccountPath = PlatformSpecificTools::getNotificationsCryptoAccountPath(); if (remove(notificationsCryptoAccountPath.c_str()) == -1 && errno != ENOENT) { throw std::runtime_error( "Unable to remove notifications crypto account. Security requirements " "might be violated."); } } void NotificationsCryptoModule::persistNotificationsSessionInternal( bool isKeyserverSession, const std::string &senderID, const std::string &picklingKey, std::shared_ptr session) { std::string serializedSession = NotificationsCryptoModule::serializeNotificationsSession( session, picklingKey); std::string notificationsSessionKey; std::string persistenceErrorMessage; if (isKeyserverSession) { notificationsSessionKey = NotificationsCryptoModule::getKeyserverNotificationsSessionKey( senderID); persistenceErrorMessage = "Failed to persist to MMKV notifications session for keyserver: " + senderID; } else { notificationsSessionKey = NotificationsCryptoModule::getDeviceNotificationsSessionKey(senderID); persistenceErrorMessage = "Failed to persist to MMKV notifications session for device: " + senderID; } bool sessionStored = CommMMKV::setString(notificationsSessionKey, serializedSession); if (!sessionStored) { throw std::runtime_error(persistenceErrorMessage); } } std::optional, std::string>> NotificationsCryptoModule::fetchNotificationsSession( bool isKeyserverSession, const std::string &senderID) { std::string notificationsSessionKey; if (isKeyserverSession) { notificationsSessionKey = NotificationsCryptoModule::getKeyserverNotificationsSessionKey( senderID); } else { notificationsSessionKey = NotificationsCryptoModule::getDeviceNotificationsSessionKey(senderID); } std::optional serializedSession; try { serializedSession = CommMMKV::getString(notificationsSessionKey); } catch (const CommMMKV::InitFromNSEForbiddenError &e) { serializedSession = std::nullopt; } if (!serializedSession.has_value() && isKeyserverSession && senderID != ashoatKeyserverIDUsedOnlyForMigrationFromLegacyNotifStorage) { throw std::runtime_error( "Missing notifications session for keyserver: " + senderID); } else if (!serializedSession.has_value()) { return std::nullopt; } return NotificationsCryptoModule::deserializeNotificationsSession( 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) { std::string picklingKey = crypto::Tools::generateRandomString(64); NotificationsCryptoModule::persistNotificationsSessionInternal( true, keyserverID, picklingKey, keyserverNotificationsSession); } void NotificationsCryptoModule::persistDeviceNotificationsSession( const std::string &deviceID, std::shared_ptr peerNotificationsSession) { std::string picklingKey = crypto::Tools::generateRandomString(64); NotificationsCryptoModule::persistNotificationsSessionInternal( false, deviceID, picklingKey, peerNotificationsSession); } bool NotificationsCryptoModule::isNotificationsSessionInitialized( const std::string &keyserverID) { std::string keyserverNotificationsSessionKey = getKeyserverNotificationsSessionKey(keyserverID); return CommMMKV::getString(keyserverNotificationsSessionKey).has_value(); } bool NotificationsCryptoModule::isDeviceNotificationsSessionInitialized( const std::string &deviceID) { std::string peerNotificationsSessionKey = getDeviceNotificationsSessionKey(deviceID); return CommMMKV::getString(peerNotificationsSessionKey).has_value(); } std::vector> NotificationsCryptoModule::isNotificationsSessionInitializedWithDevices( const std::vector &deviceIDs) { std::vector allKeys = CommMMKV::getAllKeys(); std::unordered_set allKeysSet(allKeys.begin(), allKeys.end()); std::vector> result; for (const auto &deviceID : deviceIDs) { std::string mmkvDeviceIDKey = NotificationsCryptoModule::getDeviceNotificationsSessionKey(deviceID); if (allKeysSet.find(mmkvDeviceIDKey) == allKeysSet.end()) { result.push_back({deviceID, false}); } else { result.push_back({deviceID, true}); } } 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, std::string decryptedData) : picklingKey(picklingKey), decryptedData(decryptedData) { } std::string NotificationsCryptoModule::BaseStatefulDecryptResult::getDecryptedData() { return this->decryptedData; } NotificationsCryptoModule::StatefulDecryptResult::StatefulDecryptResult( std::unique_ptr session, std::string keyserverID, std::string picklingKey, std::string decryptedData) : NotificationsCryptoModule::BaseStatefulDecryptResult:: BaseStatefulDecryptResult(picklingKey, decryptedData), sessionState(std::move(session)), keyserverID(keyserverID) { } void NotificationsCryptoModule::StatefulDecryptResult::flushState() { NotificationsCryptoModule::persistNotificationsSessionInternal( true, this->keyserverID, this->picklingKey, std::move(this->sessionState)); } NotificationsCryptoModule::LegacyStatefulDecryptResult:: LegacyStatefulDecryptResult( std::unique_ptr cryptoModule, std::string path, std::string picklingKey, std::string decryptedData) : NotificationsCryptoModule::BaseStatefulDecryptResult:: BaseStatefulDecryptResult(picklingKey, decryptedData), path(path), cryptoModule(std::move(cryptoModule)) { } void NotificationsCryptoModule::LegacyStatefulDecryptResult::flushState() { std::shared_ptr legacyNotificationsSession = this->cryptoModule->getSessionByDeviceId(keyserverHostedNotificationsID); NotificationsCryptoModule::serializeAndFlushCryptoModule( std::move(this->cryptoModule), this->path, this->picklingKey); try { NotificationsCryptoModule::persistNotificationsSession( ashoatKeyserverIDUsedOnlyForMigrationFromLegacyNotifStorage, legacyNotificationsSession); } catch (const CommMMKV::InitFromNSEForbiddenError &e) { return; } } std::unique_ptr NotificationsCryptoModule::prepareLegacyDecryptedState( const std::string &data, const size_t messageType) { folly::Optional picklingKey = comm::CommSecureStore::get( NotificationsCryptoModule::secureStoreNotificationsAccountDataKey); if (!picklingKey.hasValue()) { throw std::runtime_error( "Legacy notifications session pickling key missing."); } std::string legacyNotificationsAccountPath = comm::PlatformSpecificTools::getNotificationsCryptoAccountPath(); crypto::EncryptedData encryptedData{ std::vector(data.begin(), data.end()), messageType}; auto cryptoModule = NotificationsCryptoModule::deserializeCryptoModule( legacyNotificationsAccountPath, picklingKey.value()); std::string decryptedData = cryptoModule->decrypt( NotificationsCryptoModule::keyserverHostedNotificationsID, encryptedData); LegacyStatefulDecryptResult statefulDecryptResult( std::move(cryptoModule), legacyNotificationsAccountPath, picklingKey.value(), decryptedData); return std::make_unique( std::move(statefulDecryptResult)); } std::string NotificationsCryptoModule::decrypt( const std::string &keyserverID, const std::string &data, const size_t messageType) { auto sessionWithPicklingKey = NotificationsCryptoModule::fetchNotificationsSession(true, keyserverID); if (!sessionWithPicklingKey.has_value()) { auto statefulDecryptResult = NotificationsCryptoModule::prepareLegacyDecryptedState( data, messageType); statefulDecryptResult->flushState(); return statefulDecryptResult->getDecryptedData(); } std::unique_ptr session = std::move(sessionWithPicklingKey.value().first); std::string picklingKey = sessionWithPicklingKey.value().second; crypto::EncryptedData encryptedData{ std::vector(data.begin(), data.end()), messageType}; std::string decryptedData = session->decrypt(encryptedData); NotificationsCryptoModule::persistNotificationsSessionInternal( true, keyserverID, picklingKey, std::move(session)); return decryptedData; } crypto::EncryptedData NotificationsCryptoModule::encrypt( const std::string &deviceID, const std::string &payload) { auto sessionWithPicklingKey = NotificationsCryptoModule::fetchNotificationsSession(false, deviceID); if (!sessionWithPicklingKey.has_value()) { throw std::runtime_error( "Session with deviceID: " + deviceID + " not initialized."); } std::unique_ptr session = std::move(sessionWithPicklingKey.value().first); std::string picklingKey = sessionWithPicklingKey.value().second; crypto::EncryptedData encryptedData = session->encrypt(payload); NotificationsCryptoModule::persistNotificationsSessionInternal( false, deviceID, picklingKey, std::move(session)); return encryptedData; } std::unique_ptr NotificationsCryptoModule::statefulDecrypt( const std::string &keyserverID, const std::string &data, const size_t messageType) { auto sessionWithPicklingKey = NotificationsCryptoModule::fetchNotificationsSession(true, keyserverID); if (!sessionWithPicklingKey.has_value()) { return NotificationsCryptoModule::prepareLegacyDecryptedState( data, messageType); } std::unique_ptr session = std::move(sessionWithPicklingKey.value().first); std::string picklingKey = sessionWithPicklingKey.value().second; crypto::EncryptedData encryptedData{ std::vector(data.begin(), data.end()), messageType}; std::string decryptedData = session->decrypt(encryptedData); StatefulDecryptResult statefulDecryptResult( std::move(session), keyserverID, picklingKey, decryptedData); return std::make_unique( std::move(statefulDecryptResult)); } void NotificationsCryptoModule::flushState( std::unique_ptr baseStatefulDecryptResult) { baseStatefulDecryptResult->flushState(); } } // namespace comm diff --git a/native/cpp/CommonCpp/Notifications/BackgroundDataStorage/NotificationsCryptoModule.h b/native/cpp/CommonCpp/Notifications/BackgroundDataStorage/NotificationsCryptoModule.h index 5f0bab955..4270cfccd 100644 --- a/native/cpp/CommonCpp/Notifications/BackgroundDataStorage/NotificationsCryptoModule.h +++ b/native/cpp/CommonCpp/Notifications/BackgroundDataStorage/NotificationsCryptoModule.h @@ -1,128 +1,140 @@ #pragma once #include "../../CryptoTools/CryptoModule.h" #include #include namespace comm { class NotificationsCryptoModule { const static std::string notificationsCryptoAccountID; // Used for handling of legacy notifications sessions const static std::string secureStoreNotificationsAccountDataKey; const static std::string keyserverHostedNotificationsID; static std::unique_ptr deserializeCryptoModule( const std::string &path, const std::string &picklingKey); static void serializeAndFlushCryptoModule( std::unique_ptr cryptoModule, const std::string &path, const std::string &picklingKey); static std::string getKeyserverNotificationsSessionKey(const std::string &keyserverID); static std::string getDeviceNotificationsSessionKey(const std::string &deviceID); static std::string serializeNotificationsSession( std::shared_ptr session, std::string picklingKey); static std::pair, std::string> deserializeNotificationsSession(const std::string &serializedSession); static void persistNotificationsSessionInternal( bool isKeyserverSession, const std::string &senderID, const std::string &picklingKey, std::shared_ptr session); static std::optional, std::string>> fetchNotificationsSession( bool isKeyserverSession, const std::string &senderID); public: const static std::string initialEncryptedMessageContent; const static int olmEncryptedTypeMessage; static void clearSensitiveData(); + + // notifications sessions static void persistNotificationsSession( const std::string &keyserverID, std::shared_ptr keyserverNotificationsSession); static void persistDeviceNotificationsSession( const std::string &deviceID, std::shared_ptr peerNotificationsSession); static bool isNotificationsSessionInitialized(const std::string &keyserverID); static bool isDeviceNotificationsSessionInitialized(const std::string &deviceID); static std::vector> 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, std::string decryptedData); std::string picklingKey; std::string decryptedData; friend NotificationsCryptoModule; public: std::string getDecryptedData(); virtual void flushState() = 0; virtual ~BaseStatefulDecryptResult() = default; }; class StatefulDecryptResult : public BaseStatefulDecryptResult { StatefulDecryptResult( std::unique_ptr session, std::string keyserverID, std::string picklingKey, std::string decryptedData); std::unique_ptr sessionState; std::string keyserverID; friend NotificationsCryptoModule; public: void flushState() override; }; class LegacyStatefulDecryptResult : public BaseStatefulDecryptResult { LegacyStatefulDecryptResult( std::unique_ptr cryptoModule, std::string path, std::string picklingKey, std::string decryptedData); std::unique_ptr cryptoModule; std::string path; friend NotificationsCryptoModule; public: void flushState() override; }; private: static std::unique_ptr prepareLegacyDecryptedState( const std::string &data, const size_t messageType); public: static std::string decrypt( const std::string &keyserverID, const std::string &data, const size_t messageType); static std::unique_ptr statefulDecrypt( const std::string &keyserverID, const std::string &data, const size_t messageType); static crypto::EncryptedData encrypt(const std::string &deviceID, const std::string &payload); static void flushState(std::unique_ptr statefulDecryptResult); }; } // namespace comm