From d1bcfcefdb325ba824ac20d4e4c536950a6ce871 Mon Sep 17 00:00:00 2001 From: "Justin R. Wilson" Date: Wed, 8 Nov 2023 15:05:12 -0600 Subject: [PATCH 1/2] Publish/subscribe actions do not have a validity section Problem ------- In a collaborative DDS Domain, such as those possible with the DDS Permissions Manager, different organization will contribute different publish/subscribe actions for the same application (subject). These publish/subscribe actions will be valid in different timeframes. Solution -------- A desirable solution would be to have a permissions document with multiple grants referring to the same application. However, the "default action" in a DDS Security grant effectively means that an application (subject) can have a single grant. Thus, all of the publish/subscribe actions for an application must be combined under a single grant. This means that either 1) the validity for the grant must be crafted carefully from the validity assigned to each action or 2) each action needs a validity interval. Crafting the overall grant validity is possible but can lead to pathological behavior with respect to renewing a permissions document. That is, the application must new the permissions document at every point in time (not_before or not_after) for every publish/subscribe action. To avoid this complexity, each publish/subscribe action is given a validity which is independent from the grant validity. This is an OpenDDS extension to DDS Security. --- dds/DCPS/DisjointSequence.h | 26 +++- .../security/AccessControl/Permissions.cpp | 36 +++++ dds/DCPS/security/AccessControl/Permissions.h | 26 +++- .../security/AccessControlBuiltInImpl.cpp | 60 +++++--- dds/DCPS/security/AccessControlBuiltInImpl.h | 5 + dds/DCPS/security/SSL/SignedDocument.h | 7 +- docs/devguide/dds_security.rst | 16 ++ docs/news.d/action-validity.rst | 5 + ...ons_test_participant_01_ActionValidity.xml | 40 +++++ ...t_participant_01_ActionValidity_signed.p7s | 95 ++++++++++++ tests/unit-tests/UnitTests.mpc | 2 + .../unit-tests/dds/DCPS/DisjointSequence.cpp | 26 ++++ .../security/AccessControl/Permissions.cpp | 99 +++++++++++++ .../security/AccessControlBuiltInImpl.cpp | 140 ++++++++++++++++++ .../dds/DCPS/security/SSL/SignedDocument.cpp | 7 + 15 files changed, 558 insertions(+), 32 deletions(-) create mode 100644 docs/news.d/action-validity.rst create mode 100644 tests/security/permissions/permissions_test_participant_01_ActionValidity.xml create mode 100644 tests/security/permissions/permissions_test_participant_01_ActionValidity_signed.p7s create mode 100644 tests/unit-tests/dds/DCPS/security/AccessControl/Permissions.cpp diff --git a/dds/DCPS/DisjointSequence.h b/dds/DCPS/DisjointSequence.h index ee7fe902711..a82ec02642a 100644 --- a/dds/DCPS/DisjointSequence.h +++ b/dds/DCPS/DisjointSequence.h @@ -165,13 +165,25 @@ class OpenDDS_Dcps_Export DisjointSequence { return; } - const T above = upper + 1; - for (typename Container::iterator i = lower_bound_i(lower - 1); - i != ranges_.end() && (i->first <= above || i->second <= above); - /* iterate in loop because of removal */) { - lower = (std::min)(lower, i->first); - upper = (std::max)(upper, i->second); - ranges_.erase(i++); + typename Container::iterator pos = lower_bound_i(lower); + if (pos != ranges_.begin()) { + std::advance(pos, -1); + } + + typename Container::iterator limit = lower_bound_i(upper); + if (limit != ranges_.end()) { + std::advance(limit, 1); + } + + while (pos != limit) { + if ((upper < pos->first || lower > pos->second) && + !(static_cast(pos->first - 1) == upper || static_cast(pos->second + 1) == lower)) { + ++pos; + } else { + lower = (std::min)(lower, pos->first); + upper = (std::max)(upper, pos->second); + ranges_.erase(pos++); + } } ranges_.insert(TPair(lower, upper)); diff --git a/dds/DCPS/security/AccessControl/Permissions.cpp b/dds/DCPS/security/AccessControl/Permissions.cpp index cb5cc428201..f77e17982fa 100644 --- a/dds/DCPS/security/AccessControl/Permissions.cpp +++ b/dds/DCPS/security/AccessControl/Permissions.cpp @@ -166,6 +166,29 @@ int Permissions::load(const SSL::SignedDocument& doc) action.partitions.push_back(to_string(partitionNode)); } } + } else if (ACE_TEXT("validity") == XStr(topicListNode->getNodeName())) { + const xercesc::DOMNodeList* validityNodes = topicListNode->getChildNodes(); + for (XMLSize_t vn = 0, vn_len = validityNodes->getLength(); vn < vn_len; ++vn) { + const xercesc::DOMNode* validityNode = validityNodes->item(vn); + const XStr v_tag = validityNode->getNodeName(); + if (v_tag == ACE_TEXT("not_before")) { + if (!parse_time(validityNode->getTextContent(), action.validity.not_before)) { + if (security_debug.access_error) { + ACE_ERROR((LM_ERROR, "(%P|%t) ERROR: {access_error} Permissions::load: " + "invalid datetime in not_before\n")); + } + return -1; + } + } else if (v_tag == ACE_TEXT("not_after")) { + if (!parse_time(validityNode->getTextContent(), action.validity.not_after)) { + if (security_debug.access_error) { + ACE_ERROR((LM_ERROR, "(%P|%t) ERROR: {access_error} Permissions::load: " + "invalid datetime in not_after\n")); + } + return -1; + } + } + } } } @@ -276,6 +299,19 @@ bool Permissions::Action::partitions_match(const DDS::StringSeq& entity_partitio return allow_or_deny == ALLOW; } +bool Permissions::Action::valid(time_t now_utc) const +{ + if (validity.not_before != 0 && now_utc < validity.not_before) { + return false; + } + + if (validity.not_after != 0 && now_utc > validity.not_after) { + return false; + } + + return true; +} + } } diff --git a/dds/DCPS/security/AccessControl/Permissions.h b/dds/DCPS/security/AccessControl/Permissions.h index 5d23855bdb5..0063f0af2e7 100644 --- a/dds/DCPS/security/AccessControl/Permissions.h +++ b/dds/DCPS/security/AccessControl/Permissions.h @@ -26,7 +26,7 @@ OPENDDS_BEGIN_VERSIONED_NAMESPACE_DECL namespace OpenDDS { namespace Security { -struct Permissions : DCPS::RcObject { +struct OpenDDS_Security_Export Permissions : DCPS::RcObject { typedef DCPS::RcHandle shared_ptr; enum AllowDeny_t { @@ -42,15 +42,37 @@ struct Permissions : DCPS::RcObject { struct Validity_t { time_t not_before; time_t not_after; + + Validity_t() + : not_before(0) + , not_after(0) + {} + + Validity_t(time_t nb, time_t na) + : not_before(nb) + , not_after(na) + {} + + bool operator==(const Validity_t& other) const + { + return not_before == other.not_before && not_after == other.not_after; + } + + bool operator!=(const Validity_t& other) const + { + return not_before != other.not_before || not_after != other.not_after; + } }; - struct Action { + struct OpenDDS_Security_Export Action { PublishSubscribe_t ps_type; std::vector topics; std::vector partitions; + Validity_t validity; bool topic_matches(const char* topic) const; bool partitions_match(const DDS::StringSeq& entity_partitions, AllowDeny_t allow_or_deny) const; + bool valid(time_t now_utc) const; }; typedef std::vector Actions; diff --git a/dds/DCPS/security/AccessControlBuiltInImpl.cpp b/dds/DCPS/security/AccessControlBuiltInImpl.cpp index 48579259a62..c0864b8c332 100644 --- a/dds/DCPS/security/AccessControlBuiltInImpl.cpp +++ b/dds/DCPS/security/AccessControlBuiltInImpl.cpp @@ -396,15 +396,17 @@ ::CORBA::Boolean AccessControlBuiltInImpl::check_create_datawriter( return CommonUtilities::set_security_error(ex, -1, 0, "AccessControlBuiltInImpl::check_create_datawriter: Permissions grant not found"); } - if (!validate_date_time(grant->validity, ex)) { + const time_t now_utc = utc_now(); + if (!validate_date_time(grant->validity, now_utc, ex)) { return false; } - if (!search_permissions(topic_name, domain_id, partition, Permissions::PUBLISH, *grant, ex)) { + time_t expiration_time = grant->validity.not_after; + if (!search_permissions(topic_name, domain_id, partition, Permissions::PUBLISH, *grant, now_utc, expiration_time, ex)) { return false; } - make_task(local_rp_task_)->insert(permissions_handle, grant->validity.not_after); + make_task(local_rp_task_)->insert(permissions_handle, expiration_time); return true; } @@ -459,15 +461,17 @@ ::CORBA::Boolean AccessControlBuiltInImpl::check_create_datareader( return CommonUtilities::set_security_error(ex, -1, 0, "AccessControlBuiltInImpl::check_create_datareader: Permissions grant not found"); } - if (!validate_date_time(grant->validity, ex)) { + const time_t now_utc = utc_now(); + if (!validate_date_time(grant->validity, now_utc, ex)) { return false; } - if (!search_permissions(topic_name, domain_id, partition, Permissions::SUBSCRIBE, *grant, ex)) { + time_t expiration_time = grant->validity.not_after; + if (!search_permissions(topic_name, domain_id, partition, Permissions::SUBSCRIBE, *grant, now_utc, expiration_time, ex)) { return false; } - make_task(local_rp_task_)->insert(permissions_handle, grant->validity.not_after); + make_task(local_rp_task_)->insert(permissions_handle, expiration_time); return true; } @@ -525,7 +529,8 @@ ::CORBA::Boolean AccessControlBuiltInImpl::check_create_topic( return CommonUtilities::set_security_error(ex, -1, 0, "AccessControlBuiltInImpl::check_create_topic: grant not found"); } - if (!validate_date_time(grant->validity, ex)) { + const time_t now_utc = utc_now(); + if (!validate_date_time(grant->validity, now_utc, ex)) { return false; } @@ -731,17 +736,19 @@ ::CORBA::Boolean AccessControlBuiltInImpl::check_remote_datawriter( return CommonUtilities::set_security_error(ex, -1, 0, "AccessControlBuiltInImpl::check_remote_datawriter: Permissions grant not found"); } - if (!validate_date_time(grant->validity, ex)) { + const time_t now_utc = utc_now(); + if (!validate_date_time(grant->validity, now_utc, ex)) { return false; } + time_t expiration_time = grant->validity.not_after; if (!search_permissions(publication_data.base.base.topic_name, domain_id, publication_data.base.base.partition, Permissions::PUBLISH, - *grant, ex)) { + *grant, now_utc, expiration_time, ex)) { return false; } - make_task(remote_rp_task_)->insert(permissions_handle, grant->validity.not_after); + make_task(remote_rp_task_)->insert(permissions_handle, expiration_time); return true; } @@ -791,17 +798,19 @@ ::CORBA::Boolean AccessControlBuiltInImpl::check_remote_datareader( return CommonUtilities::set_security_error(ex, -1, 0, "AccessControlBuiltInImpl::check_remote_datareader: Permissions grant not found"); } - if (!validate_date_time(grant->validity, ex)) { + const time_t now_utc = utc_now(); + if (!validate_date_time(grant->validity, now_utc, ex)) { return false; } + time_t expiration_time = grant->validity.not_after; if (!search_permissions(subscription_data.base.base.topic_name, domain_id, subscription_data.base.base.partition, Permissions::SUBSCRIBE, - *grant, ex)) { + *grant, now_utc, expiration_time, ex)) { return false; } - make_task(remote_rp_task_)->insert(permissions_handle, grant->validity.not_after); + make_task(remote_rp_task_)->insert(permissions_handle, expiration_time); return true; } @@ -892,7 +901,8 @@ ::CORBA::Boolean AccessControlBuiltInImpl::check_remote_topic( return CommonUtilities::set_security_error(ex, -1, 0, "AccessControlBuiltInImpl::check_remote_topic: grant not found"); } - if (!validate_date_time(grant->validity, ex)) { + const time_t now_utc = utc_now(); + if (!validate_date_time(grant->validity, now_utc, ex)) { return false; } @@ -1298,8 +1308,17 @@ AccessControlBuiltInImpl::make_task(RevokePermissionsTask_rch& task) return task; } +time_t AccessControlBuiltInImpl::utc_now() +{ + // Get the current time as UTC + const time_t now = std::time(0); + std::tm* const now_utc_tm = std::gmtime(&now); + return std::mktime(now_utc_tm); +} + bool AccessControlBuiltInImpl::validate_date_time( const Permissions::Validity_t& validity, + time_t now_utc, DDS::Security::SecurityException& ex) { if (validity.not_before == 0) { @@ -1314,11 +1333,6 @@ bool AccessControlBuiltInImpl::validate_date_time( return false; } - // Get the current time as UTC - const time_t now = std::time(0); - std::tm* const now_utc_tm = std::gmtime(&now); - const time_t now_utc = std::mktime(now_utc_tm); - if (now_utc < validity.not_before) { CommonUtilities::set_security_error(ex, -1, 0, "AccessControlBuiltInImpl::validate_date_time: Permissions grant hasn't started yet."); @@ -1480,6 +1494,8 @@ bool AccessControlBuiltInImpl::search_permissions( const DDS::PartitionQosPolicy& partition, const Permissions::PublishSubscribe_t pub_or_sub, const Permissions::Grant& grant, + time_t now_utc, + time_t& expiration_time, DDS::Security::SecurityException& ex) { for (Permissions::Rules::const_iterator rit = grant.rules.begin(); rit != grant.rules.end(); ++rit) { @@ -1487,8 +1503,12 @@ bool AccessControlBuiltInImpl::search_permissions( for (Permissions::Actions::const_iterator ait = rit->actions.begin(); ait != rit->actions.end(); ++ait) { if (ait->ps_type == pub_or_sub && ait->topic_matches(topic_name) && - ait->partitions_match(partition.name, rit->ad_type)) { + ait->partitions_match(partition.name, rit->ad_type) && + ait->valid(now_utc)) { if (rit->ad_type == Permissions::ALLOW) { + if (ait->validity.not_after != 0) { + expiration_time = std::min(expiration_time, ait->validity.not_after); + } return true; } else { return CommonUtilities::set_security_error(ex, -1, 0, "AccessControlBuiltInImpl: DENY rule matched"); diff --git a/dds/DCPS/security/AccessControlBuiltInImpl.h b/dds/DCPS/security/AccessControlBuiltInImpl.h index 9c7fcff1622..69284469e7a 100644 --- a/dds/DCPS/security/AccessControlBuiltInImpl.h +++ b/dds/DCPS/security/AccessControlBuiltInImpl.h @@ -295,7 +295,10 @@ class OpenDDS_Security_Export AccessControlBuiltInImpl RevokePermissionsTask_rch& make_task(RevokePermissionsTask_rch& task); + static time_t utc_now(); + bool validate_date_time(const Permissions::Validity_t& validity, + time_t now_utc, DDS::Security::SecurityException& ex); bool get_sec_attributes(DDS::Security::PermissionsHandle permissions_handle, @@ -310,6 +313,8 @@ class OpenDDS_Security_Export AccessControlBuiltInImpl const DDS::PartitionQosPolicy& partition, Permissions::PublishSubscribe_t pub_or_sub, const Permissions::Grant& grant, + time_t now_utc, + time_t& expiration_time, DDS::Security::SecurityException& ex); void parse_class_id(const std::string& class_id, diff --git a/dds/DCPS/security/SSL/SignedDocument.h b/dds/DCPS/security/SSL/SignedDocument.h index 5fab72bfabd..3b61ee3a682 100644 --- a/dds/DCPS/security/SSL/SignedDocument.h +++ b/dds/DCPS/security/SSL/SignedDocument.h @@ -32,10 +32,11 @@ class OpenDDS_Security_Export SignedDocument { bool load(const std::string& uri, DDS::Security::SecurityException& ex); bool verify(const Certificate& ca); - const DDS::OctetSeq& original() const {return original_;} - const std::string& content() const {return content_;} + const DDS::OctetSeq& original() const { return original_; } + void content(const std::string& value) { content_ = value; } + const std::string& content() const { return content_; } bool verified() const { return verified_; } - const std::string& filename() const {return filename_;} + const std::string& filename() const { return filename_; } bool operator==(const SignedDocument& other) const; diff --git a/docs/devguide/dds_security.rst b/docs/devguide/dds_security.rst index 375ee8c2b62..f1f02722462 100644 --- a/docs/devguide/dds_security.rst +++ b/docs/devguide/dds_security.rst @@ -860,6 +860,19 @@ When no data tag list is given for an "allow" PSR rule, the empty set of data ta For "deny" PSR rules, the rule will apply if the associated DDS entity is using any of the data tags listed. When no data tag list is given for a "deny" PSR rule, the set of "all possible tags" is used as the default value. +.. _dds_security--psr-validity: + +validity +"""""""" + +.. attention:: + + This is an OpenDDS extension. + +This structure defines the validity of a particular publish or subscribe action. +Thus, it is possible to declare that an action is valid for some subset of the grant's validity. +The format for `validity` is the same as :ref:`dds_security--validity`. + .. _dds_security--default_rule: default_rule @@ -975,3 +988,6 @@ The following DDS Security features are not implemented in OpenDDS. #. Signing (without encrypting) at the payload level, see :omgissue:`DDSSEC12-59` +The following features are OpenDDS extensions: + +#. Validity of publish/subscribe actions :ref:`dds_security--psr-validity`. diff --git a/docs/news.d/action-validity.rst b/docs/news.d/action-validity.rst new file mode 100644 index 00000000000..5dc5b020622 --- /dev/null +++ b/docs/news.d/action-validity.rst @@ -0,0 +1,5 @@ +.. news-prs: 4344 + +.. news-start-section: Additions +- It is now possible to specify the validity for individual publish/subscribe actions in DDS Security Permission documents. This is an OpenDDS extension. +.. news-end-section diff --git a/tests/security/permissions/permissions_test_participant_01_ActionValidity.xml b/tests/security/permissions/permissions_test_participant_01_ActionValidity.xml new file mode 100644 index 00000000000..2de754fe28c --- /dev/null +++ b/tests/security/permissions/permissions_test_participant_01_ActionValidity.xml @@ -0,0 +1,40 @@ + + + + + CN=Ozzie Ozmann,O=Internet Widgits Pty Ltd,ST=Some-State,C=AU + + + 2009-09-15T01:00:00 + 2129-09-15T01:00:00 + + + + 0 + + + + Triangle + + + + 2009-09-15T01:00:00 + 2010-09-15T01:00:00 + + + + + Square + Circle + + + + 2009-09-15T01:00:00 + 2010-09-15T01:00:00 + + + + DENY + + + diff --git a/tests/security/permissions/permissions_test_participant_01_ActionValidity_signed.p7s b/tests/security/permissions/permissions_test_participant_01_ActionValidity_signed.p7s new file mode 100644 index 00000000000..af35d602e8b --- /dev/null +++ b/tests/security/permissions/permissions_test_participant_01_ActionValidity_signed.p7s @@ -0,0 +1,95 @@ +MIME-Version: 1.0 +Content-Type: multipart/signed; protocol="application/x-pkcs7-signature"; micalg="sha-256"; boundary="----B8C4A07EC249B6EDF5ED3FFA7676BE09" + +This is an S/MIME signed message + +------B8C4A07EC249B6EDF5ED3FFA7676BE09 +Content-Type: text/plain + + + + + + CN=Ozzie Ozmann,O=Internet Widgits Pty Ltd,ST=Some-State,C=AU + + + 2009-09-15T01:00:00 + 2129-09-15T01:00:00 + + + + 0 + + + + Triangle + + + + 2009-09-15T01:00:00 + 2010-09-15T01:00:00 + + + + + Square + Circle + + + + 2009-09-15T01:00:00 + 2010-09-15T01:00:00 + + + + DENY + + + + +------B8C4A07EC249B6EDF5ED3FFA7676BE09 +Content-Type: application/x-pkcs7-signature; name="smime.p7s" +Content-Transfer-Encoding: base64 +Content-Disposition: attachment; filename="smime.p7s" + +MIIHEAYJKoZIhvcNAQcCoIIHATCCBv0CAQExDzANBglghkgBZQMEAgEFADALBgkq +hkiG9w0BBwGgggP4MIID9DCCAtwCCQCkjopvwK438jANBgkqhkiG9w0BAQsFADCB +uzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAk1PMRQwEgYDVQQHDAtTYWludCBMb3Vp +czEvMC0GA1UECgwmT2JqZWN0IENvbXB1dGluZyAoVGVzdCBQZXJtaXNzaW9ucyBD +QSkxLzAtBgNVBAMMJk9iamVjdCBDb21wdXRpbmcgKFRlc3QgUGVybWlzc2lvbnMg +Q0EpMScwJQYJKoZIhvcNAQkBFhhpbmZvQG9iamVjdGNvbXB1dGluZy5jb20wHhcN +MTgwNjEzMDQyMDEzWhcNMjgwNjEwMDQyMDEzWjCBuzELMAkGA1UEBhMCVVMxCzAJ +BgNVBAgMAk1PMRQwEgYDVQQHDAtTYWludCBMb3VpczEvMC0GA1UECgwmT2JqZWN0 +IENvbXB1dGluZyAoVGVzdCBQZXJtaXNzaW9ucyBDQSkxLzAtBgNVBAMMJk9iamVj +dCBDb21wdXRpbmcgKFRlc3QgUGVybWlzc2lvbnMgQ0EpMScwJQYJKoZIhvcNAQkB +FhhpbmZvQG9iamVjdGNvbXB1dGluZy5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQCd3osCHskwiWPkgQ+FiUJEPj9lGAV6gqnG9XcTHPzOsv+hrWck +lq4WcTcu5ERxjvwzrfB9MV2Jj1mhnAQfp0sIuTJe4QoXigyf0IyezsSA1oeofkJu +BlA6cR+5ATzfNEcJJG3sVaEaa0L92CXb147LczMMY+6I/jD9H/Kamoph1hCgdh2l +GnYN97ETMxX5qINthO17/qZ55R+H5nE2Op1f4Y0LhjKu3WztEjIZeAJDgAksoYRy +nVhfDsshdZWUMSO0jHJGPwEvxwhTsAknWdthuE/xgZQqDP3aXj3MFJcZkydS+8xv +nX0cuHsr/7MqVK0oOmjWS7pi7cMBY9DtB3KVAgMBAAEwDQYJKoZIhvcNAQELBQAD +ggEBAE9QWa1xNjxLWIw88eVrQxOBCIlqCkAiTx2pAurEdiDtz8ZQdDMQQmoAuppT +6LWVVtOWc1bP3a+IHBolNAimXOm+B9fMSvQnqRbriJZ8Hc5+Y5TXlJ3iyqJDEyPi +WhUFLfQfnjE8hRL5oKPkhk2gRC6K5x+10cZMclgEmZONANtAuSJurMhwgqLxwgGw +51aIpL6LTxtdZ33LIPM8AN51Tgj5t2VM/49iNq9HdqAl7VQuyHEc/eCAIp7p69nq +cpS9VBJAJoHN8lmDDHYxM+pYtQAgmBKLBxTyDrgJZ+3j3FVOp0orRxarE3XjJ+0b +IVnO6yhjunPOpgsyEcxH9/7Enm8xggLcMIIC2AIBATCByTCBuzELMAkGA1UEBhMC +VVMxCzAJBgNVBAgMAk1PMRQwEgYDVQQHDAtTYWludCBMb3VpczEvMC0GA1UECgwm +T2JqZWN0IENvbXB1dGluZyAoVGVzdCBQZXJtaXNzaW9ucyBDQSkxLzAtBgNVBAMM +Jk9iamVjdCBDb21wdXRpbmcgKFRlc3QgUGVybWlzc2lvbnMgQ0EpMScwJQYJKoZI +hvcNAQkBFhhpbmZvQG9iamVjdGNvbXB1dGluZy5jb20CCQCkjopvwK438jANBglg +hkgBZQMEAgEFAKCB5DAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3 +DQEJBTEPFw0yMzExMDgxODI4MjBaMC8GCSqGSIb3DQEJBDEiBCB85k/RjCwAyP9D +1vTvMmJYtjlFw1zMd28EGP6HLwqAMzB5BgkqhkiG9w0BCQ8xbDBqMAsGCWCGSAFl +AwQBKjALBglghkgBZQMEARYwCwYJYIZIAWUDBAECMAoGCCqGSIb3DQMHMA4GCCqG +SIb3DQMCAgIAgDANBggqhkiG9w0DAgIBQDAHBgUrDgMCBzANBggqhkiG9w0DAgIB +KDANBgkqhkiG9w0BAQEFAASCAQBgluEPD/JBTBXY9S/DrfrJwJGnupybXf1DxqU+ +WToR3in64rabxlPsyZGVzTy6QkhlGGzJUTrASluTvNepqnaO5tHbj1binI71d8qi +qu0G9zDUkxUlUoD6jTyUkoK+XsdIQOKRT4CTxzwyk6r4biIzqYjU46vYIFSW7xdE +LDIjxi+PouCToPKB2GgG/iLoniidyzpnKgKcheQZMpx//Nlimn4lwWp6iNIiC+S4 +q9DXVJ6rI7Cf+t2MHhIxYClSyUAO2wxDeQ7la1qb+jkxklsKg2XzksQudmnSbl1I +nfjaUi9UsdSSRLvJ8yr+MoVfieeKQpJSSrnYeAYIIShiGfxP + +------B8C4A07EC249B6EDF5ED3FFA7676BE09-- + diff --git a/tests/unit-tests/UnitTests.mpc b/tests/unit-tests/UnitTests.mpc index 7e5308a6332..c68a1ba7149 100644 --- a/tests/unit-tests/UnitTests.mpc +++ b/tests/unit-tests/UnitTests.mpc @@ -34,6 +34,8 @@ project(UnitTests): opendds_unit_test, googlemock, msvc_bigobj, dcpsexe, dcps_tr dds/DCPS dds/DCPS/RTPS dds/DCPS/security + dds/DCPS/security/AccessControl + dds/DCPS/security/Authentication dds/DCPS/security/SSL dds/DCPS/transport/framework dds/DCPS/transport/rtps_udp diff --git a/tests/unit-tests/dds/DCPS/DisjointSequence.cpp b/tests/unit-tests/dds/DCPS/DisjointSequence.cpp index 41a21f63fce..b35bb0bfcaf 100644 --- a/tests/unit-tests/dds/DCPS/DisjointSequence.cpp +++ b/tests/unit-tests/dds/DCPS/DisjointSequence.cpp @@ -1099,3 +1099,29 @@ TEST(dds_DCPS_DisjointSequence, OrderedRanges_insert_ranges) EXPECT_TRUE(ir.has(11, 13)); EXPECT_EQ(ir.size(), 2UL); } + +typedef DisjointSequence::OrderedRanges CharRanges; +typedef DisjointSequence::OrderedRanges UCharRanges; + +TEST(dds_DCPS_DisjointSequence, OrderedRanges_add_overflow) +{ + { + CharRanges r; + r.add(0, 0); + EXPECT_TRUE(r.has(0, 0)); + r.add(-128, -1); + EXPECT_TRUE(r.has(-128, 0)); + r.add(1, 127); + EXPECT_TRUE(r.has(-128, -127)); + } + + { + UCharRanges r; + r.add(128, 128); + EXPECT_TRUE(r.has(128, 128)); + r.add(0, 127); + EXPECT_TRUE(r.has(0, 128)); + r.add(129, 255); + EXPECT_TRUE(r.has(0, 255)); + } +} diff --git a/tests/unit-tests/dds/DCPS/security/AccessControl/Permissions.cpp b/tests/unit-tests/dds/DCPS/security/AccessControl/Permissions.cpp new file mode 100644 index 00000000000..17b109a6afe --- /dev/null +++ b/tests/unit-tests/dds/DCPS/security/AccessControl/Permissions.cpp @@ -0,0 +1,99 @@ +#ifdef OPENDDS_SECURITY + +#include +#include + +#include + +using namespace OpenDDS::Security; + +TEST(dds_DCPS_security_AccessControl_Permissions, Permissions_Validity_t_ctor) +{ + { + Permissions::Validity_t v; + EXPECT_EQ(v.not_before, 0); + EXPECT_EQ(v.not_after, 0); + } + { + Permissions::Validity_t v(1,2); + EXPECT_EQ(v.not_before, 1); + EXPECT_EQ(v.not_after, 2); + } + { + Permissions::Validity_t v1(1,2); + Permissions::Validity_t v2(1,2); + Permissions::Validity_t v3(3,4); + EXPECT_EQ(v1, v2); + EXPECT_NE(v1, v3); + EXPECT_NE(v2, v3); + } +} + +TEST(dds_DCPS_security_AccessControl_Permissions, Permissions_Action_valid) +{ + Permissions::Action a; + a.validity.not_before = 1; + a.validity.not_after = 3; + EXPECT_FALSE(a.valid(0)); + EXPECT_TRUE(a.valid(1)); + EXPECT_TRUE(a.valid(2)); + EXPECT_TRUE(a.valid(3)); + EXPECT_FALSE(a.valid(4)); +} + +TEST(dds_DCPS_security_AccessControl_Permissions, Permissions_load) +{ + Permissions p; + OpenDDS::Security::SSL::SignedDocument sd; + sd.content( + "" + "" + " " + " " + " CN=Ozzie Ozmann,O=Internet Widgits Pty Ltd,ST=Some-State,C=AU" + " " + " 2015-09-15T01:00:00" + " 2025-09-15T01:00:00" + " " + " " + " " + " 0" + " " + " " + " " + " *" + " " + " " + " 2015-10-15T01:00:00" + " 2025-10-15T01:00:00" + " " + " " + " " + " DENY" + " " + " " + "" + ); + EXPECT_EQ(p.load(sd), 0); + ASSERT_EQ(p.grants_.size(), 1U); + Permissions::Grant_rch grant = p.grants_[0]; + EXPECT_EQ(grant->name, "TheGrant"); + EXPECT_EQ(grant->subject, OpenDDS::Security::SSL::SubjectName("CN=Ozzie Ozmann,O=Internet Widgits Pty Ltd,ST=Some-State,C=AU")); + EXPECT_EQ(grant->validity, Permissions::Validity_t(1442278800, 1757898000)); + EXPECT_EQ(grant->default_permission, Permissions::DENY); + ASSERT_EQ(grant->rules.size(), 1); + const Permissions::Rule& rule = grant->rules[0]; + EXPECT_EQ(rule.ad_type, Permissions::ALLOW); + ASSERT_EQ(rule.domains.size(), 1); + EXPECT_TRUE(rule.domains.has(0)); + EXPECT_FALSE(rule.domains.has(1)); + ASSERT_EQ(rule.actions.size(), 1); + const Permissions::Action& action = rule.actions[0]; + EXPECT_EQ(action.ps_type, Permissions::PUBLISH); + ASSERT_EQ(action.topics.size(), 1); + EXPECT_EQ(action.topics[0], "*"); + EXPECT_TRUE(action.partitions.empty()); + EXPECT_EQ(action.validity, Permissions::Validity_t(1444870800, 1760490000)); +} + +#endif // OPENDDS_SECURITY diff --git a/tests/unit-tests/dds/DCPS/security/AccessControlBuiltInImpl.cpp b/tests/unit-tests/dds/DCPS/security/AccessControlBuiltInImpl.cpp index 396909caafd..0a1ad6aead6 100644 --- a/tests/unit-tests/dds/DCPS/security/AccessControlBuiltInImpl.cpp +++ b/tests/unit-tests/dds/DCPS/security/AccessControlBuiltInImpl.cpp @@ -358,6 +358,10 @@ class dds_DCPS_security_AccessControlBuiltInImpl : public Test perm_date_p7s_.value = "file:../security/permissions/permissions_test_participant_01_NotBefore_signed.p7s"; perm_date_p7s_.propagate = false; + perm_action_validity_p7s_.name = DDS::Security::Properties::AccessPermissions; + perm_action_validity_p7s_.value = "file:../security/permissions/permissions_test_participant_01_ActionValidity_signed.p7s"; + perm_action_validity_p7s_.propagate = false; + perm_dateoffset_p7s_.name = DDS::Security::Properties::AccessPermissions; perm_dateoffset_p7s_.value = "file:../security/permissions/permissions_test_participant_01_NotBeforeOffset_signed.p7s"; perm_dateoffset_p7s_.propagate = false; @@ -476,6 +480,7 @@ class dds_DCPS_security_AccessControlBuiltInImpl : public Test Property_t perm_allowall_p7s_, perm_topic_p7s_, perm_topic2_p7s_; Property_t perm_date_p7s_, perm_dateoffset_p7s_; Property_t perm_parts_p7s_, perm_two_partitions_p7s_; + Property_t perm_action_validity_p7s_; private: @@ -686,6 +691,34 @@ TEST_F(dds_DCPS_security_AccessControlBuiltInImpl, check_create_datawriter_date_ ex)); } +TEST_F(dds_DCPS_security_AccessControlBuiltInImpl, check_create_datawriter_action_validity_Fail) +{ + ::DDS::Security::DomainId_t domain_id = 0; + const char * topic_name = "Triangle"; + ::DDS::DataWriterQos qos; + ::DDS::PartitionQosPolicy partition; + ::DDS::Security::DataTags data_tag; + ::DDS::Security::SecurityException ex; + + set_up_service_participant(); + add_or_replace_property(dds_DCPS_security_AccessControlBuiltInImpl::gov_6_p7s_); + add_or_replace_property(dds_DCPS_security_AccessControlBuiltInImpl::perm_action_validity_p7s_); + + ::DDS::Security::PermissionsHandle out_handle = + get_inst().validate_local_permissions(auth_plugin_.get(), 1, 0, domain_participant_qos_, ex); + + partition.name.length(0); + + EXPECT_FALSE(get_inst().check_create_datawriter( + out_handle, + domain_id, + topic_name, + qos, + partition, + data_tag, + ex)); +} + TEST_F(dds_DCPS_security_AccessControlBuiltInImpl, check_create_datareader_InvalidInput) { ::DDS::Security::PermissionsHandle permissions_handle = 1; @@ -848,6 +881,34 @@ TEST_F(dds_DCPS_security_AccessControlBuiltInImpl, check_create_datareader_defau ex)); } +TEST_F(dds_DCPS_security_AccessControlBuiltInImpl, check_create_datareader_action_validity_Fail) +{ + ::DDS::Security::DomainId_t domain_id = 0; + const char * topic_name = "Square"; + ::DDS::DataReaderQos qos; + ::DDS::PartitionQosPolicy partition; + ::DDS::Security::DataTags data_tag; + ::DDS::Security::SecurityException ex; + + set_up_service_participant(); + add_or_replace_property(dds_DCPS_security_AccessControlBuiltInImpl::gov_6_p7s_); + add_or_replace_property(dds_DCPS_security_AccessControlBuiltInImpl::perm_action_validity_p7s_); + + ::DDS::Security::PermissionsHandle permissions_handle = + get_inst().validate_local_permissions(auth_plugin_.get(), 1, 0, domain_participant_qos_, ex); + + partition.name.length(0); + + EXPECT_FALSE(get_inst().check_create_datareader( + permissions_handle, + domain_id, + topic_name, + qos, + partition, + data_tag, + ex)); +} + TEST_F(dds_DCPS_security_AccessControlBuiltInImpl, check_create_topic_InvalidInput) { ::DDS::Security::PermissionsHandle permissions_handle = 1; @@ -1091,6 +1152,45 @@ TEST_F(dds_DCPS_security_AccessControlBuiltInImpl, check_remote_datawriter_Succe remote_out_handle, domain_id, publication_data, ex)); } +TEST_F(dds_DCPS_security_AccessControlBuiltInImpl, check_remote_datawriter_action_validity_Fail) +{ + ::DDS::Security::AuthenticatedPeerCredentialToken remote_apc_token; + ::DDS::Security::DomainId_t domain_id = 0; + ::DDS::Security::PermissionsToken remote_perm_token; + ::DDS::Security::PublicationBuiltinTopicDataSecure publication_data; + ::DDS::Security::SecurityException ex; + + remote_perm_token.class_id = Expected_Permissions_Token_Class_Id; + remote_perm_token.properties.length(1); + remote_perm_token.properties[0].name = "dds.perm.ca.sn"; + remote_perm_token.properties[0].value = remote_subject_name; + + std::string id(get_file_contents(mock_1_cert_file)); + std::string pf(get_file_contents("../security/permissions/permissions_test_participant_01_ActionValidity_signed.p7s")); + + remote_apc_token.class_id = Expected_Permissions_Cred_Token_Class_Id; + remote_apc_token.binary_properties.length(2); + remote_apc_token.binary_properties[0].name = "c.id"; + remote_apc_token.binary_properties[0].value.length(static_cast(id.size())); + memcpy(remote_apc_token.binary_properties[0].value.get_buffer(), id.c_str(), id.size()); + remote_apc_token.binary_properties[0].propagate = true; + remote_apc_token.binary_properties[1].name = "c.perm"; + remote_apc_token.binary_properties[1].value.length(static_cast(pf.size())); + memcpy(remote_apc_token.binary_properties[1].value.get_buffer(), pf.c_str(), pf.size()); + remote_apc_token.binary_properties[1].propagate = true; + add_or_replace_property(dds_DCPS_security_AccessControlBuiltInImpl::gov_6_p7s_); + get_inst().validate_local_permissions(auth_plugin_.get(), 1, 1, domain_participant_qos_, ex); + + ::DDS::Security::PermissionsHandle remote_out_handle = get_inst().validate_remote_permissions( + auth_plugin_.get(), 1, 2, remote_perm_token, remote_apc_token, ex); + + // In permissions file topic is '*' + publication_data.base.base.topic_name = "Triangle"; + + EXPECT_FALSE(get_inst().check_remote_datawriter( + remote_out_handle, domain_id, publication_data, ex)); +} + TEST_F(dds_DCPS_security_AccessControlBuiltInImpl, check_remote_datareader_InvalidInput) { ::DDS::Security::DomainId_t domain_id = 1; @@ -1143,6 +1243,46 @@ TEST_F(dds_DCPS_security_AccessControlBuiltInImpl, check_remote_datareader_Succe EXPECT_FALSE(relay_only); } +TEST_F(dds_DCPS_security_AccessControlBuiltInImpl, check_remote_datareader_action_validity_Fail) +{ + ::DDS::Security::AuthenticatedPeerCredentialToken remote_apc_token; + ::DDS::Security::DomainId_t domain_id = 0; + ::DDS::Security::PermissionsToken remote_perm_token; + ::DDS::Security::SubscriptionBuiltinTopicDataSecure subscription_data; + ::DDS::Security::SecurityException ex; + bool relay_only = true; + + remote_perm_token.class_id = Expected_Permissions_Token_Class_Id; + remote_perm_token.properties.length(1); + remote_perm_token.properties[0].name = "dds.perm.ca.sn"; + remote_perm_token.properties[0].value = remote_subject_name; + + std::string id(get_file_contents(mock_1_cert_file)); + std::string pf(get_file_contents("../security/permissions/permissions_test_participant_01_ActionValidity_signed.p7s")); + + remote_apc_token.class_id = Expected_Permissions_Cred_Token_Class_Id; + remote_apc_token.binary_properties.length(2); + remote_apc_token.binary_properties[0].name = "c.id"; + remote_apc_token.binary_properties[0].value.length(static_cast(id.size())); + memcpy(remote_apc_token.binary_properties[0].value.get_buffer(), id.c_str(), id.size()); + remote_apc_token.binary_properties[0].propagate = true; + remote_apc_token.binary_properties[1].name = "c.perm"; + remote_apc_token.binary_properties[1].value.length(static_cast(pf.size())); + memcpy(remote_apc_token.binary_properties[1].value.get_buffer(), pf.c_str(), pf.size()); + remote_apc_token.binary_properties[1].propagate = true; + add_or_replace_property(dds_DCPS_security_AccessControlBuiltInImpl::gov_6_p7s_); + get_inst().validate_local_permissions(auth_plugin_.get(), 1, 1, domain_participant_qos_, ex); + + ::DDS::Security::PermissionsHandle remote_out_handle = get_inst().validate_remote_permissions( + auth_plugin_.get(), 1, 2, remote_perm_token, remote_apc_token, ex); + + // In permissions file topic is '*' + subscription_data.base.base.topic_name = "Square"; + + EXPECT_FALSE(get_inst().check_remote_datareader( + remote_out_handle, domain_id, subscription_data, relay_only, ex)); +} + TEST_F(dds_DCPS_security_AccessControlBuiltInImpl, check_remote_topic_InvalidInput) { ::DDS::Security::DomainId_t domain_id = 1; diff --git a/tests/unit-tests/dds/DCPS/security/SSL/SignedDocument.cpp b/tests/unit-tests/dds/DCPS/security/SSL/SignedDocument.cpp index b54c6f8d378..5b1e92529c8 100644 --- a/tests/unit-tests/dds/DCPS/security/SSL/SignedDocument.cpp +++ b/tests/unit-tests/dds/DCPS/security/SSL/SignedDocument.cpp @@ -122,4 +122,11 @@ TEST_F(dds_DCPS_security_SSL_SignedDocument, CopyConstruct) ASSERT_EQ(doc, doc_); } +TEST_F(dds_DCPS_security_SSL_SignedDocument, content) +{ + SignedDocument doc; + doc.content("my content"); + EXPECT_EQ(doc.content(), "my content"); +} + #endif From 2d5bc25e47c5f8738c01c9ad5d823621a93862ff Mon Sep 17 00:00:00 2001 From: Justin Wilson Date: Fri, 17 Nov 2023 13:36:55 -0600 Subject: [PATCH 2/2] Update docs/news.d/action-validity.rst Co-authored-by: Fred Hornsey --- docs/news.d/action-validity.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/news.d/action-validity.rst b/docs/news.d/action-validity.rst index 5dc5b020622..5a3455e0dd5 100644 --- a/docs/news.d/action-validity.rst +++ b/docs/news.d/action-validity.rst @@ -1,5 +1,5 @@ .. news-prs: 4344 .. news-start-section: Additions -- It is now possible to specify the validity for individual publish/subscribe actions in DDS Security Permission documents. This is an OpenDDS extension. +- It is now possible to specify the :ref:`validity for individual publish/subscribe actions ` in DDS Security Permission documents. This is an OpenDDS extension. .. news-end-section