blob: b5a7e04026f7f53654666e22f1e55329c8ede070 [file] [log] [blame]
/* -*- 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/v2/validator.hpp"
#include "ndn-cxx/security/v2/validation-policy-simple-hierarchy.hpp"
#include "tests/boost-test.hpp"
#include "tests/unit/security/v2/validator-fixture.hpp"
namespace ndn {
namespace security {
namespace v2 {
namespace tests {
using namespace ndn::tests;
BOOST_AUTO_TEST_SUITE(Security)
BOOST_AUTO_TEST_SUITE(V2)
BOOST_FIXTURE_TEST_SUITE(TestValidator, HierarchicalValidatorFixture<ValidationPolicySimpleHierarchy>)
BOOST_AUTO_TEST_CASE(ConstructorSetValidator)
{
auto middlePolicy = make_unique<ValidationPolicySimpleHierarchy>();
auto innerPolicy = make_unique<ValidationPolicySimpleHierarchy>();
validator.getPolicy().setInnerPolicy(std::move(middlePolicy));
validator.getPolicy().setInnerPolicy(std::move(innerPolicy));
BOOST_CHECK(validator.getPolicy().m_validator != nullptr);
BOOST_CHECK(validator.getPolicy().getInnerPolicy().m_validator != nullptr);
BOOST_CHECK(validator.getPolicy().getInnerPolicy().getInnerPolicy().m_validator != nullptr);
}
BOOST_AUTO_TEST_CASE(Timeouts)
{
processInterest = nullptr; // no response for all interests
Data data("/Security/V2/ValidatorFixture/Sub1/Sub2/Data");
m_keyChain.sign(data, signingByIdentity(subIdentity));
VALIDATE_FAILURE(data, "Should fail to retrieve certificate");
BOOST_CHECK_GT(face.sentInterests.size(), 1);
}
BOOST_AUTO_TEST_CASE(NackedInterests)
{
processInterest = [this] (const Interest& interest) {
lp::Nack nack(interest);
nack.setReason(lp::NackReason::NO_ROUTE);
face.receive(nack);
};
Data data("/Security/V2/ValidatorFixture/Sub1/Sub2/Data");
m_keyChain.sign(data, signingByIdentity(subIdentity));
VALIDATE_FAILURE(data, "All interests should get NACKed");
// 1 for the first interest, 3 for the retries on nack
BOOST_CHECK_EQUAL(face.sentInterests.size(), 4);
}
BOOST_AUTO_TEST_CASE(MalformedCert)
{
Data malformedCert = subIdentity.getDefaultKey().getDefaultCertificate();
malformedCert.setContentType(tlv::ContentType_Blob);
m_keyChain.sign(malformedCert, signingByIdentity(identity));
// wrong content type & missing ValidityPeriod
BOOST_REQUIRE_THROW(Certificate(malformedCert.wireEncode()), tlv::Error);
auto originalProcessInterest = processInterest;
processInterest = [this, &originalProcessInterest, &malformedCert] (const Interest& interest) {
if (interest.getName().isPrefixOf(malformedCert.getName())) {
face.receive(malformedCert);
}
else {
originalProcessInterest(interest);
}
};
Data data("/Security/V2/ValidatorFixture/Sub1/Sub2/Data");
m_keyChain.sign(data, signingByIdentity(subIdentity));
VALIDATE_FAILURE(data, "Signed by a malformed certificate");
BOOST_CHECK_EQUAL(face.sentInterests.size(), 1);
}
BOOST_AUTO_TEST_CASE(ExpiredCert)
{
Data expiredCert = subIdentity.getDefaultKey().getDefaultCertificate();
SignatureInfo info;
info.setValidityPeriod(ValidityPeriod(time::system_clock::now() - 2_h,
time::system_clock::now() - 1_h));
m_keyChain.sign(expiredCert, signingByIdentity(identity).setSignatureInfo(info));
BOOST_REQUIRE_NO_THROW(Certificate(expiredCert.wireEncode()));
auto originalProcessInterest = processInterest;
processInterest = [this, &originalProcessInterest, &expiredCert] (const Interest& interest) {
if (interest.getName().isPrefixOf(expiredCert.getName())) {
face.receive(expiredCert);
}
else {
originalProcessInterest(interest);
}
};
Data data("/Security/V2/ValidatorFixture/Sub1/Sub2/Data");
m_keyChain.sign(data, signingByIdentity(subIdentity));
VALIDATE_FAILURE(data, "Signed by an expired certificate");
BOOST_CHECK_EQUAL(face.sentInterests.size(), 1);
}
BOOST_AUTO_TEST_CASE(ResetAnchors)
{
validator.resetAnchors();
Data data("/Security/V2/ValidatorFixture/Sub1/Sub2/Data");
m_keyChain.sign(data, signingByIdentity(subIdentity));
VALIDATE_FAILURE(data, "Should fail, as no anchors configured");
}
BOOST_AUTO_TEST_CASE(TrustedCertCaching)
{
Data data("/Security/V2/ValidatorFixture/Sub1/Sub2/Data");
m_keyChain.sign(data, signingByIdentity(subIdentity));
VALIDATE_SUCCESS(data, "Should get accepted, as signed by the policy-compliant cert");
BOOST_CHECK_EQUAL(face.sentInterests.size(), 1);
face.sentInterests.clear();
processInterest = nullptr; // disable data responses from mocked network
VALIDATE_SUCCESS(data, "Should get accepted, based on the cached trusted cert");
BOOST_CHECK_EQUAL(face.sentInterests.size(), 0);
face.sentInterests.clear();
advanceClocks(1_h, 2); // expire trusted cache
VALIDATE_FAILURE(data, "Should try and fail to retrieve certs");
BOOST_CHECK_GT(face.sentInterests.size(), 1);
face.sentInterests.clear();
}
BOOST_AUTO_TEST_CASE(ResetVerifiedCertificates)
{
Data data("/Security/V2/ValidatorFixture/Sub1/Sub2/Data");
m_keyChain.sign(data, signingByIdentity(subIdentity));
VALIDATE_SUCCESS(data, "Should get accepted, as signed by the policy-compliant cert");
// reset anchors
validator.resetAnchors();
VALIDATE_SUCCESS(data, "Should get accepted, as signed by the cert in trusted cache");
// reset trusted cache
validator.resetVerifiedCertificates();
VALIDATE_FAILURE(data, "Should fail, as no trusted cache or anchors");
}
BOOST_AUTO_TEST_CASE(UntrustedCertCaching)
{
Data data("/Security/V2/ValidatorFixture/Sub1/Sub2/Data");
m_keyChain.sign(data, signingByIdentity(subSelfSignedIdentity));
VALIDATE_FAILURE(data, "Should fail, as signed by the policy-violating cert");
BOOST_CHECK_EQUAL(face.sentInterests.size(), 1);
face.sentInterests.clear();
processInterest = nullptr; // disable data responses from mocked network
VALIDATE_FAILURE(data, "Should fail again, but no network operations expected");
BOOST_CHECK_EQUAL(face.sentInterests.size(), 0);
face.sentInterests.clear();
advanceClocks(10_min, 2); // expire untrusted cache
VALIDATE_FAILURE(data, "Should try and fail to retrieve certs");
BOOST_CHECK_GT(face.sentInterests.size(), 1);
face.sentInterests.clear();
}
class ValidationPolicySimpleHierarchyForInterestOnly : public ValidationPolicySimpleHierarchy
{
public:
void
checkPolicy(const Data& data, const shared_ptr<ValidationState>& state,
const ValidationContinuation& continueValidation) override
{
continueValidation(nullptr, state);
}
};
BOOST_FIXTURE_TEST_CASE(ValidateInterestsButBypassForData,
HierarchicalValidatorFixture<ValidationPolicySimpleHierarchyForInterestOnly>)
{
Interest interest("/Security/V2/ValidatorFixture/Sub1/Sub2/Interest");
Data data("/Security/V2/ValidatorFixture/Sub1/Sub2/Interest");
VALIDATE_FAILURE(interest, "Unsigned");
VALIDATE_SUCCESS(data, "Policy requests validation bypassing for all data");
BOOST_CHECK_EQUAL(face.sentInterests.size(), 0);
face.sentInterests.clear();
interest = Interest("/Security/V2/ValidatorFixture/Sub1/Sub2/Interest");
m_keyChain.sign(interest, signingWithSha256());
m_keyChain.sign(data, signingWithSha256());
VALIDATE_FAILURE(interest, "Required KeyLocator/Name missing (not passed to policy)");
VALIDATE_SUCCESS(data, "Policy requests validation bypassing for all data");
BOOST_CHECK_EQUAL(face.sentInterests.size(), 0);
face.sentInterests.clear();
m_keyChain.sign(interest, signingByIdentity(identity));
m_keyChain.sign(data, signingByIdentity(identity));
VALIDATE_SUCCESS(interest, "Should get accepted, as signed by the anchor");
VALIDATE_SUCCESS(data, "Policy requests validation bypassing for all data");
BOOST_CHECK_EQUAL(face.sentInterests.size(), 0);
face.sentInterests.clear();
m_keyChain.sign(interest, signingByIdentity(subIdentity));
m_keyChain.sign(data, signingByIdentity(subIdentity));
VALIDATE_FAILURE(interest, "Should fail, as policy is not allowed to create new trust anchors");
VALIDATE_SUCCESS(data, "Policy requests validation bypassing for all data");
BOOST_CHECK_EQUAL(face.sentInterests.size(), 1);
face.sentInterests.clear();
m_keyChain.sign(interest, signingByIdentity(otherIdentity));
m_keyChain.sign(data, signingByIdentity(otherIdentity));
VALIDATE_FAILURE(interest, "Should fail, as signed by the policy-violating cert");
VALIDATE_SUCCESS(data, "Policy requests validation bypassing for all data");
// no network operations expected, as certificate is not validated by the policy
BOOST_CHECK_EQUAL(face.sentInterests.size(), 0);
face.sentInterests.clear();
advanceClocks(1_h, 2); // expire trusted cache
m_keyChain.sign(interest, signingByIdentity(subSelfSignedIdentity));
m_keyChain.sign(data, signingByIdentity(subSelfSignedIdentity));
VALIDATE_FAILURE(interest, "Should fail, as policy is not allowed to create new trust anchors");
VALIDATE_SUCCESS(data, "Policy requests validation bypassing for all data");
BOOST_CHECK_EQUAL(face.sentInterests.size(), 1);
face.sentInterests.clear();
}
BOOST_AUTO_TEST_CASE(InfiniteCertChain)
{
processInterest = [this] (const Interest& interest) {
// create another key for the same identity and sign it properly
Key parentKey = m_keyChain.createKey(subIdentity);
Key requestedKey = subIdentity.getKey(interest.getName());
Name certificateName = requestedKey.getName();
certificateName
.append("looper")
.appendVersion();
v2::Certificate certificate;
certificate.setName(certificateName);
// set metainfo
certificate.setContentType(tlv::ContentType_Key);
certificate.setFreshnessPeriod(1_h);
// set content
certificate.setContent(requestedKey.getPublicKey().data(), requestedKey.getPublicKey().size());
// set signature-info
SignatureInfo info;
info.setValidityPeriod(security::ValidityPeriod(time::system_clock::now() - 10_days,
time::system_clock::now() + 10_days));
m_keyChain.sign(certificate, signingByKey(parentKey).setSignatureInfo(info));
face.receive(certificate);
};
Data data("/Security/V2/ValidatorFixture/Sub1/Sub2/Data");
m_keyChain.sign(data, signingByIdentity(subIdentity));
validator.setMaxDepth(40);
BOOST_CHECK_EQUAL(validator.getMaxDepth(), 40);
VALIDATE_FAILURE(data, "Should fail, as certificate should be looped");
BOOST_CHECK_EQUAL(face.sentInterests.size(), 40);
face.sentInterests.clear();
advanceClocks(1_h, 5); // expire caches
validator.setMaxDepth(30);
BOOST_CHECK_EQUAL(validator.getMaxDepth(), 30);
VALIDATE_FAILURE(data, "Should fail, as certificate chain is infinite");
BOOST_CHECK_EQUAL(face.sentInterests.size(), 30);
}
BOOST_AUTO_TEST_CASE(LoopedCertChain)
{
auto s1 = addIdentity("/loop");
auto k1 = m_keyChain.createKey(s1, RsaKeyParams(name::Component("key1")));
auto k2 = m_keyChain.createKey(s1, RsaKeyParams(name::Component("key2")));
auto k3 = m_keyChain.createKey(s1, RsaKeyParams(name::Component("key3")));
auto makeCert = [this] (Key& key, const Key& signer) {
v2::Certificate request = key.getDefaultCertificate();
request.setName(Name(key.getName()).append("looper").appendVersion());
SignatureInfo info;
info.setValidityPeriod(ValidityPeriod(time::system_clock::now() - 100_days,
time::system_clock::now() + 100_days));
m_keyChain.sign(request, signingByKey(signer).setSignatureInfo(info));
m_keyChain.addCertificate(key, request);
cache.insert(request);
};
makeCert(k1, k2);
makeCert(k2, k3);
makeCert(k3, k1);
Data data("/loop/Data");
m_keyChain.sign(data, signingByKey(k1));
VALIDATE_FAILURE(data, "Should fail, as certificate chain loops");
BOOST_CHECK_EQUAL(face.sentInterests.size(), 3);
}
BOOST_AUTO_TEST_SUITE_END() // TestValidator
BOOST_AUTO_TEST_SUITE_END() // V2
BOOST_AUTO_TEST_SUITE_END() // Security
} // namespace tests
} // namespace v2
} // namespace security
} // namespace ndn