blob: 13bcc73068cbe200bdde2ca65bc601e28a67475c [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.
*
* @author Yingdi Yu <http://irl.cs.ucla.edu/~yingdi/>
* @author Zhiyi Zhang <zhangzhiyi1919@gmail.com>
*/
#include "validator-config.hpp"
#include "certificate-cache-ttl.hpp"
#include "../util/io.hpp"
#include <boost/filesystem.hpp>
#include <boost/property_tree/info_parser.hpp>
#include <boost/algorithm/string.hpp>
namespace ndn {
namespace security {
const shared_ptr<CertificateCache> ValidatorConfig::DEFAULT_CERTIFICATE_CACHE;
const time::milliseconds ValidatorConfig::DEFAULT_GRACE_INTERVAL(3000);
const time::system_clock::Duration ValidatorConfig::DEFAULT_KEY_TIMESTAMP_TTL = time::hours(1);
ValidatorConfig::ValidatorConfig(Face* face,
const shared_ptr<CertificateCache>& certificateCache,
const time::milliseconds& graceInterval,
const size_t stepLimit,
const size_t maxTrackedKeys,
const time::system_clock::Duration& keyTimestampTtl)
: Validator(face)
, m_shouldValidate(true)
, m_stepLimit(stepLimit)
, m_certificateCache(certificateCache)
, m_graceInterval(graceInterval < time::milliseconds::zero() ?
DEFAULT_GRACE_INTERVAL : graceInterval)
, m_maxTrackedKeys(maxTrackedKeys)
, m_keyTimestampTtl(keyTimestampTtl)
{
if (m_certificateCache == nullptr && face != nullptr)
m_certificateCache = make_shared<CertificateCacheTtl>(ref(face->getIoService()));
}
ValidatorConfig::ValidatorConfig(Face& face,
const shared_ptr<CertificateCache>& certificateCache,
const time::milliseconds& graceInterval,
const size_t stepLimit,
const size_t maxTrackedKeys,
const time::system_clock::Duration& keyTimestampTtl)
: Validator(face)
, m_shouldValidate(true)
, m_stepLimit(stepLimit)
, m_certificateCache(certificateCache)
, m_graceInterval(graceInterval < time::milliseconds::zero() ?
DEFAULT_GRACE_INTERVAL : graceInterval)
, m_maxTrackedKeys(maxTrackedKeys)
, m_keyTimestampTtl(keyTimestampTtl)
{
if (m_certificateCache == nullptr)
m_certificateCache = make_shared<CertificateCacheTtl>(ref(face.getIoService()));
}
void
ValidatorConfig::load(const std::string& filename)
{
std::ifstream inputFile;
inputFile.open(filename.c_str());
if (!inputFile.good() || !inputFile.is_open()) {
std::string msg = "Failed to read configuration file: ";
msg += filename;
BOOST_THROW_EXCEPTION(security::conf::Error(msg));
}
load(inputFile, filename);
inputFile.close();
}
void
ValidatorConfig::load(const std::string& input, const std::string& filename)
{
std::istringstream inputStream(input);
load(inputStream, filename);
}
void
ValidatorConfig::load(std::istream& input, const std::string& filename)
{
security::conf::ConfigSection tree;
try {
boost::property_tree::read_info(input, tree);
}
catch (const boost::property_tree::info_parser_error& error) {
std::stringstream msg;
msg << "Failed to parse configuration file";
msg << " " << filename;
msg << " " << error.message() << " line " << error.line();
BOOST_THROW_EXCEPTION(security::conf::Error(msg.str()));
}
load(tree, filename);
}
void
ValidatorConfig::load(const security::conf::ConfigSection& configSection,
const std::string& filename)
{
BOOST_ASSERT(!filename.empty());
reset();
if (configSection.begin() == configSection.end()) {
std::string msg = "Error processing configuration file";
msg += ": ";
msg += filename;
msg += " no data";
BOOST_THROW_EXCEPTION(security::conf::Error(msg));
}
for (security::conf::ConfigSection::const_iterator i = configSection.begin();
i != configSection.end(); ++i) {
const std::string& sectionName = i->first;
const security::conf::ConfigSection& section = i->second;
if (boost::iequals(sectionName, "rule")) {
onConfigRule(section, filename);
}
else if (boost::iequals(sectionName, "trust-anchor")) {
onConfigTrustAnchor(section, filename);
}
else {
std::string msg = "Error processing configuration file";
msg += " ";
msg += filename;
msg += " unrecognized section: " + sectionName;
BOOST_THROW_EXCEPTION(security::conf::Error(msg));
}
}
}
void
ValidatorConfig::onConfigRule(const security::conf::ConfigSection& configSection,
const std::string& filename)
{
using namespace ndn::security::conf;
ConfigSection::const_iterator propertyIt = configSection.begin();
// Get rule.id
if (propertyIt == configSection.end() || !boost::iequals(propertyIt->first, "id"))
BOOST_THROW_EXCEPTION(Error("Expect <rule.id>!"));
std::string ruleId = propertyIt->second.data();
propertyIt++;
// Get rule.for
if (propertyIt == configSection.end() || !boost::iequals(propertyIt->first,"for"))
BOOST_THROW_EXCEPTION(Error("Expect <rule.for> in rule: " + ruleId + "!"));
std::string usage = propertyIt->second.data();
propertyIt++;
bool isForData = false;
if (boost::iequals(usage, "data"))
isForData = true;
else if (boost::iequals(usage, "interest"))
isForData = false;
else
BOOST_THROW_EXCEPTION(Error("Unrecognized <rule.for>: " + usage
+ " in rule: " + ruleId));
// Get rule.filter(s)
std::vector<shared_ptr<Filter>> filters;
for (; propertyIt != configSection.end(); propertyIt++) {
if (!boost::iequals(propertyIt->first, "filter")) {
if (boost::iequals(propertyIt->first, "checker"))
break;
BOOST_THROW_EXCEPTION(Error("Expect <rule.filter> in rule: " + ruleId));
}
filters.push_back(FilterFactory::create(propertyIt->second));
continue;
}
// Get rule.checker(s)
std::vector<shared_ptr<Checker>> checkers;
for (; propertyIt != configSection.end(); propertyIt++) {
if (!boost::iequals(propertyIt->first, "checker"))
BOOST_THROW_EXCEPTION(Error("Expect <rule.checker> in rule: " + ruleId));
checkers.push_back(CheckerFactory::create(propertyIt->second, filename));
continue;
}
// Check other stuff
if (propertyIt != configSection.end())
BOOST_THROW_EXCEPTION(Error("Expect the end of rule: " + ruleId));
if (checkers.empty())
BOOST_THROW_EXCEPTION(Error("No <rule.checker> is specified in rule: " + ruleId));
if (isForData) {
shared_ptr<DataRule> rule = make_shared<DataRule>(ruleId);
for (const auto& filter : filters)
rule->addFilter(filter);
for (const auto& checker : checkers)
rule->addChecker(checker);
m_dataRules.push_back(rule);
}
else {
shared_ptr<InterestRule> rule = make_shared<InterestRule>(ruleId);;
for (const auto& filter : filters)
rule->addFilter(filter);
for (const auto& checker : checkers)
rule->addChecker(checker);
m_interestRules.push_back(rule);
}
}
void
ValidatorConfig::onConfigTrustAnchor(const security::conf::ConfigSection& configSection,
const std::string& filename)
{
using namespace ndn::security::conf;
using namespace boost::filesystem;
ConfigSection::const_iterator propertyIt = configSection.begin();
// Get trust-anchor.type
if (propertyIt == configSection.end() || !boost::iequals(propertyIt->first, "type"))
BOOST_THROW_EXCEPTION(Error("Expect <trust-anchor.type>!"));
std::string type = propertyIt->second.data();
propertyIt++;
if (boost::iequals(type, "file")) {
// Get trust-anchor.file
if (propertyIt == configSection.end() || !boost::iequals(propertyIt->first, "file-name"))
BOOST_THROW_EXCEPTION(Error("Expect <trust-anchor.file-name>!"));
std::string file = propertyIt->second.data();
propertyIt++;
// Check other stuff
if (propertyIt != configSection.end())
BOOST_THROW_EXCEPTION(Error("Expect the end of trust-anchor!"));
path certfilePath = absolute(file, path(filename).parent_path());
auto idCert = io::load<v1::IdentityCertificate>(certfilePath.string());
if (idCert != nullptr) {
BOOST_ASSERT(idCert->getName().size() >= 1);
m_staticContainer.add(idCert);
m_anchors[idCert->getName().getPrefix(-1)] = idCert;
}
else
BOOST_THROW_EXCEPTION(Error("Cannot read certificate from file: " + certfilePath.native()));
return;
}
else if (boost::iequals(type, "base64")) {
// Get trust-anchor.base64-string
if (propertyIt == configSection.end() || !boost::iequals(propertyIt->first, "base64-string"))
BOOST_THROW_EXCEPTION(Error("Expect <trust-anchor.base64-string>!"));
std::stringstream ss(propertyIt->second.data());
propertyIt++;
// Check other stuff
if (propertyIt != configSection.end())
BOOST_THROW_EXCEPTION(Error("Expect the end of trust-anchor!"));
auto idCert = io::load<v1::IdentityCertificate>(ss);
if (idCert != nullptr) {
BOOST_ASSERT(idCert->getName().size() >= 1);
m_staticContainer.add(idCert);
m_anchors[idCert->getName().getPrefix(-1)] = idCert;
}
else
BOOST_THROW_EXCEPTION(Error("Cannot decode certificate from base64-string"));
return;
}
else if (boost::iequals(type, "dir")) {
if (propertyIt == configSection.end() || !boost::iequals(propertyIt->first, "dir"))
BOOST_THROW_EXCEPTION(Error("Expect <trust-anchor.dir>"));
std::string dirString(propertyIt->second.data());
propertyIt++;
if (propertyIt != configSection.end()) {
if (boost::iequals(propertyIt->first, "refresh")) {
using namespace boost::filesystem;
time::nanoseconds refresh = getRefreshPeriod(propertyIt->second.data());
propertyIt++;
if (propertyIt != configSection.end())
BOOST_THROW_EXCEPTION(Error("Expect the end of trust-anchor"));
path dirPath = absolute(dirString, path(filename).parent_path());
m_dynamicContainers.push_back(DynamicTrustAnchorContainer(dirPath, true, refresh));
m_dynamicContainers.rbegin()->setLastRefresh(time::system_clock::now() - refresh);
return;
}
else
BOOST_THROW_EXCEPTION(Error("Expect <trust-anchor.refresh>!"));
}
else {
using namespace boost::filesystem;
path dirPath = absolute(dirString, path(filename).parent_path());
directory_iterator end;
for (directory_iterator it(dirPath); it != end; it++) {
auto idCert = io::load<v1::IdentityCertificate>(it->path().string());
if (idCert != nullptr)
m_staticContainer.add(idCert);
}
return;
}
}
else if (boost::iequals(type, "any")) {
m_shouldValidate = false;
}
else
BOOST_THROW_EXCEPTION(Error("Unsupported trust-anchor.type: " + type));
}
void
ValidatorConfig::reset()
{
if (m_certificateCache != nullptr)
m_certificateCache->reset();
m_interestRules.clear();
m_dataRules.clear();
m_anchors.clear();
m_staticContainer = TrustAnchorContainer();
m_dynamicContainers.clear();
}
bool
ValidatorConfig::isEmpty()
{
return ((m_certificateCache == nullptr || m_certificateCache->isEmpty()) &&
m_interestRules.empty() && m_dataRules.empty() && m_anchors.empty());
}
time::nanoseconds
ValidatorConfig::getRefreshPeriod(std::string inputString)
{
char unit = inputString[inputString.size() - 1];
std::string refreshString = inputString.substr(0, inputString.size() - 1);
uint32_t refreshPeriod = 0;
try {
refreshPeriod = boost::lexical_cast<uint32_t>(refreshString);
}
catch (const boost::bad_lexical_cast&) {
BOOST_THROW_EXCEPTION(Error("Bad number: " + refreshString));
}
if (refreshPeriod == 0)
return getDefaultRefreshPeriod();
switch (unit) {
case 'h':
return time::duration_cast<time::nanoseconds>(time::hours(refreshPeriod));
case 'm':
return time::duration_cast<time::nanoseconds>(time::minutes(refreshPeriod));
case 's':
return time::duration_cast<time::nanoseconds>(time::seconds(refreshPeriod));
default:
BOOST_THROW_EXCEPTION(Error(std::string("Wrong time unit: ") + unit));
}
}
time::nanoseconds
ValidatorConfig::getDefaultRefreshPeriod()
{
return time::duration_cast<time::nanoseconds>(time::seconds(3600));
}
void
ValidatorConfig::refreshAnchors()
{
time::system_clock::TimePoint now = time::system_clock::now();
bool isRefreshed = false;
for (auto cIt = m_dynamicContainers.begin();
cIt != m_dynamicContainers.end() && cIt->getLastRefresh() + cIt->getRefreshPeriod() < now;
cIt++) {
isRefreshed = true;
cIt->refresh();
cIt->setLastRefresh(now);
}
if (isRefreshed) {
m_anchors.clear();
for (const auto& cert : m_staticContainer.getAll()) {
m_anchors[cert->getName().getPrefix(-1)] = cert;
}
for (const auto& container : m_dynamicContainers) {
const CertificateList& certList = container.getAll();
for (const auto& cert :certList) {
m_anchors[cert->getName().getPrefix(-1)] = cert;
}
}
m_dynamicContainers.sort(ValidatorConfig::compareDynamicContainer);
}
}
void
ValidatorConfig::checkPolicy(const Data& data,
int nSteps,
const OnDataValidated& onValidated,
const OnDataValidationFailed& onValidationFailed,
std::vector<shared_ptr<ValidationRequest>>& nextSteps)
{
if (!m_shouldValidate)
return onValidated(data.shared_from_this());
bool isMatched = false;
int8_t checkResult = -1;
for (const auto& dataRule : m_dataRules) {
if (dataRule->match(data)) {
isMatched = true;
checkResult = dataRule->check(data, onValidated, onValidationFailed);
break;
}
}
if (!isMatched)
return onValidationFailed(data.shared_from_this(), "No rule matched!");
if (checkResult == 0) {
const Signature& signature = data.getSignature();
checkSignature(data, signature, nSteps,
onValidated, onValidationFailed, nextSteps);
}
}
void
ValidatorConfig::checkPolicy(const Interest& interest,
int nSteps,
const OnInterestValidated& onValidated,
const OnInterestValidationFailed& onValidationFailed,
std::vector<shared_ptr<ValidationRequest>>& nextSteps)
{
if (!m_shouldValidate)
return onValidated(interest.shared_from_this());
// If interestName has less than 4 name components,
// it is definitely not a signed interest.
if (interest.getName().size() < signed_interest::MIN_LENGTH)
return onValidationFailed(interest.shared_from_this(),
"Interest is not signed: " + interest.getName().toUri());
try {
const Name& interestName = interest.getName();
Signature signature(interestName[signed_interest::POS_SIG_INFO].blockFromValue(),
interestName[signed_interest::POS_SIG_VALUE].blockFromValue());
if (!signature.hasKeyLocator())
return onValidationFailed(interest.shared_from_this(), "No valid KeyLocator");
const KeyLocator& keyLocator = signature.getKeyLocator();
if (keyLocator.getType() != KeyLocator::KeyLocator_Name)
return onValidationFailed(interest.shared_from_this(), "Key Locator is not a name");
Name keyName = v1::IdentityCertificate::certificateNameToPublicKeyName(keyLocator.getName());
bool isMatched = false;
int8_t checkResult = -1;
for (const auto& interestRule : m_interestRules) {
if (interestRule->match(interest)) {
isMatched = true;
checkResult = interestRule->check(interest,
bind(&ValidatorConfig::checkTimestamp, this, _1,
keyName, onValidated, onValidationFailed),
onValidationFailed);
break;
}
}
if (!isMatched)
return onValidationFailed(interest.shared_from_this(), "No rule matched!");
if (checkResult == 0) {
checkSignature<Interest, OnInterestValidated, OnInterestValidationFailed>
(interest, signature, nSteps,
bind(&ValidatorConfig::checkTimestamp, this, _1,
keyName, onValidated, onValidationFailed),
onValidationFailed,
nextSteps);
}
}
catch (const Signature::Error& e) {
return onValidationFailed(interest.shared_from_this(), "No valid signature");
}
catch (const KeyLocator::Error& e){
return onValidationFailed(interest.shared_from_this(), "No valid KeyLocator");
}
catch (const v1::IdentityCertificate::Error& e){
return onValidationFailed(interest.shared_from_this(), "Cannot determine the signing key");
}
catch (const tlv::Error& e){
return onValidationFailed(interest.shared_from_this(), "Cannot decode signature");
}
}
void
ValidatorConfig::checkTimestamp(const shared_ptr<const Interest>& interest,
const Name& keyName,
const OnInterestValidated& onValidated,
const OnInterestValidationFailed& onValidationFailed)
{
const Name& interestName = interest->getName();
time::system_clock::TimePoint interestTime;
try {
interestTime =
time::fromUnixTimestamp(time::milliseconds(interestName.get(-signed_interest::MIN_LENGTH).toNumber()));
}
catch (const tlv::Error& e) {
return onValidationFailed(interest,
"Cannot decode signature related TLVs");
}
time::system_clock::TimePoint currentTime = time::system_clock::now();
LastTimestampMap::iterator timestampIt = m_lastTimestamp.find(keyName);
if (timestampIt == m_lastTimestamp.end()) {
if (!(currentTime - m_graceInterval <= interestTime &&
interestTime <= currentTime + m_graceInterval))
return onValidationFailed(interest,
"The command is not in grace interval: " + interest->getName().toUri());
}
else {
if (interestTime <= timestampIt->second)
return onValidationFailed(interest,
"The command is outdated: " + interest->getName().toUri());
}
// Update timestamp
if (timestampIt == m_lastTimestamp.end()) {
cleanOldKeys();
m_lastTimestamp[keyName] = interestTime;
}
else {
timestampIt->second = interestTime;
}
return onValidated(interest);
}
void
ValidatorConfig::cleanOldKeys()
{
if (m_lastTimestamp.size() < m_maxTrackedKeys)
return;
LastTimestampMap::iterator timestampIt = m_lastTimestamp.begin();
LastTimestampMap::iterator end = m_lastTimestamp.end();
time::system_clock::TimePoint now = time::system_clock::now();
LastTimestampMap::iterator oldestKeyIt = m_lastTimestamp.begin();
time::system_clock::TimePoint oldestTimestamp = oldestKeyIt->second;
while (timestampIt != end) {
if (now - timestampIt->second > m_keyTimestampTtl) {
LastTimestampMap::iterator toDelete = timestampIt;
timestampIt++;
m_lastTimestamp.erase(toDelete);
continue;
}
if (timestampIt->second < oldestTimestamp) {
oldestTimestamp = timestampIt->second;
oldestKeyIt = timestampIt;
}
timestampIt++;
}
if (m_lastTimestamp.size() >= m_maxTrackedKeys)
m_lastTimestamp.erase(oldestKeyIt);
}
void
ValidatorConfig::DynamicTrustAnchorContainer::refresh()
{
using namespace boost::filesystem;
m_certificates.clear();
if (m_isDir) {
directory_iterator end;
for (directory_iterator it(m_path); it != end; it++) {
auto idCert = io::load<v1::IdentityCertificate>(it->path().string());
if (idCert != nullptr)
m_certificates.push_back(idCert);
}
}
else {
auto idCert = io::load<v1::IdentityCertificate>(m_path.string());
if (idCert != nullptr)
m_certificates.push_back(idCert);
}
}
template<class Packet, class OnValidated, class OnFailed>
void
ValidatorConfig::checkSignature(const Packet& packet,
const Signature& signature,
size_t nSteps,
const OnValidated& onValidated,
const OnFailed& onValidationFailed,
std::vector<shared_ptr<ValidationRequest>>& nextSteps)
{
if (signature.getType() == tlv::DigestSha256) {
DigestSha256 sigSha256(signature);
if (verifySignature(packet, sigSha256))
return onValidated(packet.shared_from_this());
else
return onValidationFailed(packet.shared_from_this(), "Sha256 Signature cannot be verified!");
}
try {
switch (signature.getType()) {
case tlv::SignatureSha256WithRsa:
case tlv::SignatureSha256WithEcdsa: {
if (!signature.hasKeyLocator()) {
return onValidationFailed(packet.shared_from_this(),
"Missing KeyLocator in SignatureInfo");
}
break;
}
default:
return onValidationFailed(packet.shared_from_this(), "Unsupported signature type");
}
}
catch (const KeyLocator::Error& e) {
return onValidationFailed(packet.shared_from_this(),
"Cannot decode KeyLocator in public key signature");
}
catch (const tlv::Error& e) {
return onValidationFailed(packet.shared_from_this(), "Cannot decode public key signature");
}
if (signature.getKeyLocator().getType() != KeyLocator::KeyLocator_Name) {
return onValidationFailed(packet.shared_from_this(), "Unsupported KeyLocator type");
}
const Name& keyLocatorName = signature.getKeyLocator().getName();
shared_ptr<const v1::Certificate> trustedCert;
refreshAnchors();
AnchorList::const_iterator it = m_anchors.find(keyLocatorName);
if (m_anchors.end() == it && m_certificateCache != nullptr)
trustedCert = m_certificateCache->getCertificate(keyLocatorName);
else if (m_anchors.end() != it)
trustedCert = it->second;
if (trustedCert != nullptr) {
if (verifySignature(packet, signature, trustedCert->getPublicKeyInfo()))
return onValidated(packet.shared_from_this());
else
return onValidationFailed(packet.shared_from_this(), "Cannot verify signature");
}
else {
if (m_stepLimit == nSteps)
return onValidationFailed(packet.shared_from_this(), "Maximum steps of validation reached");
OnDataValidated onCertValidated =
bind(&ValidatorConfig::onCertValidated<Packet, OnValidated, OnFailed>,
this, _1, packet.shared_from_this(), onValidated, onValidationFailed);
OnDataValidationFailed onCertValidationFailed =
bind(&ValidatorConfig::onCertFailed<Packet, OnFailed>,
this, _1, _2, packet.shared_from_this(), onValidationFailed);
Interest certInterest(keyLocatorName);
auto nextStep = make_shared<ValidationRequest>(certInterest,
onCertValidated,
onCertValidationFailed,
1, nSteps + 1);
nextSteps.push_back(nextStep);
return;
}
return onValidationFailed(packet.shared_from_this(), "Unsupported Signature Type");
}
template<class Packet, class OnValidated, class OnFailed>
void
ValidatorConfig::onCertValidated(const shared_ptr<const Data>& signCertificate,
const shared_ptr<const Packet>& packet,
const OnValidated& onValidated,
const OnFailed& onValidationFailed)
{
if (signCertificate->getContentType() != tlv::ContentType_Key)
return onValidationFailed(packet,
"Cannot retrieve signer's cert: " +
signCertificate->getName().toUri());
shared_ptr<v1::IdentityCertificate> certificate;
try {
certificate = make_shared<v1::IdentityCertificate>(*signCertificate);
}
catch (const tlv::Error&) {
return onValidationFailed(packet,
"Cannot decode signer's cert: " +
signCertificate->getName().toUri());
}
if (!certificate->isTooLate() && !certificate->isTooEarly()) {
if (m_certificateCache != nullptr)
m_certificateCache->insertCertificate(certificate);
if (verifySignature(*packet, certificate->getPublicKeyInfo()))
return onValidated(packet);
else
return onValidationFailed(packet,
"Cannot verify signature: " + packet->getName().toUri());
}
else {
return onValidationFailed(packet,
"Signing certificate " +
signCertificate->getName().toUri() + " is no longer valid.");
}
}
template<class Packet, class OnFailed>
void
ValidatorConfig::onCertFailed(const shared_ptr<const Data>& signCertificate,
const std::string& failureInfo,
const shared_ptr<const Packet>& packet,
const OnFailed& onValidationFailed)
{
onValidationFailed(packet, failureInfo);
}
} // namespace security
} // namespace ndn