diff --git a/native/cpp/CommonCpp/Notifications/BackgroundDataStorage/NotificationsCryptoModule.cpp b/native/cpp/CommonCpp/Notifications/BackgroundDataStorage/NotificationsCryptoModule.cpp index 49538aade..87d19d549 100644 --- a/native/cpp/CommonCpp/Notifications/BackgroundDataStorage/NotificationsCryptoModule.cpp +++ b/native/cpp/CommonCpp/Notifications/BackgroundDataStorage/NotificationsCryptoModule.cpp @@ -1,251 +1,252 @@ #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 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; crypto::CryptoModule 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 crypto::CryptoModule{ notificationsCryptoAccountID, picklingKey, {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 crypto::CryptoModule{ notificationsCryptoAccountID, picklingKey, {account, sessions}}; } void NotificationsCryptoModule::serializeAndFlushCryptoModule( crypto::CryptoModule &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 temporaryPath = path + callingProcessName; - // This is for the case if any of the steps below failed/app was killed - // in a previous call to this method leaving temporary file unremoved. - // We supply `callingProcessName` as function argument in order to name - // temporary file in a deterministic way. Otherwise we would need to use - // directory search API to retrieve unremoved files paths. - remove(temporaryPath.c_str()); + 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()); } void NotificationsCryptoModule::callCryptoModule( std::function caller, const std::string &callingProcessName) { CommSecureStore secureStore{}; folly::Optional picklingKey = secureStore.get( NotificationsCryptoModule::secureStoreNotificationsAccountDataKey); if (!picklingKey.hasValue()) { throw std::runtime_error( "Attempt to retrieve notifications crypto account before it was " "correctly initialized."); } const std::string path = PlatformSpecificTools::getNotificationsCryptoAccountPath(); crypto::CryptoModule cryptoModule = NotificationsCryptoModule::deserializeCryptoModule( path, picklingKey.value()); caller(cryptoModule); NotificationsCryptoModule::serializeAndFlushCryptoModule( cryptoModule, path, picklingKey.value(), 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 CommSecureStore secureStore{}; std::string picklingKey = crypto::Tools::generateRandomString(64); secureStore.set( NotificationsCryptoModule::secureStoreNotificationsAccountDataKey, picklingKey); crypto::CryptoModule cryptoModule{ NotificationsCryptoModule::notificationsCryptoAccountID}; NotificationsCryptoModule::serializeAndFlushCryptoModule( cryptoModule, notificationsCryptoAccountPath, picklingKey, callingProcessName); } std::string NotificationsCryptoModule::getNotificationsIdentityKeys( const std::string &callingProcessName) { std::string identityKeys; auto caller = [&identityKeys](crypto::CryptoModule cryptoModule) { identityKeys = cryptoModule.getIdentityKeys(); }; NotificationsCryptoModule::callCryptoModule(caller, callingProcessName); return identityKeys; } crypto::EncryptedData NotificationsCryptoModule::initializeNotificationsSession( const std::string &identityKeys, const std::string &prekey, const std::string &prekeySignature, const std::string &oneTimeKeys, const std::string &callingProcessName) { crypto::EncryptedData initialEncryptedMessage; auto caller = [&](crypto::CryptoModule &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(oneTimeKeys.begin(), oneTimeKeys.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](crypto::CryptoModule &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 = [&](crypto::CryptoModule &cryptoModule) { decryptedData = cryptoModule.decrypt( NotificationsCryptoModule::keyserverHostedNotificationsID, {std::vector(data.begin(), data.end()), messageType}); }; NotificationsCryptoModule::callCryptoModule(caller, callingProcessName); return decryptedData; } } // namespace comm