blob: 61732744650b9d2ac4b8913e4af13eac7a6f03b1 [file] [log] [blame]
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/**
* Copyright (c) 2013-2016 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 "security/command-interest-validator.hpp"
#include "security/signing-helpers.hpp"
#include <boost/lexical_cast.hpp>
#include "boost-test.hpp"
#include "../../dummy-validator.hpp"
#include "../identity-management-time-fixture.hpp"
#include "../make-interest-data.hpp"
namespace ndn {
namespace security {
namespace tests {
using namespace ndn::tests;
class CommandInterestValidatorFixture : public IdentityManagementTimeFixture
{
protected:
CommandInterestValidatorFixture()
{
this->initialize(CommandInterestValidator::Options{});
}
void
initialize(const CommandInterestValidator::Options& options)
{
auto inner = make_unique<DummyValidator>();
this->inner = inner.get();
this->validator = make_unique<CommandInterestValidator>(std::move(inner), options);
}
Name
makeIdentity(int identity)
{
Name name("/localhost/CommandInterestValidatorIdentity");
name.appendSequenceNumber(identity);
BOOST_REQUIRE(m_keyChain.doesIdentityExist(name) || this->addIdentity(name));
return name;
}
shared_ptr<Interest>
makeCommandInterest(int identity = 0)
{
auto interest = makeInterest("/CommandInterestPrefix");
m_keyChain.sign(*interest, signingByIdentity(makeIdentity(identity)));
BOOST_TEST_MESSAGE("makeCommandInterest " << interest->getName());
return interest;
}
/** \brief check that validator accepts interest
* \param interest to be validated
*/
void
assertAccept(const Interest& interest)
{
BOOST_TEST_MESSAGE("assertAccept " << interest.getName());
int nAccepts = 0;
validator->validate(interest,
[&nAccepts] (const shared_ptr<const Interest>&) { ++nAccepts; },
[] (const shared_ptr<const Interest>&, const std::string& msg) {
BOOST_ERROR("validation request should succeed but fails with: " << msg);
});
BOOST_CHECK_EQUAL(nAccepts, 1);
}
/** \brief check that validator rejects interest
* \param interest to be validated
* \param error if not NONE, further check the error code matches \p error
* if NONE, error code is not checked
*/
void
assertReject(const Interest& interest, CommandInterestValidator::ErrorCode error)
{
BOOST_TEST_MESSAGE("assertReject " << interest.getName());
int nRejects = 0;
validator->validate(interest,
[] (const shared_ptr<const Interest>&) {
BOOST_ERROR("validation request should fail but succeeds");
},
[&nRejects, error] (const shared_ptr<const Interest>&, const std::string& msg) {
++nRejects;
if (error != CommandInterestValidator::ErrorCode::NONE) {
BOOST_CHECK_EQUAL(msg, boost::lexical_cast<std::string>(error));
}
});
BOOST_CHECK_EQUAL(nRejects, 1);
}
protected:
DummyValidator* inner;
unique_ptr<CommandInterestValidator> validator;
};
template<typename...A>
void
setNameComponent(Name& name, ssize_t index, const A& ...a)
{
Name name2 = name.getPrefix(index);
name2.append(name::Component(a...));
name2.append(name.getSubName(index + 1));
name = name2;
}
BOOST_AUTO_TEST_SUITE(Security)
BOOST_FIXTURE_TEST_SUITE(TestCommandInterestValidator, CommandInterestValidatorFixture)
BOOST_AUTO_TEST_CASE(Normal)
{
auto i1 = makeCommandInterest();
assertAccept(*i1);
advanceClocks(time::milliseconds(5));
auto i2 = makeCommandInterest();
assertAccept(*i2);
advanceClocks(time::seconds(2));
auto i3 = makeCommandInterest();
assertAccept(*i3);
}
BOOST_AUTO_TEST_CASE(DataPassthru)
{
auto d1 = makeData("/data");
int nAccepts = 0;
validator->validate(*d1,
[&nAccepts] (const shared_ptr<const Data>&) { ++nAccepts; },
[] (const shared_ptr<const Data>&, const std::string& msg) {
BOOST_ERROR("validation request should succeed but fails with " << msg);
});
BOOST_CHECK_EQUAL(nAccepts, 1);
}
BOOST_AUTO_TEST_SUITE(Rejects)
BOOST_AUTO_TEST_CASE(NameTooShort)
{
auto i1 = makeInterest("/name/too/short");
assertReject(*i1, CommandInterestValidator::ErrorCode::NAME_TOO_SHORT);
}
BOOST_AUTO_TEST_CASE(BadTimestamp)
{
auto i1 = makeCommandInterest();
Name n1 = i1->getName();
setNameComponent(n1, signed_interest::POS_TIMESTAMP, "not-timestamp");
i1->setName(n1);
assertReject(*i1, CommandInterestValidator::ErrorCode::BAD_TIMESTAMP);
}
BOOST_AUTO_TEST_CASE(BadSigInfo)
{
auto i1 = makeCommandInterest();
Name n1 = i1->getName();
setNameComponent(n1, signed_interest::POS_SIG_INFO, "not-SignatureInfo");
i1->setName(n1);
assertReject(*i1, CommandInterestValidator::ErrorCode::BAD_SIG_INFO);
}
BOOST_AUTO_TEST_CASE(MissingKeyLocator)
{
auto i1 = makeCommandInterest();
Name n1 = i1->getName();
SignatureInfo sigInfo;
setNameComponent(n1, signed_interest::POS_SIG_INFO,
sigInfo.wireEncode().begin(), sigInfo.wireEncode().end());
i1->setName(n1);
assertReject(*i1, CommandInterestValidator::ErrorCode::MISSING_KEY_LOCATOR);
}
BOOST_AUTO_TEST_CASE(BadKeyLocatorType)
{
auto i1 = makeCommandInterest();
Name n1 = i1->getName();
KeyLocator kl;
kl.setKeyDigest(makeBinaryBlock(tlv::KeyDigest, "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD", 8));
SignatureInfo sigInfo;
sigInfo.setKeyLocator(kl);
setNameComponent(n1, signed_interest::POS_SIG_INFO,
sigInfo.wireEncode().begin(), sigInfo.wireEncode().end());
i1->setName(n1);
assertReject(*i1, CommandInterestValidator::ErrorCode::BAD_KEY_LOCATOR_TYPE);
}
BOOST_AUTO_TEST_CASE(BadCertName)
{
auto i1 = makeCommandInterest();
Name n1 = i1->getName();
KeyLocator kl;
kl.setName("/bad/cert/name");
SignatureInfo sigInfo;
sigInfo.setKeyLocator(kl);
setNameComponent(n1, signed_interest::POS_SIG_INFO,
sigInfo.wireEncode().begin(), sigInfo.wireEncode().end());
i1->setName(n1);
assertReject(*i1, CommandInterestValidator::ErrorCode::BAD_CERT_NAME);
}
BOOST_AUTO_TEST_CASE(InnerReject)
{
inner->setResult(false);
auto i1 = makeCommandInterest();
assertReject(*i1, CommandInterestValidator::ErrorCode::NONE);
}
BOOST_AUTO_TEST_CASE(TimestampOutOfGracePositive)
{
CommandInterestValidator::Options options;
options.gracePeriod = time::seconds(15);
initialize(options);
auto i1 = makeCommandInterest(); // signed at 0s
advanceClocks(time::seconds(16)); // verifying at +16s
assertReject(*i1, CommandInterestValidator::ErrorCode::TIMESTAMP_OUT_OF_GRACE);
auto i2 = makeCommandInterest(); // signed at +16s
assertAccept(*i2); // verifying at +16s
}
BOOST_AUTO_TEST_CASE(TimestampOutOfGraceNegative)
{
CommandInterestValidator::Options options;
options.gracePeriod = time::seconds(15);
initialize(options);
auto i1 = makeCommandInterest(); // signed at 0s
advanceClocks(time::seconds(1));
auto i2 = makeCommandInterest(); // signed at +1s
advanceClocks(time::seconds(1));
auto i3 = makeCommandInterest(); // signed at +2s
systemClock->advance(time::seconds(-18)); // verifying at -16s
assertReject(*i1, CommandInterestValidator::ErrorCode::TIMESTAMP_OUT_OF_GRACE);
// CommandInterestValidator should not remember i1's timestamp
assertReject(*i2, CommandInterestValidator::ErrorCode::TIMESTAMP_OUT_OF_GRACE);
// CommandInterestValidator should not remember i2's timestamp, and should treat i3 as initial
advanceClocks(time::seconds(18)); // verifying at +2s
assertAccept(*i3);
}
BOOST_AUTO_TEST_CASE(TimestampReorderEqual)
{
auto i1 = makeCommandInterest(); // signed at 0s
assertAccept(*i1);
auto i2 = makeCommandInterest();
Name n1 = i1->getName();
Name n2 = i2->getName();
setNameComponent(n2, signed_interest::POS_TIMESTAMP,
n1[signed_interest::POS_TIMESTAMP]);
i2->setName(n2); // signed at 0s
assertReject(*i2, CommandInterestValidator::ErrorCode::TIMESTAMP_REORDER);
advanceClocks(time::seconds(2));
auto i3 = makeCommandInterest(); // signed at +2s
assertAccept(*i3);
}
BOOST_AUTO_TEST_CASE(TimestampReorderNegative)
{
auto i2 = makeCommandInterest(); // signed at 0ms
advanceClocks(time::milliseconds(200));
auto i3 = makeCommandInterest(); // signed at +200ms
advanceClocks(time::milliseconds(900));
auto i1 = makeCommandInterest(); // signed at +1100ms
advanceClocks(time::milliseconds(300));
auto i4 = makeCommandInterest(); // signed at +1400ms
systemClock->advance(time::milliseconds(-300)); // verifying at +1100ms
assertAccept(*i1);
systemClock->advance(time::milliseconds(-1100)); // verifying at 0ms
assertReject(*i2, CommandInterestValidator::ErrorCode::TIMESTAMP_REORDER);
// CommandInterestValidator should not remember i2's timestamp
advanceClocks(time::milliseconds(200)); // verifying at +200ms
assertReject(*i3, CommandInterestValidator::ErrorCode::TIMESTAMP_REORDER);
advanceClocks(time::milliseconds(1200)); // verifying at 1400ms
assertAccept(*i4);
}
BOOST_AUTO_TEST_SUITE_END() // Rejects
BOOST_AUTO_TEST_SUITE(Options)
typedef boost::mpl::vector<
boost::mpl::int_<0>,
boost::mpl::int_<-1>
> GraceNonPositiveValues;
BOOST_AUTO_TEST_CASE_TEMPLATE(GraceNonPositive, VALUE, GraceNonPositiveValues)
{
CommandInterestValidator::Options options;
options.gracePeriod = time::seconds(VALUE::value);
initialize(options);
auto i1 = makeCommandInterest(1); // signed at 0ms
auto i2 = makeCommandInterest(2); // signed at 0ms
for (auto interest : {i1, i2}) {
Name name = interest->getName();
setNameComponent(name, signed_interest::POS_TIMESTAMP,
name::Component::fromNumber(time::toUnixTimestamp(time::system_clock::now()).count()));
interest->setName(name);
} // ensure timestamps are exactly 0ms
assertAccept(*i1); // verifying at 0ms
advanceClocks(time::milliseconds(1));
assertReject(*i2, CommandInterestValidator::ErrorCode::TIMESTAMP_OUT_OF_GRACE); // verifying at 1ms
}
BOOST_AUTO_TEST_CASE(TimestampsLimited)
{
CommandInterestValidator::Options options;
options.gracePeriod = time::seconds(15);
options.maxTimestamps = 3;
initialize(options);
auto i1 = makeCommandInterest(1);
auto i2 = makeCommandInterest(2);
auto i3 = makeCommandInterest(3);
auto i00 = makeCommandInterest(0); // signed at 0s
advanceClocks(time::seconds(1));
auto i01 = makeCommandInterest(0); // signed at 1s
advanceClocks(time::seconds(1));
auto i02 = makeCommandInterest(0); // signed at 2s
assertAccept(*i00);
assertAccept(*i02);
assertAccept(*i1);
assertAccept(*i2);
assertAccept(*i3); // forgets identity 0
assertAccept(*i01); // accepted despite timestamp is reordered, because record has been evicted
}
BOOST_AUTO_TEST_CASE(TimestampsUnlimited)
{
CommandInterestValidator::Options options;
options.gracePeriod = time::seconds(15);
options.maxTimestamps = -1;
initialize(options);
auto i1 = makeCommandInterest(0); // signed at 0s
advanceClocks(time::seconds(1));
for (int identity = 0; identity < 20; ++identity) {
auto i2 = makeCommandInterest(identity); // signed at +1s
assertAccept(*i2);
}
assertReject(*i1, CommandInterestValidator::ErrorCode::TIMESTAMP_REORDER);
}
BOOST_AUTO_TEST_CASE(TimestampsDisabled)
{
CommandInterestValidator::Options options;
options.gracePeriod = time::seconds(15);
options.maxTimestamps = 0;
initialize(options);
auto i1 = makeCommandInterest(); // signed at 0s
advanceClocks(time::seconds(1));
auto i2 = makeCommandInterest(); // signed at +1s
assertAccept(*i2);
assertAccept(*i1); // accepted despite timestamp is reordered, because record isn't kept
}
BOOST_AUTO_TEST_CASE(TtlLimited)
{
CommandInterestValidator::Options options;
options.gracePeriod = time::seconds(120);
options.timestampTtl = time::seconds(300);
initialize(options);
auto i1 = makeCommandInterest(); // signed at 0s
advanceClocks(time::seconds(240));
auto i2 = makeCommandInterest(); // signed at +240s
advanceClocks(time::seconds(120));
auto i3 = makeCommandInterest(); // signed at +360s
systemClock->advance(time::seconds(-360)); // rewind system clock to 0s
assertAccept(*i1);
assertAccept(*i3);
advanceClocks(time::seconds(30), time::seconds(301)); // advance steady clock by 301s, and system clock to +301s
assertAccept(*i2); // accepted despite timestamp is reordered, because record has been expired
}
BOOST_AUTO_TEST_CASE(TtlZero)
{
CommandInterestValidator::Options options;
options.gracePeriod = time::seconds(15);
options.timestampTtl = time::seconds::zero();
initialize(options);
auto i1 = makeCommandInterest(); // signed at 0s
advanceClocks(time::seconds(1));
auto i2 = makeCommandInterest(); // signed at +1s
assertAccept(*i2);
assertAccept(*i1); // accepted despite timestamp is reordered, because record has been expired
}
BOOST_AUTO_TEST_SUITE_END() // Options
BOOST_AUTO_TEST_SUITE_END() // TestCommandInterestValidator
BOOST_AUTO_TEST_SUITE_END() // Security
} // namespace tests
} // namespace security
} // namespace ndn