security: Add v2::Validator implementation
Based on the code originally written by Qiuhan Ding
Change-Id: Ib66e24f49d0b6fb2ae21ea1fca7b9ec62ecb753a
Refs: #3289, #1872
diff --git a/src/security/v2/validator.cpp b/src/security/v2/validator.cpp
new file mode 100644
index 0000000..b18fc75
--- /dev/null
+++ b/src/security/v2/validator.cpp
@@ -0,0 +1,320 @@
+/* -*- 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 "validator.hpp"
+
+#include "face.hpp"
+#include "security/transform/public-key.hpp"
+#include "util/logger.hpp"
+
+namespace ndn {
+namespace security {
+namespace v2 {
+
+NDN_LOG_INIT(ndn.security.v2.Validator);
+
+#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)
+ : m_policy(std::move(policy))
+ , m_face(face)
+ , m_verifiedCertificateCache(time::hours(1))
+ , m_unverifiedCertificateCache(time::minutes(5))
+ , m_maxDepth(25)
+{
+}
+
+Validator::~Validator() = default;
+
+void
+Validator::setMaxDepth(size_t depth)
+{
+ m_maxDepth = depth;
+}
+
+size_t
+Validator::getMaxDepth() const
+{
+ return m_maxDepth;
+}
+
+void
+Validator::validate(const Data& data,
+ const DataValidationSuccessCallback& successCb,
+ const DataValidationFailureCallback& failureCb)
+{
+ auto state = make_shared<DataValidationState>(data, successCb, failureCb);
+ NDN_LOG_DEBUG_DEPTH("Start validating data " << data.getName());
+
+ m_policy->checkPolicy(data, state,
+ [this] (const shared_ptr<CertificateRequest>& certRequest, const shared_ptr<ValidationState>& state) {
+ if (certRequest == nullptr) {
+ state->bypassValidation();
+ }
+ else {
+ // need to fetch key and validate it
+ requestCertificate(certRequest, state);
+ }
+ });
+}
+
+void
+Validator::validate(const Interest& interest,
+ const InterestValidationSuccessCallback& successCb,
+ const InterestValidationFailureCallback& failureCb)
+{
+ auto state = make_shared<InterestValidationState>(interest, successCb, failureCb);
+ NDN_LOG_DEBUG_DEPTH("Start validating interest " << interest.getName());
+
+ m_policy->checkPolicy(interest, state,
+ [this] (const shared_ptr<CertificateRequest>& certRequest, const shared_ptr<ValidationState>& state) {
+ if (certRequest == nullptr) {
+ state->bypassValidation();
+ }
+ else {
+ // need to fetch key and validate it
+ requestCertificate(certRequest, state);
+ }
+ });
+}
+
+void
+Validator::validate(const Certificate& cert, const shared_ptr<ValidationState>& state)
+{
+ NDN_LOG_DEBUG_DEPTH("Start validating certificate " << cert.getName());
+ m_policy->checkPolicy(cert, state,
+ [this, cert] (const shared_ptr<CertificateRequest>& certRequest, const shared_ptr<ValidationState>& state) {
+ if (certRequest == nullptr) {
+ state->fail({ValidationError::POLICY_ERROR, "Validation policy is not allowed to designate `" +
+ cert.getName().toUri() + "` as a trust anchor"});
+ }
+ else {
+ // need to fetch key and validate it
+ state->addCertificate(cert);
+ requestCertificate(certRequest, state);
+ }
+ });
+}
+
+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)
+{
+ // TODO configurable check for the maximum number of steps
+ if (state->getDepth() >= m_maxDepth) {
+ state->fail({ValidationError::Code::EXCEEDED_DEPTH_LIMIT,
+ "Exceeded validation depth limit (" + to_string(m_maxDepth) + ")"});
+ return;
+ }
+
+ NDN_LOG_DEBUG_DEPTH("Retrieving " << certRequest->m_interest.getName());
+
+ // Check the trusted cache
+ auto cert = findTrustedCert(certRequest->m_interest, state);
+ if (cert != nullptr) {
+ cert = state->verifyCertificateChain(*cert);
+ if (cert != nullptr) {
+ state->verifyOriginalPacket(*cert);
+ }
+ for (auto trustedCert = std::make_move_iterator(state->m_certificateChain.begin());
+ trustedCert != std::make_move_iterator(state->m_certificateChain.end());
+ ++trustedCert) {
+ cacheVerifiedCertificate(*trustedCert);
+ }
+ 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() + "`"});
+ }
+}
+
+////////////////////////////////////////////////////////////////////////
+// Trust anchor management
+////////////////////////////////////////////////////////////////////////
+
+void
+Validator::loadAnchor(const std::string& groupId, Certificate&& cert)
+{
+ m_trustAnchors.insert(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);
+}
+
+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);
+}
+
+} // namespace v2
+} // namespace security
+} // namespace ndn