| /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ |
| /* |
| * Copyright (c) 2013-2020 Regents of the University of California. |
| * |
| * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions). |
| * |
| * ndn-cxx library is free software: you can redistribute it and/or modify it under the |
| * terms of the GNU Lesser General Public License as published by the Free Software |
| * Foundation, either version 3 of the License, or (at your option) any later version. |
| * |
| * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY |
| * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A |
| * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. |
| * |
| * You should have received copies of the GNU General Public License and GNU Lesser |
| * General Public License along with ndn-cxx, e.g., in COPYING.md file. If not, see |
| * <http://www.gnu.org/licenses/>. |
| * |
| * See AUTHORS.md for complete list of ndn-cxx authors and contributors. |
| */ |
| |
| #include "ndn-cxx/security/validation-policy-config.hpp" |
| |
| #include "ndn-cxx/security/transform/base64-encode.hpp" |
| #include "ndn-cxx/security/transform/buffer-source.hpp" |
| #include "ndn-cxx/security/transform/stream-sink.hpp" |
| #include "ndn-cxx/util/io.hpp" |
| |
| #include "tests/boost-test.hpp" |
| #include "tests/unit/security/validator-config/common.hpp" |
| #include "tests/unit/security/validator-fixture.hpp" |
| |
| namespace ndn { |
| namespace security { |
| inline namespace v2 { |
| namespace validator_config { |
| namespace tests { |
| |
| using namespace ndn::tests; |
| using namespace ndn::security::v2::tests; |
| |
| BOOST_AUTO_TEST_SUITE(Security) |
| BOOST_AUTO_TEST_SUITE(TestValidationPolicyConfig) |
| |
| BOOST_FIXTURE_TEST_CASE(EmptyConfig, HierarchicalValidatorFixture<ValidationPolicyConfig>) |
| { |
| this->policy.load(ConfigSection{}, "<empty>"); |
| |
| BOOST_CHECK_EQUAL(this->policy.m_isConfigured, true); |
| BOOST_CHECK_EQUAL(this->policy.m_shouldBypass, false); |
| BOOST_CHECK_EQUAL(this->policy.m_dataRules.size(), 0); |
| BOOST_CHECK_EQUAL(this->policy.m_interestRules.size(), 0); |
| |
| Data d("/Security/ValidationPolicyConfig/D"); |
| this->m_keyChain.sign(d, signingByIdentity(this->identity)); |
| VALIDATE_FAILURE(d, "Empty policy should reject everything"); |
| |
| Interest i("/Security/ValidationPolicyConfig/I"); |
| i.setCanBePrefix(false); |
| this->m_keyChain.sign(i, signingByIdentity(this->identity)); |
| VALIDATE_FAILURE(i, "Empty policy should reject everything"); |
| } |
| |
| template<typename Packet> |
| class PacketName; |
| |
| template<> |
| class PacketName<Interest> |
| { |
| public: |
| static std::string |
| getName() |
| { |
| return "interest"; |
| } |
| }; |
| |
| template<> |
| class PacketName<Data> |
| { |
| public: |
| static std::string |
| getName() |
| { |
| return "data"; |
| } |
| }; |
| |
| template<typename PacketType> |
| class ValidationPolicyConfigFixture : public HierarchicalValidatorFixture<ValidationPolicyConfig> |
| { |
| public: |
| ValidationPolicyConfigFixture() |
| : path(boost::filesystem::path(UNIT_TESTS_TMPDIR) / "security" / "validation-policy-config") |
| { |
| boost::filesystem::create_directories(path); |
| baseConfig = R"CONF( |
| rule |
| { |
| id test-rule-id |
| for )CONF" + PacketName<Packet>::getName() + R"CONF( |
| filter |
| { |
| type name |
| name )CONF" + identity.getName().toUri() + R"CONF( |
| relation is-prefix-of |
| } |
| checker |
| { |
| type hierarchical |
| sig-type rsa-sha256 |
| } |
| } |
| )CONF"; |
| } |
| |
| ~ValidationPolicyConfigFixture() |
| { |
| boost::filesystem::remove_all(path); |
| } |
| |
| protected: |
| using Packet = PacketType; |
| |
| const boost::filesystem::path path; |
| std::string baseConfig; |
| }; |
| |
| template<typename PacketType> |
| class LoadStringWithFileAnchor : public ValidationPolicyConfigFixture<PacketType> |
| { |
| public: |
| LoadStringWithFileAnchor() |
| { |
| BOOST_CHECK_EQUAL(this->policy.m_isConfigured, false); |
| |
| this->saveIdentityCert(this->identity, (this->path / "identity.ndncert").string()); |
| this->policy.load(this->baseConfig + R"CONF( |
| trust-anchor |
| { |
| type file |
| file-name "trust-anchor.ndncert" |
| } |
| )CONF", (this->path / "test-config").string()); |
| |
| BOOST_CHECK_EQUAL(this->policy.m_isConfigured, true); |
| BOOST_CHECK_EQUAL(this->policy.m_shouldBypass, false); |
| } |
| }; |
| |
| template<typename PacketType> |
| class LoadFileWithFileAnchor : public ValidationPolicyConfigFixture<PacketType> |
| { |
| public: |
| LoadFileWithFileAnchor() |
| { |
| std::string configFile = (this->path / "config.conf").string(); |
| { |
| std::ofstream config(configFile); |
| config << this->baseConfig << R"CONF( |
| trust-anchor |
| { |
| type file |
| file-name "trust-anchor.ndncert" |
| } |
| )CONF"; |
| } |
| |
| this->saveIdentityCert(this->identity, (this->path / "identity.ndncert").string()); |
| |
| BOOST_CHECK_EQUAL(this->policy.m_isConfigured, false); |
| |
| this->policy.load(configFile); |
| |
| BOOST_CHECK_EQUAL(this->policy.m_isConfigured, true); |
| BOOST_CHECK_EQUAL(this->policy.m_shouldBypass, false); |
| } |
| }; |
| |
| template<typename PacketType> |
| class LoadFileWithMultipleFileAnchors : public ValidationPolicyConfigFixture<PacketType> |
| { |
| public: |
| LoadFileWithMultipleFileAnchors() |
| { |
| std::string configFile = (this->path / "config.conf").string(); |
| { |
| std::ofstream config(configFile); |
| config << this->baseConfig << R"CONF( |
| trust-anchor |
| { |
| type file |
| file-name "identity.ndncert" |
| } |
| trust-anchor |
| { |
| type file |
| file-name "trust-anchor.ndncert" |
| } |
| )CONF"; |
| } |
| |
| this->saveIdentityCert(this->identity, (this->path / "identity.ndncert").string()); |
| |
| BOOST_CHECK_EQUAL(this->policy.m_isConfigured, false); |
| |
| this->policy.load(configFile); |
| |
| BOOST_CHECK_EQUAL(this->policy.m_isConfigured, true); |
| BOOST_CHECK_EQUAL(this->policy.m_shouldBypass, false); |
| } |
| }; |
| |
| template<typename PacketType> |
| class LoadSectionWithFileAnchor : public ValidationPolicyConfigFixture<PacketType> |
| { |
| public: |
| LoadSectionWithFileAnchor() |
| { |
| auto section = makeSection(this->baseConfig + R"CONF( |
| trust-anchor |
| { |
| type file |
| file-name "trust-anchor.ndncert" |
| } |
| )CONF"); |
| |
| this->saveIdentityCert(this->identity, (this->path / "identity.ndncert").string()); |
| |
| BOOST_CHECK_EQUAL(this->policy.m_isConfigured, false); |
| |
| this->policy.load(section, (this->path / "test-config").string()); |
| |
| BOOST_CHECK_EQUAL(this->policy.m_isConfigured, true); |
| BOOST_CHECK_EQUAL(this->policy.m_shouldBypass, false); |
| } |
| }; |
| |
| template<typename PacketType> |
| class LoadStringWithBase64Anchor : public ValidationPolicyConfigFixture<PacketType> |
| { |
| public: |
| LoadStringWithBase64Anchor() |
| { |
| BOOST_CHECK_EQUAL(this->policy.m_isConfigured, false); |
| |
| std::ostringstream os; |
| { |
| using namespace ndn::security::transform; |
| const auto& cert = this->identity.getDefaultKey().getDefaultCertificate().wireEncode(); |
| bufferSource(cert.wire(), cert.size()) >> base64Encode(false) >> streamSink(os); |
| } |
| |
| this->policy.load(this->baseConfig + R"CONF( |
| trust-anchor |
| { |
| type base64 |
| base64-string ")CONF" + os.str() + R"CONF(" |
| } |
| )CONF", (this->path / "test-config").string()); |
| |
| BOOST_CHECK_EQUAL(this->policy.m_isConfigured, true); |
| BOOST_CHECK_EQUAL(this->policy.m_shouldBypass, false); |
| } |
| }; |
| |
| class NoRefresh |
| { |
| public: |
| static std::string |
| getRefreshString() |
| { |
| return ""; |
| } |
| }; |
| |
| class Refresh1h |
| { |
| public: |
| static std::string |
| getRefreshString() |
| { |
| return "refresh 1h"; |
| } |
| |
| static time::milliseconds |
| getRefreshTime() |
| { |
| return 1_h; |
| } |
| }; |
| |
| class Refresh1m |
| { |
| public: |
| static std::string |
| getRefreshString() |
| { |
| return "refresh 1m"; |
| } |
| |
| static time::milliseconds |
| getRefreshTime() |
| { |
| return 1_min; |
| } |
| }; |
| |
| class Refresh1s |
| { |
| public: |
| static std::string |
| getRefreshString() |
| { |
| return "refresh 1s"; |
| } |
| |
| static time::milliseconds |
| getRefreshTime() |
| { |
| return 1_s; |
| } |
| }; |
| |
| template<typename PacketType, typename Refresh = NoRefresh> |
| class LoadStringWithDirAnchor : public ValidationPolicyConfigFixture<PacketType> |
| { |
| public: |
| LoadStringWithDirAnchor() |
| { |
| BOOST_CHECK_EQUAL(this->policy.m_isConfigured, false); |
| |
| boost::filesystem::create_directories(this->path / "keys"); |
| this->saveIdentityCert(this->identity, (this->path / "keys" / "identity.ndncert").string()); |
| |
| this->policy.load(this->baseConfig + R"CONF( |
| trust-anchor |
| { |
| type dir |
| dir keys |
| )CONF" + Refresh::getRefreshString() + R"CONF( |
| } |
| )CONF", (this->path / "test-config").string()); |
| |
| BOOST_CHECK_EQUAL(this->policy.m_isConfigured, true); |
| BOOST_CHECK_EQUAL(this->policy.m_shouldBypass, false); |
| } |
| }; |
| |
| using DataPolicies = boost::mpl::vector<LoadStringWithFileAnchor<Data>, |
| LoadFileWithFileAnchor<Data>, |
| LoadFileWithMultipleFileAnchors<Data>, |
| LoadSectionWithFileAnchor<Data>, |
| LoadStringWithBase64Anchor<Data>, |
| LoadStringWithDirAnchor<Data>, |
| LoadStringWithDirAnchor<Data, Refresh1h>, |
| LoadStringWithDirAnchor<Data, Refresh1m>, |
| LoadStringWithDirAnchor<Data, Refresh1s> |
| >; |
| |
| using InterestPolicies = boost::mpl::vector<LoadStringWithFileAnchor<Interest>, |
| LoadFileWithFileAnchor<Interest>, |
| LoadFileWithMultipleFileAnchors<Interest>, |
| LoadSectionWithFileAnchor<Interest>, |
| LoadStringWithBase64Anchor<Interest>, |
| LoadStringWithDirAnchor<Interest>, |
| LoadStringWithDirAnchor<Interest, Refresh1h>, |
| LoadStringWithDirAnchor<Interest, Refresh1m>, |
| LoadStringWithDirAnchor<Interest, Refresh1s> |
| >; |
| |
| BOOST_FIXTURE_TEST_CASE_TEMPLATE(ValidateData, Policy, DataPolicies, Policy) |
| { |
| BOOST_CHECK_EQUAL(this->policy.m_dataRules.size(), 1); |
| BOOST_CHECK_EQUAL(this->policy.m_interestRules.size(), 0); |
| |
| using Packet = typename Policy::Packet; |
| Packet unsignedPacket("/Security/ValidatorFixture/Sub1/Sub2/Packet"); |
| |
| Packet packet = unsignedPacket; |
| VALIDATE_FAILURE(packet, "Unsigned"); |
| |
| packet = unsignedPacket; |
| this->m_keyChain.sign(packet, signingWithSha256()); |
| VALIDATE_FAILURE(packet, "Policy doesn't accept Sha256Digest signature"); |
| |
| packet = unsignedPacket; |
| this->m_keyChain.sign(packet, signingByIdentity(this->identity)); |
| VALIDATE_SUCCESS(packet, "Should get accepted, as signed by the anchor"); |
| |
| packet = unsignedPacket; |
| this->m_keyChain.sign(packet, signingByIdentity(this->subIdentity)); |
| VALIDATE_SUCCESS(packet, "Should get accepted, as signed by the policy-compliant cert"); |
| |
| packet = unsignedPacket; |
| this->m_keyChain.sign(packet, signingByIdentity(this->otherIdentity)); |
| VALIDATE_FAILURE(packet, "Should fail, as signed by the policy-violating cert"); |
| |
| packet = unsignedPacket; |
| this->m_keyChain.sign(packet, signingByIdentity(this->subSelfSignedIdentity)); |
| VALIDATE_FAILURE(packet, "Should fail, because subSelfSignedIdentity is not a trust anchor"); |
| } |
| |
| BOOST_FIXTURE_TEST_CASE_TEMPLATE(ValidateInterest, Policy, InterestPolicies, Policy) |
| { |
| BOOST_CHECK_EQUAL(this->policy.m_dataRules.size(), 0); |
| BOOST_CHECK_EQUAL(this->policy.m_interestRules.size(), 1); |
| |
| using Packet = typename Policy::Packet; |
| Packet unsignedPacket("/Security/ValidatorFixture/Sub1/Sub2/Packet"); |
| // All of the packet types inputed to this test case template are Interests, so we can call |
| // setCanBePrefix |
| unsignedPacket.setCanBePrefix(false); |
| |
| Packet packet = unsignedPacket; |
| VALIDATE_FAILURE(packet, "Unsigned"); |
| |
| packet = unsignedPacket; |
| this->m_keyChain.sign(packet, signingWithSha256()); |
| VALIDATE_FAILURE(packet, "Policy doesn't accept Sha256Digest signature"); |
| |
| packet = unsignedPacket; |
| this->m_keyChain.sign(packet, signingByIdentity(this->identity)); |
| VALIDATE_SUCCESS(packet, "Should get accepted, as signed by the anchor"); |
| |
| packet = unsignedPacket; |
| this->m_keyChain.sign(packet, signingByIdentity(this->subIdentity)); |
| VALIDATE_FAILURE(packet, "Should fail, as there is no matching rule for data"); |
| |
| packet = unsignedPacket; |
| this->m_keyChain.sign(packet, signingByIdentity(this->otherIdentity)); |
| VALIDATE_FAILURE(packet, "Should fail, as signed by the policy-violating cert"); |
| |
| packet = unsignedPacket; |
| this->m_keyChain.sign(packet, signingByIdentity(this->subSelfSignedIdentity)); |
| VALIDATE_FAILURE(packet, "Should fail, because subSelfSignedIdentity is not a trust anchor"); |
| } |
| |
| BOOST_FIXTURE_TEST_CASE(Reload, HierarchicalValidatorFixture<ValidationPolicyConfig>) |
| { |
| BOOST_CHECK_EQUAL(this->policy.m_isConfigured, false); |
| this->policy.load(R"CONF( |
| rule |
| { |
| id test-rule-data-id |
| for data |
| filter |
| { |
| type name |
| name /foo/bar |
| relation is-prefix-of |
| } |
| checker |
| { |
| type hierarchical |
| sig-type rsa-sha256 |
| } |
| } |
| rule |
| { |
| id test-rule-interest-id |
| for interest |
| filter |
| { |
| type name |
| name /foo/bar |
| relation is-prefix-of |
| } |
| checker |
| { |
| type hierarchical |
| sig-type rsa-sha256 |
| } |
| } |
| trust-anchor |
| { |
| type dir |
| dir keys |
| refresh 1h |
| } |
| )CONF", "test-config"); |
| BOOST_CHECK_EQUAL(this->policy.m_isConfigured, true); |
| BOOST_CHECK_EQUAL(this->policy.m_shouldBypass, false); |
| BOOST_CHECK_EQUAL(this->policy.m_dataRules.size(), 1); |
| BOOST_CHECK_EQUAL(this->policy.m_interestRules.size(), 1); |
| |
| this->policy.load(R"CONF( |
| trust-anchor |
| { |
| type any |
| } |
| )CONF", "test-config"); |
| BOOST_CHECK_EQUAL(this->policy.m_isConfigured, true); |
| BOOST_CHECK_EQUAL(this->policy.m_shouldBypass, true); |
| BOOST_CHECK_EQUAL(this->policy.m_dataRules.size(), 0); |
| BOOST_CHECK_EQUAL(this->policy.m_interestRules.size(), 0); |
| } |
| |
| using Packets = boost::mpl::vector<Interest, Data>; |
| |
| BOOST_FIXTURE_TEST_CASE_TEMPLATE(TrustAnchorWildcard, Packet, Packets, ValidationPolicyConfigFixture<Packet>) |
| { |
| this->policy.load(R"CONF( |
| trust-anchor |
| { |
| type any |
| } |
| )CONF", "test-config"); |
| |
| BOOST_CHECK_EQUAL(this->policy.m_isConfigured, true); |
| BOOST_CHECK_EQUAL(this->policy.m_shouldBypass, true); |
| BOOST_CHECK_EQUAL(this->policy.m_dataRules.size(), 0); |
| BOOST_CHECK_EQUAL(this->policy.m_interestRules.size(), 0); |
| |
| Packet unsignedPacket("/Security/ValidatorFixture/Sub1/Sub2/Packet"); |
| |
| Packet packet = unsignedPacket; |
| VALIDATE_SUCCESS(packet, "Policy should accept everything"); |
| |
| packet = unsignedPacket; |
| this->m_keyChain.sign(packet, signingWithSha256()); |
| VALIDATE_SUCCESS(packet, "Policy should accept everything"); |
| |
| packet = unsignedPacket; |
| this->m_keyChain.sign(packet, signingByIdentity(this->identity)); |
| VALIDATE_SUCCESS(packet, "Policy should accept everything"); |
| |
| packet = unsignedPacket; |
| this->m_keyChain.sign(packet, signingByIdentity(this->subIdentity)); |
| VALIDATE_SUCCESS(packet, "Policy should accept everything"); |
| |
| packet = unsignedPacket; |
| this->m_keyChain.sign(packet, signingByIdentity(this->otherIdentity)); |
| VALIDATE_SUCCESS(packet, "Policy should accept everything"); |
| |
| packet = unsignedPacket; |
| this->m_keyChain.sign(packet, signingByIdentity(this->subSelfSignedIdentity)); |
| VALIDATE_SUCCESS(packet, "Policy should accept everything"); |
| } |
| |
| using RefreshPolicies = boost::mpl::vector<Refresh1h, Refresh1m, Refresh1s>; |
| |
| template<typename RefreshPolicy> |
| class RefreshPolicyFixture : public LoadStringWithDirAnchor<Data, RefreshPolicy> |
| { |
| }; |
| |
| BOOST_FIXTURE_TEST_CASE_TEMPLATE(ValidateRefresh, Refresh, RefreshPolicies, RefreshPolicyFixture<Refresh>) |
| { |
| using Packet = Data; |
| Packet unsignedPacket("/Security/ValidatorFixture/Sub1/Sub2/Packet"); |
| |
| boost::filesystem::remove(this->path / "keys" / "identity.ndncert"); |
| this->advanceClocks(Refresh::getRefreshTime(), 3); |
| |
| Packet packet = unsignedPacket; |
| this->m_keyChain.sign(packet, signingByIdentity(this->identity)); |
| VALIDATE_FAILURE(packet, "Should fail, as the trust anchor should no longer exist"); |
| |
| packet = unsignedPacket; |
| this->m_keyChain.sign(packet, signingByIdentity(this->subIdentity)); |
| VALIDATE_FAILURE(packet, "Should fail, as the trust anchor should no longer exist"); |
| } |
| |
| BOOST_FIXTURE_TEST_CASE(OrphanedPolicyLoad, HierarchicalValidatorFixture<ValidationPolicyConfig>) // Bug #4758 |
| { |
| ValidationPolicyConfig policy1; |
| BOOST_CHECK_THROW(policy1.load("trust-anchor { type any }", "test-config"), Error); |
| |
| // Reloading would have triggered a segfault |
| BOOST_CHECK_THROW(policy1.load("trust-anchor { type any }", "test-config"), Error); |
| |
| ValidationPolicyConfig policy2; |
| |
| std::string config = R"CONF( |
| trust-anchor |
| { |
| type dir |
| dir keys |
| refresh 1h |
| } |
| )CONF"; |
| |
| // Inserting trust anchor would have triggered a segfault |
| BOOST_CHECK_THROW(policy2.load(config, "test-config"), Error); |
| } |
| |
| BOOST_AUTO_TEST_SUITE_END() // TestValidationPolicyConfig |
| BOOST_AUTO_TEST_SUITE_END() // Security |
| |
| } // namespace tests |
| } // namespace validator_config |
| } // inline namespace v2 |
| } // namespace security |
| } // namespace ndn |