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 @@ -9,10 +9,18 @@ class NotificationsCryptoModule { const static std::string notificationsCryptoAccountID; - // Used for migration of legacy notifications sessions + // Used for handling of legacy notifications sessions + const static std::string secureStoreNotificationsAccountDataKey; + const static std::string keyserverHostedNotificationsID; + static std::unique_ptr<crypto::CryptoModule> deserializeCryptoModule( const std::string &path, const std::string &picklingKey); + static void serializeAndFlushCryptoModule( + std::unique_ptr<crypto::CryptoModule> cryptoModule, + const std::string &path, + const std::string &picklingKey); + static std::string getKeyserverNotificationsSessionKey(const std::string &keyserverID); static std::string serializeNotificationsSession( @@ -38,34 +46,72 @@ const std::string &keyserverID, std::shared_ptr<crypto::Session> keyserverNotificationsSession); static bool isNotificationsSessionInitialized(const std::string &keyserverID); - static std::string decrypt( - const std::string &keyserverID, - const std::string &data, - const size_t messageType); - class StatefulDecryptResult { + class BaseStatefulDecryptResult { + BaseStatefulDecryptResult( + std::string picklingKey, + std::string decryptedData); + + std::string picklingKey; + std::string decryptedData; + friend NotificationsCryptoModule; + + public: + std::string getDecryptedData(); + std::string getPicklingKey(); + virtual void flushState() = 0; + virtual ~BaseStatefulDecryptResult() = default; + }; + + class StatefulDecryptResult : public BaseStatefulDecryptResult { StatefulDecryptResult( std::unique_ptr<crypto::Session> session, std::string keyserverID, std::string picklingKey, std::string decryptedData); + std::unique_ptr<crypto::Session> sessionState; std::string keyserverID; - std::string picklingKey; - std::string decryptedData; friend NotificationsCryptoModule; public: - std::string getDecryptedData(); std::string getKeyserverID(); - std::string getPicklingKey(); + void flushState() override; + }; + + class LegacyStatefulDecryptResult : public BaseStatefulDecryptResult { + LegacyStatefulDecryptResult( + std::unique_ptr<crypto::CryptoModule> cryptoModule, + std::string path, + std::string picklingKey, + std::string decryptedData); + std::unique_ptr<crypto::CryptoModule> cryptoModule; + std::string path; + friend NotificationsCryptoModule; + + public: + std::string getPath(); + void flushState() override; }; - static std::unique_ptr<StatefulDecryptResult> statefulDecrypt( +private: + static std::unique_ptr<NotificationsCryptoModule::BaseStatefulDecryptResult> + prepareLegacyDecryptedState( + const std::string &data, + const size_t messageType); + +public: + static std::string decrypt( const std::string &keyserverID, const std::string &data, const size_t messageType); + + static std::unique_ptr<BaseStatefulDecryptResult> statefulDecrypt( + const std::string &keyserverID, + const std::string &data, + const size_t messageType); + static void - flushState(std::unique_ptr<StatefulDecryptResult> statefulDecryptResult); + flushState(std::unique_ptr<BaseStatefulDecryptResult> statefulDecryptResult); }; } // namespace comm 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 @@ -6,17 +6,23 @@ #include "../../Tools/Logger.h" #include "../../Tools/PlatformSpecificTools.h" +#include <fcntl.h> #include <folly/String.h> #include <folly/dynamic.h> #include <folly/json.h> +#include <unistd.h> #include <fstream> #include <memory> #include <sstream> 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; @@ -29,6 +35,7 @@ // app version. Do not introduce new usages of this constant in the code!!! const std::string ashoatKeyserverIDUsedOnlyForMigrationFromLegacyNotifStorage = "256"; +const int temporaryFilePathRandomSuffixLength = 32; std::unique_ptr<crypto::CryptoModule> NotificationsCryptoModule::deserializeCryptoModule( @@ -77,6 +84,68 @@ crypto::Persist({account, sessions})); } +void NotificationsCryptoModule::serializeAndFlushCryptoModule( + std::unique_ptr<crypto::CryptoModule> cryptoModule, + const std::string &path, + const std::string &picklingKey) { + crypto::Persist persist = cryptoModule->storeAsB64(picklingKey); + + folly::dynamic sessions = folly::dynamic::object; + for (auto &sessionKeyValuePair : persist.sessions) { + std::string targetUserID = sessionKeyValuePair.first; + crypto::OlmBuffer sessionData = sessionKeyValuePair.second; + sessions[targetUserID] = + std::string(sessionData.begin(), sessionData.end()); + } + + std::string account = + std::string(persist.account.begin(), persist.account.end()); + folly::dynamic persistJSON = + folly::dynamic::object("account", account)("sessions", sessions); + std::string pickledPersist = folly::toJson(persistJSON); + + std::string temporaryFilePathRandomSuffix = + crypto::Tools::generateRandomHexString( + temporaryFilePathRandomSuffixLength); + std::string temporaryPath = path + temporaryFilePathRandomSuffix; + + mode_t readWritePermissionsMode = 0666; + int temporaryFD = + open(temporaryPath.c_str(), O_CREAT | O_WRONLY, readWritePermissionsMode); + if (temporaryFD == -1) { + throw std::runtime_error( + "Failed to create temporary file. Unable to atomically update " + "notifications crypto account. Details: " + + std::string(strerror(errno))); + } + ssize_t bytesWritten = + write(temporaryFD, pickledPersist.c_str(), pickledPersist.length()); + if (bytesWritten == -1 || bytesWritten != pickledPersist.length()) { + remove(temporaryPath.c_str()); + throw std::runtime_error( + "Failed to write all data to temporary file. Unable to atomically " + "update notifications crypto account. Details: " + + std::string(strerror(errno))); + } + if (fsync(temporaryFD) == -1) { + remove(temporaryPath.c_str()); + throw std::runtime_error( + "Failed to synchronize temporary file data with hardware storage. " + "Unable to atomically update notifications crypto account. Details: " + + std::string(strerror(errno))); + }; + close(temporaryFD); + if (rename(temporaryPath.c_str(), path.c_str()) == -1) { + remove(temporaryPath.c_str()); + throw std::runtime_error( + "Failed to replace temporary file content with notifications crypto " + "account. Unable to atomically update notifications crypto account. " + "Details: " + + std::string(strerror(errno))); + } + remove(temporaryPath.c_str()); +} + std::string NotificationsCryptoModule::getKeyserverNotificationsSessionKey( const std::string &keyserverID) { return "KEYSERVER." + keyserverID + ".NOTIFS_SESSION"; @@ -214,21 +283,20 @@ return CommMMKV::getString(keyserverNotificationsSessionKey).has_value(); } -std::string NotificationsCryptoModule::decrypt( - const std::string &keyserverID, - const std::string &data, - const size_t messageType) { - std::unique_ptr<crypto::Session> session; - std::string picklingKey; - std::tie(session, picklingKey) = - NotificationsCryptoModule::fetchNotificationsSession(keyserverID); +NotificationsCryptoModule::BaseStatefulDecryptResult::BaseStatefulDecryptResult( + std::string picklingKey, + std::string decryptedData) + : picklingKey(picklingKey), decryptedData(decryptedData) { +} - crypto::EncryptedData encryptedData{ - std::vector<uint8_t>(data.begin(), data.end()), messageType}; - std::string decryptedData = session->decrypt(encryptedData); - NotificationsCryptoModule::persistNotificationsSessionInternal( - keyserverID, picklingKey, std::move(session)); - return decryptedData; +std::string +NotificationsCryptoModule::BaseStatefulDecryptResult::getDecryptedData() { + return this->decryptedData; +} + +std::string +NotificationsCryptoModule::BaseStatefulDecryptResult::getPicklingKey() { + return this->picklingKey; } NotificationsCryptoModule::StatefulDecryptResult::StatefulDecryptResult( @@ -236,50 +304,133 @@ std::string keyserverID, std::string picklingKey, std::string decryptedData) - : sessionState(std::move(session)), - keyserverID(keyserverID), - picklingKey(picklingKey), - decryptedData(decryptedData) { -} - -std::string -NotificationsCryptoModule::StatefulDecryptResult::getDecryptedData() { - return this->decryptedData; + : NotificationsCryptoModule::BaseStatefulDecryptResult:: + BaseStatefulDecryptResult(picklingKey, decryptedData), + sessionState(std::move(session)), + keyserverID(keyserverID) { } std::string NotificationsCryptoModule::StatefulDecryptResult::getKeyserverID() { return this->keyserverID; } -std::string NotificationsCryptoModule::StatefulDecryptResult::getPicklingKey() { - return this->picklingKey; +void NotificationsCryptoModule::StatefulDecryptResult::flushState() { + NotificationsCryptoModule::persistNotificationsSessionInternal( + this->getKeyserverID(), + this->getPicklingKey(), + std::move(this->sessionState)); } -std::unique_ptr<NotificationsCryptoModule::StatefulDecryptResult> -NotificationsCryptoModule::statefulDecrypt( +NotificationsCryptoModule::LegacyStatefulDecryptResult:: + LegacyStatefulDecryptResult( + std::unique_ptr<crypto::CryptoModule> cryptoModule, + std::string path, + std::string picklingKey, + std::string decryptedData) + : NotificationsCryptoModule::BaseStatefulDecryptResult:: + BaseStatefulDecryptResult(picklingKey, decryptedData), + path(path), + cryptoModule(std::move(cryptoModule)) { +} + +std::string NotificationsCryptoModule::LegacyStatefulDecryptResult::getPath() { + return this->path; +} + +void NotificationsCryptoModule::LegacyStatefulDecryptResult::flushState() { + NotificationsCryptoModule::serializeAndFlushCryptoModule( + std::move(this->cryptoModule), this->getPath(), this->getPicklingKey()); +} + +std::unique_ptr<NotificationsCryptoModule::BaseStatefulDecryptResult> +NotificationsCryptoModule::prepareLegacyDecryptedState( + const std::string &data, + const size_t messageType) { + folly::Optional<std::string> picklingKey = comm::CommSecureStore::get( + NotificationsCryptoModule::secureStoreNotificationsAccountDataKey); + + if (!picklingKey.hasValue()) { + throw std::runtime_error( + "Legacy notifications session pickling key missing."); + } + + std::string legacyNotificationsAccountPath = + comm::PlatformSpecificTools::getNotificationsCryptoAccountPath(); + + crypto::EncryptedData encryptedData{ + std::vector<uint8_t>(data.begin(), data.end()), messageType}; + + auto cryptoModule = NotificationsCryptoModule::deserializeCryptoModule( + legacyNotificationsAccountPath, picklingKey.value()); + + std::string decryptedData = cryptoModule->decrypt( + NotificationsCryptoModule::keyserverHostedNotificationsID, encryptedData); + + LegacyStatefulDecryptResult statefulDecryptResult( + std::move(cryptoModule), + legacyNotificationsAccountPath, + picklingKey.value(), + decryptedData); + + return std::make_unique<LegacyStatefulDecryptResult>( + std::move(statefulDecryptResult)); +} + +std::string NotificationsCryptoModule::decrypt( const std::string &keyserverID, const std::string &data, const size_t messageType) { std::unique_ptr<crypto::Session> session; std::string picklingKey; - std::tie(session, picklingKey) = - NotificationsCryptoModule::fetchNotificationsSession(keyserverID); crypto::EncryptedData encryptedData{ std::vector<uint8_t>(data.begin(), data.end()), messageType}; + + try { + std::tie(session, picklingKey) = + NotificationsCryptoModule::fetchNotificationsSession(keyserverID); + } catch (const std::exception &e) { + auto statefulDecryptResult = + NotificationsCryptoModule::prepareLegacyDecryptedState( + data, messageType); + statefulDecryptResult->flushState(); + return statefulDecryptResult->getDecryptedData(); + } + std::string decryptedData = session->decrypt(encryptedData); + NotificationsCryptoModule::persistNotificationsSessionInternal( + keyserverID, picklingKey, std::move(session)); + return decryptedData; +} + +std::unique_ptr<NotificationsCryptoModule::BaseStatefulDecryptResult> +NotificationsCryptoModule::statefulDecrypt( + const std::string &keyserverID, + const std::string &data, + const size_t messageType) { + std::unique_ptr<crypto::Session> session; + std::string picklingKey; + crypto::EncryptedData encryptedData{ + std::vector<uint8_t>(data.begin(), data.end()), messageType}; + try { + std::tie(session, picklingKey) = + NotificationsCryptoModule::fetchNotificationsSession(keyserverID); + } catch (const std::exception &e) { + return NotificationsCryptoModule::prepareLegacyDecryptedState( + data, messageType); + } + + std::string decryptedData = session->decrypt(encryptedData); StatefulDecryptResult statefulDecryptResult( std::move(session), keyserverID, picklingKey, decryptedData); + return std::make_unique<StatefulDecryptResult>( std::move(statefulDecryptResult)); } void NotificationsCryptoModule::flushState( - std::unique_ptr<StatefulDecryptResult> statefulDecryptResult) { - NotificationsCryptoModule::persistNotificationsSessionInternal( - statefulDecryptResult->getKeyserverID(), - statefulDecryptResult->getPicklingKey(), - std::move(statefulDecryptResult->sessionState)); + std::unique_ptr<BaseStatefulDecryptResult> baseStatefulDecryptResult) { + baseStatefulDecryptResult->flushState(); } } // namespace comm diff --git a/native/ios/NotificationService/NotificationService.mm b/native/ios/NotificationService/NotificationService.mm --- a/native/ios/NotificationService/NotificationService.mm +++ b/native/ios/NotificationService/NotificationService.mm @@ -85,7 +85,7 @@ UNNotificationContent *publicUserContent = content; // Step 1: notification decryption. - std::unique_ptr<comm::NotificationsCryptoModule::StatefulDecryptResult> + std::unique_ptr<comm::NotificationsCryptoModule::BaseStatefulDecryptResult> statefulDecryptResultPtr; BOOL decryptionExecuted = NO; @@ -178,7 +178,8 @@ " during unread count calculation."; } - if (unreadCountCalculationError.size()) { + if (unreadCountCalculationError.size() && + comm::StaffUtils::isStaffRelease()) { [errorMessages addObject:[NSString stringWithUTF8String:unreadCountCalculationError .c_str()]]; @@ -544,7 +545,7 @@ [payload[encryptionFailureKey] isEqualToNumber:@(1)]; } -- (std::unique_ptr<comm::NotificationsCryptoModule::StatefulDecryptResult>) +- (std::unique_ptr<comm::NotificationsCryptoModule::BaseStatefulDecryptResult>) decryptContentInPlace:(UNMutableNotificationContent *)content { std::string encryptedData = std::string([content.userInfo[encryptedPayloadKey] UTF8String]);