security: Add 'type dir' trust-anchor in ValidatorConfig

Refs: #1483

Change-Id: I4a34947026edc929624b64fe0a996df36d3fb8ed
diff --git a/docs/tutorials/security-validator-config.rst b/docs/tutorials/security-validator-config.rst
index ee164f5..b3b99dc 100644
--- a/docs/tutorials/security-validator-config.rst
+++ b/docs/tutorials/security-validator-config.rst
@@ -411,7 +411,31 @@
       base64-string "Bv0DGwdG...amHFvHIMDw=="
     }
 
-There is another special trust anchor "any".
+You may also specify a trust-anchor directory. All certificates under this
+directory are taken as trust anchors. For example, if all trust anchors are
+put into ``/usr/local/etc/ndn/keys``.
+
+::
+
+    trust-anchor
+    {
+      type dir
+      file-name /usr/local/etc/ndn/keys
+    }
+
+If certificates under the directory might be changed during runtime, you can
+set a refresh period, such as
+
+::
+
+    trust-anchor
+    {
+      type dir
+      file-name /usr/local/etc/ndn/keys
+      refresh 1h ; refresh certificates every hour, other units include m (for minutes) and s (for seconds)
+    }
+
+There is another special trust anchor **any**.
 As long as such a trust-anchor is defined in config file,
 packet validation will be turned off.
 
@@ -426,6 +450,7 @@
       type any
     }
 
+
 Example Configuration For NLSR
 ------------------------------
 
diff --git a/src/security/validator-config.cpp b/src/security/validator-config.cpp
index b23a47c..a0a35d8 100644
--- a/src/security/validator-config.cpp
+++ b/src/security/validator-config.cpp
@@ -252,6 +252,7 @@
       if (static_cast<bool>(idCert))
         {
           BOOST_ASSERT(idCert->getName().size() >= 1);
+          m_staticContainer.add(idCert);
           m_anchors[idCert->getName().getPrefix(-1)] = idCert;
         }
       else
@@ -278,6 +279,7 @@
       if (static_cast<bool>(idCert))
         {
           BOOST_ASSERT(idCert->getName().size() >= 1);
+          m_staticContainer.add(idCert);
           m_anchors[idCert->getName().getPrefix(-1)] = idCert;
         }
       else
@@ -285,6 +287,57 @@
 
       return;
     }
+  else if (boost::iequals(type, "dir"))
+    {
+      if (propertyIt == configSection.end() || !boost::iequals(propertyIt->first, "dir"))
+        throw 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())
+                throw 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
+            throw 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++)
+            {
+              shared_ptr<IdentityCertificate> idCert =
+                io::load<IdentityCertificate>(it->path().string());
+
+              if (static_cast<bool>(idCert))
+                m_staticContainer.add(idCert);
+            }
+
+          return;
+        }
+    }
   else if (boost::iequals(type, "any"))
     {
       m_shouldValidate = false;
@@ -293,6 +346,84 @@
     throw Error("Unsupported trust-anchor.type: " + type);
 }
 
+time::nanoseconds
+ValidatorConfig::getRefreshPeriod(std::string inputString)
+{
+  char unit = inputString[inputString.size() - 1];
+  std::string refreshString = inputString.substr(0, inputString.size() - 1);
+
+  uint32_t number;
+
+  try
+    {
+      number = boost::lexical_cast<uint32_t>(refreshString);
+    }
+  catch (boost::bad_lexical_cast&)
+    {
+      throw Error("Bad number: " + refreshString);
+    }
+
+  if (number == 0)
+    return getDefaultRefreshPeriod();
+
+  switch (unit)
+    {
+    case 'h':
+      return time::duration_cast<time::nanoseconds>(time::hours(number));
+    case 'm':
+      return time::duration_cast<time::nanoseconds>(time::minutes(number));
+    case 's':
+      return time::duration_cast<time::nanoseconds>(time::seconds(number));
+    default:
+      throw Error(std::string("Wrong time unit: ") + unit);
+    }
+}
+
+void
+ValidatorConfig::refreshAnchors()
+{
+  time::system_clock::TimePoint now = time::system_clock::now();
+
+  bool isRefreshed = false;
+
+  for (DynamicContainers::iterator cIt = m_dynamicContainers.begin();
+       cIt != m_dynamicContainers.end(); cIt++)
+    {
+      if (cIt->getLastRefresh() + cIt->getRefreshPeriod() < now)
+        {
+          isRefreshed = true;
+          cIt->refresh();
+          cIt->setLastRefresh(now);
+        }
+      else
+        break;
+    }
+
+  if (isRefreshed)
+    {
+      m_anchors.clear();
+
+      for (CertificateList::const_iterator it = m_staticContainer.getAll().begin();
+           it != m_staticContainer.getAll().end(); it++)
+        {
+          m_anchors[(*it)->getName().getPrefix(-1)] = (*it);
+        }
+
+      for (DynamicContainers::iterator cIt = m_dynamicContainers.begin();
+           cIt != m_dynamicContainers.end(); cIt++)
+        {
+          const CertificateList& certList = cIt->getAll();
+
+          for (CertificateList::const_iterator it = certList.begin();
+               it != certList.end(); it++)
+            {
+              m_anchors[(*it)->getName().getPrefix(-1)] = (*it);
+            }
+        }
+      m_dynamicContainers.sort(ValidatorConfig::compareDynamicContainer);
+    }
+}
+
 void
 ValidatorConfig::checkPolicy(const Data& data,
                              int nSteps,
@@ -303,10 +434,6 @@
   if (!m_shouldValidate)
     return onValidated(data.shared_from_this());
 
-  if (m_stepLimit == nSteps)
-    return onValidationFailed(data.shared_from_this(),
-                              "Maximum steps of validation reached");
-
   bool isMatched = false;
   int8_t checkResult = -1;
 
@@ -342,10 +469,6 @@
   if (!m_shouldValidate)
     return onValidated(interest.shared_from_this());
 
-  if (m_stepLimit == nSteps)
-    return onValidationFailed(interest.shared_from_this(),
-                              "Maximum steps of validation reached");
-
   bool isMatched = false;
   int8_t checkResult = -1;
 
@@ -375,5 +498,35 @@
     }
 }
 
+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++)
+        {
+          shared_ptr<IdentityCertificate> idCert =
+            io::load<IdentityCertificate>(it->path().string());
+
+          if (static_cast<bool>(idCert))
+            m_certificates.push_back(idCert);
+        }
+    }
+  else
+    {
+      shared_ptr<IdentityCertificate> idCert =
+        io::load<IdentityCertificate>(m_path.string());
+
+      if (static_cast<bool>(idCert))
+        m_certificates.push_back(idCert);
+    }
+}
+
 
 } // namespace ndn
diff --git a/src/security/validator-config.hpp b/src/security/validator-config.hpp
index 6759ee1..a55797a 100644
--- a/src/security/validator-config.hpp
+++ b/src/security/validator-config.hpp
@@ -33,6 +33,7 @@
 
 class ValidatorConfig : public Validator
 {
+
 public:
   class Error : public Validator::Error
   {
@@ -122,12 +123,95 @@
   onConfigTrustAnchor(const security::conf::ConfigSection& section,
                       const std::string& filename);
 
+  time::nanoseconds
+  getRefreshPeriod(std::string refreshString);
+
+  inline time::nanoseconds
+  getDefaultRefreshPeriod();
+
+  void
+  refreshAnchors();
+
+
 private:
+
+  class TrustAnchorContainer
+  {
+  public:
+    TrustAnchorContainer()
+    {
+    }
+
+    const std::list<shared_ptr<IdentityCertificate> >&
+    getAll() const
+    {
+      return m_certificates;
+    }
+
+    void
+    add(shared_ptr<IdentityCertificate> certificate)
+    {
+      m_certificates.push_back(certificate);
+    }
+
+  protected:
+    std::list<shared_ptr<IdentityCertificate> > m_certificates;
+  };
+
+  class DynamicTrustAnchorContainer : public TrustAnchorContainer
+  {
+  public:
+    DynamicTrustAnchorContainer(const boost::filesystem::path& path, bool isDir,
+                                time::nanoseconds refreshPeriod)
+      : m_path(path)
+      , m_isDir(isDir)
+      , m_refreshPeriod(refreshPeriod)
+    {
+    }
+
+    void
+    setLastRefresh(const time::system_clock::TimePoint& lastRefresh)
+    {
+      m_lastRefresh = lastRefresh;
+    }
+
+    const time::system_clock::TimePoint&
+    getLastRefresh() const
+    {
+      return m_lastRefresh;
+    }
+
+    const time::nanoseconds&
+    getRefreshPeriod() const
+    {
+      return m_refreshPeriod;
+    }
+
+    void
+    refresh();
+
+  private:
+    boost::filesystem::path m_path;
+    bool m_isDir;
+
+    time::system_clock::TimePoint m_lastRefresh;
+    time::nanoseconds m_refreshPeriod;
+  };
+
   typedef security::conf::Rule<Interest> InterestRule;
   typedef security::conf::Rule<Data>     DataRule;
   typedef std::vector<shared_ptr<InterestRule> > InterestRuleList;
   typedef std::vector<shared_ptr<DataRule> >     DataRuleList;
   typedef std::map<Name, shared_ptr<IdentityCertificate> > AnchorList;
+  typedef std::list<DynamicTrustAnchorContainer> DynamicContainers; // sorted by m_lastRefresh
+  typedef std::list<shared_ptr<IdentityCertificate> > CertificateList;
+
+  static inline bool
+  compareDynamicContainer(const DynamicTrustAnchorContainer& containerA,
+                          const DynamicTrustAnchorContainer& containerB)
+  {
+    return (containerA.getLastRefresh() < containerB.getLastRefresh());
+  }
 
   /**
    * @brief gives whether validation should be preformed
@@ -141,7 +225,11 @@
 
   InterestRuleList m_interestRules;
   DataRuleList m_dataRules;
+
   AnchorList m_anchors;
+  TrustAnchorContainer m_staticContainer;
+  DynamicContainers m_dynamicContainers;
+
 };
 
 inline void
@@ -150,7 +238,12 @@
   m_certificateCache->reset();
   m_interestRules.clear();
   m_dataRules.clear();
+
   m_anchors.clear();
+
+  m_staticContainer = TrustAnchorContainer();
+
+  m_dynamicContainers.clear();
 }
 
 inline bool
@@ -191,6 +284,8 @@
 
       shared_ptr<const Certificate> trustedCert;
 
+      refreshAnchors();
+
       AnchorList::const_iterator it = m_anchors.find(keyLocatorName);
       if (m_anchors.end() == it)
         trustedCert = m_certificateCache->getCertificate(keyLocatorName);
@@ -207,6 +302,10 @@
         }
       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);
@@ -270,6 +369,12 @@
   onValidationFailed(packet, failureInfo);
 }
 
+inline time::nanoseconds
+ValidatorConfig::getDefaultRefreshPeriod()
+{
+  return time::duration_cast<time::nanoseconds>(time::seconds(3600));
+}
+
 } // namespace ndn
 
 #endif // NDN_SECURITY_VALIDATOR_CONFIG_HPP
diff --git a/tests/integrated/test-validator-config.cpp b/tests/integrated/test-validator-config.cpp
index 1f118af..800fc2a 100644
--- a/tests/integrated/test-validator-config.cpp
+++ b/tests/integrated/test-validator-config.cpp
@@ -979,7 +979,7 @@
   boost::filesystem::remove(CERT_PATH);
 }
 
-BOOST_AUTO_TEST_CASE(Wildcard)
+BOOST_AUTO_TEST_CASE(TrustAnchorWildcard)
 {
   KeyChain keyChain;
 
@@ -1013,6 +1013,156 @@
 }
 
 
+
+struct DirTestFixture
+{
+  DirTestFixture()
+    : m_scheduler(m_face.getIoService())
+    , m_validator(m_face, ValidatorConfig::DEFAULT_CERTIFICATE_CACHE, 0)
+  {
+    m_certDirPath = (boost::filesystem::current_path() / std::string("test-cert-dir"));
+    boost::filesystem::create_directory(m_certDirPath);
+
+    m_firstCertPath = (boost::filesystem::current_path() /
+                       std::string("test-cert-dir") /
+                       std::string("trust-anchor-1.cert"));
+
+    m_secondCertPath = (boost::filesystem::current_path() /
+                        std::string("test-cert-dir") /
+                        std::string("trust-anchor-2.cert"));
+
+    m_firstIdentity = Name("/TestValidatorConfig/Dir/First");
+    BOOST_REQUIRE_NO_THROW(m_keyChain.createIdentity(m_firstIdentity));
+    Name firstCertName = m_keyChain.getDefaultCertificateNameForIdentity(m_firstIdentity);
+    m_firstCert = m_keyChain.getCertificate(firstCertName);
+    io::save(*m_firstCert, m_firstCertPath.string());
+
+    m_secondIdentity = Name("/TestValidatorConfig/Dir/Second");
+    BOOST_REQUIRE_NO_THROW(m_keyChain.createIdentity(m_secondIdentity));
+    Name secondCertName = m_keyChain.getDefaultCertificateNameForIdentity(m_secondIdentity);
+    m_secondCert = m_keyChain.getCertificate(secondCertName);
+  }
+
+  ~DirTestFixture()
+  {
+    m_keyChain.deleteIdentity(m_firstIdentity);
+    m_keyChain.deleteIdentity(m_secondIdentity);
+
+    boost::filesystem::remove_all(m_certDirPath);
+  }
+
+  void
+  insertSecondTrustAnchor()
+  {
+    io::save(*m_secondCert, m_secondCertPath.string());
+  }
+
+  void
+  validate(shared_ptr<Data> data)
+  {
+    m_validator.validate(*data,
+                         bind(&onValidated, _1),
+                         bind(&onValidationFailed, _1, _2));
+  }
+
+  void
+  invalidate(shared_ptr<Data> data)
+  {
+    m_validator.validate(*data,
+                         bind(&onIntentionalFailureValidated, _1),
+                         bind(&onIntentionalFailureInvalidated, _1, _2));
+  }
+
+  void
+  terminate()
+  {
+    m_face.getIoService().stop();
+  }
+
+protected:
+
+  KeyChain m_keyChain;
+
+  boost::filesystem::path m_certDirPath;
+  boost::filesystem::path m_firstCertPath;
+  boost::filesystem::path m_secondCertPath;
+
+  Name m_firstIdentity;
+  Name m_secondIdentity;
+
+  shared_ptr<IdentityCertificate> m_firstCert;
+  shared_ptr<IdentityCertificate> m_secondCert;
+
+  Face m_face;
+  Scheduler m_scheduler;
+  ValidatorConfig m_validator;
+};
+
+BOOST_FIXTURE_TEST_CASE(TrustAnchorDir, DirTestFixture)
+{
+  Name dataName1("/any/data/1");
+  shared_ptr<Data> data1 = make_shared<Data>(dataName1);
+  BOOST_CHECK_NO_THROW(m_keyChain.signByIdentity(*data1, m_firstIdentity));
+
+  Name dataName2("/any/data/2");
+  shared_ptr<Data> data2 = make_shared<Data>(dataName2);
+  BOOST_CHECK_NO_THROW(m_keyChain.signByIdentity(*data2, m_secondIdentity));
+
+  std::string CONFIG =
+    "rule\n"
+    "{\n"
+    "  id \"Any Rule\"\n"
+    "  for data\n"
+    "  filter\n"
+    "  {\n"
+    "    type name\n"
+    "    regex ^<>*$\n"
+    "  }\n"
+    "  checker\n"
+    "  {\n"
+    "    type customized\n"
+    "    sig-type rsa-sha256\n"
+    "    key-locator\n"
+    "    {\n"
+    "      type name\n"
+    "      regex ^<>*$\n"
+    "    }\n"
+    "  }\n"
+    "}\n"
+    "trust-anchor\n"
+    "{\n"
+    "  type dir\n"
+    "  dir test-cert-dir\n"
+    "  refresh 1s\n"
+    "}\n";
+
+  const boost::filesystem::path CONFIG_PATH =
+    (boost::filesystem::current_path() / std::string("unit-test-nfd.conf"));
+
+
+  m_validator.load(CONFIG, CONFIG_PATH.native());
+
+  m_scheduler.scheduleEvent(time::milliseconds(200),
+                            bind(&DirTestFixture::validate, this, data1));
+  m_scheduler.scheduleEvent(time::milliseconds(200),
+                            bind(&DirTestFixture::invalidate, this, data2));
+
+  m_scheduler.scheduleEvent(time::milliseconds(500),
+                            bind(&DirTestFixture::insertSecondTrustAnchor, this));
+
+  m_scheduler.scheduleEvent(time::milliseconds(1500),
+                            bind(&DirTestFixture::validate, this, data1));
+  m_scheduler.scheduleEvent(time::milliseconds(1500),
+                            bind(&DirTestFixture::validate, this, data2));
+
+  m_scheduler.scheduleEvent(time::milliseconds(2000),
+                            bind(&DirTestFixture::terminate, this));
+
+  BOOST_REQUIRE_NO_THROW(m_face.processEvents());
+}
+
+
+
 BOOST_AUTO_TEST_SUITE_END()
 
 } // namespace ndn