diff --git a/endpoint/core/include/privmx/endpoint/core/encryptors/IDataSchemaStrategy.hpp b/endpoint/core/include/privmx/endpoint/core/encryptors/IDataSchemaStrategy.hpp new file mode 100644 index 00000000..b0ec9e41 --- /dev/null +++ b/endpoint/core/include/privmx/endpoint/core/encryptors/IDataSchemaStrategy.hpp @@ -0,0 +1,32 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef _PRIVMXLIB_ENDPOINT_CORE_IDATASCHEMASTRATEGY_HPP_ +#define _PRIVMXLIB_ENDPOINT_CORE_IDATASCHEMASTRATEGY_HPP_ + +#include + +namespace privmx { +namespace endpoint { +namespace core { + +template +class IDataSchemaStrategy { +public: + virtual ~IDataSchemaStrategy() = default; + virtual TDomainObject decryptAndConvert(const TServerModel& model, const DecryptedEncKey& encKey) const = 0; +}; + +} // namespace core +} // namespace endpoint +} // namespace privmx + +#endif // _PRIVMXLIB_ENDPOINT_CORE_IDATASCHEMASTRATEGY_HPP_ diff --git a/endpoint/core/include/privmx/endpoint/core/encryptors/TypedDataSchemaStrategy.hpp b/endpoint/core/include/privmx/endpoint/core/encryptors/TypedDataSchemaStrategy.hpp new file mode 100644 index 00000000..8d68ec1c --- /dev/null +++ b/endpoint/core/include/privmx/endpoint/core/encryptors/TypedDataSchemaStrategy.hpp @@ -0,0 +1,46 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef _PRIVMXLIB_ENDPOINT_CORE_TYPEDDATASCHEMASTRATEGY_HPP_ +#define _PRIVMXLIB_ENDPOINT_CORE_TYPEDDATASCHEMASTRATEGY_HPP_ + +#include +#include + +#include "privmx/endpoint/core/encryptors/IDataSchemaStrategy.hpp" + +namespace privmx { +namespace endpoint { +namespace core { + +template +class TypedDataSchemaStrategy : public IDataSchemaStrategy { +public: + TDomainObject decryptAndConvert(const TServerModel& model, const DecryptedEncKey& encKey) const override final { + try { + return convert(model, decrypt(model, encKey)); + } catch (const core::Exception& e) { + return makeErrorResult(model, e.getCode()); + } catch (const privmx::utils::PrivmxException& e) { + return makeErrorResult(model, ExceptionConverter::convert(e).getCode()); + } catch (...) { return makeErrorResult(model, ENDPOINT_CORE_EXCEPTION_CODE); } + } + + virtual TRawData decrypt(const TServerModel& model, const DecryptedEncKey& encKey) const = 0; + virtual TDomainObject convert(const TServerModel& model, const TRawData& raw) const = 0; + virtual TDomainObject makeErrorResult(const TServerModel& model, int64_t errorCode) const = 0; +}; + +} // namespace core +} // namespace endpoint +} // namespace privmx + +#endif // _PRIVMXLIB_ENDPOINT_CORE_TYPEDDATASCHEMASTRATEGY_HPP_ diff --git a/endpoint/core/include/privmx/endpoint/core/encryptors/VersionStrategyMapper.hpp b/endpoint/core/include/privmx/endpoint/core/encryptors/VersionStrategyMapper.hpp new file mode 100644 index 00000000..7ecb13f1 --- /dev/null +++ b/endpoint/core/include/privmx/endpoint/core/encryptors/VersionStrategyMapper.hpp @@ -0,0 +1,48 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef _PRIVMXLIB_ENDPOINT_CORE_VERSIONSTRATEGYMAPPER_HPP_ +#define _PRIVMXLIB_ENDPOINT_CORE_VERSIONSTRATEGYMAPPER_HPP_ + +#include +#include +#include + +#include "privmx/endpoint/core/encryptors/IDataSchemaStrategy.hpp" + +namespace privmx { +namespace endpoint { +namespace core { + +template +class VersionStrategyMapper { +public: + using Strategy = IDataSchemaStrategy; + using StrategyPtr = std::shared_ptr; + + void registerStrategy(int64_t version, StrategyPtr strategy) { _strategies[version] = std::move(strategy); } + + StrategyPtr getStrategy(int64_t version) const { + auto it = _strategies.find(version); + if (it == _strategies.end()) + return nullptr; + return it->second; + } + +private: + std::unordered_map _strategies; +}; + +} // namespace core +} // namespace endpoint +} // namespace privmx + +#endif // _PRIVMXLIB_ENDPOINT_CORE_VERSIONSTRATEGYMAPPER_HPP_ diff --git a/endpoint/event/include/privmx/endpoint/event/EventApiImpl.hpp b/endpoint/event/include/privmx/endpoint/event/EventApiImpl.hpp index bac7260a..b3442ba7 100644 --- a/endpoint/event/include/privmx/endpoint/event/EventApiImpl.hpp +++ b/endpoint/event/include/privmx/endpoint/event/EventApiImpl.hpp @@ -5,8 +5,7 @@ #include "privmx/endpoint/event/Events.hpp" #include "privmx/endpoint/event/ServerApi.hpp" #include "privmx/endpoint/event/SubscriberImpl.hpp" -#include "privmx/endpoint/event/encryptors/event/EventDataEncryptorV5.hpp" -#include "privmx/endpoint/event/encryptors/event/OldEventDataDecryptor.hpp" +#include "privmx/endpoint/event/encryptors/event/EventDataSchemaMapper.hpp" #include #include #include @@ -75,7 +74,6 @@ class EventApiImpl : public privmx::utils::ManualManagedClass { const std::string& encryptionKey ); void validateChannelName(const std::string& channelName); - bool verifyDecryptedEventDataV5(const DecryptedEventDataV5& data); core::Connection _connection; privmx::crypto::PrivateKey _userPrivKey; privfs::RpcGateway::Ptr _gateway; @@ -86,9 +84,8 @@ class EventApiImpl : public privmx::utils::ManualManagedClass { EventKeyProvider _eventKeyProvider; SubscriberImpl _subscriber; int _notificationListenerId, _connectedListenerId, _disconnectedListenerId; - EventDataEncryptorV5 _eventDataEncryptorV5; - OldEventDataDecryptor _oldEventDataDecryptor; std::shared_ptr _guardedExecutor; + EventDataSchemaMapper _eventDataSchemaMapper; }; } // namespace event diff --git a/endpoint/event/include/privmx/endpoint/event/encryptors/event/EventDataSchemaMapper.hpp b/endpoint/event/include/privmx/endpoint/event/encryptors/event/EventDataSchemaMapper.hpp new file mode 100644 index 00000000..ba3bdaad --- /dev/null +++ b/endpoint/event/include/privmx/endpoint/event/encryptors/event/EventDataSchemaMapper.hpp @@ -0,0 +1,71 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef _PRIVMXLIB_ENDPOINT_EVENT_EVENTDATASCHEMAMAPPER_HPP_ +#define _PRIVMXLIB_ENDPOINT_EVENT_EVENTDATASCHEMAMAPPER_HPP_ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "privmx/endpoint/event/Constants.hpp" +#include "privmx/endpoint/event/EventKeyProvider.hpp" +#include "privmx/endpoint/event/EventTypes.hpp" +#include "privmx/endpoint/event/Events.hpp" +#include "privmx/endpoint/event/ServerTypes.hpp" +#include "privmx/endpoint/event/encryptors/event/EventDataEncryptorV5.hpp" +#include "privmx/endpoint/event/encryptors/event/EventDataSchemaStrategyV5.hpp" + +namespace privmx { +namespace endpoint { +namespace event { + +class EventDataSchemaMapper { +public: + EventDataSchemaMapper(const privmx::crypto::PrivateKey& userPrivKey, const core::Connection& connection); + + Poco::Dynamic::Var encrypt( + const std::string& contextId, + const core::Buffer& data, + const std::optional& type, + const std::string& key + ); + + ContextCustomEventData decrypt(const server::ContextCustomEventData& rawEvent); + DecryptedInternalContextEventDataV1 decryptInternal(const server::ContextCustomEventData& rawEvent); + + EventDataSchema::Version getDataStructureVersion(const server::ContextCustomEventData& rawEvent); + +private: + static ContextCustomEventData makeErrorResult(const server::ContextCustomEventData& rawEvent, int64_t errorCode); + bool verifyDecryptedEventData(const DecryptedEventDataV5& data); + + privmx::crypto::PrivateKey _userPrivKey; + core::Connection _connection; + EventKeyProvider _eventKeyProvider; + core::VersionStrategyMapper _strategyMapper; + std::shared_ptr _strategyV5; + EventDataEncryptorV5 _encryptorV5; +}; + +} // namespace event +} // namespace endpoint +} // namespace privmx + +#endif // _PRIVMXLIB_ENDPOINT_EVENT_EVENTDATASCHEMAMAPPER_HPP_ diff --git a/endpoint/event/include/privmx/endpoint/event/encryptors/event/EventDataSchemaStrategyV5.hpp b/endpoint/event/include/privmx/endpoint/event/encryptors/event/EventDataSchemaStrategyV5.hpp new file mode 100644 index 00000000..94be3e31 --- /dev/null +++ b/endpoint/event/include/privmx/endpoint/event/encryptors/event/EventDataSchemaStrategyV5.hpp @@ -0,0 +1,63 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef _PRIVMXLIB_ENDPOINT_EVENT_EVENTDATASCHEMASTRATEGYV5_HPP_ +#define _PRIVMXLIB_ENDPOINT_EVENT_EVENTDATASCHEMASTRATEGYV5_HPP_ + +#include +#include + +#include "privmx/endpoint/event/EventTypes.hpp" +#include "privmx/endpoint/event/Events.hpp" +#include "privmx/endpoint/event/ServerTypes.hpp" +#include "privmx/endpoint/event/encryptors/event/EventDataEncryptorV5.hpp" + +namespace privmx { +namespace endpoint { +namespace event { + +// clang-format off +class EventDataSchemaStrategyV5 : public core::TypedDataSchemaStrategy< + server::ContextCustomEventData, + DecryptedEventDataV5, + ContextCustomEventData +> { + // clang-format on +public: + DecryptedEventDataV5 decrypt( + const server::ContextCustomEventData& model, + const core::DecryptedEncKey& encKey + ) const override; + ContextCustomEventData convert( + const server::ContextCustomEventData& model, + const DecryptedEventDataV5& raw + ) const override; + ContextCustomEventData makeErrorResult( + const server::ContextCustomEventData& model, + int64_t errorCode + ) const override; + + static ContextCustomEventData toContextCustomEventData( + const server::ContextCustomEventData& raw, + const core::Buffer& payload, + int64_t statusCode, + int64_t schemaVersion + ); + +private: + mutable EventDataEncryptorV5 _encryptor; +}; + +} // namespace event +} // namespace endpoint +} // namespace privmx + +#endif // _PRIVMXLIB_ENDPOINT_EVENT_EVENTDATASCHEMASTRATEGYV5_HPP_ diff --git a/endpoint/event/include/privmx/endpoint/event/encryptors/event/OldEventDataDecryptor.hpp b/endpoint/event/include/privmx/endpoint/event/encryptors/event/OldEventDataDecryptor.hpp deleted file mode 100644 index 17852f97..00000000 --- a/endpoint/event/include/privmx/endpoint/event/encryptors/event/OldEventDataDecryptor.hpp +++ /dev/null @@ -1,41 +0,0 @@ -/* -PrivMX Endpoint. -Copyright © 2024 Simplito sp. z o.o. - -This file is part of the PrivMX Platform (https://privmx.dev). -This software is Licensed under the PrivMX Free License. - -See the License for the specific language governing permissions and -limitations under the License. -*/ -#ifndef _PRIVMXLIB_ENDPOINT_EVENT_OLDEVENTDATADECRYPTOR_HPP_ -#define _PRIVMXLIB_ENDPOINT_EVENT_OLDEVENTDATADECRYPTOR_HPP_ - -#include "privmx/endpoint/event/EventTypes.hpp" -#include -#include -#include -#include - -namespace privmx { -namespace endpoint { -namespace event { - -class OldEventDataDecryptor { -public: - DecryptedContextEventDataV1 decryptV1( - const Poco::Dynamic::Var& data, - const crypto::PublicKey& authorPublicKey, - const std::string& encryptionKey, - const crypto::PrivateKey& userPrivateKey - ); - -private: - core::DataEncryptorV4 _dataEncryptor; -}; - -} // namespace event -} // namespace endpoint -} // namespace privmx - -#endif //_PRIVMXLIB_ENDPOINT_EVENT_OLDEVENTDATADECRYPTOR_HPP_ diff --git a/endpoint/event/include_pub/privmx/endpoint/event/EventException.hpp b/endpoint/event/include_pub/privmx/endpoint/event/EventException.hpp index fd16c8a9..071491b9 100644 --- a/endpoint/event/include_pub/privmx/endpoint/event/EventException.hpp +++ b/endpoint/event/include_pub/privmx/endpoint/event/EventException.hpp @@ -42,6 +42,8 @@ DECLARE_ENDPOINT_EXCEPTION(EndpointEventException, NotSubscribedException, "Not DECLARE_ENDPOINT_EXCEPTION(EndpointEventException, AlreadySubscribedException, "Already subscribed", 0x0005) DECLARE_ENDPOINT_EXCEPTION(EndpointEventException, InvalidEncryptedEventDataVersionException, "Invalid version of encrypted event data", 0x0005) DECLARE_ENDPOINT_EXCEPTION(EndpointEventException, InvalidSubscriptionQueryException, "Invalid subscriptionQuery", 0x0006) +DECLARE_ENDPOINT_EXCEPTION(EndpointEventException, UnknownEventFormatException, "Unknown event format", 0x0007) + // clang-format on } // namespace event } // namespace endpoint diff --git a/endpoint/event/src/EventApiImpl.cpp b/endpoint/event/src/EventApiImpl.cpp index 08dec75a..a1f212c0 100644 --- a/endpoint/event/src/EventApiImpl.cpp +++ b/endpoint/event/src/EventApiImpl.cpp @@ -35,7 +35,8 @@ EventApiImpl::EventApiImpl( : _connection(connection), _userPrivKey(userPrivKey), _gateway(gateway), _serverApi(ServerApi(gateway)), _eventMiddleware(eventMiddleware), _forbiddenChannelsNames({INTERNAL_EVENT_CHANNEL_NAME}), _eventKeyProvider(EventKeyProvider(userPrivKey)), _subscriber(SubscriberImpl(gateway)), - _guardedExecutor(std::make_shared()) { + _guardedExecutor(std::make_shared()), + _eventDataSchemaMapper(userPrivKey, connection) { _notificationListenerId = _eventMiddleware->addNotificationEventListener( std::bind(&EventApiImpl::processNotificationEvent, this, std::placeholders::_1, std::placeholders::_2) ); @@ -66,11 +67,8 @@ void EventApiImpl::emitEvent( ) { validateChannelName(channelName); auto key = _eventKeyProvider.generateKey(); - auto toEncrypt = ContextEventDataToEncryptV5{ContextEventDataV5{ - .data = eventData, .type = std::nullopt, .dio = _connection.getImpl()->createDIO(contextId, "") - }}; emitEventEx( - contextId, users, channelName, _eventDataEncryptorV5.encrypt(toEncrypt, _userPrivKey, key).toJSON(), key + contextId, users, channelName, _eventDataSchemaMapper.encrypt(contextId, eventData, std::nullopt, key), key ); } @@ -80,12 +78,9 @@ void EventApiImpl::emitEventInternal( const std::vector& users ) { auto key = _eventKeyProvider.generateKey(); - auto toEncrypt = ContextEventDataToEncryptV5{ContextEventDataV5{ - .data = event.data, .type = event.type, .dio = _connection.getImpl()->createDIO(contextId, "") - }}; emitEventEx( contextId, users, INTERNAL_EVENT_CHANNEL_NAME, - _eventDataEncryptorV5.encrypt(toEncrypt, _userPrivKey, key).toJSON(), key + _eventDataSchemaMapper.encrypt(contextId, event.data, event.type, key), key ); } @@ -123,27 +118,7 @@ bool EventApiImpl::isInternalContextEvent( DecryptedInternalContextEventDataV1 EventApiImpl::extractInternalEventData(const Poco::JSON::Object::Ptr& eventData) { auto rawEvent = server::ContextCustomEventData::fromJSON(eventData); - DecryptedInternalContextEventDataV1 result; - result.dataStructureVersion = EventDataSchema::Version::VERSION_5; - auto decryptedKey = _eventKeyProvider.decryptKey( - rawEvent.key, crypto::PublicKey::fromBase58DER(rawEvent.author.pub) - ); - result.statusCode = decryptedKey.statusCode; - if (result.statusCode == 0) { - auto tmp = _eventDataEncryptorV5.decrypt( - server::EncryptedContextEventDataV5::fromJSON(rawEvent.eventData), - crypto::PublicKey::fromBase58DER(rawEvent.author.pub), decryptedKey.key - ); - result.statusCode = tmp.statusCode; - result.data = tmp.data; - result.type = tmp.type.has_value() ? tmp.type.value() : ""; - if (result.statusCode == 0) { - result.statusCode = verifyDecryptedEventDataV5(tmp) ? - 0 : - core::ExceptionConverter::getCodeOfUserVerificationFailureException(); - } - } - return result; + return _eventDataSchemaMapper.decryptInternal(rawEvent); } void EventApiImpl::processNotificationEvent(const std::string& type, const core::NotificationEvent& notification) { @@ -160,41 +135,7 @@ void EventApiImpl::processNotificationEvent(const std::string& type, const core: // fix if not internal check if (channel == "context/custom/" INTERNAL_EVENT_CHANNEL_NAME "|contextId=" + rawEvent.id) return; - auto resultEventData = ContextCustomEventData{ - .contextId = rawEvent.id, - .userId = rawEvent.author.id, - .payload = core::Buffer(), - .statusCode = 0, - .schemaVersion = EventDataSchema::Version::UNKNOWN - }; - if (rawEvent.eventData.type() == typeid(Poco::JSON::Object::Ptr)) { - auto decryptedKey = _eventKeyProvider.decryptKey( - rawEvent.key, crypto::PublicKey::fromBase58DER(rawEvent.author.pub) - ); - resultEventData.statusCode = decryptedKey.statusCode; - resultEventData.schemaVersion = EventDataSchema::Version::VERSION_5; - if (decryptedKey.statusCode == 0) { - auto tmp = _eventDataEncryptorV5.decrypt( - server::EncryptedContextEventDataV5::fromJSON(rawEvent.eventData), - crypto::PublicKey::fromBase58DER(rawEvent.author.pub), decryptedKey.key - ); - resultEventData.payload = tmp.data; - resultEventData.statusCode = tmp.statusCode; - if (resultEventData.statusCode == 0) { - resultEventData.statusCode = verifyDecryptedEventDataV5(tmp) ? - 0 : - core::ExceptionConverter::getCodeOfUserVerificationFailureException(); - } - } - } else if (rawEvent.eventData.isString()) { - resultEventData.schemaVersion = EventDataSchema::Version::VERSION_1; - auto tmp = _oldEventDataDecryptor.decryptV1( - rawEvent.eventData.convert(), crypto::PublicKey::fromBase58DER(rawEvent.author.pub), - rawEvent.key, _userPrivKey - ); - resultEventData.payload = tmp.data; - resultEventData.statusCode = tmp.statusCode; - } + auto resultEventData = _eventDataSchemaMapper.decrypt(rawEvent); auto customChannelName = privmx::utils::Utils::split(privmx::utils::Utils::split(channel, "/")[2], "|")[0]; auto event = core::EventBuilder::buildEvent( "context/" + rawEvent.id + "/" + customChannelName, resultEventData, notification @@ -233,22 +174,6 @@ void EventApiImpl::validateChannelName(const std::string& channelName) { } } -bool EventApiImpl::verifyDecryptedEventDataV5(const DecryptedEventDataV5& data) { - std::vector verifierInput{}; - verifierInput.push_back( - core::VerificationRequest{ - .contextId = data.dio.contextId, - .senderId = data.dio.creatorUserId, - .senderPubKey = data.dio.creatorPubKey, - .date = data.dio.timestamp, - .bridgeIdentity = data.dio.bridgeIdentity - } - ); - std::vector verified; - verified = _connection.getImpl()->getUserVerifier()->verify(verifierInput); - return verified[0]; -} - std::vector EventApiImpl::subscribeFor(const std::vector& subscriptionQueries) { auto result = _subscriber.subscribeFor(subscriptionQueries); _eventMiddleware->notificationEventListenerAddSubscriptionIds(_notificationListenerId, result); diff --git a/endpoint/event/src/encryptors/event/EventDataSchemaMapper.cpp b/endpoint/event/src/encryptors/event/EventDataSchemaMapper.cpp new file mode 100644 index 00000000..8d71110e --- /dev/null +++ b/endpoint/event/src/encryptors/event/EventDataSchemaMapper.cpp @@ -0,0 +1,138 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "privmx/endpoint/event/encryptors/event/EventDataSchemaMapper.hpp" + +#include +#include +#include +#include +#include + +#include "privmx/endpoint/event/EventException.hpp" + +using namespace privmx::endpoint; +using namespace privmx::endpoint::event; + +EventDataSchemaMapper::EventDataSchemaMapper( + const privmx::crypto::PrivateKey& userPrivKey, + const core::Connection& connection +) + : _userPrivKey(userPrivKey), _connection(connection), _eventKeyProvider(userPrivKey) { + _strategyV5 = std::make_shared(); + _strategyMapper.registerStrategy(EventDataSchema::Version::VERSION_5, _strategyV5); +} + +Poco::Dynamic::Var EventDataSchemaMapper::encrypt( + const std::string& contextId, + const core::Buffer& data, + const std::optional& type, + const std::string& key +) { + auto toEncrypt = ContextEventDataToEncryptV5{ + ContextEventDataV5{.data = data, .type = type, .dio = _connection.getImpl()->createDIO(contextId, "")} + }; + return _encryptorV5.encrypt(toEncrypt, _userPrivKey, key).toJSON(); +} + +ContextCustomEventData EventDataSchemaMapper::decrypt(const server::ContextCustomEventData& rawEvent) { + if (getDataStructureVersion(rawEvent) != EventDataSchema::Version::VERSION_5) { + return makeErrorResult(rawEvent, InvalidEncryptedEventDataVersionException().getCode()); + } + auto authorPubKey = privmx::crypto::PublicKey::fromBase58DER(rawEvent.author.pub); + auto decryptedKey = _eventKeyProvider.decryptKey(rawEvent.key, authorPubKey); + if (decryptedKey.statusCode != 0) { + return makeErrorResult(rawEvent, decryptedKey.statusCode); + } + core::DecryptedEncKey encKey; + encKey.key = decryptedKey.key; + encKey.statusCode = 0; + try { + auto raw = _strategyV5->decrypt(rawEvent, encKey); + if (raw.statusCode == 0 && !verifyDecryptedEventData(raw)) { + raw.statusCode = core::ExceptionConverter::getCodeOfUserVerificationFailureException(); + } + return _strategyV5->convert(rawEvent, raw); + } catch (const core::Exception& e) { + return makeErrorResult(rawEvent, e.getCode()); + } catch (const privmx::utils::PrivmxException& e) { + return makeErrorResult(rawEvent, core::ExceptionConverter::convert(e).getCode()); + } catch (...) { return makeErrorResult(rawEvent, ENDPOINT_CORE_EXCEPTION_CODE); } +} + +DecryptedInternalContextEventDataV1 EventDataSchemaMapper::decryptInternal( + const server::ContextCustomEventData& rawEvent +) { + DecryptedInternalContextEventDataV1 result; + result.dataStructureVersion = EventDataSchema::Version::VERSION_5; + if (getDataStructureVersion(rawEvent) != EventDataSchema::Version::VERSION_5) { + result.statusCode = InvalidEncryptedEventDataVersionException().getCode(); + return result; + } + auto authorPubKey = privmx::crypto::PublicKey::fromBase58DER(rawEvent.author.pub); + auto decryptedKey = _eventKeyProvider.decryptKey(rawEvent.key, authorPubKey); + result.statusCode = decryptedKey.statusCode; + if (result.statusCode != 0) + return result; + core::DecryptedEncKey encKey; + encKey.key = decryptedKey.key; + encKey.statusCode = 0; + try { + auto raw = _strategyV5->decrypt(rawEvent, encKey); + result.statusCode = raw.statusCode; + result.data = raw.data; + result.type = raw.type.has_value() ? raw.type.value() : ""; + if (result.statusCode == 0 && !verifyDecryptedEventData(raw)) { + result.statusCode = core::ExceptionConverter::getCodeOfUserVerificationFailureException(); + } + } catch (const core::Exception& e) { + result.statusCode = e.getCode(); + } catch (const privmx::utils::PrivmxException& e) { + result.statusCode = core::ExceptionConverter::convert(e).getCode(); + } catch (...) { result.statusCode = ENDPOINT_CORE_EXCEPTION_CODE; } + return result; +} + +bool EventDataSchemaMapper::verifyDecryptedEventData(const DecryptedEventDataV5& data) { + std::vector verifierInput{}; + verifierInput.push_back( + core::VerificationRequest{ + .contextId = data.dio.contextId, + .senderId = data.dio.creatorUserId, + .senderPubKey = data.dio.creatorPubKey, + .date = data.dio.timestamp, + .bridgeIdentity = data.dio.bridgeIdentity + } + ); + auto verified = _connection.getImpl()->getUserVerifier()->verify(verifierInput); + return verified[0]; +} + +EventDataSchema::Version EventDataSchemaMapper::getDataStructureVersion( + const server::ContextCustomEventData& rawEvent +) { + if (rawEvent.eventData.type() == typeid(Poco::JSON::Object::Ptr)) + return EventDataSchema::Version::VERSION_5; + return EventDataSchema::Version::UNKNOWN; +} + +ContextCustomEventData EventDataSchemaMapper::makeErrorResult( + const server::ContextCustomEventData& rawEvent, + int64_t errorCode +) { + return ContextCustomEventData{ + .contextId = rawEvent.id, + .userId = rawEvent.author.id, + .payload = {}, + .statusCode = errorCode, + .schemaVersion = EventDataSchema::Version::UNKNOWN + }; +} diff --git a/endpoint/event/src/encryptors/event/EventDataSchemaStrategyV5.cpp b/endpoint/event/src/encryptors/event/EventDataSchemaStrategyV5.cpp new file mode 100644 index 00000000..2bcc91fe --- /dev/null +++ b/endpoint/event/src/encryptors/event/EventDataSchemaStrategyV5.cpp @@ -0,0 +1,58 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "privmx/endpoint/event/encryptors/event/EventDataSchemaStrategyV5.hpp" + +#include +#include + +#include "privmx/endpoint/event/Constants.hpp" + +using namespace privmx::endpoint; +using namespace privmx::endpoint::event; + +DecryptedEventDataV5 EventDataSchemaStrategyV5::decrypt( + const server::ContextCustomEventData& model, + const core::DecryptedEncKey& encKey +) const { + auto authorPubKey = privmx::crypto::PublicKey::fromBase58DER(model.author.pub); + auto encryptedData = server::EncryptedContextEventDataV5::fromJSON(model.eventData); + return _encryptor.decrypt(encryptedData, authorPubKey, encKey.key); +} + +ContextCustomEventData EventDataSchemaStrategyV5::convert( + const server::ContextCustomEventData& model, + const DecryptedEventDataV5& raw +) const { + return toContextCustomEventData(model, raw.data, raw.statusCode, EventDataSchema::Version::VERSION_5); +} + +ContextCustomEventData EventDataSchemaStrategyV5::makeErrorResult( + const server::ContextCustomEventData& model, + int64_t errorCode +) const { + return toContextCustomEventData(model, {}, errorCode, EventDataSchema::Version::VERSION_5); +} + +ContextCustomEventData EventDataSchemaStrategyV5::toContextCustomEventData( + const server::ContextCustomEventData& raw, + const core::Buffer& payload, + int64_t statusCode, + int64_t schemaVersion +) { + return ContextCustomEventData{ + .contextId = raw.id, + .userId = raw.author.id, + .payload = payload, + .statusCode = statusCode, + .schemaVersion = schemaVersion + }; +} diff --git a/endpoint/event/src/encryptors/event/OldEventDataDecryptor.cpp b/endpoint/event/src/encryptors/event/OldEventDataDecryptor.cpp deleted file mode 100644 index 64662a70..00000000 --- a/endpoint/event/src/encryptors/event/OldEventDataDecryptor.cpp +++ /dev/null @@ -1,45 +0,0 @@ -/* -PrivMX Endpoint. -Copyright © 2024 Simplito sp. z o.o. - -This file is part of the PrivMX Platform (https://privmx.dev). -This software is Licensed under the PrivMX Free License. - -See the License for the specific language governing permissions and -limitations under the License. -*/ - -#include "privmx/endpoint/event/encryptors/event/OldEventDataDecryptor.hpp" -#include "privmx/endpoint/event/Constants.hpp" -#include "privmx/endpoint/event/EventException.hpp" -#include -#include - -using namespace privmx::endpoint; -using namespace privmx::endpoint::event; - -DecryptedContextEventDataV1 OldEventDataDecryptor::decryptV1( - const Poco::Dynamic::Var& data, - const crypto::PublicKey& authorPublicKey, - const std::string& encryptionKey, - const crypto::PrivateKey& userPrivateKey -) { - DecryptedContextEventDataV1 result; - result.statusCode = 0; - result.dataStructureVersion = EventDataSchema::Version::VERSION_1; - try { - if (data.isString()) { - auto encKey = privmx::crypto::EciesEncryptor::decryptFromBase64(userPrivateKey, encryptionKey); - result.data = _dataEncryptor.decodeAndDecryptAndVerify( - data.convert(), authorPublicKey, encKey - ); - } else { - result.statusCode = InvalidEncryptedEventDataVersionException().getCode(); - } - } catch (const privmx::endpoint::core::Exception& e) { - result.statusCode = e.getCode(); - } catch (const privmx::utils::PrivmxException& e) { - result.statusCode = core::ExceptionConverter::convert(e).getCode(); - } catch (...) { result.statusCode = ENDPOINT_CORE_EXCEPTION_CODE; } - return result; -} diff --git a/endpoint/inbox/include/privmx/endpoint/inbox/InboxApiImpl.hpp b/endpoint/inbox/include/privmx/endpoint/inbox/InboxApiImpl.hpp index ca2504b7..b087ebde 100644 --- a/endpoint/inbox/include/privmx/endpoint/inbox/InboxApiImpl.hpp +++ b/endpoint/inbox/include/privmx/endpoint/inbox/InboxApiImpl.hpp @@ -26,14 +26,13 @@ limitations under the License. #include "privmx/endpoint/inbox/Events.hpp" #include "privmx/endpoint/inbox/FileKeyIdFormatValidator.hpp" #include "privmx/endpoint/inbox/InboxApi.hpp" -#include "privmx/endpoint/inbox/InboxEntriesDataEncryptorSerializer.hpp" #include "privmx/endpoint/inbox/InboxHandleManager.hpp" #include "privmx/endpoint/inbox/MessageKeyIdFormatValidator.hpp" #include "privmx/endpoint/inbox/ServerApi.hpp" #include "privmx/endpoint/inbox/ServerTypes.hpp" #include "privmx/endpoint/inbox/SubscriberImpl.hpp" -#include "privmx/endpoint/inbox/encryptors/inbox/InboxDataProcessorV4.hpp" -#include "privmx/endpoint/inbox/encryptors/inbox/InboxDataProcessorV5.hpp" +#include "privmx/endpoint/inbox/encryptors/entry/InboxEntryDataSchemaMapper.hpp" +#include "privmx/endpoint/inbox/encryptors/inbox/InboxDataSchemaMapper.hpp" #include #include #include @@ -139,43 +138,10 @@ class InboxApiImpl : public privmx::utils::ManualManagedClass, pro inbox::Inbox _getInboxEx(const std::string& inboxId, const std::string& type); inbox::FilesConfig getFilesConfigOptOrDefault(const std::optional& fileConfig); InboxPublicViewData getInboxPublicViewData(const std::string& inboxId); - InboxDataResultV4 decryptInboxV4(inbox::server::InboxDataEntry inboxEntry, const core::DecryptedEncKey& encKey); - InboxDataResultV5 decryptInboxV5(inbox::server::InboxDataEntry inboxEntry, const core::DecryptedEncKey& encKey); - inbox::Inbox convertServerInboxToLibInbox( - inbox::server::InboxInfo inbox, - const core::Buffer& publicMeta = core::Buffer(), - const core::Buffer& privateMeta = core::Buffer(), - const std::optional& filesConfig = std::nullopt, - const int64_t& statusCode = 0, - const int64_t& schemaVersion = InboxDataSchema::Version::UNKNOWN - ); - inbox::Inbox convertInboxV4(inbox::server::InboxInfo inboxRaw, const InboxDataResultV4& inboxData); - inbox::Inbox convertInboxV5(inbox::server::InboxInfo inboxRaw, const InboxDataResultV5& inboxData); - InboxDataSchema::Version getInboxDataEntryStructureVersion(inbox::server::InboxDataEntry inboxEntry); - std::tuple decryptAndConvertInboxDataToInbox( - inbox::server::InboxInfo inbox, - inbox::server::InboxDataEntry inboxEntry, - const core::DecryptedEncKey& encKey - ); - std::vector validateDecryptAndConvertInboxesDataToInboxes(std::vector inboxes); - inbox::Inbox validateDecryptAndConvertInboxDataToInbox(inbox::server::InboxInfo inbox); - InboxInternalMetaV5 decryptInboxInternalMeta( - inbox::server::InboxDataEntry inboxEntry, - const core::DecryptedEncKey& encKey - ); inbox::server::InboxDataEntry getInboxCurrentDataEntry(inbox::server::InboxInfo inbox); - inbox::server::InboxMessageServer unpackInboxOrigMessage(const std::string& serialized); - void assertInboxDataIntegrity(inbox::server::InboxInfo inbox); - uint32_t validateInboxDataIntegrity(inbox::server::InboxInfo inbox); virtual std::pair getModuleKeysAndVersionFromServer(std::string moduleId) override; core::ModuleKeys inboxToModuleKeys(inbox::server::InboxInfo inbox); - InboxEntryResult decryptInboxEntry(thread::server::Message message, const core::ModuleKeys& inboxKeys); - inbox::InboxEntry convertInboxEntry(thread::server::Message message, const inbox::InboxEntryResult& inboxEntry); - inbox::InboxEntry decryptAndConvertInboxEntryDataToInboxEntry( - thread::server::Message message, - const core::ModuleKeys& inboxKeys - ); store::FileMetaToEncryptV4 prepareMeta(const inbox::CommitFileInfo& commitFileInfo); core::ModuleKeys getEntryDecryptionKeys(thread::server::Message message); @@ -190,8 +156,6 @@ class InboxApiImpl : public privmx::utils::ManualManagedClass, pro std::string readMessageIdFromFileKeyId(const std::string& keyId); void deleteMessageAndFiles(thread::server::Message message); thread::server::Message getServerMessage(const std::string& messageId); - InboxEntryResult getEmptyResultWithStatusCode(const int64_t statusCode); - std::vector getFilesIdsFromServerMessage(inbox::server::InboxMessageServer serverMessage); void assertInboxExist(const std::string& inboxId); static const Poco::Int64 _CHUNK_SIZE; @@ -216,8 +180,8 @@ class InboxApiImpl : public privmx::utils::ManualManagedClass, pro store::FileMetaEncryptorV5 _fileMetaEncryptorV5; SubscriberImpl _subscriber; - InboxDataProcessorV4 _inboxDataProcessorV4; - InboxDataProcessorV5 _inboxDataProcessorV5; + InboxDataSchemaMapper _inboxDataSchemaMapper; + InboxEntryDataSchemaMapper _inboxEntryDataSchemaMapper; core::DataEncryptorV4 _eventDataEncryptorV4; inline static const std::string INBOX_TYPE_FILTER_FLAG = "inbox"; }; diff --git a/endpoint/inbox/include/privmx/endpoint/inbox/InboxEntriesDataEncryptorSerializer.hpp b/endpoint/inbox/include/privmx/endpoint/inbox/InboxEntriesDataEncryptorSerializer.hpp deleted file mode 100644 index 55f97859..00000000 --- a/endpoint/inbox/include/privmx/endpoint/inbox/InboxEntriesDataEncryptorSerializer.hpp +++ /dev/null @@ -1,76 +0,0 @@ -/* -PrivMX Endpoint. -Copyright © 2024 Simplito sp. z o.o. - -This file is part of the PrivMX Platform (https://privmx.dev). -This software is Licensed under the PrivMX Free License. - -See the License for the specific language governing permissions and -limitations under the License. -*/ - -#ifndef _PRIVMXLIB_ENDPOINT_INBOX_INBOXENTRIESDATAENCRYPTORSERIALIZER_HPP_ -#define _PRIVMXLIB_ENDPOINT_INBOX_INBOXENTRIESDATAENCRYPTORSERIALIZER_HPP_ - -#include -#include - -#include -#include -#include -#include -#include - -namespace privmx { -namespace endpoint { -namespace inbox { -struct InboxEntryPublicData { - std::string userPubKey; - bool keyPreset; - std::string usedInboxKeyId; -}; - -struct InboxEntryPrivateData { - std::string filesMetaKey; - std::string text; -}; - -struct InboxEntrySendModel { - InboxEntryPublicData publicData; - InboxEntryPrivateData privateData; -}; - -struct InboxEntryPublicDataResult : public InboxEntryPublicData { - int64_t statusCode; -}; - -struct InboxEntryDataResult { - InboxEntryPublicData publicData; - InboxEntryPrivateData privateData; - int64_t statusCode; -}; - -struct InboxEntryResult : public InboxEntryDataResult { - std::string storeId; - std::vector filesIds; -}; - -class InboxEntriesDataEncryptorSerializer { -public: - using Ptr = Poco::SharedPtr; - - InboxEntriesDataEncryptorSerializer(); - std::string packMessage( - InboxEntrySendModel data, - privmx::crypto::PrivateKey& userPriv, - privmx::crypto::PublicKey& inboxPub - ); - InboxEntryDataResult unpackMessage(std::string& serializedBase64, privmx::crypto::PrivateKey& inboxPriv); - InboxEntryPublicDataResult unpackMessagePublicOnly(std::string& serializedBase64); -}; - -} // namespace inbox -} // namespace endpoint -} // namespace privmx - -#endif // _PRIVMXLIB_ENDPOINT_INBOX_INBOXENTRIESDATAENCRYPTORSERIALIZER_HPP_ \ No newline at end of file diff --git a/endpoint/inbox/include/privmx/endpoint/inbox/InboxTypes.hpp b/endpoint/inbox/include/privmx/endpoint/inbox/InboxTypes.hpp index 8a068a96..68ac319a 100644 --- a/endpoint/inbox/include/privmx/endpoint/inbox/InboxTypes.hpp +++ b/endpoint/inbox/include/privmx/endpoint/inbox/InboxTypes.hpp @@ -13,9 +13,11 @@ limitations under the License. #define _PRIVMXLIB_ENDPOINT_INBOX_INBOXTYPES_HPP_ #include +#include #include "privmx/endpoint/core/CoreTypes.hpp" #include "privmx/endpoint/inbox/ServerTypes.hpp" +#include "privmx/endpoint/inbox/Types.hpp" namespace privmx { namespace endpoint { @@ -112,6 +114,39 @@ struct InboxDataResultV5 : public core::DecryptedVersionedData { InboxPrivateDataV5AsResult privateData; }; +// Entry + +struct InboxEntryPublicData { + std::string userPubKey; + bool keyPreset; + std::string usedInboxKeyId; +}; + +struct InboxEntryPrivateData { + std::string filesMetaKey; + std::string text; +}; + +struct InboxEntrySendModel { + InboxEntryPublicData publicData; + InboxEntryPrivateData privateData; +}; + +struct InboxEntryPublicDataResult : public InboxEntryPublicData { + int64_t statusCode; +}; + +struct InboxEntryDataResult { + InboxEntryPublicData publicData; + InboxEntryPrivateData privateData; + int64_t statusCode; +}; + +struct InboxEntryResult : public InboxEntryDataResult { + std::string storeId; + std::vector filesIds; +}; + } // namespace inbox } // namespace endpoint } // namespace privmx diff --git a/endpoint/inbox/include/privmx/endpoint/inbox/encryptors/entry/InboxEntryDataEncryptorV1.hpp b/endpoint/inbox/include/privmx/endpoint/inbox/encryptors/entry/InboxEntryDataEncryptorV1.hpp new file mode 100644 index 00000000..d44e99d7 --- /dev/null +++ b/endpoint/inbox/include/privmx/endpoint/inbox/encryptors/entry/InboxEntryDataEncryptorV1.hpp @@ -0,0 +1,39 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef _PRIVMXLIB_ENDPOINT_INBOX_INBOXENTRYDATAENCRYPTORV1_HPP_ +#define _PRIVMXLIB_ENDPOINT_INBOX_INBOXENTRYDATAENCRYPTORV1_HPP_ + +#include +#include + +#include "privmx/endpoint/inbox/InboxTypes.hpp" + +namespace privmx { +namespace endpoint { +namespace inbox { + +class InboxEntryDataEncryptorV1 { +public: + std::string encrypt( + InboxEntrySendModel data, + privmx::crypto::PrivateKey& userPriv, + privmx::crypto::PublicKey& inboxPub + ); + InboxEntryDataResult decrypt(std::string& serializedBase64, privmx::crypto::PrivateKey& inboxPriv); + InboxEntryPublicDataResult decryptPublicOnly(std::string& serializedBase64); +}; + +} // namespace inbox +} // namespace endpoint +} // namespace privmx + +#endif // _PRIVMXLIB_ENDPOINT_INBOX_INBOXENTRYDATAENCRYPTORV1_HPP_ diff --git a/endpoint/inbox/include/privmx/endpoint/inbox/encryptors/entry/InboxEntryDataSchemaMapper.hpp b/endpoint/inbox/include/privmx/endpoint/inbox/encryptors/entry/InboxEntryDataSchemaMapper.hpp new file mode 100644 index 00000000..fc409d82 --- /dev/null +++ b/endpoint/inbox/include/privmx/endpoint/inbox/encryptors/entry/InboxEntryDataSchemaMapper.hpp @@ -0,0 +1,69 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef _PRIVMXLIB_ENDPOINT_INBOX_INBOXENTRYDATASCHEMAMAPPER_HPP_ +#define _PRIVMXLIB_ENDPOINT_INBOX_INBOXENTRYDATASCHEMAMAPPER_HPP_ + +#include +#include + +#include +#include +#include +#include +#include + +#include "privmx/endpoint/inbox/InboxTypes.hpp" +#include "privmx/endpoint/inbox/MessageKeyIdFormatValidator.hpp" +#include "privmx/endpoint/inbox/ServerApi.hpp" +#include "privmx/endpoint/inbox/ServerTypes.hpp" +#include "privmx/endpoint/inbox/Types.hpp" +#include "privmx/endpoint/inbox/encryptors/entry/InboxEntryDataSchemaStrategyV1.hpp" + +namespace privmx { +namespace endpoint { +namespace inbox { + +class InboxEntryDataSchemaMapper { +public: + InboxEntryDataSchemaMapper( + const std::shared_ptr& keyProvider, + const std::shared_ptr& serverApi, + const store::StoreApi& storeApi + ); + + static inbox::server::InboxMessageServer unpackInboxOrigMessage(const std::string& serialized); + + std::string encrypt( + const InboxEntrySendModel& data, + privmx::crypto::PrivateKey& userPriv, + privmx::crypto::PublicKey& inboxPub + ); + InboxEntryDataResult decrypt(std::string& data, privmx::crypto::PrivateKey& inboxPriv); + InboxEntryPublicDataResult decryptPublicOnly(std::string& data); + + InboxEntryResult decryptInboxEntry(thread::server::Message message, const core::ModuleKeys& inboxKeys); + inbox::InboxEntry convertInboxEntry(thread::server::Message message, const InboxEntryResult& inboxEntry); + inbox::InboxEntry decryptAndConvertInboxEntry(thread::server::Message message, const core::ModuleKeys& inboxKeys); + +private: + std::string readInboxIdFromMessageKeyId(const std::string& keyId); + + std::shared_ptr _keyProvider; + MessageKeyIdFormatValidator _messageKeyIdFormatValidator; + InboxEntryDataSchemaStrategyV1 _strategyV1; +}; + +} // namespace inbox +} // namespace endpoint +} // namespace privmx + +#endif // _PRIVMXLIB_ENDPOINT_INBOX_INBOXENTRYDATASCHEMAMAPPER_HPP_ diff --git a/endpoint/inbox/include/privmx/endpoint/inbox/encryptors/entry/InboxEntryDataSchemaStrategyV1.hpp b/endpoint/inbox/include/privmx/endpoint/inbox/encryptors/entry/InboxEntryDataSchemaStrategyV1.hpp new file mode 100644 index 00000000..d7cc95a1 --- /dev/null +++ b/endpoint/inbox/include/privmx/endpoint/inbox/encryptors/entry/InboxEntryDataSchemaStrategyV1.hpp @@ -0,0 +1,67 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef _PRIVMXLIB_ENDPOINT_INBOX_INBOXENTRYDATASCHEMASTRATEGYV1_HPP_ +#define _PRIVMXLIB_ENDPOINT_INBOX_INBOXENTRYDATASCHEMASTRATEGYV1_HPP_ + +#include + +#include +#include +#include +#include +#include +#include + +#include "privmx/endpoint/inbox/Constants.hpp" +#include "privmx/endpoint/inbox/InboxTypes.hpp" +#include "privmx/endpoint/inbox/ServerApi.hpp" +#include "privmx/endpoint/inbox/ServerTypes.hpp" +#include "privmx/endpoint/inbox/Types.hpp" +#include "privmx/endpoint/inbox/encryptors/entry/InboxEntryDataEncryptorV1.hpp" + +namespace privmx { +namespace endpoint { +namespace inbox { + +class InboxEntryDataSchemaStrategyV1 { +public: + InboxEntryDataSchemaStrategyV1(const std::shared_ptr& serverApi, const store::StoreApi& storeApi); + std::string encrypt( + const InboxEntrySendModel& data, + privmx::crypto::PrivateKey& userPriv, + privmx::crypto::PublicKey& inboxPub + ) const; + InboxEntryDataResult decrypt(std::string& data, privmx::crypto::PrivateKey& inboxPriv) const; + InboxEntryPublicDataResult decryptPublicOnly(std::string& data) const; + InboxEntryResult decryptEntry( + const thread::server::Message& message, + const core::ModuleKeys& inboxKeys, + const std::shared_ptr& keyProvider + ) const; + inbox::InboxEntry convertToFinal( + const thread::server::Message& message, + const InboxEntryResult& raw, + const std::string& inboxId + ) const; + +private: + static InboxEntryResult makeEmptyResultWithStatusCode(int64_t statusCode); + std::shared_ptr _serverApi; + store::StoreApi _storeApi; + mutable InboxEntryDataEncryptorV1 _encryptor; +}; + +} // namespace inbox +} // namespace endpoint +} // namespace privmx + +#endif // _PRIVMXLIB_ENDPOINT_INBOX_INBOXENTRYDATASCHEMASTRATEGYV1_HPP_ diff --git a/endpoint/inbox/include/privmx/endpoint/inbox/encryptors/inbox/InboxDataSchemaMapper.hpp b/endpoint/inbox/include/privmx/endpoint/inbox/encryptors/inbox/InboxDataSchemaMapper.hpp new file mode 100644 index 00000000..cd70926c --- /dev/null +++ b/endpoint/inbox/include/privmx/endpoint/inbox/encryptors/inbox/InboxDataSchemaMapper.hpp @@ -0,0 +1,93 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef _PRIVMXLIB_ENDPOINT_INBOX_INBOXDATASCHEMAMAPPER_HPP_ +#define _PRIVMXLIB_ENDPOINT_INBOX_INBOXDATASCHEMAMAPPER_HPP_ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include "privmx/endpoint/inbox/Constants.hpp" +#include "privmx/endpoint/inbox/InboxTypes.hpp" +#include "privmx/endpoint/inbox/ServerTypes.hpp" +#include "privmx/endpoint/inbox/Types.hpp" +#include "privmx/endpoint/inbox/encryptors/inbox/InboxDataSchemaStrategyV4.hpp" +#include "privmx/endpoint/inbox/encryptors/inbox/InboxDataSchemaStrategyV5.hpp" + +namespace privmx { +namespace endpoint { +namespace inbox { + +class InboxDataSchemaMapper { +public: + InboxDataSchemaMapper(const privmx::crypto::PrivateKey& userPrivKey, const core::Connection& connection); + + server::InboxData encrypt(const InboxDataProcessorModelV5& data, const std::string& key); + + std::tuple decrypt( + const server::InboxInfo& inbox, + const core::DecryptedEncKey& encKey + ); + + InboxDataSchema::Version getDataStructureVersion(const server::InboxDataEntry& entry); + + void assertDataIntegrity(const server::InboxInfo& inbox); + + uint32_t validateDataIntegrity(const server::InboxInfo& inbox); + + InboxPublicViewData getPublicViewData(const server::InboxGetPublicViewResult& publicView); + InboxInternalMetaV5 decryptInternalMeta(const server::InboxDataEntry& entry, const core::DecryptedEncKey& encKey); + + std::vector validateDecryptAndConvertInboxes( + const std::vector& inboxes, + const std::shared_ptr& keyProvider + ); + + Inbox validateDecryptAndConvertInbox( + const server::InboxInfo& inbox, + const std::shared_ptr& keyProvider + ); + + static Inbox toLibInbox( + const server::InboxInfo& info, + const core::Buffer& publicMeta, + const core::Buffer& privateMeta, + const std::optional& filesConfig, + int64_t statusCode, + int64_t schemaVersion + ); + +private: + privmx::crypto::PrivateKey _userPrivKey; + core::Connection _connection; + core::VersionStrategyMapper> _strategyMapper; + std::shared_ptr _strategyV4; + std::shared_ptr _strategyV5; +}; + +} // namespace inbox +} // namespace endpoint +} // namespace privmx + +#endif // _PRIVMXLIB_ENDPOINT_INBOX_INBOXDATASCHEMAMAPPER_HPP_ diff --git a/endpoint/inbox/include/privmx/endpoint/inbox/encryptors/inbox/InboxDataSchemaStrategyV4.hpp b/endpoint/inbox/include/privmx/endpoint/inbox/encryptors/inbox/InboxDataSchemaStrategyV4.hpp new file mode 100644 index 00000000..38ac3641 --- /dev/null +++ b/endpoint/inbox/include/privmx/endpoint/inbox/encryptors/inbox/InboxDataSchemaStrategyV4.hpp @@ -0,0 +1,62 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef _PRIVMXLIB_ENDPOINT_INBOX_INBOXDATASCHEMASTRATEGYV4_HPP_ +#define _PRIVMXLIB_ENDPOINT_INBOX_INBOXDATASCHEMASTRATEGYV4_HPP_ + +#include + +#include +#include +#include + +#include "privmx/endpoint/inbox/InboxTypes.hpp" +#include "privmx/endpoint/inbox/ServerTypes.hpp" +#include "privmx/endpoint/inbox/Types.hpp" +#include "privmx/endpoint/inbox/encryptors/inbox/InboxDataProcessorV4.hpp" + +namespace privmx { +namespace endpoint { +namespace inbox { + +// clang-format off +class InboxDataSchemaStrategyV4 : public core::TypedDataSchemaStrategy< + server::InboxInfo, + InboxDataResultV4, + std::tuple +> { + // clang-format on +public: + InboxDataResultV4 decrypt(const server::InboxInfo& inbox, const core::DecryptedEncKey& encKey) const override; + std::tuple convert( + const server::InboxInfo& inbox, + const InboxDataResultV4& raw + ) const override; + std::tuple makeErrorResult( + const server::InboxInfo& inbox, + int64_t errorCode + ) const override; + InboxPublicDataV4AsResult unpackPublicOnly(const Poco::Dynamic::Var& publicData) const; + InboxPublicViewData getPublicViewData(const server::InboxGetPublicViewResult& publicView) const; + InboxInternalMetaV5 decryptInternalMeta( + const server::InboxDataEntry& entry, + const core::DecryptedEncKey& encKey + ) const; + +private: + mutable InboxDataProcessorV4 _processor; +}; + +} // namespace inbox +} // namespace endpoint +} // namespace privmx + +#endif // _PRIVMXLIB_ENDPOINT_INBOX_INBOXDATASCHEMASTRATEGYV4_HPP_ diff --git a/endpoint/inbox/include/privmx/endpoint/inbox/encryptors/inbox/InboxDataSchemaStrategyV5.hpp b/endpoint/inbox/include/privmx/endpoint/inbox/encryptors/inbox/InboxDataSchemaStrategyV5.hpp new file mode 100644 index 00000000..1da60ef1 --- /dev/null +++ b/endpoint/inbox/include/privmx/endpoint/inbox/encryptors/inbox/InboxDataSchemaStrategyV5.hpp @@ -0,0 +1,69 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef _PRIVMXLIB_ENDPOINT_INBOX_INBOXDATASCHEMASTRATEGYV5_HPP_ +#define _PRIVMXLIB_ENDPOINT_INBOX_INBOXDATASCHEMASTRATEGYV5_HPP_ + +#include + +#include +#include +#include +#include + +#include "privmx/endpoint/inbox/InboxTypes.hpp" +#include "privmx/endpoint/inbox/ServerTypes.hpp" +#include "privmx/endpoint/inbox/Types.hpp" +#include "privmx/endpoint/inbox/encryptors/inbox/InboxDataProcessorV5.hpp" + +namespace privmx { +namespace endpoint { +namespace inbox { + +// clang-format off +class InboxDataSchemaStrategyV5 : public core::TypedDataSchemaStrategy< + server::InboxInfo, + InboxDataResultV5, + std::tuple +> { + // clang-format on +public: + InboxDataResultV5 decrypt(const server::InboxInfo& inbox, const core::DecryptedEncKey& encKey) const override; + std::tuple convert( + const server::InboxInfo& inbox, + const InboxDataResultV5& raw + ) const override; + std::tuple makeErrorResult( + const server::InboxInfo& inbox, + int64_t errorCode + ) const override; + core::DataIntegrityObject getDIOAndAssertIntegrity(const server::InboxData& data) const; + InboxPublicDataV5AsResult unpackPublicOnly(const Poco::Dynamic::Var& publicData) const; + InboxPublicViewData getPublicViewData(const server::InboxGetPublicViewResult& publicView) const; + InboxInternalMetaV5 decryptInternalMeta( + const server::InboxDataEntry& entry, + const core::DecryptedEncKey& encKey + ) const; + server::InboxData packForServer( + const InboxDataProcessorModelV5& data, + const privmx::crypto::PrivateKey& authorPrivateKey, + const std::string& inboxKey + ) const; + +private: + mutable InboxDataProcessorV5 _processor; +}; + +} // namespace inbox +} // namespace endpoint +} // namespace privmx + +#endif // _PRIVMXLIB_ENDPOINT_INBOX_INBOXDATASCHEMASTRATEGYV5_HPP_ diff --git a/endpoint/inbox/src/InboxApiImpl.cpp b/endpoint/inbox/src/InboxApiImpl.cpp index 4fa12352..538fe36d 100644 --- a/endpoint/inbox/src/InboxApiImpl.cpp +++ b/endpoint/inbox/src/InboxApiImpl.cpp @@ -60,7 +60,8 @@ InboxApiImpl::InboxApiImpl( _handleManager(handleManager), _inboxHandleManager(InboxHandleManager(handleManager)), _messageKeyIdFormatValidator(MessageKeyIdFormatValidator()), _fileKeyIdFormatValidator(FileKeyIdFormatValidator()), _serverRequestChunkSize(serverRequestChunkSize), - _subscriber(connection.getImpl()->getGateway(), INBOX_TYPE_FILTER_FLAG) { + _subscriber(connection.getImpl()->getGateway(), INBOX_TYPE_FILTER_FLAG), + _inboxDataSchemaMapper(userPrivKey, connection), _inboxEntryDataSchemaMapper(keyProvider, serverApi, storeApi) { _notificationListenerId = _eventMiddleware->addNotificationEventListener( std::bind(&InboxApiImpl::processNotificationEvent, this, std::placeholders::_1, std::placeholders::_2) ); @@ -104,16 +105,12 @@ std::string InboxApiImpl::createInbox( std::nullopt }; - auto storeId = (_storeApi.getImpl()) - ->createStoreEx( - contextId, users, managers, emptyBuf, randNameAsBuf, INBOX_TYPE_FILTER_FLAG, - policiesWithItems - ); - auto threadId = (_threadApi.getImpl()) - ->createThreadEx( - contextId, users, managers, emptyBuf, randNameAsBuf, INBOX_TYPE_FILTER_FLAG, - policiesWithItems - ); + auto storeId = _storeApi.getImpl()->createStoreEx( + contextId, users, managers, emptyBuf, randNameAsBuf, INBOX_TYPE_FILTER_FLAG, policiesWithItems + ); + auto threadId = _threadApi.getImpl()->createThreadEx( + contextId, users, managers, emptyBuf, randNameAsBuf, INBOX_TYPE_FILTER_FLAG, policiesWithItems + ); auto resourceId = core::EndpointUtils::generateId(); auto inboxDIO = _connection.getImpl()->createDIO(contextId, resourceId); auto inboxSecret = _keyProvider->generateSecret(); @@ -138,7 +135,7 @@ std::string InboxApiImpl::createInbox( createInboxModel.contextId = contextId; createInboxModel.users = InboxDataHelper::mapUsers(users); createInboxModel.managers = InboxDataHelper::mapUsers(managers); - createInboxModel.data = _inboxDataProcessorV5.packForServer(inboxDataIn, _userPrivKey, inboxKey.key); + createInboxModel.data = _inboxDataSchemaMapper.encrypt(inboxDataIn, inboxKey.key); createInboxModel.keyId = inboxKey.id; auto all_users = core::EndpointUtils::uniqueListUserWithPubKey(users, managers); auto keysList = _keyProvider->prepareKeysList( @@ -173,7 +170,7 @@ void InboxApiImpl::updateInbox( auto location{getModuleEncKeyLocation(currentInbox, currentInboxResourceId)}; auto inboxKeys{getAndValidateModuleKeys(currentInbox, currentInboxResourceId)}; auto currentInboxKey{findEncKeyByKeyId(inboxKeys, currentInboxEntry.keyId)}; - auto inboxInternalMeta = decryptInboxInternalMeta(currentInboxEntry, currentInboxKey); + auto inboxInternalMeta = _inboxDataSchemaMapper.decryptInternalMeta(currentInboxEntry, currentInboxKey); auto usersKeysResolver{ core::UsersKeysResolver::create(currentInbox, users, managers, forceGenerateNewKey, currentInboxKey) @@ -232,7 +229,7 @@ void InboxApiImpl::updateInbox( inboxUpdateModel.resourceId = currentInboxResourceId; inboxUpdateModel.users = InboxDataHelper::mapUsers(users); inboxUpdateModel.managers = InboxDataHelper::mapUsers(managers); - inboxUpdateModel.data = _inboxDataProcessorV5.packForServer(inboxDataIn, _userPrivKey, inboxKey.key); + inboxUpdateModel.data = _inboxDataSchemaMapper.encrypt(inboxDataIn, inboxKey.key); inboxUpdateModel.keyId = inboxKey.id; inboxUpdateModel.keys = keysList; inboxUpdateModel.force = force; @@ -250,18 +247,16 @@ void InboxApiImpl::updateInbox( _serverApi->inboxUpdate(inboxUpdateModel); invalidateModuleKeysInCache(inboxId); - auto store = (_storeApi.getImpl())->getStoreEx(currentInboxData.storeId, INBOX_TYPE_FILTER_FLAG); - (_storeApi.getImpl()) - ->updateStore( - currentInboxData.storeId, users, managers, store.publicMeta, store.privateMeta, store.version, force, - forceGenerateNewKey, policiesWithItems - ); - auto thread = (_threadApi.getImpl())->getThreadEx(currentInboxData.threadId, INBOX_TYPE_FILTER_FLAG); - (_threadApi.getImpl()) - ->updateThread( - currentInboxData.threadId, users, managers, thread.publicMeta, thread.privateMeta, thread.version, force, - forceGenerateNewKey, policiesWithItems - ); + auto store = _storeApi.getImpl()->getStoreEx(currentInboxData.storeId, INBOX_TYPE_FILTER_FLAG); + _storeApi.getImpl()->updateStore( + currentInboxData.storeId, users, managers, store.publicMeta, store.privateMeta, store.version, force, + forceGenerateNewKey, policiesWithItems + ); + auto thread = _threadApi.getImpl()->getThreadEx(currentInboxData.threadId, INBOX_TYPE_FILTER_FLAG); + _threadApi.getImpl()->updateThread( + currentInboxData.threadId, users, managers, thread.publicMeta, thread.privateMeta, thread.version, force, + forceGenerateNewKey, policiesWithItems + ); } Inbox InboxApiImpl::getInbox(const std::string& inboxId) { @@ -292,7 +287,7 @@ Inbox InboxApiImpl::_getInboxEx(const std::string& inboxId, const std::string& t auto inbox = getServerInbox(inboxId, type); PRIVMX_DEBUG_TIME_CHECKPOINT(PlatformInbox, _getInboxEx, data send) setNewModuleKeysInCache(inbox.id, inboxToModuleKeys(inbox), inbox.version); - auto result = validateDecryptAndConvertInboxDataToInbox(inbox); + auto result = _inboxDataSchemaMapper.validateDecryptAndConvertInbox(inbox, _keyProvider); PRIVMX_DEBUG_TIME_STOP(PlatformInbox, _getInboxEx, data decrypted) return result; } @@ -308,7 +303,9 @@ core::PagingList InboxApiImpl::listInboxes(const std::string& cont for (auto inbox : inboxesListResult.inboxes) { setNewModuleKeysInCache(inbox.id, inboxToModuleKeys(inbox), inbox.version); } - std::vector inboxes = validateDecryptAndConvertInboxesDataToInboxes(inboxesListResult.inboxes); + std::vector inboxes = _inboxDataSchemaMapper.validateDecryptAndConvertInboxes( + inboxesListResult.inboxes, _keyProvider + ); return core::PagingList({.totalAvailable = inboxesListResult.count, .readItems = inboxes}); } @@ -324,8 +321,8 @@ void InboxApiImpl::deleteInbox(const std::string& inboxId) { server::InboxDeleteModel inboxDeleteModel{.inboxId = inboxId}; _serverApi->inboxDelete(inboxDeleteModel); invalidateModuleKeysInCache(inboxId); - (_storeApi.getImpl())->deleteStore(inboxDataRaw.storeId); - (_threadApi.getImpl())->deleteThread(inboxDataRaw.threadId); + _storeApi.getImpl()->deleteStore(inboxDataRaw.storeId); + _threadApi.getImpl()->deleteThread(inboxDataRaw.threadId); } int64_t InboxApiImpl::prepareEntry( @@ -411,11 +408,10 @@ void InboxApiImpl::sendEntry(const int64_t inboxHandle) { requestId = commitSentInfo.filesInfo[0].fileSendResult.requestId; } - auto serializer = InboxEntriesDataEncryptorSerializer::Ptr(new InboxEntriesDataEncryptorSerializer()); auto messageDIO = _connection.getImpl()->createPublicDIO( "", core::EndpointUtils::generateId(), _userPrivKeyECC.getPublicKey(), handle->inboxId, handle->inboxResourceId ); - auto serializedMessage = serializer->packMessage(modelForSerializer, _userPrivKeyECC, inboxPubKeyECC); + auto serializedMessage = _inboxEntryDataSchemaMapper.encrypt(modelForSerializer, _userPrivKeyECC, inboxPubKeyECC); inbox::server::InboxSendModel model; if (hasFiles) { model.requestId = requestId; @@ -433,7 +429,9 @@ inbox::InboxEntry InboxApiImpl::readEntry(const std::string& inboxEntryId) { PRIVMX_DEBUG_TIME_CHECKPOINT(InboxApi, readEntry) auto messageRaw = getServerMessage(inboxEntryId); PRIVMX_DEBUG_TIME_CHECKPOINT(InboxApi, readEntry, data recv); - auto result = decryptAndConvertInboxEntryDataToInboxEntry(messageRaw, getEntryDecryptionKeys(messageRaw)); + auto result = _inboxEntryDataSchemaMapper.decryptAndConvertInboxEntry( + messageRaw, getEntryDecryptionKeys(messageRaw) + ); PRIVMX_DEBUG_TIME_STOP(InboxApi, readEntry, data decrypted) return result; } @@ -459,7 +457,9 @@ core::PagingList InboxApiImpl::listEntries( std::vector messages; if (messagesList.messages.size() > 0) { for (auto message : messagesList.messages) { - messages.push_back(decryptAndConvertInboxEntryDataToInboxEntry(message, getEntryDecryptionKeys(message))); + messages.push_back( + _inboxEntryDataSchemaMapper.decryptAndConvertInboxEntry(message, getEntryDecryptionKeys(message)) + ); } } PRIVMX_DEBUG_TIME_STOP(InboxApi, listEntries, data decrypted) @@ -490,7 +490,7 @@ int64_t InboxApiImpl::createInboxFileHandleForRead(const privmx::endpoint::store auto inboxKeys = getEntryDecryptionKeys(messageRaw); - auto messageData = decryptInboxEntry(messageRaw, inboxKeys); + auto messageData = _inboxEntryDataSchemaMapper.decryptInboxEntry(messageRaw, inboxKeys); core::DecryptedEncKey fileMetaEncKey{ core::EncKey{.id = "", .key = messageData.privateData.filesMetaKey}, core::DecryptedVersionedData{.dataStructureVersion = 0, .statusCode = 0} @@ -580,381 +580,10 @@ inbox::server::InboxDataEntry InboxApiImpl::getInboxCurrentDataEntry(inbox::serv return inboxRaw.data.back(); } -InboxDataResultV4 InboxApiImpl::decryptInboxV4( - inbox::server::InboxDataEntry inboxEntry, - const core::DecryptedEncKey& encKey -) { - return _inboxDataProcessorV4.unpackAll(inboxEntry.data, encKey.key); -} - -InboxDataResultV5 InboxApiImpl::decryptInboxV5( - inbox::server::InboxDataEntry inboxEntry, - const core::DecryptedEncKey& encKey -) { - return _inboxDataProcessorV5.unpackAll(inboxEntry.data, encKey.key); -} - -inbox::Inbox InboxApiImpl::convertServerInboxToLibInbox( - inbox::server::InboxInfo inbox, - const core::Buffer& publicMeta, - const core::Buffer& privateMeta, - const std::optional& filesConfig, - const int64_t& statusCode, - const int64_t& schemaVersion -) { - return inbox::Inbox{ - .inboxId = inbox.id, - .contextId = inbox.contextId, - .createDate = inbox.createDate, - .creator = inbox.creator, - .lastModificationDate = inbox.lastModificationDate, - .lastModifier = inbox.lastModifier, - .users = inbox.users, - .managers = inbox.managers, - .version = inbox.version, - .publicMeta = publicMeta, - .privateMeta = privateMeta, - .filesConfig = filesConfig, - .policy = core::Factory::parsePolicyServerObject(inbox.policy), - .statusCode = statusCode, - .schemaVersion = schemaVersion - }; -} - -Inbox InboxApiImpl::convertInboxV4(inbox::server::InboxInfo inboxRaw, const InboxDataResultV4& inboxData) { - return convertServerInboxToLibInbox( - inboxRaw, inboxData.publicData.publicMeta, inboxData.privateData.privateMeta, inboxData.filesConfig, - inboxData.statusCode, InboxDataSchema::VERSION_4 - ); -} - -Inbox InboxApiImpl::convertInboxV5(inbox::server::InboxInfo inboxRaw, const InboxDataResultV5& inboxData) { - return convertServerInboxToLibInbox( - inboxRaw, inboxData.publicData.publicMeta, inboxData.privateData.privateMeta, inboxData.filesConfig, - inboxData.statusCode, InboxDataSchema::VERSION_5 - ); -} - InboxPublicViewData InboxApiImpl::getInboxPublicViewData(const std::string& inboxId) { server::InboxGetModel model; model.id = inboxId; - auto publicView = _serverApi->inboxGetPublicView(model); - - InboxPublicViewData result; - - if (publicView.publicData.type() == typeid(Poco::JSON::Object::Ptr)) { - auto versioned = core::dynamic::VersionedData::fromJSON(publicView.publicData); - switch (versioned.version) { - case InboxDataSchema::Version::VERSION_4: { - auto publicData{_inboxDataProcessorV4.unpackPublicOnly(publicView.publicData)}; - result.inboxId = publicView.inboxId; - result.resourceId = ""; - result.version = publicView.version; - result.dataStructureVersion = publicData.dataStructureVersion; - result.authorPubKey = publicData.authorPubKey; - result.inboxEntriesPubKeyBase58DER = publicData.inboxEntriesPubKeyBase58DER; - result.inboxEntriesKeyId = publicData.inboxEntriesKeyId; - result.publicMeta = publicData.publicMeta; - result.statusCode = publicData.statusCode; - return result; - } - case InboxDataSchema::Version::VERSION_5: { - auto publicData{_inboxDataProcessorV5.unpackPublicOnly(publicView.publicData)}; - result.inboxId = publicView.inboxId; - result.resourceId = ""; - result.version = publicView.version; - result.dataStructureVersion = publicData.dataStructureVersion; - result.authorPubKey = publicData.authorPubKey; - result.inboxEntriesPubKeyBase58DER = publicData.inboxEntriesPubKeyBase58DER; - result.inboxEntriesKeyId = publicData.inboxEntriesKeyId; - result.publicMeta = publicData.publicMeta; - result.statusCode = publicData.statusCode; - return result; - } - } - } - auto e = UnknownInboxFormatException(); - result.statusCode = e.getCode(); - return result; -} - -InboxDataSchema::Version InboxApiImpl::getInboxDataEntryStructureVersion(inbox::server::InboxDataEntry inboxEntry) { - if (inboxEntry.data.meta.type() == typeid(Poco::JSON::Object::Ptr)) { - auto versioned = core::dynamic::VersionedData::fromJSON(inboxEntry.data.meta); - switch (versioned.version) { - case InboxDataSchema::Version::VERSION_4: - return InboxDataSchema::Version::VERSION_4; - case InboxDataSchema::Version::VERSION_5: - return InboxDataSchema::Version::VERSION_5; - default: - return InboxDataSchema::Version::UNKNOWN; - } - } - return InboxDataSchema::Version::UNKNOWN; -} - -std::tuple InboxApiImpl::decryptAndConvertInboxDataToInbox( - inbox::server::InboxInfo inbox, - inbox::server::InboxDataEntry inboxEntry, - const core::DecryptedEncKey& encKey -) { - switch (getInboxDataEntryStructureVersion(inboxEntry)) { - case InboxDataSchema::Version::UNKNOWN: { - auto e = UnknownInboxFormatException(); - return std::make_tuple( - convertServerInboxToLibInbox(inbox, {}, {}, {}, e.getCode()), core::DataIntegrityObject() - ); - } - case InboxDataSchema::Version::VERSION_4: { - auto decryptedInboxData = decryptInboxV4(inboxEntry, encKey); - return std::make_tuple( - convertInboxV4(inbox, decryptedInboxData), - core::DataIntegrityObject{ - .creatorUserId = inbox.lastModifier, - .creatorPubKey = decryptedInboxData.privateData.authorPubKey, - .contextId = inbox.contextId, - .resourceId = inbox.resourceId.value_or(""), - .timestamp = inbox.lastModificationDate, - .randomId = std::string(), - .containerId = std::nullopt, - .containerResourceId = std::nullopt, - .bridgeIdentity = std::nullopt - } - ); - } - case InboxDataSchema::Version::VERSION_5: { - auto decryptedInboxData = decryptInboxV5(inboxEntry, encKey); - return std::make_tuple(convertInboxV5(inbox, decryptedInboxData), decryptedInboxData.privateData.dio); - } - } - auto e = UnknownInboxFormatException(); - return std::make_tuple(convertServerInboxToLibInbox(inbox, {}, {}, {}, e.getCode()), core::DataIntegrityObject()); -} - -std::vector InboxApiImpl::validateDecryptAndConvertInboxesDataToInboxes( - std::vector inboxes -) { - std::vector result(inboxes.size()); - for (size_t i = 0; i < inboxes.size(); i++) { - auto inbox = inboxes[i]; - result[i].statusCode = validateInboxDataIntegrity(inbox); - if (result[i].statusCode != 0) { - result[i] = convertServerInboxToLibInbox(inbox, {}, {}, {}, result[i].statusCode); - } - } - core::KeyDecryptionAndVerificationRequest keyProviderRequest; - for (size_t i = 0; i < inboxes.size(); i++) { - auto inbox = inboxes[i]; - core::EncKeyLocation location{.contextId = inbox.contextId, .resourceId = inbox.resourceId.value_or("")}; - auto inbox_data_entry = inbox.data.back(); - keyProviderRequest.addOne(inbox.keys, inbox_data_entry.keyId, location); - } - auto inboxesKeys{_keyProvider->getKeysAndVerify(keyProviderRequest)}; - std::vector inboxesDIO(inboxes.size()); - std::map duplication_check; - for (size_t i = 0; i < inboxes.size(); i++) { - if (result[i].statusCode != 0) { - inboxesDIO.push_back(core::DataIntegrityObject{}); - } else { - auto inbox = inboxes[i]; - try { - auto tmp = decryptAndConvertInboxDataToInbox( - inbox, inbox.data.back(), - inboxesKeys - .at(core::EncKeyLocation{ - .contextId = inbox.contextId, .resourceId = inbox.resourceId.value_or("") - }) - .at(inbox.data.back().keyId) - ); - result[i] = std::get<0>(tmp); - auto inboxDIO = std::get<1>(tmp); - inboxesDIO[i] = inboxDIO; - std::string fullRandomId = inboxDIO.randomId + "-" + std::to_string(inboxDIO.timestamp); - if (duplication_check.find(fullRandomId) == duplication_check.end()) { - duplication_check.insert(std::make_pair(fullRandomId, true)); - } else { - result[i].statusCode = core::DataIntegrityObjectDuplicatedException().getCode(); - } - } catch (const core::Exception& e) { - result[i] = convertServerInboxToLibInbox(inbox, {}, {}, {}, e.getCode()); - inboxesDIO[i] = core::DataIntegrityObject{}; - } - } - } - std::vector verifierInput{}; - for (size_t i = 0; i < result.size(); i++) { - if (result[i].statusCode == 0) { - verifierInput.push_back( - core::VerificationRequest{ - .contextId = result[i].contextId, - .senderId = result[i].lastModifier, - .senderPubKey = inboxesDIO[i].creatorPubKey, - .date = result[i].lastModificationDate, - .bridgeIdentity = inboxesDIO[i].bridgeIdentity - } - ); - } - } - std::vector verified; - verified = _connection.getImpl()->getUserVerifier()->verify(verifierInput); - for (size_t j = 0, i = 0; i < result.size(); i++) { - if (result[i].statusCode == 0) { - result[i].statusCode = verified[j] ? 0 : - core::ExceptionConverter::getCodeOfUserVerificationFailureException(); - j++; - } - } - return result; -} - -inbox::Inbox InboxApiImpl::validateDecryptAndConvertInboxDataToInbox(inbox::server::InboxInfo inbox) { - auto statusCode = validateInboxDataIntegrity(inbox); - if (statusCode != 0) { - return convertServerInboxToLibInbox(inbox, {}, {}, {}, statusCode); - } - auto inbox_data_entry = inbox.data.back(); - core::KeyDecryptionAndVerificationRequest keyProviderRequest; - core::EncKeyLocation location{.contextId = inbox.contextId, .resourceId = inbox.resourceId.value_or("")}; - keyProviderRequest.addOne(inbox.keys, inbox_data_entry.keyId, location); - auto key = _keyProvider->getKeysAndVerify(keyProviderRequest).at(location).at(inbox_data_entry.keyId); - Inbox result; - core::DataIntegrityObject inboxDIO; - std::tie(result, inboxDIO) = decryptAndConvertInboxDataToInbox(inbox, inbox_data_entry, key); - if (result.statusCode != 0) - return result; - std::vector verifierInput{}; - verifierInput.push_back( - core::VerificationRequest{ - .contextId = result.contextId, - .senderId = result.lastModifier, - .senderPubKey = inboxDIO.creatorPubKey, - .date = result.lastModificationDate, - .bridgeIdentity = inboxDIO.bridgeIdentity - } - ); - std::vector verified; - verified = _connection.getImpl()->getUserVerifier()->verify(verifierInput); - result.statusCode = verified[0] ? 0 : core::ExceptionConverter::getCodeOfUserVerificationFailureException(); - return result; -} - -InboxInternalMetaV5 InboxApiImpl::decryptInboxInternalMeta( - inbox::server::InboxDataEntry inboxEntry, - const core::DecryptedEncKey& encKey -) { - switch (getInboxDataEntryStructureVersion(inboxEntry)) { - case InboxDataSchema::Version::UNKNOWN: - throw UnknownInboxFormatException(); - case InboxDataSchema::Version::VERSION_4: { - return InboxInternalMetaV5(); - } - case InboxDataSchema::Version::VERSION_5: { - auto decryptedInboxData = decryptInboxV5(inboxEntry, encKey); - return decryptedInboxData.privateData.internalMeta; - } - } - throw UnknownInboxFormatException(); -} - -inbox::server::InboxMessageServer InboxApiImpl::unpackInboxOrigMessage(const std::string& serialized) { - try { - auto json = utils::Base64::toString(serialized); - Poco::JSON::Parser parser; - return inbox::server::InboxMessageServer::fromJSON(parser.parse(json).extract()); - } catch (...) { throw FailedToExtractMessagePublicMetaException(); } -} - -InboxEntryResult InboxApiImpl::decryptInboxEntry(thread::server::Message message, const core::ModuleKeys& inboxKeys) { - InboxEntryResult result; - result.statusCode = 0; - try { - auto inboxMessageServer = unpackInboxOrigMessage(message.data); - auto msgData = inboxMessageServer.message; - - auto serializer = inbox::InboxEntriesDataEncryptorSerializer::Ptr( - new inbox::InboxEntriesDataEncryptorSerializer() - ); - auto msgPublicData = serializer->unpackMessagePublicOnly(msgData); - core::KeyDecryptionAndVerificationRequest keyProviderRequest; - core::EncKeyLocation location{.contextId = inboxKeys.contextId, .resourceId = inboxKeys.moduleResourceId}; - keyProviderRequest.addOne(inboxKeys.keys, msgPublicData.usedInboxKeyId, location); - auto encKey = _keyProvider->getKeysAndVerify(keyProviderRequest).at(location).at(msgPublicData.usedInboxKeyId); - auto eccKey = crypto::ECC::fromPrivateKey(encKey.key); - auto privKeyECC = crypto::PrivateKey(eccKey); - auto decrypted = serializer->unpackMessage(msgData, privKeyECC); - result.statusCode = encKey.statusCode; - result.publicData = decrypted.publicData; - result.privateData = decrypted.privateData; - result.storeId = inboxMessageServer.store; - result.filesIds = getFilesIdsFromServerMessage(inboxMessageServer); - return result; - } catch (const privmx::endpoint::core::Exception& e) { - return getEmptyResultWithStatusCode(e.getCode()); - } catch (const privmx::utils::PrivmxException& e) { - return getEmptyResultWithStatusCode(core::ExceptionConverter::convert(e).getCode()); - } catch (...) { return getEmptyResultWithStatusCode(ENDPOINT_CORE_EXCEPTION_CODE); } -} - -InboxEntryResult InboxApiImpl::getEmptyResultWithStatusCode(const int64_t statusCode) { - InboxEntryResult result; - result.statusCode = statusCode; - return result; -} - -std::vector InboxApiImpl::getFilesIdsFromServerMessage(inbox::server::InboxMessageServer serverMessage) { - return serverMessage.files; -} - -inbox::InboxEntry InboxApiImpl::convertInboxEntry( - thread::server::Message message, - const inbox::InboxEntryResult& inboxEntry -) { - inbox::InboxEntry result; - result.entryId = message.id; - result.inboxId = readInboxIdFromMessageKeyId(message.keyId); - result.createDate = message.createDate; - core::DecryptedEncKey fileMetaEncKey{ - core::EncKey{.id = "", .key = inboxEntry.privateData.filesMetaKey}, - core::DecryptedVersionedData{.dataStructureVersion = 0, .statusCode = 0} - }; - result.data = core::Buffer::from(inboxEntry.privateData.text); - result.authorPubKey = inboxEntry.publicData.userPubKey; - result.statusCode = inboxEntry.statusCode; - if (inboxEntry.statusCode == 0) { - try { - store::server::StoreFileGetManyModel filesGetModel; - filesGetModel.storeId = inboxEntry.storeId; - filesGetModel.fileIds = inboxEntry.filesIds; - filesGetModel.failOnError = false; - auto serverFiles{_serverApi->storeFileGetMany(filesGetModel)}; - for (auto file : serverFiles.files) { - if (!file.error.has_value()) { - result.files.push_back( - std::get<0>(_storeApi.getImpl()->decryptAndConvertFileDataToFileInfo(file, fileMetaEncKey)) - ); - } else { - store::File error; - auto e = FileFetchFailedException(); - error.statusCode = e.getCode(); - result.files.push_back(error); - } - } - } catch (const privmx::endpoint::core::Exception& e) { - result.statusCode = e.getCode(); - } catch (const privmx::utils::PrivmxException& e) { - result.statusCode = core::ExceptionConverter::convert(e).getCode(); - } catch (...) { result.statusCode = ENDPOINT_CORE_EXCEPTION_CODE; } - } - result.schemaVersion = EntryDataSchema::Version::VERSION_1; - return result; -} - -inbox::InboxEntry InboxApiImpl::decryptAndConvertInboxEntryDataToInboxEntry( - thread::server::Message message, - const core::ModuleKeys& inboxKeys -) { - auto inboxEntry = decryptInboxEntry(message, inboxKeys); - return convertInboxEntry(message, inboxEntry); + return _inboxDataSchemaMapper.getPublicViewData(_serverApi->inboxGetPublicView(model)); } inbox::FilesConfig InboxApiImpl::getFilesConfigOptOrDefault(const std::optional& fileConfig) { @@ -985,7 +614,7 @@ void InboxApiImpl::processNotificationEvent(const std::string& type, const core: auto raw = server::InboxInfo::fromJSON(notification.data); if (raw.type.value_or(std::string(INBOX_TYPE_FILTER_FLAG)) == INBOX_TYPE_FILTER_FLAG) { setNewModuleKeysInCache(raw.id, inboxToModuleKeys(raw), raw.version); - auto data = validateDecryptAndConvertInboxDataToInbox(raw); + auto data = _inboxDataSchemaMapper.validateDecryptAndConvertInbox(raw, _keyProvider); auto event = core::EventBuilder::buildEvent("inbox", data, notification); _eventMiddleware->emitApiEvent(event); } @@ -993,7 +622,7 @@ void InboxApiImpl::processNotificationEvent(const std::string& type, const core: auto raw = server::InboxInfo::fromJSON(notification.data); if (raw.type.value_or(std::string(INBOX_TYPE_FILTER_FLAG)) == INBOX_TYPE_FILTER_FLAG) { setNewModuleKeysInCache(raw.id, inboxToModuleKeys(raw), raw.version); - auto data = validateDecryptAndConvertInboxDataToInbox(raw); + auto data = _inboxDataSchemaMapper.validateDecryptAndConvertInbox(raw, _keyProvider); auto event = core::EventBuilder::buildEvent("inbox", data, notification); _eventMiddleware->emitApiEvent(event); } @@ -1009,7 +638,9 @@ void InboxApiImpl::processNotificationEvent(const std::string& type, const core: auto raw = privmx::endpoint::thread::server::ThreadMessageEventData::fromJSON(notification.data); if (raw.containerType.value_or("") == INBOX_TYPE_FILTER_FLAG) { auto inboxId = readInboxIdFromMessageKeyId(raw.keyId); - auto message = decryptAndConvertInboxEntryDataToInboxEntry(raw, getEntryDecryptionKeys(raw)); + auto message = _inboxEntryDataSchemaMapper.decryptAndConvertInboxEntry( + raw, getEntryDecryptionKeys(raw) + ); auto event = core::EventBuilder::buildEvent( "inbox/" + inboxId + "/entries", message, notification ); @@ -1081,7 +712,7 @@ std::string InboxApiImpl::readMessageIdFromFileKeyId(const std::string& keyId) { } void InboxApiImpl::deleteMessageAndFiles(thread::server::Message message) { - auto publicMeta = unpackInboxOrigMessage(message.data); + auto publicMeta = InboxEntryDataSchemaMapper::unpackInboxOrigMessage(message.data); for (auto fileId : publicMeta.files) { _storeApi.deleteFile(fileId); } @@ -1096,17 +727,16 @@ thread::server::Message InboxApiImpl::getServerMessage(const std::string& messag core::ModuleKeys InboxApiImpl::getEntryDecryptionKeys(thread::server::Message message) { auto inboxId = readInboxIdFromMessageKeyId(message.keyId); - auto inboxMessageServer = unpackInboxOrigMessage(message.data); + auto inboxMessageServer = InboxEntryDataSchemaMapper::unpackInboxOrigMessage(message.data); auto msgData = inboxMessageServer.message; - auto serializer = inbox::InboxEntriesDataEncryptorSerializer::Ptr(new inbox::InboxEntriesDataEncryptorSerializer()); - auto msgPublicData = serializer->unpackMessagePublicOnly(msgData); + auto msgPublicData = _inboxEntryDataSchemaMapper.decryptPublicOnly(msgData); auto keyId = msgPublicData.usedInboxKeyId; return getModuleKeys(inboxId, std::set{keyId}, inbox::InboxDataSchema::VERSION_4); } std::pair InboxApiImpl::getModuleKeysAndVersionFromServer(std::string moduleId) { auto inbox = getServerInbox(moduleId); - assertInboxDataIntegrity(inbox); + _inboxDataSchemaMapper.assertDataIntegrity(inbox); return std::make_pair(inboxToModuleKeys(inbox), inbox.version); } @@ -1114,43 +744,12 @@ core::ModuleKeys InboxApiImpl::inboxToModuleKeys(inbox::server::InboxInfo inbox) return core::ModuleKeys{ .keys = inbox.keys, .currentKeyId = inbox.keyId, - .moduleSchemaVersion = getInboxDataEntryStructureVersion(inbox.data.back()), + .moduleSchemaVersion = _inboxDataSchemaMapper.getDataStructureVersion(inbox.data.back()), .moduleResourceId = inbox.resourceId.value_or(""), .contextId = inbox.contextId }; } -void InboxApiImpl::assertInboxDataIntegrity(inbox::server::InboxInfo inbox) { - auto inbox_data_entry = inbox.data.back(); - switch (getInboxDataEntryStructureVersion(inbox_data_entry)) { - case InboxDataSchema::Version::UNKNOWN: - throw UnknownInboxFormatException(); - case InboxDataSchema::Version::VERSION_4: - return; - case InboxDataSchema::Version::VERSION_5: { - auto inbox_data = inbox_data_entry.data; - auto dio = _inboxDataProcessorV5.getDIOAndAssertIntegrity(inbox_data); - if (dio.contextId != inbox.contextId || - dio.resourceId != inbox.resourceId || - dio.creatorUserId != inbox.lastModifier || - !core::TimestampValidator::validate(dio.timestamp, inbox.lastModificationDate)) { - throw InboxDataIntegrityException(); - } - return; - } - } -} - -uint32_t InboxApiImpl::validateInboxDataIntegrity(inbox::server::InboxInfo inbox) { - try { - assertInboxDataIntegrity(inbox); - return 0; - } catch (const core::Exception& e) { return e.getCode(); } catch (const privmx::utils::PrivmxException& e) { - return core::ExceptionConverter::convert(e).getCode(); - } catch (...) { return ENDPOINT_CORE_EXCEPTION_CODE; } - return UnknownInboxFormatException().getCode(); -} - std::vector InboxApiImpl::subscribeFor(const std::vector& subscriptionQueries) { auto result = _subscriber.subscribeFor(subscriptionQueries); _eventMiddleware->notificationEventListenerAddSubscriptionIds(_notificationListenerId, result); diff --git a/endpoint/inbox/src/InboxEntriesDataEncryptorSerializer.cpp b/endpoint/inbox/src/encryptors/entry/InboxEntryDataEncryptorV1.cpp similarity index 90% rename from endpoint/inbox/src/InboxEntriesDataEncryptorSerializer.cpp rename to endpoint/inbox/src/encryptors/entry/InboxEntryDataEncryptorV1.cpp index 8c3ce3d3..654a7fa4 100644 --- a/endpoint/inbox/src/InboxEntriesDataEncryptorSerializer.cpp +++ b/endpoint/inbox/src/encryptors/entry/InboxEntryDataEncryptorV1.cpp @@ -9,8 +9,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -#include - #include #include #include @@ -18,15 +16,15 @@ limitations under the License. #include #include #include -#include #include +#include "privmx/endpoint/inbox/encryptors/entry/InboxEntryDataEncryptorV1.hpp" + using namespace privmx; using namespace privmx::endpoint; using namespace privmx::endpoint::inbox; -inbox::InboxEntriesDataEncryptorSerializer::InboxEntriesDataEncryptorSerializer() {} -std::string InboxEntriesDataEncryptorSerializer::packMessage( +std::string InboxEntryDataEncryptorV1::encrypt( InboxEntrySendModel data, privmx::crypto::PrivateKey& userPriv, privmx::crypto::PublicKey& inboxPub @@ -55,7 +53,7 @@ std::string InboxEntriesDataEncryptorSerializer::packMessage( return utils::Base64::from(concatBuffer.str()); } -InboxEntryDataResult InboxEntriesDataEncryptorSerializer::unpackMessage( +InboxEntryDataResult InboxEntryDataEncryptorV1::decrypt( std::string& serializedBase64, privmx::crypto::PrivateKey& inboxPriv ) { @@ -93,7 +91,7 @@ InboxEntryDataResult InboxEntriesDataEncryptorSerializer::unpackMessage( return result; } -InboxEntryPublicDataResult InboxEntriesDataEncryptorSerializer::unpackMessagePublicOnly(std::string& serializedBase64) { +InboxEntryPublicDataResult InboxEntryDataEncryptorV1::decryptPublicOnly(std::string& serializedBase64) { InboxEntryPublicDataResult result; try { result.statusCode = 0; @@ -112,4 +110,4 @@ InboxEntryPublicDataResult InboxEntriesDataEncryptorSerializer::unpackMessagePub result.statusCode = core::ExceptionConverter::convert(e).getCode(); } catch (...) { result.statusCode = ENDPOINT_CORE_EXCEPTION_CODE; } return result; -} \ No newline at end of file +} diff --git a/endpoint/inbox/src/encryptors/entry/InboxEntryDataSchemaMapper.cpp b/endpoint/inbox/src/encryptors/entry/InboxEntryDataSchemaMapper.cpp new file mode 100644 index 00000000..97457bf4 --- /dev/null +++ b/endpoint/inbox/src/encryptors/entry/InboxEntryDataSchemaMapper.cpp @@ -0,0 +1,80 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include + +#include + +#include "privmx/endpoint/inbox/InboxException.hpp" +#include "privmx/endpoint/inbox/encryptors/entry/InboxEntryDataSchemaMapper.hpp" + +using namespace privmx; +using namespace privmx::endpoint; +using namespace privmx::endpoint::inbox; + +InboxEntryDataSchemaMapper::InboxEntryDataSchemaMapper( + const std::shared_ptr& keyProvider, + const std::shared_ptr& serverApi, + const store::StoreApi& storeApi +) + : _keyProvider(keyProvider), _strategyV1(serverApi, storeApi) {} + +inbox::server::InboxMessageServer InboxEntryDataSchemaMapper::unpackInboxOrigMessage(const std::string& serialized) { + try { + auto json = utils::Base64::toString(serialized); + return inbox::server::InboxMessageServer::deserialize(json); + } catch (...) { throw FailedToExtractMessagePublicMetaException(); } +} + +std::string InboxEntryDataSchemaMapper::encrypt( + const InboxEntrySendModel& data, + privmx::crypto::PrivateKey& userPriv, + privmx::crypto::PublicKey& inboxPub +) { + return _strategyV1.encrypt(data, userPriv, inboxPub); +} + +InboxEntryDataResult InboxEntryDataSchemaMapper::decrypt(std::string& data, privmx::crypto::PrivateKey& inboxPriv) { + return _strategyV1.decrypt(data, inboxPriv); +} + +InboxEntryPublicDataResult InboxEntryDataSchemaMapper::decryptPublicOnly(std::string& data) { + return _strategyV1.decryptPublicOnly(data); +} + +InboxEntryResult InboxEntryDataSchemaMapper::decryptInboxEntry( + thread::server::Message message, + const core::ModuleKeys& inboxKeys +) { + return _strategyV1.decryptEntry(message, inboxKeys, _keyProvider); +} + +inbox::InboxEntry InboxEntryDataSchemaMapper::convertInboxEntry( + thread::server::Message message, + const InboxEntryResult& inboxEntry +) { + return _strategyV1.convertToFinal(message, inboxEntry, readInboxIdFromMessageKeyId(message.keyId)); +} + +inbox::InboxEntry InboxEntryDataSchemaMapper::decryptAndConvertInboxEntry( + thread::server::Message message, + const core::ModuleKeys& inboxKeys +) { + auto inboxEntry = decryptInboxEntry(message, inboxKeys); + return convertInboxEntry(message, inboxEntry); +} + +std::string InboxEntryDataSchemaMapper::readInboxIdFromMessageKeyId(const std::string& keyId) { + _messageKeyIdFormatValidator.assertKeyIdFormat(keyId); + std::string trimmedKeyId = keyId.substr(1, keyId.size() - 2); + std::vector tmp = utils::Utils::split(trimmedKeyId, "-"); + return tmp[1]; +} diff --git a/endpoint/inbox/src/encryptors/entry/InboxEntryDataSchemaStrategyV1.cpp b/endpoint/inbox/src/encryptors/entry/InboxEntryDataSchemaStrategyV1.cpp new file mode 100644 index 00000000..9b254981 --- /dev/null +++ b/endpoint/inbox/src/encryptors/entry/InboxEntryDataSchemaStrategyV1.cpp @@ -0,0 +1,135 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include +#include +#include +#include +#include +#include + +#include "privmx/endpoint/inbox/InboxException.hpp" +#include "privmx/endpoint/inbox/encryptors/entry/InboxEntryDataSchemaStrategyV1.hpp" + +using namespace privmx; +using namespace privmx::endpoint; +using namespace privmx::endpoint::inbox; + +InboxEntryDataSchemaStrategyV1::InboxEntryDataSchemaStrategyV1( + const std::shared_ptr& serverApi, + const store::StoreApi& storeApi +) + : _serverApi(serverApi), _storeApi(storeApi) {} + +std::string InboxEntryDataSchemaStrategyV1::encrypt( + const InboxEntrySendModel& data, + privmx::crypto::PrivateKey& userPriv, + privmx::crypto::PublicKey& inboxPub +) const { + return _encryptor.encrypt(data, userPriv, inboxPub); +} + +InboxEntryDataResult InboxEntryDataSchemaStrategyV1::decrypt( + std::string& data, + privmx::crypto::PrivateKey& inboxPriv +) const { + return _encryptor.decrypt(data, inboxPriv); +} + +InboxEntryPublicDataResult InboxEntryDataSchemaStrategyV1::decryptPublicOnly(std::string& data) const { + return _encryptor.decryptPublicOnly(data); +} + +InboxEntryResult InboxEntryDataSchemaStrategyV1::decryptEntry( + const thread::server::Message& message, + const core::ModuleKeys& inboxKeys, + const std::shared_ptr& keyProvider +) const { + InboxEntryResult result; + result.statusCode = 0; + try { + inbox::server::InboxMessageServer inboxMessageServer; + try { + inboxMessageServer = inbox::server::InboxMessageServer::deserialize(utils::Base64::toString(message.data)); + } catch (...) { throw FailedToExtractMessagePublicMetaException(); } + auto msgData = inboxMessageServer.message; + + auto msgPublicData = decryptPublicOnly(msgData); + core::KeyDecryptionAndVerificationRequest keyProviderRequest; + core::EncKeyLocation location{.contextId = inboxKeys.contextId, .resourceId = inboxKeys.moduleResourceId}; + keyProviderRequest.addOne(inboxKeys.keys, msgPublicData.usedInboxKeyId, location); + auto encKey = keyProvider->getKeysAndVerify(keyProviderRequest).at(location).at(msgPublicData.usedInboxKeyId); + auto eccKey = crypto::ECC::fromPrivateKey(encKey.key); + auto privKeyECC = crypto::PrivateKey(eccKey); + auto decrypted = decrypt(msgData, privKeyECC); + result.statusCode = encKey.statusCode; + result.publicData = decrypted.publicData; + result.privateData = decrypted.privateData; + result.storeId = inboxMessageServer.store; + result.filesIds = inboxMessageServer.files; + return result; + } catch (const core::Exception& e) { + return makeEmptyResultWithStatusCode(e.getCode()); + } catch (const privmx::utils::PrivmxException& e) { + return makeEmptyResultWithStatusCode(core::ExceptionConverter::convert(e).getCode()); + } catch (...) { return makeEmptyResultWithStatusCode(ENDPOINT_CORE_EXCEPTION_CODE); } +} + +inbox::InboxEntry InboxEntryDataSchemaStrategyV1::convertToFinal( + const thread::server::Message& message, + const InboxEntryResult& raw, + const std::string& inboxId +) const { + inbox::InboxEntry result; + result.entryId = message.id; + result.inboxId = inboxId; + result.createDate = message.createDate; + result.data = core::Buffer::from(raw.privateData.text); + result.authorPubKey = raw.publicData.userPubKey; + result.statusCode = raw.statusCode; + if (raw.statusCode == 0) { + try { + core::DecryptedEncKey fileMetaEncKey{ + core::EncKey{.id = "", .key = raw.privateData.filesMetaKey}, + core::DecryptedVersionedData{.dataStructureVersion = 0, .statusCode = 0} + }; + store::server::StoreFileGetManyModel filesGetModel; + filesGetModel.storeId = raw.storeId; + filesGetModel.fileIds = raw.filesIds; + filesGetModel.failOnError = false; + auto serverFiles{_serverApi->storeFileGetMany(filesGetModel)}; + for (auto file : serverFiles.files) { + if (!file.error.has_value()) { + result.files.push_back( + std::get<0>(_storeApi.getImpl()->getFileMetaDataSchemaMapper().decrypt(file, fileMetaEncKey)) + ); + } else { + store::File error; + auto e = FileFetchFailedException(); + error.statusCode = e.getCode(); + result.files.push_back(error); + } + } + } catch (const core::Exception& e) { + result.statusCode = e.getCode(); + } catch (const privmx::utils::PrivmxException& e) { + result.statusCode = core::ExceptionConverter::convert(e).getCode(); + } catch (...) { result.statusCode = ENDPOINT_CORE_EXCEPTION_CODE; } + } + result.schemaVersion = EntryDataSchema::Version::VERSION_1; + return result; +} + +InboxEntryResult InboxEntryDataSchemaStrategyV1::makeEmptyResultWithStatusCode(int64_t statusCode) { + InboxEntryResult result; + result.statusCode = statusCode; + return result; +} diff --git a/endpoint/inbox/src/encryptors/inbox/InboxDataSchemaMapper.cpp b/endpoint/inbox/src/encryptors/inbox/InboxDataSchemaMapper.cpp new file mode 100644 index 00000000..dbc27b50 --- /dev/null +++ b/endpoint/inbox/src/encryptors/inbox/InboxDataSchemaMapper.cpp @@ -0,0 +1,253 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "privmx/endpoint/inbox/encryptors/inbox/InboxDataSchemaMapper.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "privmx/endpoint/inbox/InboxException.hpp" + +using namespace privmx::endpoint; +using namespace privmx::endpoint::inbox; + +InboxDataSchemaMapper::InboxDataSchemaMapper( + const privmx::crypto::PrivateKey& userPrivKey, + const core::Connection& connection +) + : _userPrivKey(userPrivKey), _connection(connection) { + _strategyV4 = std::make_shared(); + _strategyMapper.registerStrategy(InboxDataSchema::Version::VERSION_4, _strategyV4); + _strategyV5 = std::make_shared(); + _strategyMapper.registerStrategy(InboxDataSchema::Version::VERSION_5, _strategyV5); +} + +server::InboxData InboxDataSchemaMapper::encrypt(const InboxDataProcessorModelV5& data, const std::string& key) { + return _strategyV5->packForServer(data, _userPrivKey, key); +} + +std::tuple InboxDataSchemaMapper::decrypt( + const server::InboxInfo& inbox, + const core::DecryptedEncKey& encKey +) { + auto version = getDataStructureVersion(inbox.data.back()); + auto strategy = _strategyMapper.getStrategy(static_cast(version)); + if (!strategy) { + auto e = UnknownInboxFormatException(); + return { + toLibInbox(inbox, {}, {}, {}, e.getCode(), InboxDataSchema::Version::UNKNOWN), core::DataIntegrityObject{} + }; + } + return strategy->decryptAndConvert(inbox, encKey); +} + +InboxDataSchema::Version InboxDataSchemaMapper::getDataStructureVersion(const server::InboxDataEntry& entry) { + if (entry.data.meta.type() == typeid(Poco::JSON::Object::Ptr)) { + auto versioned = core::dynamic::VersionedData::fromJSON(entry.data.meta); + switch (versioned.version) { + case InboxDataSchema::Version::VERSION_4: + return InboxDataSchema::Version::VERSION_4; + case InboxDataSchema::Version::VERSION_5: + return InboxDataSchema::Version::VERSION_5; + default: + return InboxDataSchema::Version::UNKNOWN; + } + } + return InboxDataSchema::Version::UNKNOWN; +} + +void InboxDataSchemaMapper::assertDataIntegrity(const server::InboxInfo& inbox) { + const auto& entry = inbox.data.back(); + switch (getDataStructureVersion(entry)) { + case InboxDataSchema::Version::UNKNOWN: + throw UnknownInboxFormatException(); + case InboxDataSchema::Version::VERSION_4: + return; + case InboxDataSchema::Version::VERSION_5: { + auto dio = _strategyV5->getDIOAndAssertIntegrity(entry.data); + if (dio.contextId != inbox.contextId || + dio.resourceId != inbox.resourceId || + dio.creatorUserId != inbox.lastModifier || + !core::TimestampValidator::validate(dio.timestamp, inbox.lastModificationDate)) { + throw InboxDataIntegrityException(); + } + return; + } + default: + throw UnknownInboxFormatException(); + } +} + +uint32_t InboxDataSchemaMapper::validateDataIntegrity(const server::InboxInfo& inbox) { + try { + assertDataIntegrity(inbox); + return 0; + } catch (const core::Exception& e) { return e.getCode(); } catch (const privmx::utils::PrivmxException& e) { + return core::ExceptionConverter::convert(e).getCode(); + } catch (...) { return ENDPOINT_CORE_EXCEPTION_CODE; } +} + +InboxPublicViewData InboxDataSchemaMapper::getPublicViewData(const server::InboxGetPublicViewResult& publicView) { + InboxPublicViewData result; + if (publicView.publicData.type() == typeid(Poco::JSON::Object::Ptr)) { + auto versioned = core::dynamic::VersionedData::fromJSON(publicView.publicData); + switch (versioned.version) { + case InboxDataSchema::Version::VERSION_4: + return _strategyV4->getPublicViewData(publicView); + case InboxDataSchema::Version::VERSION_5: + return _strategyV5->getPublicViewData(publicView); + } + } + auto e = UnknownInboxFormatException(); + result.statusCode = e.getCode(); + return result; +} + +InboxInternalMetaV5 InboxDataSchemaMapper::decryptInternalMeta( + const server::InboxDataEntry& entry, + const core::DecryptedEncKey& encKey +) { + switch (getDataStructureVersion(entry)) { + case InboxDataSchema::Version::UNKNOWN: + throw UnknownInboxFormatException(); + case InboxDataSchema::Version::VERSION_4: + return _strategyV4->decryptInternalMeta(entry, encKey); + case InboxDataSchema::Version::VERSION_5: + return _strategyV5->decryptInternalMeta(entry, encKey); + default: + throw UnknownInboxFormatException(); + } +} + +std::vector InboxDataSchemaMapper::validateDecryptAndConvertInboxes( + const std::vector& inboxes, + const std::shared_ptr& keyProvider +) { + if (inboxes.size() == 0) { + return std::vector{}; + } + std::vector result(inboxes.size()); + std::vector inboxesDIO(inboxes.size()); + // integrity validation + for (size_t i = 0; i < inboxes.size(); i++) { + result[i].statusCode = validateDataIntegrity(inboxes[i]); + if (result[i].statusCode != 0) { + result[i] = toLibInbox(inboxes[i], {}, {}, {}, result[i].statusCode, InboxDataSchema::Version::UNKNOWN); + } else { + result[i].statusCode = 0; + } + } + // batch key fetch + core::KeyDecryptionAndVerificationRequest keyRequest; + for (size_t i = 0; i < inboxes.size(); i++) { + if (result[i].statusCode != 0) { + continue; + } + auto& inbox = inboxes[i]; + core::EncKeyLocation location{.contextId = inbox.contextId, .resourceId = inbox.resourceId.value_or("")}; + keyRequest.addOne(inbox.keys, inbox.data.back().keyId, location); + } + auto inboxesKeys = keyProvider->getKeysAndVerify(keyRequest); + std::set seenRandomIds; + // decrypt + deduplication + for (size_t i = 0; i < inboxes.size(); i++) { + if (result[i].statusCode != 0) { + continue; + } + auto& inbox = inboxes[i]; + try { + auto inboxKeysIt = inboxesKeys.find( + core::EncKeyLocation{.contextId = inbox.contextId, .resourceId = inbox.resourceId.value_or("")} + ); + if (inboxKeysIt == inboxesKeys.end()) { + throw UnknownInboxFormatException(); + } + auto [decryptedInbox, dio] = decrypt(inbox, inboxKeysIt->second.at(inbox.data.back().keyId)); + result[i] = decryptedInbox; + inboxesDIO[i] = dio; + if (!seenRandomIds.insert(dio.randomId + "-" + std::to_string(dio.timestamp)).second) { + result[i].statusCode = core::DataIntegrityObjectDuplicatedException().getCode(); + } + } catch (const core::Exception& e) { + result[i] = toLibInbox(inbox, {}, {}, {}, e.getCode(), InboxDataSchema::Version::UNKNOWN); + } catch (const privmx::utils::PrivmxException& e) { + result[i] = toLibInbox( + inbox, {}, {}, {}, core::ExceptionConverter::convert(e).getCode(), InboxDataSchema::Version::UNKNOWN + ); + } catch (...) { + result[i] = toLibInbox(inbox, {}, {}, {}, ENDPOINT_CORE_EXCEPTION_CODE, InboxDataSchema::Version::UNKNOWN); + } + } + // batch identity verification + std::vector verifyRequests; + std::vector verifyIndices; + for (size_t i = 0; i < result.size(); i++) { + if (result[i].statusCode != 0) { + continue; + } + verifyRequests.push_back( + {.contextId = result[i].contextId, + .senderId = result[i].lastModifier, + .senderPubKey = inboxesDIO[i].creatorPubKey, + .date = result[i].lastModificationDate, + .bridgeIdentity = inboxesDIO[i].bridgeIdentity} + ); + verifyIndices.push_back(i); + } + auto verified = _connection.getImpl()->getUserVerifier()->verify(verifyRequests); + for (size_t j = 0; j < verifyIndices.size(); j++) { + result[verifyIndices[j]].statusCode = verified[j] ? + 0 : + core::ExceptionConverter::getCodeOfUserVerificationFailureException(); + } + return result; +} + +Inbox InboxDataSchemaMapper::validateDecryptAndConvertInbox( + const server::InboxInfo& inbox, + const std::shared_ptr& keyProvider +) { + return validateDecryptAndConvertInboxes({inbox}, keyProvider)[0]; +} + +Inbox InboxDataSchemaMapper::toLibInbox( + const server::InboxInfo& info, + const core::Buffer& publicMeta, + const core::Buffer& privateMeta, + const std::optional& filesConfig, + int64_t statusCode, + int64_t schemaVersion +) { + return Inbox{ + .inboxId = info.id, + .contextId = info.contextId, + .createDate = info.createDate, + .creator = info.creator, + .lastModificationDate = info.lastModificationDate, + .lastModifier = info.lastModifier, + .users = info.users, + .managers = info.managers, + .version = info.version, + .publicMeta = publicMeta, + .privateMeta = privateMeta, + .filesConfig = filesConfig, + .policy = core::Factory::parsePolicyServerObject(info.policy), + .statusCode = statusCode, + .schemaVersion = schemaVersion + }; +} diff --git a/endpoint/inbox/src/encryptors/inbox/InboxDataSchemaStrategyV4.cpp b/endpoint/inbox/src/encryptors/inbox/InboxDataSchemaStrategyV4.cpp new file mode 100644 index 00000000..35584933 --- /dev/null +++ b/endpoint/inbox/src/encryptors/inbox/InboxDataSchemaStrategyV4.cpp @@ -0,0 +1,118 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "privmx/endpoint/inbox/encryptors/inbox/InboxDataSchemaStrategyV4.hpp" + +#include +#include +#include + +#include "privmx/endpoint/inbox/Constants.hpp" + +using namespace privmx::endpoint; +using namespace privmx::endpoint::inbox; + +InboxDataResultV4 InboxDataSchemaStrategyV4::decrypt( + const server::InboxInfo& inbox, + const core::DecryptedEncKey& encKey +) const { + return _processor.unpackAll(inbox.data.back().data, encKey.key); +} + +std::tuple InboxDataSchemaStrategyV4::convert( + const server::InboxInfo& inbox, + const InboxDataResultV4& raw +) const { + return { + Inbox{ + .inboxId = inbox.id, + .contextId = inbox.contextId, + .createDate = inbox.createDate, + .creator = inbox.creator, + .lastModificationDate = inbox.lastModificationDate, + .lastModifier = inbox.lastModifier, + .users = inbox.users, + .managers = inbox.managers, + .version = inbox.version, + .publicMeta = raw.publicData.publicMeta, + .privateMeta = raw.privateData.privateMeta, + .filesConfig = raw.filesConfig, + .policy = core::Factory::parsePolicyServerObject(inbox.policy), + .statusCode = raw.statusCode, + .schemaVersion = InboxDataSchema::Version::VERSION_4 + }, + core::DataIntegrityObject{ + .creatorUserId = inbox.lastModifier, + .creatorPubKey = raw.privateData.authorPubKey, + .contextId = inbox.contextId, + .resourceId = inbox.resourceId.value_or(""), + .timestamp = inbox.lastModificationDate, + .randomId = std::string(), + .containerId = std::nullopt, + .containerResourceId = std::nullopt, + .bridgeIdentity = std::nullopt + } + }; +} + +InboxPublicDataV4AsResult InboxDataSchemaStrategyV4::unpackPublicOnly(const Poco::Dynamic::Var& publicData) const { + return _processor.unpackPublicOnly(publicData); +} + +InboxPublicViewData InboxDataSchemaStrategyV4::getPublicViewData( + const server::InboxGetPublicViewResult& publicView +) const { + auto publicData = unpackPublicOnly(publicView.publicData); + InboxPublicViewData result; + result.authorPubKey = publicData.authorPubKey; + result.publicMeta = publicData.publicMeta; + result.inboxEntriesPubKeyBase58DER = publicData.inboxEntriesPubKeyBase58DER; + result.inboxEntriesKeyId = publicData.inboxEntriesKeyId; + result.inboxId = publicView.inboxId; + result.resourceId = ""; + result.version = publicView.version; + result.dataStructureVersion = publicData.dataStructureVersion; + result.statusCode = publicData.statusCode; + return result; +} + +InboxInternalMetaV5 InboxDataSchemaStrategyV4::decryptInternalMeta( + const server::InboxDataEntry& /*entry*/, + const core::DecryptedEncKey& /*encKey*/ +) const { + return InboxInternalMetaV5(); +} + +std::tuple InboxDataSchemaStrategyV4::makeErrorResult( + const server::InboxInfo& inbox, + int64_t errorCode +) const { + return { + Inbox{ + .inboxId = inbox.id, + .contextId = inbox.contextId, + .createDate = inbox.createDate, + .creator = inbox.creator, + .lastModificationDate = inbox.lastModificationDate, + .lastModifier = inbox.lastModifier, + .users = inbox.users, + .managers = inbox.managers, + .version = inbox.version, + .publicMeta = {}, + .privateMeta = {}, + .filesConfig = {}, + .policy = core::Factory::parsePolicyServerObject(inbox.policy), + .statusCode = errorCode, + .schemaVersion = InboxDataSchema::Version::VERSION_4 + }, + core::DataIntegrityObject{} + }; +} diff --git a/endpoint/inbox/src/encryptors/inbox/InboxDataSchemaStrategyV5.cpp b/endpoint/inbox/src/encryptors/inbox/InboxDataSchemaStrategyV5.cpp new file mode 100644 index 00000000..493f31b5 --- /dev/null +++ b/endpoint/inbox/src/encryptors/inbox/InboxDataSchemaStrategyV5.cpp @@ -0,0 +1,120 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "privmx/endpoint/inbox/encryptors/inbox/InboxDataSchemaStrategyV5.hpp" + +#include +#include +#include + +#include "privmx/endpoint/inbox/Constants.hpp" + +using namespace privmx::endpoint; +using namespace privmx::endpoint::inbox; + +InboxDataResultV5 InboxDataSchemaStrategyV5::decrypt( + const server::InboxInfo& inbox, + const core::DecryptedEncKey& encKey +) const { + return _processor.unpackAll(inbox.data.back().data, encKey.key); +} + +std::tuple InboxDataSchemaStrategyV5::convert( + const server::InboxInfo& inbox, + const InboxDataResultV5& raw +) const { + return { + Inbox{ + .inboxId = inbox.id, + .contextId = inbox.contextId, + .createDate = inbox.createDate, + .creator = inbox.creator, + .lastModificationDate = inbox.lastModificationDate, + .lastModifier = inbox.lastModifier, + .users = inbox.users, + .managers = inbox.managers, + .version = inbox.version, + .publicMeta = raw.publicData.publicMeta, + .privateMeta = raw.privateData.privateMeta, + .filesConfig = raw.filesConfig, + .policy = core::Factory::parsePolicyServerObject(inbox.policy), + .statusCode = raw.statusCode, + .schemaVersion = InboxDataSchema::Version::VERSION_5 + }, + raw.privateData.dio + }; +} + +std::tuple InboxDataSchemaStrategyV5::makeErrorResult( + const server::InboxInfo& inbox, + int64_t errorCode +) const { + return { + Inbox{ + .inboxId = inbox.id, + .contextId = inbox.contextId, + .createDate = inbox.createDate, + .creator = inbox.creator, + .lastModificationDate = inbox.lastModificationDate, + .lastModifier = inbox.lastModifier, + .users = inbox.users, + .managers = inbox.managers, + .version = inbox.version, + .publicMeta = {}, + .privateMeta = {}, + .filesConfig = {}, + .policy = core::Factory::parsePolicyServerObject(inbox.policy), + .statusCode = errorCode, + .schemaVersion = InboxDataSchema::Version::VERSION_5 + }, + core::DataIntegrityObject{} + }; +} + +InboxPublicDataV5AsResult InboxDataSchemaStrategyV5::unpackPublicOnly(const Poco::Dynamic::Var& publicData) const { + return _processor.unpackPublicOnly(publicData); +} + +InboxPublicViewData InboxDataSchemaStrategyV5::getPublicViewData( + const server::InboxGetPublicViewResult& publicView +) const { + auto publicData = unpackPublicOnly(publicView.publicData); + InboxPublicViewData result; + result.authorPubKey = publicData.authorPubKey; + result.publicMeta = publicData.publicMeta; + result.inboxEntriesPubKeyBase58DER = publicData.inboxEntriesPubKeyBase58DER; + result.inboxEntriesKeyId = publicData.inboxEntriesKeyId; + result.inboxId = publicView.inboxId; + result.resourceId = ""; + result.version = publicView.version; + result.dataStructureVersion = publicData.dataStructureVersion; + result.statusCode = publicData.statusCode; + return result; +} + +InboxInternalMetaV5 InboxDataSchemaStrategyV5::decryptInternalMeta( + const server::InboxDataEntry& entry, + const core::DecryptedEncKey& encKey +) const { + return _processor.unpackAll(entry.data, encKey.key).privateData.internalMeta; +} + +core::DataIntegrityObject InboxDataSchemaStrategyV5::getDIOAndAssertIntegrity(const server::InboxData& data) const { + return _processor.getDIOAndAssertIntegrity(data); +} + +server::InboxData InboxDataSchemaStrategyV5::packForServer( + const InboxDataProcessorModelV5& data, + const privmx::crypto::PrivateKey& authorPrivateKey, + const std::string& inboxKey +) const { + return _processor.packForServer(data, authorPrivateKey, inboxKey); +} diff --git a/endpoint/kvdb/include/privmx/endpoint/kvdb/KvdbApiImpl.hpp b/endpoint/kvdb/include/privmx/endpoint/kvdb/KvdbApiImpl.hpp index 586d5f04..8152772c 100644 --- a/endpoint/kvdb/include/privmx/endpoint/kvdb/KvdbApiImpl.hpp +++ b/endpoint/kvdb/include/privmx/endpoint/kvdb/KvdbApiImpl.hpp @@ -22,7 +22,6 @@ limitations under the License. #include #include #include -#include #include #include "privmx/endpoint/core/Factory.hpp" @@ -31,7 +30,8 @@ limitations under the License. #include "privmx/endpoint/kvdb/KvdbApi.hpp" #include "privmx/endpoint/kvdb/ServerApi.hpp" #include "privmx/endpoint/kvdb/SubscriberImpl.hpp" -#include "privmx/endpoint/kvdb/encryptors/entry/EntryDataEncryptorV5.hpp" +#include "privmx/endpoint/kvdb/encryptors/entry/EntryDataSchemaMapper.hpp" +#include "privmx/endpoint/kvdb/encryptors/kvdb/KvdbDataSchemaMapper.hpp" #include namespace privmx { @@ -122,53 +122,15 @@ class KvdbApiImpl : public privmx::utils::ManualManagedClass, prote void processDisconnectedEvent(); std::vector mapUsers(const std::vector& users); - Kvdb convertServerKvdbToLibKvdb( - server::KvdbInfo kvdb, - const core::Buffer& publicMeta = core::Buffer(), - const core::Buffer& privateMeta = core::Buffer(), - const int64_t& statusCode = 0, - const int64_t& schemaVersion = KvdbDataSchema::Version::UNKNOWN - ); - Kvdb convertDecryptedKvdbDataV5ToKvdb(server::KvdbInfo kvdbInfo, const core::DecryptedModuleDataV5& kvdbData); KvdbDataSchema::Version getKvdbDataEntryStructureVersion(server::KvdbDataEntry kvdbEntry); std::tuple decryptAndConvertKvdbDataToKvdb( server::KvdbInfo kvdb, - server::KvdbDataEntry kvdbEntry, const core::DecryptedEncKey& encKey ); - std::vector validateDecryptAndConvertKvdbsDataToKvdbs(std::vector kvdbs); - Kvdb validateDecryptAndConvertKvdbDataToKvdb(server::KvdbInfo kvdb); - void assertKvdbDataIntegrity(server::KvdbInfo kvdb); - uint32_t validateKvdbDataIntegrity(server::KvdbInfo kvdb); virtual std::pair getModuleKeysAndVersionFromServer(std::string moduleId) override; core::ModuleKeys kvdbToModuleKeys(server::KvdbInfo kvdb); - DecryptedKvdbEntryDataV5 decryptKvdbEntryDataV5(server::KvdbEntryInfo entry, const core::DecryptedEncKey& encKey); - KvdbEntry convertDecryptedKvdbEntryDataV5ToKvdbEntry( - server::KvdbEntryInfo entry, - DecryptedKvdbEntryDataV5 entryData - ); - KvdbEntry convertServerKvdbEntryToLibKvdbEntry( - server::KvdbEntryInfo entry, - const core::Buffer& publicMeta = core::Buffer(), - const core::Buffer& privateMeta = core::Buffer(), - const core::Buffer& data = core::Buffer(), - const std::string& authorPubKey = std::string(), - const int64_t& statusCode = 0, - const int64_t& schemaVersion = KvdbEntryDataSchema::Version::UNKNOWN - ); - KvdbEntryDataSchema::Version getEntryDataStructureVersion(server::KvdbEntryInfo entry); - std::tuple decryptAndConvertEntryDataToEntry( - server::KvdbEntryInfo entry, - const core::DecryptedEncKey& encKey - ); - std::vector validateDecryptAndConvertKvdbEntriesDataToKvdbEntries( - std::vector entries, - const core::ModuleKeys& kvdbKeys - ); - KvdbEntry validateDecryptAndConvertEntryDataToEntry(server::KvdbEntryInfo entry, const core::ModuleKeys& kvdbKeys); core::ModuleKeys getEntryDecryptionKeys(server::KvdbEntryInfo entry); - uint32_t validateEntryDataIntegrity(server::KvdbEntryInfo entry, const std::string& kvdbResourceId); Poco::Dynamic::Var encryptEntryData( const std::string& kvdbId, const std::string& resourceId, @@ -196,8 +158,8 @@ class KvdbApiImpl : public privmx::utils::ManualManagedClass, prote core::Connection _connection; ServerApi _serverApi; SubscriberImpl _subscriber; - core::ModuleDataEncryptorV5 _kvdbDataEncryptorV5; - EntryDataEncryptorV5 _entryDataEncryptorV5; + KvdbDataSchemaMapper _kvdbDataSchemaMapper; + EntryDataSchemaMapper _entryDataSchemaMapper; int _notificationListenerId, _connectedListenerId, _disconnectedListenerId; inline static const std::string KVDB_TYPE_FILTER_FLAG = "kvdb"; }; diff --git a/endpoint/kvdb/include/privmx/endpoint/kvdb/encryptors/entry/EntryDataSchemaMapper.hpp b/endpoint/kvdb/include/privmx/endpoint/kvdb/encryptors/entry/EntryDataSchemaMapper.hpp new file mode 100644 index 00000000..a725726c --- /dev/null +++ b/endpoint/kvdb/include/privmx/endpoint/kvdb/encryptors/entry/EntryDataSchemaMapper.hpp @@ -0,0 +1,101 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef _PRIVMXLIB_ENDPOINT_KVDB_ENTRYDATASCHEMAMAPPER_HPP_ +#define _PRIVMXLIB_ENDPOINT_KVDB_ENTRYDATASCHEMAMAPPER_HPP_ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "privmx/endpoint/kvdb/Constants.hpp" +#include "privmx/endpoint/kvdb/KvdbTypes.hpp" +#include "privmx/endpoint/kvdb/ServerTypes.hpp" +#include "privmx/endpoint/kvdb/Types.hpp" +#include "privmx/endpoint/kvdb/encryptors/entry/EntryDataEncryptorV5.hpp" +#include "privmx/endpoint/kvdb/encryptors/entry/EntryDataSchemaStrategyV5.hpp" + +namespace privmx { +namespace endpoint { +namespace kvdb { + +class EntryDataSchemaMapper { +public: + EntryDataSchemaMapper(const privmx::crypto::PrivateKey& userPrivKey, const core::Connection& connection); + + Poco::Dynamic::Var encrypt( + const std::string& kvdbId, + const std::string& resourceId, + const std::string& contextId, + const std::string& moduleResourceId, + const core::Buffer& publicMeta, + const core::Buffer& privateMeta, + const core::Buffer& data, + const core::DecryptedEncKeyV2& entryKey + ); + + std::tuple decrypt( + const server::KvdbEntryInfo& entry, + const core::DecryptedEncKey& encKey + ); + + KvdbEntryDataSchema::Version getDataStructureVersion(const server::KvdbEntryInfo& entry); + + uint32_t validateEntryDataIntegrity(const server::KvdbEntryInfo& entry, const std::string& kvdbResourceId); + + KvdbEntry validateDecryptAndConvertEntryDataToEntry( + const server::KvdbEntryInfo& entry, + const core::ModuleKeys& kvdbKeys, + const std::shared_ptr& keyProvider + ); + + std::vector validateDecryptAndConvertKvdbEntriesDataToKvdbEntries( + const std::vector& entries, + const core::ModuleKeys& kvdbKeys, + const std::shared_ptr& keyProvider + ); + + static KvdbEntry toLibKvdbEntry( + const server::KvdbEntryInfo& entry, + const core::Buffer& publicMeta, + const core::Buffer& privateMeta, + const core::Buffer& data, + const std::string& authorPubKey, + int64_t statusCode, + int64_t schemaVersion + ); + +private: + privmx::crypto::PrivateKey _userPrivKey; + core::Connection _connection; + core::VersionStrategyMapper> + _strategyMapper; + std::shared_ptr _strategyV5; + EntryDataEncryptorV5 _encryptorV5; +}; + +} // namespace kvdb +} // namespace endpoint +} // namespace privmx + +#endif // _PRIVMXLIB_ENDPOINT_KVDB_ENTRYDATAENCRYPTORMAPPER_HPP_ diff --git a/endpoint/kvdb/include/privmx/endpoint/kvdb/encryptors/entry/EntryDataSchemaStrategyV5.hpp b/endpoint/kvdb/include/privmx/endpoint/kvdb/encryptors/entry/EntryDataSchemaStrategyV5.hpp new file mode 100644 index 00000000..eb246411 --- /dev/null +++ b/endpoint/kvdb/include/privmx/endpoint/kvdb/encryptors/entry/EntryDataSchemaStrategyV5.hpp @@ -0,0 +1,60 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef _PRIVMXLIB_ENDPOINT_KVDB_ENTRYDATASCHEMASTRATEGYV5_HPP_ +#define _PRIVMXLIB_ENDPOINT_KVDB_ENTRYDATASCHEMASTRATEGYV5_HPP_ + +#include + +#include +#include + +#include "privmx/endpoint/kvdb/Constants.hpp" +#include "privmx/endpoint/kvdb/KvdbTypes.hpp" +#include "privmx/endpoint/kvdb/ServerTypes.hpp" +#include "privmx/endpoint/kvdb/Types.hpp" +#include "privmx/endpoint/kvdb/encryptors/entry/EntryDataEncryptorV5.hpp" + +namespace privmx { +namespace endpoint { +namespace kvdb { + +// clang-format off +class EntryDataSchemaStrategyV5 : public core::TypedDataSchemaStrategy< + server::KvdbEntryInfo, + DecryptedKvdbEntryDataV5, + std::tuple +> { + // clang-format on +public: + DecryptedKvdbEntryDataV5 decrypt( + const server::KvdbEntryInfo& entry, + const core::DecryptedEncKey& encKey + ) const override; + std::tuple convert( + const server::KvdbEntryInfo& entry, + const DecryptedKvdbEntryDataV5& raw + ) const override; + std::tuple makeErrorResult( + const server::KvdbEntryInfo& entry, + int64_t errorCode + ) const override; + core::DataIntegrityObject getDIOAndAssertIntegrity(const server::EncryptedKvdbEntryDataV5& encData) const; + +private: + mutable EntryDataEncryptorV5 _encryptor; +}; + +} // namespace kvdb +} // namespace endpoint +} // namespace privmx + +#endif // _PRIVMXLIB_ENDPOINT_KVDB_ENTRYDATASCHEMASTRATEGYV5_HPP_ diff --git a/endpoint/kvdb/include/privmx/endpoint/kvdb/encryptors/kvdb/KvdbDataSchemaMapper.hpp b/endpoint/kvdb/include/privmx/endpoint/kvdb/encryptors/kvdb/KvdbDataSchemaMapper.hpp new file mode 100644 index 00000000..5121a238 --- /dev/null +++ b/endpoint/kvdb/include/privmx/endpoint/kvdb/encryptors/kvdb/KvdbDataSchemaMapper.hpp @@ -0,0 +1,88 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef _PRIVMXLIB_ENDPOINT_KVDB_KVDBDATASCHEMAMAPPER_HPP_ +#define _PRIVMXLIB_ENDPOINT_KVDB_KVDBDATASCHEMAMAPPER_HPP_ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "privmx/endpoint/kvdb/Constants.hpp" +#include "privmx/endpoint/kvdb/ServerTypes.hpp" +#include "privmx/endpoint/kvdb/Types.hpp" +#include "privmx/endpoint/kvdb/encryptors/kvdb/KvdbDataSchemaStrategyV5.hpp" + +namespace privmx { +namespace endpoint { +namespace kvdb { + +class KvdbDataSchemaMapper { +public: + KvdbDataSchemaMapper(const privmx::crypto::PrivateKey& userPrivKey, const core::Connection& connection); + + Poco::Dynamic::Var encrypt(const core::ModuleDataToEncryptV5& data, const std::string& key); + + std::tuple decrypt( + const server::KvdbInfo& kvdb, + const core::DecryptedEncKey& encKey + ); + + KvdbDataSchema::Version getDataStructureVersion(const server::KvdbDataEntry& entry); + + void assertDataIntegrity(const server::KvdbInfo& kvdb); + + uint32_t validateDataIntegrity(const server::KvdbInfo& kvdb); + + std::vector validateDecryptAndConvertKvdbs( + const std::vector& kvdbs, + const std::shared_ptr& keyProvider + ); + + Kvdb validateDecryptAndConvertKvdb( + const server::KvdbInfo& kvdb, + const std::shared_ptr& keyProvider + ); + + static Kvdb toLibKvdb( + const server::KvdbInfo& info, + const core::Buffer& publicMeta, + const core::Buffer& privateMeta, + int64_t statusCode, + int64_t schemaVersion + ); + +private: + privmx::crypto::PrivateKey _userPrivKey; + core::Connection _connection; + core::VersionStrategyMapper> _strategyMapper; + std::shared_ptr _strategyV5; + core::ModuleDataEncryptorV5 _encryptorV5; +}; + +} // namespace kvdb +} // namespace endpoint +} // namespace privmx + +#endif // _PRIVMXLIB_ENDPOINT_KVDB_KVDBDATAENCRYPTORMAPPER_HPP_ diff --git a/endpoint/kvdb/include/privmx/endpoint/kvdb/encryptors/kvdb/KvdbDataSchemaStrategyV5.hpp b/endpoint/kvdb/include/privmx/endpoint/kvdb/encryptors/kvdb/KvdbDataSchemaStrategyV5.hpp new file mode 100644 index 00000000..90513220 --- /dev/null +++ b/endpoint/kvdb/include/privmx/endpoint/kvdb/encryptors/kvdb/KvdbDataSchemaStrategyV5.hpp @@ -0,0 +1,61 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef _PRIVMXLIB_ENDPOINT_KVDB_KVDBDATASCHEMASTRATEGYV5_HPP_ +#define _PRIVMXLIB_ENDPOINT_KVDB_KVDBDATASCHEMASTRATEGYV5_HPP_ + +#include + +#include +#include +#include +#include +#include + +#include "privmx/endpoint/kvdb/Constants.hpp" +#include "privmx/endpoint/kvdb/ServerTypes.hpp" +#include "privmx/endpoint/kvdb/Types.hpp" + +namespace privmx { +namespace endpoint { +namespace kvdb { + +// clang-format off +class KvdbDataSchemaStrategyV5 : public core::TypedDataSchemaStrategy< + server::KvdbInfo, + core::DecryptedModuleDataV5, + std::tuple +> { + // clang-format on +public: + core::DecryptedModuleDataV5 decrypt( + const server::KvdbInfo& kvdb, + const core::DecryptedEncKey& encKey + ) const override; + std::tuple convert( + const server::KvdbInfo& kvdb, + const core::DecryptedModuleDataV5& raw + ) const override; + std::tuple makeErrorResult( + const server::KvdbInfo& kvdb, + int64_t errorCode + ) const override; + core::DataIntegrityObject getDIOAndAssertIntegrity(const core::dynamic::EncryptedModuleDataV5& encData) const; + +private: + mutable core::ModuleDataEncryptorV5 _encryptor; +}; + +} // namespace kvdb +} // namespace endpoint +} // namespace privmx + +#endif // _PRIVMXLIB_ENDPOINT_KVDB_KVDBDATASCHEMASTRATEGYV5_HPP_ diff --git a/endpoint/kvdb/src/KvdbApiImpl.cpp b/endpoint/kvdb/src/KvdbApiImpl.cpp index eca08393..33f72930 100644 --- a/endpoint/kvdb/src/KvdbApiImpl.cpp +++ b/endpoint/kvdb/src/KvdbApiImpl.cpp @@ -44,7 +44,8 @@ KvdbApiImpl::KvdbApiImpl( ) : ModuleBaseApi(userPrivKey, keyProvider, host, eventMiddleware, connection), _gateway(gateway), _userPrivKey(userPrivKey), _keyProvider(keyProvider), _host(host), _eventMiddleware(eventMiddleware), - _connection(connection), _serverApi(ServerApi(gateway)), _subscriber(gateway, KVDB_TYPE_FILTER_FLAG) { + _connection(connection), _serverApi(ServerApi(gateway)), _subscriber(gateway, KVDB_TYPE_FILTER_FLAG), + _kvdbDataSchemaMapper(userPrivKey, connection), _entryDataSchemaMapper(userPrivKey, connection) { _notificationListenerId = _eventMiddleware->addNotificationEventListener( std::bind(&KvdbApiImpl::processNotificationEvent, this, std::placeholders::_1, std::placeholders::_2) ); @@ -101,7 +102,7 @@ std::string KvdbApiImpl::createKvdbEx( create_kvdb_model.resourceId = resourceId; create_kvdb_model.contextId = contextId; create_kvdb_model.keyId = kvdbKey.id; - create_kvdb_model.data = _kvdbDataEncryptorV5.encrypt(kvdbDataToEncrypt, _userPrivKey, kvdbKey.key).toJSON(); + create_kvdb_model.data = _kvdbDataSchemaMapper.encrypt(kvdbDataToEncrypt, kvdbKey.key); auto allUsers = core::EndpointUtils::uniqueListUserWithPubKey(users, managers); create_kvdb_model.keys = _keyProvider->prepareKeysList( allUsers, kvdbKey, kvdbDIO, {.contextId = contextId, .resourceId = resourceId}, kvdbSecret @@ -203,7 +204,7 @@ void KvdbApiImpl::updateKvdb( }, .dio = updateKvdbDio }; - model.data = _kvdbDataEncryptorV5.encrypt(kvdbDataToEncrypt, _userPrivKey, kvdbKey.key).toJSON(); + model.data = _kvdbDataSchemaMapper.encrypt(kvdbDataToEncrypt, kvdbKey.key); PRIVMX_DEBUG_TIME_CHECKPOINT(PlatformKvdb, updateKvdb, data encrypted) _serverApi.kvdbUpdate(model); @@ -232,7 +233,7 @@ Kvdb KvdbApiImpl::getKvdbEx(const std::string& kvdbId, const std::string& type) auto kvdb = _serverApi.kvdbGet(params).kvdb; PRIVMX_DEBUG_TIME_CHECKPOINT(PlatformKvdb, _getKvdbEx, data send) setNewModuleKeysInCache(kvdb.id, kvdbToModuleKeys(kvdb), kvdb.version); - auto result = validateDecryptAndConvertKvdbDataToKvdb(kvdb); + auto result = _kvdbDataSchemaMapper.validateDecryptAndConvertKvdb(kvdb, _keyProvider); PRIVMX_DEBUG_TIME_CHECKPOINT(PlatformKvdb, _getKvdbEx, data decrypted) return result; } @@ -259,7 +260,7 @@ core::PagingList KvdbApiImpl::listKvdbsEx( for (auto kvdb : kvdbsList.kvdbs) { setNewModuleKeysInCache(kvdb.id, kvdbToModuleKeys(kvdb), kvdb.version); } - std::vector kvdbs = validateDecryptAndConvertKvdbsDataToKvdbs(kvdbsList.kvdbs); + std::vector kvdbs = _kvdbDataSchemaMapper.validateDecryptAndConvertKvdbs(kvdbsList.kvdbs, _keyProvider); PRIVMX_DEBUG_TIME_STOP(PlatformKvdb, _listKvdbsEx, data decrypted) return core::PagingList({.totalAvailable = kvdbsList.count, .readItems = kvdbs}); } @@ -272,7 +273,9 @@ KvdbEntry KvdbApiImpl::getEntry(const std::string& kvdbId, const std::string& ke PRIVMX_DEBUG_TIME_CHECKPOINT(PlatformKvdb, getEntry, data recived); KvdbEntry result; PRIVMX_DEBUG_TIME_CHECKPOINT(PlatformKvdb, getEntry, getting kvdb) - result = validateDecryptAndConvertEntryDataToEntry(entry, getEntryDecryptionKeys(entry)); + result = _entryDataSchemaMapper.validateDecryptAndConvertEntryDataToEntry( + entry, getEntryDecryptionKeys(entry), _keyProvider + ); PRIVMX_DEBUG_TIME_STOP(PlatformKvdb, getEntry, data decrypted) return result; } @@ -320,11 +323,11 @@ core::PagingList KvdbApiImpl::listEntries(const std::string& kvdbId, auto entriesList = _serverApi.kvdbListEntries(model); PRIVMX_DEBUG_TIME_CHECKPOINT(PlatformKvdb, listEntriesKeys, data send) auto kvdb = entriesList.kvdb; - assertKvdbDataIntegrity(kvdb); + _kvdbDataSchemaMapper.assertDataIntegrity(kvdb); setNewModuleKeysInCache(kvdb.id, kvdbToModuleKeys(kvdb), kvdb.version); PRIVMX_DEBUG_TIME_CHECKPOINT(PlatformKvdb, listEntriesKeys, data send) - auto entries = validateDecryptAndConvertKvdbEntriesDataToKvdbEntries( - entriesList.kvdbEntries, kvdbToModuleKeys(kvdb) + auto entries = _entryDataSchemaMapper.validateDecryptAndConvertKvdbEntriesDataToKvdbEntries( + entriesList.kvdbEntries, kvdbToModuleKeys(kvdb), _keyProvider ); PRIVMX_DEBUG_TIME_STOP(PlatformKvdb, listEntriesKeys, data decrypted) return core::PagingList({.totalAvailable = entriesList.count, .readItems = entries}); @@ -412,7 +415,9 @@ void KvdbApiImpl::processNotificationEvent(const std::string& type, const core:: auto raw = server::KvdbInfo::fromJSON(notification.data); if (raw.type.value_or(std::string(KVDB_TYPE_FILTER_FLAG)) == KVDB_TYPE_FILTER_FLAG) { setNewModuleKeysInCache(raw.id, kvdbToModuleKeys(raw), raw.version); - privmx::endpoint::kvdb::Kvdb data = validateDecryptAndConvertKvdbDataToKvdb(raw); + privmx::endpoint::kvdb::Kvdb data = _kvdbDataSchemaMapper.validateDecryptAndConvertKvdb( + raw, _keyProvider + ); auto event = core::EventBuilder::buildEvent("kvdb", data, notification); _eventMiddleware->emitApiEvent(event); } @@ -420,7 +425,9 @@ void KvdbApiImpl::processNotificationEvent(const std::string& type, const core:: auto raw = server::KvdbInfo::fromJSON(notification.data); if (raw.type.value_or(std::string(KVDB_TYPE_FILTER_FLAG)) == KVDB_TYPE_FILTER_FLAG) { setNewModuleKeysInCache(raw.id, kvdbToModuleKeys(raw), raw.version); - privmx::endpoint::kvdb::Kvdb data = validateDecryptAndConvertKvdbDataToKvdb(raw); + privmx::endpoint::kvdb::Kvdb data = _kvdbDataSchemaMapper.validateDecryptAndConvertKvdb( + raw, _keyProvider + ); auto event = core::EventBuilder::buildEvent("kvdb", data, notification); _eventMiddleware->emitApiEvent(event); } @@ -442,7 +449,9 @@ void KvdbApiImpl::processNotificationEvent(const std::string& type, const core:: } else if (type == "kvdbNewEntry") { auto raw = server::KvdbEntryEventData::fromJSON(notification.data); if (raw.containerType.value_or(std::string(KVDB_TYPE_FILTER_FLAG)) == KVDB_TYPE_FILTER_FLAG) { - auto data = validateDecryptAndConvertEntryDataToEntry(raw, getEntryDecryptionKeys(raw)); + auto data = _entryDataSchemaMapper.validateDecryptAndConvertEntryDataToEntry( + raw, getEntryDecryptionKeys(raw), _keyProvider + ); auto event = core::EventBuilder::buildEvent( "kvdb/" + raw.kvdbId + "/entries", data, notification ); @@ -451,7 +460,9 @@ void KvdbApiImpl::processNotificationEvent(const std::string& type, const core:: } else if (type == "kvdbUpdatedEntry") { auto raw = server::KvdbEntryEventData::fromJSON(notification.data); if (raw.containerType.value_or(std::string(KVDB_TYPE_FILTER_FLAG)) == KVDB_TYPE_FILTER_FLAG) { - auto data = validateDecryptAndConvertEntryDataToEntry(raw, getEntryDecryptionKeys(raw)); + auto data = _entryDataSchemaMapper.validateDecryptAndConvertEntryDataToEntry( + raw, getEntryDecryptionKeys(raw), _keyProvider + ); auto event = core::EventBuilder::buildEvent( "kvdb/" + raw.kvdbId + "/entries", data, notification ); @@ -497,436 +508,21 @@ std::vector KvdbApiImpl::mapUsers(const std::vector users; - std::vector managers; - for (auto x : kvdb.users) { - users.push_back(x); - } - for (auto x : kvdb.managers) { - managers.push_back(x); - } - return Kvdb{ - .contextId = kvdb.contextId, - .kvdbId = kvdb.id, - .createDate = kvdb.createDate, - .creator = kvdb.creator, - .lastModificationDate = kvdb.lastModificationDate, - .lastModifier = kvdb.lastModifier, - .users = users, - .managers = managers, - .version = kvdb.version, - .publicMeta = publicMeta, - .privateMeta = privateMeta, - .entries = kvdb.entries, - .lastEntryDate = kvdb.lastEntryDate, - .policy = core::Factory::parsePolicyServerObject(kvdb.policy), - .statusCode = statusCode, - .schemaVersion = schemaVersion - }; -} - -Kvdb KvdbApiImpl::convertDecryptedKvdbDataV5ToKvdb( - server::KvdbInfo kvdbInfo, - const core::DecryptedModuleDataV5& kvdbData -) { - return convertServerKvdbToLibKvdb( - kvdbInfo, kvdbData.publicMeta, kvdbData.privateMeta, kvdbData.statusCode, KvdbDataSchema::Version::VERSION_5 - ); -} - KvdbDataSchema::Version KvdbApiImpl::getKvdbDataEntryStructureVersion(server::KvdbDataEntry kvdbEntry) { - if (kvdbEntry.data.type() == typeid(Poco::JSON::Object::Ptr)) { - auto versioned = core::dynamic::VersionedData::fromJSON(kvdbEntry.data); - auto version = versioned.version; - switch (version) { - case core::ModuleDataSchema::Version::VERSION_5: - return KvdbDataSchema::Version::VERSION_5; - default: - return KvdbDataSchema::Version::UNKNOWN; - } - } - return KvdbDataSchema::Version::UNKNOWN; + return _kvdbDataSchemaMapper.getDataStructureVersion(kvdbEntry); } std::tuple KvdbApiImpl::decryptAndConvertKvdbDataToKvdb( server::KvdbInfo kvdb, - server::KvdbDataEntry kvdbEntry, - const core::DecryptedEncKey& encKey -) { - switch (getKvdbDataEntryStructureVersion(kvdbEntry)) { - case KvdbDataSchema::Version::UNKNOWN: { - auto e = UnknownKvdbFormatException(); - return std::make_tuple(convertServerKvdbToLibKvdb(kvdb, {}, {}, e.getCode()), core::DataIntegrityObject()); - } - case KvdbDataSchema::Version::VERSION_5: { - auto decryptedKvdbData = decryptModuleDataV5(kvdbEntry, encKey); - return std::make_tuple(convertDecryptedKvdbDataV5ToKvdb(kvdb, decryptedKvdbData), decryptedKvdbData.dio); - } - } - auto e = UnknownKvdbFormatException(); - return std::make_tuple(convertServerKvdbToLibKvdb(kvdb, {}, {}, e.getCode()), core::DataIntegrityObject()); -} - -std::vector KvdbApiImpl::validateDecryptAndConvertKvdbsDataToKvdbs(std::vector kvdbs) { - // Create Result Array - std::vector result(kvdbs.size()); - // Validate data Integrity - for (size_t i = 0; i < kvdbs.size(); i++) { - auto kvdb = kvdbs[i]; - result[i].statusCode = validateKvdbDataIntegrity(kvdb); - if (result[i].statusCode != 0) { - result[i] = convertServerKvdbToLibKvdb(kvdb, {}, {}, result[i].statusCode); - } - } - core::KeyDecryptionAndVerificationRequest keyProviderRequest; - // Create request to KeyProvider for keys - for (size_t i = 0; i < kvdbs.size(); i++) { - auto kvdb = kvdbs[i]; - core::EncKeyLocation location{.contextId = kvdb.contextId, .resourceId = kvdb.resourceId}; - auto kvdb_data_entry = kvdb.data.back(); - keyProviderRequest.addOne(kvdb.keys, kvdb_data_entry.keyId, location); - } - // Send request to KeyProvider - auto kvdbsKeys{_keyProvider->getKeysAndVerify(keyProviderRequest)}; - std::vector kvdbsDIO(kvdbs.size()); - std::map duplication_check; - for (size_t i = 0; i < kvdbs.size(); i++) { - if (result[i].statusCode != 0) { - kvdbsDIO.push_back(core::DataIntegrityObject{}); - } else { - auto kvdb = kvdbs[i]; - try { - auto tmp = decryptAndConvertKvdbDataToKvdb( - kvdb, kvdb.data.back(), - kvdbsKeys.at(core::EncKeyLocation{.contextId = kvdb.contextId, .resourceId = kvdb.resourceId}) - .at(kvdb.data.back().keyId) - ); - result[i] = std::get<0>(tmp); - auto kvdbDIO = std::get<1>(tmp); - kvdbsDIO[i] = kvdbDIO; - //find duplication - std::string fullRandomId = kvdbDIO.randomId + "-" + std::to_string(kvdbDIO.timestamp); - if (duplication_check.find(fullRandomId) == duplication_check.end()) { - duplication_check.insert(std::make_pair(fullRandomId, true)); - } else { - result[i].statusCode = core::DataIntegrityObjectDuplicatedException().getCode(); - } - } catch (const core::Exception& e) { - result[i] = convertServerKvdbToLibKvdb(kvdb, {}, {}, e.getCode()); - kvdbsDIO[i] = core::DataIntegrityObject{}; - } - } - } - std::vector verifierInput{}; - for (size_t i = 0; i < result.size(); i++) { - if (result[i].statusCode == 0) { - verifierInput.push_back( - core::VerificationRequest{ - .contextId = result[i].contextId, - .senderId = result[i].lastModifier, - .senderPubKey = kvdbsDIO[i].creatorPubKey, - .date = result[i].lastModificationDate, - .bridgeIdentity = kvdbsDIO[i].bridgeIdentity - } - ); - } - } - std::vector verified; - verified = _connection.getImpl()->getUserVerifier()->verify(verifierInput); - for (size_t j = 0, i = 0; i < result.size(); i++) { - if (result[i].statusCode == 0) { - result[i].statusCode = verified[j] ? 0 : - core::ExceptionConverter::getCodeOfUserVerificationFailureException(); - j++; - } - } - return result; -} - -Kvdb KvdbApiImpl::validateDecryptAndConvertKvdbDataToKvdb(server::KvdbInfo kvdb) { - // Validate data Integrity - auto statusCode = validateKvdbDataIntegrity(kvdb); - if (statusCode != 0) { - return convertServerKvdbToLibKvdb(kvdb, {}, {}, statusCode); - } - // Get current KvdbEntry and Key - auto kvdb_data_entry = kvdb.data.back(); - // Create request to KeyProvider for keys - core::KeyDecryptionAndVerificationRequest keyProviderRequest; - core::EncKeyLocation location{.contextId = kvdb.contextId, .resourceId = kvdb.resourceId}; - keyProviderRequest.addOne(kvdb.keys, kvdb_data_entry.keyId, location); - //Send request to KeyProvider - auto key = _keyProvider->getKeysAndVerify(keyProviderRequest).at(location).at(kvdb_data_entry.keyId); - Kvdb result; - core::DataIntegrityObject kvdbDIO; - // Decrypt - std::tie(result, kvdbDIO) = decryptAndConvertKvdbDataToKvdb(kvdb, kvdb_data_entry, key); - // Validate with UserVerifier - if (result.statusCode != 0) - return result; - std::vector verifierInput{}; - verifierInput.push_back( - core::VerificationRequest{ - .contextId = result.contextId, - .senderId = result.lastModifier, - .senderPubKey = kvdbDIO.creatorPubKey, - .date = result.lastModificationDate, - .bridgeIdentity = kvdbDIO.bridgeIdentity - } - ); - std::vector verified; - verified = _connection.getImpl()->getUserVerifier()->verify(verifierInput); - result.statusCode = verified[0] ? 0 : core::ExceptionConverter::getCodeOfUserVerificationFailureException(); - return result; -} - -DecryptedKvdbEntryDataV5 KvdbApiImpl::decryptKvdbEntryDataV5( - server::KvdbEntryInfo entry, - const core::DecryptedEncKey& encKey -) { - try { - auto encryptedEntryData = server::EncryptedKvdbEntryDataV5::fromJSON(entry.kvdbEntryValue); - if (encKey.statusCode != 0) { - auto tmp = _entryDataEncryptorV5.extractPublic(encryptedEntryData); - tmp.statusCode = encKey.statusCode; - return tmp; - } - return _entryDataEncryptorV5.decrypt(encryptedEntryData, encKey.key); - } catch (const core::Exception& e) { - return DecryptedKvdbEntryDataV5{ - {.dataStructureVersion = core::ModuleDataSchema::Version::VERSION_5, .statusCode = e.getCode()}, - {}, - {}, - {}, - {}, - {}, - {} - }; - } catch (const privmx::utils::PrivmxException& e) { - return DecryptedKvdbEntryDataV5{ - {.dataStructureVersion = core::ModuleDataSchema::Version::VERSION_5, - .statusCode = core::ExceptionConverter::convert(e).getCode()}, - {}, - {}, - {}, - {}, - {}, - {} - }; - } catch (...) { - return DecryptedKvdbEntryDataV5{ - {.dataStructureVersion = core::ModuleDataSchema::Version::VERSION_5, - .statusCode = ENDPOINT_CORE_EXCEPTION_CODE}, - {}, - {}, - {}, - {}, - {}, - {} - }; - } -} - -KvdbEntry KvdbApiImpl::convertServerKvdbEntryToLibKvdbEntry( - server::KvdbEntryInfo entry, - const core::Buffer& publicMeta, - const core::Buffer& privateMeta, - const core::Buffer& data, - const std::string& authorPubKey, - const int64_t& statusCode, - const int64_t& schemaVersion -) { - return KvdbEntry{ - .info = - { - .kvdbId = entry.kvdbId, - .key = entry.kvdbEntryKey, - .createDate = entry.createDate, - .author = entry.author, - }, - .publicMeta = publicMeta, - .privateMeta = privateMeta, - .data = data, - .authorPubKey = authorPubKey, - .version = entry.version, - .statusCode = statusCode, - .schemaVersion = schemaVersion - }; -} - -KvdbEntry KvdbApiImpl::convertDecryptedKvdbEntryDataV5ToKvdbEntry( - server::KvdbEntryInfo entry, - DecryptedKvdbEntryDataV5 entryData -) { - return convertServerKvdbEntryToLibKvdbEntry( - entry, entryData.publicMeta, entryData.privateMeta, entryData.data, entryData.authorPubKey, - entryData.statusCode, KvdbEntryDataSchema::Version::VERSION_5 - ); -} - -KvdbEntryDataSchema::Version KvdbApiImpl::getEntryDataStructureVersion(server::KvdbEntryInfo entry) { - if (entry.kvdbEntryValue.type() == typeid(Poco::JSON::Object::Ptr)) { - auto versioned = core::dynamic::VersionedData::fromJSON(entry.kvdbEntryValue); - auto version = versioned.version; - switch (version) { - case core::ModuleDataSchema::Version::VERSION_5: - return KvdbEntryDataSchema::Version::VERSION_5; - default: - return KvdbEntryDataSchema::Version::UNKNOWN; - } - } - return KvdbEntryDataSchema::Version::UNKNOWN; -} - -std::tuple KvdbApiImpl::decryptAndConvertEntryDataToEntry( - server::KvdbEntryInfo entry, const core::DecryptedEncKey& encKey ) { - switch (getEntryDataStructureVersion(entry)) { - case KvdbEntryDataSchema::Version::UNKNOWN: { - auto e = UnknownKvdbEntryFormatException(); - return std::make_tuple( - convertServerKvdbEntryToLibKvdbEntry(entry, {}, {}, {}, {}, e.getCode()), core::DataIntegrityObject() - ); - } - case KvdbEntryDataSchema::Version::VERSION_5: { - auto decryptEntry = decryptKvdbEntryDataV5(entry, encKey); - return std::make_tuple(convertDecryptedKvdbEntryDataV5ToKvdbEntry(entry, decryptEntry), decryptEntry.dio); - } - } - auto e = UnknownKvdbEntryFormatException(); - return std::make_tuple( - convertServerKvdbEntryToLibKvdbEntry(entry, {}, {}, {}, {}, e.getCode()), core::DataIntegrityObject() - ); -} - -std::vector KvdbApiImpl::validateDecryptAndConvertKvdbEntriesDataToKvdbEntries( - std::vector entries, - const core::ModuleKeys& kvdbKeys -) { - std::set keyIds; - for (auto entry : entries) { - keyIds.insert(entry.keyId); - } - core::KeyDecryptionAndVerificationRequest keyProviderRequest; - core::EncKeyLocation location{.contextId = kvdbKeys.contextId, .resourceId = kvdbKeys.moduleResourceId}; - keyProviderRequest.addMany(kvdbKeys.keys, keyIds, location); - auto keyMap = _keyProvider->getKeysAndVerify(keyProviderRequest).at(location); - std::vector result; - std::vector entriesDIO; - std::map duplication_check; - for (auto entry : entries) { - try { - auto statusCode = validateEntryDataIntegrity(entry, kvdbKeys.moduleResourceId); - if (statusCode == 0) { - auto tmp = decryptAndConvertEntryDataToEntry(entry, keyMap.at(entry.keyId)); - result.push_back(std::get<0>(tmp)); - auto entryDIO = std::get<1>(tmp); - entriesDIO.push_back(entryDIO); - //find duplication - std::string fullRandomId = entryDIO.randomId + "-" + std::to_string(entryDIO.timestamp); - if (duplication_check.find(fullRandomId) == duplication_check.end()) { - duplication_check.insert(std::make_pair(fullRandomId, true)); - } else { - result[result.size() - 1].statusCode = core::DataIntegrityObjectDuplicatedException().getCode(); - } - } else { - result.push_back(convertServerKvdbEntryToLibKvdbEntry(entry, {}, {}, {}, {}, statusCode)); - } - } catch (const core::Exception& e) { - result.push_back(convertServerKvdbEntryToLibKvdbEntry(entry, {}, {}, {}, {}, e.getCode())); - } catch (const privmx::utils::PrivmxException& e) { - result.push_back(convertServerKvdbEntryToLibKvdbEntry( - entry, {}, {}, {}, {}, core::ExceptionConverter::convert(e).getCode() - )); - } catch (...) { - result.push_back(convertServerKvdbEntryToLibKvdbEntry(entry, {}, {}, {}, {}, ENDPOINT_CORE_EXCEPTION_CODE)); - } - } - std::vector verifierInput{}; - for (size_t i = 0; i < result.size(); i++) { - if (result[i].statusCode == 0) { - verifierInput.push_back( - core::VerificationRequest{ - .contextId = kvdbKeys.contextId, - .senderId = result[i].info.author, - .senderPubKey = result[i].authorPubKey, - .date = result[i].info.createDate, - .bridgeIdentity = entriesDIO[i].bridgeIdentity - } - ); - } - } - std::vector verified; - verified = _connection.getImpl()->getUserVerifier()->verify(verifierInput); - for (size_t j = 0, i = 0; i < result.size(); ++i) { - if (result[i].statusCode == 0) { - result[i].statusCode = verified[j] ? 0 : - core::ExceptionConverter::getCodeOfUserVerificationFailureException(); - j++; - } - } - return result; -} - -KvdbEntry KvdbApiImpl::validateDecryptAndConvertEntryDataToEntry( - server::KvdbEntryInfo entry, - const core::ModuleKeys& kvdbKeys -) { - try { - auto keyId = entry.keyId; - // Validate data Integrity - auto statusCode = validateEntryDataIntegrity(entry, kvdbKeys.moduleResourceId); - if (statusCode != 0) { - return convertServerKvdbEntryToLibKvdbEntry(entry, {}, {}, {}, {}, statusCode); - } - // Create request to KeyProvider for keys - core::KeyDecryptionAndVerificationRequest keyProviderRequest; - core::EncKeyLocation location{.contextId = entry.contextId, .resourceId = kvdbKeys.moduleResourceId}; - keyProviderRequest.addOne(kvdbKeys.keys, keyId, location); - // Send request to KeyProvider - auto encKey = _keyProvider->getKeysAndVerify(keyProviderRequest).at(location).at(keyId); - // decrypt entry - KvdbEntry result; - core::DataIntegrityObject entryDIO; - std::tie(result, entryDIO) = decryptAndConvertEntryDataToEntry(entry, encKey); - if (result.statusCode != 0) - return result; - // Validate with UserVerifier - std::vector verifierInput{}; - verifierInput.push_back( - core::VerificationRequest{ - .contextId = entry.contextId, - .senderId = result.info.author, - .senderPubKey = result.authorPubKey, - .date = result.info.createDate, - .bridgeIdentity = entryDIO.bridgeIdentity - } - ); - std::vector verified; - verified = _connection.getImpl()->getUserVerifier()->verify(verifierInput); - result.statusCode = verified[0] ? 0 : core::ExceptionConverter::getCodeOfUserVerificationFailureException(); - return result; - } catch (const core::Exception& e) { - return convertServerKvdbEntryToLibKvdbEntry(entry, {}, {}, {}, {}, e.getCode()); - } catch (const privmx::utils::PrivmxException& e) { - return convertServerKvdbEntryToLibKvdbEntry( - entry, {}, {}, {}, {}, core::ExceptionConverter::convert(e).getCode() - ); - } catch (...) { return convertServerKvdbEntryToLibKvdbEntry(entry, {}, {}, {}, {}, ENDPOINT_CORE_EXCEPTION_CODE); } + return _kvdbDataSchemaMapper.decrypt(kvdb, encKey); } core::ModuleKeys KvdbApiImpl::getEntryDecryptionKeys(server::KvdbEntryInfo entry) { auto keyId = entry.keyId; kvdb::KvdbDataSchema::Version minimumKvdbSchemaVersion; - switch (getEntryDataStructureVersion(entry)) { + switch (_entryDataSchemaMapper.getDataStructureVersion(entry)) { case kvdb::KvdbEntryDataSchema::Version::UNKNOWN: minimumKvdbSchemaVersion = kvdb::KvdbDataSchema::UNKNOWN; break; @@ -946,26 +542,9 @@ Poco::Dynamic::Var KvdbApiImpl::encryptEntryData( const core::ModuleKeys& kvdbKeys ) { core::DecryptedEncKeyV2 msgKey = getAndValidateModuleCurrentEncKey(kvdbKeys); - switch (msgKey.dataStructureVersion) { - case core::EncryptionKeyDataSchema::Version::UNKNOWN: - case core::EncryptionKeyDataSchema::Version::VERSION_1: - throw UnknownKvdbFormatException(); - case core::EncryptionKeyDataSchema::Version::VERSION_2: { - auto entryDIO = _connection.getImpl()->createDIO( - kvdbKeys.contextId, resourceId, kvdbId, kvdbKeys.moduleResourceId - ); - KvdbEntryDataToEncryptV5 entryData{ - .publicMeta = publicMeta, - .privateMeta = privateMeta, - .data = data, - .internalMeta = std::nullopt, - .dio = entryDIO - }; - auto encryptedEntryData = _entryDataEncryptorV5.encrypt(entryData, _userPrivKey, msgKey.key); - return encryptedEntryData.toJSON(); - } - } - throw UnknownKvdbFormatException(); + return _entryDataSchemaMapper.encrypt( + kvdbId, resourceId, kvdbKeys.contextId, kvdbKeys.moduleResourceId, publicMeta, privateMeta, data, msgKey + ); } void KvdbApiImpl::assertKvdbExist(const std::string& kvdbId) { @@ -977,7 +556,7 @@ std::pair KvdbApiImpl::getModuleKeysAndVersionFromSer kvdb::server::KvdbGetModel params{.kvdbId = moduleId, .type = std::nullopt}; auto kvdb = _serverApi.kvdbGet(params).kvdb; // validate kvdb Data before returning data - assertKvdbDataIntegrity(kvdb); + _kvdbDataSchemaMapper.assertDataIntegrity(kvdb); return std::make_pair(kvdbToModuleKeys(kvdb), kvdb.version); } @@ -991,64 +570,6 @@ core::ModuleKeys KvdbApiImpl::kvdbToModuleKeys(server::KvdbInfo kvdb) { }; } -void KvdbApiImpl::assertKvdbDataIntegrity(server::KvdbInfo kvdb) { - auto kvdb_data_entry = kvdb.data.back(); - switch (getKvdbDataEntryStructureVersion(kvdb_data_entry)) { - case KvdbDataSchema::Version::UNKNOWN: - throw UnknownKvdbFormatException(); - case KvdbDataSchema::Version::VERSION_5: { - auto kvdb_data = core::dynamic::EncryptedModuleDataV5::fromJSON(kvdb_data_entry.data); - auto dio = _kvdbDataEncryptorV5.getDIOAndAssertIntegrity(kvdb_data); - if (dio.contextId != kvdb.contextId || - dio.resourceId != kvdb.resourceId || - dio.creatorUserId != kvdb.lastModifier || - !core::TimestampValidator::validate(dio.timestamp, kvdb.lastModificationDate)) { - throw KvdbDataIntegrityException(); - } - return; - } - } - throw UnknownKvdbFormatException(); -} - -uint32_t KvdbApiImpl::validateKvdbDataIntegrity(server::KvdbInfo kvdb) { - try { - assertKvdbDataIntegrity(kvdb); - return 0; - } catch (const core::Exception& e) { return e.getCode(); } catch (const privmx::utils::PrivmxException& e) { - return core::ExceptionConverter::convert(e).getCode(); - ; - } catch (...) { return ENDPOINT_CORE_EXCEPTION_CODE; } - return UnknownKvdbFormatException().getCode(); -} - -uint32_t KvdbApiImpl::validateEntryDataIntegrity(server::KvdbEntryInfo entry, const std::string& kvdbResourceId) { - try { - switch (getEntryDataStructureVersion(entry)) { - case KvdbEntryDataSchema::Version::UNKNOWN: - return UnknownKvdbEntryFormatException().getCode(); - case KvdbEntryDataSchema::Version::VERSION_5: { - auto encData = server::EncryptedKvdbEntryDataV5::fromJSON(entry.kvdbEntryValue); - auto dio = _entryDataEncryptorV5.getDIOAndAssertIntegrity(encData); - if (dio.contextId != entry.contextId || - dio.resourceId != entry.kvdbEntryKey || - !dio.containerId.has_value() || - dio.containerId.value() != entry.kvdbId || - !dio.containerResourceId.has_value() || - dio.containerResourceId.value() != kvdbResourceId || - dio.creatorUserId != entry.lastModifier || - !core::TimestampValidator::validate(dio.timestamp, entry.lastModificationDate)) { - return KvdbEntryDataIntegrityException().getCode(); - } - return 0; - } - } - } catch (const core::Exception& e) { return e.getCode(); } catch (const privmx::utils::PrivmxException& e) { - return core::ExceptionConverter::convert(e).getCode(); - } catch (...) { return ENDPOINT_CORE_EXCEPTION_CODE; } - return UnknownKvdbEntryFormatException().getCode(); -} - std::vector KvdbApiImpl::subscribeFor(const std::vector& subscriptionQueries) { auto result = _subscriber.subscribeFor(subscriptionQueries); _eventMiddleware->notificationEventListenerAddSubscriptionIds(_notificationListenerId, result); diff --git a/endpoint/kvdb/src/encryptors/entry/EntryDataSchemaMapper.cpp b/endpoint/kvdb/src/encryptors/entry/EntryDataSchemaMapper.cpp new file mode 100644 index 00000000..e347847e --- /dev/null +++ b/endpoint/kvdb/src/encryptors/entry/EntryDataSchemaMapper.cpp @@ -0,0 +1,238 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "privmx/endpoint/kvdb/encryptors/entry/EntryDataSchemaMapper.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "privmx/endpoint/kvdb/KvdbException.hpp" + +using namespace privmx::endpoint; +using namespace privmx::endpoint::kvdb; + +EntryDataSchemaMapper::EntryDataSchemaMapper( + const privmx::crypto::PrivateKey& userPrivKey, + const core::Connection& connection +) + : _userPrivKey(userPrivKey), _connection(connection) { + _strategyV5 = std::make_shared(); + _strategyMapper.registerStrategy(KvdbEntryDataSchema::Version::VERSION_5, _strategyV5); +} + +Poco::Dynamic::Var EntryDataSchemaMapper::encrypt( + const std::string& kvdbId, + const std::string& resourceId, + const std::string& contextId, + const std::string& moduleResourceId, + const core::Buffer& publicMeta, + const core::Buffer& privateMeta, + const core::Buffer& data, + const core::DecryptedEncKeyV2& entryKey +) { + switch (entryKey.dataStructureVersion) { + case core::EncryptionKeyDataSchema::Version::UNKNOWN: + case core::EncryptionKeyDataSchema::Version::VERSION_1: + throw UnknownKvdbEntryFormatException(); + case core::EncryptionKeyDataSchema::Version::VERSION_2: { + auto entryDIO = _connection.getImpl()->createDIO(contextId, resourceId, kvdbId, moduleResourceId); + KvdbEntryDataToEncryptV5 entryData{ + .publicMeta = publicMeta, + .privateMeta = privateMeta, + .data = data, + .internalMeta = std::nullopt, + .dio = entryDIO + }; + return _encryptorV5.encrypt(entryData, _userPrivKey, entryKey.key).toJSON(); + } + } + throw UnknownKvdbEntryFormatException(); +} + +std::tuple EntryDataSchemaMapper::decrypt( + const server::KvdbEntryInfo& entry, + const core::DecryptedEncKey& encKey +) { + auto version = getDataStructureVersion(entry); + auto strategy = _strategyMapper.getStrategy(static_cast(version)); + if (!strategy) { + auto e = UnknownKvdbEntryFormatException(); + return { + toLibKvdbEntry(entry, {}, {}, {}, {}, e.getCode(), KvdbEntryDataSchema::Version::UNKNOWN), + core::DataIntegrityObject{} + }; + } + return strategy->decryptAndConvert(entry, encKey); +} + +KvdbEntryDataSchema::Version EntryDataSchemaMapper::getDataStructureVersion(const server::KvdbEntryInfo& entry) { + if (entry.kvdbEntryValue.type() == typeid(Poco::JSON::Object::Ptr)) { + auto versioned = core::dynamic::VersionedData::fromJSON(entry.kvdbEntryValue); + switch (versioned.version) { + case KvdbEntryDataSchema::Version::VERSION_5: + return KvdbEntryDataSchema::Version::VERSION_5; + default: + return KvdbEntryDataSchema::Version::UNKNOWN; + } + } + return KvdbEntryDataSchema::Version::UNKNOWN; +} + +uint32_t EntryDataSchemaMapper::validateEntryDataIntegrity( + const server::KvdbEntryInfo& entry, + const std::string& kvdbResourceId +) { + try { + switch (getDataStructureVersion(entry)) { + case KvdbEntryDataSchema::Version::UNKNOWN: + return UnknownKvdbEntryFormatException().getCode(); + case KvdbEntryDataSchema::Version::VERSION_5: { + auto encData = server::EncryptedKvdbEntryDataV5::fromJSON(entry.kvdbEntryValue); + auto dio = _strategyV5->getDIOAndAssertIntegrity(encData); + if (dio.contextId != entry.contextId || + dio.resourceId != entry.kvdbEntryKey || + !dio.containerId.has_value() || + dio.containerId.value() != entry.kvdbId || + !dio.containerResourceId.has_value() || + dio.containerResourceId.value() != kvdbResourceId || + dio.creatorUserId != entry.lastModifier || + !core::TimestampValidator::validate(dio.timestamp, entry.lastModificationDate)) { + return KvdbEntryDataIntegrityException().getCode(); + } + return 0; + } + default: + return UnknownKvdbEntryFormatException().getCode(); + } + } catch (const core::Exception& e) { return e.getCode(); } catch (const privmx::utils::PrivmxException& e) { + return core::ExceptionConverter::convert(e).getCode(); + } catch (...) { return ENDPOINT_CORE_EXCEPTION_CODE; } +} + +std::vector EntryDataSchemaMapper::validateDecryptAndConvertKvdbEntriesDataToKvdbEntries( + const std::vector& entries, + const core::ModuleKeys& kvdbKeys, + const std::shared_ptr& keyProvider +) { + if (entries.empty()) { + return {}; + } + + std::vector result(entries.size()); + std::vector entriesDIO(entries.size()); + std::set seenRandomIds; + + for (size_t i = 0; i < entries.size(); i++) { + auto code = validateEntryDataIntegrity(entries[i], kvdbKeys.moduleResourceId); + if (code != 0) { + result[i] = toLibKvdbEntry(entries[i], {}, {}, {}, {}, code, KvdbEntryDataSchema::Version::UNKNOWN); + } + } + + const core::EncKeyLocation location{.contextId = kvdbKeys.contextId, .resourceId = kvdbKeys.moduleResourceId}; + core::KeyDecryptionAndVerificationRequest keyRequest; + for (size_t i = 0; i < entries.size(); i++) { + if (result[i].statusCode != 0) { + continue; + } + keyRequest.addOne(kvdbKeys.keys, entries[i].keyId, location); + } + auto keysResult = keyProvider->getKeysAndVerify(keyRequest); + auto keyMapIt = keysResult.find(location); + + for (size_t i = 0; i < entries.size(); i++) { + if (result[i].statusCode != 0) { + continue; + } + try { + auto [decryptedEntry, dio] = decrypt(entries[i], keyMapIt->second.at(entries[i].keyId)); + result[i] = decryptedEntry; + entriesDIO[i] = dio; + if (!seenRandomIds.insert(dio.randomId + "-" + std::to_string(dio.timestamp)).second) { + result[i].statusCode = core::DataIntegrityObjectDuplicatedException().getCode(); + } + } catch (const core::Exception& e) { + result[i] = toLibKvdbEntry(entries[i], {}, {}, {}, {}, e.getCode(), KvdbEntryDataSchema::Version::UNKNOWN); + } catch (const privmx::utils::PrivmxException& e) { + result[i] = toLibKvdbEntry( + entries[i], {}, {}, {}, {}, core::ExceptionConverter::convert(e).getCode(), + KvdbEntryDataSchema::Version::UNKNOWN + ); + } catch (...) { + result[i] = toLibKvdbEntry( + entries[i], {}, {}, {}, {}, ENDPOINT_CORE_EXCEPTION_CODE, KvdbEntryDataSchema::Version::UNKNOWN + ); + } + } + + std::vector verifyRequests; + std::vector verifyIndices; + for (size_t i = 0; i < result.size(); i++) { + if (result[i].statusCode != 0) { + continue; + } + verifyRequests.push_back( + {.contextId = kvdbKeys.contextId, + .senderId = result[i].info.author, + .senderPubKey = result[i].authorPubKey, + .date = result[i].info.createDate, + .bridgeIdentity = entriesDIO[i].bridgeIdentity} + ); + verifyIndices.push_back(i); + } + auto verified = _connection.getImpl()->getUserVerifier()->verify(verifyRequests); + for (size_t j = 0; j < verifyIndices.size(); j++) { + result[verifyIndices[j]].statusCode = verified[j] ? + 0 : + core::ExceptionConverter::getCodeOfUserVerificationFailureException(); + } + return result; +} + +KvdbEntry EntryDataSchemaMapper::validateDecryptAndConvertEntryDataToEntry( + const server::KvdbEntryInfo& entry, + const core::ModuleKeys& kvdbKeys, + const std::shared_ptr& keyProvider +) { + return validateDecryptAndConvertKvdbEntriesDataToKvdbEntries({entry}, kvdbKeys, keyProvider)[0]; +} + +KvdbEntry EntryDataSchemaMapper::toLibKvdbEntry( + const server::KvdbEntryInfo& entry, + const core::Buffer& publicMeta, + const core::Buffer& privateMeta, + const core::Buffer& data, + const std::string& authorPubKey, + int64_t statusCode, + int64_t schemaVersion +) { + return KvdbEntry{ + .info = + { + .kvdbId = entry.kvdbId, + .key = entry.kvdbEntryKey, + .createDate = entry.createDate, + .author = entry.author, + }, + .publicMeta = publicMeta, + .privateMeta = privateMeta, + .data = data, + .authorPubKey = authorPubKey, + .version = entry.version, + .statusCode = statusCode, + .schemaVersion = schemaVersion + }; +} diff --git a/endpoint/kvdb/src/encryptors/entry/EntryDataSchemaStrategyV5.cpp b/endpoint/kvdb/src/encryptors/entry/EntryDataSchemaStrategyV5.cpp new file mode 100644 index 00000000..72bed802 --- /dev/null +++ b/endpoint/kvdb/src/encryptors/entry/EntryDataSchemaStrategyV5.cpp @@ -0,0 +1,62 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "privmx/endpoint/kvdb/encryptors/entry/EntryDataSchemaStrategyV5.hpp" +#include "privmx/endpoint/kvdb/encryptors/entry/EntryDataSchemaMapper.hpp" + +#include "privmx/endpoint/kvdb/Constants.hpp" + +using namespace privmx::endpoint; +using namespace privmx::endpoint::kvdb; + +DecryptedKvdbEntryDataV5 EntryDataSchemaStrategyV5::decrypt( + const server::KvdbEntryInfo& entry, + const core::DecryptedEncKey& encKey +) const { + auto encryptedEntryData = server::EncryptedKvdbEntryDataV5::fromJSON(entry.kvdbEntryValue); + if (encKey.statusCode != 0) { + auto tmp = _encryptor.extractPublic(encryptedEntryData); + tmp.statusCode = encKey.statusCode; + return tmp; + } + return _encryptor.decrypt(encryptedEntryData, encKey.key); +} + +std::tuple EntryDataSchemaStrategyV5::convert( + const server::KvdbEntryInfo& entry, + const DecryptedKvdbEntryDataV5& raw +) const { + return { + EntryDataSchemaMapper::toLibKvdbEntry( + entry, raw.publicMeta, raw.privateMeta, raw.data, raw.authorPubKey, raw.statusCode, + KvdbEntryDataSchema::Version::VERSION_5 + ), + raw.dio + }; +} + +std::tuple EntryDataSchemaStrategyV5::makeErrorResult( + const server::KvdbEntryInfo& entry, + int64_t errorCode +) const { + return { + EntryDataSchemaMapper::toLibKvdbEntry( + entry, {}, {}, {}, {}, errorCode, KvdbEntryDataSchema::Version::VERSION_5 + ), + core::DataIntegrityObject{} + }; +} + +core::DataIntegrityObject EntryDataSchemaStrategyV5::getDIOAndAssertIntegrity( + const server::EncryptedKvdbEntryDataV5& encData +) const { + return _encryptor.getDIOAndAssertIntegrity(encData); +} diff --git a/endpoint/kvdb/src/encryptors/kvdb/KvdbDataSchemaMapper.cpp b/endpoint/kvdb/src/encryptors/kvdb/KvdbDataSchemaMapper.cpp new file mode 100644 index 00000000..1851e070 --- /dev/null +++ b/endpoint/kvdb/src/encryptors/kvdb/KvdbDataSchemaMapper.cpp @@ -0,0 +1,209 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "privmx/endpoint/kvdb/encryptors/kvdb/KvdbDataSchemaMapper.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "privmx/endpoint/kvdb/KvdbException.hpp" + +using namespace privmx::endpoint; +using namespace privmx::endpoint::kvdb; + +KvdbDataSchemaMapper::KvdbDataSchemaMapper( + const privmx::crypto::PrivateKey& userPrivKey, + const core::Connection& connection +) + : _userPrivKey(userPrivKey), _connection(connection) { + _strategyV5 = std::make_shared(); + _strategyMapper.registerStrategy(KvdbDataSchema::Version::VERSION_5, _strategyV5); +} + +Poco::Dynamic::Var KvdbDataSchemaMapper::encrypt(const core::ModuleDataToEncryptV5& data, const std::string& key) { + return _encryptorV5.encrypt(data, _userPrivKey, key).toJSON(); +} + +std::tuple KvdbDataSchemaMapper::decrypt( + const server::KvdbInfo& kvdb, + const core::DecryptedEncKey& encKey +) { + auto version = getDataStructureVersion(kvdb.data.back()); + auto strategy = _strategyMapper.getStrategy(static_cast(version)); + if (!strategy) { + auto e = UnknownKvdbFormatException(); + return {toLibKvdb(kvdb, {}, {}, e.getCode(), KvdbDataSchema::Version::UNKNOWN), core::DataIntegrityObject{}}; + } + return strategy->decryptAndConvert(kvdb, encKey); +} + +KvdbDataSchema::Version KvdbDataSchemaMapper::getDataStructureVersion(const server::KvdbDataEntry& entry) { + if (entry.data.type() == typeid(Poco::JSON::Object::Ptr)) { + auto versioned = core::dynamic::VersionedData::fromJSON(entry.data); + switch (versioned.version) { + case core::ModuleDataSchema::Version::VERSION_5: + return KvdbDataSchema::Version::VERSION_5; + default: + return KvdbDataSchema::Version::UNKNOWN; + } + } + return KvdbDataSchema::Version::UNKNOWN; +} + +void KvdbDataSchemaMapper::assertDataIntegrity(const server::KvdbInfo& kvdb) { + const auto& entry = kvdb.data.back(); + switch (getDataStructureVersion(entry)) { + case KvdbDataSchema::Version::UNKNOWN: + throw UnknownKvdbFormatException(); + case KvdbDataSchema::Version::VERSION_5: { + auto encData = core::dynamic::EncryptedModuleDataV5::fromJSON(entry.data); + auto dio = _strategyV5->getDIOAndAssertIntegrity(encData); + if (dio.contextId != kvdb.contextId || + dio.resourceId != kvdb.resourceId || + dio.creatorUserId != kvdb.lastModifier || + !core::TimestampValidator::validate(dio.timestamp, kvdb.lastModificationDate)) { + throw KvdbDataIntegrityException(); + } + return; + } + default: + throw UnknownKvdbFormatException(); + } +} + +uint32_t KvdbDataSchemaMapper::validateDataIntegrity(const server::KvdbInfo& kvdb) { + try { + assertDataIntegrity(kvdb); + return 0; + } catch (const core::Exception& e) { return e.getCode(); } catch (const privmx::utils::PrivmxException& e) { + return core::ExceptionConverter::convert(e).getCode(); + } catch (...) { return ENDPOINT_CORE_EXCEPTION_CODE; } +} + +std::vector KvdbDataSchemaMapper::validateDecryptAndConvertKvdbs( + const std::vector& kvdbs, + const std::shared_ptr& keyProvider +) { + std::vector result(kvdbs.size()); + std::vector result_dio(kvdbs.size()); + + for (size_t i = 0; i < kvdbs.size(); i++) { + auto code = validateDataIntegrity(kvdbs[i]); + if (code != 0) { + result[i] = toLibKvdb(kvdbs[i], {}, {}, code, KvdbDataSchema::Version::UNKNOWN); + } + } + + core::KeyDecryptionAndVerificationRequest keyRequest; + for (size_t i = 0; i < kvdbs.size(); i++) { + if (result[i].statusCode != 0) { + continue; + } + const auto& kvdb = kvdbs[i]; + core::EncKeyLocation loc{.contextId = kvdb.contextId, .resourceId = kvdb.resourceId}; + keyRequest.addOne(kvdb.keys, kvdb.data.back().keyId, loc); + } + auto kvdbKeys = keyProvider->getKeysAndVerify(keyRequest); + std::set seenRandomIds; + + for (size_t i = 0; i < kvdbs.size(); i++) { + if (result[i].statusCode != 0) { + continue; + } + const auto& kvdb = kvdbs[i]; + core::EncKeyLocation loc{.contextId = kvdb.contextId, .resourceId = kvdb.resourceId}; + try { + auto it = kvdbKeys.find(loc); + if (it == kvdbKeys.end()) { + throw UnknownKvdbFormatException(); + } + auto [decryptedKvdb, dio] = decrypt(kvdb, it->second.at(kvdb.data.back().keyId)); + result[i] = decryptedKvdb; + result_dio[i] = dio; + if (!seenRandomIds.insert(dio.randomId + "-" + std::to_string(dio.timestamp)).second) { + result[i].statusCode = core::DataIntegrityObjectDuplicatedException().getCode(); + } + } catch (const core::Exception& e) { + result[i] = toLibKvdb(kvdb, {}, {}, e.getCode(), KvdbDataSchema::Version::UNKNOWN); + } catch (const privmx::utils::PrivmxException& e) { + result[i] = toLibKvdb( + kvdb, {}, {}, core::ExceptionConverter::convert(e).getCode(), KvdbDataSchema::Version::UNKNOWN + ); + } catch (...) { + result[i] = toLibKvdb(kvdb, {}, {}, ENDPOINT_CORE_EXCEPTION_CODE, KvdbDataSchema::Version::UNKNOWN); + } + } + + std::vector verifyRequests; + std::vector verifyIndices; + for (size_t i = 0; i < result.size(); i++) { + if (result[i].statusCode != 0) { + continue; + } + verifyRequests.push_back( + {.contextId = result[i].contextId, + .senderId = result[i].lastModifier, + .senderPubKey = result_dio[i].creatorPubKey, + .date = result[i].lastModificationDate, + .bridgeIdentity = result_dio[i].bridgeIdentity} + ); + verifyIndices.push_back(i); + } + auto verified = _connection.getImpl()->getUserVerifier()->verify(verifyRequests); + for (size_t j = 0; j < verifyIndices.size(); j++) { + result[verifyIndices[j]].statusCode = verified[j] ? + 0 : + core::ExceptionConverter::getCodeOfUserVerificationFailureException(); + } + return result; +} + +Kvdb KvdbDataSchemaMapper::validateDecryptAndConvertKvdb( + const server::KvdbInfo& kvdb, + const std::shared_ptr& keyProvider +) { + return validateDecryptAndConvertKvdbs({kvdb}, keyProvider)[0]; +} + +Kvdb KvdbDataSchemaMapper::toLibKvdb( + const server::KvdbInfo& info, + const core::Buffer& publicMeta, + const core::Buffer& privateMeta, + int64_t statusCode, + int64_t schemaVersion +) { + return Kvdb{ + .contextId = info.contextId, + .kvdbId = info.id, + .createDate = info.createDate, + .creator = info.creator, + .lastModificationDate = info.lastModificationDate, + .lastModifier = info.lastModifier, + .users = info.users, + .managers = info.managers, + .version = info.version, + .publicMeta = publicMeta, + .privateMeta = privateMeta, + .entries = info.entries, + .lastEntryDate = info.lastEntryDate, + .policy = core::Factory::parsePolicyServerObject(info.policy), + .statusCode = statusCode, + .schemaVersion = schemaVersion + }; +} \ No newline at end of file diff --git a/endpoint/kvdb/src/encryptors/kvdb/KvdbDataSchemaStrategyV5.cpp b/endpoint/kvdb/src/encryptors/kvdb/KvdbDataSchemaStrategyV5.cpp new file mode 100644 index 00000000..d8c53769 --- /dev/null +++ b/endpoint/kvdb/src/encryptors/kvdb/KvdbDataSchemaStrategyV5.cpp @@ -0,0 +1,63 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "privmx/endpoint/kvdb/encryptors/kvdb/KvdbDataSchemaStrategyV5.hpp" +#include "privmx/endpoint/kvdb/encryptors/kvdb/KvdbDataSchemaMapper.hpp" + +#include + +#include "privmx/endpoint/kvdb/Constants.hpp" + +using namespace privmx::endpoint; +using namespace privmx::endpoint::kvdb; + +core::DecryptedModuleDataV5 KvdbDataSchemaStrategyV5::decrypt( + const server::KvdbInfo& kvdb, + const core::DecryptedEncKey& encKey +) const { + auto encryptedData = core::dynamic::EncryptedModuleDataV5::fromJSON(kvdb.data.back().data); + core::DecryptedModuleDataV5 result; + if (encKey.statusCode != 0) { + result = _encryptor.extractPublic(encryptedData); + result.statusCode = encKey.statusCode; + } else { + result = _encryptor.decrypt(encryptedData, encKey.key); + } + return result; +} + +std::tuple KvdbDataSchemaStrategyV5::convert( + const server::KvdbInfo& kvdb, + const core::DecryptedModuleDataV5& raw +) const { + return { + KvdbDataSchemaMapper::toLibKvdb( + kvdb, raw.publicMeta, raw.privateMeta, raw.statusCode, KvdbDataSchema::Version::VERSION_5 + ), + raw.dio + }; +} + +std::tuple KvdbDataSchemaStrategyV5::makeErrorResult( + const server::KvdbInfo& kvdb, + int64_t errorCode +) const { + return { + KvdbDataSchemaMapper::toLibKvdb(kvdb, {}, {}, errorCode, KvdbDataSchema::Version::VERSION_5), + core::DataIntegrityObject{} + }; +} + +core::DataIntegrityObject KvdbDataSchemaStrategyV5::getDIOAndAssertIntegrity( + const core::dynamic::EncryptedModuleDataV5& encData +) const { + return _encryptor.getDIOAndAssertIntegrity(encData); +} \ No newline at end of file diff --git a/endpoint/store/include/privmx/endpoint/store/DynamicTypes.hpp b/endpoint/store/include/privmx/endpoint/store/DynamicTypes.hpp index 589e5d2a..ef2f4331 100644 --- a/endpoint/store/include/privmx/endpoint/store/DynamicTypes.hpp +++ b/endpoint/store/include/privmx/endpoint/store/DynamicTypes.hpp @@ -62,46 +62,6 @@ JSON_STRUCT_EXT(File, Blob, FILE_FIELDS_DYNAMIC); F(chunkSize, int64_t) JSON_STRUCT(SendFileResult, SEND_FILE_RESULT_FIELDS); -namespace compat_v1 { - -#define STORE_DATA_FIELDS(F) \ - F(name, std::string) \ - F(statusCode, int64_t) -JSON_STRUCT(StoreData, STORE_DATA_FIELDS); - -#define STORE_FILE_META_AUTHOR_FIELDS(F) F(pubKey, std::string) -JSON_STRUCT(StoreFileMetaAuthor, STORE_FILE_META_AUTHOR_FIELDS); - -#define STORE_FILE_META_DESTINATION_FIELDS(F) \ - F(server, std::string) \ - F(contextId, std::string) \ - F(storeId, std::string) \ - F(store, std::string) -JSON_STRUCT(StoreFileMetaDestination, STORE_FILE_META_DESTINATION_FIELDS); - -#define STORE_META_FIELDS(F) \ - F(mimetype, std::string) \ - F(size, int64_t) \ - F(cipherType, int64_t) \ - F(chunkSize, int64_t) \ - F(key, std::string) \ - F(hmac, std::string) \ - F(statusCode, int64_t) -JSON_STRUCT(StoreMeta, STORE_META_FIELDS); - -#define STORE_THUMB_META_FIELDS(F) -JSON_STRUCT_EXT(StoreThumbMeta, StoreMeta, STORE_THUMB_META_FIELDS); - -#define STORE_FILE_META_FIELDS(F) \ - F(ver, int64_t) \ - F(name, std::string) \ - F(author, StoreFileMetaAuthor) \ - F(destination, StoreFileMetaDestination) \ - F(thumb, std::optional) -JSON_STRUCT_EXT(StoreFileMeta, StoreMeta, STORE_FILE_META_FIELDS); - -} // namespace compat_v1 - } // namespace dynamic } // namespace store } // namespace endpoint diff --git a/endpoint/store/include/privmx/endpoint/store/StoreApiImpl.hpp b/endpoint/store/include/privmx/endpoint/store/StoreApiImpl.hpp index 64beba00..4bae58a3 100644 --- a/endpoint/store/include/privmx/endpoint/store/StoreApiImpl.hpp +++ b/endpoint/store/include/privmx/endpoint/store/StoreApiImpl.hpp @@ -20,15 +20,6 @@ limitations under the License. #include -#include -#include -#include -#include -#include -#include -#include -#include - #include "privmx/endpoint/core/Factory.hpp" #include "privmx/endpoint/core/ModuleBaseApi.hpp" #include "privmx/endpoint/store/Constants.hpp" @@ -36,14 +27,17 @@ limitations under the License. #include "privmx/endpoint/store/Events.hpp" #include "privmx/endpoint/store/FileDataProvider.hpp" #include "privmx/endpoint/store/FileHandle.hpp" -#include "privmx/endpoint/store/FileKeyIdFormatValidator.hpp" #include "privmx/endpoint/store/RequestApi.hpp" #include "privmx/endpoint/store/ServerApi.hpp" #include "privmx/endpoint/store/StoreApi.hpp" #include "privmx/endpoint/store/SubscriberImpl.hpp" -#include "privmx/endpoint/store/encryptors/file/FileMetaEncryptorV1.hpp" -#include "privmx/endpoint/store/encryptors/file/FileMetaEncryptorV4.hpp" -#include "privmx/endpoint/store/encryptors/file/FileMetaEncryptorV5.hpp" +#include "privmx/endpoint/store/encryptors/file/FileMetaDataSchemaMapper.hpp" +#include "privmx/endpoint/store/encryptors/store/StoreDataSchemaMapper.hpp" +#include +#include +#include +#include +#include #include namespace privmx { @@ -126,11 +120,6 @@ class StoreApiImpl : public privmx::utils::ManualManagedClass, pro void syncFile(const int64_t handle); std::string closeFile(const int64_t handle); FileDecryptionParams getFileDecryptionParams(server::File file, const core::DecryptedEncKey& encKey); - std::tuple decryptAndConvertFileDataToFileInfo( - server::File file, - const core::DecryptedEncKey& encKey - ); - std::vector subscribeFor(const std::vector& subscriptionQueries); void unsubscribeFrom(const std::vector& subscriptionIds); std::string buildSubscriptionQuery( @@ -138,6 +127,7 @@ class StoreApiImpl : public privmx::utils::ManualManagedClass, pro EventSelectorType selectorType, const std::string& selectorId ); + inline FileMetaDataSchemaMapper& getFileMetaDataSchemaMapper() { return _fileMetaDataSchemaMapper; } private: std::string _storeCreateEx( @@ -165,67 +155,10 @@ class StoreApiImpl : public privmx::utils::ManualManagedClass, pro void processNotificationEvent(const std::string& type, const core::NotificationEvent& notification); void processConnectedEvent(); void processDisconnectedEvent(); - dynamic::compat_v1::StoreData decryptStoreV1( - server::StoreDataEntry storeEntry, - const core::DecryptedEncKey& encKey - ); - Store convertServerStoreToLibStore( - server::Store store, - const core::Buffer& publicMeta = core::Buffer(), - const core::Buffer& privateMeta = core::Buffer(), - const int64_t& statusCode = 0, - const int64_t& schemaVersion = StoreDataSchema::Version::UNKNOWN - ); - Store convertStoreDataV1ToStore(server::Store store, dynamic::compat_v1::StoreData storeData); - Store convertDecryptedStoreDataV4ToStore(server::Store store, const core::DecryptedModuleDataV4& storeData); - Store convertDecryptedStoreDataV5ToStore(server::Store store, const core::DecryptedModuleDataV5& storeData); - StoreDataSchema::Version getStoreEntryDataStructureVersion(server::StoreDataEntry storeEntry); - std::tuple decryptAndConvertStoreDataToStore( - server::Store store, - server::StoreDataEntry storeEntry, - const core::DecryptedEncKey& encKey - ); - std::vector validateDecryptAndConvertStoresDataToStores(std::vector stores); - Store validateDecryptAndConvertStoreDataToStore(server::Store store); - void assertStoreDataIntegrity(server::Store store); - uint32_t validateStoreDataIntegrity(server::Store store); virtual std::pair getModuleKeysAndVersionFromServer(std::string moduleId) override; core::ModuleKeys storeToModuleKeys(server::Store store); - // OLD CODE - StoreFile decryptStoreFileV1(server::File file, const core::DecryptedEncKey& encKey); - // OLD CODE - std::string verifyFileV1Signature(FileMetaSigned meta, server::File raw, std::string& serverId); - // OLD CODE - StoreFile decryptAndVerifyFileV1(const std::string& filesKey, server::File x); - DecryptedFileMetaV4 decryptFileMetaV4(server::File file, const core::DecryptedEncKey& encKey); - DecryptedFileMetaV5 decryptFileMetaV5(server::File file, const core::DecryptedEncKey& encKey); - File convertServerFileToLibFile( - server::File file, - const core::Buffer& publicMeta = core::Buffer(), - const core::Buffer& privateMeta = core::Buffer(), - const int64_t& size = 0, - const std::string& authorPubKey = std::string(), - const int64_t& statusCode = 0, - const int64_t& schemaVersion = FileDataSchema::Version::UNKNOWN, - const bool& randomWrite = false - ); - File convertStoreFileMetaV1ToFile(server::File file, dynamic::compat_v1::StoreFileMeta fileData); - File convertDecryptedFileMetaV4ToFile(server::File file, const DecryptedFileMetaV4& fileData); - File convertDecryptedFileMetaV5ToFile(server::File file, const DecryptedFileMetaV5& fileData); - FileDataSchema::Version getFileDataStructureVersion(server::File file); - std::vector validateDecryptAndConvertFilesDataToFilesInfo( - std::vector files, - const core::ModuleKeys& storeKeys - ); - File validateDecryptAndConvertFileDataToFileInfo(server::File file, const core::ModuleKeys& storeKeys); - dynamic::InternalStoreFileMeta decryptFileInternalMeta(server::File file, const core::DecryptedEncKey& encKey); - dynamic::InternalStoreFileMeta validateDecryptFileInternalMeta( - server::File file, - const core::ModuleKeys& storeKeys - ); core::ModuleKeys getFileDecryptionKeys(server::File file); - uint32_t validateFileDataIntegrity(server::File file, const std::string& storeResourceId); std::string storeFileFinalizeWrite(const std::shared_ptr& handle); FileEncryptionParams getFileEncryptionParams(server::File file, const core::DecryptedEncKey& encKey); FileEncryptionParams getFileEncryptionParams(server::File file, server::Store store); @@ -253,17 +186,12 @@ class StoreApiImpl : public privmx::utils::ManualManagedClass, pro size_t _serverRequestChunkSize; FileHandleManager _fileHandleManager; - core::DataEncryptor _dataEncryptorCompatV1; - FileMetaEncryptorV1 _fileMetaEncryptorV1; - FileKeyIdFormatValidator _fileKeyIdFormatValidator; SubscriberImpl _subscriber; int _notificationListenerId, _connectedListenerId, _disconnectedListenerId; std::string _fileDecryptorId, _fileOpenerId, _fileSeekerId, _fileReaderId, _fileCloserId; - FileMetaEncryptorV4 _fileMetaEncryptorV4; - core::ModuleDataEncryptorV4 _storeDataEncryptorV4; - FileMetaEncryptorV5 _fileMetaEncryptorV5; - core::ModuleDataEncryptorV5 _storeDataEncryptorV5; + StoreDataSchemaMapper _storeDataSchemaMapper; + FileMetaDataSchemaMapper _fileMetaDataSchemaMapper; core::DataEncryptorV4 _eventDataEncryptorV4; inline static const std::string STORE_TYPE_FILTER_FLAG = "store"; diff --git a/endpoint/store/include/privmx/endpoint/store/StoreTypes.hpp b/endpoint/store/include/privmx/endpoint/store/StoreTypes.hpp index 0faa6b27..2e8ac6cf 100644 --- a/endpoint/store/include/privmx/endpoint/store/StoreTypes.hpp +++ b/endpoint/store/include/privmx/endpoint/store/StoreTypes.hpp @@ -39,18 +39,6 @@ struct FileDecryptionParams { int64_t version; }; -struct FileMetaSigned { - dynamic::compat_v1::StoreFileMeta meta; - std::string metaBuf; - std::string signature; -}; - -struct StoreFile { - server::File raw; - dynamic::compat_v1::StoreFileMeta meta; - std::string verified; -}; - struct FileMetaToEncryptV4 { core::Buffer publicMeta; core::Buffer privateMeta; diff --git a/endpoint/store/include/privmx/endpoint/store/encryptors/file/FileDataSchemaStrategyV4.hpp b/endpoint/store/include/privmx/endpoint/store/encryptors/file/FileDataSchemaStrategyV4.hpp new file mode 100644 index 00000000..d3f37471 --- /dev/null +++ b/endpoint/store/include/privmx/endpoint/store/encryptors/file/FileDataSchemaStrategyV4.hpp @@ -0,0 +1,64 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef _PRIVMXLIB_ENDPOINT_STORE_FILEDATASCHEMASTRATEGYV4_HPP_ +#define _PRIVMXLIB_ENDPOINT_STORE_FILEDATASCHEMASTRATEGYV4_HPP_ + +#include + +#include +#include +#include + +#include "privmx/endpoint/store/ServerTypes.hpp" +#include "privmx/endpoint/store/StoreTypes.hpp" +#include "privmx/endpoint/store/Types.hpp" +#include "privmx/endpoint/store/encryptors/file/FileMetaEncryptorV4.hpp" + +namespace privmx { +namespace endpoint { +namespace store { + +// clang-format off +class FileDataSchemaStrategyV4 : public core::TypedDataSchemaStrategy< + server::File, + DecryptedFileMetaV4, + std::tuple +> { + // clang-format on +public: + DecryptedFileMetaV4 decrypt(const server::File& file, const core::DecryptedEncKey& encKey) const override; + DecryptedFileMetaV4 decryptFileMeta(const server::File& file, const core::DecryptedEncKey& encKey) const; + std::tuple convert( + const server::File& file, + const DecryptedFileMetaV4& raw + ) const override; + server::EncryptedFileMetaV4 encrypt( + const core::Buffer& publicMeta, + const core::Buffer& privateMeta, + const core::Buffer& internalMeta, + const privmx::crypto::PrivateKey& userPrivKey, + const std::string& key + ) const; + std::tuple makeErrorResult( + const server::File& file, + int64_t errorCode + ) const override; + +private: + mutable FileMetaEncryptorV4 _encryptor; +}; + +} // namespace store +} // namespace endpoint +} // namespace privmx + +#endif // _PRIVMXLIB_ENDPOINT_STORE_FILEDATASCHEMASTRATEGYV4_HPP_ diff --git a/endpoint/store/include/privmx/endpoint/store/encryptors/file/FileDataSchemaStrategyV5.hpp b/endpoint/store/include/privmx/endpoint/store/encryptors/file/FileDataSchemaStrategyV5.hpp new file mode 100644 index 00000000..28ca2689 --- /dev/null +++ b/endpoint/store/include/privmx/endpoint/store/encryptors/file/FileDataSchemaStrategyV5.hpp @@ -0,0 +1,66 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef _PRIVMXLIB_ENDPOINT_STORE_FILEDATASCHEMASTRATEGYV5_HPP_ +#define _PRIVMXLIB_ENDPOINT_STORE_FILEDATASCHEMASTRATEGYV5_HPP_ + +#include + +#include +#include +#include + +#include "privmx/endpoint/store/ServerTypes.hpp" +#include "privmx/endpoint/store/StoreTypes.hpp" +#include "privmx/endpoint/store/Types.hpp" +#include "privmx/endpoint/store/encryptors/file/FileMetaEncryptorV5.hpp" + +namespace privmx { +namespace endpoint { +namespace store { + +// clang-format off +class FileDataSchemaStrategyV5 : public core::TypedDataSchemaStrategy< + server::File, + DecryptedFileMetaV5, + std::tuple +> { + // clang-format on +public: + DecryptedFileMetaV5 decrypt(const server::File& file, const core::DecryptedEncKey& encKey) const override; + DecryptedFileMetaV5 decryptFileMeta(const server::File& file, const core::DecryptedEncKey& encKey) const; + std::tuple convert( + const server::File& file, + const DecryptedFileMetaV5& raw + ) const override; + std::tuple makeErrorResult( + const server::File& file, + int64_t errorCode + ) const override; + server::EncryptedFileMetaV5 encrypt( + const core::Buffer& publicMeta, + const core::Buffer& privateMeta, + const core::Buffer& internalMeta, + const privmx::crypto::PrivateKey& userPrivKey, + const std::string& key, + const core::DataIntegrityObject& dio + ) const; + core::DataIntegrityObject getDIOAndAssertIntegrity(const server::EncryptedFileMetaV5& encData) const; + +private: + mutable FileMetaEncryptorV5 _encryptor; +}; + +} // namespace store +} // namespace endpoint +} // namespace privmx + +#endif // _PRIVMXLIB_ENDPOINT_STORE_FILEDATASCHEMASTRATEGYV5_HPP_ diff --git a/endpoint/store/include/privmx/endpoint/store/encryptors/file/FileMetaDataSchemaMapper.hpp b/endpoint/store/include/privmx/endpoint/store/encryptors/file/FileMetaDataSchemaMapper.hpp new file mode 100644 index 00000000..2828184b --- /dev/null +++ b/endpoint/store/include/privmx/endpoint/store/encryptors/file/FileMetaDataSchemaMapper.hpp @@ -0,0 +1,111 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef _PRIVMXLIB_ENDPOINT_STORE_FILEMETADATASCHEMAMAPPER_HPP_ +#define _PRIVMXLIB_ENDPOINT_STORE_FILEMETADATASCHEMAMAPPER_HPP_ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "privmx/endpoint/store/Constants.hpp" +#include "privmx/endpoint/store/FileKeyIdFormatValidator.hpp" +#include "privmx/endpoint/store/ServerTypes.hpp" +#include "privmx/endpoint/store/StoreTypes.hpp" +#include "privmx/endpoint/store/Types.hpp" +#include "privmx/endpoint/store/encryptors/file/FileDataSchemaStrategyV4.hpp" +#include "privmx/endpoint/store/encryptors/file/FileDataSchemaStrategyV5.hpp" + +namespace privmx { +namespace endpoint { +namespace store { + +class FileMetaDataSchemaMapper { +public: + FileMetaDataSchemaMapper(const privmx::crypto::PrivateKey& userPrivKey, const core::Connection& connection); + + Poco::Dynamic::Var encrypt( + const std::string& storeId, + const std::string& fileResourceId, + const std::string& contextId, + const std::string& storeResourceId, + const core::Buffer& publicMeta, + const core::Buffer& privateMeta, + const core::Buffer& internalMeta, + const core::DecryptedEncKeyV2& fileKey + ); + + std::tuple decrypt(const server::File& file, const core::DecryptedEncKey& encKey); + + FileDataSchema::Version getDataStructureVersion(const server::File& file); + StoreDataSchema::Version getMinimumStoreSchemaVersion(const server::File& file); + + uint32_t validateDataIntegrity(const server::File& file, const std::string& storeResourceId); + + DecryptedFileMetaV4 decryptFileMetaV4(const server::File& file, const core::DecryptedEncKey& encKey); + DecryptedFileMetaV5 decryptFileMetaV5(const server::File& file, const core::DecryptedEncKey& encKey); + dynamic::InternalStoreFileMeta decryptFileInternalMeta( + const server::File& file, + const core::DecryptedEncKey& encKey + ); + + std::vector validateDecryptAndConvertFiles( + const std::vector& files, + const core::ModuleKeys& storeKeys, + const std::shared_ptr& keyProvider + ); + + File validateDecryptAndConvertFile( + const server::File& file, + const core::ModuleKeys& storeKeys, + const std::shared_ptr& keyProvider + ); + + dynamic::InternalStoreFileMeta validateDecryptFileInternalMeta( + const server::File& file, + const core::ModuleKeys& storeKeys, + const std::shared_ptr& keyProvider + ); + + static File toLibFile( + const server::File& file, + const core::Buffer& publicMeta, + const core::Buffer& privateMeta, + int64_t size, + const std::string& authorPubKey, + int64_t statusCode, + int64_t schemaVersion, + bool randomWrite = false + ); + +private: + privmx::crypto::PrivateKey _userPrivKey; + core::Connection _connection; + FileKeyIdFormatValidator _fileKeyIdFormatValidator; + core::VersionStrategyMapper> _strategyMapper; + std::shared_ptr _strategyV4; + std::shared_ptr _strategyV5; +}; + +} // namespace store +} // namespace endpoint +} // namespace privmx + +#endif // _PRIVMXLIB_ENDPOINT_STORE_FILEMETADATASCHEMAMAPPER_HPP_ diff --git a/endpoint/store/include/privmx/endpoint/store/encryptors/file/FileMetaEncryptor.hpp b/endpoint/store/include/privmx/endpoint/store/encryptors/file/FileMetaEncryptor.hpp index d2b8fb25..7d3f1657 100644 --- a/endpoint/store/include/privmx/endpoint/store/encryptors/file/FileMetaEncryptor.hpp +++ b/endpoint/store/include/privmx/endpoint/store/encryptors/file/FileMetaEncryptor.hpp @@ -13,7 +13,6 @@ limitations under the License. #define _PRIVMXLIB_ENDPOINT_STORE_FILEMETAENCRYPTOR_HPP_ #include "privmx/endpoint/store/Constants.hpp" -#include "privmx/endpoint/store/encryptors/file/FileMetaEncryptorV1.hpp" #include "privmx/endpoint/store/encryptors/file/FileMetaEncryptorV4.hpp" #include "privmx/endpoint/store/encryptors/file/FileMetaEncryptorV5.hpp" #include @@ -30,16 +29,12 @@ class FileMetaEncryptor { public: struct DecryptedFileMeta { public: - DecryptedFileMeta() - : version(FileDataSchema::Version::UNKNOWN), v1(std::nullopt), v4(std::nullopt), v5(std::nullopt) {}; - DecryptedFileMeta(const FileMetaSigned& v1) - : version(FileDataSchema::Version::VERSION_1), v1(v1), v4(std::nullopt), v5(std::nullopt) {}; + DecryptedFileMeta() : version(FileDataSchema::Version::UNKNOWN), v4(std::nullopt), v5(std::nullopt) {}; DecryptedFileMeta(const DecryptedFileMetaV4& v4) - : version(FileDataSchema::Version::VERSION_4), v1(std::nullopt), v4(v4), v5(std::nullopt) {}; + : version(FileDataSchema::Version::VERSION_4), v4(v4), v5(std::nullopt) {}; DecryptedFileMeta(const DecryptedFileMetaV5& v5) - : version(FileDataSchema::Version::VERSION_5), v1(std::nullopt), v4(std::nullopt), v5(v5) {}; + : version(FileDataSchema::Version::VERSION_5), v4(std::nullopt), v5(v5) {}; FileDataSchema::Version version; - std::optional v1; std::optional v4; std::optional v5; }; @@ -60,7 +55,6 @@ class FileMetaEncryptor { privmx::endpoint::core::DataIntegrityObject createDIO(const FileInfo& fileInfo); privmx::crypto::PrivateKey _userPrivKey; core::Connection _connection; - FileMetaEncryptorV1 _fileMetaEncryptorV1; FileMetaEncryptorV4 _fileMetaEncryptorV4; FileMetaEncryptorV5 _fileMetaEncryptorV5; }; diff --git a/endpoint/store/include/privmx/endpoint/store/encryptors/file/FileMetaEncryptorV1.hpp b/endpoint/store/include/privmx/endpoint/store/encryptors/file/FileMetaEncryptorV1.hpp deleted file mode 100644 index 140a8067..00000000 --- a/endpoint/store/include/privmx/endpoint/store/encryptors/file/FileMetaEncryptorV1.hpp +++ /dev/null @@ -1,41 +0,0 @@ -/* -PrivMX Endpoint. -Copyright © 2024 Simplito sp. z o.o. - -This file is part of the PrivMX Platform (https://privmx.dev). -This software is Licensed under the PrivMX Free License. - -See the License for the specific language governing permissions and -limitations under the License. -*/ - -#ifndef _PRIVMXLIB_ENDPOINT_STORE_FILEMETAENCRYPTORV1_HPP_ -#define _PRIVMXLIB_ENDPOINT_STORE_FILEMETAENCRYPTORV1_HPP_ - -#include - -#include -#include - -#include "privmx/endpoint/store/DynamicTypes.hpp" -#include "privmx/endpoint/store/StoreTypes.hpp" - -namespace privmx { -namespace endpoint { -namespace store { - -class FileMetaEncryptorV1 { -public: - std::string signAndEncrypt( - const dynamic::compat_v1::StoreFileMeta& data, - const privmx::crypto::PrivateKey& priv, - const std::string& key - ); - FileMetaSigned decrypt(const std::string& data, const std::string& key); -}; - -} // namespace store -} // namespace endpoint -} // namespace privmx - -#endif // _PRIVMXLIB_ENDPOINT_STORE_FILEMETAENCRYPTORV1_HPP_ diff --git a/endpoint/store/include/privmx/endpoint/store/encryptors/store/StoreDataSchemaMapper.hpp b/endpoint/store/include/privmx/endpoint/store/encryptors/store/StoreDataSchemaMapper.hpp new file mode 100644 index 00000000..0a151a36 --- /dev/null +++ b/endpoint/store/include/privmx/endpoint/store/encryptors/store/StoreDataSchemaMapper.hpp @@ -0,0 +1,86 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef _PRIVMXLIB_ENDPOINT_STORE_STOREDATASCHEMAMAPPER_HPP_ +#define _PRIVMXLIB_ENDPOINT_STORE_STOREDATASCHEMAMAPPER_HPP_ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "privmx/endpoint/store/Constants.hpp" +#include "privmx/endpoint/store/ServerTypes.hpp" +#include "privmx/endpoint/store/Types.hpp" +#include "privmx/endpoint/store/encryptors/store/StoreDataSchemaStrategyV4.hpp" +#include "privmx/endpoint/store/encryptors/store/StoreDataSchemaStrategyV5.hpp" + +namespace privmx { +namespace endpoint { +namespace store { + +class StoreDataSchemaMapper { +public: + StoreDataSchemaMapper(const privmx::crypto::PrivateKey& userPrivKey, const core::Connection& connection); + + Poco::Dynamic::Var encrypt(const core::ModuleDataToEncryptV5& data, const std::string& key); + + std::tuple decrypt( + const server::Store& store, + const core::DecryptedEncKey& encKey + ); + + StoreDataSchema::Version getDataStructureVersion(const server::StoreDataEntry& entry); + + void assertDataIntegrity(const server::Store& store); + + uint32_t validateDataIntegrity(const server::Store& store); + + std::vector validateDecryptAndConvertStores( + const std::vector& stores, + const std::shared_ptr& keyProvider + ); + + Store validateDecryptAndConvertStore( + const server::Store& store, + const std::shared_ptr& keyProvider + ); + + static Store toLibStore( + const server::Store& store, + const core::Buffer& publicMeta, + const core::Buffer& privateMeta, + int64_t statusCode, + int64_t schemaVersion + ); + +private: + privmx::crypto::PrivateKey _userPrivKey; + core::Connection _connection; + core::VersionStrategyMapper> _strategyMapper; + std::shared_ptr _strategyV4; + std::shared_ptr _strategyV5; +}; + +} // namespace store +} // namespace endpoint +} // namespace privmx + +#endif // _PRIVMXLIB_ENDPOINT_STORE_STOREDATASCHEMAMAPPER_HPP_ diff --git a/endpoint/store/include/privmx/endpoint/store/encryptors/store/StoreDataSchemaStrategyV4.hpp b/endpoint/store/include/privmx/endpoint/store/encryptors/store/StoreDataSchemaStrategyV4.hpp new file mode 100644 index 00000000..1b1acb40 --- /dev/null +++ b/endpoint/store/include/privmx/endpoint/store/encryptors/store/StoreDataSchemaStrategyV4.hpp @@ -0,0 +1,55 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef _PRIVMXLIB_ENDPOINT_STORE_STOREDATASCHEMASTRATEGYV4_HPP_ +#define _PRIVMXLIB_ENDPOINT_STORE_STOREDATASCHEMASTRATEGYV4_HPP_ + +#include + +#include +#include +#include +#include + +#include "privmx/endpoint/store/ServerTypes.hpp" +#include "privmx/endpoint/store/Types.hpp" + +namespace privmx { +namespace endpoint { +namespace store { + +// clang-format off +class StoreDataSchemaStrategyV4 : public core::TypedDataSchemaStrategy< + server::Store, + core::DecryptedModuleDataV4, + std::tuple +> { + // clang-format on +public: + core::DecryptedModuleDataV4 decrypt(const server::Store& store, const core::DecryptedEncKey& encKey) const override; + std::tuple convert( + const server::Store& store, + const core::DecryptedModuleDataV4& raw + ) const override; + std::tuple makeErrorResult( + const server::Store& store, + int64_t errorCode + ) const override; + +private: + mutable core::ModuleDataEncryptorV4 _encryptor; +}; + +} // namespace store +} // namespace endpoint +} // namespace privmx + +#endif // _PRIVMXLIB_ENDPOINT_STORE_STOREDATASCHEMASTRATEGYV4_HPP_ diff --git a/endpoint/store/include/privmx/endpoint/store/encryptors/store/StoreDataSchemaStrategyV5.hpp b/endpoint/store/include/privmx/endpoint/store/encryptors/store/StoreDataSchemaStrategyV5.hpp new file mode 100644 index 00000000..08463a45 --- /dev/null +++ b/endpoint/store/include/privmx/endpoint/store/encryptors/store/StoreDataSchemaStrategyV5.hpp @@ -0,0 +1,63 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef _PRIVMXLIB_ENDPOINT_STORE_STOREDATASCHEMASTRATEGYV5_HPP_ +#define _PRIVMXLIB_ENDPOINT_STORE_STOREDATASCHEMASTRATEGYV5_HPP_ + +#include + +#include +#include +#include +#include +#include +#include + +#include "privmx/endpoint/store/ServerTypes.hpp" +#include "privmx/endpoint/store/Types.hpp" + +namespace privmx { +namespace endpoint { +namespace store { + +// clang-format off +class StoreDataSchemaStrategyV5 : public core::TypedDataSchemaStrategy< + server::Store, + core::DecryptedModuleDataV5, + std::tuple +> { + // clang-format on +public: + core::DecryptedModuleDataV5 decrypt(const server::Store& store, const core::DecryptedEncKey& encKey) const override; + std::tuple convert( + const server::Store& store, + const core::DecryptedModuleDataV5& raw + ) const override; + std::tuple makeErrorResult( + const server::Store& store, + int64_t errorCode + ) const override; + core::dynamic::EncryptedModuleDataV5 encrypt( + const core::ModuleDataToEncryptV5& data, + const privmx::crypto::PrivateKey& userPrivKey, + const std::string& key + ) const; + core::DataIntegrityObject getDIOAndAssertIntegrity(const core::dynamic::EncryptedModuleDataV5& encData) const; + +private: + mutable core::ModuleDataEncryptorV5 _encryptor; +}; + +} // namespace store +} // namespace endpoint +} // namespace privmx + +#endif // _PRIVMXLIB_ENDPOINT_STORE_STOREDATASCHEMASTRATEGYV5_HPP_ diff --git a/endpoint/store/src/StoreApiImpl.cpp b/endpoint/store/src/StoreApiImpl.cpp index 34f84d8d..a82992e5 100644 --- a/endpoint/store/src/StoreApiImpl.cpp +++ b/endpoint/store/src/StoreApiImpl.cpp @@ -33,7 +33,6 @@ limitations under the License. #include "privmx/endpoint/store/ChunkDataProvider.hpp" #include "privmx/endpoint/store/ChunkReader.hpp" #include "privmx/endpoint/store/FileHandler.hpp" -#include "privmx/endpoint/store/encryptors/file/FileMetaEncryptor.hpp" #include "privmx/endpoint/store/encryptors/fileData/ChunkEncryptor.hpp" #include "privmx/endpoint/store/encryptors/fileData/HmacList.hpp" #include "privmx/endpoint/store/interfaces/IChunkDataProvider.hpp" @@ -63,10 +62,8 @@ StoreApiImpl::StoreApiImpl( _connection(connection), _serverRequestChunkSize(serverRequestChunkSize), _fileHandleManager(FileHandleManager(handleManager, "Store")), - _dataEncryptorCompatV1(core::DataEncryptor()), - _fileMetaEncryptorV1(FileMetaEncryptorV1()), _fileKeyIdFormatValidator(FileKeyIdFormatValidator()), _subscriber(connection.getImpl()->getGateway(), STORE_TYPE_FILTER_FLAG), - _fileMetaEncryptorV4(FileMetaEncryptorV4()) { + _storeDataSchemaMapper(userPrivKey, connection), _fileMetaDataSchemaMapper(userPrivKey, connection) { _notificationListenerId = _eventMiddleware->addNotificationEventListener( std::bind(&StoreApiImpl::processNotificationEvent, this, std::placeholders::_1, std::placeholders::_2) ); @@ -134,7 +131,7 @@ std::string StoreApiImpl::_storeCreateEx( storeCreateModel.resourceId = resourceId; storeCreateModel.contextId = contextId; storeCreateModel.keyId = storeKey.id; - storeCreateModel.data = _storeDataEncryptorV5.encrypt(storeDataToEncrypt, _userPrivKey, storeKey.key).toJSON(); + storeCreateModel.data = _storeDataSchemaMapper.encrypt(storeDataToEncrypt, storeKey.key); if (type.length() > 0) { storeCreateModel.type = type; } @@ -253,7 +250,7 @@ void StoreApiImpl::updateStore( }, .dio = updateStoreDio }; - model.data = _storeDataEncryptorV5.encrypt(storeDataToEncrypt, _userPrivKey, storeKey.key).toJSON(); + model.data = _storeDataSchemaMapper.encrypt(storeDataToEncrypt, storeKey.key); PRIVMX_DEBUG_TIME_CHECKPOINT(PlatformStore, storeUpdate, data encrypted) _serverApi->storeUpdate(model); @@ -287,7 +284,7 @@ Store StoreApiImpl::_storeGetEx(const std::string& storeId, const std::string& t auto store = _serverApi->storeGet(model).store; setNewModuleKeysInCache(store.id, storeToModuleKeys(store), store.version); PRIVMX_DEBUG_TIME_CHECKPOINT(PlatformStore, _storeGetEx, data send) - auto result = validateDecryptAndConvertStoreDataToStore(store); + auto result = _storeDataSchemaMapper.validateDecryptAndConvertStore(store, _keyProvider); PRIVMX_DEBUG_TIME_STOP(PlatformStore, _getStoreEx, data decrypted) return result; } @@ -322,7 +319,7 @@ core::PagingList StoreApiImpl::_storeListEx( for (auto store : storesList.stores) { setNewModuleKeysInCache(store.id, storeToModuleKeys(store), store.version); } - auto stores = validateDecryptAndConvertStoresDataToStores(storesList.stores); + auto stores = _storeDataSchemaMapper.validateDecryptAndConvertStores(storesList.stores, _keyProvider); PRIVMX_DEBUG_TIME_STOP(PlatformStore, _listStoresEx, data decrypted) return core::PagingList({.totalAvailable = storesList.count, .readItems = stores}); } @@ -335,16 +332,20 @@ File StoreApiImpl::getFile(const std::string& fileId) { auto serverFileResult = _serverApi->storeFileGet(storeFileGetModel); PRIVMX_DEBUG_TIME_CHECKPOINT(PlatformStore, getFile, data send) auto store = serverFileResult.store; - assertStoreDataIntegrity(store); + _storeDataSchemaMapper.assertDataIntegrity(store); setNewModuleKeysInCache(store.id, storeToModuleKeys(store), store.version); - auto statusCode = validateFileDataIntegrity(serverFileResult.file, store.resourceId.value_or("")); + auto statusCode = _fileMetaDataSchemaMapper.validateDataIntegrity( + serverFileResult.file, store.resourceId.value_or("") + ); if (statusCode != 0) { PRIVMX_DEBUG_TIME_STOP(PlatformStore, getFile, data integrity validation failed) File result; result.statusCode = statusCode; return result; } - auto ret{validateDecryptAndConvertFileDataToFileInfo(serverFileResult.file, storeToModuleKeys(store))}; + auto ret{_fileMetaDataSchemaMapper.validateDecryptAndConvertFile( + serverFileResult.file, storeToModuleKeys(store), _keyProvider + )}; PRIVMX_DEBUG_TIME_STOP(PlatformStore, getFile, data decrypted) return ret; } @@ -359,9 +360,11 @@ core::PagingList StoreApiImpl::listFiles(const std::string& storeId, const auto serverFilesResult = _serverApi->storeFileList(model); PRIVMX_DEBUG_TIME_CHECKPOINT(PlatformStore, storeFileList, data send); auto store = serverFilesResult.store; - assertStoreDataIntegrity(store); + _storeDataSchemaMapper.assertDataIntegrity(store); setNewModuleKeysInCache(store.id, storeToModuleKeys(store), store.version); - auto files = validateDecryptAndConvertFilesDataToFilesInfo(serverFilesResult.files, storeToModuleKeys(store)); + auto files = _fileMetaDataSchemaMapper.validateDecryptAndConvertFiles( + serverFilesResult.files, storeToModuleKeys(store), _keyProvider + ); PRIVMX_DEBUG_TIME_STOP(PlatformStore, storeFileList, data decrypted) return core::PagingList({.totalAvailable = serverFilesResult.count, .readItems = files}); } @@ -400,7 +403,9 @@ int64_t StoreApiImpl::updateFile( server::StoreFileGetModel storeFileGetModel; storeFileGetModel.fileId = fileId; auto result = _serverApi->storeFileGet(storeFileGetModel); - auto internalMeta = validateDecryptFileInternalMeta(result.file, storeToModuleKeys(result.store)); + auto internalMeta = _fileMetaDataSchemaMapper.validateDecryptFileInternalMeta( + result.file, storeToModuleKeys(result.store), _keyProvider + ); std::shared_ptr handle = _fileHandleManager.createFileWriteHandle( result.store.id, fileId, result.file.resourceId, (uint64_t)size, publicMeta, privateMeta, _CHUNK_SIZE, _serverRequestChunkSize, _requestApi, internalMeta.randomWrite.value_or(false) @@ -431,7 +436,7 @@ int64_t StoreApiImpl::openFile(const std::string& fileId) { } FileDecryptionParams StoreApiImpl::getFileDecryptionParams(server::File file, const core::DecryptedEncKey& encKey) { - auto internalMeta = decryptFileInternalMeta(file, core::DecryptedEncKey(encKey)); + auto internalMeta = _fileMetaDataSchemaMapper.decryptFileInternalMeta(file, core::DecryptedEncKey(encKey)); return getFileDecryptionParams(file, internalMeta); } @@ -553,7 +558,7 @@ std::string StoreApiImpl::storeFileFinalizeWrite(const std::shared_ptrgetRandomWriteSupport(); - auto fileId = core::EndpointUtils::generateId(); - Poco::Dynamic::Var encryptedMetaVar; - switch (storeKey.moduleSchemaVersion) { - case StoreDataSchema::Version::UNKNOWN: - throw UnknowStoreFormatException(); - case StoreDataSchema::Version::VERSION_1: - case StoreDataSchema::Version::VERSION_4: { - store::FileMetaToEncryptV4 fileMeta{ - .publicMeta = handle->getPublicMeta(), - .privateMeta = handle->getPrivateMeta(), - .fileSize = handle->getSize(), - .internalMeta = core::Buffer::from(internalFileMeta.serialize()) - }; - encryptedMetaVar = _fileMetaEncryptorV4.encrypt(fileMeta, _userPrivKey, key.key).toJSON(); - break; - } - case StoreDataSchema::Version::VERSION_5: { - privmx::endpoint::core::DataIntegrityObject fileDIO = _connection.getImpl()->createDIO( - storeKey.contextId, handle->getResourceId(), handle->getStoreId(), storeKey.moduleResourceId - ); - store::FileMetaToEncryptV5 fileMeta{ - .publicMeta = handle->getPublicMeta(), - .privateMeta = handle->getPrivateMeta(), - .internalMeta = core::Buffer::from(internalFileMeta.serialize()), - .dio = fileDIO - }; - auto encryptedMeta = _fileMetaEncryptorV5.encrypt(fileMeta, _userPrivKey, key.key); - encryptedMetaVar = encryptedMeta.toJSON(); - - break; - } - } + auto encryptedMetaVar = _fileMetaDataSchemaMapper.encrypt( + handle->getStoreId(), handle->getResourceId(), storeKey.contextId, storeKey.moduleResourceId, + handle->getPublicMeta(), handle->getPrivateMeta(), core::Buffer::from(internalFileMeta.serialize()), key + ); if (handle->getFileId().empty()) { server::StoreFileCreateModel storeFileCreateModel; storeFileCreateModel.fileIndex = 0; @@ -655,7 +632,7 @@ void StoreApiImpl::processNotificationEvent(const std::string& type, const core: auto raw = server::Store::fromJSON(notification.data); if (raw.type.value_or(std::string(STORE_TYPE_FILTER_FLAG)) == STORE_TYPE_FILTER_FLAG) { setNewModuleKeysInCache(raw.id, storeToModuleKeys(raw), raw.version); - auto data = validateDecryptAndConvertStoreDataToStore(raw); + auto data = _storeDataSchemaMapper.validateDecryptAndConvertStore(raw, _keyProvider); auto event = core::EventBuilder::buildEvent("store", data, notification); _eventMiddleware->emitApiEvent(event); } @@ -663,7 +640,7 @@ void StoreApiImpl::processNotificationEvent(const std::string& type, const core: auto raw = server::Store::fromJSON(notification.data); if (raw.type.value_or(std::string(STORE_TYPE_FILTER_FLAG)) == STORE_TYPE_FILTER_FLAG) { setNewModuleKeysInCache(raw.id, storeToModuleKeys(raw), raw.version); - auto data = validateDecryptAndConvertStoreDataToStore(raw); + auto data = _storeDataSchemaMapper.validateDecryptAndConvertStore(raw, _keyProvider); auto event = core::EventBuilder::buildEvent("store", data, notification); _eventMiddleware->emitApiEvent(event); } @@ -685,7 +662,9 @@ void StoreApiImpl::processNotificationEvent(const std::string& type, const core: } else if (type == "storeFileCreated") { auto raw = server::StoreFileEventData::fromJSON(notification.data); if (raw.containerType.value_or(std::string(STORE_TYPE_FILTER_FLAG)) == STORE_TYPE_FILTER_FLAG) { - auto file = validateDecryptAndConvertFileDataToFileInfo(raw, getFileDecryptionKeys(raw)); + auto file = _fileMetaDataSchemaMapper.validateDecryptAndConvertFile( + raw, getFileDecryptionKeys(raw), _keyProvider + ); auto event = core::EventBuilder::buildEvent( "store/" + raw.storeId + "/files", file, notification ); @@ -695,8 +674,10 @@ void StoreApiImpl::processNotificationEvent(const std::string& type, const core: auto raw = server::StoreFileUpdatedEventData::fromJSON(notification.data); if (raw.containerType.value_or(std::string(STORE_TYPE_FILTER_FLAG)) == STORE_TYPE_FILTER_FLAG) { auto storeKeys = getFileDecryptionKeys(raw); - auto file = validateDecryptAndConvertFileDataToFileInfo(raw, storeKeys); - auto internalMeta = validateDecryptFileInternalMeta(raw, storeKeys); + auto file = _fileMetaDataSchemaMapper.validateDecryptAndConvertFile(raw, storeKeys, _keyProvider); + auto internalMeta = _fileMetaDataSchemaMapper.validateDecryptFileInternalMeta( + raw, storeKeys, _keyProvider + ); auto fileDecryptionParams = getFileDecryptionParams(raw, internalMeta); auto data = Mapper::mapToStoreFileUpdatedEventData(raw, file, fileDecryptionParams); auto event = core::EventBuilder::buildEvent( @@ -738,279 +719,11 @@ void StoreApiImpl::processDisconnectedEvent() { privmx::utils::ManualManagedClass::cleanup(); } -dynamic::compat_v1::StoreData StoreApiImpl::decryptStoreV1( - server::StoreDataEntry storeEntry, - const core::DecryptedEncKey& encKey -) { - try { - return _dataEncryptorCompatV1.decrypt(storeEntry.data.convert(), encKey); - } catch (const core::Exception& e) { - dynamic::compat_v1::StoreData result; - result.name = std::string(); - result.statusCode = e.getCode(); - return result; - } catch (const privmx::utils::PrivmxException& e) { - dynamic::compat_v1::StoreData result; - result.name = std::string(); - result.statusCode = core::ExceptionConverter::convert(e).getCode(); - return result; - } catch (...) { - dynamic::compat_v1::StoreData result; - result.name = std::string(); - result.statusCode = ENDPOINT_CORE_EXCEPTION_CODE; - return result; - } -} - -Store StoreApiImpl::convertServerStoreToLibStore( - server::Store store, - const core::Buffer& publicMeta, - const core::Buffer& privateMeta, - const int64_t& statusCode, - const int64_t& schemaVersion -) { - std::vector users; - std::vector managers; - for (auto x : store.users) { - users.push_back(x); - } - for (auto x : store.managers) { - managers.push_back(x); - } - return Store{ - .storeId = store.id, - .contextId = store.contextId, - .createDate = store.createDate, - .creator = store.creator, - .lastModificationDate = store.lastModificationDate, - .lastFileDate = store.lastFileDate, - .lastModifier = store.lastModifier, - .users = users, - .managers = managers, - .version = store.version, - .publicMeta = publicMeta, - .privateMeta = privateMeta, - .policy = core::Factory::parsePolicyServerObject(store.policy), - .filesCount = store.files, - .statusCode = statusCode, - .schemaVersion = schemaVersion - }; -} - -Store StoreApiImpl::convertStoreDataV1ToStore(server::Store store, dynamic::compat_v1::StoreData storeData) { - Poco::JSON::Object::Ptr privateMeta = Poco::JSON::Object::Ptr(new Poco::JSON::Object()); - privateMeta->set("title", storeData.name); - return convertServerStoreToLibStore( - store, core::Buffer::from(""), core::Buffer::from(utils::Utils::stringify(privateMeta)), storeData.statusCode, - StoreDataSchema::Version::VERSION_1 - - ); -} - -Store StoreApiImpl::convertDecryptedStoreDataV4ToStore( - server::Store store, - const core::DecryptedModuleDataV4& storeData -) { - return convertServerStoreToLibStore( - store, storeData.publicMeta, storeData.privateMeta, storeData.statusCode, StoreDataSchema::Version::VERSION_4 - ); -} - -Store StoreApiImpl::convertDecryptedStoreDataV5ToStore( - server::Store store, - const core::DecryptedModuleDataV5& storeData -) { - return convertServerStoreToLibStore( - store, storeData.publicMeta, storeData.privateMeta, storeData.statusCode, StoreDataSchema::Version::VERSION_5 - ); -} - -StoreDataSchema::Version StoreApiImpl::getStoreEntryDataStructureVersion(server::StoreDataEntry storeEntry) { - if (storeEntry.data.type() == typeid(Poco::JSON::Object::Ptr)) { - auto versioned = core::dynamic::VersionedData::fromJSON(storeEntry.data); - switch (versioned.version) { - case core::ModuleDataSchema::Version::VERSION_4: - return StoreDataSchema::Version::VERSION_4; - case core::ModuleDataSchema::Version::VERSION_5: - return StoreDataSchema::Version::VERSION_5; - default: - return StoreDataSchema::Version::UNKNOWN; - } - } else if (storeEntry.data.isString()) { - return StoreDataSchema::Version::VERSION_1; - } - return StoreDataSchema::Version::UNKNOWN; -} - -std::tuple StoreApiImpl::decryptAndConvertStoreDataToStore( - server::Store store, - server::StoreDataEntry storeEntry, - const core::DecryptedEncKey& encKey -) { - switch (getStoreEntryDataStructureVersion(storeEntry)) { - case StoreDataSchema::Version::UNKNOWN: { - auto e = UnknowStoreFormatException(); - return std::make_tuple(convertServerStoreToLibStore(store, {}, {}, e.getCode()), core::DataIntegrityObject()); - } - case StoreDataSchema::Version::VERSION_1: { - return std::make_tuple( - convertStoreDataV1ToStore(store, decryptStoreV1(storeEntry, encKey)), - core::DataIntegrityObject{ - .creatorUserId = store.lastModifier, - .creatorPubKey = "", - .contextId = store.contextId, - .resourceId = store.resourceId.value_or(""), - .timestamp = store.lastModificationDate, - .randomId = std::string(), - .containerId = std::nullopt, - .containerResourceId = std::nullopt, - .bridgeIdentity = std::nullopt - } - ); - } - case StoreDataSchema::Version::VERSION_4: { - auto decryptedStoreData = decryptModuleDataV4(storeEntry, encKey); - return std::make_tuple( - convertDecryptedStoreDataV4ToStore(store, decryptedStoreData), - core::DataIntegrityObject{ - .creatorUserId = store.lastModifier, - .creatorPubKey = decryptedStoreData.authorPubKey, - .contextId = store.contextId, - .resourceId = store.resourceId.value_or(""), - .timestamp = store.lastModificationDate, - .randomId = std::string(), - .containerId = std::nullopt, - .containerResourceId = std::nullopt, - .bridgeIdentity = std::nullopt - } - ); - } - case StoreDataSchema::Version::VERSION_5: { - auto decryptedStoreData = decryptModuleDataV5(storeEntry, encKey); - return std::make_tuple(convertDecryptedStoreDataV5ToStore(store, decryptedStoreData), decryptedStoreData.dio); - } - } - auto e = UnknowStoreFormatException(); - return std::make_tuple(convertServerStoreToLibStore(store, {}, {}, e.getCode()), core::DataIntegrityObject()); -} - -std::vector StoreApiImpl::validateDecryptAndConvertStoresDataToStores(std::vector stores) { - std::vector result(stores.size()); - for (size_t i = 0; i < stores.size(); i++) { - auto store = stores[i]; - result[i].statusCode = validateStoreDataIntegrity(store); - if (result[i].statusCode != 0) { - result[i] = convertServerStoreToLibStore(store, {}, {}, result[i].statusCode); - } - } - core::KeyDecryptionAndVerificationRequest keyProviderRequest; - for (size_t i = 0; i < stores.size(); i++) { - auto store = stores[i]; - core::EncKeyLocation location{.contextId = store.contextId, .resourceId = store.resourceId.value_or("")}; - auto store_data_entry = store.data.back(); - keyProviderRequest.addOne(store.keys, store_data_entry.keyId, location); - } - auto storesKeys{_keyProvider->getKeysAndVerify(keyProviderRequest)}; - std::vector storesDIO(stores.size()); - std::map duplication_check; - for (size_t i = 0; i < stores.size(); i++) { - if (result[i].statusCode != 0) { - storesDIO.push_back(core::DataIntegrityObject{}); - } else { - auto store = stores[i]; - try { - auto tmp = decryptAndConvertStoreDataToStore( - store, store.data.back(), - storesKeys - .at(core::EncKeyLocation{ - .contextId = store.contextId, .resourceId = store.resourceId.value_or("") - }) - .at(store.data.back().keyId) - ); - result[i] = std::get<0>(tmp); - auto storeDIO = std::get<1>(tmp); - storesDIO[i] = storeDIO; - std::string fullRandomId = storeDIO.randomId + "-" + std::to_string(storeDIO.timestamp); - if (duplication_check.find(fullRandomId) == duplication_check.end()) { - duplication_check.insert(std::make_pair(fullRandomId, true)); - } else { - result[i].statusCode = core::DataIntegrityObjectDuplicatedException().getCode(); - } - } catch (const core::Exception& e) { - result[i] = convertServerStoreToLibStore(store, {}, {}, e.getCode()); - storesDIO[i] = core::DataIntegrityObject{}; - } - } - } - std::vector verifierInput{}; - for (size_t i = 0; i < result.size(); i++) { - if (result[i].statusCode == 0) { - verifierInput.push_back( - core::VerificationRequest{ - .contextId = result[i].contextId, - .senderId = result[i].lastModifier, - .senderPubKey = storesDIO[i].creatorPubKey, - .date = result[i].lastModificationDate, - .bridgeIdentity = storesDIO[i].bridgeIdentity - } - ); - } - } - std::vector verified; - verified = _connection.getImpl()->getUserVerifier()->verify(verifierInput); - for (size_t j = 0, i = 0; i < result.size(); i++) { - if (result[i].statusCode == 0) { - result[i].statusCode = verified[j] ? 0 : - core::ExceptionConverter::getCodeOfUserVerificationFailureException(); - j++; - } - } - return result; -} - -Store StoreApiImpl::validateDecryptAndConvertStoreDataToStore(server::Store store) { - - try { - auto statusCode = validateStoreDataIntegrity(store); - if (statusCode != 0) { - return convertServerStoreToLibStore(store, {}, {}, statusCode); - } - auto store_data_entry = store.data.back(); - core::KeyDecryptionAndVerificationRequest keyProviderRequest; - core::EncKeyLocation location{.contextId = store.contextId, .resourceId = store.resourceId.value_or("")}; - keyProviderRequest.addOne(store.keys, store_data_entry.keyId, location); - auto key = _keyProvider->getKeysAndVerify(keyProviderRequest).at(location).at(store_data_entry.keyId); - Store result; - core::DataIntegrityObject storeDIO; - std::tie(result, storeDIO) = decryptAndConvertStoreDataToStore(store, store_data_entry, key); - if (result.statusCode != 0) - return result; - std::vector verifierInput{}; - verifierInput.push_back( - core::VerificationRequest{ - .contextId = result.contextId, - .senderId = result.lastModifier, - .senderPubKey = storeDIO.creatorPubKey, - .date = result.lastModificationDate, - .bridgeIdentity = storeDIO.bridgeIdentity - } - ); - std::vector verified; - verified = _connection.getImpl()->getUserVerifier()->verify(verifierInput); - result.statusCode = verified[0] ? 0 : core::ExceptionConverter::getCodeOfUserVerificationFailureException(); - return result; - } catch (const core::Exception& e) { - return convertServerStoreToLibStore(store, {}, {}, e.getCode()); - } catch (const privmx::utils::PrivmxException& e) { - return convertServerStoreToLibStore(store, {}, {}, core::ExceptionConverter::convert(e).getCode()); - } catch (...) { return convertServerStoreToLibStore(store, {}, {}, ENDPOINT_CORE_EXCEPTION_CODE); } -} - FileEncryptionParams StoreApiImpl::getFileEncryptionParams(server::File file, const core::DecryptedEncKey& encKey) { File decryptedFile; core::DataIntegrityObject fileDIO; - std::tie(decryptedFile, fileDIO) = decryptAndConvertFileDataToFileInfo(file, core::DecryptedEncKey(encKey)); - auto internalMeta = decryptFileInternalMeta(file, core::DecryptedEncKey(encKey)); + std::tie(decryptedFile, fileDIO) = _fileMetaDataSchemaMapper.decrypt(file, core::DecryptedEncKey(encKey)); + auto internalMeta = _fileMetaDataSchemaMapper.decryptFileInternalMeta(file, core::DecryptedEncKey(encKey)); return FileEncryptionParams{ FileMeta{ .publicMeta = decryptedFile.publicMeta, @@ -1029,440 +742,10 @@ FileEncryptionParams StoreApiImpl::getFileEncryptionParams(server::File file, se return getFileEncryptionParams(file, key); } -// OLD CODE -StoreFile StoreApiImpl::decryptStoreFileV1(server::File file, const core::DecryptedEncKey& encKey) { - try { - auto storeFile = decryptAndVerifyFileV1(encKey.key, file); - return storeFile; - } catch (const privmx::endpoint::core::Exception& e) { - StoreFile result = {.raw = file, .meta = dynamic::compat_v1::StoreFileMeta{}, .verified = "invalid"}; - result.meta.statusCode = e.getCode(); - return result; - } catch (const privmx::utils::PrivmxException& e) { - StoreFile result = {.raw = file, .meta = dynamic::compat_v1::StoreFileMeta{}, .verified = "invalid"}; - result.meta.statusCode = core::ExceptionConverter::convert(e).getCode(); - return result; - } catch (...) { - StoreFile result = {.raw = file, .meta = dynamic::compat_v1::StoreFileMeta{}, .verified = "invalid"}; - result.meta.statusCode = ENDPOINT_CORE_EXCEPTION_CODE; - return result; - } -} -// OLD CODE -std::string StoreApiImpl::verifyFileV1Signature(FileMetaSigned meta, server::File raw, std::string& serverId) { - try { - if (meta.signature.length() == 0 || meta.meta.author.pubKey.empty() || meta.meta.destination.server.empty()) { - return std::string("no-signature"); - } - if (meta.meta.destination.server != serverId) { - return std::string("invalid"); - } - auto storeIdOrStore = meta.meta.destination.storeId.empty() ? meta.meta.destination.store : - meta.meta.destination.storeId; - if (storeIdOrStore != raw.id) { - return std::string("invalid"); - } - auto pubKey = privmx::crypto::PublicKey::fromBase58DER(meta.meta.author.pubKey); - auto result = pubKey.verifyCompactSignatureWithHash(meta.metaBuf, meta.signature); - return result ? std::string("verified") : std::string("invalid"); - } catch (...) { return std::string("invalid"); } -} -// OLD CODE -StoreFile StoreApiImpl::decryptAndVerifyFileV1(const std::string& filesKey, server::File x) { - StoreFile odp; - auto fileMetaSigned = _fileMetaEncryptorV1.decrypt(x.meta.convert(), filesKey); - odp.raw = x; - odp.meta = fileMetaSigned.meta; - odp.verified = verifyFileV1Signature(fileMetaSigned, x, _host); - return odp; -} - -DecryptedFileMetaV4 StoreApiImpl::decryptFileMetaV4(server::File file, const core::DecryptedEncKey& encKey) { - try { - auto encryptedFileMeta = server::EncryptedFileMetaV4::fromJSON(file.meta); - return _fileMetaEncryptorV4.decrypt(encryptedFileMeta, encKey.key); - } catch (const core::Exception& e) { - return DecryptedFileMetaV4( - {{.dataStructureVersion = FileDataSchema::Version::VERSION_4, .statusCode = e.getCode()}, - {}, - {}, - {}, - {}, - {}} - ); - } catch (const privmx::utils::PrivmxException& e) { - return DecryptedFileMetaV4( - {{.dataStructureVersion = FileDataSchema::Version::VERSION_4, - .statusCode = core::ExceptionConverter::convert(e).getCode()}, - {}, - {}, - {}, - {}, - {}} - ); - } catch (...) { - return DecryptedFileMetaV4( - {{.dataStructureVersion = FileDataSchema::Version::VERSION_4, .statusCode = ENDPOINT_CORE_EXCEPTION_CODE}, - {}, - {}, - {}, - {}, - {}} - ); - } -} - -DecryptedFileMetaV5 StoreApiImpl::decryptFileMetaV5(server::File file, const core::DecryptedEncKey& encKey) { - try { - auto encryptedFileMeta = server::EncryptedFileMetaV5::fromJSON(file.meta); - if (encKey.statusCode != 0) { - auto tmp = _fileMetaEncryptorV5.extractPublic(encryptedFileMeta); - tmp.statusCode = encKey.statusCode; - return tmp; - } else { - return _fileMetaEncryptorV5.decrypt(encryptedFileMeta, encKey.key); - } - } catch (const core::Exception& e) { - return DecryptedFileMetaV5( - {{.dataStructureVersion = FileDataSchema::Version::VERSION_5, .statusCode = e.getCode()}, - {}, - {}, - {}, - {}, - {}} - ); - } catch (const privmx::utils::PrivmxException& e) { - return DecryptedFileMetaV5( - {{.dataStructureVersion = FileDataSchema::Version::VERSION_5, - .statusCode = core::ExceptionConverter::convert(e).getCode()}, - {}, - {}, - {}, - {}, - {}} - ); - } catch (...) { - return DecryptedFileMetaV5( - {{.dataStructureVersion = FileDataSchema::Version::VERSION_5, .statusCode = ENDPOINT_CORE_EXCEPTION_CODE}, - {}, - {}, - {}, - {}, - {}} - ); - } -} - -File StoreApiImpl::convertServerFileToLibFile( - server::File file, - const core::Buffer& publicMeta, - const core::Buffer& privateMeta, - const int64_t& size, - const std::string& authorPubKey, - const int64_t& statusCode, - const int64_t& schemaVersion, - const bool& randomWrite -) { - return File{ - .info = - { - .storeId = file.storeId, - .fileId = file.id, - .createDate = file.created, - .author = file.creator, - }, - .publicMeta = publicMeta, - .privateMeta = privateMeta, - .size = size, - .authorPubKey = authorPubKey, - .statusCode = statusCode, - .schemaVersion = schemaVersion, - .randomWrite = randomWrite - }; -} - -File StoreApiImpl::convertStoreFileMetaV1ToFile(server::File file, dynamic::compat_v1::StoreFileMeta storeFileMeta) { - return convertServerFileToLibFile( - file, core::Buffer(), core::Buffer::from(storeFileMeta.serialize()), storeFileMeta.size, - storeFileMeta.author.pubKey, storeFileMeta.statusCode, FileDataSchema::Version::VERSION_1, false - ); -} - -File StoreApiImpl::convertDecryptedFileMetaV4ToFile(server::File file, const DecryptedFileMetaV4& fileData) { - bool randomWrite = false; - auto statusCode = fileData.statusCode; - if (statusCode == 0) { - try { - auto internalMeta = dynamic::InternalStoreFileMeta::fromJSON( - utils::Utils::parseJson(fileData.internalMeta.stdString()) - ); - randomWrite = internalMeta.randomWrite.value_or(false); - } catch (const privmx::endpoint::core::Exception& e) { - statusCode = e.getCode(); - } catch (const privmx::utils::PrivmxException& e) { - statusCode = core::ExceptionConverter::convert(e).getCode(); - } catch (...) { statusCode = ENDPOINT_CORE_EXCEPTION_CODE; } - } - return convertServerFileToLibFile( - file, fileData.publicMeta, fileData.privateMeta, fileData.fileSize, fileData.authorPubKey, fileData.statusCode, - FileDataSchema::Version::VERSION_4, randomWrite - ); -} - -File StoreApiImpl::convertDecryptedFileMetaV5ToFile(server::File file, const DecryptedFileMetaV5& fileData) { - int64_t size = 0; - bool randomWrite = false; - auto statusCode = fileData.statusCode; - if (statusCode == 0) { - try { - auto internalMeta = dynamic::InternalStoreFileMeta::fromJSON( - utils::Utils::parseJson(fileData.internalMeta.stdString()) - ); - size = internalMeta.size; - randomWrite = internalMeta.randomWrite.value_or(false); - } catch (const privmx::endpoint::core::Exception& e) { - statusCode = e.getCode(); - } catch (const privmx::utils::PrivmxException& e) { - statusCode = core::ExceptionConverter::convert(e).getCode(); - } catch (...) { statusCode = ENDPOINT_CORE_EXCEPTION_CODE; } - } - return convertServerFileToLibFile( - file, fileData.publicMeta, fileData.privateMeta, size, fileData.authorPubKey, statusCode, - FileDataSchema::Version::VERSION_5, randomWrite - ); -} - -FileDataSchema::Version StoreApiImpl::getFileDataStructureVersion(server::File file) { - if (file.meta.type() == typeid(Poco::JSON::Object::Ptr)) { - auto versioned = core::dynamic::VersionedData::fromJSON(file.meta); - switch (versioned.version) { - case FileDataSchema::Version::VERSION_4: - return FileDataSchema::Version::VERSION_4; - case FileDataSchema::Version::VERSION_5: - return FileDataSchema::Version::VERSION_5; - default: - return FileDataSchema::Version::UNKNOWN; - } - } else if (file.meta.isString()) { - return FileDataSchema::Version::VERSION_1; - } - return FileDataSchema::Version::UNKNOWN; -} - -std::tuple StoreApiImpl::decryptAndConvertFileDataToFileInfo( - server::File file, - const core::DecryptedEncKey& encKey -) { - switch (getFileDataStructureVersion(file)) { - case FileDataSchema::Version::UNKNOWN: { - auto e = UnknowFileFormatException(); - return std::make_tuple( - convertServerFileToLibFile(file, {}, {}, {}, {}, e.getCode()), core::DataIntegrityObject() - ); - } - case FileDataSchema::Version::VERSION_1: { - auto decryptedFile = decryptStoreFileV1(file, encKey).meta; - return std::make_tuple( - convertStoreFileMetaV1ToFile(file, decryptedFile), - core::DataIntegrityObject{ - .creatorUserId = file.lastModifier, - .creatorPubKey = decryptedFile.author.pubKey, - .contextId = file.contextId, - .resourceId = file.resourceId, - .timestamp = file.lastModificationDate, - .randomId = std::string(), - .containerId = file.storeId, - .containerResourceId = std::string(), - .bridgeIdentity = std::nullopt - } - ); - } - case FileDataSchema::Version::VERSION_4: { - auto decryptedFile = decryptFileMetaV4(file, encKey); - return std::make_tuple( - convertDecryptedFileMetaV4ToFile(file, decryptedFile), - core::DataIntegrityObject{ - .creatorUserId = file.lastModifier, - .creatorPubKey = decryptedFile.authorPubKey, - .contextId = file.contextId, - .resourceId = file.resourceId, - .timestamp = file.lastModificationDate, - .randomId = std::string(), - .containerId = file.storeId, - .containerResourceId = std::string(), - .bridgeIdentity = std::nullopt - } - ); - } - case FileDataSchema::Version::VERSION_5: { - auto decryptedFile = decryptFileMetaV5(file, encKey); - return std::make_tuple( - convertDecryptedFileMetaV5ToFile(file, decryptFileMetaV5(file, encKey)), decryptedFile.dio - ); - } - } - auto e = UnknowFileFormatException(); - return std::make_tuple(convertServerFileToLibFile(file, {}, {}, {}, {}, e.getCode()), core::DataIntegrityObject()); -} - -std::vector StoreApiImpl::validateDecryptAndConvertFilesDataToFilesInfo( - std::vector files, - const core::ModuleKeys& storeKeys -) { - std::set keyIds; - for (auto file : files) { - keyIds.insert(file.keyId); - } - core::KeyDecryptionAndVerificationRequest keyProviderRequest; - core::EncKeyLocation location{.contextId = storeKeys.contextId, .resourceId = storeKeys.moduleResourceId}; - keyProviderRequest.addMany(storeKeys.keys, keyIds, location); - auto keyMap = _keyProvider->getKeysAndVerify(keyProviderRequest).at(location); - std::vector result; - std::vector filesDIO; - std::map duplication_check; - for (auto file : files) { - try { - auto statusCode = validateFileDataIntegrity(file, storeKeys.moduleResourceId); - if (statusCode == 0) { - auto tmp = decryptAndConvertFileDataToFileInfo(file, keyMap.at(file.keyId)); - result.push_back(std::get<0>(tmp)); - auto fileDIO = std::get<1>(tmp); - filesDIO.push_back(fileDIO); - std::string fullRandomId = fileDIO.randomId + "-" + std::to_string(fileDIO.timestamp); - if (duplication_check.find(fullRandomId) == duplication_check.end()) { - duplication_check.insert(std::make_pair(fullRandomId, true)); - } else { - result[result.size() - 1].statusCode = core::DataIntegrityObjectDuplicatedException().getCode(); - } - } else { - result.push_back(convertServerFileToLibFile(file, {}, {}, {}, {}, statusCode)); - } - } catch (const core::Exception& e) { - result.push_back(convertServerFileToLibFile(file, {}, {}, {}, {}, e.getCode())); - } - } - std::vector verifierInput{}; - for (size_t i = 0; i < result.size(); i++) { - if (result[i].statusCode == 0) { - verifierInput.push_back( - core::VerificationRequest{ - .contextId = storeKeys.contextId, - .senderId = result[i].info.author, - .senderPubKey = result[i].authorPubKey, - .date = result[i].info.createDate, - .bridgeIdentity = filesDIO[i].bridgeIdentity - } - ); - } - } - std::vector verified; - verified = _connection.getImpl()->getUserVerifier()->verify(verifierInput); - for (size_t j = 0, i = 0; i < result.size(); ++i) { - if (result[i].statusCode == 0) { - result[i].statusCode = verified[j] ? 0 : - core::ExceptionConverter::getCodeOfUserVerificationFailureException(); - j++; - } - } - return result; -} - -File StoreApiImpl::validateDecryptAndConvertFileDataToFileInfo(server::File file, const core::ModuleKeys& storeKeys) { - try { - auto keyId = file.keyId; - _fileKeyIdFormatValidator.assertKeyIdFormat(keyId); - core::KeyDecryptionAndVerificationRequest keyProviderRequest; - core::EncKeyLocation location{.contextId = file.contextId, .resourceId = storeKeys.moduleResourceId}; - keyProviderRequest.addOne(storeKeys.keys, keyId, location); - auto encKey = _keyProvider->getKeysAndVerify(keyProviderRequest).at(location).at(keyId); - File result; - core::DataIntegrityObject fileDIO; - std::tie(result, fileDIO) = decryptAndConvertFileDataToFileInfo(file, encKey); - if (result.statusCode != 0) - return result; - std::vector verifierInput{}; - verifierInput.push_back( - core::VerificationRequest{ - .contextId = file.contextId, - .senderId = result.info.author, - .senderPubKey = result.authorPubKey, - .date = result.info.createDate, - .bridgeIdentity = fileDIO.bridgeIdentity - } - ); - std::vector verified; - verified = _connection.getImpl()->getUserVerifier()->verify(verifierInput); - result.statusCode = verified[0] ? 0 : core::ExceptionConverter::getCodeOfUserVerificationFailureException(); - return result; - } catch (const core::Exception& e) { - return convertServerFileToLibFile(file, {}, {}, {}, {}, e.getCode()); - } catch (const privmx::utils::PrivmxException& e) { - return convertServerFileToLibFile(file, {}, {}, {}, {}, core::ExceptionConverter::convert(e).getCode()); - } catch (...) { return convertServerFileToLibFile(file, {}, {}, {}, {}, ENDPOINT_CORE_EXCEPTION_CODE); } -} - -dynamic::InternalStoreFileMeta StoreApiImpl::decryptFileInternalMeta( - server::File file, - const core::DecryptedEncKey& encKey -) { - if (encKey.statusCode == 0) { - switch (getFileDataStructureVersion(file)) { - case FileDataSchema::Version::UNKNOWN: { - throw UnknowFileFormatException(); - } - case FileDataSchema::Version::VERSION_1: { - auto decryptedFile = decryptStoreFileV1(file, encKey); - dynamic::InternalStoreFileMeta internalFileMeta; - internalFileMeta.version = 1; - internalFileMeta.size = decryptedFile.meta.size; - internalFileMeta.cipherType = decryptedFile.meta.cipherType; - internalFileMeta.chunkSize = decryptedFile.meta.chunkSize; - internalFileMeta.key = utils::Base64::from(decryptedFile.meta.key); - internalFileMeta.hmac = utils::Base64::from(decryptedFile.meta.hmac); - return internalFileMeta; - } - case FileDataSchema::Version::VERSION_4: - return dynamic::InternalStoreFileMeta::fromJSON( - utils::Utils::parseJson(decryptFileMetaV4(file, encKey).internalMeta.stdString()) - ); - case FileDataSchema::Version::VERSION_5: - return dynamic::InternalStoreFileMeta::fromJSON( - utils::Utils::parseJson(decryptFileMetaV5(file, encKey).internalMeta.stdString()) - ); - } - } - throw UnknowFileFormatException(); -} - -dynamic::InternalStoreFileMeta StoreApiImpl::validateDecryptFileInternalMeta( - server::File file, - const core::ModuleKeys& storeKeys -) { - auto keyId = file.keyId; - core::KeyDecryptionAndVerificationRequest keyProviderRequest; - core::EncKeyLocation location{.contextId = file.contextId, .resourceId = storeKeys.moduleResourceId}; - keyProviderRequest.addOne(storeKeys.keys, keyId, location); - auto encKey = _keyProvider->getKeysAndVerify(keyProviderRequest).at(location).at(keyId); - _fileKeyIdFormatValidator.assertKeyIdFormat(keyId); - return decryptFileInternalMeta(file, encKey); -} - core::ModuleKeys StoreApiImpl::getFileDecryptionKeys(server::File file) { - auto keyId = file.keyId; - store::StoreDataSchema::Version minimumStoreSchemaVersion; - switch (getFileDataStructureVersion(file)) { - case store::FileDataSchema::Version::UNKNOWN: - minimumStoreSchemaVersion = store::StoreDataSchema::UNKNOWN; - break; - case store::FileDataSchema::Version::VERSION_1: - case store::FileDataSchema::Version::VERSION_4: - minimumStoreSchemaVersion = store::StoreDataSchema::VERSION_1; - break; - case store::FileDataSchema::Version::VERSION_5: - minimumStoreSchemaVersion = store::StoreDataSchema::VERSION_5; - break; - } - return getModuleKeys(file.storeId, std::set{keyId}, minimumStoreSchemaVersion); + return getModuleKeys( + file.storeId, std::set{file.keyId}, _fileMetaDataSchemaMapper.getMinimumStoreSchemaVersion(file) + ); } void StoreApiImpl::updateFileMeta( @@ -1477,7 +760,7 @@ void StoreApiImpl::updateFileMeta( server::Store store = storeFileGetResult.store; setNewModuleKeysInCache(store.id, storeToModuleKeys(store), store.version); server::File file = storeFileGetResult.file; - auto statusCode = validateFileDataIntegrity(file, store.resourceId.value_or("")); + auto statusCode = _fileMetaDataSchemaMapper.validateDataIntegrity(file, store.resourceId.value_or("")); if (statusCode != 0) { throw FileDataIntegrityException(); } @@ -1487,35 +770,14 @@ void StoreApiImpl::updateFileMeta( "Current encryption key statusCode: " + std::to_string(key.statusCode) ); } - Poco::Dynamic::Var encryptedMetaVar; - auto fileInternalMeta = validateDecryptFileInternalMeta(file, storeToModuleKeys(store)); + auto fileInternalMeta = _fileMetaDataSchemaMapper.validateDecryptFileInternalMeta( + file, storeToModuleKeys(store), _keyProvider + ); auto internalMeta = core::Buffer::from(fileInternalMeta.serialize()); - switch (getStoreEntryDataStructureVersion(store.data.back())) { - case StoreDataSchema::Version::UNKNOWN: - throw UnknowStoreFormatException(); - case StoreDataSchema::Version::VERSION_1: - case StoreDataSchema::Version::VERSION_4: { - store::FileMetaToEncryptV4 fileMeta{ - .publicMeta = publicMeta, - .privateMeta = privateMeta, - .fileSize = fileInternalMeta.size, - .internalMeta = internalMeta - }; - encryptedMetaVar = _fileMetaEncryptorV4.encrypt(fileMeta, _userPrivKey, key.key).toJSON(); - break; - } - case StoreDataSchema::Version::VERSION_5: { - privmx::endpoint::core::DataIntegrityObject fileDIO = _connection.getImpl()->createDIO( - file.contextId, file.resourceId.empty() ? core::EndpointUtils::generateId() : file.resourceId, file.storeId, - store.resourceId - ); - store::FileMetaToEncryptV5 fileMeta{ - .publicMeta = publicMeta, .privateMeta = privateMeta, .internalMeta = internalMeta, .dio = fileDIO - }; - encryptedMetaVar = _fileMetaEncryptorV5.encrypt(fileMeta, _userPrivKey, key.key).toJSON(); - break; - } - } + auto encryptedMetaVar = _fileMetaDataSchemaMapper.encrypt( + file.storeId, file.resourceId.empty() ? core::EndpointUtils::generateId() : file.resourceId, file.contextId, + store.resourceId.value_or(""), publicMeta, privateMeta, internalMeta, key + ); server::StoreFileUpdateModel storeFileUpdateModel; storeFileUpdateModel.fileId = fileId; storeFileUpdateModel.meta = encryptedMetaVar; @@ -1536,7 +798,7 @@ void StoreApiImpl::assertFileExist(const std::string& fileId) { std::pair StoreApiImpl::getModuleKeysAndVersionFromServer(std::string moduleId) { store::server::StoreGetModel params{.storeId = moduleId, .type = std::nullopt}; auto store = _serverApi->storeGet(params).store; - assertStoreDataIntegrity(store); + _storeDataSchemaMapper.assertDataIntegrity(store); return std::make_pair(storeToModuleKeys(store), store.version); } @@ -1544,79 +806,12 @@ core::ModuleKeys StoreApiImpl::storeToModuleKeys(server::Store store) { return core::ModuleKeys{ .keys = store.keys, .currentKeyId = store.keyId, - .moduleSchemaVersion = getStoreEntryDataStructureVersion(store.data.back()), + .moduleSchemaVersion = _storeDataSchemaMapper.getDataStructureVersion(store.data.back()), .moduleResourceId = store.resourceId.value_or(""), .contextId = store.contextId }; } -void StoreApiImpl::assertStoreDataIntegrity(server::Store store) { - auto store_data_entry = store.data.back(); - switch (getStoreEntryDataStructureVersion(store_data_entry)) { - case StoreDataSchema::Version::UNKNOWN: - throw UnknowStoreFormatException(); - case StoreDataSchema::Version::VERSION_1: - return; - case StoreDataSchema::Version::VERSION_4: - return; - case StoreDataSchema::Version::VERSION_5: { - auto store_data = core::dynamic::EncryptedModuleDataV5::fromJSON(store_data_entry.data); - auto dio = _storeDataEncryptorV5.getDIOAndAssertIntegrity(store_data); - if (dio.contextId != store.contextId || - dio.resourceId != store.resourceId || - dio.creatorUserId != store.lastModifier || - !core::TimestampValidator::validate(dio.timestamp, store.lastModificationDate)) { - throw StoreDataIntegrityException(); - } - return; - } - } - throw UnknowStoreFormatException(); -} - -uint32_t StoreApiImpl::validateStoreDataIntegrity(server::Store store) { - try { - assertStoreDataIntegrity(store); - return 0; - } catch (const core::Exception& e) { return e.getCode(); } catch (const privmx::utils::PrivmxException& e) { - return core::ExceptionConverter::convert(e).getCode(); - ; - } catch (...) { return ENDPOINT_CORE_EXCEPTION_CODE; } - return UnknowStoreFormatException().getCode(); -} - -uint32_t StoreApiImpl::validateFileDataIntegrity(server::File file, const std::string& storeResourceId) { - try { - switch (getFileDataStructureVersion(file)) { - case FileDataSchema::Version::UNKNOWN: - return UnknowFileFormatException().getCode(); - case FileDataSchema::Version::VERSION_1: - return 0; - case FileDataSchema::Version::VERSION_4: - return 0; - case FileDataSchema::Version::VERSION_5: { - auto fileMeta = server::EncryptedFileMetaV5::fromJSON(file.meta); - auto dio = _fileMetaEncryptorV5.getDIOAndAssertIntegrity(fileMeta); - if (dio.contextId != file.contextId || - dio.resourceId != file.resourceId || - !dio.containerId.has_value() || - dio.containerId.value() != file.storeId || - !dio.containerResourceId.has_value() || - dio.containerResourceId.value() != storeResourceId || - dio.creatorUserId != file.lastModifier || - !core::TimestampValidator::validate(dio.timestamp, file.lastModificationDate)) { - return FileDataIntegrityException().getCode(); - ; - } - return 0; - } - } - } catch (const core::Exception& e) { return e.getCode(); } catch (const privmx::utils::PrivmxException& e) { - return core::ExceptionConverter::convert(e).getCode(); - } catch (...) { return ENDPOINT_CORE_EXCEPTION_CODE; } - return UnknowFileFormatException().getCode(); -} - std::vector StoreApiImpl::subscribeFor(const std::vector& subscriptionQueries) { auto result = _subscriber.subscribeFor(subscriptionQueries); _eventMiddleware->notificationEventListenerAddSubscriptionIds(_notificationListenerId, result); diff --git a/endpoint/store/src/encryptors/file/FileDataSchemaStrategyV4.cpp b/endpoint/store/src/encryptors/file/FileDataSchemaStrategyV4.cpp new file mode 100644 index 00000000..a744f80b --- /dev/null +++ b/endpoint/store/src/encryptors/file/FileDataSchemaStrategyV4.cpp @@ -0,0 +1,128 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "privmx/endpoint/store/encryptors/file/FileDataSchemaStrategyV4.hpp" +#include "privmx/endpoint/store/encryptors/file/FileMetaDataSchemaMapper.hpp" + +#include +#include +#include + +#include "privmx/endpoint/store/Constants.hpp" +#include "privmx/endpoint/store/DynamicTypes.hpp" + +using namespace privmx::endpoint; +using namespace privmx::endpoint::store; + +DecryptedFileMetaV4 FileDataSchemaStrategyV4::decrypt( + const server::File& file, + const core::DecryptedEncKey& encKey +) const { + auto encryptedFileMeta = server::EncryptedFileMetaV4::fromJSON(file.meta); + return _encryptor.decrypt(encryptedFileMeta, encKey.key); +} + +DecryptedFileMetaV4 FileDataSchemaStrategyV4::decryptFileMeta( + const server::File& file, + const core::DecryptedEncKey& encKey +) const { + try { + return decrypt(file, encKey); + } catch (const core::Exception& e) { + return DecryptedFileMetaV4( + {{.dataStructureVersion = FileDataSchema::Version::VERSION_4, .statusCode = e.getCode()}, + {}, + {}, + {}, + {}, + {}} + ); + } catch (const privmx::utils::PrivmxException& e) { + return DecryptedFileMetaV4( + {{.dataStructureVersion = FileDataSchema::Version::VERSION_4, + .statusCode = core::ExceptionConverter::convert(e).getCode()}, + {}, + {}, + {}, + {}, + {}} + ); + } catch (...) { + return DecryptedFileMetaV4( + {{.dataStructureVersion = FileDataSchema::Version::VERSION_4, .statusCode = ENDPOINT_CORE_EXCEPTION_CODE}, + {}, + {}, + {}, + {}, + {}} + ); + } +} + +std::tuple FileDataSchemaStrategyV4::convert( + const server::File& file, + const DecryptedFileMetaV4& raw +) const { + bool randomWrite = false; + auto statusCode = raw.statusCode; + if (statusCode == 0) { + try { + auto internalMeta = dynamic::InternalStoreFileMeta::fromJSON( + utils::Utils::parseJson(raw.internalMeta.stdString()) + ); + randomWrite = internalMeta.randomWrite.value_or(false); + } catch (const core::Exception& e) { + statusCode = e.getCode(); + } catch (const privmx::utils::PrivmxException& e) { + statusCode = core::ExceptionConverter::convert(e).getCode(); + } catch (...) { statusCode = ENDPOINT_CORE_EXCEPTION_CODE; } + } + return { + FileMetaDataSchemaMapper::toLibFile( + file, raw.publicMeta, raw.privateMeta, raw.fileSize, raw.authorPubKey, statusCode, + FileDataSchema::Version::VERSION_4, randomWrite + ), + core::DataIntegrityObject{ + .creatorUserId = file.lastModifier, + .creatorPubKey = raw.authorPubKey, + .contextId = file.contextId, + .resourceId = file.resourceId, + .timestamp = file.lastModificationDate, + .randomId = std::string(), + .containerId = file.storeId, + .containerResourceId = std::string(), + .bridgeIdentity = std::nullopt + } + }; +} + +server::EncryptedFileMetaV4 FileDataSchemaStrategyV4::encrypt( + const core::Buffer& publicMeta, + const core::Buffer& privateMeta, + const core::Buffer& internalMeta, + const privmx::crypto::PrivateKey& userPrivKey, + const std::string& key +) const { + FileMetaToEncryptV4 fileMeta{ + .publicMeta = publicMeta, .privateMeta = privateMeta, .fileSize = 0, .internalMeta = internalMeta + }; + return _encryptor.encrypt(fileMeta, userPrivKey, key); +} + +std::tuple FileDataSchemaStrategyV4::makeErrorResult( + const server::File& file, + int64_t errorCode +) const { + return { + FileMetaDataSchemaMapper::toLibFile(file, {}, {}, 0, {}, errorCode, FileDataSchema::Version::VERSION_4, false), + core::DataIntegrityObject{} + }; +} diff --git a/endpoint/store/src/encryptors/file/FileDataSchemaStrategyV5.cpp b/endpoint/store/src/encryptors/file/FileDataSchemaStrategyV5.cpp new file mode 100644 index 00000000..9dbd453d --- /dev/null +++ b/endpoint/store/src/encryptors/file/FileDataSchemaStrategyV5.cpp @@ -0,0 +1,132 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "privmx/endpoint/store/encryptors/file/FileDataSchemaStrategyV5.hpp" +#include "privmx/endpoint/store/encryptors/file/FileMetaDataSchemaMapper.hpp" + +#include +#include +#include + +#include "privmx/endpoint/store/Constants.hpp" +#include "privmx/endpoint/store/DynamicTypes.hpp" + +using namespace privmx::endpoint; +using namespace privmx::endpoint::store; + +DecryptedFileMetaV5 FileDataSchemaStrategyV5::decrypt( + const server::File& file, + const core::DecryptedEncKey& encKey +) const { + auto encryptedFileMeta = server::EncryptedFileMetaV5::fromJSON(file.meta); + if (encKey.statusCode != 0) { + auto result = _encryptor.extractPublic(encryptedFileMeta); + result.statusCode = encKey.statusCode; + return result; + } + return _encryptor.decrypt(encryptedFileMeta, encKey.key); +} + +DecryptedFileMetaV5 FileDataSchemaStrategyV5::decryptFileMeta( + const server::File& file, + const core::DecryptedEncKey& encKey +) const { + try { + return decrypt(file, encKey); + } catch (const core::Exception& e) { + return DecryptedFileMetaV5( + {{.dataStructureVersion = FileDataSchema::Version::VERSION_5, .statusCode = e.getCode()}, + {}, + {}, + {}, + {}, + {}} + ); + } catch (const privmx::utils::PrivmxException& e) { + return DecryptedFileMetaV5( + {{.dataStructureVersion = FileDataSchema::Version::VERSION_5, + .statusCode = core::ExceptionConverter::convert(e).getCode()}, + {}, + {}, + {}, + {}, + {}} + ); + } catch (...) { + return DecryptedFileMetaV5( + {{.dataStructureVersion = FileDataSchema::Version::VERSION_5, .statusCode = ENDPOINT_CORE_EXCEPTION_CODE}, + {}, + {}, + {}, + {}, + {}} + ); + } +} + +std::tuple FileDataSchemaStrategyV5::convert( + const server::File& file, + const DecryptedFileMetaV5& raw +) const { + int64_t size = 0; + bool randomWrite = false; + auto statusCode = raw.statusCode; + if (statusCode == 0) { + try { + auto internalMeta = dynamic::InternalStoreFileMeta::fromJSON( + utils::Utils::parseJson(raw.internalMeta.stdString()) + ); + size = internalMeta.size; + randomWrite = internalMeta.randomWrite.value_or(false); + } catch (const core::Exception& e) { + statusCode = e.getCode(); + } catch (const privmx::utils::PrivmxException& e) { + statusCode = core::ExceptionConverter::convert(e).getCode(); + } catch (...) { statusCode = ENDPOINT_CORE_EXCEPTION_CODE; } + } + return { + FileMetaDataSchemaMapper::toLibFile( + file, raw.publicMeta, raw.privateMeta, size, raw.authorPubKey, statusCode, + FileDataSchema::Version::VERSION_5, randomWrite + ), + raw.dio + }; +} + +std::tuple FileDataSchemaStrategyV5::makeErrorResult( + const server::File& file, + int64_t errorCode +) const { + return { + FileMetaDataSchemaMapper::toLibFile(file, {}, {}, 0, {}, errorCode, FileDataSchema::Version::VERSION_5, false), + core::DataIntegrityObject{} + }; +} + +server::EncryptedFileMetaV5 FileDataSchemaStrategyV5::encrypt( + const core::Buffer& publicMeta, + const core::Buffer& privateMeta, + const core::Buffer& internalMeta, + const privmx::crypto::PrivateKey& userPrivKey, + const std::string& key, + const core::DataIntegrityObject& dio +) const { + FileMetaToEncryptV5 fileMeta{ + .publicMeta = publicMeta, .privateMeta = privateMeta, .internalMeta = internalMeta, .dio = dio + }; + return _encryptor.encrypt(fileMeta, userPrivKey, key); +} + +core::DataIntegrityObject FileDataSchemaStrategyV5::getDIOAndAssertIntegrity( + const server::EncryptedFileMetaV5& encData +) const { + return _encryptor.getDIOAndAssertIntegrity(encData); +} diff --git a/endpoint/store/src/encryptors/file/FileMetaDataSchemaMapper.cpp b/endpoint/store/src/encryptors/file/FileMetaDataSchemaMapper.cpp new file mode 100644 index 00000000..928c1b34 --- /dev/null +++ b/endpoint/store/src/encryptors/file/FileMetaDataSchemaMapper.cpp @@ -0,0 +1,309 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "privmx/endpoint/store/encryptors/file/FileMetaDataSchemaMapper.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "privmx/endpoint/store/StoreException.hpp" + +using namespace privmx::endpoint; +using namespace privmx::endpoint::store; + +FileMetaDataSchemaMapper::FileMetaDataSchemaMapper( + const privmx::crypto::PrivateKey& userPrivKey, + const core::Connection& connection +) + : _userPrivKey(userPrivKey), _connection(connection) { + _strategyV4 = std::make_shared(); + _strategyMapper.registerStrategy(FileDataSchema::Version::VERSION_4, _strategyV4); + _strategyV5 = std::make_shared(); + _strategyMapper.registerStrategy(FileDataSchema::Version::VERSION_5, _strategyV5); +} + +Poco::Dynamic::Var FileMetaDataSchemaMapper::encrypt( + const std::string& storeId, + const std::string& fileResourceId, + const std::string& contextId, + const std::string& storeResourceId, + const core::Buffer& publicMeta, + const core::Buffer& privateMeta, + const core::Buffer& internalMeta, + const core::DecryptedEncKeyV2& fileKey +) { + switch (fileKey.dataStructureVersion) { + case core::EncryptionKeyDataSchema::Version::UNKNOWN: + throw UnknowFileFormatException(); + case core::EncryptionKeyDataSchema::Version::VERSION_1: + return _strategyV4->encrypt(publicMeta, privateMeta, internalMeta, _userPrivKey, fileKey.key).toJSON(); + case core::EncryptionKeyDataSchema::Version::VERSION_2: { + auto fileDIO = _connection.getImpl()->createDIO(contextId, fileResourceId, storeId, storeResourceId); + return _strategyV5->encrypt(publicMeta, privateMeta, internalMeta, _userPrivKey, fileKey.key, fileDIO).toJSON(); + } + } + throw UnknowFileFormatException(); +} + +std::tuple FileMetaDataSchemaMapper::decrypt( + const server::File& file, + const core::DecryptedEncKey& encKey +) { + auto version = getDataStructureVersion(file); + auto strategy = _strategyMapper.getStrategy(static_cast(version)); + if (!strategy) { + auto e = UnknowFileFormatException(); + return { + toLibFile(file, {}, {}, 0, {}, e.getCode(), FileDataSchema::Version::UNKNOWN, false), + core::DataIntegrityObject{} + }; + } + return strategy->decryptAndConvert(file, encKey); +} + +StoreDataSchema::Version FileMetaDataSchemaMapper::getMinimumStoreSchemaVersion(const server::File& file) { + switch (getDataStructureVersion(file)) { + case FileDataSchema::Version::VERSION_4: + return StoreDataSchema::VERSION_4; + case FileDataSchema::Version::VERSION_5: + return StoreDataSchema::VERSION_5; + default: + return StoreDataSchema::UNKNOWN; + } +} + +FileDataSchema::Version FileMetaDataSchemaMapper::getDataStructureVersion(const server::File& file) { + if (file.meta.type() == typeid(Poco::JSON::Object::Ptr)) { + auto versioned = core::dynamic::VersionedData::fromJSON(file.meta); + switch (versioned.version) { + case FileDataSchema::Version::VERSION_4: + return FileDataSchema::Version::VERSION_4; + case FileDataSchema::Version::VERSION_5: + return FileDataSchema::Version::VERSION_5; + default: + return FileDataSchema::Version::UNKNOWN; + } + } + return FileDataSchema::Version::UNKNOWN; +} + +uint32_t FileMetaDataSchemaMapper::validateDataIntegrity(const server::File& file, const std::string& storeResourceId) { + try { + switch (getDataStructureVersion(file)) { + case FileDataSchema::Version::VERSION_4: + return 0; + case FileDataSchema::Version::VERSION_5: { + auto fileMeta = server::EncryptedFileMetaV5::fromJSON(file.meta); + auto dio = _strategyV5->getDIOAndAssertIntegrity(fileMeta); + if (dio.contextId != file.contextId || + dio.resourceId != file.resourceId || + !dio.containerId.has_value() || + dio.containerId.value() != file.storeId || + !dio.containerResourceId.has_value() || + dio.containerResourceId.value() != storeResourceId || + dio.creatorUserId != file.lastModifier || + !core::TimestampValidator::validate(dio.timestamp, file.lastModificationDate)) { + return FileDataIntegrityException().getCode(); + } + return 0; + } + default: + return UnknowFileFormatException().getCode(); + } + } catch (const core::Exception& e) { return e.getCode(); } catch (const privmx::utils::PrivmxException& e) { + return core::ExceptionConverter::convert(e).getCode(); + } catch (...) { return ENDPOINT_CORE_EXCEPTION_CODE; } +} + +DecryptedFileMetaV5 FileMetaDataSchemaMapper::decryptFileMetaV5( + const server::File& file, + const core::DecryptedEncKey& encKey +) { + return _strategyV5->decryptFileMeta(file, encKey); +} + +DecryptedFileMetaV4 FileMetaDataSchemaMapper::decryptFileMetaV4( + const server::File& file, + const core::DecryptedEncKey& encKey +) { + return _strategyV4->decryptFileMeta(file, encKey); +} + +dynamic::InternalStoreFileMeta FileMetaDataSchemaMapper::decryptFileInternalMeta( + const server::File& file, + const core::DecryptedEncKey& encKey +) { + if (encKey.statusCode == 0) { + switch (getDataStructureVersion(file)) { + case FileDataSchema::Version::VERSION_4: + return dynamic::InternalStoreFileMeta::fromJSON( + utils::Utils::parseJson(decryptFileMetaV4(file, encKey).internalMeta.stdString()) + ); + case FileDataSchema::Version::VERSION_5: + return dynamic::InternalStoreFileMeta::fromJSON( + utils::Utils::parseJson(decryptFileMetaV5(file, encKey).internalMeta.stdString()) + ); + default: + throw UnknowFileFormatException(); + } + } + throw UnknowFileFormatException(); +} + +std::vector FileMetaDataSchemaMapper::validateDecryptAndConvertFiles( + const std::vector& files, + const core::ModuleKeys& storeKeys, + const std::shared_ptr& keyProvider +) { + if (files.size() == 0) { + return std::vector{}; + } + std::vector result(files.size()); + std::vector filesDIO(files.size()); + std::set seenRandomIds; + + // integrity validation + for (size_t i = 0; i < files.size(); i++) { + auto code = validateDataIntegrity(files[i], storeKeys.moduleResourceId); + + if (code != 0) { + result[i] = toLibFile(files[i], {}, {}, 0, {}, code, FileDataSchema::Version::UNKNOWN, false); + } else { + result[i].statusCode = 0; + } + } + + // batch key fetch with per-file key ID format validation + const core::EncKeyLocation location{.contextId = storeKeys.contextId, .resourceId = storeKeys.moduleResourceId}; + core::KeyDecryptionAndVerificationRequest keyRequest; + for (size_t i = 0; i < files.size(); i++) { + if (result[i].statusCode != 0) { + continue; + } + try { + _fileKeyIdFormatValidator.assertKeyIdFormat(files[i].keyId); + } catch (const core::Exception& e) { + result[i] = toLibFile(files[i], {}, {}, 0, {}, e.getCode(), FileDataSchema::Version::UNKNOWN, false); + continue; + } + keyRequest.addOne(storeKeys.keys, files[i].keyId, location); + } + auto keysResult = keyProvider->getKeysAndVerify(keyRequest); + auto keyMapIt = keysResult.find(location); + + // decrypt + deduplication + for (size_t i = 0; i < files.size(); i++) { + if (result[i].statusCode != 0) { + continue; + } + try { + if (keyMapIt == keysResult.end()) { + throw UnknowFileFormatException(); + } + auto [file, dio] = decrypt(files[i], keyMapIt->second.at(files[i].keyId)); + result[i] = file; + filesDIO[i] = dio; + if (!seenRandomIds.insert(dio.randomId + "-" + std::to_string(dio.timestamp)).second) { + result[i].statusCode = core::DataIntegrityObjectDuplicatedException().getCode(); + } + } catch (const core::Exception& e) { + result[i] = toLibFile(files[i], {}, {}, 0, {}, e.getCode(), FileDataSchema::Version::UNKNOWN, false); + } catch (const privmx::utils::PrivmxException& e) { + result[i] = toLibFile( + files[i], {}, {}, {}, {}, core::ExceptionConverter::convert(e).getCode(), + FileDataSchema::Version::UNKNOWN + ); + } catch (...) { + result[i] = toLibFile( + files[i], {}, {}, {}, {}, ENDPOINT_CORE_EXCEPTION_CODE, FileDataSchema::Version::UNKNOWN + ); + } + } + + // batch identity verification + std::vector verifyRequests; + std::vector verifyIndices; + for (size_t i = 0; i < result.size(); i++) { + if (result[i].statusCode != 0) { + continue; + } + verifyRequests.push_back( + {.contextId = storeKeys.contextId, + .senderId = result[i].info.author, + .senderPubKey = result[i].authorPubKey, + .date = result[i].info.createDate, + .bridgeIdentity = filesDIO[i].bridgeIdentity} + ); + verifyIndices.push_back(i); + } + auto verified = _connection.getImpl()->getUserVerifier()->verify(verifyRequests); + for (size_t j = 0; j < verifyIndices.size(); j++) { + result[verifyIndices[j]].statusCode = verified[j] ? + 0 : + core::ExceptionConverter::getCodeOfUserVerificationFailureException(); + } + return result; +} + +File FileMetaDataSchemaMapper::validateDecryptAndConvertFile( + const server::File& file, + const core::ModuleKeys& storeKeys, + const std::shared_ptr& keyProvider +) { + return validateDecryptAndConvertFiles({file}, storeKeys, keyProvider)[0]; +} + +dynamic::InternalStoreFileMeta FileMetaDataSchemaMapper::validateDecryptFileInternalMeta( + const server::File& file, + const core::ModuleKeys& storeKeys, + const std::shared_ptr& keyProvider +) { + const auto& keyId = file.keyId; + core::KeyDecryptionAndVerificationRequest keyProviderRequest; + core::EncKeyLocation location{.contextId = file.contextId, .resourceId = storeKeys.moduleResourceId}; + keyProviderRequest.addOne(storeKeys.keys, keyId, location); + auto encKey = keyProvider->getKeysAndVerify(keyProviderRequest).at(location).at(keyId); + return decryptFileInternalMeta(file, encKey); +} + +File FileMetaDataSchemaMapper::toLibFile( + const server::File& file, + const core::Buffer& publicMeta, + const core::Buffer& privateMeta, + int64_t size, + const std::string& authorPubKey, + int64_t statusCode, + int64_t schemaVersion, + bool randomWrite +) { + return File{ + .info = + { + .storeId = file.storeId, + .fileId = file.id, + .createDate = file.created, + .author = file.creator, + }, + .publicMeta = publicMeta, + .privateMeta = privateMeta, + .size = size, + .authorPubKey = authorPubKey, + .statusCode = statusCode, + .schemaVersion = schemaVersion, + .randomWrite = randomWrite + }; +} diff --git a/endpoint/store/src/encryptors/file/FileMetaEncryptor.cpp b/endpoint/store/src/encryptors/file/FileMetaEncryptor.cpp index a346ba0f..60b645e0 100644 --- a/endpoint/store/src/encryptors/file/FileMetaEncryptor.cpp +++ b/endpoint/store/src/encryptors/file/FileMetaEncryptor.cpp @@ -64,13 +64,6 @@ FileMetaEncryptor::DecryptedFileMeta FileMetaEncryptor::decrypt( core::EncKey encKey ) { switch (getFileDataStructureVersion(encryptedFileMeta)) { - case FileDataSchema::Version::UNKNOWN: - return FileMetaEncryptor::DecryptedFileMeta(); - case FileDataSchema::Version::VERSION_1: { - // this can throw TODO - auto encryptedFileMetaV1 = encryptedFileMeta.convert(); - return FileMetaEncryptor::DecryptedFileMeta(_fileMetaEncryptorV1.decrypt(encryptedFileMetaV1, encKey.key)); - } case FileDataSchema::Version::VERSION_4: { auto encryptedFileMetaV4 = server::EncryptedFileMetaV4::fromJSON(encryptedFileMeta); return FileMetaEncryptor::DecryptedFileMeta(_fileMetaEncryptorV4.decrypt(encryptedFileMetaV4, encKey.key)); @@ -79,19 +72,14 @@ FileMetaEncryptor::DecryptedFileMeta FileMetaEncryptor::decrypt( auto encryptedFileMetaV5 = server::EncryptedFileMetaV5::fromJSON(encryptedFileMeta); return FileMetaEncryptor::DecryptedFileMeta(_fileMetaEncryptorV5.decrypt(encryptedFileMetaV5, encKey.key)); } + default: + return FileMetaEncryptor::DecryptedFileMeta(); } return FileMetaEncryptor::DecryptedFileMeta(); } FileMetaEncryptor::DecryptedFileMeta FileMetaEncryptor::extractPublic(Poco::Dynamic::Var encryptedFileMeta) { switch (getFileDataStructureVersion(encryptedFileMeta)) { - case FileDataSchema::Version::UNKNOWN: - return FileMetaEncryptor::DecryptedFileMeta(); - case FileDataSchema::Version::VERSION_1: { - // this can throw TODO - auto encryptedFileMetaV1 = encryptedFileMeta.convert(); - return FileMetaEncryptor::DecryptedFileMeta(_fileMetaEncryptorV1.decrypt(encryptedFileMetaV1, "")); - } case FileDataSchema::Version::VERSION_4: { auto encryptedFileMetaV4 = server::EncryptedFileMetaV4::fromJSON(encryptedFileMeta); return FileMetaEncryptor::DecryptedFileMeta(_fileMetaEncryptorV4.decrypt(encryptedFileMetaV4, "")); @@ -100,6 +88,8 @@ FileMetaEncryptor::DecryptedFileMeta FileMetaEncryptor::extractPublic(Poco::Dyna auto encryptedFileMetaV5 = server::EncryptedFileMetaV5::fromJSON(encryptedFileMeta); return FileMetaEncryptor::DecryptedFileMeta(_fileMetaEncryptorV5.extractPublic(encryptedFileMetaV5)); } + default: + return FileMetaEncryptor::DecryptedFileMeta(); } return FileMetaEncryptor::DecryptedFileMeta(); } @@ -115,8 +105,6 @@ FileDataSchema::Version FileMetaEncryptor::getFileDataStructureVersion(Poco::Dyn default: return FileDataSchema::Version::UNKNOWN; } - } else if (encryptedFileMeta.isString()) { - return FileDataSchema::Version::VERSION_1; } return FileDataSchema::Version::UNKNOWN; } diff --git a/endpoint/store/src/encryptors/file/FileMetaEncryptorV1.cpp b/endpoint/store/src/encryptors/file/FileMetaEncryptorV1.cpp deleted file mode 100644 index cca3de37..00000000 --- a/endpoint/store/src/encryptors/file/FileMetaEncryptorV1.cpp +++ /dev/null @@ -1,56 +0,0 @@ -/* -PrivMX Endpoint. -Copyright © 2024 Simplito sp. z o.o. - -This file is part of the PrivMX Platform (https://privmx.dev). -This software is Licensed under the PrivMX Free License. - -See the License for the specific language governing permissions and -limitations under the License. -*/ - -#include -#include -#include -#include - -#include "privmx/endpoint/store/encryptors/file/FileMetaEncryptorV1.hpp" - -using namespace privmx::endpoint::store; - -std::string FileMetaEncryptorV1::signAndEncrypt( - const dynamic::compat_v1::StoreFileMeta& data, - const privmx::crypto::PrivateKey& priv, - const std::string& key -) { - auto buffer{data.serialize()}; - auto signature = priv.signToCompactSignatureWithHash(buffer); - std::string plain; - plain.push_back(1); - plain.push_back(signature.length()); - plain.append(signature).append(buffer); - auto cipher = crypto::CryptoPrivmx::privmxEncrypt(crypto::CryptoPrivmx::privmxOptAesWithSignature(), plain, key); - return utils::Base64::from(cipher); -} - -FileMetaSigned FileMetaEncryptorV1::decrypt(const std::string& data, const std::string& key) { - FileMetaSigned metaSigned; - auto plain = crypto::CryptoPrivmx::privmxDecrypt(true, utils::Base64::toString(data), key); - Poco::JSON::Parser parser; - - if (plain[0] == 1) { - size_t sigLen = reinterpret_cast(plain[1]); - auto signature = plain.substr(2, sigLen); - auto metaBuf = plain.substr(2 + sigLen); - auto meta = dynamic::compat_v1::StoreFileMeta::fromJSON(parser.parse(metaBuf)); - metaSigned.signature = signature; - metaSigned.metaBuf = metaBuf; - metaSigned.meta = meta; - } else if (plain[0] == 123) { - auto meta = dynamic::compat_v1::StoreFileMeta::fromJSON(parser.parse(plain)); - metaSigned.signature = Pson::BinaryString(); - metaSigned.metaBuf = Pson::BinaryString(); - metaSigned.meta = meta; - } - return metaSigned; -} diff --git a/endpoint/store/src/encryptors/store/StoreDataSchemaMapper.cpp b/endpoint/store/src/encryptors/store/StoreDataSchemaMapper.cpp new file mode 100644 index 00000000..3cbff17b --- /dev/null +++ b/endpoint/store/src/encryptors/store/StoreDataSchemaMapper.cpp @@ -0,0 +1,218 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "privmx/endpoint/store/encryptors/store/StoreDataSchemaMapper.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "privmx/endpoint/store/StoreException.hpp" + +using namespace privmx::endpoint; +using namespace privmx::endpoint::store; + +StoreDataSchemaMapper::StoreDataSchemaMapper( + const privmx::crypto::PrivateKey& userPrivKey, + const core::Connection& connection +) + : _userPrivKey(userPrivKey), _connection(connection) { + _strategyV4 = std::make_shared(); + _strategyMapper.registerStrategy(StoreDataSchema::Version::VERSION_4, _strategyV4); + _strategyV5 = std::make_shared(); + _strategyMapper.registerStrategy(StoreDataSchema::Version::VERSION_5, _strategyV5); +} + +Poco::Dynamic::Var StoreDataSchemaMapper::encrypt(const core::ModuleDataToEncryptV5& data, const std::string& key) { + return _strategyV5->encrypt(data, _userPrivKey, key).toJSON(); +} + +std::tuple StoreDataSchemaMapper::decrypt( + const server::Store& store, + const core::DecryptedEncKey& encKey +) { + auto version = getDataStructureVersion(store.data.back()); + auto strategy = _strategyMapper.getStrategy(static_cast(version)); + if (!strategy) { + auto e = UnknowStoreFormatException(); + return {toLibStore(store, {}, {}, e.getCode(), StoreDataSchema::Version::UNKNOWN), core::DataIntegrityObject{}}; + } + return strategy->decryptAndConvert(store, encKey); +} + +StoreDataSchema::Version StoreDataSchemaMapper::getDataStructureVersion(const server::StoreDataEntry& entry) { + if (entry.data.type() == typeid(Poco::JSON::Object::Ptr)) { + auto versioned = core::dynamic::VersionedData::fromJSON(entry.data); + switch (versioned.version) { + case core::ModuleDataSchema::Version::VERSION_4: + return StoreDataSchema::Version::VERSION_4; + case core::ModuleDataSchema::Version::VERSION_5: + return StoreDataSchema::Version::VERSION_5; + default: + return StoreDataSchema::Version::UNKNOWN; + } + } + return StoreDataSchema::Version::UNKNOWN; +} + +void StoreDataSchemaMapper::assertDataIntegrity(const server::Store& store) { + const auto& entry = store.data.back(); + switch (getDataStructureVersion(entry)) { + case StoreDataSchema::Version::UNKNOWN: + throw UnknowStoreFormatException(); + case StoreDataSchema::Version::VERSION_4: + return; + case StoreDataSchema::Version::VERSION_5: { + auto encData = core::dynamic::EncryptedModuleDataV5::fromJSON(entry.data); + auto dio = _strategyV5->getDIOAndAssertIntegrity(encData); + if (dio.contextId != store.contextId || + dio.resourceId != store.resourceId || + dio.creatorUserId != store.lastModifier || + !core::TimestampValidator::validate(dio.timestamp, store.lastModificationDate)) { + throw StoreDataIntegrityException(); + } + return; + } + default: + throw UnknowStoreFormatException(); + } +} + +uint32_t StoreDataSchemaMapper::validateDataIntegrity(const server::Store& store) { + try { + assertDataIntegrity(store); + return 0; + } catch (const core::Exception& e) { return e.getCode(); } catch (const privmx::utils::PrivmxException& e) { + return core::ExceptionConverter::convert(e).getCode(); + } catch (...) { return ENDPOINT_CORE_EXCEPTION_CODE; } +} + +std::vector StoreDataSchemaMapper::validateDecryptAndConvertStores( + const std::vector& stores, + const std::shared_ptr& keyProvider +) { + if (stores.size() == 0) { + return std::vector{}; + } + std::vector result(stores.size()); + std::vector storesDIO(stores.size()); + // integrity validation + for (size_t i = 0; i < stores.size(); i++) { + result[i].statusCode = validateDataIntegrity(stores[i]); + if (result[i].statusCode != 0) { + result[i] = toLibStore(stores[i], {}, {}, result[i].statusCode, StoreDataSchema::Version::UNKNOWN); + } + } + // batch key fetch + core::KeyDecryptionAndVerificationRequest keyRequest; + for (size_t i = 0; i < stores.size(); i++) { + if (result[i].statusCode != 0) { + continue; + } + auto& store = stores[i]; + core::EncKeyLocation location{.contextId = store.contextId, .resourceId = store.resourceId.value_or("")}; + keyRequest.addOne(store.keys, store.data.back().keyId, location); + } + auto storesKeys = keyProvider->getKeysAndVerify(keyRequest); + std::set seenRandomIds; + // decrypt + deduplication + for (size_t i = 0; i < stores.size(); i++) { + if (result[i].statusCode != 0) { + continue; + } + auto& store = stores[i]; + try { + auto storeKeysIt = storesKeys.find( + core::EncKeyLocation{.contextId = store.contextId, .resourceId = store.resourceId.value_or("")} + ); + if (storeKeysIt == storesKeys.end()) { + throw UnknowStoreFormatException(); + } + auto [decryptedStore, dio] = decrypt(store, storeKeysIt->second.at(store.data.back().keyId)); + result[i] = decryptedStore; + storesDIO[i] = dio; + if (!seenRandomIds.insert(storesDIO[i].randomId + "-" + std::to_string(storesDIO[i].timestamp)).second) { + result[i].statusCode = core::DataIntegrityObjectDuplicatedException().getCode(); + } + } catch (const core::Exception& e) { + result[i] = toLibStore(store, {}, {}, e.getCode(), StoreDataSchema::Version::UNKNOWN); + } catch (const privmx::utils::PrivmxException& e) { + result[i] = toLibStore( + store, {}, {}, core::ExceptionConverter::convert(e).getCode(), StoreDataSchema::Version::UNKNOWN + ); + } catch (...) { + result[i] = toLibStore(store, {}, {}, ENDPOINT_CORE_EXCEPTION_CODE, StoreDataSchema::Version::UNKNOWN); + } + } + // batch identity verification + std::vector verifyRequests; + std::vector verifyIndices; + for (size_t i = 0; i < result.size(); i++) { + if (result[i].statusCode != 0) { + continue; + } + verifyRequests.push_back( + {.contextId = result[i].contextId, + .senderId = result[i].lastModifier, + .senderPubKey = storesDIO[i].creatorPubKey, + .date = result[i].lastModificationDate, + .bridgeIdentity = storesDIO[i].bridgeIdentity} + ); + verifyIndices.push_back(i); + } + auto verified = _connection.getImpl()->getUserVerifier()->verify(verifyRequests); + for (size_t j = 0; j < verifyIndices.size(); j++) { + result[verifyIndices[j]].statusCode = verified[j] ? + 0 : + core::ExceptionConverter::getCodeOfUserVerificationFailureException(); + } + return result; +} + +Store StoreDataSchemaMapper::validateDecryptAndConvertStore( + const server::Store& store, + const std::shared_ptr& keyProvider +) { + return validateDecryptAndConvertStores({store}, keyProvider)[0]; +} + +Store StoreDataSchemaMapper::toLibStore( + const server::Store& store, + const core::Buffer& publicMeta, + const core::Buffer& privateMeta, + int64_t statusCode, + int64_t schemaVersion +) { + return Store{ + .storeId = store.id, + .contextId = store.contextId, + .createDate = store.createDate, + .creator = store.creator, + .lastModificationDate = store.lastModificationDate, + .lastFileDate = store.lastFileDate, + .lastModifier = store.lastModifier, + .users = store.users, + .managers = store.managers, + .version = store.version, + .publicMeta = publicMeta, + .privateMeta = privateMeta, + .policy = core::Factory::parsePolicyServerObject(store.policy), + .filesCount = store.files, + .statusCode = statusCode, + .schemaVersion = schemaVersion + }; +} diff --git a/endpoint/store/src/encryptors/store/StoreDataSchemaStrategyV4.cpp b/endpoint/store/src/encryptors/store/StoreDataSchemaStrategyV4.cpp new file mode 100644 index 00000000..15be3f39 --- /dev/null +++ b/endpoint/store/src/encryptors/store/StoreDataSchemaStrategyV4.cpp @@ -0,0 +1,62 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "privmx/endpoint/store/encryptors/store/StoreDataSchemaStrategyV4.hpp" +#include "privmx/endpoint/store/encryptors/store/StoreDataSchemaMapper.hpp" + +#include +#include +#include + +#include "privmx/endpoint/store/Constants.hpp" + +using namespace privmx::endpoint; +using namespace privmx::endpoint::store; + +core::DecryptedModuleDataV4 StoreDataSchemaStrategyV4::decrypt( + const server::Store& store, + const core::DecryptedEncKey& encKey +) const { + auto encryptedData = core::dynamic::EncryptedModuleDataV4::fromJSON(store.data.back().data); + return _encryptor.decrypt(encryptedData, encKey.key); +} + +std::tuple StoreDataSchemaStrategyV4::convert( + const server::Store& store, + const core::DecryptedModuleDataV4& raw +) const { + return { + StoreDataSchemaMapper::toLibStore( + store, raw.publicMeta, raw.privateMeta, raw.statusCode, StoreDataSchema::Version::VERSION_4 + ), + core::DataIntegrityObject{ + .creatorUserId = store.lastModifier, + .creatorPubKey = raw.authorPubKey, + .contextId = store.contextId, + .resourceId = store.resourceId.value_or(""), + .timestamp = store.lastModificationDate, + .randomId = std::string(), + .containerId = std::nullopt, + .containerResourceId = std::nullopt, + .bridgeIdentity = std::nullopt + } + }; +} + +std::tuple StoreDataSchemaStrategyV4::makeErrorResult( + const server::Store& store, + int64_t errorCode +) const { + return { + StoreDataSchemaMapper::toLibStore(store, {}, {}, errorCode, StoreDataSchema::Version::VERSION_4), + core::DataIntegrityObject{} + }; +} diff --git a/endpoint/store/src/encryptors/store/StoreDataSchemaStrategyV5.cpp b/endpoint/store/src/encryptors/store/StoreDataSchemaStrategyV5.cpp new file mode 100644 index 00000000..60150dd7 --- /dev/null +++ b/endpoint/store/src/encryptors/store/StoreDataSchemaStrategyV5.cpp @@ -0,0 +1,74 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "privmx/endpoint/store/encryptors/store/StoreDataSchemaStrategyV5.hpp" +#include "privmx/endpoint/store/encryptors/store/StoreDataSchemaMapper.hpp" + +#include +#include + +#include "privmx/endpoint/store/Constants.hpp" + +using namespace privmx::endpoint; +using namespace privmx::endpoint::store; + +core::DecryptedModuleDataV5 StoreDataSchemaStrategyV5::decrypt( + const server::Store& store, + const core::DecryptedEncKey& encKey +) const { + auto encryptedData = core::dynamic::EncryptedModuleDataV5::fromJSON(store.data.back().data); + core::DecryptedModuleDataV5 result; + if (encKey.statusCode != 0) { + result = _encryptor.extractPublic(encryptedData); + } else { + result = _encryptor.decrypt(encryptedData, encKey.key); + } + if (encKey.statusCode != 0) { + result.statusCode = encKey.statusCode; + } + return result; +} + +std::tuple StoreDataSchemaStrategyV5::convert( + const server::Store& store, + const core::DecryptedModuleDataV5& raw +) const { + return { + StoreDataSchemaMapper::toLibStore( + store, raw.publicMeta, raw.privateMeta, raw.statusCode, StoreDataSchema::Version::VERSION_5 + ), + raw.dio + }; +} + +std::tuple StoreDataSchemaStrategyV5::makeErrorResult( + const server::Store& store, + int64_t errorCode +) const { + return { + StoreDataSchemaMapper::toLibStore(store, {}, {}, errorCode, StoreDataSchema::Version::VERSION_5), + core::DataIntegrityObject{} + }; +} + +core::dynamic::EncryptedModuleDataV5 StoreDataSchemaStrategyV5::encrypt( + const core::ModuleDataToEncryptV5& data, + const privmx::crypto::PrivateKey& userPrivKey, + const std::string& key +) const { + return _encryptor.encrypt(data, userPrivKey, key); +} + +core::DataIntegrityObject StoreDataSchemaStrategyV5::getDIOAndAssertIntegrity( + const core::dynamic::EncryptedModuleDataV5& encData +) const { + return _encryptor.getDIOAndAssertIntegrity(encData); +} diff --git a/endpoint/stream/stream/include/privmx/endpoint/stream/StreamApiLowImpl.hpp b/endpoint/stream/stream/include/privmx/endpoint/stream/StreamApiLowImpl.hpp index 0a79c73e..1261824a 100644 --- a/endpoint/stream/stream/include/privmx/endpoint/stream/StreamApiLowImpl.hpp +++ b/endpoint/stream/stream/include/privmx/endpoint/stream/StreamApiLowImpl.hpp @@ -19,7 +19,6 @@ limitations under the License. #include #include #include -#include #include #include #include @@ -30,6 +29,7 @@ limitations under the License. #include "privmx/endpoint/stream/Types.hpp" #include "privmx/endpoint/stream/WebRTCInterface.hpp" #include "privmx/endpoint/stream/encryptors/dataChannel/DataChannelMessageEncryptorV1.hpp" +#include "privmx/endpoint/stream/encryptors/streamRoom/StreamRoomDataSchemaMapper.hpp" #include namespace privmx { namespace endpoint { @@ -160,30 +160,6 @@ class StreamApiLowImpl : public privmx::utils::ManualManagedClass mapUsers(const std::vector& users); - StreamRoom convertServerStreamRoomToLibStreamRoom( - server::StreamRoomInfo streamRoomInfo, - const core::Buffer& publicMeta = core::Buffer(), - const core::Buffer& privateMeta = core::Buffer(), - const int64_t& statusCode = 0, - const int64_t& schemaVersion = StreamRoomDataSchema::Version::UNKNOWN - ); - - StreamRoom convertDecryptedStreamRoomDataV5ToStreamRoom( - server::StreamRoomInfo streamRoomInfo, - const core::DecryptedModuleDataV5& streamRoomData - ); - StreamRoomDataSchema::Version getStreamRoomEntryDataStructureVersion(server::StreamRoomDataEntry streamRoomEntry); - std::tuple decryptAndConvertStreamRoomDataToStreamRoom( - server::StreamRoomInfo streamRoom, - server::StreamRoomDataEntry streamRoomEntry, - const core::DecryptedEncKey& encKey - ); - std::vector decryptAndConvertStreamRoomsDataToStreamRooms( - std::vector streamRooms - ); - StreamRoom decryptAndConvertStreamRoomDataToStreamRoom(server::StreamRoomInfo streamRoom); - void assertStreamRoomDataIntegrity(server::StreamRoomInfo streamRoom); - uint32_t validateStreamRoomDataIntegrity(server::StreamRoomInfo streamRoom); std::shared_ptr createEmptyStreamRoomData( const std::string& streamRoomId, std::shared_ptr webRtc @@ -214,7 +190,7 @@ class StreamApiLowImpl : public privmx::utils::ManualManagedClass _eventMiddleware; std::shared_ptr _serverApi; stream::SubscriberImpl _subscriber; - core::ModuleDataEncryptorV5 _streamRoomDataEncryptorV5; + StreamRoomDataSchemaMapper _streamRoomDataSchemaMapper; // v3 webrtc privmx::utils::ThreadSaveMap> _streamRoomMap; diff --git a/endpoint/stream/stream/include/privmx/endpoint/stream/encryptors/streamRoom/StreamRoomDataSchemaMapper.hpp b/endpoint/stream/stream/include/privmx/endpoint/stream/encryptors/streamRoom/StreamRoomDataSchemaMapper.hpp new file mode 100644 index 00000000..64dcff9e --- /dev/null +++ b/endpoint/stream/stream/include/privmx/endpoint/stream/encryptors/streamRoom/StreamRoomDataSchemaMapper.hpp @@ -0,0 +1,88 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef _PRIVMXLIB_ENDPOINT_STREAM_STREAMROOMDATASCHEMAMAPPER_HPP_ +#define _PRIVMXLIB_ENDPOINT_STREAM_STREAMROOMDATASCHEMAMAPPER_HPP_ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "privmx/endpoint/stream/Constants.hpp" +#include "privmx/endpoint/stream/ServerTypes.hpp" +#include "privmx/endpoint/stream/Types.hpp" +#include "privmx/endpoint/stream/encryptors/streamRoom/StreamRoomDataSchemaStrategyV5.hpp" + +namespace privmx { +namespace endpoint { +namespace stream { + +class StreamRoomDataSchemaMapper { +public: + StreamRoomDataSchemaMapper(const privmx::crypto::PrivateKey& userPrivKey, const core::Connection& connection); + + Poco::Dynamic::Var encrypt(const core::ModuleDataToEncryptV5& data, const std::string& key); + + std::tuple decrypt( + const server::StreamRoomInfo& streamRoom, + const core::DecryptedEncKey& encKey + ); + + StreamRoomDataSchema::Version getDataStructureVersion(const server::StreamRoomDataEntry& entry); + + void assertDataIntegrity(const server::StreamRoomInfo& streamRoom); + + uint32_t validateDataIntegrity(const server::StreamRoomInfo& streamRoom); + + std::vector validateDecryptAndConvertStreamRooms( + const std::vector& streamRooms, + const std::shared_ptr& keyProvider + ); + + StreamRoom validateDecryptAndConvertStreamRoom( + const server::StreamRoomInfo& streamRoom, + const std::shared_ptr& keyProvider + ); + static StreamRoom toLibStreamRoom( + const server::StreamRoomInfo& info, + const core::Buffer& publicMeta, + const core::Buffer& privateMeta, + int64_t statusCode, + int64_t schemaVersion + ); + +private: + privmx::crypto::PrivateKey _userPrivKey; + core::Connection _connection; + core::VersionStrategyMapper> + _strategyMapper; + std::shared_ptr _strategyV5; + core::ModuleDataEncryptorV5 _encryptorV5; +}; + +} // namespace stream +} // namespace endpoint +} // namespace privmx + +#endif // _PRIVMXLIB_ENDPOINT_STREAM_STREAMROOMDATASCHEMAMAPPER_HPP_ diff --git a/endpoint/stream/stream/include/privmx/endpoint/stream/encryptors/streamRoom/StreamRoomDataSchemaStrategyV5.hpp b/endpoint/stream/stream/include/privmx/endpoint/stream/encryptors/streamRoom/StreamRoomDataSchemaStrategyV5.hpp new file mode 100644 index 00000000..d2016a65 --- /dev/null +++ b/endpoint/stream/stream/include/privmx/endpoint/stream/encryptors/streamRoom/StreamRoomDataSchemaStrategyV5.hpp @@ -0,0 +1,61 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef _PRIVMXLIB_ENDPOINT_STREAM_STREAMROOMDATASCHEMASTRATEGYV5_HPP_ +#define _PRIVMXLIB_ENDPOINT_STREAM_STREAMROOMDATASCHEMASTRATEGYV5_HPP_ + +#include + +#include +#include +#include +#include +#include + +#include "privmx/endpoint/stream/Constants.hpp" +#include "privmx/endpoint/stream/ServerTypes.hpp" +#include "privmx/endpoint/stream/Types.hpp" + +namespace privmx { +namespace endpoint { +namespace stream { + +// clang-format off +class StreamRoomDataSchemaStrategyV5 : public core::TypedDataSchemaStrategy< + server::StreamRoomInfo, + core::DecryptedModuleDataV5, + std::tuple +> { + // clang-format on +public: + core::DecryptedModuleDataV5 decrypt( + const server::StreamRoomInfo& streamRoom, + const core::DecryptedEncKey& encKey + ) const override; + std::tuple convert( + const server::StreamRoomInfo& streamRoom, + const core::DecryptedModuleDataV5& raw + ) const override; + std::tuple makeErrorResult( + const server::StreamRoomInfo& streamRoom, + int64_t errorCode + ) const override; + core::DataIntegrityObject getDIOAndAssertIntegrity(const core::dynamic::EncryptedModuleDataV5& encData) const; + +private: + mutable core::ModuleDataEncryptorV5 _encryptor; +}; + +} // namespace stream +} // namespace endpoint +} // namespace privmx + +#endif // _PRIVMXLIB_ENDPOINT_STREAM_STREAMROOMDATASCHEMASTRATEGYV5_HPP_ diff --git a/endpoint/stream/stream/src/StreamApiLowImpl.cpp b/endpoint/stream/stream/src/StreamApiLowImpl.cpp index a79dfc9a..490d922d 100644 --- a/endpoint/stream/stream/src/StreamApiLowImpl.cpp +++ b/endpoint/stream/stream/src/StreamApiLowImpl.cpp @@ -10,6 +10,7 @@ limitations under the License. */ #include "privmx/endpoint/stream/StreamApiLowImpl.hpp" +#include "privmx/endpoint/stream/encryptors/streamRoom/StreamRoomDataSchemaStrategyV5.hpp" #include @@ -48,7 +49,8 @@ StreamApiLowImpl::StreamApiLowImpl( : ModuleBaseApi(userPrivKey, keyProvider, host, eventMiddleware, connection), _connection(connection.getImpl()), _userPrivKey(userPrivKey), _keyProvider(keyProvider), _host(host), _eventMiddleware(eventMiddleware), _serverApi(std::make_shared(gateway)), - _subscriber(stream::SubscriberImpl(gateway, STREAM_TYPE_FILTER_FLAG)) { + _subscriber(stream::SubscriberImpl(gateway, STREAM_TYPE_FILTER_FLAG)), + _streamRoomDataSchemaMapper(userPrivKey, connection) { _notificationListenerId = _eventMiddleware->addNotificationEventListener( std::bind(&StreamApiLowImpl::onNotificationEvent, this, std::placeholders::_1, std::placeholders::_2) ); @@ -118,7 +120,7 @@ void StreamApiLowImpl::processNotificationEvent(const core::NotificationEvent& n if (raw.type.value_or(std::string(STREAM_TYPE_FILTER_FLAG)) != STREAM_TYPE_FILTER_FLAG) { return; } - auto eventData = decryptAndConvertStreamRoomDataToStreamRoom(raw); + auto eventData = _streamRoomDataSchemaMapper.validateDecryptAndConvertStreamRoom(raw, _keyProvider); auto event = core::EventBuilder::buildEvent( "stream", eventData, notification ); @@ -129,7 +131,7 @@ void StreamApiLowImpl::processNotificationEvent(const core::NotificationEvent& n if (raw.type.value_or(std::string(STREAM_TYPE_FILTER_FLAG)) != STREAM_TYPE_FILTER_FLAG) { return; } - auto eventData = decryptAndConvertStreamRoomDataToStreamRoom(raw); + auto eventData = _streamRoomDataSchemaMapper.validateDecryptAndConvertStreamRoom(raw, _keyProvider); auto event = core::EventBuilder::buildEvent( "stream", eventData, notification ); @@ -331,7 +333,7 @@ std::vector StreamApiLowImpl::getStreamRoomRecordingKey params.id = streamRoomId; params.type = STREAM_TYPE_FILTER_FLAG; auto streamRoom = _serverApi->streamRoomGet(params).streamRoom; - auto statusCode = validateStreamRoomDataIntegrity(streamRoom); + auto statusCode = _streamRoomDataSchemaMapper.validateDataIntegrity(streamRoom); if (statusCode != 0) { throw StreamRoomDataIntegrityException(); } @@ -581,8 +583,7 @@ std::string StreamApiLowImpl::createStreamRoom( createStreamRoomModel.resourceId = resourceId; createStreamRoomModel.contextId = contextId; createStreamRoomModel.keyId = streamRoomKey.id; - createStreamRoomModel - .data = _streamRoomDataEncryptorV5.encrypt(streamRoomDataToEncrypt, _userPrivKey, streamRoomKey.key).toJSON(); + createStreamRoomModel.data = _streamRoomDataSchemaMapper.encrypt(streamRoomDataToEncrypt, streamRoomKey.key); auto allUsers = core::EndpointUtils::uniqueListUserWithPubKey(users, managers); createStreamRoomModel.keys = _keyProvider->prepareKeysList( allUsers, streamRoomKey, streamRoomDIO, {.contextId = contextId, .resourceId = resourceId}, streamRoomSecret @@ -682,7 +683,7 @@ void StreamApiLowImpl::updateStreamRoom( }, .dio = updateStreamRoomDio }; - model.data = _streamRoomDataEncryptorV5.encrypt(streamRoomDataToEncrypt, _userPrivKey, streamRoomKey.key).toJSON(); + model.data = _streamRoomDataSchemaMapper.encrypt(streamRoomDataToEncrypt, streamRoomKey.key); _serverApi->streamRoomUpdate(model); } @@ -696,23 +697,9 @@ core::PagingList StreamApiLowImpl::listStreamRooms( model.type = type; core::ListQueryMapper::map(model, query); auto streamRoomsList = _serverApi->streamRoomList(model); - std::vector streamRooms; - for (size_t i = 0; i < streamRoomsList.list.size(); i++) { - auto streamRoom = streamRoomsList.list[i]; - auto statusCode = validateStreamRoomDataIntegrity(streamRoom); - streamRooms.push_back(convertServerStreamRoomToLibStreamRoom(streamRoom, {}, {}, statusCode)); - if (statusCode != 0) { - streamRoomsList.list.erase(streamRoomsList.list.begin() + i); - i--; - } - } - auto tmp = decryptAndConvertStreamRoomsDataToStreamRooms(streamRoomsList.list); - for (size_t j = 0, i = 0; i < streamRooms.size(); i++) { - if (streamRooms[i].statusCode == 0) { - streamRooms[i] = tmp[j]; - j++; - } - } + auto streamRooms = _streamRoomDataSchemaMapper.validateDecryptAndConvertStreamRooms( + streamRoomsList.list, _keyProvider + ); return core::PagingList({.totalAvailable = streamRoomsList.count, .readItems = streamRooms}); } @@ -721,11 +708,7 @@ StreamRoom StreamApiLowImpl::getStreamRoom(const std::string& streamRoomId, cons params.id = streamRoomId; params.type = type; auto streamRoom = _serverApi->streamRoomGet(params).streamRoom; - auto statusCode = validateStreamRoomDataIntegrity(streamRoom); - if (statusCode != 0) { - return convertServerStreamRoomToLibStreamRoom(streamRoom, {}, {}, statusCode); - } - auto result = decryptAndConvertStreamRoomDataToStreamRoom(streamRoom); + auto result = _streamRoomDataSchemaMapper.validateDecryptAndConvertStreamRoom(streamRoom, _keyProvider); return result; } @@ -735,186 +718,6 @@ void StreamApiLowImpl::deleteStreamRoom(const std::string& streamRoomId) { _serverApi->streamRoomDelete(model); } -StreamRoom StreamApiLowImpl::convertServerStreamRoomToLibStreamRoom( - server::StreamRoomInfo streamRoomInfo, - const core::Buffer& publicMeta, - const core::Buffer& privateMeta, - const int64_t& statusCode, - const int64_t& schemaVersion -) { - return StreamRoom{ - .contextId = streamRoomInfo.contextId, - .streamRoomId = streamRoomInfo.id, - .createDate = streamRoomInfo.createDate, - .creator = streamRoomInfo.creator, - .lastModificationDate = streamRoomInfo.lastModificationDate, - .lastModifier = streamRoomInfo.lastModifier, - .users = streamRoomInfo.users, - .managers = streamRoomInfo.managers, - .version = streamRoomInfo.version, - .publicMeta = publicMeta, - .privateMeta = privateMeta, - .policy = core::Factory::parsePolicyServerObject(streamRoomInfo.policy), - .statusCode = statusCode, - .schemaVersion = schemaVersion, - .closed = streamRoomInfo.closed.value_or(true), - }; -} - -StreamRoom StreamApiLowImpl::convertDecryptedStreamRoomDataV5ToStreamRoom( - server::StreamRoomInfo streamRoomInfo, - const core::DecryptedModuleDataV5& streamRoomData -) { - return convertServerStreamRoomToLibStreamRoom( - streamRoomInfo, streamRoomData.publicMeta, streamRoomData.privateMeta, streamRoomData.statusCode, - StreamRoomDataSchema::Version::VERSION_5 - ); -} - -StreamRoomDataSchema::Version StreamApiLowImpl::getStreamRoomEntryDataStructureVersion( - server::StreamRoomDataEntry streamRoomEntry -) { - if (streamRoomEntry.data.type() == typeid(Poco::JSON::Object::Ptr)) { - auto versioned = core::dynamic::VersionedData::fromJSON(streamRoomEntry.data); - auto version = versioned.version; - switch (version) { - case core::ModuleDataSchema::Version::VERSION_5: - return StreamRoomDataSchema::Version::VERSION_5; - default: - return StreamRoomDataSchema::Version::UNKNOWN; - } - } - return StreamRoomDataSchema::Version::UNKNOWN; -} - -std::tuple StreamApiLowImpl::decryptAndConvertStreamRoomDataToStreamRoom( - server::StreamRoomInfo streamRoom, - server::StreamRoomDataEntry streamRoomEntry, - const core::DecryptedEncKey& encKey -) { - switch (getStreamRoomEntryDataStructureVersion(streamRoomEntry)) { - case StreamRoomDataSchema::Version::UNKNOWN: { - auto e = UnknowStreamRoomFormatException(); - return std::make_tuple( - convertServerStreamRoomToLibStreamRoom(streamRoom, {}, {}, e.getCode()), core::DataIntegrityObject() - ); - } - case StreamRoomDataSchema::Version::VERSION_5: { - auto decryptedStreamRoomData = decryptModuleDataV5(streamRoomEntry, encKey); - return std::make_tuple( - convertDecryptedStreamRoomDataV5ToStreamRoom(streamRoom, decryptedStreamRoomData), - decryptedStreamRoomData.dio - ); - } - } - auto e = UnknowStreamRoomFormatException(); - return std::make_tuple( - convertServerStreamRoomToLibStreamRoom(streamRoom, {}, {}, e.getCode()), core::DataIntegrityObject() - ); -} - -std::vector StreamApiLowImpl::decryptAndConvertStreamRoomsDataToStreamRooms( - std::vector streamRooms -) { - std::vector result; - core::KeyDecryptionAndVerificationRequest keyProviderRequest; - //create request to KeyProvider for keys - for (size_t i = 0; i < streamRooms.size(); i++) { - auto streamRoom = streamRooms[i]; - core::EncKeyLocation location{ - .contextId = streamRoom.contextId, .resourceId = streamRoom.resourceId.value_or("") - }; - auto streamRoom_data_entry = streamRoom.data.back(); - keyProviderRequest.addOne(streamRoom.keys, streamRoom_data_entry.keyId, location); - } - //send request to KeyProvider - auto streamRoomsKeys{_keyProvider->getKeysAndVerify(keyProviderRequest)}; - std::vector streamRoomsDIO; - std::map duplication_check; - for (auto streamRoom : streamRooms) { - try { - auto tmp = decryptAndConvertStreamRoomDataToStreamRoom( - streamRoom, streamRoom.data.back(), - streamRoomsKeys - .at(core::EncKeyLocation{ - .contextId = streamRoom.contextId, .resourceId = streamRoom.resourceId.value_or("") - }) - .at(streamRoom.data.back().keyId) - ); - result.push_back(std::get<0>(tmp)); - auto streamRoomDIO = std::get<1>(tmp); - streamRoomsDIO.push_back(streamRoomDIO); - //find duplication - std::string fullRandomId = streamRoomDIO.randomId + "-" + std::to_string(streamRoomDIO.timestamp); - if (duplication_check.find(fullRandomId) == duplication_check.end()) { - duplication_check.insert(std::make_pair(fullRandomId, true)); - } else { - result[result.size() - 1].statusCode = core::DataIntegrityObjectDuplicatedException().getCode(); - } - } catch (const core::Exception& e) { - result.push_back(convertServerStreamRoomToLibStreamRoom(streamRoom, {}, {}, e.getCode())); - streamRoomsDIO.push_back(core::DataIntegrityObject{}); - } - } - std::vector verifierInput{}; - for (size_t i = 0; i < result.size(); i++) { - if (result[i].statusCode == 0) { - verifierInput.push_back( - core::VerificationRequest{ - .contextId = result[i].contextId, - .senderId = result[i].lastModifier, - .senderPubKey = streamRoomsDIO[i].creatorPubKey, - .date = result[i].lastModificationDate, - .bridgeIdentity = streamRoomsDIO[i].bridgeIdentity - } - ); - } - } - std::vector verified; - try { - verified = _connection->getUserVerifier()->verify(verifierInput); - } catch (...) { throw core::UserVerificationMethodUnhandledException(); } - for (size_t j = 0, i = 0; i < result.size(); i++) { - if (result[i].statusCode == 0) { - result[i].statusCode = verified[j] ? 0 : - core::ExceptionConverter::getCodeOfUserVerificationFailureException(); - j++; - } - } - return result; -} - -StreamRoom StreamApiLowImpl::decryptAndConvertStreamRoomDataToStreamRoom(server::StreamRoomInfo streamRoom) { - auto streamRoom_data_entry = streamRoom.data.back(); - core::KeyDecryptionAndVerificationRequest keyProviderRequest; - core::EncKeyLocation location{.contextId = streamRoom.contextId, .resourceId = streamRoom.resourceId.value_or("")}; - keyProviderRequest.addOne(streamRoom.keys, streamRoom_data_entry.keyId, location); - auto key = _keyProvider->getKeysAndVerify(keyProviderRequest).at(location).at(streamRoom_data_entry.keyId); - StreamRoom result; - core::DataIntegrityObject streamRoomDIO; - std::tie(result, streamRoomDIO) = decryptAndConvertStreamRoomDataToStreamRoom( - streamRoom, streamRoom_data_entry, key - ); - if (result.statusCode != 0) - return result; - std::vector verifierInput{}; - verifierInput.push_back( - core::VerificationRequest{ - .contextId = result.contextId, - .senderId = result.lastModifier, - .senderPubKey = streamRoomDIO.creatorPubKey, - .date = result.lastModificationDate, - .bridgeIdentity = streamRoomDIO.bridgeIdentity - } - ); - std::vector verified; - try { - verified = _connection->getUserVerifier()->verify(verifierInput); - } catch (...) { throw core::UserVerificationMethodUnhandledException(); } - result.statusCode = verified[0] ? 0 : core::ExceptionConverter::getCodeOfUserVerificationFailureException(); - return result; -} - std::vector StreamApiLowImpl::mapUsers(const std::vector& users) { std::vector result; for (auto user : users) { @@ -965,7 +768,7 @@ std::pair StreamApiLowImpl::getModuleKeysAndVersionFr params.id = moduleId; auto stream = _serverApi->streamRoomGet(params).streamRoom; // validate stream Data before returning data - assertStreamRoomDataIntegrity(stream); + _streamRoomDataSchemaMapper.assertDataIntegrity(stream); return std::make_pair(streamRoomToModuleKeys(stream), stream.version); } @@ -973,41 +776,12 @@ core::ModuleKeys StreamApiLowImpl::streamRoomToModuleKeys(server::StreamRoomInfo return core::ModuleKeys{ .keys = stream.keys, .currentKeyId = stream.keyId, - .moduleSchemaVersion = getStreamRoomEntryDataStructureVersion(stream.data.back()), + .moduleSchemaVersion = _streamRoomDataSchemaMapper.getDataStructureVersion(stream.data.back()), .moduleResourceId = stream.resourceId.value_or(""), .contextId = stream.contextId }; } -void StreamApiLowImpl::assertStreamRoomDataIntegrity(server::StreamRoomInfo streamRoom) { - auto streamRoom_data_entry = streamRoom.data.back(); - switch (getStreamRoomEntryDataStructureVersion(streamRoom_data_entry)) { - case StreamRoomDataSchema::Version::UNKNOWN: - throw UnknowStreamRoomFormatException(); - case StreamRoomDataSchema::Version::VERSION_5: { - auto streamRoom_data = core::dynamic::EncryptedModuleDataV5::fromJSON(streamRoom_data_entry.data); - auto dio = _streamRoomDataEncryptorV5.getDIOAndAssertIntegrity(streamRoom_data); - if (dio.contextId != streamRoom.contextId || - dio.resourceId != streamRoom.resourceId.value_or("") || - dio.creatorUserId != streamRoom.lastModifier || - !core::TimestampValidator::validate(dio.timestamp, streamRoom.lastModificationDate)) { - throw StreamRoomDataIntegrityException(); - } - return; - } - } - throw UnknowStreamRoomFormatException(); -} - -uint32_t StreamApiLowImpl::validateStreamRoomDataIntegrity(server::StreamRoomInfo streamRoom) { - try { - assertStreamRoomDataIntegrity(streamRoom); - return 0; - } catch (const core::Exception& e) { return e.getCode(); } catch (const privmx::utils::PrivmxException& e) { - return core::ExceptionConverter::convert(e).getCode(); - } catch (...) { return ENDPOINT_CORE_EXCEPTION_CODE; } -} - void StreamApiLowImpl::assertTurnServerUri(const std::string& uri) { try { const Poco::URI parsed(uri); diff --git a/endpoint/stream/stream/src/encryptors/streamRoom/StreamRoomDataSchemaMapper.cpp b/endpoint/stream/stream/src/encryptors/streamRoom/StreamRoomDataSchemaMapper.cpp new file mode 100644 index 00000000..3a694c7f --- /dev/null +++ b/endpoint/stream/stream/src/encryptors/streamRoom/StreamRoomDataSchemaMapper.cpp @@ -0,0 +1,218 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "privmx/endpoint/stream/encryptors/streamRoom/StreamRoomDataSchemaMapper.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "privmx/endpoint/stream/StreamException.hpp" + +using namespace privmx::endpoint; +using namespace privmx::endpoint::stream; + +StreamRoomDataSchemaMapper::StreamRoomDataSchemaMapper( + const privmx::crypto::PrivateKey& userPrivKey, + const core::Connection& connection +) + : _userPrivKey(userPrivKey), _connection(connection) { + _strategyV5 = std::make_shared(); + _strategyMapper.registerStrategy(StreamRoomDataSchema::Version::VERSION_5, _strategyV5); +} + +Poco::Dynamic::Var StreamRoomDataSchemaMapper::encrypt( + const core::ModuleDataToEncryptV5& data, + const std::string& key +) { + return _encryptorV5.encrypt(data, _userPrivKey, key).toJSON(); +} + +std::tuple StreamRoomDataSchemaMapper::decrypt( + const server::StreamRoomInfo& streamRoom, + const core::DecryptedEncKey& encKey +) { + auto version = getDataStructureVersion(streamRoom.data.back()); + auto strategy = _strategyMapper.getStrategy(static_cast(version)); + if (!strategy) { + auto e = UnknowStreamRoomFormatException(); + return { + toLibStreamRoom(streamRoom, {}, {}, e.getCode(), StreamRoomDataSchema::Version::UNKNOWN), + core::DataIntegrityObject{} + }; + } + return strategy->decryptAndConvert(streamRoom, encKey); +} + +StreamRoomDataSchema::Version StreamRoomDataSchemaMapper::getDataStructureVersion( + const server::StreamRoomDataEntry& entry +) { + if (entry.data.type() == typeid(Poco::JSON::Object::Ptr)) { + auto versioned = core::dynamic::VersionedData::fromJSON(entry.data); + switch (versioned.version) { + case core::ModuleDataSchema::Version::VERSION_5: + return StreamRoomDataSchema::Version::VERSION_5; + default: + return StreamRoomDataSchema::Version::UNKNOWN; + } + } + return StreamRoomDataSchema::Version::UNKNOWN; +} + +void StreamRoomDataSchemaMapper::assertDataIntegrity(const server::StreamRoomInfo& streamRoom) { + const auto& entry = streamRoom.data.back(); + switch (getDataStructureVersion(entry)) { + case StreamRoomDataSchema::Version::VERSION_5: { + auto encData = core::dynamic::EncryptedModuleDataV5::fromJSON(entry.data); + auto dio = _strategyV5->getDIOAndAssertIntegrity(encData); + if (dio.contextId != streamRoom.contextId || + dio.resourceId != streamRoom.resourceId.value_or("") || + dio.creatorUserId != streamRoom.lastModifier || + !core::TimestampValidator::validate(dio.timestamp, streamRoom.lastModificationDate)) { + throw StreamRoomDataIntegrityException(); + } + return; + } + default: + throw UnknowStreamRoomFormatException(); + } + throw UnknowStreamRoomFormatException(); +} + +uint32_t StreamRoomDataSchemaMapper::validateDataIntegrity(const server::StreamRoomInfo& streamRoom) { + try { + assertDataIntegrity(streamRoom); + return 0; + } catch (const core::Exception& e) { return e.getCode(); } catch (const privmx::utils::PrivmxException& e) { + return core::ExceptionConverter::convert(e).getCode(); + } catch (...) { return ENDPOINT_CORE_EXCEPTION_CODE; } +} + +std::vector StreamRoomDataSchemaMapper::validateDecryptAndConvertStreamRooms( + const std::vector& streamRooms, + const std::shared_ptr& keyProvider +) { + if (streamRooms.empty()) { + return {}; + } + + std::vector result(streamRooms.size()); + std::vector result_dio(streamRooms.size()); + std::set seenRandomIds; + + for (size_t i = 0; i < streamRooms.size(); i++) { + auto code = validateDataIntegrity(streamRooms[i]); + if (code != 0) { + result[i] = toLibStreamRoom(streamRooms[i], {}, {}, code, StreamRoomDataSchema::Version::UNKNOWN); + } + } + + core::KeyDecryptionAndVerificationRequest keyRequest; + for (size_t i = 0; i < streamRooms.size(); i++) { + if (result[i].statusCode != 0) { + continue; + } + const auto& room = streamRooms[i]; + core::EncKeyLocation loc{.contextId = room.contextId, .resourceId = room.resourceId.value_or("")}; + keyRequest.addOne(room.keys, room.data.back().keyId, loc); + } + auto roomKeys = keyProvider->getKeysAndVerify(keyRequest); + + for (size_t i = 0; i < streamRooms.size(); i++) { + if (result[i].statusCode != 0) { + continue; + } + const auto& room = streamRooms[i]; + core::EncKeyLocation loc{.contextId = room.contextId, .resourceId = room.resourceId.value_or("")}; + try { + auto it = roomKeys.find(loc); + if (it == roomKeys.end()) { + throw UnknowStreamRoomFormatException(); + } + auto [decryptedRoom, dio] = decrypt(room, it->second.at(room.data.back().keyId)); + result[i] = decryptedRoom; + result_dio[i] = dio; + if (!seenRandomIds.insert(dio.randomId + "-" + std::to_string(dio.timestamp)).second) { + result[i].statusCode = core::DataIntegrityObjectDuplicatedException().getCode(); + } + } catch (const core::Exception& e) { + result[i] = toLibStreamRoom(room, {}, {}, e.getCode(), StreamRoomDataSchema::Version::UNKNOWN); + } catch (const privmx::utils::PrivmxException& e) { + result[i] = toLibStreamRoom( + room, {}, {}, core::ExceptionConverter::convert(e).getCode(), StreamRoomDataSchema::Version::UNKNOWN + ); + } catch (...) { + result[i] = toLibStreamRoom( + room, {}, {}, ENDPOINT_CORE_EXCEPTION_CODE, StreamRoomDataSchema::Version::UNKNOWN + ); + } + } + + std::vector verifyRequests; + std::vector verifyIndices; + for (size_t i = 0; i < result.size(); i++) { + if (result[i].statusCode != 0) { + continue; + } + verifyRequests.push_back( + {.contextId = result[i].contextId, + .senderId = result[i].lastModifier, + .senderPubKey = result_dio[i].creatorPubKey, + .date = result[i].lastModificationDate, + .bridgeIdentity = result_dio[i].bridgeIdentity} + ); + verifyIndices.push_back(i); + } + auto verified = _connection.getImpl()->getUserVerifier()->verify(verifyRequests); + for (size_t j = 0; j < verifyIndices.size(); j++) { + result[verifyIndices[j]].statusCode = verified[j] ? + 0 : + core::ExceptionConverter::getCodeOfUserVerificationFailureException(); + } + return result; +} + +StreamRoom StreamRoomDataSchemaMapper::validateDecryptAndConvertStreamRoom( + const server::StreamRoomInfo& streamRoom, + const std::shared_ptr& keyProvider +) { + return validateDecryptAndConvertStreamRooms({streamRoom}, keyProvider)[0]; +} + +StreamRoom StreamRoomDataSchemaMapper::toLibStreamRoom( + const server::StreamRoomInfo& info, + const core::Buffer& publicMeta, + const core::Buffer& privateMeta, + int64_t statusCode, + int64_t schemaVersion +) { + return StreamRoom{ + .contextId = info.contextId, + .streamRoomId = info.id, + .createDate = info.createDate, + .creator = info.creator, + .lastModificationDate = info.lastModificationDate, + .lastModifier = info.lastModifier, + .users = info.users, + .managers = info.managers, + .version = info.version, + .publicMeta = publicMeta, + .privateMeta = privateMeta, + .policy = core::Factory::parsePolicyServerObject(info.policy), + .statusCode = statusCode, + .schemaVersion = schemaVersion, + .closed = info.closed.value_or(true) + }; +} \ No newline at end of file diff --git a/endpoint/stream/stream/src/encryptors/streamRoom/StreamRoomDataSchemaStrategyV5.cpp b/endpoint/stream/stream/src/encryptors/streamRoom/StreamRoomDataSchemaStrategyV5.cpp new file mode 100644 index 00000000..4711d928 --- /dev/null +++ b/endpoint/stream/stream/src/encryptors/streamRoom/StreamRoomDataSchemaStrategyV5.cpp @@ -0,0 +1,63 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "privmx/endpoint/stream/encryptors/streamRoom/StreamRoomDataSchemaStrategyV5.hpp" +#include "privmx/endpoint/stream/encryptors/streamRoom/StreamRoomDataSchemaMapper.hpp" + +#include + +using namespace privmx::endpoint; +using namespace privmx::endpoint::stream; + +core::DecryptedModuleDataV5 StreamRoomDataSchemaStrategyV5::decrypt( + const server::StreamRoomInfo& streamRoom, + const core::DecryptedEncKey& encKey +) const { + auto encryptedData = core::dynamic::EncryptedModuleDataV5::fromJSON(streamRoom.data.back().data); + core::DecryptedModuleDataV5 result; + if (encKey.statusCode != 0) { + result = _encryptor.extractPublic(encryptedData); + result.statusCode = encKey.statusCode; + } else { + result = _encryptor.decrypt(encryptedData, encKey.key); + } + return result; +} + +std::tuple StreamRoomDataSchemaStrategyV5::convert( + const server::StreamRoomInfo& streamRoom, + const core::DecryptedModuleDataV5& raw +) const { + return { + StreamRoomDataSchemaMapper::toLibStreamRoom( + streamRoom, raw.publicMeta, raw.privateMeta, raw.statusCode, StreamRoomDataSchema::Version::VERSION_5 + ), + raw.dio + }; +} + +std::tuple StreamRoomDataSchemaStrategyV5::makeErrorResult( + const server::StreamRoomInfo& streamRoom, + int64_t errorCode +) const { + return { + StreamRoomDataSchemaMapper::toLibStreamRoom( + streamRoom, {}, {}, errorCode, StreamRoomDataSchema::Version::VERSION_5 + ), + core::DataIntegrityObject{} + }; +} + +core::DataIntegrityObject StreamRoomDataSchemaStrategyV5::getDIOAndAssertIntegrity( + const core::dynamic::EncryptedModuleDataV5& encData +) const { + return _encryptor.getDIOAndAssertIntegrity(encData); +} diff --git a/endpoint/thread/include/privmx/endpoint/thread/DynamicTypes.hpp b/endpoint/thread/include/privmx/endpoint/thread/DynamicTypes.hpp deleted file mode 100644 index 92902962..00000000 --- a/endpoint/thread/include/privmx/endpoint/thread/DynamicTypes.hpp +++ /dev/null @@ -1,77 +0,0 @@ -/* -PrivMX Endpoint. -Copyright © 2024 Simplito sp. z o.o. - -This file is part of the PrivMX Platform (https://privmx.dev). -This software is Licensed under the PrivMX Free License. - -See the License for the specific language governing permissions and -limitations under the License. -*/ - -#ifndef _PRIVMXLIB_ENDPOINT_THREAD_DYNAMICTYPES_HPP_ -#define _PRIVMXLIB_ENDPOINT_THREAD_DYNAMICTYPES_HPP_ - -#include - -#include - -namespace privmx { -namespace endpoint { -namespace thread { -namespace dynamic { - -#define THREAD_DATA_V1_FIELDS(F) \ - F(title, std::string) \ - F(statusCode, int64_t) -JSON_STRUCT(ThreadDataV1, THREAD_DATA_V1_FIELDS); - -#define MESSAGE_DATA_AUTHOR_FIELDS(F) \ - F(userId, std::string) \ - F(pubKey, std::string) -JSON_STRUCT(MessageDataAuthor, MESSAGE_DATA_AUTHOR_FIELDS); - -#define MESSAGE_DATA_DESTINATION_FIELDS(F) \ - F(server, std::string) \ - F(contextId, std::string) \ - F(threadId, std::string) -JSON_STRUCT(MessageDataDestination, MESSAGE_DATA_DESTINATION_FIELDS); - -#define I_MESSAGE_DATA_FIELDS(F) F(v, int64_t) -JSON_STRUCT(IMessageData, I_MESSAGE_DATA_FIELDS); - -#define I_MESSAGE_DATA_SIGNED_FIELDS(F) \ - F(data, IMessageData) \ - F(dataBuf, Pson::BinaryString) \ - F(dataSignature, Pson::BinaryString) -JSON_STRUCT(IMessageDataSigned, I_MESSAGE_DATA_SIGNED_FIELDS); - -#define MESSAGE_DATA_V2_FIELDS(F) \ - F(msgId, std::string) \ - F(type, std::string) \ - F(text, std::string) \ - F(date, int64_t) \ - F(deleted, bool) \ - F(author, MessageDataAuthor) \ - F(destination, MessageDataDestination) \ - F(statusCode, int64_t) -JSON_STRUCT_EXT(MessageDataV2, IMessageData, MESSAGE_DATA_V2_FIELDS); - -#define MESSAGE_DATA_V2_SIGNED_FIELDS(F) F(data, MessageDataV2) -JSON_STRUCT_EXT(MessageDataV2Signed, IMessageDataSigned, MESSAGE_DATA_V2_SIGNED_FIELDS); - -#define MESSAGE_DATA_V3_FIELDS(F) \ - F(publicMeta, Pson::BinaryString) \ - F(privateMeta, Pson::BinaryString) \ - F(data, Pson::BinaryString) \ - F(statusCode, int64_t) -JSON_STRUCT_EXT(MessageDataV3, IMessageData, MESSAGE_DATA_V3_FIELDS); -#define MESSAGE_DATA_V3_SIGNED_FIELDS(F) F(data, MessageDataV3) -JSON_STRUCT_EXT(MessageDataV3Signed, IMessageDataSigned, MESSAGE_DATA_V3_SIGNED_FIELDS); - -} // namespace dynamic -} // namespace thread -} // namespace endpoint -} // namespace privmx - -#endif // _PRIVMXLIB_ENDPOINT_THREAD_DYNAMICTYPES_HPP_ diff --git a/endpoint/thread/include/privmx/endpoint/thread/ThreadApiImpl.hpp b/endpoint/thread/include/privmx/endpoint/thread/ThreadApiImpl.hpp index 274d47f4..e18fe545 100644 --- a/endpoint/thread/include/privmx/endpoint/thread/ThreadApiImpl.hpp +++ b/endpoint/thread/include/privmx/endpoint/thread/ThreadApiImpl.hpp @@ -19,25 +19,17 @@ limitations under the License. #include #include -#include -#include -#include -#include -#include #include "privmx/endpoint/core/ContainerKeyCache.hpp" #include "privmx/endpoint/core/Factory.hpp" #include "privmx/endpoint/core/ModuleBaseApi.hpp" #include "privmx/endpoint/thread/Constants.hpp" -#include "privmx/endpoint/thread/DynamicTypes.hpp" #include "privmx/endpoint/thread/Events.hpp" -#include "privmx/endpoint/thread/MessageKeyIdFormatValidator.hpp" #include "privmx/endpoint/thread/ServerApi.hpp" #include "privmx/endpoint/thread/SubscriberImpl.hpp" #include "privmx/endpoint/thread/ThreadApi.hpp" -#include "privmx/endpoint/thread/encryptors/message/MessageDataEncryptor.hpp" -#include "privmx/endpoint/thread/encryptors/message/MessageDataEncryptorV4.hpp" -#include "privmx/endpoint/thread/encryptors/message/MessageDataEncryptorV5.hpp" +#include "privmx/endpoint/thread/encryptors/message/MessageDataSchemaMapper.hpp" +#include "privmx/endpoint/thread/encryptors/thread/ThreadDataSchemaMapper.hpp" #include namespace privmx { @@ -141,65 +133,10 @@ class ThreadApiImpl : public privmx::utils::ManualManagedClass, p void processConnectedEvent(); void processDisconnectedEvent(); std::vector mapUsers(const std::vector& users); - dynamic::ThreadDataV1 decryptThreadV1(server::Thread2DataEntry threadEntry, const core::DecryptedEncKey& encKey); - Thread convertServerThreadToLibThread( - server::ThreadInfo threadInfo, - const core::Buffer& publicMeta = core::Buffer(), - const core::Buffer& privateMeta = core::Buffer(), - const int64_t& statusCode = 0, - const int64_t& schemaVersion = ThreadDataSchema::Version::UNKNOWN - ); - Thread convertThreadDataV1ToThread(server::ThreadInfo threadInfo, dynamic::ThreadDataV1 threadData); - Thread convertDecryptedThreadDataV4ToThread( - server::ThreadInfo threadInfo, - const core::DecryptedModuleDataV4& threadData - ); - Thread convertDecryptedThreadDataV5ToThread( - server::ThreadInfo threadInfo, - const core::DecryptedModuleDataV5& threadData - ); - ThreadDataSchema::Version getThreadEntryDataStructureVersion(server::Thread2DataEntry threadEntry); - std::tuple decryptAndConvertThreadDataToThread( - server::ThreadInfo thread, - server::Thread2DataEntry threadEntry, - const core::DecryptedEncKey& encKey - ); - std::vector validateDecryptAndConvertThreadsDataToThreads(std::vector threads); - Thread validateDecryptAndConvertThreadDataToThread(server::ThreadInfo thread); - void assertThreadDataIntegrity(server::ThreadInfo thread); - uint32_t validateThreadDataIntegrity(server::ThreadInfo thread); virtual std::pair getModuleKeysAndVersionFromServer(std::string moduleId) override; core::ModuleKeys threadToModuleKeys(server::ThreadInfo thread); - dynamic::MessageDataV2 decryptMessageDataV2(server::Message message, const core::DecryptedEncKey& encKey); - dynamic::MessageDataV3 decryptMessageDataV3(server::Message message, const core::DecryptedEncKey& encKey); - DecryptedMessageDataV4 decryptMessageDataV4(server::Message message, const core::DecryptedEncKey& encKey); - DecryptedMessageDataV5 decryptMessageDataV5(server::Message message, const core::DecryptedEncKey& encKey); - Message convertServerMessageToLibMessage( - server::Message message, - const core::Buffer& publicMeta = core::Buffer(), - const core::Buffer& privateMeta = core::Buffer(), - const core::Buffer& data = core::Buffer(), - const std::string& authorPubKey = std::string(), - const int64_t& statusCode = 0, - const int64_t& schemaVersion = MessageDataSchema::Version::UNKNOWN - ); - Message convertMessageDataV2ToMessage(server::Message message, dynamic::MessageDataV2 messageData); - Message convertMessageDataV3ToMessage(server::Message message, dynamic::MessageDataV3 messageData); - Message convertDecryptedMessageDataV4ToMessage(server::Message message, DecryptedMessageDataV4 messageData); - Message convertDecryptedMessageDataV5ToMessage(server::Message message, DecryptedMessageDataV5 messageData); - MessageDataSchema::Version getMessagesDataStructureVersion(server::Message message); - std::tuple decryptAndConvertMessageDataToMessage( - server::Message message, - const core::DecryptedEncKey& encKey - ); - std::vector validateDecryptAndConvertMessagesDataToMessages( - std::vector messages, - const core::ModuleKeys& threadKeys - ); - Message validateDecryptAndConvertMessageDataToMessage(server::Message message, const core::ModuleKeys& threadKeys); core::ModuleKeys getMessageDecryptionKeys(server::Message message); - uint32_t validateMessageDataIntegrity(server::Message message, const std::string& threadResourceId); Poco::Dynamic::Var encryptMessageData( const std::string& threadId, const std::string& resourceId, @@ -234,18 +171,12 @@ class ThreadApiImpl : public privmx::utils::ManualManagedClass, p std::shared_ptr _eventMiddleware; core::Connection _connection; ServerApi _serverApi; - core::DataEncryptor _dataEncryptorThread; - MessageDataV2Encryptor _messageDataV2Encryptor; - MessageDataV3Encryptor _messageDataV3Encryptor; - MessageKeyIdFormatValidator _messageKeyIdFormatValidator; SubscriberImpl _subscriber; int _notificationListenerId, _connectedListenerId, _disconnectedListenerId; std::string _messageDecryptorId, _messageDeleterId; - MessageDataEncryptorV4 _messageDataEncryptorV4; - core::ModuleDataEncryptorV4 _threadDataEncryptorV4; - MessageDataEncryptorV5 _messageDataEncryptorV5; - core::ModuleDataEncryptorV5 _threadDataEncryptorV5; + MessageDataSchemaMapper _messageDataSchemaMapper; + ThreadDataSchemaMapper _threadDataSchemaMapper; core::DataEncryptorV4 _eventDataEncryptorV4; std::vector _forbiddenChannelsNames; diff --git a/endpoint/thread/include/privmx/endpoint/thread/ThreadTypes.hpp b/endpoint/thread/include/privmx/endpoint/thread/ThreadTypes.hpp index c4f04257..049e5967 100644 --- a/endpoint/thread/include/privmx/endpoint/thread/ThreadTypes.hpp +++ b/endpoint/thread/include/privmx/endpoint/thread/ThreadTypes.hpp @@ -12,10 +12,12 @@ limitations under the License. #ifndef _PRIVMXLIB_ENDPOINT_THREAD_THREADTYPES_HPP_ #define _PRIVMXLIB_ENDPOINT_THREAD_THREADTYPES_HPP_ +#include #include #include "privmx/endpoint/core/CoreTypes.hpp" -#include "privmx/endpoint/thread/DynamicTypes.hpp" +#include "privmx/endpoint/core/encryptors/module/Types.hpp" +#include "privmx/endpoint/thread/Constants.hpp" namespace privmx { namespace endpoint { diff --git a/endpoint/thread/include/privmx/endpoint/thread/encryptors/message/MessageDataEncryptor.hpp b/endpoint/thread/include/privmx/endpoint/thread/encryptors/message/MessageDataEncryptor.hpp deleted file mode 100644 index 76855ffc..00000000 --- a/endpoint/thread/include/privmx/endpoint/thread/encryptors/message/MessageDataEncryptor.hpp +++ /dev/null @@ -1,60 +0,0 @@ -/* -PrivMX Endpoint. -Copyright © 2024 Simplito sp. z o.o. - -This file is part of the PrivMX Platform (https://privmx.dev). -This software is Licensed under the PrivMX Free License. - -See the License for the specific language governing permissions and -limitations under the License. -*/ - -#ifndef _PRIVMXLIB_ENDPOINT_THREAD_MESSAGEDATAENCRYPTOR_HPP_ -#define _PRIVMXLIB_ENDPOINT_THREAD_MESSAGEDATAENCRYPTOR_HPP_ - -#include - -#include - -#include -#include - -#include "privmx/endpoint/thread/ServerTypes.hpp" -#include "privmx/endpoint/thread/ThreadTypes.hpp" - -namespace privmx { -namespace endpoint { -namespace thread { - -class MessageDataV2Encryptor { -public: - std::string signAndEncrypt( - const dynamic::MessageDataV2& data, - const privmx::crypto::PrivateKey& priv, - const core::EncKey& encKey - ); - dynamic::MessageDataV2Signed decryptAndGetSign(const std::string& data, const core::EncKey& key); - -private: - core::DataEncryptor _dataEncryptor; -}; - -class MessageDataV3Encryptor { -public: - std::string signAndEncrypt( - const dynamic::MessageDataV3& data, - const privmx::crypto::PrivateKey& priv, - const core::EncKey& encKey - ); - dynamic::MessageDataV3Signed decryptAndGetSign(const std::string& data, const core::EncKey& key); - -private: - core::DataEncryptor _dataEncryptorBinaryString; - core::DataEncryptor _dataEncryptorMessageDataV3; -}; - -} // namespace thread -} // namespace endpoint -} // namespace privmx - -#endif // _PRIVMXLIB_ENDPOINT_THREAD_MESSAGEDATAENCRYPTOR_HPP_ diff --git a/endpoint/thread/include/privmx/endpoint/thread/encryptors/message/MessageDataSchemaMapper.hpp b/endpoint/thread/include/privmx/endpoint/thread/encryptors/message/MessageDataSchemaMapper.hpp new file mode 100644 index 00000000..dada8091 --- /dev/null +++ b/endpoint/thread/include/privmx/endpoint/thread/encryptors/message/MessageDataSchemaMapper.hpp @@ -0,0 +1,101 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef _PRIVMXLIB_ENDPOINT_THREAD_MESSAGEDATASCHEMAMAPPER_HPP_ +#define _PRIVMXLIB_ENDPOINT_THREAD_MESSAGEDATASCHEMAMAPPER_HPP_ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "privmx/endpoint/thread/Constants.hpp" +#include "privmx/endpoint/thread/MessageKeyIdFormatValidator.hpp" +#include "privmx/endpoint/thread/ServerTypes.hpp" +#include "privmx/endpoint/thread/Types.hpp" +#include "privmx/endpoint/thread/encryptors/message/MessageDataSchemaStrategyV4.hpp" +#include "privmx/endpoint/thread/encryptors/message/MessageDataSchemaStrategyV5.hpp" + +namespace privmx { +namespace endpoint { +namespace thread { + +class MessageDataSchemaMapper { +public: + MessageDataSchemaMapper(const privmx::crypto::PrivateKey& userPrivKey, const core::Connection& connection); + + Poco::Dynamic::Var encrypt( + const std::string& threadId, + const std::string& resourceId, + const std::string& contextId, + const std::string& moduleResourceId, + const core::Buffer& publicMeta, + const core::Buffer& privateMeta, + const core::Buffer& data, + const core::DecryptedEncKeyV2& msgKey + ); + + std::tuple decrypt( + const server::Message& message, + const core::DecryptedEncKey& encKey + ); + + MessageDataSchema::Version getMessagesDataStructureVersion(const server::Message& message); + + uint32_t validateMessageDataIntegrity(const server::Message& message, const std::string& threadResourceId); + + ThreadDataSchema::Version getMinimumContainerSchemaVersionForMessage(const server::Message& message); + + std::vector validateDecryptAndConvertMessages( + const std::vector& messages, + const core::ModuleKeys& threadKeys, + const std::shared_ptr& keyProvider + ); + + Message validateDecryptAndConvertMessage( + const server::Message& message, + const core::ModuleKeys& threadKeys, + const std::shared_ptr& keyProvider + ); + + static Message toLibMessage( + const server::Message& message, + const core::Buffer& publicMeta, + const core::Buffer& privateMeta, + const core::Buffer& data, + const std::string& authorPubKey, + int64_t statusCode, + int64_t schemaVersion + ); + +private: + privmx::crypto::PrivateKey _userPrivKey; + core::Connection _connection; + MessageKeyIdFormatValidator _messageKeyIdFormatValidator; + core::VersionStrategyMapper> _strategyMapper; + std::shared_ptr _strategyV4; + std::shared_ptr _strategyV5; +}; + +} // namespace thread +} // namespace endpoint +} // namespace privmx + +#endif // _PRIVMXLIB_ENDPOINT_THREAD_MESSAGEDATASCHEMAMAPPER_HPP_ diff --git a/endpoint/thread/include/privmx/endpoint/thread/encryptors/message/MessageDataSchemaStrategyV4.hpp b/endpoint/thread/include/privmx/endpoint/thread/encryptors/message/MessageDataSchemaStrategyV4.hpp new file mode 100644 index 00000000..ead1de50 --- /dev/null +++ b/endpoint/thread/include/privmx/endpoint/thread/encryptors/message/MessageDataSchemaStrategyV4.hpp @@ -0,0 +1,61 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef _PRIVMXLIB_ENDPOINT_THREAD_MESSAGEDATASCHEMASTRATEGYV4_HPP_ +#define _PRIVMXLIB_ENDPOINT_THREAD_MESSAGEDATASCHEMASTRATEGYV4_HPP_ + +#include + +#include +#include + +#include "privmx/endpoint/thread/ServerTypes.hpp" +#include "privmx/endpoint/thread/ThreadTypes.hpp" +#include "privmx/endpoint/thread/Types.hpp" +#include "privmx/endpoint/thread/encryptors/message/MessageDataEncryptorV4.hpp" + +namespace privmx { +namespace endpoint { +namespace thread { +// clang-format off +class MessageDataSchemaStrategyV4 : public core::TypedDataSchemaStrategy< + server::Message, + DecryptedMessageDataV4, + std::tuple +> { + // clang-format on +public: + server::EncryptedMessageDataV4 encrypt( + const core::Buffer& publicMeta, + const core::Buffer& privateMeta, + const core::Buffer& data, + const privmx::crypto::PrivateKey& userPrivKey, + const std::string& key + ) const; + DecryptedMessageDataV4 decrypt(const server::Message& message, const core::DecryptedEncKey& encKey) const override; + std::tuple convert( + const server::Message& message, + const DecryptedMessageDataV4& raw + ) const override; + std::tuple makeErrorResult( + const server::Message& message, + int64_t errorCode + ) const override; + +private: + mutable MessageDataEncryptorV4 _encryptor; +}; + +} // namespace thread +} // namespace endpoint +} // namespace privmx + +#endif // _PRIVMXLIB_ENDPOINT_THREAD_MESSAGEDATASCHEMASTRATEGYV4_HPP_ diff --git a/endpoint/thread/include/privmx/endpoint/thread/encryptors/message/MessageDataSchemaStrategyV5.hpp b/endpoint/thread/include/privmx/endpoint/thread/encryptors/message/MessageDataSchemaStrategyV5.hpp new file mode 100644 index 00000000..443b5fed --- /dev/null +++ b/endpoint/thread/include/privmx/endpoint/thread/encryptors/message/MessageDataSchemaStrategyV5.hpp @@ -0,0 +1,63 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef _PRIVMXLIB_ENDPOINT_THREAD_MESSAGEDATASCHEMASTRATEGYV5_HPP_ +#define _PRIVMXLIB_ENDPOINT_THREAD_MESSAGEDATASCHEMASTRATEGYV5_HPP_ + +#include + +#include +#include + +#include "privmx/endpoint/thread/ServerTypes.hpp" +#include "privmx/endpoint/thread/ThreadTypes.hpp" +#include "privmx/endpoint/thread/Types.hpp" +#include "privmx/endpoint/thread/encryptors/message/MessageDataEncryptorV5.hpp" + +namespace privmx { +namespace endpoint { +namespace thread { +// clang-format off +class MessageDataSchemaStrategyV5 : public core::TypedDataSchemaStrategy< + server::Message, + DecryptedMessageDataV5, + std::tuple +> { + // clang-format on +public: + server::EncryptedMessageDataV5 encrypt( + const core::Buffer& publicMeta, + const core::Buffer& privateMeta, + const core::Buffer& data, + const privmx::crypto::PrivateKey& userPrivKey, + const std::string& key, + const core::DataIntegrityObject& dio + ) const; + DecryptedMessageDataV5 decrypt(const server::Message& message, const core::DecryptedEncKey& encKey) const override; + std::tuple convert( + const server::Message& message, + const DecryptedMessageDataV5& raw + ) const override; + std::tuple makeErrorResult( + const server::Message& message, + int64_t errorCode + ) const override; + core::DataIntegrityObject getDIOAndAssertIntegrity(const server::EncryptedMessageDataV5& encData) const; + +private: + mutable MessageDataEncryptorV5 _encryptor; +}; + +} // namespace thread +} // namespace endpoint +} // namespace privmx + +#endif // _PRIVMXLIB_ENDPOINT_THREAD_MESSAGEDATASCHEMASTRATEGYV5_HPP_ diff --git a/endpoint/thread/include/privmx/endpoint/thread/encryptors/thread/ThreadDataSchemaMapper.hpp b/endpoint/thread/include/privmx/endpoint/thread/encryptors/thread/ThreadDataSchemaMapper.hpp new file mode 100644 index 00000000..014cb9a9 --- /dev/null +++ b/endpoint/thread/include/privmx/endpoint/thread/encryptors/thread/ThreadDataSchemaMapper.hpp @@ -0,0 +1,87 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef _PRIVMXLIB_ENDPOINT_THREAD_THREADDATASCHEMAMAPPER_HPP_ +#define _PRIVMXLIB_ENDPOINT_THREAD_THREADDATASCHEMAMAPPER_HPP_ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "privmx/endpoint/thread/Constants.hpp" +#include "privmx/endpoint/thread/ServerTypes.hpp" +#include "privmx/endpoint/thread/Types.hpp" +#include "privmx/endpoint/thread/encryptors/thread/ThreadDataSchemaStrategyV4.hpp" +#include "privmx/endpoint/thread/encryptors/thread/ThreadDataSchemaStrategyV5.hpp" + +namespace privmx { +namespace endpoint { +namespace thread { + +class ThreadDataSchemaMapper { +public: + ThreadDataSchemaMapper(const privmx::crypto::PrivateKey& userPrivKey, const core::Connection& connection); + + Poco::Dynamic::Var encrypt(const core::ModuleDataToEncryptV5& data, const std::string& key); + + std::tuple decrypt( + const server::ThreadInfo& thread, + const core::DecryptedEncKey& encKey + ); + + ThreadDataSchema::Version getDataStructureVersion(const server::Thread2DataEntry& entry); + + void assertDataIntegrity(const server::ThreadInfo& thread); + + uint32_t validateDataIntegrity(const server::ThreadInfo& thread); + + std::vector validateDecryptAndConvertThreads( + const std::vector& threads, + const std::shared_ptr& keyProvider + ); + + Thread validateDecryptAndConvertThread( + const server::ThreadInfo& thread, + const std::shared_ptr& keyProvider + ); + + static Thread toLibThread( + const server::ThreadInfo& info, + const core::Buffer& publicMeta, + const core::Buffer& privateMeta, + int64_t statusCode, + int64_t schemaVersion + ); + +private: + privmx::crypto::PrivateKey _userPrivKey; + core::Connection _connection; + core::VersionStrategyMapper> _strategyMapper; + std::shared_ptr _strategyV5; +}; + +} // namespace thread +} // namespace endpoint +} // namespace privmx + +#endif // _PRIVMXLIB_ENDPOINT_THREAD_THREADDATASCHEMAMAPPER_HPP_ diff --git a/endpoint/thread/include/privmx/endpoint/thread/encryptors/thread/ThreadDataSchemaStrategyV4.hpp b/endpoint/thread/include/privmx/endpoint/thread/encryptors/thread/ThreadDataSchemaStrategyV4.hpp new file mode 100644 index 00000000..b23925f1 --- /dev/null +++ b/endpoint/thread/include/privmx/endpoint/thread/encryptors/thread/ThreadDataSchemaStrategyV4.hpp @@ -0,0 +1,59 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef _PRIVMXLIB_ENDPOINT_THREAD_THREADDATASCHEMASTRATEGYV4_HPP_ +#define _PRIVMXLIB_ENDPOINT_THREAD_THREADDATASCHEMASTRATEGYV4_HPP_ + +#include + +#include +#include +#include +#include +#include + +#include "privmx/endpoint/thread/ServerTypes.hpp" +#include "privmx/endpoint/thread/Types.hpp" + +namespace privmx { +namespace endpoint { +namespace thread { + +// clang-format off +class ThreadDataSchemaStrategyV4 : public core::TypedDataSchemaStrategy< + server::ThreadInfo, + core::DecryptedModuleDataV4, + std::tuple +> { + // clang-format on +public: + core::DecryptedModuleDataV4 decrypt( + const server::ThreadInfo& thread, + const core::DecryptedEncKey& encKey + ) const override; + std::tuple convert( + const server::ThreadInfo& thread, + const core::DecryptedModuleDataV4& raw + ) const override; + std::tuple makeErrorResult( + const server::ThreadInfo& thread, + int64_t errorCode + ) const override; + +private: + mutable core::ModuleDataEncryptorV4 _encryptor; +}; + +} // namespace thread +} // namespace endpoint +} // namespace privmx + +#endif // _PRIVMXLIB_ENDPOINT_THREAD_THREADDATASCHEMASTRATEGYV4_HPP_ diff --git a/endpoint/thread/include/privmx/endpoint/thread/encryptors/thread/ThreadDataSchemaStrategyV5.hpp b/endpoint/thread/include/privmx/endpoint/thread/encryptors/thread/ThreadDataSchemaStrategyV5.hpp new file mode 100644 index 00000000..322b510c --- /dev/null +++ b/endpoint/thread/include/privmx/endpoint/thread/encryptors/thread/ThreadDataSchemaStrategyV5.hpp @@ -0,0 +1,66 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef _PRIVMXLIB_ENDPOINT_THREAD_THREADDATASCHEMASTRATEGYV5_HPP_ +#define _PRIVMXLIB_ENDPOINT_THREAD_THREADDATASCHEMASTRATEGYV5_HPP_ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "privmx/endpoint/thread/ServerTypes.hpp" +#include "privmx/endpoint/thread/Types.hpp" + +namespace privmx { +namespace endpoint { +namespace thread { +// clang-format off +class ThreadDataSchemaStrategyV5 : public core::TypedDataSchemaStrategy< + server::ThreadInfo, + core::DecryptedModuleDataV5, + std::tuple +> { + // clang-format on +public: + core::dynamic::EncryptedModuleDataV5 encrypt( + const core::ModuleDataToEncryptV5& data, + const privmx::crypto::PrivateKey& userPrivKey, + const std::string& key + ) const; + core::DecryptedModuleDataV5 decrypt( + const server::ThreadInfo& thread, + const core::DecryptedEncKey& encKey + ) const override; + std::tuple convert( + const server::ThreadInfo& thread, + const core::DecryptedModuleDataV5& raw + ) const override; + std::tuple makeErrorResult( + const server::ThreadInfo& thread, + int64_t errorCode + ) const override; + core::DataIntegrityObject getDIOAndAssertIntegrity(const core::dynamic::EncryptedModuleDataV5& encData) const; + +private: + mutable core::ModuleDataEncryptorV5 _encryptor; +}; + +} // namespace thread +} // namespace endpoint +} // namespace privmx + +#endif // _PRIVMXLIB_ENDPOINT_THREAD_THREADDATASCHEMASTRATEGYV5_HPP_ diff --git a/endpoint/thread/src/ThreadApiImpl.cpp b/endpoint/thread/src/ThreadApiImpl.cpp index 8461c1e9..08049790 100644 --- a/endpoint/thread/src/ThreadApiImpl.cpp +++ b/endpoint/thread/src/ThreadApiImpl.cpp @@ -27,7 +27,6 @@ limitations under the License. #include "privmx/endpoint/core/ListQueryMapper.hpp" #include "privmx/endpoint/core/Mapper.hpp" #include "privmx/endpoint/core/UsersKeysResolver.hpp" -#include "privmx/endpoint/thread/DynamicTypes.hpp" #include "privmx/endpoint/thread/Mapper.hpp" #include "privmx/endpoint/thread/ServerTypes.hpp" #include "privmx/endpoint/thread/ThreadApiImpl.hpp" @@ -47,10 +46,8 @@ ThreadApiImpl::ThreadApiImpl( ) : ModuleBaseApi(userPrivKey, keyProvider, host, eventMiddleware, connection), _gateway(gateway), _userPrivKey(userPrivKey), _keyProvider(keyProvider), _host(host), _eventMiddleware(eventMiddleware), - _connection(connection), _serverApi(ServerApi(gateway)), - _dataEncryptorThread(core::DataEncryptor()), - _messageDataV2Encryptor(MessageDataV2Encryptor()), _messageDataV3Encryptor(MessageDataV3Encryptor()), - _messageKeyIdFormatValidator(MessageKeyIdFormatValidator()), _subscriber(gateway, THREAD_TYPE_FILTER_FLAG), + _connection(connection), _serverApi(ServerApi(gateway)), _subscriber(gateway, THREAD_TYPE_FILTER_FLAG), + _messageDataSchemaMapper(userPrivKey, connection), _threadDataSchemaMapper(userPrivKey, connection), _forbiddenChannelsNames({INTERNAL_EVENT_CHANNEL_NAME, "thread", "messages"}) { _notificationListenerId = _eventMiddleware->addNotificationEventListener( std::bind(&ThreadApiImpl::processNotificationEvent, this, std::placeholders::_1, std::placeholders::_2) @@ -121,8 +118,7 @@ std::string ThreadApiImpl::_createThreadEx( create_thread_model.resourceId = resourceId; create_thread_model.contextId = contextId; create_thread_model.keyId = threadKey.id; - create_thread_model.data = _threadDataEncryptorV5.encrypt(threadDataToEncrypt, _userPrivKey, threadKey.key) - .toJSON(); + create_thread_model.data = _threadDataSchemaMapper.encrypt(threadDataToEncrypt, threadKey.key); create_thread_model.keys = _keyProvider->prepareKeysList( allUsers, threadKey, threadDIO, {.contextId = contextId, .resourceId = resourceId}, threadSecret ); @@ -218,7 +214,7 @@ void ThreadApiImpl::updateThread( }, .dio = updateThreadDio }; - model.data = _threadDataEncryptorV5.encrypt(threadDataToEncrypt, _userPrivKey, threadKey.key).toJSON(); + model.data = _threadDataSchemaMapper.encrypt(threadDataToEncrypt, threadKey.key); PRIVMX_DEBUG_TIME_CHECKPOINT(PlatformThread, updateThread, data encrypted) _serverApi.threadUpdate(model); @@ -253,7 +249,7 @@ Thread ThreadApiImpl::_getThreadEx(const std::string& threadId, const std::strin // Add to cache setNewModuleKeysInCache(thread.id, threadToModuleKeys(thread), thread.version); // decrypt - auto result = validateDecryptAndConvertThreadDataToThread(thread); + auto result = _threadDataSchemaMapper.validateDecryptAndConvertThread(thread, _keyProvider); PRIVMX_DEBUG_TIME_CHECKPOINT(PlatformThread, _getThreadEx, data decrypted) return result; } @@ -291,10 +287,13 @@ core::PagingList ThreadApiImpl::_listThreadsEx( for (const auto& thread : threadsList.threads) { setNewModuleKeysInCache(thread.id, threadToModuleKeys(thread), thread.version); } - std::vector threads = validateDecryptAndConvertThreadsDataToThreads(threadsList.threads); + std::vector threads = _threadDataSchemaMapper.validateDecryptAndConvertThreads( + threadsList.threads, _keyProvider + ); PRIVMX_DEBUG_TIME_STOP(PlatformThread, _listThreadsEx, data decrypted) return core::PagingList({.totalAvailable = threadsList.count, .readItems = threads}); } + Message ThreadApiImpl::getMessage(const std::string& messageId) { PRIVMX_DEBUG_TIME_START(PlatformThread, getMessage) server::ThreadMessageGetModel model{.messageId = messageId}; @@ -303,7 +302,9 @@ Message ThreadApiImpl::getMessage(const std::string& messageId) { PRIVMX_DEBUG_TIME_CHECKPOINT(PlatformThread, getMessage, data recived); Message result; PRIVMX_DEBUG_TIME_CHECKPOINT(PlatformThread, getMessage, decrypting message) - result = validateDecryptAndConvertMessageDataToMessage(message, getMessageDecryptionKeys(message)); + result = _messageDataSchemaMapper.validateDecryptAndConvertMessage( + message, getMessageDecryptionKeys(message), _keyProvider + ); PRIVMX_DEBUG_TIME_STOP(PlatformThread, getMessage, data decrypted) return result; } @@ -320,10 +321,12 @@ core::PagingList ThreadApiImpl::listMessages( auto messagesList = _serverApi.threadMessagesGet(model); PRIVMX_DEBUG_TIME_CHECKPOINT(PlatformThread, listMessages, getting thread) const auto& thread = messagesList.thread; - assertThreadDataIntegrity(thread); + _threadDataSchemaMapper.assertDataIntegrity(thread); setNewModuleKeysInCache(thread.id, threadToModuleKeys(thread), thread.version); PRIVMX_DEBUG_TIME_CHECKPOINT(PlatformThread, listMessages, data send) - auto messages = validateDecryptAndConvertMessagesDataToMessages(messagesList.messages, threadToModuleKeys(thread)); + auto messages = _messageDataSchemaMapper.validateDecryptAndConvertMessages( + messagesList.messages, threadToModuleKeys(thread), _keyProvider + ); PRIVMX_DEBUG_TIME_STOP(PlatformThread, listMessages, data decrypted) return core::PagingList({.totalAvailable = messagesList.count, .readItems = messages}); } @@ -441,7 +444,7 @@ void ThreadApiImpl::processNotificationEvent(const std::string& type, const core auto raw = server::ThreadInfo::fromJSON(notification.data); if (raw.type.value_or(std::string(THREAD_TYPE_FILTER_FLAG)) == THREAD_TYPE_FILTER_FLAG) { setNewModuleKeysInCache(raw.id, threadToModuleKeys(raw), raw.version); - auto data = validateDecryptAndConvertThreadDataToThread(raw); + auto data = _threadDataSchemaMapper.validateDecryptAndConvertThread(raw, _keyProvider); auto event = core::EventBuilder::buildEvent("thread", data, notification); _eventMiddleware->emitApiEvent(event); } @@ -449,7 +452,7 @@ void ThreadApiImpl::processNotificationEvent(const std::string& type, const core auto raw = server::ThreadInfo::fromJSON(notification.data); if (raw.type.value_or(std::string(THREAD_TYPE_FILTER_FLAG)) == THREAD_TYPE_FILTER_FLAG) { setNewModuleKeysInCache(raw.id, threadToModuleKeys(raw), raw.version); - auto data = validateDecryptAndConvertThreadDataToThread(raw); + auto data = _threadDataSchemaMapper.validateDecryptAndConvertThread(raw, _keyProvider); auto event = core::EventBuilder::buildEvent("thread", data, notification); _eventMiddleware->emitApiEvent(event); } @@ -471,7 +474,9 @@ void ThreadApiImpl::processNotificationEvent(const std::string& type, const core } else if (type == "threadNewMessage") { auto raw = server::ThreadMessageEventData::fromJSON(notification.data); if (raw.containerType.value_or(std::string(THREAD_TYPE_FILTER_FLAG)) == THREAD_TYPE_FILTER_FLAG) { - auto data = validateDecryptAndConvertMessageDataToMessage(raw, getMessageDecryptionKeys(raw)); + auto data = _messageDataSchemaMapper.validateDecryptAndConvertMessage( + raw, getMessageDecryptionKeys(raw), _keyProvider + ); auto event = core::EventBuilder::buildEvent( "thread/" + raw.threadId + "/messages", data, notification ); @@ -480,7 +485,9 @@ void ThreadApiImpl::processNotificationEvent(const std::string& type, const core } else if (type == "threadUpdatedMessage") { auto raw = server::ThreadMessageEventData::fromJSON(notification.data); if (raw.containerType.value_or(std::string(THREAD_TYPE_FILTER_FLAG)) == THREAD_TYPE_FILTER_FLAG) { - auto data = validateDecryptAndConvertMessageDataToMessage(raw, getMessageDecryptionKeys(raw)); + auto data = _messageDataSchemaMapper.validateDecryptAndConvertMessage( + raw, getMessageDecryptionKeys(raw), _keyProvider + ); auto event = core::EventBuilder::buildEvent( "thread/" + raw.threadId + "/messages", data, notification ); @@ -528,701 +535,11 @@ std::vector ThreadApiImpl::mapUsers(const std::vectorset("title", threadData.title); - return convertServerThreadToLibThread( - threadInfo, core::Buffer::from(""), core::Buffer::from(utils::Utils::stringify(privateMeta)), - threadData.statusCode, ThreadDataSchema::Version::VERSION_1 - ); -} - -Thread ThreadApiImpl::convertDecryptedThreadDataV4ToThread( - server::ThreadInfo threadInfo, - const core::DecryptedModuleDataV4& threadData -) { - return convertServerThreadToLibThread( - threadInfo, threadData.publicMeta, threadData.privateMeta, threadData.statusCode, - ThreadDataSchema::Version::VERSION_4 - ); -} - -Thread ThreadApiImpl::convertDecryptedThreadDataV5ToThread( - server::ThreadInfo threadInfo, - const core::DecryptedModuleDataV5& threadData -) { - return convertServerThreadToLibThread( - threadInfo, threadData.publicMeta, threadData.privateMeta, threadData.statusCode, - ThreadDataSchema::Version::VERSION_5 - ); -} - -ThreadDataSchema::Version ThreadApiImpl::getThreadEntryDataStructureVersion(server::Thread2DataEntry threadEntry) { - if (threadEntry.data.type() == typeid(Poco::JSON::Object::Ptr)) { - auto versioned = core::dynamic::VersionedData::fromJSON(threadEntry.data); - switch (versioned.version) { - case core::ModuleDataSchema::Version::VERSION_4: - return ThreadDataSchema::Version::VERSION_4; - case core::ModuleDataSchema::Version::VERSION_5: - return ThreadDataSchema::Version::VERSION_5; - default: - return ThreadDataSchema::Version::UNKNOWN; - } - } else if (threadEntry.data.isString()) { - return ThreadDataSchema::Version::VERSION_1; - } - return ThreadDataSchema::Version::UNKNOWN; -} - -std::tuple ThreadApiImpl::decryptAndConvertThreadDataToThread( - server::ThreadInfo thread, - server::Thread2DataEntry threadEntry, - const core::DecryptedEncKey& encKey -) { - switch (getThreadEntryDataStructureVersion(threadEntry)) { - case ThreadDataSchema::Version::UNKNOWN: { - auto e = UnknowThreadFormatException(); - return std::make_tuple( - convertServerThreadToLibThread(thread, {}, {}, e.getCode()), core::DataIntegrityObject() - ); - } - case ThreadDataSchema::Version::VERSION_1: { - return std::make_tuple( - convertThreadDataV1ToThread(thread, decryptThreadV1(threadEntry, encKey)), - core::DataIntegrityObject{ - .creatorUserId = thread.lastModifier, - .creatorPubKey = "", - .contextId = thread.contextId, - .resourceId = thread.resourceId.value_or(""), - .timestamp = thread.lastModificationDate, - .randomId = std::string(), - .containerId = std::nullopt, - .containerResourceId = std::nullopt, - .bridgeIdentity = std::nullopt - } - ); - } - case ThreadDataSchema::Version::VERSION_4: { - auto decryptedThreadData = decryptModuleDataV4(threadEntry, encKey); - return std::make_tuple( - convertDecryptedThreadDataV4ToThread(thread, decryptedThreadData), - core::DataIntegrityObject{ - .creatorUserId = thread.lastModifier, - .creatorPubKey = decryptedThreadData.authorPubKey, - .contextId = thread.contextId, - .resourceId = thread.resourceId.value_or(""), - .timestamp = thread.lastModificationDate, - .randomId = std::string(), - .containerId = std::nullopt, - .containerResourceId = std::nullopt, - .bridgeIdentity = std::nullopt - } - ); - } - case ThreadDataSchema::Version::VERSION_5: { - auto decryptedThreadData = decryptModuleDataV5(threadEntry, encKey); - return std::make_tuple( - convertDecryptedThreadDataV5ToThread(thread, decryptedThreadData), decryptedThreadData.dio - ); - } - } - auto e = UnknowThreadFormatException(); - return std::make_tuple(convertServerThreadToLibThread(thread, {}, {}, e.getCode()), core::DataIntegrityObject()); -} - -std::vector ThreadApiImpl::validateDecryptAndConvertThreadsDataToThreads( - std::vector threads -) { - // Create Result Array - std::vector result(threads.size()); - // Validate data Integrity - for (size_t i = 0; i < threads.size(); i++) { - const auto& thread = threads[i]; - result[i].statusCode = validateThreadDataIntegrity(thread); - if (result[i].statusCode != 0) { - result[i] = convertServerThreadToLibThread(thread, {}, {}, result[i].statusCode); - } - } - core::KeyDecryptionAndVerificationRequest keyProviderRequest; - // Create request to KeyProvider for keys - for (size_t i = 0; i < threads.size(); i++) { - const auto& thread = threads[i]; - core::EncKeyLocation location{.contextId = thread.contextId, .resourceId = thread.resourceId.value_or("")}; - const auto& thread_data_entry = thread.data.back(); - keyProviderRequest.addOne(thread.keys, thread_data_entry.keyId, location); - } - // Send request to KeyProvider - auto threadsKeys{_keyProvider->getKeysAndVerify(keyProviderRequest)}; - std::vector threadsDIO(threads.size()); - std::map duplication_check; - for (size_t i = 0; i < threads.size(); i++) { - if (result[i].statusCode != 0) { - threadsDIO.push_back(core::DataIntegrityObject{}); - } else { - const auto& thread = threads[i]; - try { - core::EncKeyLocation location{ - .contextId = thread.contextId, .resourceId = thread.resourceId.value_or("") - }; - auto tmp = decryptAndConvertThreadDataToThread( - thread, thread.data.back(), threadsKeys.at(location).at(thread.data.back().keyId) - ); - result[i] = std::get<0>(tmp); - auto threadDIO = std::get<1>(tmp); - threadsDIO[i] = threadDIO; - //find duplication - std::string fullRandomId = threadDIO.randomId + "-" + std::to_string(threadDIO.timestamp); - if (duplication_check.find(fullRandomId) == duplication_check.end()) { - duplication_check.insert(std::make_pair(fullRandomId, true)); - } else { - result[i].statusCode = core::DataIntegrityObjectDuplicatedException().getCode(); - } - } catch (const core::Exception& e) { - result[i] = convertServerThreadToLibThread(thread, {}, {}, e.getCode()); - threadsDIO[i] = core::DataIntegrityObject{}; - } - } - } - std::vector verifierInput{}; - for (size_t i = 0; i < result.size(); i++) { - if (result[i].statusCode == 0) { - verifierInput.push_back( - core::VerificationRequest{ - .contextId = result[i].contextId, - .senderId = result[i].lastModifier, - .senderPubKey = threadsDIO[i].creatorPubKey, - .date = result[i].lastModificationDate, - .bridgeIdentity = threadsDIO[i].bridgeIdentity - } - ); - } - } - std::vector verified; - verified = _connection.getImpl()->getUserVerifier()->verify(verifierInput); - for (size_t j = 0, i = 0; i < result.size(); i++) { - if (result[i].statusCode == 0) { - result[i].statusCode = verified[j] ? 0 : - core::ExceptionConverter::getCodeOfUserVerificationFailureException(); - j++; - } - } - return result; -} - -Thread ThreadApiImpl::validateDecryptAndConvertThreadDataToThread(server::ThreadInfo thread) { - // Validate data Integrity - auto statusCode = validateThreadDataIntegrity(thread); - if (statusCode != 0) { - return convertServerThreadToLibThread(thread, {}, {}, statusCode); - } - // Get current ThreadEntry and Key - const auto& thread_data_entry = thread.data.back(); - // Create request to KeyProvider for keys - core::KeyDecryptionAndVerificationRequest keyProviderRequest; - core::EncKeyLocation location{.contextId = thread.contextId, .resourceId = thread.resourceId.value_or("")}; - keyProviderRequest.addOne(thread.keys, thread_data_entry.keyId, location); - //Send request to KeyProvider - auto key = _keyProvider->getKeysAndVerify(keyProviderRequest).at(location).at(thread_data_entry.keyId); - Thread result; - core::DataIntegrityObject threadDIO; - // Decrypt - std::tie(result, threadDIO) = decryptAndConvertThreadDataToThread(thread, thread_data_entry, key); - // Validate with UserVerifier - if (result.statusCode != 0) - return result; - std::vector verifierInput{}; - verifierInput.push_back( - core::VerificationRequest{ - .contextId = result.contextId, - .senderId = result.lastModifier, - .senderPubKey = threadDIO.creatorPubKey, - .date = result.lastModificationDate, - .bridgeIdentity = threadDIO.bridgeIdentity - } - ); - std::vector verified; - verified = _connection.getImpl()->getUserVerifier()->verify(verifierInput); - result.statusCode = verified[0] ? 0 : core::ExceptionConverter::getCodeOfUserVerificationFailureException(); - return result; -} - -dynamic::MessageDataV2 ThreadApiImpl::decryptMessageDataV2( - server::Message message, - const core::DecryptedEncKey& encKey -) { - try { - auto msg = _messageDataV2Encryptor.decryptAndGetSign(message.data, encKey); - return msg.data; - } catch (const core::Exception& e) { - dynamic::MessageDataV2 result; - result.v = 0; - result.statusCode = e.getCode(); - return result; - } catch (const privmx::utils::PrivmxException& e) { - dynamic::MessageDataV2 result; - result.v = 0; - result.statusCode = core::ExceptionConverter::convert(e).getCode(); - return result; - } catch (...) { - dynamic::MessageDataV2 result; - result.v = 0; - result.statusCode = ENDPOINT_CORE_EXCEPTION_CODE; - return result; - } -} - -dynamic::MessageDataV3 ThreadApiImpl::decryptMessageDataV3( - server::Message message, - const core::DecryptedEncKey& encKey -) { - try { - auto msg = _messageDataV3Encryptor.decryptAndGetSign(message.data, encKey); - return msg.data; - } catch (const core::Exception& e) { - dynamic::MessageDataV3 result; - result.v = 0; - result.statusCode = e.getCode(); - return result; - } catch (const privmx::utils::PrivmxException& e) { - dynamic::MessageDataV3 result; - result.v = 0; - result.statusCode = core::ExceptionConverter::convert(e).getCode(); - ; - return result; - } catch (...) { - dynamic::MessageDataV3 result; - result.v = 0; - result.statusCode = ENDPOINT_CORE_EXCEPTION_CODE; - return result; - } -} - -DecryptedMessageDataV4 ThreadApiImpl::decryptMessageDataV4( - server::Message message, - const core::DecryptedEncKey& encKey -) { - try { - auto encryptedMessageData = server::EncryptedMessageDataV4::fromJSON(message.data); - return _messageDataEncryptorV4.decrypt(encryptedMessageData, encKey.key); - } catch (const core::Exception& e) { - return DecryptedMessageDataV4{ - {.dataStructureVersion = MessageDataSchema::Version::VERSION_4, .statusCode = e.getCode()}, - {}, - {}, - {}, - {}, - {} - }; - } catch (const privmx::utils::PrivmxException& e) { - return DecryptedMessageDataV4{ - {.dataStructureVersion = MessageDataSchema::Version::VERSION_4, - .statusCode = core::ExceptionConverter::convert(e).getCode()}, - {}, - {}, - {}, - {}, - {} - }; - } catch (...) { - return DecryptedMessageDataV4{ - {.dataStructureVersion = MessageDataSchema::Version::VERSION_4, .statusCode = ENDPOINT_CORE_EXCEPTION_CODE}, - {}, - {}, - {}, - {}, - {} - }; - } -} - -DecryptedMessageDataV5 ThreadApiImpl::decryptMessageDataV5( - server::Message message, - const core::DecryptedEncKey& encKey -) { - try { - auto encryptedMessageData = server::EncryptedMessageDataV5::fromJSON(message.data); - if (encKey.statusCode != 0) { - auto tmp = _messageDataEncryptorV5.extractPublic(encryptedMessageData); - tmp.statusCode = encKey.statusCode; - return tmp; - } - return _messageDataEncryptorV5.decrypt(encryptedMessageData, encKey.key); - } catch (const core::Exception& e) { - return DecryptedMessageDataV5{ - {.dataStructureVersion = MessageDataSchema::Version::VERSION_5, .statusCode = e.getCode()}, - {}, - {}, - {}, - {}, - {}, - {} - }; - } catch (const privmx::utils::PrivmxException& e) { - return DecryptedMessageDataV5{ - {.dataStructureVersion = MessageDataSchema::Version::VERSION_5, - .statusCode = core::ExceptionConverter::convert(e).getCode()}, - {}, - {}, - {}, - {}, - {}, - {} - }; - } catch (...) { - return DecryptedMessageDataV5{ - {.dataStructureVersion = MessageDataSchema::Version::VERSION_5, .statusCode = ENDPOINT_CORE_EXCEPTION_CODE}, - {}, - {}, - {}, - {}, - {}, - {} - }; - } -} - -Message ThreadApiImpl::convertServerMessageToLibMessage( - server::Message message, - const core::Buffer& publicMeta, - const core::Buffer& privateMeta, - const core::Buffer& data, - const std::string& authorPubKey, - const int64_t& statusCode, - const int64_t& schemaVersion -) { - return Message{ - .info = - { - .threadId = message.threadId, - .messageId = message.id, - .createDate = message.createDate, - .author = message.author, - }, - .publicMeta = publicMeta, - .privateMeta = privateMeta, - .data = data, - .authorPubKey = authorPubKey, - .statusCode = statusCode, - .schemaVersion = schemaVersion - }; -} - -Message ThreadApiImpl::convertMessageDataV2ToMessage(server::Message message, dynamic::MessageDataV2 messageData) { - //TO REMOVE - return convertServerMessageToLibMessage( - message, core::Buffer(), core::Buffer(), core::Buffer(), messageData.author.pubKey, messageData.statusCode, - MessageDataSchema::Version::VERSION_2 - ); -} - -Message ThreadApiImpl::convertMessageDataV3ToMessage(server::Message message, dynamic::MessageDataV3 messageData) { - return convertServerMessageToLibMessage( - message, core::Buffer::from(messageData.publicMeta), core::Buffer::from(messageData.privateMeta), - core::Buffer::from(messageData.data), std::string(), messageData.statusCode, - MessageDataSchema::Version::VERSION_3 - ); -} - -Message ThreadApiImpl::convertDecryptedMessageDataV4ToMessage( - server::Message message, - DecryptedMessageDataV4 messageData -) { - return convertServerMessageToLibMessage( - message, messageData.publicMeta, messageData.privateMeta, messageData.data, messageData.authorPubKey, - messageData.statusCode, MessageDataSchema::Version::VERSION_4 - ); -} - -Message ThreadApiImpl::convertDecryptedMessageDataV5ToMessage( - server::Message message, - DecryptedMessageDataV5 messageData -) { - return convertServerMessageToLibMessage( - message, messageData.publicMeta, messageData.privateMeta, messageData.data, messageData.authorPubKey, - messageData.statusCode, MessageDataSchema::Version::VERSION_5 - ); -} - -MessageDataSchema::Version ThreadApiImpl::getMessagesDataStructureVersion(server::Message message) { - // If data is not string, then data is object and has version field - // Solution with data as object is newer than data as base64 string - if (message.data.type() == typeid(Poco::JSON::Object::Ptr)) { - auto versioned = core::dynamic::VersionedData::fromJSON(message.data); - switch (versioned.version) { - case MessageDataSchema::Version::VERSION_4: - return MessageDataSchema::Version::VERSION_4; - case MessageDataSchema::Version::VERSION_5: - return MessageDataSchema::Version::VERSION_5; - default: - return MessageDataSchema::Version::UNKNOWN; - } - } else if (message.data.isString()) { - // Temporary Solution need better way to dif V3 from V2 - if (core::DataEncryptorUtil::hasSign(utils::Base64::toString(message.data))) { - return MessageDataSchema::Version::VERSION_3; - } else { - return MessageDataSchema::Version::VERSION_2; - } - } - return MessageDataSchema::Version::UNKNOWN; -} - -std::tuple ThreadApiImpl::decryptAndConvertMessageDataToMessage( - server::Message message, - const core::DecryptedEncKey& encKey -) { - switch (getMessagesDataStructureVersion(message)) { - case MessageDataSchema::Version::UNKNOWN: { - auto e = UnknowMessageFormatException(); - return std::make_tuple( - convertServerMessageToLibMessage(message, {}, {}, {}, {}, e.getCode()), core::DataIntegrityObject() - ); - } - case MessageDataSchema::Version::VERSION_2: { - return std::make_tuple( - convertMessageDataV2ToMessage(message, decryptMessageDataV2(message, encKey)), - core::DataIntegrityObject{ - .creatorUserId = message.updates.empty() ? message.author : message.updates.back().author, - .creatorPubKey = "", - .contextId = message.contextId, - .resourceId = message.resourceId, - .timestamp = message.updates.empty() ? message.createDate : message.updates.back().createDate, - .randomId = std::string(), - .containerId = message.threadId, - .containerResourceId = std::string(), - .bridgeIdentity = std::nullopt - } - ); - } - case MessageDataSchema::Version::VERSION_3: { - return std::make_tuple( - convertMessageDataV3ToMessage(message, decryptMessageDataV3(message, encKey)), - core::DataIntegrityObject{ - .creatorUserId = message.updates.empty() ? message.author : message.updates.back().author, - .creatorPubKey = std::string(), - .contextId = message.contextId, - .resourceId = message.resourceId, - .timestamp = message.updates.empty() ? message.createDate : message.updates.back().createDate, - .randomId = std::string(), - .containerId = message.threadId, - .containerResourceId = std::string(), - .bridgeIdentity = std::nullopt - } - ); - } - case MessageDataSchema::Version::VERSION_4: { - auto decryptedMessage = decryptMessageDataV4(message, encKey); - return std::make_tuple( - convertDecryptedMessageDataV4ToMessage(message, decryptedMessage), - core::DataIntegrityObject{ - .creatorUserId = message.updates.empty() ? message.author : message.updates.back().author, - .creatorPubKey = decryptedMessage.authorPubKey, - .contextId = message.contextId, - .resourceId = message.resourceId, - .timestamp = message.updates.empty() ? message.createDate : message.updates.back().createDate, - .randomId = std::string(), - .containerId = message.threadId, - .containerResourceId = std::string(), - .bridgeIdentity = std::nullopt - } - ); - } - case MessageDataSchema::Version::VERSION_5: { - auto decryptedMessage = decryptMessageDataV5(message, encKey); - return std::make_tuple(convertDecryptedMessageDataV5ToMessage(message, decryptedMessage), decryptedMessage.dio); - } - } - auto e = UnknowMessageFormatException(); - return std::make_tuple( - convertServerMessageToLibMessage(message, {}, {}, {}, {}, e.getCode()), core::DataIntegrityObject() - ); -} - -std::vector ThreadApiImpl::validateDecryptAndConvertMessagesDataToMessages( - std::vector messages, - const core::ModuleKeys& threadKeys -) { - std::set keyIds; - for (const auto& message : messages) { - keyIds.insert(message.keyId); - } - core::KeyDecryptionAndVerificationRequest keyProviderRequest; - core::EncKeyLocation location{.contextId = threadKeys.contextId, .resourceId = threadKeys.moduleResourceId}; - keyProviderRequest.addMany(threadKeys.keys, keyIds, location); - auto keyMap = _keyProvider->getKeysAndVerify(keyProviderRequest).at(location); - std::vector result; - std::vector messagesDIO; - std::map duplication_check; - for (const auto& message : messages) { - try { - auto statusCode = validateMessageDataIntegrity(message, threadKeys.moduleResourceId); - if (statusCode == 0) { - auto tmp = decryptAndConvertMessageDataToMessage(message, keyMap.at(message.keyId)); - result.push_back(std::get<0>(tmp)); - auto messageDIO = std::get<1>(tmp); - messagesDIO.push_back(messageDIO); - //find duplication - std::string fullRandomId = messageDIO.randomId + "-" + std::to_string(messageDIO.timestamp); - if (duplication_check.find(fullRandomId) == duplication_check.end()) { - duplication_check.insert(std::make_pair(fullRandomId, true)); - } else { - result[result.size() - 1].statusCode = core::DataIntegrityObjectDuplicatedException().getCode(); - } - } else { - result.push_back(convertServerMessageToLibMessage(message, {}, {}, {}, {}, statusCode)); - } - } catch (const core::Exception& e) { - result.push_back(convertServerMessageToLibMessage(message, {}, {}, {}, {}, e.getCode())); - } catch (const privmx::utils::PrivmxException& e) { - result.push_back(convertServerMessageToLibMessage( - message, {}, {}, {}, {}, core::ExceptionConverter::convert(e).getCode() - )); - } catch (...) { - result.push_back(convertServerMessageToLibMessage(message, {}, {}, {}, {}, ENDPOINT_CORE_EXCEPTION_CODE)); - } - } - std::vector verifierInput{}; - for (size_t i = 0; i < result.size(); i++) { - if (result[i].statusCode == 0) { - verifierInput.push_back( - core::VerificationRequest{ - .contextId = threadKeys.contextId, - .senderId = result[i].info.author, - .senderPubKey = result[i].authorPubKey, - .date = result[i].info.createDate, - .bridgeIdentity = messagesDIO[i].bridgeIdentity - } - ); - } - } - std::vector verified; - verified = _connection.getImpl()->getUserVerifier()->verify(verifierInput); - for (size_t j = 0, i = 0; i < result.size(); ++i) { - if (result[i].statusCode == 0) { - result[i].statusCode = verified[j] ? 0 : - core::ExceptionConverter::getCodeOfUserVerificationFailureException(); - j++; - } - } - return result; -} - -Message ThreadApiImpl::validateDecryptAndConvertMessageDataToMessage( - server::Message message, - const core::ModuleKeys& threadKeys -) { - try { - auto keyId = message.keyId; - // Validate data Integrity - auto statusCode = validateMessageDataIntegrity(message, threadKeys.moduleResourceId); - if (statusCode != 0) { - return convertServerMessageToLibMessage(message, {}, {}, {}, {}, statusCode); - } - _messageKeyIdFormatValidator.assertKeyIdFormat(keyId); - // Create request to KeyProvider for keys - core::KeyDecryptionAndVerificationRequest keyProviderRequest; - core::EncKeyLocation location{.contextId = message.contextId, .resourceId = threadKeys.moduleResourceId}; - keyProviderRequest.addOne(threadKeys.keys, keyId, location); - // Send request to KeyProvider - auto encKey = _keyProvider->getKeysAndVerify(keyProviderRequest).at(location).at(keyId); - // decrypt message - Message result; - core::DataIntegrityObject messageDIO; - std::tie(result, messageDIO) = decryptAndConvertMessageDataToMessage(message, encKey); - if (result.statusCode != 0) - return result; - // Validate with UserVerifier - std::vector verifierInput{}; - verifierInput.push_back( - core::VerificationRequest{ - .contextId = message.contextId, - .senderId = result.info.author, - .senderPubKey = result.authorPubKey, - .date = result.info.createDate, - .bridgeIdentity = messageDIO.bridgeIdentity - } - ); - std::vector verified; - verified = _connection.getImpl()->getUserVerifier()->verify(verifierInput); - result.statusCode = verified[0] ? 0 : core::ExceptionConverter::getCodeOfUserVerificationFailureException(); - return result; - } catch (const core::Exception& e) { - return convertServerMessageToLibMessage(message, {}, {}, {}, {}, e.getCode()); - } catch (const privmx::utils::PrivmxException& e) { - return convertServerMessageToLibMessage( - message, {}, {}, {}, {}, core::ExceptionConverter::convert(e).getCode() - ); - } catch (...) { return convertServerMessageToLibMessage(message, {}, {}, {}, {}, ENDPOINT_CORE_EXCEPTION_CODE); } -} - core::ModuleKeys ThreadApiImpl::getMessageDecryptionKeys(server::Message message) { - auto keyId = message.keyId; - thread::ThreadDataSchema::Version minimumThreadSchemaVersion; - switch (getMessagesDataStructureVersion(message)) { - case thread::MessageDataSchema::Version::UNKNOWN: - minimumThreadSchemaVersion = thread::ThreadDataSchema::UNKNOWN; - break; - case thread::MessageDataSchema::Version::VERSION_2: - case thread::MessageDataSchema::Version::VERSION_3: - case thread::MessageDataSchema::Version::VERSION_4: - minimumThreadSchemaVersion = thread::ThreadDataSchema::VERSION_1; - break; - case thread::MessageDataSchema::Version::VERSION_5: - minimumThreadSchemaVersion = thread::ThreadDataSchema::VERSION_5; - break; - } - return getModuleKeys(message.threadId, std::set{keyId}, minimumThreadSchemaVersion); + return getModuleKeys( + message.threadId, std::set{message.keyId}, + _messageDataSchemaMapper.getMinimumContainerSchemaVersionForMessage(message) + ); } Poco::Dynamic::Var ThreadApiImpl::encryptMessageData( @@ -1234,32 +551,9 @@ Poco::Dynamic::Var ThreadApiImpl::encryptMessageData( const core::ModuleKeys& threadKeys ) { core::DecryptedEncKeyV2 msgKey = getAndValidateModuleCurrentEncKey(threadKeys); - switch (msgKey.dataStructureVersion) { - case core::EncryptionKeyDataSchema::Version::UNKNOWN: - throw UnknowThreadFormatException(); - case core::EncryptionKeyDataSchema::Version::VERSION_1: { - MessageDataToEncryptV4 messageData{ - .publicMeta = publicMeta, .privateMeta = privateMeta, .data = data, .internalMeta = std::nullopt - }; - auto encryptedMessageData = _messageDataEncryptorV4.encrypt(messageData, _userPrivKey, msgKey.key); - return encryptedMessageData.toJSON(); - } - case core::EncryptionKeyDataSchema::Version::VERSION_2: { - auto messageDIO = _connection.getImpl()->createDIO( - threadKeys.contextId, resourceId, threadId, threadKeys.moduleResourceId - ); - MessageDataToEncryptV5 messageData{ - .publicMeta = publicMeta, - .privateMeta = privateMeta, - .data = data, - .internalMeta = std::nullopt, - .dio = messageDIO - }; - auto encryptedMessageData = _messageDataEncryptorV5.encrypt(messageData, _userPrivKey, msgKey.key); - return encryptedMessageData.toJSON(); - } - } - throw UnknowThreadFormatException(); + return _messageDataSchemaMapper.encrypt( + threadId, resourceId, threadKeys.contextId, threadKeys.moduleResourceId, publicMeta, privateMeta, data, msgKey + ); } void ThreadApiImpl::assertThreadExist(const std::string& threadId) { @@ -1272,7 +566,7 @@ std::pair ThreadApiImpl::getModuleKeysAndVersionFromS thread::server::ThreadGetModel params{.threadId = moduleId, .type = std::nullopt}; auto thread = _serverApi.threadGet(params).thread; // validate thread Data before returning data - assertThreadDataIntegrity(thread); + _threadDataSchemaMapper.assertDataIntegrity(thread); return std::make_pair(threadToModuleKeys(thread), thread.version); } @@ -1280,81 +574,12 @@ core::ModuleKeys ThreadApiImpl::threadToModuleKeys(server::ThreadInfo thread) { return core::ModuleKeys{ .keys = thread.keys, .currentKeyId = thread.keyId, - .moduleSchemaVersion = getThreadEntryDataStructureVersion(thread.data.back()), + .moduleSchemaVersion = _threadDataSchemaMapper.getDataStructureVersion(thread.data.back()), .moduleResourceId = thread.resourceId.value_or(""), .contextId = thread.contextId }; } -void ThreadApiImpl::assertThreadDataIntegrity(server::ThreadInfo thread) { - const auto& thread_data_entry = thread.data.back(); - switch (getThreadEntryDataStructureVersion(thread_data_entry)) { - case ThreadDataSchema::Version::UNKNOWN: - throw UnknowThreadFormatException(); - case ThreadDataSchema::Version::VERSION_1: - return; - case ThreadDataSchema::Version::VERSION_4: - return; - case ThreadDataSchema::Version::VERSION_5: { - auto thread_data = core::dynamic::EncryptedModuleDataV5::fromJSON(thread_data_entry.data); - auto dio = _threadDataEncryptorV5.getDIOAndAssertIntegrity(thread_data); - if (dio.contextId != thread.contextId || - dio.resourceId != thread.resourceId || - dio.creatorUserId != thread.lastModifier || - !core::TimestampValidator::validate(dio.timestamp, thread.lastModificationDate)) { - throw ThreadDataIntegrityException(); - } - return; - } - } - throw UnknowThreadFormatException(); -} - -uint32_t ThreadApiImpl::validateThreadDataIntegrity(server::ThreadInfo thread) { - try { - assertThreadDataIntegrity(thread); - return 0; - } catch (const core::Exception& e) { return e.getCode(); } catch (const privmx::utils::PrivmxException& e) { - return core::ExceptionConverter::convert(e).getCode(); - ; - } catch (...) { return ENDPOINT_CORE_EXCEPTION_CODE; } -} - -uint32_t ThreadApiImpl::validateMessageDataIntegrity(server::Message message, const std::string& threadResourceId) { - try { - switch (getMessagesDataStructureVersion(message)) { - case MessageDataSchema::Version::UNKNOWN: - return UnknowMessageFormatException().getCode(); - case MessageDataSchema::Version::VERSION_2: - return 0; - case MessageDataSchema::Version::VERSION_3: - return 0; - case MessageDataSchema::Version::VERSION_4: - return 0; - case MessageDataSchema::Version::VERSION_5: { - auto encData = server::EncryptedMessageDataV5::fromJSON(message.data); - auto dio = _messageDataEncryptorV5.getDIOAndAssertIntegrity(encData); - if (dio.contextId != message.contextId || - dio.resourceId != message.resourceId || - !dio.containerId.has_value() || - dio.containerId.value() != message.threadId || - !dio.containerResourceId.has_value() || - dio.containerResourceId.value() != threadResourceId || - dio.creatorUserId != (message.updates.empty() ? message.author : message.updates.back().author) || - !core::TimestampValidator::validate( - dio.timestamp, (message.updates.empty() ? message.createDate : message.updates.back().createDate) - )) { - return MessageDataIntegrityException().getCode(); - } - return 0; - } - } - } catch (const core::Exception& e) { return e.getCode(); } catch (const privmx::utils::PrivmxException& e) { - return core::ExceptionConverter::convert(e).getCode(); - } catch (...) { return ENDPOINT_CORE_EXCEPTION_CODE; } - return UnknowMessageFormatException().getCode(); -} - std::vector ThreadApiImpl::subscribeFor(const std::vector& subscriptionQueries) { auto result = _subscriber.subscribeFor(subscriptionQueries); _eventMiddleware->notificationEventListenerAddSubscriptionIds(_notificationListenerId, result); diff --git a/endpoint/thread/src/encryptors/message/MessageDataEncryptor.cpp b/endpoint/thread/src/encryptors/message/MessageDataEncryptor.cpp deleted file mode 100644 index 058b90a5..00000000 --- a/endpoint/thread/src/encryptors/message/MessageDataEncryptor.cpp +++ /dev/null @@ -1,84 +0,0 @@ -/* -PrivMX Endpoint. -Copyright © 2024 Simplito sp. z o.o. - -This file is part of the PrivMX Platform (https://privmx.dev). -This software is Licensed under the PrivMX Free License. - -See the License for the specific language governing permissions and -limitations under the License. -*/ - -#include - -#include -#include -#include - -#include "privmx/endpoint/thread/encryptors/message/MessageDataEncryptor.hpp" -#include -#include - -using namespace privmx::endpoint::thread; - -std::string MessageDataV2Encryptor::signAndEncrypt( - const dynamic::MessageDataV2& data, - const privmx::crypto::PrivateKey& priv, - const core::EncKey& encKey -) { - return _dataEncryptor.signAndEncrypt(data, priv, encKey); -} - -dynamic::MessageDataV2Signed MessageDataV2Encryptor::decryptAndGetSign( - const std::string& data, - const core::EncKey& key -) { - dynamic::MessageDataV2Signed result; - auto decrypted = _dataEncryptor.decryptAndGetSign(data, key); - result.dataSignature = std::get<0>(decrypted); - result.data = std::get<1>(decrypted); - result.data.statusCode = 0; - result.dataBuf = std::get<2>(decrypted); - return result; -} - -std::string MessageDataV3Encryptor::signAndEncrypt( - const dynamic::MessageDataV3& data, - const privmx::crypto::PrivateKey& priv, - const core::EncKey& encKey -) { - dynamic::MessageDataV3 messageDataV3Encrypted; - messageDataV3Encrypted.publicMeta = utils::Base64::from(data.publicMeta); // for extra save - messageDataV3Encrypted.privateMeta = _dataEncryptorBinaryString.encrypt(data.privateMeta, encKey); - messageDataV3Encrypted.data = _dataEncryptorBinaryString.encrypt(data.data, encKey); - return utils::Base64::from(_dataEncryptorMessageDataV3.sign(messageDataV3Encrypted, priv)); -} - -dynamic::MessageDataV3Signed MessageDataV3Encryptor::decryptAndGetSign( - const std::string& data, - const core::EncKey& key -) { - dynamic::MessageDataV3Signed result; - Pson::BinaryString dataBuf, dataSignature; - std::tie(dataSignature, dataBuf) = _dataEncryptorMessageDataV3.extractSignAndDataBuff( - utils::Base64::toString(data) - ); - dynamic::MessageDataV3 messageDataV3Encrypted = dynamic::MessageDataV3::fromJSON( - privmx::utils::Utils::parseJsonObject(dataBuf) - ); - dynamic::MessageDataV3 messageDataV3; - messageDataV3.publicMeta = utils::Base64::toString(messageDataV3Encrypted.publicMeta); - try { - messageDataV3.privateMeta = _dataEncryptorBinaryString.decrypt(messageDataV3Encrypted.privateMeta, key); - messageDataV3.data = _dataEncryptorBinaryString.decrypt(messageDataV3Encrypted.data, key); - messageDataV3.statusCode = 0; - } catch (const privmx::endpoint::core::Exception& e) { - messageDataV3.statusCode = e.getCode(); - } catch (const privmx::utils::PrivmxException& e) { - messageDataV3.statusCode = core::ExceptionConverter::convert(e).getCode(); - } catch (...) { messageDataV3.statusCode = ENDPOINT_CORE_EXCEPTION_CODE; } - result.data = messageDataV3; - result.dataSignature = dataSignature; - result.dataBuf = dataBuf; - return result; -} \ No newline at end of file diff --git a/endpoint/thread/src/encryptors/message/MessageDataSchemaMapper.cpp b/endpoint/thread/src/encryptors/message/MessageDataSchemaMapper.cpp new file mode 100644 index 00000000..fc07e802 --- /dev/null +++ b/endpoint/thread/src/encryptors/message/MessageDataSchemaMapper.cpp @@ -0,0 +1,259 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "privmx/endpoint/thread/encryptors/message/MessageDataSchemaMapper.hpp" + +#include "privmx/endpoint/thread/ThreadException.hpp" +#include +#include +#include +#include +#include +#include +#include + +using namespace privmx::endpoint; +using namespace privmx::endpoint::thread; + +MessageDataSchemaMapper::MessageDataSchemaMapper( + const privmx::crypto::PrivateKey& userPrivKey, + const core::Connection& connection +) + : _userPrivKey(userPrivKey), _connection(connection) { + _strategyV4 = std::make_shared(); + _strategyMapper.registerStrategy(MessageDataSchema::Version::VERSION_4, _strategyV4); + _strategyV5 = std::make_shared(); + _strategyMapper.registerStrategy(MessageDataSchema::Version::VERSION_5, _strategyV5); +} + +Poco::Dynamic::Var MessageDataSchemaMapper::encrypt( + const std::string& threadId, + const std::string& resourceId, + const std::string& contextId, + const std::string& moduleResourceId, + const core::Buffer& publicMeta, + const core::Buffer& privateMeta, + const core::Buffer& data, + const core::DecryptedEncKeyV2& msgKey +) { + switch (msgKey.dataStructureVersion) { + case core::EncryptionKeyDataSchema::Version::UNKNOWN: + throw UnknowThreadFormatException(); + case core::EncryptionKeyDataSchema::Version::VERSION_1: { + return _strategyV4->encrypt(publicMeta, privateMeta, data, _userPrivKey, msgKey.key).toJSON(); + } + case core::EncryptionKeyDataSchema::Version::VERSION_2: { + auto messageDIO = _connection.getImpl()->createDIO(contextId, resourceId, threadId, moduleResourceId); + return _strategyV5->encrypt(publicMeta, privateMeta, data, _userPrivKey, msgKey.key, messageDIO).toJSON(); + } + } + throw UnknowThreadFormatException(); +} + +std::tuple MessageDataSchemaMapper::decrypt( + const server::Message& message, + const core::DecryptedEncKey& encKey +) { + auto version = getMessagesDataStructureVersion(message); + auto strategy = _strategyMapper.getStrategy(static_cast(version)); + if (!strategy) { + auto e = UnknowMessageFormatException(); + return { + toLibMessage(message, {}, {}, {}, {}, e.getCode(), MessageDataSchema::Version::UNKNOWN), + core::DataIntegrityObject{} + }; + } + return strategy->decryptAndConvert(message, encKey); +} + +MessageDataSchema::Version MessageDataSchemaMapper::getMessagesDataStructureVersion(const server::Message& message) { + if (message.data.type() == typeid(Poco::JSON::Object::Ptr)) { + auto versioned = core::dynamic::VersionedData::fromJSON(message.data); + switch (versioned.version) { + case MessageDataSchema::Version::VERSION_4: + return MessageDataSchema::Version::VERSION_4; + case MessageDataSchema::Version::VERSION_5: + return MessageDataSchema::Version::VERSION_5; + default: + return MessageDataSchema::Version::UNKNOWN; + } + } + return MessageDataSchema::Version::UNKNOWN; +} + +uint32_t MessageDataSchemaMapper::validateMessageDataIntegrity( + const server::Message& message, + const std::string& threadResourceId +) { + try { + switch (getMessagesDataStructureVersion(message)) { + case MessageDataSchema::Version::VERSION_4: + return 0; + case MessageDataSchema::Version::VERSION_5: { + auto encData = server::EncryptedMessageDataV5::fromJSON(message.data); + auto dio = _strategyV5->getDIOAndAssertIntegrity(encData); + if (dio.contextId != message.contextId || + dio.resourceId != message.resourceId || + !dio.containerId.has_value() || + dio.containerId.value() != message.threadId || + !dio.containerResourceId.has_value() || + dio.containerResourceId.value() != threadResourceId || + dio.creatorUserId != (message.updates.empty() ? message.author : message.updates.back().author) || + !core::TimestampValidator::validate( + dio.timestamp, (message.updates.empty() ? message.createDate : message.updates.back().createDate) + )) { + return MessageDataIntegrityException().getCode(); + } + return 0; + } + default: + return UnknowMessageFormatException().getCode(); + } + } catch (const core::Exception& e) { return e.getCode(); } catch (const privmx::utils::PrivmxException& e) { + return core::ExceptionConverter::convert(e).getCode(); + } catch (...) { return ENDPOINT_CORE_EXCEPTION_CODE; } +} + +ThreadDataSchema::Version MessageDataSchemaMapper::getMinimumContainerSchemaVersionForMessage( + const server::Message& message +) { + switch (getMessagesDataStructureVersion(message)) { + case MessageDataSchema::Version::VERSION_4: + return ThreadDataSchema::VERSION_4; + case MessageDataSchema::Version::VERSION_5: + return ThreadDataSchema::VERSION_5; + default: + return ThreadDataSchema::UNKNOWN; + } + return ThreadDataSchema::UNKNOWN; +} + +Message MessageDataSchemaMapper::toLibMessage( + const server::Message& message, + const core::Buffer& publicMeta, + const core::Buffer& privateMeta, + const core::Buffer& data, + const std::string& authorPubKey, + int64_t statusCode, + int64_t schemaVersion +) { + return Message{ + .info = + {.threadId = message.threadId, + .messageId = message.id, + .createDate = message.createDate, + .author = message.author}, + .publicMeta = publicMeta, + .privateMeta = privateMeta, + .data = data, + .authorPubKey = authorPubKey, + .statusCode = statusCode, + .schemaVersion = schemaVersion + }; +} + +std::vector MessageDataSchemaMapper::validateDecryptAndConvertMessages( + const std::vector& messages, + const core::ModuleKeys& threadKeys, + const std::shared_ptr& keyProvider +) { + if (messages.size() == 0) { + return std::vector{}; + } + std::vector result(messages.size()); + std::vector messagesDIO(messages.size()); + std::set seenRandomIds; + + // integrity validation + for (size_t i = 0; i < messages.size(); i++) { + result[i].statusCode = validateMessageDataIntegrity(messages[i], threadKeys.moduleResourceId); + if (result[i].statusCode != 0) { + result[i] = toLibMessage( + messages[i], {}, {}, {}, {}, result[i].statusCode, MessageDataSchema::Version::UNKNOWN + ); + } + } + + // single batch key fetch + const core::EncKeyLocation location{.contextId = threadKeys.contextId, .resourceId = threadKeys.moduleResourceId}; + core::KeyDecryptionAndVerificationRequest keyRequest; + for (size_t i = 0; i < messages.size(); i++) { + if (result[i].statusCode != 0) { + continue; + } + try { + _messageKeyIdFormatValidator.assertKeyIdFormat(messages[i].keyId); + } catch (const core::Exception& e) { + result[i] = toLibMessage(messages[i], {}, {}, {}, {}, e.getCode(), MessageDataSchema::Version::UNKNOWN); + continue; + } + keyRequest.addOne(threadKeys.keys, messages[i].keyId, location); + } + auto keysResult = keyProvider->getKeysAndVerify(keyRequest); + auto keyMapIt = keysResult.find(location); + + // decrypt, deduplication check + for (size_t i = 0; i < messages.size(); i++) { + if (result[i].statusCode != 0) { + continue; + } + try { + auto [decryptedMessage, dio] = decrypt(messages[i], keyMapIt->second.at(messages[i].keyId)); + result[i] = decryptedMessage; + messagesDIO[i] = dio; + if (!seenRandomIds.insert(dio.randomId + "-" + std::to_string(dio.timestamp)).second) { + result[i].statusCode = core::DataIntegrityObjectDuplicatedException().getCode(); + } + } catch (const core::Exception& e) { + result[i] = toLibMessage(messages[i], {}, {}, {}, {}, e.getCode(), MessageDataSchema::Version::UNKNOWN); + } catch (const privmx::utils::PrivmxException& e) { + result[i] = toLibMessage( + messages[i], {}, {}, {}, {}, core::ExceptionConverter::convert(e).getCode(), + MessageDataSchema::Version::UNKNOWN + ); + } catch (...) { + result[i] = toLibMessage( + messages[i], {}, {}, {}, {}, ENDPOINT_CORE_EXCEPTION_CODE, MessageDataSchema::Version::UNKNOWN + ); + } + } + + // single batch identity verification + std::vector verifyRequests; + std::vector verifyIndices; + for (size_t i = 0; i < result.size(); i++) { + if (result[i].statusCode != 0) { + continue; + } + verifyRequests.push_back( + {.contextId = threadKeys.contextId, + .senderId = result[i].info.author, + .senderPubKey = result[i].authorPubKey, + .date = result[i].info.createDate, + .bridgeIdentity = messagesDIO[i].bridgeIdentity} + ); + verifyIndices.push_back(i); + } + auto verified = _connection.getImpl()->getUserVerifier()->verify(verifyRequests); + for (size_t j = 0; j < verifyIndices.size(); j++) + result[verifyIndices[j]].statusCode = verified[j] ? + 0 : + core::ExceptionConverter::getCodeOfUserVerificationFailureException(); + return result; +} + +Message MessageDataSchemaMapper::validateDecryptAndConvertMessage( + const server::Message& message, + const core::ModuleKeys& threadKeys, + const std::shared_ptr& keyProvider +) { + return validateDecryptAndConvertMessages({message}, threadKeys, keyProvider)[0]; +} diff --git a/endpoint/thread/src/encryptors/message/MessageDataSchemaStrategyV4.cpp b/endpoint/thread/src/encryptors/message/MessageDataSchemaStrategyV4.cpp new file mode 100644 index 00000000..ca6bf864 --- /dev/null +++ b/endpoint/thread/src/encryptors/message/MessageDataSchemaStrategyV4.cpp @@ -0,0 +1,79 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "privmx/endpoint/thread/encryptors/message/MessageDataSchemaStrategyV4.hpp" +#include "privmx/endpoint/thread/encryptors/message/MessageDataSchemaMapper.hpp" + +#include +#include + +#include "privmx/endpoint/thread/Constants.hpp" + +using namespace privmx::endpoint; +using namespace privmx::endpoint::thread; + +server::EncryptedMessageDataV4 MessageDataSchemaStrategyV4::encrypt( + const core::Buffer& publicMeta, + const core::Buffer& privateMeta, + const core::Buffer& data, + const privmx::crypto::PrivateKey& userPrivKey, + const std::string& key +) const { + MessageDataToEncryptV4 messageData{ + .publicMeta = publicMeta, .privateMeta = privateMeta, .data = data, .internalMeta = std::nullopt + }; + return _encryptor.encrypt(messageData, userPrivKey, key); +} + +DecryptedMessageDataV4 MessageDataSchemaStrategyV4::decrypt( + const server::Message& message, + const core::DecryptedEncKey& encKey +) const { + auto encryptedMessageData = server::EncryptedMessageDataV4::fromJSON(message.data); + return _encryptor.decrypt(encryptedMessageData, encKey.key); +} + +std::tuple MessageDataSchemaStrategyV4::convert( + const server::Message& message, + const DecryptedMessageDataV4& raw +) const { + const auto& lastAuthor = message.updates.empty() ? message.author : message.updates.back().author; + const auto& lastDate = message.updates.empty() ? message.createDate : message.updates.back().createDate; + return { + MessageDataSchemaMapper::toLibMessage( + message, raw.publicMeta, raw.privateMeta, raw.data, raw.authorPubKey, raw.statusCode, + MessageDataSchema::Version::VERSION_4 + ), + core::DataIntegrityObject{ + .creatorUserId = lastAuthor, + .creatorPubKey = raw.authorPubKey, + .contextId = message.contextId, + .resourceId = message.resourceId, + .timestamp = lastDate, + .randomId = std::string(), + .containerId = message.threadId, + .containerResourceId = std::string(), + .bridgeIdentity = std::nullopt + } + }; +} + +std::tuple MessageDataSchemaStrategyV4::makeErrorResult( + const server::Message& message, + int64_t errorCode +) const { + return { + MessageDataSchemaMapper::toLibMessage( + message, {}, {}, {}, {}, errorCode, MessageDataSchema::Version::VERSION_4 + ), + core::DataIntegrityObject{} + }; +} diff --git a/endpoint/thread/src/encryptors/message/MessageDataSchemaStrategyV5.cpp b/endpoint/thread/src/encryptors/message/MessageDataSchemaStrategyV5.cpp new file mode 100644 index 00000000..0bfbe220 --- /dev/null +++ b/endpoint/thread/src/encryptors/message/MessageDataSchemaStrategyV5.cpp @@ -0,0 +1,80 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "privmx/endpoint/thread/encryptors/message/MessageDataSchemaStrategyV5.hpp" +#include "privmx/endpoint/thread/encryptors/message/MessageDataSchemaMapper.hpp" + +#include +#include + +#include "privmx/endpoint/thread/Constants.hpp" + +using namespace privmx::endpoint; +using namespace privmx::endpoint::thread; + +server::EncryptedMessageDataV5 MessageDataSchemaStrategyV5::encrypt( + const core::Buffer& publicMeta, + const core::Buffer& privateMeta, + const core::Buffer& data, + const privmx::crypto::PrivateKey& userPrivKey, + const std::string& key, + const core::DataIntegrityObject& dio +) const { + MessageDataToEncryptV5 messageData{ + .publicMeta = publicMeta, .privateMeta = privateMeta, .data = data, .internalMeta = std::nullopt, .dio = dio + }; + return _encryptor.encrypt(messageData, userPrivKey, key); +} + +DecryptedMessageDataV5 MessageDataSchemaStrategyV5::decrypt( + const server::Message& message, + const core::DecryptedEncKey& encKey +) const { + auto encryptedMessageData = server::EncryptedMessageDataV5::fromJSON(message.data); + if (encKey.statusCode == 0) { + return _encryptor.decrypt(encryptedMessageData, encKey.key); + } else { + auto result = _encryptor.extractPublic(encryptedMessageData); + result.statusCode = encKey.statusCode; + return result; + } +} + +std::tuple MessageDataSchemaStrategyV5::convert( + const server::Message& message, + const DecryptedMessageDataV5& raw +) const { + return { + MessageDataSchemaMapper::toLibMessage( + message, raw.publicMeta, raw.privateMeta, raw.data, raw.authorPubKey, raw.statusCode, + MessageDataSchema::Version::VERSION_5 + ), + raw.dio + }; +} + +std::tuple MessageDataSchemaStrategyV5::makeErrorResult( + const server::Message& message, + int64_t errorCode +) const { + return { + MessageDataSchemaMapper::toLibMessage( + message, {}, {}, {}, {}, errorCode, MessageDataSchema::Version::VERSION_5 + ), + core::DataIntegrityObject{} + }; +} + +core::DataIntegrityObject MessageDataSchemaStrategyV5::getDIOAndAssertIntegrity( + const server::EncryptedMessageDataV5& encData +) const { + return _encryptor.getDIOAndAssertIntegrity(encData); +} diff --git a/endpoint/thread/src/encryptors/thread/ThreadDataSchemaMapper.cpp b/endpoint/thread/src/encryptors/thread/ThreadDataSchemaMapper.cpp new file mode 100644 index 00000000..8ec3a267 --- /dev/null +++ b/endpoint/thread/src/encryptors/thread/ThreadDataSchemaMapper.cpp @@ -0,0 +1,219 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "privmx/endpoint/thread/encryptors/thread/ThreadDataSchemaMapper.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "privmx/endpoint/thread/ThreadException.hpp" + +using namespace privmx::endpoint; +using namespace privmx::endpoint::thread; + +ThreadDataSchemaMapper::ThreadDataSchemaMapper( + const privmx::crypto::PrivateKey& userPrivKey, + const core::Connection& connection +) + : _userPrivKey(userPrivKey), _connection(connection) { + _strategyMapper.registerStrategy( + ThreadDataSchema::Version::VERSION_4, std::make_shared() + ); + _strategyV5 = std::make_shared(); + _strategyMapper.registerStrategy(ThreadDataSchema::Version::VERSION_5, _strategyV5); +} + +Poco::Dynamic::Var ThreadDataSchemaMapper::encrypt(const core::ModuleDataToEncryptV5& data, const std::string& key) { + return _strategyV5->encrypt(data, _userPrivKey, key).toJSON(); +} + +std::tuple ThreadDataSchemaMapper::decrypt( + const server::ThreadInfo& thread, + const core::DecryptedEncKey& encKey +) { + auto version = getDataStructureVersion(thread.data.back()); + auto strategy = _strategyMapper.getStrategy(static_cast(version)); + if (!strategy) { + auto e = UnknowThreadFormatException(); + return { + toLibThread(thread, {}, {}, e.getCode(), ThreadDataSchema::Version::UNKNOWN), core::DataIntegrityObject{} + }; + } + return strategy->decryptAndConvert(thread, encKey); +} + +ThreadDataSchema::Version ThreadDataSchemaMapper::getDataStructureVersion(const server::Thread2DataEntry& entry) { + if (entry.data.type() == typeid(Poco::JSON::Object::Ptr)) { + auto versioned = core::dynamic::VersionedData::fromJSON(entry.data); + switch (versioned.version) { + case core::ModuleDataSchema::Version::VERSION_4: + return ThreadDataSchema::Version::VERSION_4; + case core::ModuleDataSchema::Version::VERSION_5: + return ThreadDataSchema::Version::VERSION_5; + default: + return ThreadDataSchema::Version::UNKNOWN; + } + } + return ThreadDataSchema::Version::UNKNOWN; +} + +void ThreadDataSchemaMapper::assertDataIntegrity(const server::ThreadInfo& thread) { + const auto& entry = thread.data.back(); + switch (getDataStructureVersion(entry)) { + case ThreadDataSchema::Version::VERSION_4: + return; + case ThreadDataSchema::Version::VERSION_5: { + auto encData = core::dynamic::EncryptedModuleDataV5::fromJSON(entry.data); + auto dio = _strategyV5->getDIOAndAssertIntegrity(encData); + if (dio.contextId != thread.contextId || + dio.resourceId != thread.resourceId || + dio.creatorUserId != thread.lastModifier || + !core::TimestampValidator::validate(dio.timestamp, thread.lastModificationDate)) { + throw ThreadDataIntegrityException(); + } + return; + } + default: + throw UnknowThreadFormatException(); + } +} + +uint32_t ThreadDataSchemaMapper::validateDataIntegrity(const server::ThreadInfo& thread) { + try { + assertDataIntegrity(thread); + return 0; + } catch (const core::Exception& e) { return e.getCode(); } catch (const privmx::utils::PrivmxException& e) { + return core::ExceptionConverter::convert(e).getCode(); + } catch (...) { return ENDPOINT_CORE_EXCEPTION_CODE; } +} + +Thread ThreadDataSchemaMapper::toLibThread( + const server::ThreadInfo& info, + const core::Buffer& publicMeta, + const core::Buffer& privateMeta, + int64_t statusCode, + int64_t schemaVersion +) { + return Thread{ + .contextId = info.contextId, + .threadId = info.id, + .createDate = info.createDate, + .creator = info.creator, + .lastModificationDate = info.lastModificationDate, + .lastModifier = info.lastModifier, + .users = info.users, + .managers = info.managers, + .version = info.version, + .lastMsgDate = info.lastMsgDate, + .publicMeta = publicMeta, + .privateMeta = privateMeta, + .policy = core::Factory::parsePolicyServerObject(info.policy), + .messagesCount = info.messages, + .statusCode = statusCode, + .schemaVersion = schemaVersion + }; +} + +std::vector ThreadDataSchemaMapper::validateDecryptAndConvertThreads( + const std::vector& threads, + const std::shared_ptr& keyProvider +) { + + std::vector result(threads.size()); + std::vector result_dio(threads.size()); + + // integrity validation + for (size_t i = 0; i < threads.size(); i++) { + result[i].statusCode = validateDataIntegrity(threads[i]); + if (result[i].statusCode != 0) { + result[i] = toLibThread(threads[i], {}, {}, result[i].statusCode, ThreadDataSchema::Version::UNKNOWN); + } + } + + // single batch key fetch + core::KeyDecryptionAndVerificationRequest keyRequest; + for (size_t i = 0; i < threads.size(); i++) { + if (result[i].statusCode != 0) { + continue; + } + const auto& t = threads[i]; + core::EncKeyLocation loc{.contextId = t.contextId, .resourceId = t.resourceId.value_or("")}; + keyRequest.addOne(t.keys, t.data.back().keyId, loc); + } + auto threadKeys = keyProvider->getKeysAndVerify(keyRequest); + std::set seenRandomIds; + + //decrypt, deduplication check + for (size_t i = 0; i < threads.size(); i++) { + if (result[i].statusCode != 0) { + continue; + } + const auto& thread = threads[i]; + core::EncKeyLocation loc{.contextId = thread.contextId, .resourceId = thread.resourceId.value_or("")}; + try { + auto threadKeysIt = threadKeys.find( + core::EncKeyLocation{.contextId = thread.contextId, .resourceId = thread.resourceId.value_or("")} + ); + if (threadKeysIt == threadKeys.end()) { + throw UnknowThreadFormatException(); + } + auto [decryptedThread, dio] = decrypt(thread, threadKeysIt->second.at(thread.data.back().keyId)); + result[i] = decryptedThread; + result_dio[i] = dio; + if (!seenRandomIds.insert(dio.randomId + "-" + std::to_string(dio.timestamp)).second) { + result[i].statusCode = core::DataIntegrityObjectDuplicatedException().getCode(); + } + } catch (const core::Exception& e) { + result[i] = toLibThread(thread, {}, {}, e.getCode(), ThreadDataSchema::Version::UNKNOWN); + } catch (const privmx::utils::PrivmxException& e) { + result[i] = toLibThread( + thread, {}, {}, core::ExceptionConverter::convert(e).getCode(), ThreadDataSchema::Version::UNKNOWN + ); + } catch (...) { + result[i] = toLibThread(thread, {}, {}, ENDPOINT_CORE_EXCEPTION_CODE, ThreadDataSchema::Version::UNKNOWN); + } + } + + // single batch identity verification + std::vector verifyRequests; + std::vector verifyIndices; + for (size_t i = 0; i < result.size(); i++) { + if (result[i].statusCode != 0) { + continue; + } + verifyRequests.push_back( + {.contextId = result[i].contextId, + .senderId = result[i].lastModifier, + .senderPubKey = result_dio[i].creatorPubKey, + .date = result[i].lastModificationDate, + .bridgeIdentity = result_dio[i].bridgeIdentity} + ); + verifyIndices.push_back(i); + } + auto verified = _connection.getImpl()->getUserVerifier()->verify(verifyRequests); + for (size_t j = 0; j < verifyIndices.size(); j++) + result[verifyIndices[j]].statusCode = verified[j] ? + 0 : + core::ExceptionConverter::getCodeOfUserVerificationFailureException(); + return result; +} + +Thread ThreadDataSchemaMapper::validateDecryptAndConvertThread( + const server::ThreadInfo& thread, + const std::shared_ptr& keyProvider +) { + return validateDecryptAndConvertThreads({thread}, keyProvider)[0]; +} diff --git a/endpoint/thread/src/encryptors/thread/ThreadDataSchemaStrategyV4.cpp b/endpoint/thread/src/encryptors/thread/ThreadDataSchemaStrategyV4.cpp new file mode 100644 index 00000000..041d559b --- /dev/null +++ b/endpoint/thread/src/encryptors/thread/ThreadDataSchemaStrategyV4.cpp @@ -0,0 +1,62 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "privmx/endpoint/thread/encryptors/thread/ThreadDataSchemaStrategyV4.hpp" +#include "privmx/endpoint/thread/encryptors/thread/ThreadDataSchemaMapper.hpp" + +#include +#include +#include + +#include "privmx/endpoint/thread/Constants.hpp" + +using namespace privmx::endpoint; +using namespace privmx::endpoint::thread; + +core::DecryptedModuleDataV4 ThreadDataSchemaStrategyV4::decrypt( + const server::ThreadInfo& thread, + const core::DecryptedEncKey& encKey +) const { + auto encryptedData = core::dynamic::EncryptedModuleDataV4::fromJSON(thread.data.back().data); + return _encryptor.decrypt(encryptedData, encKey.key); +} + +std::tuple ThreadDataSchemaStrategyV4::convert( + const server::ThreadInfo& thread, + const core::DecryptedModuleDataV4& raw +) const { + return { + ThreadDataSchemaMapper::toLibThread( + thread, raw.publicMeta, raw.privateMeta, raw.statusCode, ThreadDataSchema::Version::VERSION_4 + ), + core::DataIntegrityObject{ + .creatorUserId = thread.lastModifier, + .creatorPubKey = raw.authorPubKey, + .contextId = thread.contextId, + .resourceId = thread.resourceId.value_or(""), + .timestamp = thread.lastModificationDate, + .randomId = std::string(), + .containerId = std::nullopt, + .containerResourceId = std::nullopt, + .bridgeIdentity = std::nullopt + } + }; +} + +std::tuple ThreadDataSchemaStrategyV4::makeErrorResult( + const server::ThreadInfo& thread, + int64_t errorCode +) const { + return { + ThreadDataSchemaMapper::toLibThread(thread, {}, {}, errorCode, ThreadDataSchema::Version::VERSION_4), + core::DataIntegrityObject{} + }; +} diff --git a/endpoint/thread/src/encryptors/thread/ThreadDataSchemaStrategyV5.cpp b/endpoint/thread/src/encryptors/thread/ThreadDataSchemaStrategyV5.cpp new file mode 100644 index 00000000..424144ca --- /dev/null +++ b/endpoint/thread/src/encryptors/thread/ThreadDataSchemaStrategyV5.cpp @@ -0,0 +1,71 @@ +/* +PrivMX Endpoint. +Copyright © 2024 Simplito sp. z o.o. + +This file is part of the PrivMX Platform (https://privmx.dev). +This software is Licensed under the PrivMX Free License. + +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "privmx/endpoint/thread/encryptors/thread/ThreadDataSchemaStrategyV5.hpp" +#include "privmx/endpoint/thread/encryptors/thread/ThreadDataSchemaMapper.hpp" + +#include +#include + +#include "privmx/endpoint/thread/Constants.hpp" + +using namespace privmx::endpoint; +using namespace privmx::endpoint::thread; + +core::dynamic::EncryptedModuleDataV5 ThreadDataSchemaStrategyV5::encrypt( + const core::ModuleDataToEncryptV5& data, + const privmx::crypto::PrivateKey& userPrivKey, + const std::string& key +) const { + return _encryptor.encrypt(data, userPrivKey, key); +} + +core::DecryptedModuleDataV5 ThreadDataSchemaStrategyV5::decrypt( + const server::ThreadInfo& thread, + const core::DecryptedEncKey& encKey +) const { + auto encryptedData = core::dynamic::EncryptedModuleDataV5::fromJSON(thread.data.back().data); + if (encKey.statusCode == 0) { + return _encryptor.decrypt(encryptedData, encKey.key); + } else { + auto result = _encryptor.extractPublic(encryptedData); + result.statusCode = encKey.statusCode; + return result; + } +} + +std::tuple ThreadDataSchemaStrategyV5::convert( + const server::ThreadInfo& thread, + const core::DecryptedModuleDataV5& raw +) const { + return { + ThreadDataSchemaMapper::toLibThread( + thread, raw.publicMeta, raw.privateMeta, raw.statusCode, ThreadDataSchema::Version::VERSION_5 + ), + raw.dio + }; +} + +std::tuple ThreadDataSchemaStrategyV5::makeErrorResult( + const server::ThreadInfo& thread, + int64_t errorCode +) const { + return { + ThreadDataSchemaMapper::toLibThread(thread, {}, {}, errorCode, ThreadDataSchema::Version::VERSION_5), + core::DataIntegrityObject{} + }; +} + +core::DataIntegrityObject ThreadDataSchemaStrategyV5::getDIOAndAssertIntegrity( + const core::dynamic::EncryptedModuleDataV5& encData +) const { + return _encryptor.getDIOAndAssertIntegrity(encData); +} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index aa84c8a1..049ebbb4 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -27,20 +27,20 @@ add_executable(test_e2e_StoreTest ${CMAKE_CURRENT_SOURCE_DIR}/tests/StoreTest.cp target_link_libraries(test_e2e_StoreTest privmx privmxendpointcore privmxendpointstore Poco::Foundation Poco::Util GTest::GTest) add_executable(test_e2e_StoreModuleEventsTest ${CMAKE_CURRENT_SOURCE_DIR}/tests/StoreModuleEventsTest.cpp) target_link_libraries(test_e2e_StoreModuleEventsTest privmx privmxendpointcore privmxendpointstore Poco::Foundation Poco::Util GTest::GTest) -# # Inbox +# Inbox add_executable(test_e2e_InboxTest ${CMAKE_CURRENT_SOURCE_DIR}/tests/InboxTest.cpp) target_link_libraries(test_e2e_InboxTest privmx privmxendpointcore privmxendpointthread privmxendpointstore privmxendpointinbox Poco::Foundation Poco::Util GTest::GTest) add_executable(test_e2e_InboxModuleEventsTest ${CMAKE_CURRENT_SOURCE_DIR}/tests/InboxModuleEventsTest.cpp) target_link_libraries(test_e2e_InboxModuleEventsTest privmx privmxendpointcore privmxendpointthread privmxendpointstore privmxendpointinbox Poco::Foundation Poco::Util GTest::GTest) -# # Event +# Event add_executable(test_e2e_EventsTest ${CMAKE_CURRENT_SOURCE_DIR}/tests/EventsTest.cpp) target_link_libraries(test_e2e_EventsTest privmx privmxendpointcore privmxendpointevent Poco::Foundation Poco::Util GTest::GTest) -# # Other +# Other add_executable(test_e2e_CryptoTest ${CMAKE_CURRENT_SOURCE_DIR}/tests/CryptoTest.cpp) target_link_libraries(test_e2e_CryptoTest privmx privmxendpointcore privmxendpointcrypto Poco::Foundation Poco::Util GTest::GTest) add_executable(test_e2e_UtilsTest ${CMAKE_CURRENT_SOURCE_DIR}/tests/UtilsTest.cpp) target_link_libraries(test_e2e_UtilsTest privmx privmxendpointcore Poco::Foundation Poco::Util GTest::GTest) -# # Kvdb +# Kvdb add_executable(test_e2e_KvdbTest ${CMAKE_CURRENT_SOURCE_DIR}/tests/KvdbTest.cpp) target_link_libraries(test_e2e_KvdbTest privmx privmxendpointcore privmxendpointkvdb Poco::Foundation Poco::Util GTest::GTest) add_executable(test_e2e_KvdbModuleEventsTest ${CMAKE_CURRENT_SOURCE_DIR}/tests/KvdbModuleEventsTest.cpp)