From 8bfb20971f6e073e84ba02e2e35fb17a01d4ae15 Mon Sep 17 00:00:00 2001 From: Rich Logan Date: Fri, 28 Jun 2024 15:05:41 +0100 Subject: [PATCH] Add support for null cipher --- include/qmedia/QController.hpp | 11 +- include/qmedia/QSFrameContext.hpp | 1 + include/qmedia/QuicrDelegates.hpp | 17 ++- src/QController.cpp | 18 ++- src/QSFrameContext.cpp | 9 ++ src/QuicrDelegates.cpp | 196 ++++++++++++++++++++---------- test/qmedia.cpp | 17 ++- 7 files changed, 186 insertions(+), 83 deletions(-) diff --git a/include/qmedia/QController.hpp b/include/qmedia/QController.hpp index a4b603a..42f6118 100644 --- a/include/qmedia/QController.hpp +++ b/include/qmedia/QController.hpp @@ -11,6 +11,8 @@ #include #include +#include +#include using json = nlohmann::json; using SourceId = std::string; @@ -18,6 +20,8 @@ using SourceId = std::string; namespace qmedia { +constexpr sframe::CipherSuite Default_Cipher_Suite = sframe::CipherSuite::AES_GCM_128_SHA256; + class QController { public: @@ -30,7 +34,8 @@ class QController QController(std::shared_ptr subscriberDelegate, std::shared_ptr publisherDelegate, const cantina::LoggerPointer& logger, - const bool debugging = false); + const bool debugging = false, + const std::optional cipherSuite = Default_Cipher_Suite); ~QController(); @@ -97,7 +102,8 @@ class QController const quicr::TransportMode transportMode, const std::string& authToken, quicr::bytes&& e2eToken, - std::shared_ptr delegate); + std::shared_ptr delegate, + const std::optional cipherSuite); std::shared_ptr findQuicrPublicationDelegate(const quicr::Namespace& quicrNamespace); @@ -166,6 +172,7 @@ class QController bool closed; bool is_singleordered_subscription = true; bool is_singleordered_publication = false; + std::optional cipher_suite; }; } // namespace qmedia diff --git a/include/qmedia/QSFrameContext.hpp b/include/qmedia/QSFrameContext.hpp index 9f94986..e4c5817 100644 --- a/include/qmedia/QSFrameContext.hpp +++ b/include/qmedia/QSFrameContext.hpp @@ -15,6 +15,7 @@ class QSFrameContext { public: QSFrameContext(sframe::CipherSuite cipher_suite); + QSFrameContext(QSFrameContext& other); void addEpoch(uint64_t epoch_id, const quicr::bytes& epoch_secret); void enableEpoch(uint64_t epoch_id); diff --git a/include/qmedia/QuicrDelegates.hpp b/include/qmedia/QuicrDelegates.hpp index 7f59c9f..226e113 100644 --- a/include/qmedia/QuicrDelegates.hpp +++ b/include/qmedia/QuicrDelegates.hpp @@ -9,6 +9,7 @@ #include #include +#include #include namespace qmedia @@ -24,7 +25,8 @@ class SubscriptionDelegate : public quicr::SubscriberDelegate, public std::enabl const std::string& authToken, quicr::bytes e2eToken, std::shared_ptr qDelegate, - const cantina::LoggerPointer& logger); + const cantina::LoggerPointer& logger, + const std::optional cipherSuite); public: [[nodiscard]] static std::shared_ptr @@ -36,7 +38,8 @@ class SubscriptionDelegate : public quicr::SubscriberDelegate, public std::enabl const std::string& authToken, quicr::bytes e2eToken, std::shared_ptr qDelegate, - const cantina::LoggerPointer& logger); + const cantina::LoggerPointer& logger, + const std::optional cipherSuite); std::shared_ptr getptr() { return shared_from_this(); } @@ -86,7 +89,7 @@ class SubscriptionDelegate : public quicr::SubscriberDelegate, public std::enabl std::uint32_t currentGroupId; std::uint16_t currentObjectId; - QSFrameContext sframe_context; + std::optional sframe_context; }; class PublicationDelegate : public quicr::PublisherDelegate, public std::enable_shared_from_this @@ -100,7 +103,8 @@ class PublicationDelegate : public quicr::PublisherDelegate, public std::enable_ quicr::bytes&& payload, const std::vector& priority, const std::vector& expiry, - const cantina::LoggerPointer& logger); + const cantina::LoggerPointer& logger, + const std::optional cipherSuite); public: [[nodiscard]] static std::shared_ptr @@ -113,7 +117,8 @@ class PublicationDelegate : public quicr::PublisherDelegate, public std::enable_ quicr::bytes&& payload, const std::vector& priority, const std::vector& expiry, - const cantina::LoggerPointer& logger); + const cantina::LoggerPointer& logger, + const std::optional cipherSuite); std::shared_ptr getptr() { return shared_from_this(); } @@ -156,6 +161,6 @@ class PublicationDelegate : public quicr::PublisherDelegate, public std::enable_ std::shared_ptr qDelegate; const cantina::LoggerPointer logger; - QSFrameContext sframe_context; + std::optional sframe_context; }; } // namespace qmedia diff --git a/src/QController.cpp b/src/QController.cpp index 937a1f1..03bc783 100644 --- a/src/QController.cpp +++ b/src/QController.cpp @@ -13,12 +13,14 @@ namespace qmedia QController::QController(std::shared_ptr qSubscriberDelegate, std::shared_ptr qPublisherDelegate, const cantina::LoggerPointer& logger, - const bool debugging) : + const bool debugging, + const std::optional cipher_suite) : logger(std::make_shared("QCTRL", logger)), qSubscriberDelegate(std::move(qSubscriberDelegate)), qPublisherDelegate(std::move(qPublisherDelegate)), stop(false), - closed(false) + closed(false), + cipher_suite(cipher_suite) { // If there's a parent logger, its log level will be used. // Otherwise, query the debugging flag. @@ -179,7 +181,8 @@ QController::createQuicrSubscriptionDelegate(const std::string& sourceId, const quicr::TransportMode transportMode, const std::string& authToken, quicr::bytes&& e2eToken, - std::shared_ptr qDelegate) + std::shared_ptr qDelegate, + const std::optional cipherSuite) { std::lock_guard _(subsMutex); if (quicrSubscriptionsMap.contains(quicrNamespace)) @@ -196,7 +199,8 @@ QController::createQuicrSubscriptionDelegate(const std::string& sourceId, authToken, std::move(e2eToken), std::move(qDelegate), - logger); + logger, + cipherSuite); return quicrSubscriptionsMap[quicrNamespace]; } @@ -239,7 +243,8 @@ QController::createQuicrPublicationDelegate(std::shared_ptrgetptr(); @@ -308,7 +313,8 @@ int QController::startSubscription(std::shared_ptr lock(other.context_mutex); + cipher_suite = other.cipher_suite; + current_epoch = other.current_epoch; + epoch_secrets = other.epoch_secrets; + ns_contexts = other.ns_contexts; +} + void QSFrameContext::addEpoch(uint64_t epoch_id, const quicr::bytes& epoch_secret) { std::lock_guard lock(context_mutex); diff --git a/src/QuicrDelegates.cpp b/src/QuicrDelegates.cpp index d840ca2..89c0e7d 100644 --- a/src/QuicrDelegates.cpp +++ b/src/QuicrDelegates.cpp @@ -14,7 +14,6 @@ constexpr quicr::Name Object_ID_Mask = ~(~0x0_name << 16); constexpr uint64_t Fixed_Epoch = 1; constexpr uint8_t Quicr_SFrame_Sig_Bits = 80; -constexpr sframe::CipherSuite Default_Cipher_Suite = sframe::CipherSuite::AES_GCM_128_SHA256; namespace qmedia { @@ -26,7 +25,8 @@ SubscriptionDelegate::SubscriptionDelegate(const std::string& sourceId, const std::string& authToken, quicr::bytes e2eToken, std::shared_ptr qDelegate, - const cantina::LoggerPointer& logger) : + const cantina::LoggerPointer& logger, + std::optional cipherSuite) : canReceiveSubs(true), sourceId(sourceId), quicrNamespace(quicrNamespace), @@ -36,21 +36,28 @@ SubscriptionDelegate::SubscriptionDelegate(const std::string& sourceId, e2eToken(e2eToken), qDelegate(std::move(qDelegate)), logger(std::make_shared("TSDEL", logger)), - sframe_context(Default_Cipher_Suite) + sframe_context(cipherSuite ? std::optional(*cipherSuite) : std::nullopt) { currentGroupId = 0; currentObjectId = -1; - // TODO: This needs to be replaced with valid keying material - std::string salt_string = "Quicr epoch master key " + std::to_string(Fixed_Epoch); - sframe::bytes salt(salt_string.begin(), salt_string.end()); - auto epoch_key = hkdf_extract( - Default_Cipher_Suite, - salt, - {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}); - sframe_context.addEpoch(Fixed_Epoch, epoch_key); - - sframe_context.enableEpoch(Fixed_Epoch); + if (sframe_context) + { + // TODO: This needs to be replaced with valid keying material + sframe::CipherSuite suite = *cipherSuite; + std::string salt_string = "Quicr epoch master key " + std::to_string(Fixed_Epoch); + sframe::bytes salt(salt_string.begin(), salt_string.end()); + auto epoch_key = hkdf_extract( + suite, + salt, + {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}); + sframe_context->addEpoch(Fixed_Epoch, epoch_key); + sframe_context->enableEpoch(Fixed_Epoch); + } + else + { + LOGGER_WARNING(logger, "[" << quicrNamespace << "] This subscription will not attempt to encrypt data"); + } } std::shared_ptr @@ -62,7 +69,8 @@ SubscriptionDelegate::create(const std::string& sourceId, const std::string& authToken, quicr::bytes e2eToken, std::shared_ptr qDelegate, - const cantina::LoggerPointer& logger) + const cantina::LoggerPointer& logger, + std::optional cipherSuite) { return std::shared_ptr(new SubscriptionDelegate(sourceId, @@ -73,7 +81,8 @@ SubscriptionDelegate::create(const std::string& sourceId, authToken, e2eToken, std::move(qDelegate), - logger)); + logger, + cipherSuite)); } void SubscriptionDelegate::onSubscribeResponse(const quicr::Namespace& /* quicr_namespace */, @@ -138,34 +147,61 @@ void SubscriptionDelegate::onSubscribedObject(const quicr::Name& quicrName, currentGroupId = groupId; currentObjectId = objectId; - // Decrypt the received data using sframe - try + quicr::bytes output_buffer; + if (sframe_context) { - auto buf = quicr::messages::MessageBuffer(data); - quicr::uintVar_t epoch; - buf >> epoch; - const auto ciphertext = buf.take(); - quicr::bytes output_buffer(ciphertext.size()); - auto cleartext = sframe_context.unprotect(epoch, - quicr::Namespace(quicrName, Quicr_SFrame_Sig_Bits), - quicrName.bits(0, 48), - output_buffer, - ciphertext); - output_buffer.resize(cleartext.size()); + // Decrypt the received data using sframe + try + { + auto buf = quicr::messages::MessageBuffer(data); + quicr::uintVar_t epoch; + buf >> epoch; + const auto ciphertext = buf.take(); + output_buffer = quicr::bytes(ciphertext.size()); + auto cleartext = sframe_context->unprotect(epoch, + quicr::Namespace(quicrName, Quicr_SFrame_Sig_Bits), + quicrName.bits(0, 48), + output_buffer, + ciphertext); + output_buffer.resize(cleartext.size()); + } + catch (const std::exception& e) + { + LOGGER_ERROR(logger, "Exception trying to decrypt sframe: " << e.what()); + return; + } + catch (const std::string& s) + { + LOGGER_ERROR(logger, "Exception trying to decrypt sframe: " << s); + return; + } + catch (...) + { + LOGGER_ERROR(logger, "Unknown error trying to decrypt sframe"); + return; + } + } + else + { + output_buffer = std::move(data); + } + // Forward the object on. + try + { qDelegate->subscribedObject(this->quicrNamespace, std::move(output_buffer), groupId, objectId); } catch (const std::exception& e) { - LOGGER_ERROR(logger, "Exception trying to decrypt with sframe and forward object: " << e.what()); + LOGGER_ERROR(logger, "Exception trying to forward object: " << e.what()); } catch (const std::string& s) { - LOGGER_ERROR(logger, "Exception trying to decrypt sframe and forward object: " << s); + LOGGER_ERROR(logger, "Exception trying to forward object: " << s); } catch (...) { - LOGGER_ERROR(logger, "Unknown error trying to decrypt sframe and forward object"); + LOGGER_ERROR(logger, "Unknown error trying to forward object"); } } @@ -212,7 +248,8 @@ PublicationDelegate::PublicationDelegate(std::shared_ptr& priority, const std::vector& expiry, - const cantina::LoggerPointer& logger) : + const cantina::LoggerPointer& logger, + const std::optional cipherSuite) : // canPublish(true), sourceId(sourceId), originUrl(originUrl), @@ -226,17 +263,22 @@ PublicationDelegate::PublicationDelegate(std::shared_ptr("TPDEL", logger)), - sframe_context(Default_Cipher_Suite) + sframe_context(cipherSuite ? std::optional(*cipherSuite) : std::nullopt) { - // TODO: This needs to be replaced with valid keying material - std::string salt_string = "Quicr epoch master key " + std::to_string(Fixed_Epoch); - sframe::bytes salt(salt_string.begin(), salt_string.end()); - auto epoch_key = hkdf_extract( - Default_Cipher_Suite, - salt, - std::vector{ - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}); - sframe_context.addEpoch(Fixed_Epoch, epoch_key); + if (sframe_context) { + // TODO: This needs to be replaced with valid keying material + std::string salt_string = "Quicr epoch master key " + std::to_string(Fixed_Epoch); + sframe::bytes salt(salt_string.begin(), salt_string.end()); + auto epoch_key = hkdf_extract( + *cipherSuite, + salt, + std::vector{ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}); + sframe_context->addEpoch(Fixed_Epoch, epoch_key); + sframe_context->enableEpoch(Fixed_Epoch); + } else { + LOGGER_WARNING(logger, "[" << quicrNamespace << "] This publication will not attempt to encrypt data"); + } // Publish named object and intent require priorities. Set defaults if missing if (this->priority.empty()) { @@ -245,8 +287,6 @@ PublicationDelegate::PublicationDelegate(std::shared_ptrpriority.size() == 1) { this->priority.emplace_back(priority[0]); } - - sframe_context.enableEpoch(Fixed_Epoch); } std::shared_ptr PublicationDelegate::create(std::shared_ptr qDelegate, const std::string& sourceId, @@ -257,7 +297,8 @@ std::shared_ptr PublicationDelegate::create(std::shared_ptr quicr::bytes&& payload, const std::vector& priority, const std::vector& expiry, - const cantina::LoggerPointer& logger) + const cantina::LoggerPointer& logger, + const std::optional cipherSuite) { return std::shared_ptr(new PublicationDelegate(std::move(qDelegate), sourceId, @@ -268,7 +309,8 @@ std::shared_ptr PublicationDelegate::create(std::shared_ptr std::move(payload), priority, expiry, - logger)); + logger, + cipherSuite)); } void PublicationDelegate::onPublishIntentResponse(const quicr::Namespace& quicr_namespace, @@ -350,37 +392,63 @@ void PublicationDelegate::publishNamedObject(std::shared_ptr clie quicrName = (0x0_name | ++objectId) | (quicrName & ~Object_ID_Mask); } - // Encrypt using sframe + quicr::bytes to_publish; + if (sframe_context) + { + // Encrypt using sframe + try + { + trace.push_back({"qMediaDelegate:publishNamedObject:beforeEncrypt", trace.front().start_time}); + quicr::bytes output_buffer(len + 16); + auto ciphertext = sframe_context->protect(quicr::Namespace(quicrName, Quicr_SFrame_Sig_Bits), + quicrName.bits(0, 48), + output_buffer, + {data, len}); + output_buffer.resize(ciphertext.size()); + auto buf = quicr::messages::MessageBuffer(output_buffer.size() + 8); + buf << quicr::uintVar_t(Fixed_Epoch); + buf.push(std::move(output_buffer)); + trace.push_back({"qMediaDelegate:publishNamedObject:afterEncrypt", trace.front().start_time}); + to_publish = buf.take(); + } + catch (const std::exception& e) + { + LOGGER_ERROR(logger, "Exception trying to encrypt: " << e.what()); + return; + } + catch (const std::string& s) + { + LOGGER_ERROR(logger, "Exception trying to encrypt: " << s); + return; + } + catch (...) + { + LOGGER_ERROR(logger, "Unknown error trying to encrypt"); + return; + } + } + else + { + to_publish = quicr::bytes(data, data + len); + } + try { - trace.push_back({"qMediaDelegate:publishNamedObject:beforeEncrypt", trace.front().start_time}); - quicr::bytes output_buffer(len + 16); - auto ciphertext = sframe_context.protect(quicr::Namespace(quicrName, Quicr_SFrame_Sig_Bits), - quicrName.bits(0, 48), - output_buffer, - {data, len}); - output_buffer.resize(ciphertext.size()); - auto buf = quicr::messages::MessageBuffer(output_buffer.size() + 8); - buf << quicr::uintVar_t(Fixed_Epoch); - buf.push(std::move(output_buffer)); - - trace.push_back({"qMediaDelegate:publishNamedObject:afterEncrypt", trace.front().start_time}); - - client->publishNamedObject(quicrName, pri, exp, buf.take(), std::move(trace)); + client->publishNamedObject(quicrName, pri, exp, std::move(to_publish), std::move(trace)); } catch (const std::exception& e) { - LOGGER_ERROR(logger, "Exception trying to encrypt sframe and publish: " << e.what()); + LOGGER_ERROR(logger, "Exception trying to publish: " << e.what()); return; } catch (const std::string& s) { - LOGGER_ERROR(logger, "Exception trying to encrypt sframe and publish: " << s); + LOGGER_ERROR(logger, "Exception trying to publish: " << s); return; } catch (...) { - LOGGER_ERROR(logger, "Unknown error trying to encrypt sframe and publish"); + LOGGER_ERROR(logger, "Unknown error trying publish"); return; } } diff --git a/test/qmedia.cpp b/test/qmedia.cpp index 557364b..1452985 100644 --- a/test/qmedia.cpp +++ b/test/qmedia.cpp @@ -187,13 +187,14 @@ class QPublisherTestDelegate : public qmedia::QPublisherDelegate int removePubByNamespace(const quicr::Namespace& /* quicrNamespace */) { return 0; } }; -static qmedia::QController make_controller(std::shared_ptr collector) +static qmedia::QController make_controller(std::shared_ptr collector, bool encrypt = true) { const auto sub = std::make_shared(std::move(collector)); const auto pub = std::make_shared(); const auto logger = std::make_shared("QTest", "QTEST"); logger->SetLogLevel("DEBUG"); - return {sub, pub, logger}; + const auto suite = encrypt ? std::optional(qmedia::Default_Cipher_Suite) : std::nullopt; + return qmedia::QController(sub, pub, logger, true, suite); } static qmedia::manifest::MediaStream make_media_stream(uint32_t endpoint_id) @@ -241,7 +242,7 @@ static std::set test_data(uint8_t label) return out; } -TEST_CASE("Two-party session") +static void two_party_session(bool encrypt) { // Start up a local relay const auto relay = LocalhostRelay(); @@ -249,10 +250,10 @@ TEST_CASE("Two-party session") // Instantiate two QControllers auto collector_a = std::make_shared(); - auto controller_a = make_controller(collector_a); + auto controller_a = make_controller(collector_a, encrypt); auto collector_b = std::make_shared(); - auto controller_b = make_controller(collector_b); + auto controller_b = make_controller(collector_b, encrypt); // Connect to the relay qtransport::TransportConfig config{ @@ -312,6 +313,12 @@ TEST_CASE("Two-party session") // REQUIRE(collector_a->qualityProfile() == "opus,br=6"); } +TEST_CASE("Two-party session") +{ + two_party_session(true); + two_party_session(false); +} + TEST_CASE("Fetch Switching Sets & Subscriptions") { // Setup.