security: Convert ValidatorConfig to ValidationPolicyConfig

The security API also provides a convenience ValidatorConfig helper.

Change-Id: Ic86dec4904b917361cb4740204de4b6710d2a386
Refs: #3920
diff --git a/src/security/v2/validation-policy-config.cpp b/src/security/v2/validation-policy-config.cpp
new file mode 100644
index 0000000..ba87a23
--- /dev/null
+++ b/src/security/v2/validation-policy-config.cpp
@@ -0,0 +1,314 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2017 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 "validation-policy-config.hpp"
+#include "validator.hpp"
+#include "../../util/io.hpp"
+
+#include <boost/algorithm/string.hpp>
+#include <boost/filesystem.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/property_tree/info_parser.hpp>
+
+namespace ndn {
+namespace security {
+namespace v2 {
+namespace validator_config {
+
+ValidationPolicyConfig::ValidationPolicyConfig()
+  : m_shouldBypass(false)
+  , m_isConfigured(false)
+{
+}
+
+void
+ValidationPolicyConfig::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(Error(msg));
+  }
+  load(inputFile, filename);
+  inputFile.close();
+}
+
+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& error) {
+    std::stringstream msg;
+    msg << "Failed to parse configuration file";
+    msg << " " << filename;
+    msg << " " << error.message() << " line " << error.line();
+    BOOST_THROW_EXCEPTION(Error(msg.str()));
+  }
+
+  load(tree, filename);
+}
+
+void
+ValidationPolicyConfig::load(const ConfigSection& configSection,
+                             const std::string& filename)
+{
+  if (m_isConfigured) {
+    BOOST_THROW_EXCEPTION(std::logic_error("ValidationPolicyConfig can be configured only once"));
+  }
+  m_isConfigured = true;
+
+  BOOST_ASSERT(!filename.empty());
+
+  if (configSection.begin() == configSection.end()) {
+    std::string msg = "Error processing configuration file";
+    msg += ": ";
+    msg += filename;
+    msg += " no data";
+    BOOST_THROW_EXCEPTION(Error(msg));
+  }
+
+  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 {
+      std::string msg = "Error processing configuration file";
+      msg += " ";
+      msg += filename;
+      msg += " unrecognized section: " + sectionName;
+      BOOST_THROW_EXCEPTION(Error(msg));
+    }
+  }
+}
+
+void
+ValidationPolicyConfig::processConfigTrustAnchor(const ConfigSection& configSection, const std::string& filename)
+{
+  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("Expecting <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("Expecting <trust-anchor.file-name>"));
+    }
+
+    std::string file = propertyIt->second.data();
+    propertyIt++;
+
+    time::nanoseconds refresh = getRefreshPeriod(propertyIt, configSection.end());
+    if (propertyIt != configSection.end()) {
+      BOOST_THROW_EXCEPTION(Error("Expect the end of trust-anchor!"));
+    }
+
+    m_validator->loadAnchor(filename, absolute(file, path(filename).parent_path()).string(),
+                            refresh, false);
+    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("Expecting <trust-anchor.base64-string>"));
+
+    std::stringstream ss(propertyIt->second.data());
+    propertyIt++;
+
+    // Check other stuff
+    if (propertyIt != configSection.end())
+      BOOST_THROW_EXCEPTION(Error("Expecting the end of trust-anchor"));
+
+    auto idCert = io::load<Certificate>(ss);
+    if (idCert != nullptr) {
+      m_validator->loadAnchor("", std::move(*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++;
+
+    time::nanoseconds refresh = getRefreshPeriod(propertyIt, configSection.end());
+    if (propertyIt != configSection.end()) {
+      BOOST_THROW_EXCEPTION(Error("Expecting the end of trust-anchor"));
+    }
+
+    path dirPath = absolute(dirString, path(filename).parent_path());
+    m_validator->loadAnchor(dirString, dirPath.string(), refresh, true);
+    return;
+  }
+  else if (boost::iequals(type, "any")) {
+    m_shouldBypass = true;
+  }
+  else {
+    BOOST_THROW_EXCEPTION(Error("Unsupported trust-anchor.type: " + type));
+  }
+}
+
+time::nanoseconds
+ValidationPolicyConfig::getRefreshPeriod(ConfigSection::const_iterator& it,
+                                         const ConfigSection::const_iterator& end)
+{
+  time::nanoseconds refresh = time::nanoseconds::max();
+  if (it == end) {
+    return refresh;
+  }
+
+  if (!boost::iequals(it->first, "refresh")) {
+    BOOST_THROW_EXCEPTION(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);
+
+  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
+ValidationPolicyConfig::getDefaultRefreshPeriod()
+{
+  return time::duration_cast<time::nanoseconds>(time::seconds(3600));
+}
+
+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, *state);
+  if (!state->getOutcome()) { // already failed
+    return;
+  }
+
+  for (const auto& rule : m_dataRules) {
+    if (rule->match(tlv::Data, data.getName())) {
+      if (rule->check(tlv::Data, data.getName(), klName, state)) {
+        return continueValidation(make_shared<CertificateRequest>(Interest(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);
+  }
+
+  Name klName = getKeyLocatorName(interest, *state);
+  if (!state->getOutcome()) { // already failed
+    return;
+  }
+
+  for (const auto& rule : m_interestRules) {
+    if (rule->match(tlv::Interest, interest.getName())) {
+      if (rule->check(tlv::Interest, interest.getName(), klName, state)) {
+        return continueValidation(make_shared<CertificateRequest>(Interest(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 validator_config
+} // namespace v2
+} // namespace security
+} // namespace ndn