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 @@ -28,6 +28,7 @@ void generateOneTimeKeys(size_t oneTimeKeysAmount); // returns number of published keys size_t publishOneTimeKeys(); + bool verifyPrekeyPublishTime(uint64_t threshold); public: const std::string id; @@ -82,6 +83,7 @@ const std::string &publicKey, const std::string &message, const std::string &signature); + std::optional validateAndPublishPrekey(); }; } // 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,18 @@ return ::olm_account_mark_keys_as_published(this->getOlmAccount()); } +bool CryptoModule::verifyPrekeyPublishTime(uint64_t threshold) { + if (this->getUnpublishedPrekey().hasValue()) { + 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) { @@ -447,5 +460,24 @@ } } +std::optional CryptoModule::validateAndPublishPrekey() { + static const uint64_t maxPrekeyPublishTime = 10 * 60; + static const uint64_t maxOldPrekeyAge = 2 * 60; + std::optional maybeNewPrekey; + + bool shouldRotatePrekey = this->verifyPrekeyPublishTime(maxPrekeyPublishTime); + if (shouldRotatePrekey) { + maybeNewPrekey = this->generateAndGetPrekey(); + this->markPrekeyAsPublished(); + } + + bool shouldForgetPrekey = this->verifyPrekeyPublishTime(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,71 @@ }); } +jsi::Value CommCoreModule::validateAndUploadPrekeys(jsi::Runtime &rt) { + return createPromiseAsJSIValue( + rt, [=](jsi::Runtime &innerRt, std::shared_ptr promise) { + taskType job = [=, &innerRt]() { + std::string error; + std::string copyPickleKey; + crypto::Persist cryptoModuleCopy; + std::optional maybeNewPrekey; + + try { + copyPickleKey = crypto::Tools::generateRandomString(64); + cryptoModuleCopy = this->cryptoModule->storeAsB64(copyPickleKey); + maybeNewPrekey = this->cryptoModule->validateAndPublishPrekey(); + if (!maybeNewPrekey.has_value()) { + this->persistCryptoModule(); + } + } catch (const std::exception &e) { + error = e.what(); + } + + if (error.size()) { + this->jsInvoker_->invokeAsync( + [=, &innerRt]() { promise->reject(error); }); + return; + } else if (!maybeNewPrekey.has_value()) { + this->jsInvoker_->invokeAsync( + [=]() { promise->resolve(jsi::Value::undefined()); }); + return; + } + + std::string newPrekey = maybeNewPrekey.value(); + std::string prekeyUploadError; + + try { + try { + // TODO: upload prekey to identity service + } catch (const std::exception &e) { + prekeyUploadError = e.what(); + } + if (prekeyUploadError.size()) { + this->cryptoModule->restoreFromB64( + copyPickleKey, cryptoModuleCopy); + } else { + 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/_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,