security: Add configuration based validator

configuration file format can be found at: http://redmine.named-data.net/projects/ndn-cpp-dev/wiki/CommandValidatorConf

Change-Id: Icc2725f349aed7513f35f2cccdcd4463fadeef31
diff --git a/src/security/validator-config.cpp b/src/security/validator-config.cpp
new file mode 100644
index 0000000..2085daf
--- /dev/null
+++ b/src/security/validator-config.cpp
@@ -0,0 +1,352 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
+/**
+ * Copyright (C) 2013 Regents of the University of California.
+ * See COPYING for copyright and distribution information.
+ */
+
+#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 {
+
+const shared_ptr<CertificateCache> ValidatorConfig::DEFAULT_CERTIFICATE_CACHE;
+
+ValidatorConfig::ValidatorConfig(shared_ptr<Face> face,
+                                 shared_ptr<CertificateCache> certificateCache,
+                                 const int stepLimit)
+  : Validator(face)
+  , m_stepLimit(stepLimit)
+  , m_certificateCache(certificateCache)
+{
+  if (!static_cast<bool>(face))
+    throw Error("Face is not set!");
+
+  if (!static_cast<bool>(m_certificateCache))
+    m_certificateCache = make_shared<CertificateCacheTtl>(m_face->ioService());
+}
+
+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;
+      throw 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();
+      throw security::conf::Error(msg.str());
+    }
+
+  process(tree, filename);
+}
+
+void
+ValidatorConfig::process(const security::conf::ConfigSection& configSection,
+                         const std::string& filename)
+{
+  BOOST_ASSERT(!filename.empty());
+
+  if (configSection.begin() == configSection.end())
+    {
+      std::string msg = "Error processing configuration file";
+      msg += ": ";
+      msg += filename;
+      msg += " no data";
+      throw 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;
+          throw 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"))
+    throw Error("Expect <rule.id>!");
+
+  std::string ruleId = propertyIt->second.data();
+  propertyIt++;
+
+  // Get rule.for
+  if (propertyIt == configSection.end() || !boost::iequals(propertyIt->first,"for"))
+    throw Error("Expect <rule.for> in rule: " + ruleId + "!");
+
+  std::string usage = propertyIt->second.data();
+  propertyIt++;
+
+  bool isForData;
+  if (boost::iequals(usage, "data"))
+    isForData = true;
+  else if (boost::iequals(usage, "interest"))
+    isForData = false;
+  else
+    throw 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;
+          throw 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"))
+        throw Error("Expect <rule.checker> in rule: " + ruleId);
+
+      checkers.push_back(CheckerFactory::create(propertyIt->second, filename));
+      continue;
+    }
+
+  // Check other stuff
+  if (propertyIt != configSection.end())
+    throw Error("Expect the end of rule: " + ruleId);
+
+  if (checkers.size() == 0)
+    throw Error("No <rule.checker> is specified in rule: " + ruleId);
+
+  if (isForData)
+    {
+      shared_ptr<DataRule> rule(new DataRule(ruleId));
+      for (size_t i = 0; i < filters.size(); i++)
+        rule->addFilter(filters[i]);
+      for (size_t i = 0; i < checkers.size(); i++)
+        rule->addChecker(checkers[i]);
+
+      m_dataRules.push_back(rule);
+    }
+  else
+    {
+      shared_ptr<InterestRule> rule(new InterestRule(ruleId));
+      for (size_t i = 0; i < filters.size(); i++)
+        rule->addFilter(filters[i]);
+      for (size_t i = 0; i < checkers.size(); i++)
+        rule->addChecker(checkers[i]);
+
+      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"))
+    throw 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"))
+        throw Error("Expect <trust-anchor.file-name>!");
+
+      std::string file = propertyIt->second.data();
+      propertyIt++;
+
+      // Check other stuff
+      if (propertyIt != configSection.end())
+        throw Error("Expect the end of trust-anchor!");
+
+      path certfilePath = absolute(file, path(filename).parent_path());
+      shared_ptr<IdentityCertificate> idCert =
+        io::load<IdentityCertificate>(certfilePath.string());
+
+      if (static_cast<bool>(idCert))
+        {
+          BOOST_ASSERT(idCert->getName().size() >= 1);
+          m_anchors[idCert->getName().getPrefix(-1)] = idCert;
+        }
+      else
+        throw 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"))
+        throw Error("Expect <trust-anchor.base64-string>!");
+
+      std::stringstream ss(propertyIt->second.data());
+      propertyIt++;
+
+      // Check other stuff
+      if (propertyIt != configSection.end())
+        throw Error("Expect the end of trust-anchor!");
+
+      shared_ptr<IdentityCertificate> idCert = io::load<IdentityCertificate>(ss);
+
+      if (static_cast<bool>(idCert))
+        {
+          BOOST_ASSERT(idCert->getName().size() >= 1);
+          m_anchors[idCert->getName().getPrefix(-1)] = idCert;
+        }
+      else
+        throw Error("Cannot decode certificate from base64-string");
+
+      return;
+    }
+  else
+    throw Error("Unsupported trust-anchor.type: " + type);
+}
+
+void
+ValidatorConfig::checkPolicy(const Data& data,
+                             int stepCount,
+                             const OnDataValidated& onValidated,
+                             const OnDataValidationFailed& onValidationFailed,
+                             std::vector<shared_ptr<ValidationRequest> >& nextSteps)
+{
+  if (m_stepLimit == stepCount)
+    return onValidationFailed(data.shared_from_this(),
+                              "Maximum steps of validation reached");
+
+  bool isMatched = false;
+  int8_t checkResult = -1;
+
+  for (DataRuleList::iterator it = m_dataRules.begin();
+       it != m_dataRules.end(); it++)
+    {
+      if ((*it)->match(data))
+        {
+          isMatched = true;
+          checkResult = (*it)->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, stepCount,
+                     onValidated, onValidationFailed, nextSteps);
+    }
+}
+
+void
+ValidatorConfig::checkPolicy(const Interest& interest,
+                             int stepCount,
+                             const OnInterestValidated& onValidated,
+                             const OnInterestValidationFailed& onValidationFailed,
+                             std::vector<shared_ptr<ValidationRequest> >& nextSteps)
+{
+  if (m_stepLimit == stepCount)
+    return onValidationFailed(interest.shared_from_this(),
+                              "Maximum steps of validation reached");
+
+  bool isMatched = false;
+  int8_t checkResult = -1;
+
+  for (InterestRuleList::iterator it = m_interestRules.begin();
+       it != m_interestRules.end(); it++)
+    {
+      if ((*it)->match(interest))
+        {
+          isMatched = true;
+          checkResult = (*it)->check(interest, onValidated, onValidationFailed);
+          break;
+        }
+    }
+
+  if (!isMatched)
+    return onValidationFailed(interest.shared_from_this(), "No rule matched!");
+
+  if (checkResult == 0)
+    {
+      const Name& interestName = interest.getName();
+      Name signedName = interestName.getPrefix(-2);
+      Signature signature(interestName[-2].blockFromValue(),
+                          interestName[-1].blockFromValue());
+
+      checkSignature(interest, signature, stepCount,
+                     onValidated, onValidationFailed, nextSteps);
+    }
+}
+
+
+} // namespace ndn