diff --git a/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp b/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp index 4671f3360..b32573966 100644 --- a/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp +++ b/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp @@ -1,1719 +1,1755 @@ #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 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; 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(); } catch (std::system_error &e) { error = e.what(); } auto draftsVectorPtr = std::make_shared>(std::move(draftsVector)); auto messagesVectorPtr = std::make_shared< std::vector>>>( 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)); this->jsInvoker_->invokeAsync([&innerRt, draftsVectorPtr, messagesVectorPtr, threadsVectorPtr, messageStoreThreadsVectorPtr, reportStoreVectorPtr, userStoreVectorPtr, keyserveStoreVectorPtr, communityStoreVectorPtr, integrityStoreVectorPtr, 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]() { 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); 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); 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< std::vector>>>(rt, []() { return DatabaseManager::getQueryExecutor().getAllMessages(); }); auto messagesVectorPtr = std::make_shared>>>( std::move(messagesVector)); jsi::Array jsiMessages = this->messageStore.parseDBDataStore(rt, messagesVectorPtr); return jsiMessages; } jsi::Value CommCoreModule::processDraftStoreOperations( jsi::Runtime &rt, jsi::Array operations) { return this->draftStore.processStoreOperations(rt, std::move(operations)); } jsi::Value CommCoreModule::processMessageStoreOperations( jsi::Runtime &rt, jsi::Array operations) { return this->messageStore.processStoreOperations(rt, std::move(operations)); } 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; } jsi::Value CommCoreModule::processThreadStoreOperations( jsi::Runtime &rt, jsi::Array operations) { return this->threadStore.processStoreOperations(rt, std::move(operations)); } void CommCoreModule::processThreadStoreOperationsSync( jsi::Runtime &rt, jsi::Array operations) { this->threadStore.processStoreOperationsSync(rt, std::move(operations)); } jsi::Value CommCoreModule::processReportStoreOperations( jsi::Runtime &rt, jsi::Array operations) { return this->reportStore.processStoreOperations(rt, std::move(operations)); } void CommCoreModule::processReportStoreOperationsSync( jsi::Runtime &rt, jsi::Array operations) { this->reportStore.processStoreOperationsSync(rt, std::move(operations)); } jsi::Value CommCoreModule::processUserStoreOperations( jsi::Runtime &rt, jsi::Array operations) { return this->userStore.processStoreOperations(rt, std::move(operations)); } jsi::Value CommCoreModule::processKeyserverStoreOperations( jsi::Runtime &rt, jsi::Array operations) { return this->keyserverStore.processStoreOperations(rt, std::move(operations)); } jsi::Value CommCoreModule::processCommunityStoreOperations( jsi::Runtime &rt, jsi::Array operations) { return this->communityStore.processStoreOperations(rt, std::move(operations)); } jsi::Value CommCoreModule::processIntegrityStoreOperations( jsi::Runtime &rt, jsi::Array operations) { return this->integrityStore.processStoreOperations(rt, std::move(operations)); } void CommCoreModule::terminate(jsi::Runtime &rt) { TerminateApp::terminate(); } -void CommCoreModule::persistCryptoModule() { +void CommCoreModule::persistCryptoModules( + bool persistContentModule, + bool persistNotifsModule) { folly::Optional storedSecretKey = CommSecureStore::get(this->secureStoreAccountDataKey); if (!storedSecretKey.hasValue()) { storedSecretKey = crypto::Tools::generateRandomString(64); CommSecureStore::set( this->secureStoreAccountDataKey, storedSecretKey.value()); } - crypto::Persist newPersist = - this->cryptoModule->storeAsB64(storedSecretKey.value()); + if (!persistContentModule && !persistNotifsModule) { + return; + } + + crypto::Persist newContentPersist; + if (persistContentModule) { + newContentPersist = + this->contentCryptoModule->storeAsB64(storedSecretKey.value()); + } + + crypto::Persist newNotifsPersist; + if (persistNotifsModule) { + newNotifsPersist = + this->notifsCryptoModule->storeAsB64(storedSecretKey.value()); + } std::promise persistencePromise; std::future persistenceFuture = persistencePromise.get_future(); GlobalDBSingleton::instance.scheduleOrRunCancellable( [=, &persistencePromise]() { try { - DatabaseManager::getQueryExecutor().storeOlmPersistData( - DatabaseManager::getQueryExecutor().getContentAccountID(), - newPersist); + if (persistContentModule) { + DatabaseManager::getQueryExecutor().storeOlmPersistData( + DatabaseManager::getQueryExecutor().getContentAccountID(), + newContentPersist); + } + if (persistNotifsModule) { + DatabaseManager::getQueryExecutor().storeOlmPersistData( + DatabaseManager::getQueryExecutor().getNotifsAccountID(), + newNotifsPersist); + } persistencePromise.set_value(); } catch (std::system_error &e) { 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 persist; + crypto::Persist contentPersist; + crypto::Persist notifsPersist; std::string error; try { - std::optional accountData = + std::optional contentAccountData = DatabaseManager::getQueryExecutor().getOlmPersistAccountData( DatabaseManager::getQueryExecutor().getContentAccountID()); - if (accountData.has_value()) { - persist.account = - crypto::OlmBuffer(accountData->begin(), accountData->end()); + 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()); - persist.sessions.insert(std::make_pair( + contentPersist.sessions.insert(std::make_pair( sessionsDataItem.target_user_id, sessionDataBuffer)); } } + + 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->cryptoModule.reset(new crypto::CryptoModule( - this->publicCryptoAccountID, storedSecretKey.value(), persist)); - if (persist.isEmpty()) { - crypto::Persist newPersist = - this->cryptoModule->storeAsB64(storedSecretKey.value()); - GlobalDBSingleton::instance.scheduleOrRunCancellable( - [=]() { - std::string error; - try { - DatabaseManager::getQueryExecutor().storeOlmPersistData( - DatabaseManager::getQueryExecutor() - .getContentAccountID(), - newPersist); - } catch (std::system_error &e) { - error = e.what(); - } - this->jsInvoker_->invokeAsync([=]() { - if (error.size()) { - promise->reject(error); - return; - } - }); - }, - promise, - this->jsInvoker_); + this->contentCryptoModule.reset(new crypto::CryptoModule( + this->publicCryptoAccountID, + storedSecretKey.value(), + contentPersist)); + + this->notifsCryptoModule.reset(new crypto::CryptoModule( + this->notifsCryptoAccountID, + storedSecretKey.value(), + notifsPersist)); - } else { - this->cryptoModule->restoreFromB64( - storedSecretKey.value(), persist); - this->jsInvoker_->invokeAsync([=]() { - if (error.size()) { - promise->reject(error); - return; - } - }); - } try { - NotificationsCryptoModule::initializeNotificationsCryptoAccount( - "Comm"); + this->persistCryptoModules( + contentPersist.isEmpty(), notifsPersist.isEmpty()); } catch (const std::exception &e) { error = e.what(); } + this->jsInvoker_->invokeAsync([=]() { if (error.size()) { promise->reject(error); return; } - promise->resolve(jsi::Value::undefined()); }); + + 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->cryptoModule == nullptr) { + if (this->contentCryptoModule == nullptr || + this->notifsCryptoModule == nullptr) { error = "user has not been initialized"; } else { - primaryKeysResult = this->cryptoModule->getIdentityKeys(); - } - try { - if (!error.size()) { - notificationsKeysResult = - NotificationsCryptoModule::getNotificationsIdentityKeys( - "Comm"); - } - } catch (const std::exception &e) { - error = e.what(); + primaryKeysResult = this->contentCryptoModule->getIdentityKeys(); + notificationsKeysResult = + this->notifsCryptoModule->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->cryptoModule->signMessage(blobPayloadCpp); + 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::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->cryptoModule == nullptr) { + if (this->contentCryptoModule == nullptr || + this->notifsCryptoModule == nullptr) { this->jsInvoker_->invokeAsync([=, &innerRt]() { promise->reject("user has not been initialized"); }); return; } try { - contentResult = this->cryptoModule->getOneTimeKeysForPublishing( + contentResult = + this->contentCryptoModule->getOneTimeKeysForPublishing( + oneTimeKeysAmount); + notifResult = this->notifsCryptoModule->getOneTimeKeysForPublishing( oneTimeKeysAmount); - this->persistCryptoModule(); - notifResult = NotificationsCryptoModule:: - getNotificationsOneTimeKeysForPublishing( - oneTimeKeysAmount, "Comm"); + this->persistCryptoModules(true, true); } 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); }); } -std::pair getNotificationsPrekeyAndSignature() { - // TODO: Implement notifs prekey rotation. - // Notifications prekey is not rotated at this moment. It - // is fetched with signature to match identity service API. - std::string notificationsPrekey = - NotificationsCryptoModule::getNotificationsPrekey("Comm"); - std::string notificationsPrekeySignature = - NotificationsCryptoModule::getNotificationsPrekeySignature("Comm"); - - return std::make_pair(notificationsPrekey, notificationsPrekeySignature); -} - 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 maybePrekeyToUpload; + std::optional maybeContentPrekeyToUpload; + std::optional maybeNotifsPrekeyToUpload; - if (this->cryptoModule == nullptr) { + if (this->contentCryptoModule == nullptr || + this->notifsCryptoModule == nullptr) { this->jsInvoker_->invokeAsync([=, &innerRt]() { promise->reject("user has not been initialized"); }); return; } try { - maybePrekeyToUpload = this->cryptoModule->validatePrekey(); - this->persistCryptoModule(); - if (!maybePrekeyToUpload.has_value()) { - maybePrekeyToUpload = this->cryptoModule->getUnpublishedPrekey(); + maybeContentPrekeyToUpload = + this->contentCryptoModule->validatePrekey(); + maybeNotifsPrekeyToUpload = + this->notifsCryptoModule->validatePrekey(); + this->persistCryptoModules(true, true); + + if (!maybeContentPrekeyToUpload.has_value()) { + maybeContentPrekeyToUpload = + this->contentCryptoModule->getUnpublishedPrekey(); + } + if (!maybeNotifsPrekeyToUpload.has_value()) { + maybeNotifsPrekeyToUpload = + this->notifsCryptoModule->getUnpublishedPrekey(); } } catch (const std::exception &e) { error = e.what(); } if (error.size()) { this->jsInvoker_->invokeAsync( [=, &innerRt]() { promise->reject(error); }); return; - } else if (!maybePrekeyToUpload.has_value()) { + } + + if (!maybeContentPrekeyToUpload.has_value() && + !maybeNotifsPrekeyToUpload.has_value()) { this->jsInvoker_->invokeAsync( [=]() { promise->resolve(jsi::Value::undefined()); }); return; } - std::string prekeyToUpload = maybePrekeyToUpload.value(); + 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(); + } + std::string prekeyUploadError; try { - std::string prekeySignature = - this->cryptoModule->getPrekeySignature(); - std::string notificationsPrekey, notificationsPrekeySignature; - std::tie(notificationsPrekey, notificationsPrekeySignature) = - getNotificationsPrekeyAndSignature(); + std::string contentPrekeySignature = + this->contentCryptoModule->getPrekeySignature(); + std::string notifsPrekeySignature = + this->notifsCryptoModule->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 prekeyToUploadRust = - rust::String(parseOLMPrekey(prekeyToUpload)); - auto prekeySignatureRust = rust::string(prekeySignature); - auto notificationsPrekeyRust = - rust::String(parseOLMPrekey(notificationsPrekey)); + auto contentPrekeyToUploadRust = + rust::String(parseOLMPrekey(contentPrekeyToUpload)); + auto prekeySignatureRust = rust::string(contentPrekeySignature); + auto notifsPrekeyToUploadRust = + rust::String(parseOLMPrekey(notifsPrekeyToUpload)); auto notificationsPrekeySignatureRust = - rust::string(notificationsPrekeySignature); + rust::string(notifsPrekeySignature); ::identityRefreshUserPrekeys( authUserIDRust, authDeviceIDRust, authAccessTokenRust, - prekeyToUploadRust, + contentPrekeyToUploadRust, prekeySignatureRust, - notificationsPrekeyRust, + notifsPrekeyToUploadRust, notificationsPrekeySignatureRust, currentID); prekeyFuture.get(); } catch (const std::exception &e) { prekeyUploadError = e.what(); } if (!prekeyUploadError.size()) { - this->cryptoModule->markPrekeyAsPublished(); - this->persistCryptoModule(); + this->contentCryptoModule->markPrekeyAsPublished(); + this->notifsCryptoModule->markPrekeyAsPublished(); + this->persistCryptoModules(true, true); } } 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, - notifPrekeyBlob, notifPrekeySignature; + notifPrekeySignature; std::optional contentPrekeyBlob; + std::optional notifPrekeyBlob; - if (this->cryptoModule == nullptr) { + if (this->contentCryptoModule == nullptr || + this->notifsCryptoModule == nullptr) { this->jsInvoker_->invokeAsync([=, &innerRt]() { promise->reject("user has not been initialized"); }); return; } try { - contentPrekeyBlob = this->cryptoModule->validatePrekey(); + contentPrekeyBlob = this->contentCryptoModule->validatePrekey(); if (!contentPrekeyBlob) { - contentPrekeyBlob = this->cryptoModule->getUnpublishedPrekey(); + contentPrekeyBlob = + this->contentCryptoModule->getUnpublishedPrekey(); } if (!contentPrekeyBlob) { - contentPrekeyBlob = this->cryptoModule->getPrekey(); + contentPrekeyBlob = this->contentCryptoModule->getPrekey(); } - this->persistCryptoModule(); - contentPrekeySignature = this->cryptoModule->getPrekeySignature(); + notifPrekeyBlob = this->notifsCryptoModule->validatePrekey(); + if (!notifPrekeyBlob) { + notifPrekeyBlob = + this->notifsCryptoModule->getUnpublishedPrekey(); + } + if (!notifPrekeyBlob) { + notifPrekeyBlob = this->notifsCryptoModule->getPrekey(); + } + this->persistCryptoModules(true, true); - std::tie(notifPrekeyBlob, notifPrekeySignature) = - getNotificationsPrekeyAndSignature(); + contentPrekeySignature = + this->contentCryptoModule->getPrekeySignature(); + notifPrekeySignature = + this->notifsCryptoModule->getPrekeySignature(); contentPrekey = parseOLMPrekey(contentPrekeyBlob.value()); - notifPrekey = parseOLMPrekey(notifPrekeyBlob); + 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, jsi::String oneTimeKey, jsi::String keyserverID) { auto identityKeysCpp{identityKeys.utf8(rt)}; auto prekeyCpp{prekey.utf8(rt)}; auto prekeySignatureCpp{prekeySignature.utf8(rt)}; auto oneTimeKeyCpp{oneTimeKey.utf8(rt)}; return createPromiseAsJSIValue( rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [=, &innerRt]() { std::string error; crypto::EncryptedData result; try { + // Introduced temporarily to make this diff non-breaking change + NotificationsCryptoModule::initializeNotificationsCryptoAccount( + "Comm"); result = NotificationsCryptoModule::initializeNotificationsSession( identityKeysCpp, prekeyCpp, prekeySignatureCpp, oneTimeKeyCpp, "Comm"); } 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::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, jsi::String oneTimeKey, jsi::String deviceID) { auto identityKeysCpp{identityKeys.utf8(rt)}; auto prekeyCpp{prekey.utf8(rt)}; auto prekeySignatureCpp{prekeySignature.utf8(rt)}; auto oneTimeKeyCpp{oneTimeKey.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 initialEncryptedMessage; try { - this->cryptoModule->initializeOutboundForSendingSession( + this->contentCryptoModule->initializeOutboundForSendingSession( deviceIDCpp, std::vector( identityKeysCpp.begin(), identityKeysCpp.end()), std::vector(prekeyCpp.begin(), prekeyCpp.end()), std::vector( prekeySignatureCpp.begin(), prekeySignatureCpp.end()), std::vector( oneTimeKeyCpp.begin(), oneTimeKeyCpp.end())); const std::string initMessage = "{\"type\": \"init\"}"; initialEncryptedMessage = - cryptoModule->encrypt(deviceIDCpp, initMessage); - this->persistCryptoModule(); + contentCryptoModule->encrypt(deviceIDCpp, initMessage); + this->persistCryptoModules(true, false); } 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{ initialEncryptedMessage.message.begin(), initialEncryptedMessage.message.end()})); }); }; this->cryptoThread->scheduleTask(job); }); } jsi::Value CommCoreModule::initializeContentInboundSession( jsi::Runtime &rt, jsi::String identityKeys, jsi::String encryptedMessage, jsi::String deviceID) { auto identityKeysCpp{identityKeys.utf8(rt)}; auto encryptedMessageCpp{encryptedMessage.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->cryptoModule->initializeInboundForReceivingSession( + this->contentCryptoModule->initializeInboundForReceivingSession( deviceIDCpp, std::vector( encryptedMessageCpp.begin(), encryptedMessageCpp.end()), std::vector( identityKeysCpp.begin(), identityKeysCpp.end())); crypto::EncryptedData encryptedData{std::vector( encryptedMessageCpp.begin(), encryptedMessageCpp.end())}; decryptedMessage = - cryptoModule->decrypt(deviceIDCpp, encryptedData); - this->persistCryptoModule(); + this->contentCryptoModule->decrypt(deviceIDCpp, encryptedData); + this->persistCryptoModules(true, false); } 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::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 = cryptoModule->encrypt(deviceIDCpp, messageCpp); - this->persistCryptoModule(); + encryptedMessage = + contentCryptoModule->encrypt(deviceIDCpp, messageCpp); + this->persistCryptoModules(true, false); } 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{ encryptedMessage.message.begin(), encryptedMessage.message.end()})); }); }; this->cryptoThread->scheduleTask(job); }); } jsi::Value CommCoreModule::decrypt( 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; std::string decryptedMessage; try { crypto::EncryptedData encryptedData{ std::vector(messageCpp.begin(), messageCpp.end()), ENCRYPTED_MESSAGE_TYPE}; decryptedMessage = - cryptoModule->decrypt(deviceIDCpp, encryptedData); - this->persistCryptoModule(); + this->contentCryptoModule->decrypt(deviceIDCpp, encryptedData); + this->persistCryptoModules(true, false); } 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->cryptoModule->signMessage(messageStr); + 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); }); } 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) { 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::setCurrentUserID(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().setCurrentUserID(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::getCurrentUserID(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().getCurrentUserID(); } 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 { 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) { taskType job = [this, promise, userIDStr, deviceIDStr, accessTokenStr]() { std::string error; try { CommSecureStore::set(CommSecureStore::userID, userIDStr); CommSecureStore::set(CommSecureStore::deviceID, deviceIDStr); CommSecureStore::set( CommSecureStore::commServicesAccessToken, accessTokenStr); CommServicesAuthMetadataEmitter::sendAuthMetadataToJS( accessTokenStr, userIDStr); } 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::getCommServicesAuthMetadata(jsi::Runtime &rt) { return createPromiseAsJSIValue( rt, [this](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [this, &innerRt, 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)); } }); }; GlobalDBSingleton::instance.scheduleOrRunCancellable( job, promise, this->jsInvoker_); }); } 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) { taskType job = [this, promise, accessTokenStr]() { 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()); } }); }; GlobalDBSingleton::instance.scheduleOrRunCancellable( job, promise, this->jsInvoker_); }); } jsi::Value CommCoreModule::clearCommServicesAccessToken(jsi::Runtime &rt) { return createPromiseAsJSIValue( rt, [this](jsi::Runtime &innerRt, std::shared_ptr promise) { taskType job = [this, 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()); } }); }; GlobalDBSingleton::instance.scheduleOrRunCancellable( job, promise, this->jsInvoker_); }); } 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::createNewBackup(jsi::Runtime &rt, jsi::String backupSecret) { std::string backupSecretStr = backupSecret.utf8(rt); 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->cryptoModule->storeAsB64(pickleKey); + 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(backupSecretStr), rust::string(pickleKey), rust::string(pickledAccount), currentID); } else { this->jsInvoker_->invokeAsync( [=, &innerRt]() { promise->reject(error); }); } }); }); } jsi::Value CommCoreModule::restoreBackup(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}); ::restoreBackup(rust::string(backupSecretStr), currentID); }); } jsi::Value CommCoreModule::restoreBackupData( jsi::Runtime &rt, jsi::String backupID, jsi::String backupDataKey, jsi::String backupLogDataKey) { std::string backupIDStr = backupID.utf8(rt); std::string backupDataKeyStr = backupDataKey.utf8(rt); std::string backupLogDataKeyStr = backupLogDataKey.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), 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); }); } } // namespace comm diff --git a/native/cpp/CommonCpp/NativeModules/CommCoreModule.h b/native/cpp/CommonCpp/NativeModules/CommCoreModule.h index a3928aa4f..b5c50695c 100644 --- a/native/cpp/CommonCpp/NativeModules/CommCoreModule.h +++ b/native/cpp/CommonCpp/NativeModules/CommCoreModule.h @@ -1,174 +1,177 @@ #pragma once #include "../CryptoTools/CryptoModule.h" #include "../Tools/CommMMKV.h" #include "../Tools/CommSecureStore.h" #include "../Tools/WorkerThread.h" #include "../_generated/commJSI.h" #include "PersistentStorageUtilities/DataStores/CommunityStore.h" #include "PersistentStorageUtilities/DataStores/DraftStore.h" #include "PersistentStorageUtilities/DataStores/IntegrityStore.h" #include "PersistentStorageUtilities/DataStores/KeyserverStore.h" #include "PersistentStorageUtilities/DataStores/MessageStore.h" #include "PersistentStorageUtilities/DataStores/ReportStore.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{330}; std::unique_ptr cryptoThread; const std::string secureStoreAccountDataKey = "cryptoAccountDataKey"; const std::string publicCryptoAccountID = "publicCryptoAccountID"; - std::unique_ptr cryptoModule; + 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; - void persistCryptoModule(); + void + persistCryptoModules(bool persistContentModule, bool persistNotifsModule); 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 jsi::Value processDraftStoreOperations(jsi::Runtime &rt, jsi::Array operations) override; virtual jsi::Value processReportStoreOperations( jsi::Runtime &rt, jsi::Array operations) override; virtual void processReportStoreOperationsSync( jsi::Runtime &rt, jsi::Array operations) override; virtual jsi::Value processMessageStoreOperations( 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 jsi::Value processThreadStoreOperations( jsi::Runtime &rt, jsi::Array operations) override; virtual void processThreadStoreOperationsSync( jsi::Runtime &rt, jsi::Array operations) override; virtual jsi::Value processUserStoreOperations(jsi::Runtime &rt, jsi::Array operations) override; virtual jsi::Value processKeyserverStoreOperations( jsi::Runtime &rt, jsi::Array operations) override; virtual jsi::Value processCommunityStoreOperations( jsi::Runtime &rt, jsi::Array operations) override; virtual jsi::Value processIntegrityStoreOperations( jsi::Runtime &rt, jsi::Array operations) override; 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, jsi::String oneTimeKeys, jsi::String keyserverID) override; virtual jsi::Value isNotificationsSessionInitialized(jsi::Runtime &rt) 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, jsi::String oneTimeKeys, jsi::String deviceID) override; virtual jsi::Value initializeContentInboundSession( jsi::Runtime &rt, jsi::String identityKeys, jsi::String encryptedMessage, jsi::String deviceID) override; virtual jsi::Value encrypt(jsi::Runtime &rt, jsi::String message, jsi::String deviceID) override; virtual jsi::Value decrypt(jsi::Runtime &rt, jsi::String message, jsi::String deviceID) override; virtual jsi::Value signMessage(jsi::Runtime &rt, jsi::String message) 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 setCurrentUserID(jsi::Runtime &rt, jsi::String userID) override; virtual jsi::Value getCurrentUserID(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 jsi::Value getCommServicesAuthMetadata(jsi::Runtime &rt) override; 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 restoreBackup(jsi::Runtime &rt, jsi::String backupSecret) override; virtual jsi::Value restoreBackupData( jsi::Runtime &rt, jsi::String backupID, jsi::String backupDataKey, jsi::String backupLogDataKey) override; virtual jsi::Value retrieveBackupKeys(jsi::Runtime &rt, jsi::String backupSecret) 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 c1ebe866f..7b1badaf0 100644 --- a/native/cpp/CommonCpp/Notifications/BackgroundDataStorage/NotificationsCryptoModule.cpp +++ b/native/cpp/CommonCpp/Notifications/BackgroundDataStorage/NotificationsCryptoModule.cpp @@ -1,344 +1,299 @@ #include "NotificationsCryptoModule.h" #include "../../CryptoTools/Persist.h" #include "../../CryptoTools/Tools.h" #include "../../Tools/CommSecureStore.h" #include "../../Tools/PlatformSpecificTools.h" #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; const int temporaryFilePathRandomSuffixLength = 32; 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()); } 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, const std::string &callingProcessName) { 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; 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 + callingProcessName + 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::getPicklingKey() { folly::Optional picklingKey = CommSecureStore::get( NotificationsCryptoModule::secureStoreNotificationsAccountDataKey); if (!picklingKey.hasValue()) { throw std::runtime_error( "Attempt to retrieve notifications crypto account before it was " "correctly initialized."); } return picklingKey.value(); } void NotificationsCryptoModule::callCryptoModule( std::function< void(const std::unique_ptr &cryptoModule)> caller, const std::string &callingProcessName) { const std::string picklingKey = NotificationsCryptoModule::getPicklingKey(); const std::string path = PlatformSpecificTools::getNotificationsCryptoAccountPath(); std::unique_ptr cryptoModule = NotificationsCryptoModule::deserializeCryptoModule(path, picklingKey); caller(cryptoModule); NotificationsCryptoModule::serializeAndFlushCryptoModule( std::move(cryptoModule), path, picklingKey, callingProcessName); } void NotificationsCryptoModule::initializeNotificationsCryptoAccount( const std::string &callingProcessName) { const std::string notificationsCryptoAccountPath = PlatformSpecificTools::getNotificationsCryptoAccountPath(); std::ifstream notificationCryptoAccountCheck(notificationsCryptoAccountPath); if (notificationCryptoAccountCheck.good()) { // Implemented in CommmCoreModule semantics regarding public olm account // initialization is idempotent. We should follow the same approach when it // comes to notifications notificationCryptoAccountCheck.close(); return; } // There is no reason to check if the key is already present since if we are // in this place in the code we are about to create new account std::string picklingKey = crypto::Tools::generateRandomString(64); CommSecureStore::set( NotificationsCryptoModule::secureStoreNotificationsAccountDataKey, picklingKey); std::unique_ptr cryptoModule = std::make_unique( NotificationsCryptoModule::notificationsCryptoAccountID); NotificationsCryptoModule::serializeAndFlushCryptoModule( std::move(cryptoModule), notificationsCryptoAccountPath, picklingKey, callingProcessName); } -std::string NotificationsCryptoModule::getNotificationsIdentityKeys( - const std::string &callingProcessName) { - std::string identityKeys; - auto caller = [&identityKeys]( - const std::unique_ptr &cryptoModule) { - identityKeys = cryptoModule->getIdentityKeys(); - }; - NotificationsCryptoModule::callCryptoModule(caller, callingProcessName); - return identityKeys; -} - -std::string NotificationsCryptoModule::getNotificationsPrekey( - const std::string &callingProcessName) { - std::string prekey; - auto caller = - [&prekey](const std::unique_ptr &cryptoModule) { - prekey = cryptoModule->getPrekey(); - }; - NotificationsCryptoModule::callCryptoModule(caller, callingProcessName); - return prekey; -} - -std::string NotificationsCryptoModule::getNotificationsPrekeySignature( - const std::string &callingProcessName) { - std::string prekeySignature; - auto caller = [&prekeySignature]( - const std::unique_ptr &cryptoModule) { - prekeySignature = cryptoModule->getPrekeySignature(); - }; - NotificationsCryptoModule::callCryptoModule(caller, callingProcessName); - return prekeySignature; -} - -std::string NotificationsCryptoModule::getNotificationsOneTimeKeysForPublishing( - const size_t oneTimeKeysAmount, - const std::string &callingProcessName) { - std::string oneTimeKeys; - auto caller = [&oneTimeKeys, oneTimeKeysAmount]( - const std::unique_ptr &cryptoModule) { - oneTimeKeys = cryptoModule->getOneTimeKeysForPublishing(oneTimeKeysAmount); - }; - NotificationsCryptoModule::callCryptoModule(caller, callingProcessName); - return oneTimeKeys; -} - crypto::EncryptedData NotificationsCryptoModule::initializeNotificationsSession( const std::string &identityKeys, const std::string &prekey, const std::string &prekeySignature, const std::string &oneTimeKey, const std::string &callingProcessName) { crypto::EncryptedData initialEncryptedMessage; auto caller = [&](const std::unique_ptr &cryptoModule) { cryptoModule->initializeOutboundForSendingSession( NotificationsCryptoModule::keyserverHostedNotificationsID, std::vector(identityKeys.begin(), identityKeys.end()), std::vector(prekey.begin(), prekey.end()), std::vector(prekeySignature.begin(), prekeySignature.end()), std::vector(oneTimeKey.begin(), oneTimeKey.end())); initialEncryptedMessage = cryptoModule->encrypt( NotificationsCryptoModule::keyserverHostedNotificationsID, NotificationsCryptoModule::initialEncryptedMessageContent); }; NotificationsCryptoModule::callCryptoModule(caller, callingProcessName); return initialEncryptedMessage; } bool NotificationsCryptoModule::isNotificationsSessionInitialized( const std::string &callingProcessName) { bool sessionInitialized; auto caller = [&sessionInitialized]( const std::unique_ptr &cryptoModule) { sessionInitialized = cryptoModule->hasSessionFor( NotificationsCryptoModule::keyserverHostedNotificationsID); }; NotificationsCryptoModule::callCryptoModule(caller, callingProcessName); return sessionInitialized; } 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."); } } std::string NotificationsCryptoModule::decrypt( const std::string &data, const size_t messageType, const std::string &callingProcessName) { std::string decryptedData; auto caller = [&](const std::unique_ptr &cryptoModule) { crypto::EncryptedData encryptedData{ std::vector(data.begin(), data.end()), messageType}; decryptedData = cryptoModule->decrypt( NotificationsCryptoModule::keyserverHostedNotificationsID, encryptedData); }; NotificationsCryptoModule::callCryptoModule(caller, callingProcessName); return decryptedData; } NotificationsCryptoModule::StatefulDecryptResult::StatefulDecryptResult( std::unique_ptr cryptoModule, std::string decryptedData) : cryptoModuleState(std::move(cryptoModule)), decryptedData(decryptedData) { } std::string NotificationsCryptoModule::StatefulDecryptResult::getDecryptedData() { return this->decryptedData; } std::unique_ptr NotificationsCryptoModule::statefulDecrypt( const std::string &data, const size_t messageType) { std::string path = PlatformSpecificTools::getNotificationsCryptoAccountPath(); std::string picklingKey = NotificationsCryptoModule::getPicklingKey(); std::unique_ptr cryptoModule = NotificationsCryptoModule::deserializeCryptoModule(path, picklingKey); crypto::EncryptedData encryptedData{ std::vector(data.begin(), data.end()), messageType}; std::string decryptedData = cryptoModule->decrypt( NotificationsCryptoModule::keyserverHostedNotificationsID, encryptedData); StatefulDecryptResult statefulDecryptResult( std::move(cryptoModule), decryptedData); return std::make_unique( std::move(statefulDecryptResult)); } void NotificationsCryptoModule::flushState( std::unique_ptr statefulDecryptResult, const std::string &callingProcessName) { std::string path = PlatformSpecificTools::getNotificationsCryptoAccountPath(); std::string picklingKey = NotificationsCryptoModule::getPicklingKey(); NotificationsCryptoModule::serializeAndFlushCryptoModule( std::move(statefulDecryptResult->cryptoModuleState), path, picklingKey, callingProcessName); } } // namespace comm diff --git a/native/cpp/CommonCpp/Notifications/BackgroundDataStorage/NotificationsCryptoModule.h b/native/cpp/CommonCpp/Notifications/BackgroundDataStorage/NotificationsCryptoModule.h index 7a9f4368b..4f44ad58c 100644 --- a/native/cpp/CommonCpp/Notifications/BackgroundDataStorage/NotificationsCryptoModule.h +++ b/native/cpp/CommonCpp/Notifications/BackgroundDataStorage/NotificationsCryptoModule.h @@ -1,74 +1,65 @@ #pragma once #include "../../CryptoTools/CryptoModule.h" #include #include namespace comm { class NotificationsCryptoModule { const static std::string secureStoreNotificationsAccountDataKey; const static std::string notificationsCryptoAccountID; const static std::string keyserverHostedNotificationsID; const static std::string initialEncryptedMessageContent; static std::string getPicklingKey(); static void serializeAndFlushCryptoModule( std::unique_ptr cryptoModule, const std::string &path, const std::string &picklingKey, const std::string &callingProcessName); static std::unique_ptr deserializeCryptoModule( const std::string &path, const std::string &picklingKey); static void callCryptoModule( std::function &cryptoModule)> caller, const std::string &callingProcessName); public: const static int olmEncryptedTypeMessage; static void initializeNotificationsCryptoAccount(const std::string &callingProcessName); static void clearSensitiveData(); - static std::string - getNotificationsIdentityKeys(const std::string &callingProcessName); - static std::string - getNotificationsPrekey(const std::string &callingProcessName); - static std::string - getNotificationsPrekeySignature(const std::string &callingProcessName); - static std::string getNotificationsOneTimeKeysForPublishing( - const size_t oneTimeKeysAmount, - const std::string &callingProcessName); static crypto::EncryptedData initializeNotificationsSession( const std::string &identityKeys, const std::string &prekey, const std::string &prekeySignature, const std::string &oneTimeKeys, const std::string &callingProcessName); static bool isNotificationsSessionInitialized(const std::string &callingProcessName); static std::string decrypt( const std::string &data, const size_t messageType, const std::string &callingProcessName); class StatefulDecryptResult { StatefulDecryptResult( std::unique_ptr cryptoModule, std::string decryptedData); std::unique_ptr cryptoModuleState; std::string decryptedData; friend NotificationsCryptoModule; public: std::string getDecryptedData(); }; static std::unique_ptr statefulDecrypt(const std::string &data, const size_t messageType); static void flushState( std::unique_ptr statefulDecryptResult, const std::string &callingProcessName); }; } // namespace comm