security: various enhancements to Interest and Data validation

* Avoid decoding SignatureInfo multiple times while validating an Interest
 * Fix handling of signed Interests with malformed InterestSignatureInfo
 * Report a NO_SIGNATURE error when SignatureInfo is missing or malformed
 * Fail with POLICY_ERROR in ValidationPolicySimpleHierarchy when the
   signing identity violates the policy
 * Reduce code duplication
 * Expand unit test coverage

Change-Id: I1c9d532b2307d5df8f4bd75152af57a4e10835aa
This commit is contained in:
Davide Pesavento committed 2022-10-05 21:18:07 -04:00
1 parent ea5ce49651
commit 2acce25696
21 files changed
+514 -418

No files matched your search

@@ -61,7 +61,59 @@ ValidationPolicyCommandInterest::checkPolicy(const Interest& interest, const sha
if (!checkTimestamp(state, keyName, timestamp)) {
return;
}
getInnerPolicy().checkPolicy(interest, state, std::bind(continueValidation, _1, _2));
getInnerPolicy().checkPolicy(interest, state, continueValidation);
}
std::tuple<bool, Name, time::system_clock::TimePoint>
ValidationPolicyCommandInterest::parseCommandInterest(const Interest& interest,
const shared_ptr<ValidationState>& state)
{
auto sigInfo = getSignatureInfo(interest, *state);
if (!state->getOutcome()) { // already failed
return std::make_tuple(false, Name(), time::system_clock::TimePoint{});
}
time::system_clock::TimePoint timestamp;
auto fmt = state->getTag<SignedInterestFormatTag>();
BOOST_ASSERT(fmt);
if (*fmt == SignedInterestFormat::V03) {
// SignatureTime is a hard requirement of this policy
// New apps should use the more flexible ValidationPolicySignedInterest (v0.3 only)
auto optionalTimestamp = sigInfo.getTime();
if (!optionalTimestamp) {
state->fail({ValidationError::POLICY_ERROR, "Signed Interest `" +
interest.getName().toUri() + "` lacks required SignatureTime element"});
return std::make_tuple(false, Name(), time::system_clock::TimePoint{});
}
timestamp = *optionalTimestamp;
}
else {
const Name& name = interest.getName();
if (name.size() < command_interest::MIN_SIZE) {
state->fail({ValidationError::POLICY_ERROR,
"Command Interest name too short `" + interest.getName().toUri() + "`"});
return std::make_tuple(false, Name(), time::system_clock::TimePoint{});
}
const auto& timestampComp = name.at(command_interest::POS_TIMESTAMP);
if (!timestampComp.isNumber()) {
state->fail({ValidationError::POLICY_ERROR, "Command Interest `" +
interest.getName().toUri() + "` lacks required timestamp component"});
return std::make_tuple(false, Name(), time::system_clock::TimePoint{});
}
timestamp = time::fromUnixTimestamp(time::milliseconds(timestampComp.toNumber()));
}
Name klName = getKeyLocatorName(sigInfo, *state);
if (!state->getOutcome()) { // already failed
return std::make_tuple(false, Name(), time::system_clock::TimePoint{});
}
return std::make_tuple(true, klName, timestamp);
}
void
@@ -76,53 +128,6 @@ ValidationPolicyCommandInterest::cleanup()
}
}
std::tuple<bool, Name, time::system_clock::TimePoint>
ValidationPolicyCommandInterest::parseCommandInterest(const Interest& interest,
const shared_ptr<ValidationState>& state) const
{
auto fmt = state->getTag<SignedInterestFormatTag>();
BOOST_ASSERT(fmt);
time::system_clock::TimePoint timestamp;
if (*fmt == SignedInterestFormat::V03) {
BOOST_ASSERT(interest.getSignatureInfo());
auto optionalTimestamp = interest.getSignatureInfo()->getTime();
// Note that timestamp is a hard requirement of this policy
// TODO: Refactor to support other/combinations of the restrictions based on Nonce, Time, and/or SeqNum
if (!optionalTimestamp) {
state->fail({ValidationError::POLICY_ERROR, "Signed Interest `" +
interest.getName().toUri() + "` does not include required SignatureTime element"});
return std::make_tuple(false, Name(), time::system_clock::TimePoint{});
}
timestamp = *optionalTimestamp;
}
else {
const Name& name = interest.getName();
if (name.size() < command_interest::MIN_SIZE) {
state->fail({ValidationError::POLICY_ERROR, "Command interest name `" +
interest.getName().toUri() + "` is too short"});
return std::make_tuple(false, Name(), time::system_clock::TimePoint{});
}
const auto& timestampComp = name.at(command_interest::POS_TIMESTAMP);
if (!timestampComp.isNumber()) {
state->fail({ValidationError::POLICY_ERROR, "Command interest `" +
interest.getName().toUri() + "` does not include timestamp component"});
return std::make_tuple(false, Name(), time::system_clock::TimePoint{});
}
timestamp = time::fromUnixTimestamp(time::milliseconds(timestampComp.toNumber()));
}
Name klName = getKeyLocatorName(interest, *state);
if (!state->getOutcome()) { // already failed
return std::make_tuple(false, Name(), time::system_clock::TimePoint{});
}
return std::make_tuple(true, klName, timestamp);
}
bool
ValidationPolicyCommandInterest::checkTimestamp(const shared_ptr<ValidationState>& state,
const Name& keyName, time::system_clock::TimePoint timestamp)
@@ -33,11 +33,13 @@ namespace ndn {
namespace security {
inline namespace v2 {
/** \brief Validation policy for stop-and-wait command Interests
* \sa https://redmine.named-data.net/projects/ndn-cxx/wiki/CommandInterest
/**
* @brief Validation policy for stop-and-wait command Interests.
*
* This policy checks the timestamp field of a stop-and-wait command Interest.
* Signed Interest validation and Data validation requests are delegated to an inner policy.
* This policy checks the timestamp field of a stop-and-wait command Interest.
* Signed Interest validation and Data validation requests are delegated to an inner policy.
*
* @sa https://redmine.named-data.net/projects/ndn-cxx/wiki/CommandInterest
*/
class ValidationPolicyCommandInterest : public ValidationPolicy
{
@@ -112,12 +114,12 @@ protected:
const ValidationContinuation& continueValidation) override;
private:
static std::tuple<bool, Name, time::system_clock::TimePoint>
parseCommandInterest(const Interest& interest, const shared_ptr<ValidationState>& state);
void
cleanup();
std::tuple<bool, Name, time::system_clock::TimePoint>
parseCommandInterest(const Interest& interest, const shared_ptr<ValidationState>& state) const;
bool
checkTimestamp(const shared_ptr<ValidationState>& state,
const Name& keyName, time::system_clock::TimePoint timestamp);
+12 -30
View File
@@ -241,15 +241,16 @@ ValidationPolicyConfig::checkPolicy(const Data& data, const shared_ptr<Validatio
return continueValidation(nullptr, state);
}
Name klName = getKeyLocatorName(data, *state);
Name klName = getKeyLocatorName(data.getSignatureInfo(), *state);
if (!state->getOutcome()) { // already failed
return;
}
auto sigType = tlv::SignatureTypeValue(data.getSignatureType());
for (const auto& rule : m_dataRules) {
if (rule->match(tlv::Data, data.getName(), state)) {
if (rule->check(tlv::Data, tlv::SignatureTypeValue(data.getSignatureType()),
data.getName(), klName, state)) {
if (rule->check(tlv::Data, sigType, data.getName(), klName, state)) {
return continueValidation(make_shared<CertificateRequest>(klName), state);
}
// rule->check calls state->fail(...) if the check fails
@@ -271,39 +272,20 @@ ValidationPolicyConfig::checkPolicy(const Interest& interest, const shared_ptr<V
return continueValidation(nullptr, state);
}
Name klName = getKeyLocatorName(interest, *state);
auto sigInfo = getSignatureInfo(interest, *state);
if (!state->getOutcome()) { // already failed
return;
}
Name klName = getKeyLocatorName(sigInfo, *state);
if (!state->getOutcome()) { // already failed
return;
}
auto sigType = tlv::SignatureTypeValue(sigInfo.getSignatureType());
for (const auto& rule : m_interestRules) {
if (rule->match(tlv::Interest, interest.getName(), state)) {
tlv::SignatureTypeValue sigType;
auto fmt = state->getTag<SignedInterestFormatTag>();
BOOST_ASSERT(fmt);
if (*fmt == SignedInterestFormat::V03) {
sigType = tlv::SignatureTypeValue(interest.getSignatureInfo()->getSignatureType());
}
else {
if (interest.getName().size() < signed_interest::MIN_SIZE) {
state->fail({ValidationError::INVALID_KEY_LOCATOR, "Invalid signed Interest: name too short"});
return;
}
SignatureInfo si;
try {
si.wireDecode(interest.getName()[signed_interest::POS_SIG_INFO].blockFromValue());
}
catch (const tlv::Error& e) {
state->fail({ValidationError::INVALID_KEY_LOCATOR, "Invalid signed Interest: "s + e.what()});
return;
}
sigType = tlv::SignatureTypeValue(si.getSignatureType());
}
if (rule->check(tlv::Interest, sigType, interest.getName(), klName, state)) {
return continueValidation(make_shared<CertificateRequest>(klName), state);
}
@@ -1,6 +1,6 @@
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
* Copyright (c) 2013-2020 Regents of the University of California.
* Copyright (c) 2013-2022 Regents of the University of California.
*
* This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
*
@@ -52,37 +52,40 @@ ValidationPolicySignedInterest::checkPolicy(const Interest& interest,
const shared_ptr<ValidationState>& state,
const ValidationContinuation& continueValidation)
{
auto fmt = state->getTag<SignedInterestFormatTag>();
BOOST_ASSERT(fmt);
if (!state->getOutcome()) {
return;
}
auto fmt = state->getTag<SignedInterestFormatTag>();
BOOST_ASSERT(fmt);
if (*fmt == SignedInterestFormat::V03 && !checkIncomingInterest(state, interest)) {
return;
}
getInnerPolicy().checkPolicy(interest, state, std::bind(continueValidation, _1, _2));
getInnerPolicy().checkPolicy(interest, state, continueValidation);
}
bool
ValidationPolicySignedInterest::checkIncomingInterest(const shared_ptr<ValidationState>& state,
const Interest& interest)
{
// Extract information from Interest
BOOST_ASSERT(interest.getSignatureInfo());
Name keyName = getKeyLocatorName(interest, *state);
auto timestamp = interest.getSignatureInfo()->getTime();
auto seqNum = interest.getSignatureInfo()->getSeqNum();
auto nonce = interest.getSignatureInfo()->getNonce();
auto sigInfo = getSignatureInfo(interest, *state);
if (!state->getOutcome()) {
return false;
}
Name keyName = getKeyLocatorName(sigInfo, *state);
if (!state->getOutcome()) {
return false;
}
auto timestamp = sigInfo.getTime();
auto seqNum = sigInfo.getSeqNum();
auto nonce = sigInfo.getNonce();
auto record = m_byKeyName.find(keyName);
if (m_options.shouldValidateTimestamps) {
if (!timestamp.has_value()) {
state->fail({ValidationError::POLICY_ERROR,
"Timestamp is required by policy but is not present"});
state->fail({ValidationError::POLICY_ERROR, "Timestamp is required by policy but is not present"});
return false;
}
@@ -1,6 +1,6 @@
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
* Copyright (c) 2013-2021 Regents of the University of California.
* Copyright (c) 2013-2022 Regents of the University of California.
*
* This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
*
@@ -34,9 +34,12 @@ namespace ndn {
namespace security {
inline namespace v2 {
/** \brief Validation policy for signed Interests
/**
* @brief Validation policy for signed Interests.
*
* This policy checks the timestamp, sequence number, and nonce fields of signed Interests.
* This policy checks the timestamp, sequence number, and/or nonce fields of signed Interests.
*
* @sa https://named-data.net/doc/NDN-packet-spec/0.3/signed-interest.html
*/
class ValidationPolicySignedInterest : public ValidationPolicy
{
@@ -29,48 +29,58 @@ void
ValidationPolicySimpleHierarchy::checkPolicy(const Data& data, const shared_ptr<ValidationState>& state,
const ValidationContinuation& continueValidation)
{
Name klName = getKeyLocatorName(data, *state);
Name klName = getKeyLocatorName(data.getSignatureInfo(), *state);
if (!state->getOutcome()) { // already failed
return;
}
Name identity;
try {
if (extractIdentityNameFromKeyLocator(klName).isPrefixOf(data.getName())) {
continueValidation(make_shared<CertificateRequest>(klName), state);
return;
}
identity = extractIdentityNameFromKeyLocator(klName);
}
catch (const KeyLocator::Error& e) {
state->fail({ValidationError::INVALID_KEY_LOCATOR, e.what()});
return;
}
state->fail({ValidationError::INVALID_KEY_LOCATOR, "Data signing policy violation for " +
data.getName().toUri() + " by " + klName.toUri()});
if (!identity.isPrefixOf(data.getName())) {
state->fail({ValidationError::POLICY_ERROR,
"Data " + data.getName().toUri() + " signed by " + klName.toUri()});
return;
}
continueValidation(make_shared<CertificateRequest>(klName), state);
}
void
ValidationPolicySimpleHierarchy::checkPolicy(const Interest& interest, const shared_ptr<ValidationState>& state,
const ValidationContinuation& continueValidation)
{
Name klName = getKeyLocatorName(interest, *state);
auto sigInfo = getSignatureInfo(interest, *state);
if (!state->getOutcome()) { // already failed
return;
}
Name klName = getKeyLocatorName(sigInfo, *state);
if (!state->getOutcome()) { // already failed
return;
}
Name identity;
try {
if (extractIdentityNameFromKeyLocator(klName).isPrefixOf(interest.getName())) {
continueValidation(make_shared<CertificateRequest>(klName), state);
return;
}
identity = extractIdentityNameFromKeyLocator(klName);
}
catch (const KeyLocator::Error& e) {
state->fail({ValidationError::INVALID_KEY_LOCATOR, e.what()});
return;
}
state->fail({ValidationError::INVALID_KEY_LOCATOR, "Interest signing policy violation for " +
interest.getName().toUri() + " by " + klName.toUri()});
if (!identity.isPrefixOf(interest.getName())) {
state->fail({ValidationError::POLICY_ERROR,
"Interest " + interest.getName().toUri() + " signed by " + klName.toUri()});
return;
}
continueValidation(make_shared<CertificateRequest>(klName), state);
}
} // inline namespace v2
+18 -22
View File
@@ -60,7 +60,7 @@ ValidationPolicy::setValidator(Validator& validator)
}
}
static Name
Name
getKeyLocatorName(const SignatureInfo& si, ValidationState& state)
{
if (si.getSignatureType() == tlv::DigestSha256) {
@@ -81,40 +81,32 @@ getKeyLocatorName(const SignatureInfo& si, ValidationState& state)
return kl.getName();
}
Name
getKeyLocatorName(const Data& data, ValidationState& state)
{
return getKeyLocatorName(data.getSignatureInfo(), state);
}
Name
getKeyLocatorName(const Interest& interest, ValidationState& state)
SignatureInfo
getSignatureInfo(const Interest& interest, ValidationState& state)
{
auto fmt = state.getTag<SignedInterestFormatTag>();
BOOST_ASSERT(fmt);
if (*fmt == SignedInterestFormat::V03) {
BOOST_ASSERT(interest.getSignatureInfo());
return getKeyLocatorName(*interest.getSignatureInfo(), state);
BOOST_ASSERT(interest.getSignatureInfo().has_value());
return *interest.getSignatureInfo();
}
// Try Signed Interest format from Packet Specification v0.2
// Try the old Signed Interest format from Packet Specification v0.2
const Name& name = interest.getName();
if (name.size() < signed_interest::MIN_SIZE) {
state.fail({ValidationError::INVALID_KEY_LOCATOR, "Invalid signed Interest: name too short"});
state.fail({ValidationError::NO_SIGNATURE, "Interest name too short `" + name.toUri() + "`"});
return {};
}
SignatureInfo si;
try {
si.wireDecode(name[signed_interest::POS_SIG_INFO].blockFromValue());
return SignatureInfo(name[signed_interest::POS_SIG_INFO].blockFromValue());
}
catch (const tlv::Error& e) {
state.fail({ValidationError::INVALID_KEY_LOCATOR, "Invalid signed Interest: "s + e.what()});
state.fail({ValidationError::NO_SIGNATURE,
"Malformed SignatureInfo in `" + name.toUri() + "`: " + e.what()});
return {};
}
return getKeyLocatorName(si, state);
}
Name
@@ -126,13 +118,17 @@ extractIdentityNameFromKeyLocator(const Name& keyLocator)
return keyLocator;
}
for (ssize_t i = 1; i <= std::min<ssize_t>(-Certificate::KEY_COMPONENT_OFFSET, keyLocator.size()); ++i) {
if (keyLocator[-i] == Certificate::KEY_COMPONENT) {
return keyLocator.getPrefix(-i);
auto len = static_cast<ssize_t>(keyLocator.size());
// note that KEY_COMPONENT_OFFSET is negative
auto lowerBound = std::max<ssize_t>(len + Certificate::KEY_COMPONENT_OFFSET, 0);
for (ssize_t i = len - 1; i >= lowerBound; --i) {
if (keyLocator[i] == Certificate::KEY_COMPONENT) {
return keyLocator.getPrefix(i);
}
}
NDN_THROW(KeyLocator::Error("KeyLocator name `" + keyLocator.toUri() + "` does not respect the naming conventions"));
NDN_THROW(KeyLocator::Error("KeyLocator `" + keyLocator.toUri() +
"` does not respect the naming conventions"));
}
} // inline namespace v2
+19 -16
View File
@@ -1,6 +1,6 @@
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
* Copyright (c) 2013-2021 Regents of the University of California.
* Copyright (c) 2013-2022 Regents of the University of California.
*
* This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
*
@@ -32,7 +32,7 @@ namespace security {
inline namespace v2 {
/**
* @brief Abstraction that implements validation policy for Data and Interest packets
* @brief Abstraction that implements a validation policy for Interest and Data packets.
*/
class ValidationPolicy : noncopyable
{
@@ -147,26 +147,29 @@ NDN_CXX_PUBLIC_WITH_TESTS_ELSE_PROTECTED:
unique_ptr<ValidationPolicy> m_innerPolicy;
};
/** \brief extract KeyLocator.Name from a Data packet
/**
* @brief Extract SignatureInfo from a signed Interest.
*
* The Data packet must contain a KeyLocator of Name type.
* Otherwise, state.fail is invoked with INVALID_KEY_LOCATOR error.
* Signed Interests according to Packet Specification v0.3+, as identified by the
* SignedInterestFormatTag inside @p state, must have an InterestSignatureInfo element.
* Legacy signed Interests must contain a (%Data)%SignatureInfo name component.
* In both cases, if any TLV parsing errors are encountered, ValidationState::fail()
* is invoked on @p state with a ValidationError::NO_SIGNATURE error code.
*
* @pre @p state must contain a SignedInterestFormatTag to indicate whether the %Interest is
* signed according to Packet Specification v0.3+ or a previous specification.
*/
Name
getKeyLocatorName(const Data& data, ValidationState& state);
SignatureInfo
getSignatureInfo(const Interest& interest, ValidationState& state);
/** \brief extract KeyLocator.Name from signed Interest
/**
* @brief Extract the KeyLocator name from a SignatureInfo element.
*
* Signed Interests according to Packet Specification v0.3+, as identified inside the state, must
* have an InterestSignatureInfo element. Legacy signed Interests must contain a
* (Data)SignatureInfo name component. In both cases, the included KeyLocator must be of the Name
* type. otherwise, state.fail will be invoked with an INVALID_KEY_LOCATOR error.
*
* Interests specified to this method must be tagged with a SignedInterestFormatTag to indicate
* whether they are signed according to Packet Specification v0.3+ or a previous specification.
* @p sigInfo must contain a KeyLocator of %Name type. Otherwise, ValidationState::fail()
* is invoked on @p state with a ValidationError::INVALID_KEY_LOCATOR error code.
*/
Name
getKeyLocatorName(const Interest& interest, ValidationState& state);
getKeyLocatorName(const SignatureInfo& sigInfo, ValidationState& state);
/**
* @brief Extract identity name from key, version-less certificate, or certificate name
+6 -10
View File
@@ -59,15 +59,13 @@ ValidationState::verifyCertificateChain(const Certificate& trustedCert)
const auto& certToValidate = *it;
if (!verifySignature(certToValidate, *validatedCert)) {
this->fail({ValidationError::INVALID_SIGNATURE, "Invalid signature of certificate `" +
certToValidate.getName().toUri() + "`"});
this->fail({ValidationError::INVALID_SIGNATURE, "Certificate " + certToValidate.getName().toUri()});
m_certificateChain.erase(it, m_certificateChain.end());
return nullptr;
}
else {
NDN_LOG_TRACE_DEPTH("OK signature for certificate `" << certToValidate.getName() << "`");
validatedCert = &certToValidate;
}
NDN_LOG_TRACE_DEPTH("OK signature for certificate `" << certToValidate.getName() << "`");
validatedCert = &certToValidate;
}
return validatedCert;
}
@@ -103,8 +101,7 @@ DataValidationState::verifyOriginalPacket(const optional<Certificate>& trustedCe
m_outcome = true;
}
else {
this->fail({ValidationError::INVALID_SIGNATURE, "Invalid signature of data `" +
m_data.getName().toUri() + "`"});
this->fail({ValidationError::INVALID_SIGNATURE, "Data " + m_data.getName().toUri()});
}
}
@@ -157,8 +154,7 @@ InterestValidationState::verifyOriginalPacket(const optional<Certificate>& trust
m_outcome = true;
}
else {
this->fail({ValidationError::INVALID_SIGNATURE, "Invalid signature of interest `" +
m_interest.getName().toUri() + "`"});
this->fail({ValidationError::INVALID_SIGNATURE, "Interest " + m_interest.getName().toUri()});
}
}
+31 -25
View File
@@ -51,19 +51,11 @@ Validator::validate(const Data& data,
const DataValidationFailureCallback& failureCb)
{
auto state = make_shared<DataValidationState>(data, successCb, failureCb);
NDN_LOG_DEBUG_DEPTH("Start validating data " << data.getName());
m_policy->checkPolicy(data, state,
[this] (const shared_ptr<CertificateRequest>& certRequest, const shared_ptr<ValidationState>& state) {
if (certRequest == nullptr) {
state->bypassValidation();
}
else {
// need to fetch key and validate it
requestCertificate(certRequest, state);
}
});
m_policy->checkPolicy(data, state, [this] (auto&&... args) {
continueValidation(std::forward<decltype(args)>(args)...);
});
}
void
@@ -72,21 +64,20 @@ Validator::validate(const Interest& interest,
const InterestValidationFailureCallback& failureCb)
{
auto state = make_shared<InterestValidationState>(interest, successCb, failureCb);
auto fmt = interest.getSignatureInfo() ? SignedInterestFormat::V03 : SignedInterestFormat::V02;
state->setTag(make_shared<SignedInterestFormatTag>(fmt));
NDN_LOG_DEBUG_DEPTH("Start validating interest " << interest.getName());
NDN_LOG_DEBUG_DEPTH("Start validating interest (" << fmt << ") " << interest.getName());
try {
auto fmt = interest.getSignatureInfo() ? SignedInterestFormat::V03 : SignedInterestFormat::V02;
state->setTag(make_shared<SignedInterestFormatTag>(fmt));
}
catch (const tlv::Error& e) {
return state->fail({ValidationError::NO_SIGNATURE, "Malformed InterestSignatureInfo in `" +
interest.getName().toUri() + "`: " + e.what()});
}
m_policy->checkPolicy(interest, state,
[this] (const shared_ptr<CertificateRequest>& certRequest, const shared_ptr<ValidationState>& state) {
if (certRequest == nullptr) {
state->bypassValidation();
}
else {
// need to fetch key and validate it
requestCertificate(certRequest, state);
}
});
m_policy->checkPolicy(interest, state, [this] (auto&&... args) {
continueValidation(std::forward<decltype(args)>(args)...);
});
}
void
@@ -113,6 +104,21 @@ Validator::validate(const Certificate& cert, const shared_ptr<ValidationState>&
});
}
void
Validator::continueValidation(const shared_ptr<CertificateRequest>& certRequest,
const shared_ptr<ValidationState>& state)
{
BOOST_ASSERT(state);
if (certRequest == nullptr) {
state->bypassValidation();
}
else {
// need to fetch key and validate it
requestCertificate(certRequest, state);
}
}
void
Validator::requestCertificate(const shared_ptr<CertificateRequest>& certRequest,
const shared_ptr<ValidationState>& state)
@@ -128,7 +134,7 @@ Validator::requestCertificate(const shared_ptr<CertificateRequest>& certRequest,
}
if (state->hasSeenCertificateName(certRequest->interest.getName())) {
state->fail({ValidationError::LOOP_DETECTED, "`" + certRequest->interest.getName().toUri() + "`"});
state->fail({ValidationError::LOOP_DETECTED, certRequest->interest.getName().toUri()});
return;
}
+11 -1
View File
@@ -178,10 +178,20 @@ private: // Common validator operations
void
validate(const Certificate& cert, const shared_ptr<ValidationState>& state);
/**
* @brief Validation callback invoked by the policy.
*
* @param certRequest Certificate request, may be nullptr.
* @param state The current validation state.
*/
void
continueValidation(const shared_ptr<CertificateRequest>& certRequest,
const shared_ptr<ValidationState>& state);
/**
* @brief Request certificate for further validation.
*
* @param certRequest Certificate request.
* @param certRequest Certificate request, must not be nullptr.
* @param state The current validation state.
*/
void
@@ -21,11 +21,10 @@
#include "ndn-cxx/security/certificate-fetcher-direct-fetch.hpp"
#include "ndn-cxx/lp/nack.hpp"
#include "ndn-cxx/lp/tags.hpp"
#include "ndn-cxx/security/validation-policy-simple-hierarchy.hpp"
#include "tests/boost-test.hpp"
#include "tests/test-common.hpp"
#include "tests/unit/security/validator-fixture.hpp"
#include <boost/range/adaptor/sliced.hpp>
@@ -133,9 +132,7 @@ template<>
void
CertificateFetcherDirectFetchFixture<Nack>::makeResponse(const Interest& interest)
{
lp::Nack nack(interest);
nack.setHeader(lp::NackHeader().setReason(lp::NackReason::NO_ROUTE));
face.receive(nack);
face.receive(makeNack(interest, lp::NackReason::NO_ROUTE));
}
using Failures = boost::mpl::vector<Timeout, Nack>;
@@ -174,9 +171,10 @@ BOOST_FIXTURE_TEST_CASE(ValidateSuccessDataDirectOnly, CertificateFetcherDirectF
BOOST_FIXTURE_TEST_CASE_TEMPLATE(ValidateFailureData, T, Failures, CertificateFetcherDirectFetchFixture<T>)
{
VALIDATE_FAILURE(this->data, "Should fail, as all interests either NACKed or timeout");
BOOST_TEST(this->lastError.getCode() == ValidationError::CANNOT_RETRIEVE_CERT);
// Direct fetcher sends two interests each time - to network and face
// 3 retries on nack or timeout (2 * (1 + 3) = 8)
BOOST_CHECK_EQUAL(this->face.sentInterests.size(), 8);
BOOST_TEST(this->face.sentInterests.size() == 8);
// odd interests
for (const auto& sentInterest : this->face.sentInterests | boost::adaptors::strided(2)) {
@@ -197,9 +195,10 @@ BOOST_FIXTURE_TEST_CASE_TEMPLATE(ValidateFailureDataDirectOnly, T, Failures, Cer
static_cast<CertificateFetcherDirectFetch&>(this->validator.getFetcher()).setSendDirectInterestOnly(true);
VALIDATE_FAILURE(this->data, "Should fail, as all interests either NACKed or timeout");
BOOST_TEST(this->lastError.getCode() == ValidationError::CANNOT_RETRIEVE_CERT);
// Direct fetcher sends two interests each time - to network and face
// 3 retries on nack or timeout (1 + 3 = 4)
BOOST_CHECK_EQUAL(this->face.sentInterests.size(), 4);
BOOST_TEST(this->face.sentInterests.size() == 4);
for (const auto& sentInterest : this->face.sentInterests) {
BOOST_CHECK(sentInterest.template getTag<lp::NextHopFaceIdTag>() != nullptr);
@@ -215,8 +214,8 @@ BOOST_FIXTURE_TEST_CASE_TEMPLATE(ValidateFailureDataNoTagDirectOnly, T, Failures
this->interest.template removeTag<lp::IncomingFaceIdTag>();
VALIDATE_FAILURE(this->data, "Should fail, as no interests are expected");
BOOST_CHECK_EQUAL(this->face.sentInterests.size(), 0);
BOOST_CHECK_NE(this->lastError.getCode(), ValidationError::IMPLEMENTATION_ERROR);
BOOST_TEST(this->lastError.getCode() == ValidationError::CANNOT_RETRIEVE_CERT);
BOOST_TEST(this->face.sentInterests.size() == 0);
}
BOOST_FIXTURE_TEST_CASE(ValidateSuccessInterest, CertificateFetcherDirectFetchFixture<Cert>)
@@ -240,9 +239,10 @@ BOOST_FIXTURE_TEST_CASE(ValidateSuccessInterest, CertificateFetcherDirectFetchFi
BOOST_FIXTURE_TEST_CASE_TEMPLATE(ValidateFailureInterest, T, Failures, CertificateFetcherDirectFetchFixture<T>)
{
VALIDATE_FAILURE(this->interest, "Should fail, as all interests either NACKed or timeout");
BOOST_TEST(this->lastError.getCode() == ValidationError::CANNOT_RETRIEVE_CERT);
// Direct fetcher sends two interests each time - to network and face
// 3 retries on nack or timeout (2 * (1 + 3) = 4)
BOOST_CHECK_EQUAL(this->face.sentInterests.size(), 8);
BOOST_TEST(this->face.sentInterests.size() == 8);
// odd interests
for (const auto& sentInterest : this->face.sentInterests | boost::adaptors::strided(2)) {
@@ -119,15 +119,17 @@ BOOST_FIXTURE_TEST_CASE(ValidateSuccess, CertificateFetcherFromNetworkFixture<Ce
BOOST_FIXTURE_TEST_CASE_TEMPLATE(ValidateFailure, T, Failures, CertificateFetcherFromNetworkFixture<T>)
{
VALIDATE_FAILURE(this->data, "Should fail, as interests don't bring data");
BOOST_TEST(this->lastError.getCode() == ValidationError::CANNOT_RETRIEVE_CERT);
// first interest + 3 retries
BOOST_CHECK_EQUAL(this->face.sentInterests.size(), 4);
BOOST_TEST(this->face.sentInterests.size() == 4);
this->face.sentInterests.clear();
this->advanceClocks(1_h, 2); // expire validator caches
VALIDATE_FAILURE(this->interest, "Should fail, as interests don't bring data");
BOOST_CHECK_EQUAL(this->face.sentInterests.size(), 4);
BOOST_TEST(this->lastError.getCode() == ValidationError::CANNOT_RETRIEVE_CERT);
BOOST_TEST(this->face.sentInterests.size() == 4);
}
BOOST_AUTO_TEST_SUITE_END() // TestCertificateFetcherFromNetwork
@@ -1,6 +1,6 @@
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
* Copyright (c) 2013-2020 Regents of the University of California.
* Copyright (c) 2013-2022 Regents of the University of California.
*
* This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
*
@@ -54,7 +54,8 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(Validate, Packet, Packets)
auto packet = Packet::makePacket(name);
m_keyChain.sign(packet, signingByIdentity(subIdentity));
VALIDATE_FAILURE(packet, "Should fail, as no cert should be requested");
BOOST_CHECK_EQUAL(this->face.sentInterests.size(), 0);
BOOST_TEST(this->lastError.getCode() == ValidationError::CANNOT_RETRIEVE_CERT);
BOOST_TEST(this->face.sentInterests.size() == 0);
packet = Packet::makePacket(name);
m_keyChain.sign(packet, signingByIdentity(identity));
@@ -28,7 +28,6 @@
#include "tests/test-common.hpp"
#include "tests/unit/security/validator-fixture.hpp"
#include <boost/lexical_cast.hpp>
#include <boost/mpl/vector.hpp>
namespace ndn {
@@ -40,9 +39,8 @@ using namespace ndn::tests;
BOOST_AUTO_TEST_SUITE(Security)
class CommandInterestDefaultOptions
struct CommandInterestDefaultOptions
{
public:
static ValidationPolicyCommandInterest::Options
getOptions()
{
@@ -61,62 +59,82 @@ public:
};
template<class T, class InnerPolicy = ValidationPolicySimpleHierarchy>
class ValidationPolicyCommandInterestFixture : public HierarchicalValidatorFixture<CommandInterestPolicyWrapper<T, InnerPolicy>>
class ValidationPolicyCommandInterestFixture
: public HierarchicalValidatorFixture<CommandInterestPolicyWrapper<T, InnerPolicy>>
{
public:
protected:
Interest
makeCommandInterest(const Identity& identity, bool wantV3 = false)
makeCommandInterest(const Identity& identity, SignedInterestFormat format = SignedInterestFormat::V02)
{
if (wantV3) {
Interest i(Name(identity.getName()).append("CMD"));
m_signer.makeSignedInterest(i, signingByIdentity(identity));
return i;
}
else {
return m_signer.makeCommandInterest(Name(identity.getName()).append("CMD"),
signingByIdentity(identity));
Name name = identity.getName();
name.append("CMD");
switch (format) {
case SignedInterestFormat::V02:
return m_signer.makeCommandInterest(name, signingByIdentity(identity));
case SignedInterestFormat::V03: {
Interest interest(name);
m_signer.makeSignedInterest(interest, signingByIdentity(identity));
return interest;
}
}
NDN_CXX_UNREACHABLE;
}
public:
protected:
InterestSigner m_signer{this->m_keyChain};
};
BOOST_FIXTURE_TEST_SUITE(TestValidationPolicyCommandInterest,
ValidationPolicyCommandInterestFixture<CommandInterestDefaultOptions>)
template<int secs>
struct GracePeriodSeconds
{
static ValidationPolicyCommandInterest::Options
getOptions()
{
ValidationPolicyCommandInterest::Options options;
options.gracePeriod = time::seconds(secs);
return options;
}
};
BOOST_AUTO_TEST_SUITE(Accepts)
BOOST_AUTO_TEST_CASE(Basic)
BOOST_AUTO_TEST_CASE(BasicV02)
{
auto i1 = makeCommandInterest(identity);
auto i1 = makeCommandInterest(identity, SignedInterestFormat::V02);
VALIDATE_SUCCESS(i1, "Should succeed (within grace period)");
VALIDATE_FAILURE(i1, "Should fail (replay attack)");
BOOST_TEST(lastError.getCode() == ValidationError::POLICY_ERROR);
advanceClocks(5_ms);
auto i2 = makeCommandInterest(identity);
auto i2 = makeCommandInterest(identity, SignedInterestFormat::V02);
VALIDATE_SUCCESS(i2, "Should succeed (timestamp larger than previous)");
auto i3 = m_signer.makeCommandInterest(Name(identity.getName()).append("CMD"), signingWithSha256());
auto i3 = m_signer.makeCommandInterest(Name(identity.getName()).append("CMD"), signingWithSha256());
VALIDATE_FAILURE(i3, "Should fail (Sha256 signature violates policy)");
BOOST_TEST(lastError.getCode() == ValidationError::POLICY_ERROR);
}
BOOST_AUTO_TEST_CASE(BasicV3)
BOOST_AUTO_TEST_CASE(BasicV03)
{
auto i1 = makeCommandInterest(identity, true);
auto i1 = makeCommandInterest(identity, SignedInterestFormat::V03);
VALIDATE_SUCCESS(i1, "Should succeed (within grace period)");
VALIDATE_FAILURE(i1, "Should fail (replay attack)");
BOOST_TEST(lastError.getCode() == ValidationError::POLICY_ERROR);
advanceClocks(5_ms);
auto i2 = makeCommandInterest(identity, true);
auto i2 = makeCommandInterest(identity, SignedInterestFormat::V03);
VALIDATE_SUCCESS(i2, "Should succeed (timestamp larger than previous)");
Interest i3(Name(identity.getName()).append("CMD"));
m_signer.makeSignedInterest(i3, signingWithSha256());
VALIDATE_FAILURE(i3, "Should fail (Sha256 signature violates policy)");
BOOST_TEST(lastError.getCode() == ValidationError::POLICY_ERROR);
}
BOOST_AUTO_TEST_CASE(DataPassthru)
BOOST_AUTO_TEST_CASE(DataPassthrough)
{
Data d1("/Security/ValidatorFixture/Sub1");
m_keyChain.sign(d1);
@@ -131,6 +149,7 @@ BOOST_FIXTURE_TEST_CASE(SignedWithSha256, ValidationPolicyAcceptAllCommands) //
auto i1 = m_signer.makeCommandInterest("/hello/world/CMD", signingWithSha256());
VALIDATE_SUCCESS(i1, "Should succeed (within grace period)");
VALIDATE_FAILURE(i1, "Should fail (replay attack)");
BOOST_TEST(lastError.getCode() == ValidationError::POLICY_ERROR);
advanceClocks(5_ms);
auto i2 = m_signer.makeCommandInterest("/hello/world/CMD", signingWithSha256());
@@ -141,89 +160,118 @@ BOOST_AUTO_TEST_SUITE_END() // Accepts
BOOST_AUTO_TEST_SUITE(Rejects)
BOOST_AUTO_TEST_CASE(NameTooShort)
BOOST_AUTO_TEST_CASE(NotSigned)
{
auto i1 = makeInterest("/name/too/short");
VALIDATE_FAILURE(*i1, "Should fail (name is too short)");
}
BOOST_AUTO_TEST_CASE(BadTimestamp)
{
auto i1 = makeCommandInterest(identity);
setNameComponent(i1, command_interest::POS_TIMESTAMP, "not-timestamp");
VALIDATE_FAILURE(i1, "Should fail (timestamp is missing)");
auto i1 = Interest("/short");
BOOST_TEST_REQUIRE(i1.getName().size() < signed_interest::MIN_SIZE);
VALIDATE_FAILURE(i1, "Should fail (not signed / name too short)");
BOOST_TEST(lastError.getCode() == ValidationError::NO_SIGNATURE);
}
BOOST_AUTO_TEST_CASE(BadSigInfo)
{
auto i1 = makeCommandInterest(identity);
auto i1 = makeCommandInterest(identity, SignedInterestFormat::V02);
setNameComponent(i1, command_interest::POS_SIG_INFO, "not-SignatureInfo");
BOOST_TEST_REQUIRE(i1.getName().size() >= command_interest::MIN_SIZE);
VALIDATE_FAILURE(i1, "Should fail (signature info is missing)");
BOOST_TEST(lastError.getCode() == ValidationError::NO_SIGNATURE);
}
BOOST_AUTO_TEST_CASE(MissingKeyLocator)
BOOST_AUTO_TEST_CASE(BadTimestampV02)
{
auto i1 = makeCommandInterest(identity);
// i1 is a valid signed interest (in v0.2 format) but is not a valid command interest
auto i1 = Interest("/short");
m_keyChain.sign(i1, signingByIdentity(identity).setSignedInterestFormat(SignedInterestFormat::V02));
BOOST_TEST_REQUIRE((i1.getName().size() >= signed_interest::MIN_SIZE &&
i1.getName().size() < command_interest::MIN_SIZE));
VALIDATE_FAILURE(i1, "Should fail (timestamp is missing)");
BOOST_TEST(lastError.getCode() == ValidationError::POLICY_ERROR);
auto i2 = makeCommandInterest(identity, SignedInterestFormat::V02);
setNameComponent(i2, command_interest::POS_TIMESTAMP, "not-timestamp");
BOOST_TEST_REQUIRE(i2.getName().size() >= command_interest::MIN_SIZE);
VALIDATE_FAILURE(i2, "Should fail (timestamp is malformed)");
BOOST_TEST(lastError.getCode() == ValidationError::POLICY_ERROR);
}
BOOST_AUTO_TEST_CASE(BadTimestampV03)
{
auto i1 = makeCommandInterest(identity, SignedInterestFormat::V03);
auto si = i1.getSignatureInfo().value();
si.setTime(nullopt);
i1.setSignatureInfo(si);
VALIDATE_FAILURE(i1, "Should fail (timestamp is missing)");
BOOST_TEST(lastError.getCode() == ValidationError::POLICY_ERROR);
}
BOOST_AUTO_TEST_CASE(MissingKeyLocatorV02)
{
auto i1 = makeCommandInterest(identity, SignedInterestFormat::V02);
SignatureInfo sigInfo(tlv::SignatureSha256WithRsa);
setNameComponent(i1, command_interest::POS_SIG_INFO,
sigInfo.wireEncode().begin(), sigInfo.wireEncode().end());
VALIDATE_FAILURE(i1, "Should fail (missing KeyLocator)");
BOOST_TEST(lastError.getCode() == ValidationError::INVALID_KEY_LOCATOR);
}
BOOST_AUTO_TEST_CASE(MissingKeyLocatorV03)
{
auto i1 = makeCommandInterest(identity, SignedInterestFormat::V03);
auto si = i1.getSignatureInfo().value();
si.setKeyLocator(nullopt);
i1.setSignatureInfo(si);
VALIDATE_FAILURE(i1, "Should fail (missing KeyLocator)");
BOOST_TEST(lastError.getCode() == ValidationError::INVALID_KEY_LOCATOR);
}
BOOST_AUTO_TEST_CASE(BadKeyLocatorType)
{
auto i1 = makeCommandInterest(identity);
auto i1 = makeCommandInterest(identity, SignedInterestFormat::V02);
KeyLocator kl;
kl.setKeyDigest(makeBinaryBlock(tlv::KeyDigest, {0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD}));
SignatureInfo sigInfo(tlv::SignatureSha256WithRsa);
sigInfo.setKeyLocator(kl);
SignatureInfo sigInfo(tlv::SignatureSha256WithRsa, kl);
setNameComponent(i1, command_interest::POS_SIG_INFO,
sigInfo.wireEncode().begin(), sigInfo.wireEncode().end());
VALIDATE_FAILURE(i1, "Should fail (bad KeyLocator type)");
BOOST_TEST(lastError.getCode() == ValidationError::INVALID_KEY_LOCATOR);
}
BOOST_AUTO_TEST_CASE(BadCertName)
{
auto i1 = makeCommandInterest(identity);
KeyLocator kl;
kl.setName("/bad/cert/name");
SignatureInfo sigInfo(tlv::SignatureSha256WithRsa);
sigInfo.setKeyLocator(kl);
auto i1 = makeCommandInterest(identity, SignedInterestFormat::V02);
SignatureInfo sigInfo(tlv::SignatureSha256WithEcdsa, KeyLocator("/bad/cert/name"));
setNameComponent(i1, command_interest::POS_SIG_INFO,
sigInfo.wireEncode().begin(), sigInfo.wireEncode().end());
VALIDATE_FAILURE(i1, "Should fail (bad certificate name)");
BOOST_TEST(lastError.getCode() == ValidationError::INVALID_KEY_LOCATOR);
}
BOOST_AUTO_TEST_CASE(InnerPolicyReject)
{
auto i1 = makeCommandInterest(otherIdentity);
auto i1 = makeCommandInterest(otherIdentity, SignedInterestFormat::V02);
VALIDATE_FAILURE(i1, "Should fail (inner policy should reject)");
BOOST_TEST(lastError.getCode() == ValidationError::LOOP_DETECTED);
auto i2 = makeCommandInterest(otherIdentity, SignedInterestFormat::V03);
VALIDATE_FAILURE(i2, "Should fail (inner policy should reject)");
BOOST_TEST(lastError.getCode() == ValidationError::LOOP_DETECTED);
}
class GracePeriod15Sec
{
public:
static ValidationPolicyCommandInterest::Options
getOptions()
{
ValidationPolicyCommandInterest::Options options;
options.gracePeriod = 15_s;
return options;
}
};
BOOST_FIXTURE_TEST_CASE(TimestampOutOfGracePositive, ValidationPolicyCommandInterestFixture<GracePeriod15Sec>)
BOOST_FIXTURE_TEST_CASE(TimestampOutOfGracePositive,
ValidationPolicyCommandInterestFixture<GracePeriodSeconds<15>>)
{
auto i1 = makeCommandInterest(identity); // signed at 0s
advanceClocks(16_s); // verifying at +16s
VALIDATE_FAILURE(i1, "Should fail (timestamp outside the grace period)");
BOOST_TEST(lastError.getCode() == ValidationError::POLICY_ERROR);
rewindClockAfterValidation();
auto i2 = makeCommandInterest(identity); // signed at +16s
VALIDATE_SUCCESS(i2, "Should succeed");
}
BOOST_FIXTURE_TEST_CASE(TimestampOutOfGraceNegative, ValidationPolicyCommandInterestFixture<GracePeriod15Sec>)
BOOST_FIXTURE_TEST_CASE(TimestampOutOfGraceNegative,
ValidationPolicyCommandInterestFixture<GracePeriodSeconds<15>>)
{
auto i1 = makeCommandInterest(identity); // signed at 0s
advanceClocks(1_s);
@@ -233,10 +281,12 @@ BOOST_FIXTURE_TEST_CASE(TimestampOutOfGraceNegative, ValidationPolicyCommandInte
m_systemClock->advance(-18_s); // verifying at -16s
VALIDATE_FAILURE(i1, "Should fail (timestamp outside the grace period)");
BOOST_TEST(lastError.getCode() == ValidationError::POLICY_ERROR);
rewindClockAfterValidation();
// CommandInterestValidator should not remember i1's timestamp
VALIDATE_FAILURE(i2, "Should fail (timestamp outside the grace period)");
BOOST_TEST(lastError.getCode() == ValidationError::POLICY_ERROR);
rewindClockAfterValidation();
// CommandInterestValidator should not remember i2's timestamp, and should treat i3 as initial
@@ -250,9 +300,9 @@ BOOST_AUTO_TEST_CASE(TimestampReorderEqual)
VALIDATE_SUCCESS(i1, "Should succeed");
auto i2 = makeCommandInterest(identity); // signed at 0s
setNameComponent(i2, command_interest::POS_TIMESTAMP,
i1.getName()[command_interest::POS_TIMESTAMP]);
setNameComponent(i2, command_interest::POS_TIMESTAMP, i1.getName()[command_interest::POS_TIMESTAMP]);
VALIDATE_FAILURE(i2, "Should fail (timestamp reordered)");
BOOST_TEST(lastError.getCode() == ValidationError::POLICY_ERROR);
advanceClocks(2_s);
auto i3 = makeCommandInterest(identity); // signed at +2s
@@ -275,11 +325,13 @@ BOOST_AUTO_TEST_CASE(TimestampReorderNegative)
m_systemClock->advance(-1100_ms); // verifying at 0ms
VALIDATE_FAILURE(i2, "Should fail (timestamp reordered)");
BOOST_TEST(lastError.getCode() == ValidationError::POLICY_ERROR);
rewindClockAfterValidation();
// CommandInterestValidator should not remember i2's timestamp
advanceClocks(200_ms); // verifying at +200ms
VALIDATE_FAILURE(i3, "Should fail (timestamp reordered)");
BOOST_TEST(lastError.getCode() == ValidationError::POLICY_ERROR);
rewindClockAfterValidation();
advanceClocks(1200_ms); // verifying at 1400ms
@@ -290,25 +342,12 @@ BOOST_AUTO_TEST_SUITE_END() // Rejects
BOOST_AUTO_TEST_SUITE(Options)
template<class T>
class GracePeriod
{
public:
static ValidationPolicyCommandInterest::Options
getOptions()
{
ValidationPolicyCommandInterest::Options options;
options.gracePeriod = time::seconds(T::value);
return options;
}
};
using NonPositiveGracePeriods = boost::mpl::vector<
GracePeriodSeconds<0>,
GracePeriodSeconds<-1>
>;
typedef boost::mpl::vector<
GracePeriod<boost::mpl::int_<0>>,
GracePeriod<boost::mpl::int_<-1>>
> GraceNonPositiveValues;
BOOST_FIXTURE_TEST_CASE_TEMPLATE(GraceNonPositive, GracePeriod, GraceNonPositiveValues,
BOOST_FIXTURE_TEST_CASE_TEMPLATE(GraceNonPositive, GracePeriod, NonPositiveGracePeriods,
ValidationPolicyCommandInterestFixture<GracePeriod>)
{
auto i1 = this->makeCommandInterest(this->identity); // signed at 0ms
@@ -323,6 +362,7 @@ BOOST_FIXTURE_TEST_CASE_TEMPLATE(GraceNonPositive, GracePeriod, GraceNonPositive
this->advanceClocks(1_ms);
VALIDATE_FAILURE(i2, "Should fail when validating at 1ms");
BOOST_TEST(this->lastError.getCode() == ValidationError::POLICY_ERROR);
}
class LimitedRecordsOptions
@@ -402,11 +442,12 @@ BOOST_FIXTURE_TEST_CASE(UnlimitedRecords, ValidationPolicyCommandInterestFixture
advanceClocks(1_s);
for (size_t i = 0; i < 20; ++i) {
auto i2 = makeCommandInterest(identities.at(i)); // signed at +1s
VALIDATE_SUCCESS(i2, "Should succeed");
rewindClockAfterValidation();
}
VALIDATE_FAILURE(i1, "Should fail (timestamp reorder)");
BOOST_TEST(lastError.getCode() == ValidationError::POLICY_ERROR);
}
class ZeroRecordsOptions
@@ -28,7 +28,6 @@
#include "tests/test-common.hpp"
#include "tests/unit/security/validator-fixture.hpp"
#include <boost/lexical_cast.hpp>
#include <boost/mpl/vector.hpp>
namespace ndn {
@@ -40,9 +39,8 @@ using namespace ndn::tests;
BOOST_AUTO_TEST_SUITE(Security)
class SignedInterestDefaultOptions
struct SignedInterestDefaultOptions
{
public:
static ValidationPolicySignedInterest::Options
getOptions()
{
@@ -64,12 +62,7 @@ template<class T, class InnerPolicy = ValidationPolicySimpleHierarchy>
class ValidationPolicySignedInterestFixture
: public HierarchicalValidatorFixture<SignedInterestPolicyWrapper<T, InnerPolicy>>
{
public:
ValidationPolicySignedInterestFixture()
: m_signer(this->m_keyChain)
{
}
protected:
Interest
makeSignedInterest(const Identity& identity,
uint32_t signingFlags = InterestSigner::WantNonce | InterestSigner::WantTime)
@@ -79,8 +72,8 @@ public:
return i;
}
public:
InterestSigner m_signer;
protected:
InterestSigner m_signer{this->m_keyChain};
static constexpr uint32_t WantAll = InterestSigner::WantNonce |
InterestSigner::WantTime |
@@ -90,11 +83,12 @@ public:
BOOST_FIXTURE_TEST_SUITE(TestValidationPolicySignedInterest,
ValidationPolicySignedInterestFixture<SignedInterestDefaultOptions>)
BOOST_AUTO_TEST_CASE(BasicV3)
BOOST_AUTO_TEST_CASE(Basic)
{
auto i1 = makeSignedInterest(identity, WantAll);
VALIDATE_SUCCESS(i1, "Should succeed (within grace period)");
VALIDATE_FAILURE(i1, "Should fail (replay attack)");
BOOST_TEST(lastError.getCode() == ValidationError::POLICY_ERROR);
advanceClocks(5_ms);
auto i2 = makeSignedInterest(identity, WantAll);
@@ -103,6 +97,7 @@ BOOST_AUTO_TEST_CASE(BasicV3)
Interest i3(Name(identity.getName()).append("CMD"));
m_signer.makeSignedInterest(i3, signingWithSha256());
VALIDATE_FAILURE(i3, "Should fail (Sha256 signature violates policy)");
BOOST_TEST(lastError.getCode() == ValidationError::POLICY_ERROR);
}
BOOST_AUTO_TEST_CASE(DataPassthrough)
@@ -116,22 +111,23 @@ BOOST_AUTO_TEST_CASE(InnerPolicyReject)
{
auto i1 = makeSignedInterest(otherIdentity);
VALIDATE_FAILURE(i1, "Should fail (inner policy should reject)");
BOOST_TEST(lastError.getCode() == ValidationError::LOOP_DETECTED);
}
class LimitedRecordsOptions
template<ssize_t count>
struct MaxRecordCount
{
public:
static ValidationPolicySignedInterest::Options
getOptions()
{
ValidationPolicySignedInterest::Options options;
options.timestampGracePeriod = 15_s;
options.maxRecordCount = 3;
options.maxRecordCount = count;
return options;
}
};
BOOST_FIXTURE_TEST_CASE(LimitedRecords, ValidationPolicySignedInterestFixture<LimitedRecordsOptions>)
BOOST_FIXTURE_TEST_CASE(LimitedRecords, ValidationPolicySignedInterestFixture<MaxRecordCount<3>>)
{
Identity id1 = addSubCertificate("/Security/ValidatorFixture/Sub1", identity);
cache.insert(id1.getDefaultKey().getDefaultCertificate());
@@ -169,20 +165,7 @@ BOOST_FIXTURE_TEST_CASE(LimitedRecords, ValidationPolicySignedInterestFixture<Li
VALIDATE_SUCCESS(i01, "Should succeed despite timestamp is reordered, because record has been evicted");
}
class UnlimitedRecordsOptions
{
public:
static ValidationPolicySignedInterest::Options
getOptions()
{
ValidationPolicySignedInterest::Options options;
options.timestampGracePeriod = 15_s;
options.maxRecordCount = -1;
return options;
}
};
BOOST_FIXTURE_TEST_CASE(UnlimitedRecords, ValidationPolicySignedInterestFixture<UnlimitedRecordsOptions>)
BOOST_FIXTURE_TEST_CASE(UnlimitedRecords, ValidationPolicySignedInterestFixture<MaxRecordCount<-1>>)
{
std::vector<Identity> identities;
for (size_t i = 0; i < 20; ++i) {
@@ -195,27 +178,15 @@ BOOST_FIXTURE_TEST_CASE(UnlimitedRecords, ValidationPolicySignedInterestFixture<
advanceClocks(1_s);
for (size_t i = 0; i < 20; ++i) {
auto i2 = makeSignedInterest(identities.at(i)); // signed at +1s
VALIDATE_SUCCESS(i2, "Should succeed");
rewindClockAfterValidation();
}
VALIDATE_FAILURE(i1, "Should fail (timestamp reorder)");
BOOST_TEST(lastError.getCode() == ValidationError::POLICY_ERROR);
}
class ZeroRecordsOptions
{
public:
static ValidationPolicySignedInterest::Options
getOptions()
{
ValidationPolicySignedInterest::Options options;
options.timestampGracePeriod = 15_s;
options.maxRecordCount = 0;
return options;
}
};
BOOST_FIXTURE_TEST_CASE(ZeroRecords, ValidationPolicySignedInterestFixture<ZeroRecordsOptions>)
BOOST_FIXTURE_TEST_CASE(ZeroRecords, ValidationPolicySignedInterestFixture<MaxRecordCount<0>>)
{
auto i1 = makeSignedInterest(identity); // signed at 0s
advanceClocks(1_s);
@@ -232,11 +203,11 @@ BOOST_AUTO_TEST_CASE(MissingTimestamp)
{
auto i1 = makeSignedInterest(identity, InterestSigner::WantSeqNum);
VALIDATE_FAILURE(i1, "Should fail (timestamp missing)");
BOOST_TEST(lastError.getCode() == ValidationError::POLICY_ERROR);
}
class DisabledTimestampValidationOptions
struct DisabledTimestampValidationOptions
{
public:
static ValidationPolicySignedInterest::Options
getOptions()
{
@@ -261,30 +232,31 @@ BOOST_FIXTURE_TEST_CASE(Disabled,
VALIDATE_SUCCESS(i2, "Should succeed");
}
class GracePeriod15Sec
template<int secs>
struct GracePeriodSeconds
{
public:
static ValidationPolicySignedInterest::Options
getOptions()
{
ValidationPolicySignedInterest::Options options;
options.timestampGracePeriod = 15_s;
options.timestampGracePeriod = time::seconds(secs);
return options;
}
};
BOOST_FIXTURE_TEST_CASE(TimestampTooOld, ValidationPolicySignedInterestFixture<GracePeriod15Sec>)
BOOST_FIXTURE_TEST_CASE(TimestampTooOld, ValidationPolicySignedInterestFixture<GracePeriodSeconds<15>>)
{
auto i1 = makeSignedInterest(identity); // signed at 0s
advanceClocks(16_s); // verifying at +16s
VALIDATE_FAILURE(i1, "Should fail (timestamp outside the grace period)");
BOOST_TEST(lastError.getCode() == ValidationError::POLICY_ERROR);
rewindClockAfterValidation();
auto i2 = makeSignedInterest(identity); // signed at +16s
VALIDATE_SUCCESS(i2, "Should succeed");
}
BOOST_FIXTURE_TEST_CASE(TimestampTooNew, ValidationPolicySignedInterestFixture<GracePeriod15Sec>)
BOOST_FIXTURE_TEST_CASE(TimestampTooNew, ValidationPolicySignedInterestFixture<GracePeriodSeconds<15>>)
{
auto i1 = makeSignedInterest(identity); // signed at 0s
advanceClocks(1_s);
@@ -294,10 +266,12 @@ BOOST_FIXTURE_TEST_CASE(TimestampTooNew, ValidationPolicySignedInterestFixture<G
m_systemClock->advance(-18_s); // verifying at -16s
VALIDATE_FAILURE(i1, "Should fail (timestamp outside the grace period)");
BOOST_TEST(lastError.getCode() == ValidationError::POLICY_ERROR);
rewindClockAfterValidation();
// SignedInterestValidator should not remember i1's timestamp
VALIDATE_FAILURE(i2, "Should fail (timestamp outside the grace period)");
BOOST_TEST(lastError.getCode() == ValidationError::POLICY_ERROR);
rewindClockAfterValidation();
// SignedInterestValidator should not remember i2's timestamp, and should treat i3 as initial
@@ -315,6 +289,7 @@ BOOST_AUTO_TEST_CASE(TimestampReorderEqual)
si2->setTime(i1.getSignatureInfo()->getTime());
i2.setSignatureInfo(*si2);
VALIDATE_FAILURE(i2, "Should fail (timestamp reordered)");
BOOST_TEST(lastError.getCode() == ValidationError::POLICY_ERROR);
advanceClocks(2_s);
auto i3 = makeSignedInterest(identity); // signed at +2s
@@ -337,36 +312,25 @@ BOOST_AUTO_TEST_CASE(TimestampReorderNegative)
m_systemClock->advance(-1100_ms); // verifying at 0ms
VALIDATE_FAILURE(i2, "Should fail (timestamp reordered)");
BOOST_TEST(lastError.getCode() == ValidationError::POLICY_ERROR);
rewindClockAfterValidation();
// SignedInterestValidator should not remember i2's timestamp
advanceClocks(200_ms); // verifying at +200ms
VALIDATE_FAILURE(i3, "Should fail (timestamp reordered)");
BOOST_TEST(lastError.getCode() == ValidationError::POLICY_ERROR);
rewindClockAfterValidation();
advanceClocks(1200_ms); // verifying at 1400ms
VALIDATE_SUCCESS(i4, "Should succeed");
}
template<class T>
class GracePeriod
{
public:
static ValidationPolicySignedInterest::Options
getOptions()
{
ValidationPolicySignedInterest::Options options;
options.timestampGracePeriod = time::seconds(T::value);
return options;
}
};
using NonPositiveGracePeriods = boost::mpl::vector<
GracePeriodSeconds<0>,
GracePeriodSeconds<-1>
>;
typedef boost::mpl::vector<
GracePeriod<boost::mpl::int_<0>>,
GracePeriod<boost::mpl::int_<-1>>
> GraceNonPositiveValues;
BOOST_FIXTURE_TEST_CASE_TEMPLATE(GraceNonPositive, GracePeriod, GraceNonPositiveValues,
BOOST_FIXTURE_TEST_CASE_TEMPLATE(GraceNonPositive, GracePeriod, NonPositiveGracePeriods,
ValidationPolicySignedInterestFixture<GracePeriod>)
{
auto i1 = this->makeSignedInterest(this->identity); // signed at 0ms
@@ -383,6 +347,7 @@ BOOST_FIXTURE_TEST_CASE_TEMPLATE(GraceNonPositive, GracePeriod, GraceNonPositive
this->advanceClocks(1_ms);
VALIDATE_FAILURE(i2, "Should fail when validating at 1ms");
BOOST_TEST(this->lastError.getCode() == ValidationError::POLICY_ERROR);
}
BOOST_AUTO_TEST_SUITE_END() // TimestampValidation
@@ -403,9 +368,8 @@ BOOST_AUTO_TEST_CASE(Disabled)
VALIDATE_SUCCESS(i2, "Should succeed");
}
class SeqNumValidationOptions
struct SeqNumValidationOptions
{
public:
static ValidationPolicySignedInterest::Options
getOptions()
{
@@ -420,6 +384,7 @@ BOOST_FIXTURE_TEST_CASE(MissingSeqNum,
{
auto i1 = makeSignedInterest(identity, InterestSigner::WantTime);
VALIDATE_FAILURE(i1, "Should fail (sequence number missing");
BOOST_TEST(lastError.getCode() == ValidationError::POLICY_ERROR);
}
BOOST_FIXTURE_TEST_CASE(SeqNumReorder,
@@ -433,6 +398,7 @@ BOOST_FIXTURE_TEST_CASE(SeqNumReorder,
si2->setSeqNum(i1.getSignatureInfo()->getSeqNum());
i2.setSignatureInfo(*si2);
VALIDATE_FAILURE(i2, "Should fail (sequence number reordered)");
BOOST_TEST(lastError.getCode() == ValidationError::POLICY_ERROR);
auto i3 = makeSignedInterest(identity, WantAll); // seq num is i+2
VALIDATE_SUCCESS(i3, "Should succeed");
@@ -446,6 +412,7 @@ BOOST_AUTO_TEST_CASE(MissingNonce)
{
auto i1 = makeSignedInterest(identity, InterestSigner::WantTime); // Specifically exclude nonce
VALIDATE_FAILURE(i1, "Should fail (nonce missing)");
BOOST_TEST(lastError.getCode() == ValidationError::POLICY_ERROR);
}
BOOST_AUTO_TEST_CASE(DuplicateNonce)
@@ -458,6 +425,7 @@ BOOST_AUTO_TEST_CASE(DuplicateNonce)
si2->setNonce(i1.getSignatureInfo()->getNonce());
i2.setSignatureInfo(*si2);
VALIDATE_FAILURE(i2, "Should fail (duplicate nonce)");
BOOST_TEST(lastError.getCode() == ValidationError::POLICY_ERROR);
auto i3 = makeSignedInterest(identity, WantAll);
// On the off chance that the generated nonce is identical to i1
@@ -467,9 +435,8 @@ BOOST_AUTO_TEST_CASE(DuplicateNonce)
VALIDATE_SUCCESS(i3, "Should succeed");
}
class DisabledNonceValidationOptions
struct DisabledNonceValidationOptions
{
public:
static ValidationPolicySignedInterest::Options
getOptions()
{
@@ -499,9 +466,8 @@ BOOST_FIXTURE_TEST_CASE(Disabled,
VALIDATE_SUCCESS(i3, "Should succeed");
}
class NonceLimit2Options
struct NonceLimit2Options
{
public:
static ValidationPolicySignedInterest::Options
getOptions()
{
@@ -533,6 +499,7 @@ BOOST_FIXTURE_TEST_CASE(NonceRecordLimit,
m_keyChain.sign(i3, signingByIdentity(identity).setSignedInterestFormat(SignedInterestFormat::V03)
.setSignatureInfo(*si3));
VALIDATE_FAILURE(i3, "Should fail (duplicate nonce)");
BOOST_TEST(lastError.getCode() == ValidationError::POLICY_ERROR);
// Pop i1's nonce off the list
auto i4 = makeSignedInterest(identity, WantAll);
@@ -43,10 +43,13 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(Validate, Packet, Packets)
auto packet = Packet::makePacket(name);
VALIDATE_FAILURE(packet, "Unsigned");
BOOST_TEST((lastError.getCode() == ValidationError::NO_SIGNATURE || // Interest
lastError.getCode() == ValidationError::INVALID_KEY_LOCATOR)); // Data
packet = Packet::makePacket(name);
m_keyChain.sign(packet, signingWithSha256());
VALIDATE_FAILURE(packet, "Should not be accepted, name not prefix of /localhost/identity/digest-sha256");
BOOST_TEST(lastError.getCode() == ValidationError::POLICY_ERROR);
packet = Packet::makePacket("/localhost/identity/digest-sha256/foobar");
m_keyChain.sign(packet, signingWithSha256());
@@ -63,15 +66,17 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(Validate, Packet, Packets)
packet = Packet::makePacket(name);
m_keyChain.sign(packet, signingByIdentity(otherIdentity));
VALIDATE_FAILURE(packet, "Should fail, as signed by the policy-violating cert");
BOOST_TEST(lastError.getCode() == ValidationError::POLICY_ERROR);
packet = Packet::makePacket(name);
m_keyChain.sign(packet, signingByIdentity(subSelfSignedIdentity));
VALIDATE_FAILURE(packet, "Should fail, because subSelfSignedIdentity is not a trust anchor");
BOOST_TEST(lastError.getCode() == ValidationError::LOOP_DETECTED);
// TODO add checks with malformed packets
}
BOOST_AUTO_TEST_CASE(NonKeyNameInsideLocator)
BOOST_AUTO_TEST_CASE(CertNameInKeyLocator)
{
// auto cert = identity.getDefaultKey().getDefaultCertificate().wireEncode();
// std::cerr << "Certificate idCert{\"" << toHex(cert) << "\"_block};" << std::endl;
@@ -107,14 +112,13 @@ BOOST_AUTO_TEST_CASE(NonKeyNameInsideLocator)
"483046022100BDD3E0EF2385658825EB73E87A02D1A16AA8ACE50840C1B91782836164AACA3B0221008007B3EBA9"
"B7638BD204766B08AF6E4221CDB88156CC7DA13CD916610D6D3AED"_block};
BOOST_REQUIRE_EQUAL(packet.getKeyLocator().value().getName(),
"/Security/ValidatorFixture/Sub1/KEY/%D7j1%B0%1E%14%09%2B/parent/%FD%00%00%01I%9DY%8C%A0");
this->cache.insert(idCert);
this->cache.insert(subIdCert);
this->validator.loadAnchor("", std::move(idCert));
BOOST_REQUIRE(packet.getKeyLocator());
BOOST_CHECK_EQUAL(packet.getKeyLocator()->getName(),
"/Security/ValidatorFixture/Sub1/KEY/%D7j1%B0%1E%14%09%2B/parent/%FD%00%00%01I%9DY%8C%A0");
VALIDATE_SUCCESS(packet, "Should get accepted, as signed by the policy-compliant cert");
}
+9 -5
View File
@@ -1,6 +1,6 @@
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
* Copyright (c) 2013-2020 Regents of the University of California.
* Copyright (c) 2013-2022 Regents of the University of California.
*
* This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
*
@@ -35,17 +35,21 @@ BOOST_FIXTURE_TEST_SUITE(TestValidationPolicy, ndn::tests::KeyChainFixture)
BOOST_AUTO_TEST_CASE(ExtractIdentityNameFromKeyLocator)
{
auto id = m_keyChain.createIdentity("/random/identity");
auto keyName = id.getDefaultKey().getName();
auto certName = id.getDefaultKey().getDefaultCertificate().getName();
auto partialCertName = id.getDefaultKey().getDefaultCertificate().getName().getPrefix(-1);
auto partialCertName = certName.getPrefix(-1);
BOOST_CHECK_EQUAL(extractIdentityNameFromKeyLocator(keyName), "/random/identity");
BOOST_CHECK_EQUAL(extractIdentityNameFromKeyLocator(certName), "/random/identity");
BOOST_CHECK_EQUAL(extractIdentityNameFromKeyLocator(partialCertName), "/random/identity");
BOOST_CHECK_EQUAL(extractIdentityNameFromKeyLocator("/KEY"), "/");
BOOST_CHECK_THROW(extractIdentityNameFromKeyLocator(Name("/name/without/key/component")), std::runtime_error);
BOOST_CHECK_THROW(extractIdentityNameFromKeyLocator(Name("/name/with/KEY/but/in/a/wrong/place")), std::runtime_error);
BOOST_CHECK_THROW(extractIdentityNameFromKeyLocator(Name("/short/name")),
KeyLocator::Error);
BOOST_CHECK_THROW(extractIdentityNameFromKeyLocator(Name("/name/without/key/component")),
KeyLocator::Error);
BOOST_CHECK_THROW(extractIdentityNameFromKeyLocator(Name("/name/with/KEY/but/in/a/wrong/place")),
KeyLocator::Error);
}
BOOST_AUTO_TEST_SUITE_END() // TestValidationPolicy
@@ -56,6 +56,12 @@ ValidatorFixtureBase::mockNetworkOperations()
advanceClocks(s_mockPeriod, s_mockTimes);
}
void
ValidatorFixtureBase::rewindClockAfterValidation()
{
m_systemClock->advance(s_mockPeriod * s_mockTimes * -1);
}
Identity
ValidatorFixtureBase::addSubCertificate(const Name& subIdentityName, const Identity& issuer)
{
+6 -7
View File
@@ -44,13 +44,11 @@ protected:
void
mockNetworkOperations();
/** \brief undo clock advancement of mockNetworkOperations()
/**
* @brief Undo clock advancement of mockNetworkOperations()
*/
void
rewindClockAfterValidation()
{
m_systemClock->advance(s_mockPeriod * s_mockTimes * -1);
}
rewindClockAfterValidation();
/**
* @brief Issues a certificate for @p subIdentityName signed by @p issuer
@@ -71,8 +69,8 @@ protected:
ValidationError lastError{ValidationError::NO_ERROR};
private:
const static time::milliseconds s_mockPeriod;
const static int s_mockTimes;
static const time::milliseconds s_mockPeriod;
static const int s_mockTimes;
};
template<class ValidationPolicyT, class CertificateFetcherT = CertificateFetcherFromNetwork>
@@ -93,6 +91,7 @@ protected:
size_t nCallbacks = 0;
this->validator.validate(packet,
[&] (const Packet&) {
lastError = ValidationError::NO_ERROR;
++nCallbacks;
BOOST_CHECK_MESSAGE(expectSuccess,
(expectSuccess ? "OK: " : "FAILED: ") + detailedInfo);
+98 -42
View File
@@ -22,7 +22,7 @@
#include "ndn-cxx/security/validator.hpp"
#include "ndn-cxx/security/validation-policy-simple-hierarchy.hpp"
#include "tests/boost-test.hpp"
#include "tests/test-common.hpp"
#include "tests/unit/security/validator-fixture.hpp"
namespace ndn {
@@ -46,33 +46,75 @@ BOOST_AUTO_TEST_CASE(ConstructorSetValidator)
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_CHECK_THROW(validator.getPolicy().setInnerPolicy(nullptr), std::invalid_argument);
}
BOOST_AUTO_TEST_CASE(Timeouts)
BOOST_AUTO_TEST_CASE(BadSignatureInfo)
{
processInterest = nullptr; // no response for all interests
Interest interest("/Security/ValidatorFixture/Sub1/Sub2/Interest");
m_keyChain.sign(interest, signingByIdentity(subIdentity)
.setSignedInterestFormat(SignedInterestFormat::V03));
// add an unrecognized critical element inside InterestSignatureInfo
auto si = interest.getSignatureInfo().value();
si.addCustomTlv("7F00"_block);
interest.setSignatureInfo(si);
BOOST_REQUIRE_THROW(interest.getSignatureInfo(), tlv::Error);
BOOST_REQUIRE_NO_THROW(interest.getSignatureValue());
VALIDATE_FAILURE(interest, "InterestSignatureInfo decoding should fail");
BOOST_TEST(lastError.getCode() == ValidationError::NO_SIGNATURE);
BOOST_TEST(face.sentInterests.size() == 0);
}
BOOST_AUTO_TEST_CASE(BadSignatureValue)
{
const uint8_t sv[] = {0x12, 0x34, 0x56, 0x78};
Interest interest("/Security/ValidatorFixture/Sub1/Sub2/Interest");
m_keyChain.sign(interest, signingByIdentity(subIdentity)
.setSignedInterestFormat(SignedInterestFormat::V03));
interest.setSignatureValue(sv);
VALIDATE_FAILURE(interest, "Signature check should fail");
BOOST_TEST(lastError.getCode() == ValidationError::INVALID_SIGNATURE);
BOOST_TEST(face.sentInterests.size() == 1);
Data data("/Security/ValidatorFixture/Sub1/Sub2/Data");
m_keyChain.sign(data, signingByIdentity(subIdentity));
data.setSignatureValue(sv);
VALIDATE_FAILURE(data, "Signature check should fail");
BOOST_TEST(lastError.getCode() == ValidationError::INVALID_SIGNATURE);
BOOST_TEST(face.sentInterests.size() == 1);
}
BOOST_AUTO_TEST_CASE(Timeout)
{
processInterest = nullptr; // no response for any interest
Data data("/Security/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_TEST(lastError.getCode() == ValidationError::CANNOT_RETRIEVE_CERT);
BOOST_TEST(face.sentInterests.size() == 4);
}
BOOST_AUTO_TEST_CASE(NackedInterests)
BOOST_AUTO_TEST_CASE(Nack)
{
processInterest = [this] (const Interest& interest) {
lp::Nack nack(interest);
nack.setReason(lp::NackReason::NO_ROUTE);
face.receive(nack);
face.receive(makeNack(interest, lp::NackReason::NO_ROUTE));
};
Data data("/Security/ValidatorFixture/Sub1/Sub2/Data");
m_keyChain.sign(data, signingByIdentity(subIdentity));
VALIDATE_FAILURE(data, "All interests should get NACKed");
BOOST_TEST(lastError.getCode() == ValidationError::CANNOT_RETRIEVE_CERT);
// 1 for the first interest, 3 for the retries on nack
BOOST_CHECK_EQUAL(face.sentInterests.size(), 4);
BOOST_TEST(face.sentInterests.size() == 4);
}
BOOST_AUTO_TEST_CASE(MalformedCert)
@@ -84,7 +126,7 @@ BOOST_AUTO_TEST_CASE(MalformedCert)
BOOST_REQUIRE_THROW(Certificate(malformedCert.wireEncode()), tlv::Error);
auto originalProcessInterest = processInterest;
processInterest = [this, &originalProcessInterest, &malformedCert] (const Interest& interest) {
processInterest = [&] (const Interest& interest) {
if (interest.getName().isPrefixOf(malformedCert.getName())) {
face.receive(malformedCert);
}
@@ -97,20 +139,20 @@ BOOST_AUTO_TEST_CASE(MalformedCert)
m_keyChain.sign(data, signingByIdentity(subIdentity));
VALIDATE_FAILURE(data, "Signed by a malformed certificate");
BOOST_CHECK_EQUAL(face.sentInterests.size(), 1);
BOOST_TEST(lastError.getCode() == ValidationError::MALFORMED_CERT);
BOOST_TEST(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));
info.setValidityPeriod(ValidityPeriod::makeRelative(-2_h, -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) {
processInterest = [&] (const Interest& interest) {
if (interest.getName().isPrefixOf(expiredCert.getName())) {
face.receive(expiredCert);
}
@@ -123,7 +165,8 @@ BOOST_AUTO_TEST_CASE(ExpiredCert)
m_keyChain.sign(data, signingByIdentity(subIdentity));
VALIDATE_FAILURE(data, "Signed by an expired certificate");
BOOST_CHECK_EQUAL(face.sentInterests.size(), 1);
BOOST_TEST(lastError.getCode() == ValidationError::EXPIRED_CERT);
BOOST_TEST(face.sentInterests.size() == 1);
}
BOOST_AUTO_TEST_CASE(ResetAnchors)
@@ -133,6 +176,7 @@ BOOST_AUTO_TEST_CASE(ResetAnchors)
Data data("/Security/ValidatorFixture/Sub1/Sub2/Data");
m_keyChain.sign(data, signingByIdentity(subIdentity));
VALIDATE_FAILURE(data, "Should fail, as no anchors configured");
BOOST_TEST(lastError.getCode() == ValidationError::LOOP_DETECTED);
}
BOOST_AUTO_TEST_CASE(TrustedCertCaching)
@@ -141,23 +185,23 @@ BOOST_AUTO_TEST_CASE(TrustedCertCaching)
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);
BOOST_TEST(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);
BOOST_TEST(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_TEST(lastError.getCode() == ValidationError::CANNOT_RETRIEVE_CERT);
BOOST_TEST(face.sentInterests.size() > 1);
}
BOOST_AUTO_TEST_CASE(ResetVerifiedCertificates)
BOOST_AUTO_TEST_CASE(ResetVerifiedCerts)
{
Data data("/Security/ValidatorFixture/Sub1/Sub2/Data");
m_keyChain.sign(data, signingByIdentity(subIdentity));
@@ -170,6 +214,7 @@ BOOST_AUTO_TEST_CASE(ResetVerifiedCertificates)
// reset trusted cache
validator.resetVerifiedCertificates();
VALIDATE_FAILURE(data, "Should fail, as no trusted cache or anchors");
BOOST_TEST(lastError.getCode() == ValidationError::LOOP_DETECTED);
}
BOOST_AUTO_TEST_CASE(UntrustedCertCaching)
@@ -178,20 +223,22 @@ BOOST_AUTO_TEST_CASE(UntrustedCertCaching)
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);
BOOST_TEST(lastError.getCode() == ValidationError::LOOP_DETECTED);
BOOST_TEST(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);
BOOST_TEST(lastError.getCode() == ValidationError::LOOP_DETECTED);
BOOST_TEST(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();
BOOST_TEST(lastError.getCode() == ValidationError::CANNOT_RETRIEVE_CERT);
BOOST_TEST(face.sentInterests.size() > 1);
}
class ValidationPolicySimpleHierarchyForInterestOnly : public ValidationPolicySimpleHierarchy
@@ -209,41 +256,45 @@ BOOST_FIXTURE_TEST_CASE(ValidateInterestsButBypassForData,
HierarchicalValidatorFixture<ValidationPolicySimpleHierarchyForInterestOnly>)
{
Interest interest("/Security/ValidatorFixture/Sub1/Sub2/Interest");
Data data("/Security/ValidatorFixture/Sub1/Sub2/Interest");
Data data("/Security/ValidatorFixture/Sub1/Sub2/Data");
VALIDATE_FAILURE(interest, "Unsigned");
VALIDATE_SUCCESS(data, "Policy requests validation bypassing for all data");
BOOST_CHECK_EQUAL(face.sentInterests.size(), 0);
BOOST_TEST(lastError.getCode() == ValidationError::NO_SIGNATURE);
VALIDATE_SUCCESS(data, "Policy bypasses validation for all data");
BOOST_TEST(face.sentInterests.size() == 0);
face.sentInterests.clear();
interest = Interest("/Security/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);
BOOST_TEST(lastError.getCode() == ValidationError::POLICY_ERROR);
VALIDATE_SUCCESS(data, "Policy bypasses validation for all data");
BOOST_TEST(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);
VALIDATE_SUCCESS(data, "Policy bypasses validation for all data");
BOOST_TEST(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);
BOOST_TEST(lastError.getCode() == ValidationError::POLICY_ERROR);
VALIDATE_SUCCESS(data, "Policy bypasses validation for all data");
BOOST_TEST(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");
BOOST_TEST(lastError.getCode() == ValidationError::POLICY_ERROR);
VALIDATE_SUCCESS(data, "Policy bypasses validation for all data");
// no network operations expected, as certificate is not validated by the policy
BOOST_CHECK_EQUAL(face.sentInterests.size(), 0);
BOOST_TEST(face.sentInterests.size() == 0);
face.sentInterests.clear();
advanceClocks(1_h, 2); // expire trusted cache
@@ -251,8 +302,9 @@ BOOST_FIXTURE_TEST_CASE(ValidateInterestsButBypassForData,
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);
BOOST_TEST(lastError.getCode() == ValidationError::POLICY_ERROR);
VALIDATE_SUCCESS(data, "Policy bypasses validation for all data");
BOOST_TEST(face.sentInterests.size() == 1);
face.sentInterests.clear();
}
@@ -272,13 +324,15 @@ BOOST_AUTO_TEST_CASE(InfiniteCertChain)
};
Data data("/Security/ValidatorFixture/Sub1/Sub2/Data");
m_keyChain.sign(data, signingByIdentity(subIdentity).setSignatureInfo(
SignatureInfo().setKeyLocator(subIdentity.getDefaultKey().getName())));
m_keyChain.sign(data, signingByIdentity(subIdentity)
.setSignatureInfo(SignatureInfo()
.setKeyLocator(subIdentity.getDefaultKey().getName())));
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);
BOOST_TEST(lastError.getCode() == ValidationError::EXCEEDED_DEPTH_LIMIT);
BOOST_TEST(face.sentInterests.size() == 40);
face.sentInterests.clear();
advanceClocks(1_h, 5); // expire caches
@@ -286,7 +340,8 @@ BOOST_AUTO_TEST_CASE(InfiniteCertChain)
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_TEST(lastError.getCode() == ValidationError::EXCEEDED_DEPTH_LIMIT);
BOOST_TEST(face.sentInterests.size() == 30);
}
BOOST_AUTO_TEST_CASE(LoopedCertChain)
@@ -313,7 +368,8 @@ BOOST_AUTO_TEST_CASE(LoopedCertChain)
Data data("/loop/Data");
m_keyChain.sign(data, signingByKey(k1));
VALIDATE_FAILURE(data, "Should fail, as certificate chain loops");
BOOST_REQUIRE_EQUAL(face.sentInterests.size(), 3);
BOOST_TEST(lastError.getCode() == ValidationError::LOOP_DETECTED);
BOOST_TEST_REQUIRE(face.sentInterests.size() == 3);
BOOST_CHECK_EQUAL(face.sentInterests[0].getName(), k1.getDefaultCertificate().getName());
BOOST_CHECK_EQUAL(face.sentInterests[1].getName(), k2.getName());
BOOST_CHECK_EQUAL(face.sentInterests[2].getName(), k3.getName());