diff --git a/native/cpp/CommonCpp/CryptoTools/CryptoModule.h b/native/cpp/CommonCpp/CryptoTools/CryptoModule.h --- a/native/cpp/CommonCpp/CryptoTools/CryptoModule.h +++ b/native/cpp/CommonCpp/CryptoTools/CryptoModule.h @@ -4,7 +4,6 @@ #include #include -#include "folly/Optional.h" #include "olm/olm.h" #include "Persist.h" @@ -28,6 +27,7 @@ void generateOneTimeKeys(size_t oneTimeKeysAmount); // returns number of published keys size_t publishOneTimeKeys(); + bool prekeyExistsAndOlderThan(uint64_t threshold); public: const std::string id; @@ -49,7 +49,7 @@ std::uint8_t getNumPrekeys(); std::string getPrekey(); std::string getPrekeySignature(); - folly::Optional getUnpublishedPrekey(); + std::optional getUnpublishedPrekey(); std::string generateAndGetPrekey(); void markPrekeyAsPublished(); void forgetOldPrekey(); @@ -82,6 +82,7 @@ const std::string &publicKey, const std::string &message, const std::string &signature); + std::optional validatePrekey(); }; } // namespace crypto diff --git a/native/cpp/CommonCpp/CryptoTools/CryptoModule.cpp b/native/cpp/CommonCpp/CryptoTools/CryptoModule.cpp --- a/native/cpp/CommonCpp/CryptoTools/CryptoModule.cpp +++ b/native/cpp/CommonCpp/CryptoTools/CryptoModule.cpp @@ -4,6 +4,7 @@ #include "olm/account.hh" #include "olm/session.hh" +#include #include namespace comm { @@ -102,6 +103,22 @@ return ::olm_account_mark_keys_as_published(this->getOlmAccount()); } +bool CryptoModule::prekeyExistsAndOlderThan(uint64_t threshold) { + // Our fork of Olm only remembers two prekeys at a time. + // If the new one hasn't been published, then the old one is still active. + // In that scenario, we need to avoid rotating the prekey because it will + // result in the old active prekey being discarded. + if (this->getUnpublishedPrekey().has_value()) { + return false; + } + + uint64_t currentTime = std::time(nullptr); + uint64_t lastPrekeyPublishTime = + ::olm_account_get_last_prekey_publish_time(this->getOlmAccount()); + + return currentTime - lastPrekeyPublishTime >= threshold; +} + Keys CryptoModule::keysFromStrings( const std::string &identityKeys, const std::string &oneTimeKeys) { @@ -167,7 +184,7 @@ return std::string{signatureBuffer.begin(), signatureBuffer.end()}; } -folly::Optional CryptoModule::getUnpublishedPrekey() { +std::optional CryptoModule::getUnpublishedPrekey() { OlmBuffer prekey; prekey.resize(::olm_account_prekey_length(this->getOlmAccount())); @@ -175,7 +192,7 @@ this->getOlmAccount(), prekey.data(), prekey.size()); if (0 == retval) { - return folly::none; + return std::nullopt; } else if (-1 == retval) { throw std::runtime_error{ "error getUnpublishedPrekey => " + @@ -447,5 +464,24 @@ } } +std::optional CryptoModule::validatePrekey() { + static const uint64_t maxPrekeyPublishTime = 10 * 60; + static const uint64_t maxOldPrekeyAge = 2 * 60; + std::optional maybeNewPrekey; + + bool shouldRotatePrekey = + this->prekeyExistsAndOlderThan(maxPrekeyPublishTime); + if (shouldRotatePrekey) { + maybeNewPrekey = this->generateAndGetPrekey(); + } + + bool shouldForgetPrekey = this->prekeyExistsAndOlderThan(maxOldPrekeyAge); + if (shouldForgetPrekey) { + this->forgetOldPrekey(); + } + + return maybeNewPrekey; +} + } // namespace crypto } // namespace comm diff --git a/native/cpp/CommonCpp/NativeModules/CommCoreModule.h b/native/cpp/CommonCpp/NativeModules/CommCoreModule.h --- a/native/cpp/CommonCpp/NativeModules/CommCoreModule.h +++ b/native/cpp/CommonCpp/NativeModules/CommCoreModule.h @@ -72,6 +72,7 @@ jsi::Runtime &rt, double oneTimeKeysAmount) override; virtual jsi::Value generateAndGetPrekeys(jsi::Runtime &rt) override; + virtual jsi::Value validateAndUploadPrekeys(jsi::Runtime &rt) override; virtual jsi::Value initializeNotificationsSession( jsi::Runtime &rt, jsi::String identityKeys, diff --git a/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp b/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp --- a/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp +++ b/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp @@ -654,6 +654,78 @@ }); } +jsi::Value CommCoreModule::validateAndUploadPrekeys(jsi::Runtime &rt) { + return createPromiseAsJSIValue( + rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { + taskType job = [=, &innerRt]() { + std::string error; + std::optional maybePrekeyToUpload; + + try { + maybePrekeyToUpload = this->cryptoModule->validatePrekey(); + if (maybePrekeyToUpload.has_value()) { + this->persistCryptoModule(); + } else { + maybePrekeyToUpload = this->cryptoModule->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()) { + this->jsInvoker_->invokeAsync( + [=]() { promise->resolve(jsi::Value::undefined()); }); + return; + } + + std::string prekeyToUpload = maybePrekeyToUpload.value(); + std::string prekeyUploadError; + + try { + std::string prekeySignature = + this->cryptoModule->getPrekeySignature(); + // 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"); + try { + // TODO: upload prekey to identity service + } catch (const std::exception &e) { + prekeyUploadError = e.what(); + } + + if (!prekeyUploadError.size()) { + this->cryptoModule->markPrekeyAsPublished(); + this->persistCryptoModule(); + } + } 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::initializeNotificationsSession( jsi::Runtime &rt, jsi::String identityKeys, diff --git a/native/cpp/CommonCpp/Notifications/BackgroundDataStorage/NotificationsCryptoModule.h b/native/cpp/CommonCpp/Notifications/BackgroundDataStorage/NotificationsCryptoModule.h --- a/native/cpp/CommonCpp/Notifications/BackgroundDataStorage/NotificationsCryptoModule.h +++ b/native/cpp/CommonCpp/Notifications/BackgroundDataStorage/NotificationsCryptoModule.h @@ -33,6 +33,8 @@ static std::string getNotificationsIdentityKeys(const std::string &callingProcessName); static std::string + getNotificationsPrekey(const std::string &callingProcessName); + static std::string generateAndGetNotificationsPrekey(const std::string &callingProcessName); static std::string getNotificationsPrekeySignature(const std::string &callingProcessName); diff --git a/native/cpp/CommonCpp/Notifications/BackgroundDataStorage/NotificationsCryptoModule.cpp b/native/cpp/CommonCpp/Notifications/BackgroundDataStorage/NotificationsCryptoModule.cpp --- a/native/cpp/CommonCpp/Notifications/BackgroundDataStorage/NotificationsCryptoModule.cpp +++ b/native/cpp/CommonCpp/Notifications/BackgroundDataStorage/NotificationsCryptoModule.cpp @@ -203,6 +203,17 @@ 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::generateAndGetNotificationsPrekey( const std::string &callingProcessName) { std::string prekey; diff --git a/native/cpp/CommonCpp/_generated/commJSI-generated.cpp b/native/cpp/CommonCpp/_generated/commJSI-generated.cpp --- a/native/cpp/CommonCpp/_generated/commJSI-generated.cpp +++ b/native/cpp/CommonCpp/_generated/commJSI-generated.cpp @@ -75,6 +75,9 @@ static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_generateAndGetPrekeys(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { return static_cast(&turboModule)->generateAndGetPrekeys(rt); } +static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_validateAndUploadPrekeys(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + return static_cast(&turboModule)->validateAndUploadPrekeys(rt); +} static jsi::Value __hostFunction_CommCoreModuleSchemaCxxSpecJSI_initializeNotificationsSession(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { return static_cast(&turboModule)->initializeNotificationsSession(rt, args[0].asString(rt), args[1].asString(rt), args[2].asString(rt), args[3].asString(rt)); } @@ -169,6 +172,7 @@ methodMap_["getPrimaryOneTimeKeys"] = MethodMetadata {1, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getPrimaryOneTimeKeys}; methodMap_["getNotificationsOneTimeKeys"] = MethodMetadata {1, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_getNotificationsOneTimeKeys}; methodMap_["generateAndGetPrekeys"] = MethodMetadata {0, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_generateAndGetPrekeys}; + methodMap_["validateAndUploadPrekeys"] = MethodMetadata {0, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_validateAndUploadPrekeys}; methodMap_["initializeNotificationsSession"] = MethodMetadata {4, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_initializeNotificationsSession}; methodMap_["isNotificationsSessionInitialized"] = MethodMetadata {0, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_isNotificationsSessionInitialized}; methodMap_["initializeContentOutboundSession"] = MethodMetadata {5, __hostFunction_CommCoreModuleSchemaCxxSpecJSI_initializeContentOutboundSession}; diff --git a/native/cpp/CommonCpp/_generated/commJSI.h b/native/cpp/CommonCpp/_generated/commJSI.h --- a/native/cpp/CommonCpp/_generated/commJSI.h +++ b/native/cpp/CommonCpp/_generated/commJSI.h @@ -40,6 +40,7 @@ virtual jsi::Value getPrimaryOneTimeKeys(jsi::Runtime &rt, double oneTimeKeysAmount) = 0; virtual jsi::Value getNotificationsOneTimeKeys(jsi::Runtime &rt, double oneTimeKeysAmount) = 0; virtual jsi::Value generateAndGetPrekeys(jsi::Runtime &rt) = 0; + virtual jsi::Value validateAndUploadPrekeys(jsi::Runtime &rt) = 0; virtual jsi::Value initializeNotificationsSession(jsi::Runtime &rt, jsi::String identityKeys, jsi::String prekey, jsi::String prekeySignature, jsi::String oneTimeKeys) = 0; virtual jsi::Value isNotificationsSessionInitialized(jsi::Runtime &rt) = 0; virtual jsi::Value initializeContentOutboundSession(jsi::Runtime &rt, jsi::String identityKeys, jsi::String prekey, jsi::String prekeySignature, jsi::String oneTimeKeys, jsi::String deviceID) = 0; @@ -244,6 +245,14 @@ return bridging::callFromJs( rt, &T::generateAndGetPrekeys, jsInvoker_, instance_); } + jsi::Value validateAndUploadPrekeys(jsi::Runtime &rt) override { + static_assert( + bridging::getParameterCount(&T::validateAndUploadPrekeys) == 1, + "Expected validateAndUploadPrekeys(...) to have 1 parameters"); + + return bridging::callFromJs( + rt, &T::validateAndUploadPrekeys, jsInvoker_, instance_); + } jsi::Value initializeNotificationsSession(jsi::Runtime &rt, jsi::String identityKeys, jsi::String prekey, jsi::String prekeySignature, jsi::String oneTimeKeys) override { static_assert( bridging::getParameterCount(&T::initializeNotificationsSession) == 5, diff --git a/native/schema/CommCoreModuleSchema.js b/native/schema/CommCoreModuleSchema.js --- a/native/schema/CommCoreModuleSchema.js +++ b/native/schema/CommCoreModuleSchema.js @@ -82,6 +82,7 @@ oneTimeKeysAmount: number, ) => Promise; +generateAndGetPrekeys: () => Promise; + +validateAndUploadPrekeys: () => Promise; +initializeNotificationsSession: ( identityKeys: string, prekey: string,