security: Abstract certificate fetching from v2::Validator

Change-Id: Ia98d11ac67b0095f632818ac37a19a1e5a7656a8
Refs: #3921
diff --git a/src/security/v2/certificate-fetcher-from-network.cpp b/src/security/v2/certificate-fetcher-from-network.cpp
new file mode 100644
index 0000000..5d7bcaa
--- /dev/null
+++ b/src/security/v2/certificate-fetcher-from-network.cpp
@@ -0,0 +1,116 @@
+/* -*- 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 "certificate-fetcher-from-network.hpp"
+#include "face.hpp"
+#include "util/logger.hpp"
+
+namespace ndn {
+namespace security {
+namespace v2 {
+
+NDN_LOG_INIT(ndn.security.v2.CertificateFetcher);
+
+#define NDN_LOG_DEBUG_DEPTH(x) NDN_LOG_DEBUG(std::string(state->getDepth() + 1, '>') << " " << x)
+#define NDN_LOG_TRACE_DEPTH(x) NDN_LOG_TRACE(std::string(state->getDepth() + 1, '>') << " " << x)
+
+CertificateFetcherFromNetwork::CertificateFetcherFromNetwork(Face& face)
+  : m_face(face)
+{
+}
+
+void
+CertificateFetcherFromNetwork::doFetch(const shared_ptr<CertificateRequest>& certRequest,
+                                       const shared_ptr<ValidationState>& state,
+                                       const ValidationContinuation& continueValidation)
+{
+  m_face.expressInterest(certRequest->m_interest,
+                         [=] (const Interest& interest, const Data& data) {
+                           dataCallback(data, certRequest, state, continueValidation);
+                         },
+                         [=] (const Interest& interest, const lp::Nack& nack) {
+                           nackCallback(nack, certRequest, state, continueValidation);
+                         },
+                         [=] (const Interest& interest) {
+                           timeoutCallback(certRequest, state, continueValidation);
+                         });
+}
+
+void
+CertificateFetcherFromNetwork::dataCallback(const Data& data,
+                                            const shared_ptr<CertificateRequest>& certRequest,
+                                            const shared_ptr<ValidationState>& state,
+                                            const ValidationContinuation& continueValidation)
+{
+  NDN_LOG_DEBUG_DEPTH("Fetched certificate from network " << data.getName());
+
+  Certificate cert;
+  try {
+    cert = Certificate(data);
+  }
+  catch (const tlv::Error& e) {
+    return state->fail({ValidationError::Code::MALFORMED_CERT, "Fetched a malformed certificate "
+                        "`" + data.getName().toUri() + "` (" + e.what() + ")"});
+  }
+  continueValidation(cert, state);
+}
+
+void
+CertificateFetcherFromNetwork::nackCallback(const lp::Nack& nack,
+                                            const shared_ptr<CertificateRequest>& certRequest,
+                                            const shared_ptr<ValidationState>& state,
+                                            const ValidationContinuation& continueValidation)
+{
+  NDN_LOG_DEBUG_DEPTH("NACK (" << nack.getReason() <<  ") while fetching certificate "
+                      << certRequest->m_interest.getName());
+
+  --certRequest->m_nRetriesLeft;
+  if (certRequest->m_nRetriesLeft >= 0) {
+    // TODO implement delay for the the next fetch
+    fetch(certRequest, state, continueValidation);
+  }
+  else {
+    state->fail({ValidationError::Code::CANNOT_RETRIEVE_CERT, "Cannot fetch certificate after all "
+                 "retries `" + certRequest->m_interest.getName().toUri() + "`"});
+  }
+}
+
+void
+CertificateFetcherFromNetwork::timeoutCallback(const shared_ptr<CertificateRequest>& certRequest,
+                                               const shared_ptr<ValidationState>& state,
+                                               const ValidationContinuation& continueValidation)
+{
+  NDN_LOG_DEBUG_DEPTH("Timeout while fetching certificate " << certRequest->m_interest.getName()
+                      << ", retrying");
+
+  --certRequest->m_nRetriesLeft;
+  if (certRequest->m_nRetriesLeft >= 0) {
+    fetch(certRequest, state, continueValidation);
+  }
+  else {
+    state->fail({ValidationError::Code::CANNOT_RETRIEVE_CERT, "Cannot fetch certificate after all "
+                 "retries `" + certRequest->m_interest.getName().toUri() + "`"});
+  }
+}
+
+} // namespace v2
+} // namespace security
+} // namespace ndn
diff --git a/src/security/v2/certificate-fetcher-from-network.hpp b/src/security/v2/certificate-fetcher-from-network.hpp
new file mode 100644
index 0000000..e5076c3
--- /dev/null
+++ b/src/security/v2/certificate-fetcher-from-network.hpp
@@ -0,0 +1,88 @@
+/* -*- 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.
+ */
+
+#ifndef NDN_SECURITY_V2_CERTIFICATE_FETCHER_FROM_NETWORK_HPP
+#define NDN_SECURITY_V2_CERTIFICATE_FETCHER_FROM_NETWORK_HPP
+
+#include "certificate-fetcher.hpp"
+
+namespace ndn {
+
+namespace lp {
+class Nack;
+} // namespace lp
+
+namespace security {
+namespace v2 {
+
+/**
+ * @brief Fetch missing keys from the network
+ */
+class CertificateFetcherFromNetwork : public CertificateFetcher
+{
+public:
+  explicit
+  CertificateFetcherFromNetwork(Face& face);
+
+protected:
+  void
+  doFetch(const shared_ptr<CertificateRequest>& certRequest, const shared_ptr<ValidationState>& state,
+          const ValidationContinuation& continueValidation) override;
+
+private:
+  /**
+   * @brief Callback invoked when certificate is retrieved.
+   */
+  void
+  dataCallback(const Data& data,
+               const shared_ptr<CertificateRequest>& certRequest, const shared_ptr<ValidationState>& state,
+               const ValidationContinuation& continueValidation);
+
+  /**
+   * @brief Callback invoked when interest for fetching certificate gets NACKed.
+   *
+   * It will retry if certRequest->m_nRetriesLeft > 0
+   *
+   * @todo Delay retry for some amount of time
+   */
+  void
+  nackCallback(const lp::Nack& nack,
+               const shared_ptr<CertificateRequest>& certRequest, const shared_ptr<ValidationState>& state,
+               const ValidationContinuation& continueValidation);
+
+  /**
+   * @brief Callback invoked when interest for fetching certificate times out.
+   *
+   * It will retry if certRequest->m_nRetriesLeft > 0
+   */
+  void
+  timeoutCallback(const shared_ptr<CertificateRequest>& certRequest, const shared_ptr<ValidationState>& state,
+                  const ValidationContinuation& continueValidation);
+
+protected:
+  Face& m_face;
+};
+
+} // namespace v2
+} // namespace security
+} // namespace ndn
+
+#endif // NDN_SECURITY_V2_CERTIFICATE_FETCHER_FROM_NETWORK_HPP
diff --git a/src/security/v2/certificate-fetcher-offline.cpp b/src/security/v2/certificate-fetcher-offline.cpp
new file mode 100644
index 0000000..8bf28e1
--- /dev/null
+++ b/src/security/v2/certificate-fetcher-offline.cpp
@@ -0,0 +1,39 @@
+/* -*- 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 "certificate-fetcher-offline.hpp"
+
+namespace ndn {
+namespace security {
+namespace v2 {
+
+void
+CertificateFetcherOffline::doFetch(const shared_ptr<CertificateRequest>& certRequest,
+                                   const shared_ptr<ValidationState>& state,
+                                   const ValidationContinuation& continueValidation)
+{
+  state->fail({ValidationError::Code::CANNOT_RETRIEVE_CERT,
+               "Cannot fetch certificate " + certRequest->m_interest.getName().toUri() + " in offline mode"});
+}
+
+} // namespace v2
+} // namespace security
+} // namespace ndn
diff --git a/src/security/v2/certificate-fetcher-offline.hpp b/src/security/v2/certificate-fetcher-offline.hpp
new file mode 100644
index 0000000..9cfa176
--- /dev/null
+++ b/src/security/v2/certificate-fetcher-offline.hpp
@@ -0,0 +1,46 @@
+/* -*- 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.
+ */
+
+#ifndef NDN_SECURITY_V2_CERTIFICATE_FETCHER_OFFLINE_HPP
+#define NDN_SECURITY_V2_CERTIFICATE_FETCHER_OFFLINE_HPP
+
+#include "certificate-fetcher.hpp"
+
+namespace ndn {
+namespace security {
+namespace v2 {
+
+/**
+ * @brief Certificate fetcher realization that does not fetch keys (always offline)
+ */
+class CertificateFetcherOffline : public CertificateFetcher
+{
+protected:
+  void
+  doFetch(const shared_ptr<CertificateRequest>& certRequest, const shared_ptr<ValidationState>& state,
+          const ValidationContinuation& continueValidation) override;
+};
+
+} // namespace v2
+} // namespace security
+} // namespace ndn
+
+#endif // NDN_SECURITY_V2_CERTIFICATE_FETCHER_OFFLINE_HPP
diff --git a/src/security/v2/certificate-fetcher.cpp b/src/security/v2/certificate-fetcher.cpp
new file mode 100644
index 0000000..ffb4416
--- /dev/null
+++ b/src/security/v2/certificate-fetcher.cpp
@@ -0,0 +1,67 @@
+/* -*- 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 "certificate-fetcher.hpp"
+#include "util/logger.hpp"
+
+namespace ndn {
+namespace security {
+namespace v2 {
+
+NDN_LOG_INIT(ndn.security.v2.CertificateFetcher);
+
+#define NDN_LOG_DEBUG_DEPTH(x) NDN_LOG_DEBUG(std::string(state->getDepth() + 1, '>') << " " << x)
+
+CertificateFetcher::CertificateFetcher()
+  : m_certStorage(nullptr)
+{
+}
+
+CertificateFetcher::~CertificateFetcher() = default;
+
+void
+CertificateFetcher::setCertificateStorage(CertificateStorage& certStorage)
+{
+  m_certStorage = &certStorage;
+}
+
+void
+CertificateFetcher::fetch(const shared_ptr<CertificateRequest>& certRequest,
+                          const shared_ptr<ValidationState>& state,
+                          const ValidationContinuation& continueValidation)
+{
+  BOOST_ASSERT(m_certStorage != nullptr);
+  auto cert = m_certStorage->getUnverifiedCertCache().find(certRequest->m_interest);
+  if (cert != nullptr) {
+    NDN_LOG_DEBUG_DEPTH("Found certificate in **un**verified key cache " << cert->getName());
+    continueValidation(*cert, state);
+    return;
+  }
+  doFetch(certRequest, state,
+          [continueValidation, this] (const Certificate& cert, const shared_ptr<ValidationState>& state) {
+            m_certStorage->cacheUnverifiedCert(Certificate(cert));
+            continueValidation(cert, state);
+          });
+}
+
+} // namespace v2
+} // namespace security
+} // namespace ndn
diff --git a/src/security/v2/certificate-fetcher.hpp b/src/security/v2/certificate-fetcher.hpp
new file mode 100644
index 0000000..7abc5f5
--- /dev/null
+++ b/src/security/v2/certificate-fetcher.hpp
@@ -0,0 +1,91 @@
+/* -*- 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.
+ */
+
+#ifndef NDN_SECURITY_V2_CERTIFICATE_FETCHER_HPP
+#define NDN_SECURITY_V2_CERTIFICATE_FETCHER_HPP
+
+#include "certificate-request.hpp"
+#include "certificate-storage.hpp"
+#include "validation-state.hpp"
+
+namespace ndn {
+
+class Face;
+
+namespace security {
+namespace v2 {
+
+/**
+ * @brief Interface used by the validator to fetch missing certificates
+ */
+class CertificateFetcher : noncopyable
+{
+public:
+  using ValidationContinuation = std::function<void(const Certificate& cert,
+                                                    const shared_ptr<ValidationState>& state)>;
+
+  CertificateFetcher();
+
+  virtual
+  ~CertificateFetcher();
+
+  /**
+   * @brief Assign certificate storage to check known certificate and to cache unverified ones
+   * @note The supplied @p certStorage should be valid for the lifetime of CertificateFetcher
+   */
+  void
+  setCertificateStorage(CertificateStorage& certStorage);
+
+  /**
+   * @brief Asynchronously fetch certificate
+   * @pre m_certStorage != nullptr
+   *
+   * If the requested certificate exists in the storage, then this method will immediately call
+   * continueValidation with the certification.  If certificate is not available, the
+   * implementation-specific doFetch will be called to asynchronously fetch certificate.  The
+   * successfully retrieved certificate will be automatically added to the unverified cache of
+   * the certificate storage.
+   *
+   * When the requested certificate is retrieved, continueValidation is called.  Otherwise, the
+   * fetcher implementation call state->failed() with the appropriate error code and diagnostic
+   * message.
+   */
+  void
+  fetch(const shared_ptr<CertificateRequest>& certRequest, const shared_ptr<ValidationState>& state,
+        const ValidationContinuation& continueValidation);
+
+private:
+  /**
+   * @brief Asynchronous certificate fetching implementation
+   */
+  virtual void
+  doFetch(const shared_ptr<CertificateRequest>& certRequest, const shared_ptr<ValidationState>& state,
+          const ValidationContinuation& continueValidation) = 0;
+
+protected:
+  CertificateStorage* m_certStorage;
+};
+
+} // namespace v2
+} // namespace security
+} // namespace ndn
+
+#endif // NDN_SECURITY_V2_CERTIFICATE_FETCHER_HPP
diff --git a/src/security/v2/certificate-storage.cpp b/src/security/v2/certificate-storage.cpp
new file mode 100644
index 0000000..89869fa
--- /dev/null
+++ b/src/security/v2/certificate-storage.cpp
@@ -0,0 +1,99 @@
+/* -*- 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 "certificate-storage.hpp"
+
+namespace ndn {
+namespace security {
+namespace v2 {
+
+CertificateStorage::CertificateStorage()
+  : m_verifiedCertCache(time::hours(1))
+  , m_unverifiedCertCache(time::minutes(5))
+{
+}
+
+const Certificate*
+CertificateStorage::findTrustedCert(const Interest& interestForCert) const
+{
+  auto cert = m_trustAnchors.find(interestForCert);
+  if (cert != nullptr) {
+    return cert;
+  }
+
+  cert = m_verifiedCertCache.find(interestForCert);
+  return cert;
+}
+
+bool
+CertificateStorage::isCertKnown(const Name& certName) const
+{
+  return (m_trustAnchors.find(certName) != nullptr ||
+          m_verifiedCertCache.find(certName) != nullptr ||
+          m_unverifiedCertCache.find(certName) != nullptr);
+}
+
+void
+CertificateStorage::loadAnchor(const std::string& groupId, Certificate&& cert)
+{
+  m_trustAnchors.insert(groupId, std::move(cert));
+}
+
+void
+CertificateStorage::loadAnchor(const std::string& groupId, const std::string& certfilePath,
+                               time::nanoseconds refreshPeriod, bool isDir)
+{
+  m_trustAnchors.insert(groupId, certfilePath, refreshPeriod, isDir);
+}
+
+void
+CertificateStorage::cacheVerifiedCert(Certificate&& cert)
+{
+  m_verifiedCertCache.insert(std::move(cert));
+}
+
+void
+CertificateStorage::cacheUnverifiedCert(Certificate&& cert)
+{
+  m_unverifiedCertCache.insert(std::move(cert));
+}
+
+const TrustAnchorContainer&
+CertificateStorage::getTrustAnchors() const
+{
+  return m_trustAnchors;
+}
+
+const CertificateCache&
+CertificateStorage::getVerifiedCertCache() const
+{
+  return m_verifiedCertCache;
+}
+
+const CertificateCache&
+CertificateStorage::getUnverifiedCertCache() const
+{
+  return m_unverifiedCertCache;
+}
+
+} // namespace v2
+} // namespace security
+} // namespace ndn
diff --git a/src/security/v2/certificate-storage.hpp b/src/security/v2/certificate-storage.hpp
new file mode 100644
index 0000000..ff54ae2
--- /dev/null
+++ b/src/security/v2/certificate-storage.hpp
@@ -0,0 +1,131 @@
+/* -*- 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.
+ */
+
+#ifndef NDN_SECURITY_V2_CERTIFICATE_STORAGE_HPP
+#define NDN_SECURITY_V2_CERTIFICATE_STORAGE_HPP
+
+#include "certificate.hpp"
+#include "certificate-cache.hpp"
+#include "trust-anchor-container.hpp"
+
+namespace ndn {
+namespace security {
+namespace v2 {
+
+/**
+ * @brief Storage for trusted anchors, verified certificate cache, and unverified certificate cache.
+ */
+class CertificateStorage : noncopyable
+{
+public:
+  CertificateStorage();
+
+  /**
+   * @brief Find a trusted certificate in trust anchor container or in verified cache
+   * @param interestForCert Interest for certificate
+   * @return found certificate, nullptr if not found.
+   *
+   * @note The returned pointer may get invalidated after next findTrustedCert or findCert calls.
+   */
+  const Certificate*
+  findTrustedCert(const Interest& interestForCert) const;
+
+  /**
+   * @brief Check if certificate exists in verified, unverified cache, or in the set of trust
+   *        anchors
+   */
+  bool
+  isCertKnown(const Name& certPrefix) const;
+
+  /**
+   * @brief Cache unverified certificate for a period of time (5 minutes)
+   * @param cert  The certificate packet
+   *
+   * @todo Add ability to customize time period
+   */
+  void
+  cacheUnverifiedCert(Certificate&& cert);
+
+  /**
+   * @return Trust anchor container
+   */
+  const TrustAnchorContainer&
+  getTrustAnchors() const;
+
+  /**
+   * @return Verified certificate cache
+   */
+  const CertificateCache&
+  getVerifiedCertCache() const;
+
+  /**
+   * @return Unverified certificate cache
+   */
+  const CertificateCache&
+  getUnverifiedCertCache() const;
+
+protected:
+  /**
+   * @brief load static trust anchor.
+   *
+   * Static trust anchors are permanently associated with the validator and never expire.
+   *
+   * @param groupId  Certificate group id.
+   * @param cert     Certificate to load as a trust anchor.
+   */
+  void
+  loadAnchor(const std::string& groupId, Certificate&& cert);
+
+  /**
+   * @brief load dynamic trust anchors.
+   *
+   * Dynamic trust anchors are associated with the validator for as long as the underlying
+   * trust anchor file (set of files) exist(s).
+   *
+   * @param groupId          Certificate group id, must not be empty.
+   * @param certfilePath     Specifies the path to load the trust anchors.
+   * @param refreshPeriod    Refresh period for the trust anchors, must be positive.
+   * @param isDir            Tells whether the path is a directory or a single file.
+   */
+  void
+  loadAnchor(const std::string& groupId, const std::string& certfilePath,
+             time::nanoseconds refreshPeriod, bool isDir = false);
+
+  /**
+   * @brief Cache verified certificate a period of time (1 hour)
+   * @param cert  The certificate packet
+   *
+   * @todo Add ability to customize time period
+   */
+  void
+  cacheVerifiedCert(Certificate&& cert);
+
+protected:
+  TrustAnchorContainer m_trustAnchors;
+  CertificateCache m_verifiedCertCache;
+  CertificateCache m_unverifiedCertCache;
+};
+
+} // namespace v2
+} // namespace security
+} // namespace ndn
+
+#endif // NDN_SECURITY_V2_CERTIFICATE_STORAGE_HPP
diff --git a/src/security/v2/validator.cpp b/src/security/v2/validator.cpp
index b18fc75..4928c67 100644
--- a/src/security/v2/validator.cpp
+++ b/src/security/v2/validator.cpp
@@ -34,13 +34,14 @@
 #define NDN_LOG_DEBUG_DEPTH(x) NDN_LOG_DEBUG(std::string(state->getDepth() + 1, '>') << " " << x)
 #define NDN_LOG_TRACE_DEPTH(x) NDN_LOG_TRACE(std::string(state->getDepth() + 1, '>') << " " << x)
 
-Validator::Validator(unique_ptr<ValidationPolicy> policy, Face* face)
+Validator::Validator(unique_ptr<ValidationPolicy> policy, unique_ptr<CertificateFetcher> certFetcher)
   : m_policy(std::move(policy))
-  , m_face(face)
-  , m_verifiedCertificateCache(time::hours(1))
-  , m_unverifiedCertificateCache(time::minutes(5))
+  , m_certFetcher(std::move(certFetcher))
   , m_maxDepth(25)
 {
+  BOOST_ASSERT(m_policy != nullptr);
+  BOOST_ASSERT(m_certFetcher != nullptr);
+  m_certFetcher->setCertificateStorage(*this);
 }
 
 Validator::~Validator() = default;
@@ -101,6 +102,12 @@
 Validator::validate(const Certificate& cert, const shared_ptr<ValidationState>& state)
 {
   NDN_LOG_DEBUG_DEPTH("Start validating certificate " << cert.getName());
+
+  if (!cert.isValid()) {
+    return state->fail({ValidationError::Code::EXPIRED_CERT, "Retrieved certificate is not yet valid or expired "
+          "`" + cert.getName().toUri() + "`"});
+  }
+
   m_policy->checkPolicy(cert, state,
       [this, cert] (const shared_ptr<CertificateRequest>& certRequest, const shared_ptr<ValidationState>& state) {
       if (certRequest == nullptr) {
@@ -115,23 +122,6 @@
     });
 }
 
-const Certificate*
-Validator::findTrustedCert(const Interest& interestForCertificate, const shared_ptr<ValidationState>& state)
-{
-  auto anchor = m_trustAnchors.find(interestForCertificate);
-  if (anchor != nullptr) {
-    NDN_LOG_TRACE_DEPTH("Found certificate in anchor cache " << anchor->getName());
-    return anchor;
-  }
-
-  auto key = m_verifiedCertificateCache.find(interestForCertificate);
-  if (key != nullptr) {
-    NDN_LOG_TRACE_DEPTH("Found certificate in verified key cache " << key->getName());
-    return key;
-  }
-  return nullptr;
-}
-
 void
 Validator::requestCertificate(const shared_ptr<CertificateRequest>& certRequest,
                               const shared_ptr<ValidationState>& state)
@@ -145,9 +135,10 @@
 
   NDN_LOG_DEBUG_DEPTH("Retrieving " << certRequest->m_interest.getName());
 
-  // Check the trusted cache
-  auto cert = findTrustedCert(certRequest->m_interest, state);
+  auto cert = findTrustedCert(certRequest->m_interest);
   if (cert != nullptr) {
+    NDN_LOG_TRACE_DEPTH("Found trusted certificate " << cert->getName());
+
     cert = state->verifyCertificateChain(*cert);
     if (cert != nullptr) {
       state->verifyOriginalPacket(*cert);
@@ -160,159 +151,34 @@
     return;
   }
 
-  if (state->hasSeenCertificateName(certRequest->m_interest.getName())) {
-    state->fail({ValidationError::Code::LOOP_DETECTED,
-                 "Loop detected at " + certRequest->m_interest.getName().toUri()});
-    return;
-  }
-
-  // Check untrusted cache
-  cert = m_unverifiedCertificateCache.find(certRequest->m_interest);
-  if (cert != nullptr) {
-    NDN_LOG_DEBUG_DEPTH("Found certificate in **un**verified key cache " << cert->getName());
-    return dataCallback(*cert, certRequest, state, false); // to avoid caching the cached key
-  }
-
-  // Attempt to retrieve certificate from the network
-  fetchCertificateFromNetwork(certRequest, state);
-}
-
-void
-Validator::fetchCertificateFromNetwork(const shared_ptr<CertificateRequest>& certRequest,
-                                       const shared_ptr<ValidationState>& state)
-{
-  if (m_face == nullptr) {
-    state->fail({ValidationError::Code::CANNOT_RETRIEVE_CERT, "Cannot fetch certificate in offline mode "
-                 "`" + certRequest->m_interest.getName().toUri() + "`"});
-    return;
-  }
-
-  m_face->expressInterest(certRequest->m_interest,
-                          [=] (const Interest& interest, const Data& data) {
-                            dataCallback(data, certRequest, state);
-                          },
-                          [=] (const Interest& interest, const lp::Nack& nack) {
-                            nackCallback(nack, certRequest, state);
-                          },
-                          [=] (const Interest& interest) {
-                            timeoutCallback(certRequest, state);
-                          });
-}
-
-void
-Validator::dataCallback(const Data& data,
-                        const shared_ptr<CertificateRequest>& certRequest,
-                        const shared_ptr<ValidationState>& state,
-                        bool isFromNetwork)
-{
-  NDN_LOG_DEBUG_DEPTH("Retrieved certificate " << (isFromNetwork ? "from network " : "from cache ") << data.getName());
-
-  Certificate cert;
-  try {
-    cert = Certificate(data);
-  }
-  catch (const tlv::Error& e) {
-    return state->fail({ValidationError::Code::MALFORMED_CERT, "Retrieved a malformed certificate "
-                        "`" + data.getName().toUri() + "` (" + e.what() + ")"});
-  }
-
-  if (!cert.isValid()) {
-    return state->fail({ValidationError::Code::EXPIRED_CERT, "Retrieved certificate is not yet "
-                        "valid or has expired `" + cert.getName().toUri() + "`"});
-  }
-  if (isFromNetwork) {
-    cacheUnverifiedCertificate(Certificate(cert));
-  }
-  return validate(cert, state); // recursion step
-}
-
-void
-Validator::nackCallback(const lp::Nack& nack, const shared_ptr<CertificateRequest>& certRequest,
-                        const shared_ptr<ValidationState>& state)
-{
-  NDN_LOG_DEBUG_DEPTH("NACK (" << nack.getReason() <<  ") while retrieving certificate "
-                      << certRequest->m_interest.getName());
-
-  --certRequest->m_nRetriesLeft;
-  if (certRequest->m_nRetriesLeft > 0) {
-    // TODO implement delay for the the next fetch
-    fetchCertificateFromNetwork(certRequest, state);
-  }
-  else {
-    state->fail({ValidationError::Code::CANNOT_RETRIEVE_CERT, "Cannot fetch certificate after all "
-                 "retries `" + certRequest->m_interest.getName().toUri() + "`"});
-  }
-}
-
-void
-Validator::timeoutCallback(const shared_ptr<CertificateRequest>& certRequest,
-                           const shared_ptr<ValidationState>& state)
-{
-  NDN_LOG_DEBUG_DEPTH("Timeout while retrieving certificate "
-                      << certRequest->m_interest.getName() << ", retrying");
-
-  --certRequest->m_nRetriesLeft;
-  if (certRequest->m_nRetriesLeft > 0) {
-    fetchCertificateFromNetwork(certRequest, state);
-  }
-  else {
-    state->fail({ValidationError::Code::CANNOT_RETRIEVE_CERT, "Cannot fetch certificate after all "
-                 "retries `" + certRequest->m_interest.getName().toUri() + "`"});
-  }
+  m_certFetcher->fetch(certRequest, state, [this] (const Certificate& cert, const shared_ptr<ValidationState>& state) {
+      validate(cert, state);
+    });
 }
 
 ////////////////////////////////////////////////////////////////////////
 // Trust anchor management
 ////////////////////////////////////////////////////////////////////////
 
+// to change visibility from protected to public
+
 void
 Validator::loadAnchor(const std::string& groupId, Certificate&& cert)
 {
-  m_trustAnchors.insert(groupId, std::move(cert));
+  CertificateStorage::loadAnchor(groupId, std::move(cert));
 }
 
 void
 Validator::loadAnchor(const std::string& groupId, const std::string& certfilePath,
                       time::nanoseconds refreshPeriod, bool isDir)
 {
-  m_trustAnchors.insert(groupId, certfilePath, refreshPeriod, isDir);
+  CertificateStorage::loadAnchor(groupId, certfilePath, refreshPeriod, isDir);
 }
 
 void
 Validator::cacheVerifiedCertificate(Certificate&& cert)
 {
-  m_verifiedCertificateCache.insert(std::move(cert));
-}
-
-void
-Validator::cacheUnverifiedCertificate(Certificate&& cert)
-{
-  m_unverifiedCertificateCache.insert(std::move(cert));
-}
-
-const TrustAnchorContainer&
-Validator::getTrustAnchors() const
-{
-  return m_trustAnchors;
-}
-
-const CertificateCache&
-Validator::getVerifiedCertificateCache() const
-{
-  return m_verifiedCertificateCache;
-}
-
-const CertificateCache&
-Validator::getUnverifiedCertificateCache() const
-{
-  return m_unverifiedCertificateCache;
-}
-
-bool
-Validator::isCertificateCached(const Name& certName) const
-{
-  return (getVerifiedCertificateCache().find(certName) != nullptr ||
-          getVerifiedCertificateCache().find(certName) != nullptr);
+  CertificateStorage::cacheVerifiedCert(std::move(cert));
 }
 
 } // namespace v2
diff --git a/src/security/v2/validator.hpp b/src/security/v2/validator.hpp
index c17ce82..f24499c 100644
--- a/src/security/v2/validator.hpp
+++ b/src/security/v2/validator.hpp
@@ -22,10 +22,9 @@
 #ifndef NDN_SECURITY_V2_VALIDATOR_HPP
 #define NDN_SECURITY_V2_VALIDATOR_HPP
 
-#include "certificate.hpp"
-#include "certificate-cache.hpp"
+#include "certificate-fetcher.hpp"
 #include "certificate-request.hpp"
-#include "trust-anchor-container.hpp"
+#include "certificate-storage.hpp"
 #include "validation-callback.hpp"
 #include "validation-policy.hpp"
 #include "validation-state.hpp"
@@ -34,10 +33,6 @@
 
 class Face;
 
-namespace lp {
-class Nack;
-} // namespace lp
-
 namespace security {
 namespace v2 {
 
@@ -52,8 +47,8 @@
  * - record names of the requested certificates to detect loops in the certificate chain
  * - keep track of the validation chain size (aka validation "depth")
  *
- * During validation, policy can augment validation state with policy- and fetcher-specific
- * information using ndn::Tag's.
+ * During validation, policy and/or key fetcher can augment validation state with policy- and
+ * fetcher-specific information using ndn::Tag's.
  *
  * A validator has a trust anchor cache to save static and dynamic trust anchors, a verified
  * certificate cache for saving certificates that are already verified and an unverified
@@ -63,18 +58,16 @@
  * @todo Ability to customize maximum lifetime for trusted and untrusted certificate caches.
  *       Current implementation hard-codes them to be 1 hour and 5 minutes.
  */
-class Validator : noncopyable
+class Validator : public CertificateStorage
 {
 public:
   /**
    * @brief Validator constructor.
    *
-   * @param policy Validation policy to be associated with the validator
-   * @param face   Face for fetching certificates from network.  If provided, the Validator
-   *               operates in online mode; otherwise, the Validator operates in offline mode.
+   * @param policy      Validation policy to be associated with the validator
+   * @param certFetcher Certificate fetcher implementation.
    */
-  explicit
-  Validator(unique_ptr<ValidationPolicy> policy, Face* face = nullptr);
+  Validator(unique_ptr<ValidationPolicy> policy, unique_ptr<CertificateFetcher> certFetcher);
 
   ~Validator();
 
@@ -145,38 +138,6 @@
   void
   cacheVerifiedCertificate(Certificate&& cert);
 
-  /**
-   * @brief Cache unverified @p cert for a period of time (5 minutes)
-   *
-   * @todo Add ability to customize time period
-   */
-  void
-  cacheUnverifiedCertificate(Certificate&& cert);
-
-  /**
-   * @return Trust anchor container
-   */
-  const TrustAnchorContainer&
-  getTrustAnchors() const;
-
-  /**
-   * @return Verified certificate cache
-   */
-  const CertificateCache&
-  getVerifiedCertificateCache() const;
-
-  /**
-   * @return Unverified certificate cache
-   */
-  const CertificateCache&
-  getUnverifiedCertificateCache() const;
-
-  /**
-   * @brief Check if certificate with @p certName exists in verified or unverified cache
-   */
-  bool
-  isCertificateCached(const Name& certName) const;
-
 private: // Common validator operations
   /**
    * @brief Recursive validation of the certificate in the certification chain
@@ -197,75 +158,9 @@
   requestCertificate(const shared_ptr<CertificateRequest>& certRequest,
                      const shared_ptr<ValidationState>& state);
 
-  /**
-   * @brief Find trusted certificate among trust anchors and verified certificates.
-   *
-   * @param interestForCertificate Interest for certificate
-   * @param state                  The current validation state.
-   *
-   * @return found certificate, nullptr if not found.
-   *
-   * @note The returned pointer may get invalidated after next findTrustedCert call.
-   */
-  const Certificate*
-  findTrustedCert(const Interest& interestForCertificate,
-                  const shared_ptr<ValidationState>& state);
-
-  /**
-   * @brief fetch certificate from network based on certificate request.
-   *
-   * @param certRequest Certificate request.
-   * @param state       The current validation state.
-   */
-  void
-  fetchCertificateFromNetwork(const shared_ptr<CertificateRequest>& certRequest,
-                              const shared_ptr<ValidationState>& state);
-
-  /**
-   * @brief Callback invoked when certificated is retrieved.
-   *
-   * @param data        Retrieved certificate.
-   * @param certRequest Certificate request.
-   * @param state       The current validation state.
-   * @param isFromNetwork Flag to indicate that the data packet is retrieved (to avoid re-caching).
-   */
-  void
-  dataCallback(const Data& data,
-               const shared_ptr<CertificateRequest>& certRequest,
-               const shared_ptr<ValidationState>& state,
-               bool isFromNetwork = true);
-
-  /**
-   * @brief Callback invoked when interest for fetching certificate gets NACKed.
-   *
-   * It will retry for pre-configured amount of retries.
-   *
-   * @param nack        Received NACK
-   * @param certRequest Certificate request.
-   * @param state       The current validation state.
-   */
-  void
-  nackCallback(const lp::Nack& nack, const shared_ptr<CertificateRequest>& certRequest,
-               const shared_ptr<ValidationState>& state);
-
-  /**
-   * @brief Callback invoked when interest for fetching certificate times out.
-   *
-   * It will retry for pre-configured amount of retries.
-   *
-   * @param certRequest Certificate request.
-   * @param state       The current validation state.
-   */
-  void
-  timeoutCallback(const shared_ptr<CertificateRequest>& certRequest,
-                  const shared_ptr<ValidationState>& state);
-
 private:
   unique_ptr<ValidationPolicy> m_policy;
-  Face* m_face;
-  TrustAnchorContainer m_trustAnchors;
-  CertificateCache m_verifiedCertificateCache;
-  CertificateCache m_unverifiedCertificateCache;
+  unique_ptr<CertificateFetcher> m_certFetcher;
   size_t m_maxDepth;
 };
 
diff --git a/tests/unit-tests/security/v2/certificate-fetcher-from-network.t.cpp b/tests/unit-tests/security/v2/certificate-fetcher-from-network.t.cpp
new file mode 100644
index 0000000..0caa12f
--- /dev/null
+++ b/tests/unit-tests/security/v2/certificate-fetcher-from-network.t.cpp
@@ -0,0 +1,138 @@
+/* -*- 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 "security/v2/certificate-fetcher-from-network.hpp"
+#include "security/v2/validation-policy-simple-hierarchy.hpp"
+#include "lp/nack.hpp"
+
+#include "boost-test.hpp"
+#include "validator-fixture.hpp"
+
+namespace ndn {
+namespace security {
+namespace v2 {
+namespace tests {
+
+using namespace ndn::tests;
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(V2)
+BOOST_AUTO_TEST_SUITE(TestCertificateFetcherFromNetwork)
+
+class Cert
+{
+};
+
+class Timeout
+{
+};
+
+class Nack
+{
+};
+
+template<class Response>
+class CertificateFetcherFromNetworkFixture : public HierarchicalValidatorFixture<ValidationPolicySimpleHierarchy,
+                                                                                 CertificateFetcherFromNetwork>
+{
+public:
+  CertificateFetcherFromNetworkFixture()
+    : data("/Security/V2/ValidatorFixture/Sub1/Sub3/Data")
+    , interest("/Security/V2/ValidatorFixture/Sub1/Sub3/Interest")
+  {
+    Identity subSubIdentity = addSubCertificate("/Security/V2/ValidatorFixture/Sub1/Sub3", subIdentity);
+    cache.insert(subSubIdentity.getDefaultKey().getDefaultCertificate());
+
+    m_keyChain.sign(data, signingByIdentity(subSubIdentity));
+    m_keyChain.sign(interest, signingByIdentity(subSubIdentity));
+
+    processInterest = bind(&CertificateFetcherFromNetworkFixture<Response>::makeResponse, this, _1);
+  }
+
+  void
+  makeResponse(const Interest& interest);
+
+public:
+  Data data;
+  Interest interest;
+};
+
+template<>
+void
+CertificateFetcherFromNetworkFixture<Cert>::makeResponse(const Interest& interest)
+{
+  auto cert = cache.find(interest);
+  if (cert == nullptr) {
+    return;
+  }
+  face.receive(*cert);
+}
+
+template<>
+void
+CertificateFetcherFromNetworkFixture<Timeout>::makeResponse(const Interest& interest)
+{
+  // do nothing
+}
+
+template<>
+void
+CertificateFetcherFromNetworkFixture<Nack>::makeResponse(const Interest& interest)
+{
+  lp::Nack nack(interest);
+  nack.setHeader(lp::NackHeader().setReason(lp::NackReason::NO_ROUTE));
+  face.receive(nack);
+}
+
+using Failures = boost::mpl::vector<Timeout, Nack>;
+
+BOOST_FIXTURE_TEST_CASE(ValidateSuccess, CertificateFetcherFromNetworkFixture<Cert>)
+{
+  VALIDATE_SUCCESS(this->data, "Should get accepted, as normal interests bring cert");
+  BOOST_CHECK_EQUAL(this->face.sentInterests.size(), 2);
+  this->face.sentInterests.clear();
+
+  this->advanceClocks(time::hours(1), 2); // expire validator caches
+
+  VALIDATE_SUCCESS(this->interest, "Should get accepted, as interests bring certs");
+  BOOST_CHECK_EQUAL(this->face.sentInterests.size(), 2);
+}
+
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(ValidateFailure, T, Failures, CertificateFetcherFromNetworkFixture<T>)
+{
+  VALIDATE_FAILURE(this->data, "Should fail, as interests don't bring data");
+  BOOST_CHECK_GT(this->face.sentInterests.size(), 2);
+  this->face.sentInterests.clear();
+
+  this->advanceClocks(time::hours(1), 2); // expire validator caches
+
+  VALIDATE_FAILURE(this->interest, "Should fail, as interests don't bring data");
+  BOOST_CHECK_GT(this->face.sentInterests.size(), 2);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestCertificateFetcherFromNetwork
+BOOST_AUTO_TEST_SUITE_END() // V2
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace v2
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit-tests/security/v2/certificate-fetcher-offline.t.cpp b/tests/unit-tests/security/v2/certificate-fetcher-offline.t.cpp
new file mode 100644
index 0000000..652a7a2
--- /dev/null
+++ b/tests/unit-tests/security/v2/certificate-fetcher-offline.t.cpp
@@ -0,0 +1,75 @@
+/* -*- 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 "security/v2/certificate-fetcher-offline.hpp"
+#include "security/v2/validation-policy-simple-hierarchy.hpp"
+
+#include "boost-test.hpp"
+#include "validator-fixture.hpp"
+
+namespace ndn {
+namespace security {
+namespace v2 {
+namespace tests {
+
+using namespace ndn::tests;
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(V2)
+
+class CertificateFetcherOfflineWrapper : public CertificateFetcherOffline
+{
+public:
+  CertificateFetcherOfflineWrapper(Face&)
+  {
+  }
+};
+
+using CertificateFetcherOfflineFixture = HierarchicalValidatorFixture<ValidationPolicySimpleHierarchy,
+                                                                      CertificateFetcherOfflineWrapper>;
+
+BOOST_FIXTURE_TEST_SUITE(TestCertificateFetcherOffline, CertificateFetcherOfflineFixture)
+
+typedef boost::mpl::vector<Interest, Data> Packets;
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(Validate, Packet, Packets)
+{
+  Packet unsignedPacket("/Security/V2/ValidatorFixture/Sub1/Packet");
+
+  Packet packet = unsignedPacket;
+  m_keyChain.sign(packet, signingByIdentity(subIdentity));
+  VALIDATE_FAILURE(packet, "Should fail, as no cert should be requested");
+  BOOST_CHECK_EQUAL(this->face.sentInterests.size(), 0);
+
+  packet = unsignedPacket;
+  m_keyChain.sign(packet, signingByIdentity(identity));
+  VALIDATE_SUCCESS(packet, "Should succeed, as signed by trust anchor");
+  BOOST_CHECK_EQUAL(this->face.sentInterests.size(), 0);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestCertificateFetcherOffline
+BOOST_AUTO_TEST_SUITE_END() // V2
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace v2
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit-tests/security/v2/validator-fixture.hpp b/tests/unit-tests/security/v2/validator-fixture.hpp
index 8fac880..aa512ea 100644
--- a/tests/unit-tests/security/v2/validator-fixture.hpp
+++ b/tests/unit-tests/security/v2/validator-fixture.hpp
@@ -23,6 +23,7 @@
 #define NDN_TESTS_SECURITY_V2_VALIDATOR_FIXTURE_HPP
 
 #include "security/v2/validator.hpp"
+#include "security/v2/certificate-fetcher-from-network.hpp"
 #include "util/dummy-client-face.hpp"
 
 #include "../../identity-management-time-fixture.hpp"
@@ -34,13 +35,13 @@
 namespace v2 {
 namespace tests {
 
-template<class ValidationPolicy>
+template<class ValidationPolicy, class CertificateFetcher = CertificateFetcherFromNetwork>
 class ValidatorFixture : public ndn::tests::IdentityManagementTimeFixture
 {
 public:
   ValidatorFixture()
     : face(io, {true, true})
-    , validator(make_unique<ValidationPolicy>(), &face)
+    , validator(make_unique<ValidationPolicy>(), make_unique<CertificateFetcher>(face))
     , cache(time::days(100))
   {
     processInterest = [this] (const Interest& interest) {
@@ -96,8 +97,8 @@
   CertificateCache cache;
 };
 
-template<class ValidationPolicy>
-class HierarchicalValidatorFixture : public ValidatorFixture<ValidationPolicy>
+template<class ValidationPolicy, class CertificateFetcher = CertificateFetcherFromNetwork>
+class HierarchicalValidatorFixture : public ValidatorFixture<ValidationPolicy, CertificateFetcher>
 {
 public:
   HierarchicalValidatorFixture()