blob: 2222fc21c2ae40f0ac9dfee20050773b8671058d [file] [log] [blame]
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
* Copyright (c) 2013-2024 Regents of the University of California.
*
* This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
*
* ndn-cxx library is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later version.
*
* ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received copies of the GNU General Public License and GNU Lesser
* General Public License along with ndn-cxx, e.g., in COPYING.md file. If not, see
* <http://www.gnu.org/licenses/>.
*
* See AUTHORS.md for complete list of ndn-cxx authors and contributors.
*/
#include "ndn-cxx/security/validation-policy-config.hpp"
#include "ndn-cxx/security/validator.hpp"
#include "ndn-cxx/util/io.hpp"
#include <boost/algorithm/string/predicate.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/property_tree/info_parser.hpp>
#include <filesystem>
#include <fstream>
namespace ndn::security::validator_config {
void
ValidationPolicyConfig::load(const std::string& filename)
{
std::ifstream inputFile(filename);
if (!inputFile) {
NDN_THROW(Error("Failed to read configuration file: " + filename));
}
load(inputFile, filename);
}
void
ValidationPolicyConfig::load(const std::string& input, const std::string& filename)
{
std::istringstream inputStream(input);
load(inputStream, filename);
}
void
ValidationPolicyConfig::load(std::istream& input, const std::string& filename)
{
ConfigSection tree;
try {
boost::property_tree::read_info(input, tree);
}
catch (const boost::property_tree::info_parser_error& e) {
NDN_THROW(Error("Failed to parse configuration file " + filename +
" line " + to_string(e.line()) + ": " + e.message()));
}
load(tree, filename);
}
void
ValidationPolicyConfig::load(const ConfigSection& configSection, const std::string& filename)
{
BOOST_ASSERT(!filename.empty());
if (m_validator == nullptr) {
NDN_THROW(Error("Validator instance not assigned on the policy"));
}
if (m_isConfigured) {
m_shouldBypass = false;
m_dataRules.clear();
m_interestRules.clear();
m_validator->resetAnchors();
m_validator->resetVerifiedCertificates();
}
m_isConfigured = true;
for (const auto& subSection : configSection) {
const std::string& sectionName = subSection.first;
const ConfigSection& section = subSection.second;
if (boost::iequals(sectionName, "rule")) {
auto rule = Rule::create(section, filename);
if (rule->getPktType() == tlv::Data) {
m_dataRules.push_back(std::move(rule));
}
else if (rule->getPktType() == tlv::Interest) {
m_interestRules.push_back(std::move(rule));
}
}
else if (boost::iequals(sectionName, "trust-anchor")) {
processConfigTrustAnchor(section, filename);
}
else {
NDN_THROW(Error("Error processing configuration file " + filename +
": unrecognized section " + sectionName));
}
}
}
void
ValidationPolicyConfig::processConfigTrustAnchor(const ConfigSection& configSection,
const std::string& filename)
{
auto propertyIt = configSection.begin();
// Get trust-anchor.type
if (propertyIt == configSection.end() || !boost::iequals(propertyIt->first, "type")) {
NDN_THROW(Error("Expecting <trust-anchor.type>"));
}
auto baseDir = std::filesystem::absolute(filename).parent_path().lexically_normal();
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")) {
NDN_THROW(Error("Expecting <trust-anchor.file-name>"));
}
std::string file = propertyIt->second.data();
propertyIt++;
time::nanoseconds refresh = getRefreshPeriod(propertyIt, configSection.end());
if (propertyIt != configSection.end())
NDN_THROW(Error("Expecting end of <trust-anchor>"));
m_validator->loadAnchor(file, baseDir / file, refresh, false);
}
else if (boost::iequals(type, "base64")) {
// Get trust-anchor.base64-string
if (propertyIt == configSection.end() || !boost::iequals(propertyIt->first, "base64-string"))
NDN_THROW(Error("Expecting <trust-anchor.base64-string>"));
std::stringstream ss(propertyIt->second.data());
propertyIt++;
if (propertyIt != configSection.end())
NDN_THROW(Error("Expecting end of <trust-anchor>"));
auto idCert = io::load<Certificate>(ss);
if (idCert != nullptr) {
m_validator->loadAnchor("", std::move(*idCert));
}
else {
NDN_THROW(Error("Cannot decode certificate from base64-string"));
}
}
else if (boost::iequals(type, "dir")) {
if (propertyIt == configSection.end() || !boost::iequals(propertyIt->first, "dir"))
NDN_THROW(Error("Expecting <trust-anchor.dir>"));
std::string dirString(propertyIt->second.data());
propertyIt++;
time::nanoseconds refresh = getRefreshPeriod(propertyIt, configSection.end());
if (propertyIt != configSection.end())
NDN_THROW(Error("Expecting end of <trust-anchor>"));
m_validator->loadAnchor(dirString, baseDir / dirString, refresh, true);
}
else if (boost::iequals(type, "any")) {
m_shouldBypass = true;
}
else {
NDN_THROW(Error("Unrecognized <trust-anchor.type>: " + type));
}
}
time::nanoseconds
ValidationPolicyConfig::getRefreshPeriod(ConfigSection::const_iterator& it,
const ConfigSection::const_iterator& end)
{
auto refresh = time::nanoseconds::max();
if (it == end) {
return refresh;
}
if (!boost::iequals(it->first, "refresh")) {
NDN_THROW(Error("Expecting <trust-anchor.refresh>"));
}
std::string inputString = it->second.data();
++it;
char unit = inputString[inputString.size() - 1];
std::string refreshString = inputString.substr(0, inputString.size() - 1);
int32_t refreshPeriod = -1;
try {
refreshPeriod = boost::lexical_cast<int32_t>(refreshString);
}
catch (const boost::bad_lexical_cast&) {
// pass
}
if (refreshPeriod < 0) {
NDN_THROW(Error("Bad refresh value: " + refreshString));
}
if (refreshPeriod == 0) {
return getDefaultRefreshPeriod();
}
switch (unit) {
case 'h':
return time::hours(refreshPeriod);
case 'm':
return time::minutes(refreshPeriod);
case 's':
return time::seconds(refreshPeriod);
default:
NDN_THROW(Error("Bad refresh time unit: "s + unit));
}
}
time::nanoseconds
ValidationPolicyConfig::getDefaultRefreshPeriod()
{
return 1_h;
}
void
ValidationPolicyConfig::checkPolicy(const Data& data, const shared_ptr<ValidationState>& state,
const ValidationContinuation& continueValidation)
{
BOOST_ASSERT_MSG(!hasInnerPolicy(), "ValidationPolicyConfig must be a terminal inner policy");
if (m_shouldBypass) {
return continueValidation(nullptr, 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, sigType, data.getName(), klName, state)) {
return continueValidation(make_shared<CertificateRequest>(klName), state);
}
// rule->check calls state->fail(...) if the check fails
return;
}
}
return state->fail({ValidationError::POLICY_ERROR,
"No rule matched for data `" + data.getName().toUri() + "`"});
}
void
ValidationPolicyConfig::checkPolicy(const Interest& interest, const shared_ptr<ValidationState>& state,
const ValidationContinuation& continueValidation)
{
BOOST_ASSERT_MSG(!hasInnerPolicy(), "ValidationPolicyConfig must be a terminal inner policy");
if (m_shouldBypass) {
return continueValidation(nullptr, 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)) {
if (rule->check(tlv::Interest, sigType, interest.getName(), klName, state)) {
return continueValidation(make_shared<CertificateRequest>(klName), state);
}
// rule->check calls state->fail(...) if the check fails
return;
}
}
return state->fail({ValidationError::POLICY_ERROR,
"No rule matched for interest `" + interest.getName().toUri() + "`"});
}
} // namespace ndn::security::validator_config