mgmt: CommandAuthenticator

refs #2063

Change-Id: I19a82d8d1fdfb3cc5a003166b1a8c1c32bbf24b5
diff --git a/daemon/mgmt/command-authenticator.cpp b/daemon/mgmt/command-authenticator.cpp
new file mode 100644
index 0000000..cd45b84
--- /dev/null
+++ b/daemon/mgmt/command-authenticator.cpp
@@ -0,0 +1,234 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "command-authenticator.hpp"
+#include "core/logger.hpp"
+
+#include <ndn-cxx/security/identity-certificate.hpp>
+#include <ndn-cxx/security/validator-null.hpp>
+#include <ndn-cxx/util/io.hpp>
+
+#include <boost/filesystem.hpp>
+
+namespace nfd {
+
+NFD_LOG_INIT("CommandAuthenticator");
+// INFO: configuration change, etc
+// DEBUG: per authentication request result
+
+shared_ptr<CommandAuthenticator>
+CommandAuthenticator::create()
+{
+  return shared_ptr<CommandAuthenticator>(new CommandAuthenticator());
+}
+
+CommandAuthenticator::CommandAuthenticator()
+  : m_validator(make_unique<ndn::ValidatorNull>())
+{
+}
+
+void
+CommandAuthenticator::setConfigFile(ConfigFile& configFile)
+{
+  configFile.addSectionHandler("authorizations",
+    bind(&CommandAuthenticator::processConfig, this, _1, _2, _3));
+}
+
+void
+CommandAuthenticator::processConfig(const ConfigSection& section, bool isDryRun, const std::string& filename)
+{
+  if (!isDryRun) {
+    NFD_LOG_INFO("clear-authorizations");
+    for (auto& kv : m_moduleAuth) {
+      kv.second.allowAny = false;
+      kv.second.certs.clear();
+    }
+  }
+
+  if (section.empty()) {
+    BOOST_THROW_EXCEPTION(ConfigFile::Error("'authorize' is missing under 'authorizations'"));
+  }
+
+  int authSectionIndex = 0;
+  for (const auto& kv : section) {
+    if (kv.first != "authorize") {
+      BOOST_THROW_EXCEPTION(ConfigFile::Error(
+        "'" + kv.first + "' section is not permitted under 'authorizations'"));
+    }
+    const ConfigSection& authSection = kv.second;
+
+    std::string certfile;
+    try {
+      certfile = authSection.get<std::string>("certfile");
+    }
+    catch (const boost::property_tree::ptree_error&) {
+      BOOST_THROW_EXCEPTION(ConfigFile::Error(
+        "'certfile' is missing under authorize[" + to_string(authSectionIndex) + "]"));
+    }
+
+    bool isAny = false;
+    shared_ptr<ndn::IdentityCertificate> cert;
+    if (certfile == "any") {
+      isAny = true;
+      NFD_LOG_WARN("'certfile any' is intended for demo purposes only and "
+                   "SHOULD NOT be used in production environments");
+    }
+    else {
+      using namespace boost::filesystem;
+      path certfilePath = absolute(certfile, path(filename).parent_path());
+      cert = ndn::io::load<ndn::IdentityCertificate>(certfilePath.string());
+      if (cert == nullptr) {
+        BOOST_THROW_EXCEPTION(ConfigFile::Error(
+          "cannot load certfile " + certfilePath.string() +
+          " for authorize[" + to_string(authSectionIndex) + "]"));
+      }
+    }
+
+    const ConfigSection* privSection = nullptr;
+    try {
+      privSection = &authSection.get_child("privileges");
+    }
+    catch (const boost::property_tree::ptree_error&) {
+      BOOST_THROW_EXCEPTION(ConfigFile::Error(
+        "'privileges' is missing under authorize[" + to_string(authSectionIndex) + "]"));
+    }
+
+    if (privSection->empty()) {
+      NFD_LOG_WARN("No privileges granted to certificate " << certfile);
+    }
+    for (const auto& kv : *privSection) {
+      const std::string& module = kv.first;
+      auto found = m_moduleAuth.find(module);
+      if (found == m_moduleAuth.end()) {
+        BOOST_THROW_EXCEPTION(ConfigFile::Error(
+          "unknown module '" + module + "' under authorize[" + to_string(authSectionIndex) + "]"));
+      }
+
+      if (isDryRun) {
+        continue;
+      }
+
+      if (isAny) {
+        found->second.allowAny = true;
+        NFD_LOG_INFO("authorize module=" << module << " signer=any");
+      }
+      else {
+        const Name& keyName = cert->getPublicKeyName();
+        found->second.certs.emplace(keyName, cert->getPublicKeyInfo());
+        NFD_LOG_INFO("authorize module=" << module << " signer=" << keyName <<
+                     " certfile=" << certfile);
+      }
+    }
+
+    ++authSectionIndex;
+  }
+}
+
+ndn::mgmt::Authorization
+CommandAuthenticator::makeAuthorization(const std::string& module, const std::string& verb)
+{
+  m_moduleAuth[module]; // declares module, so that privilege is recognized
+
+  auto self = this->shared_from_this();
+  return [=] (const Name& prefix, const Interest& interest,
+              const ndn::mgmt::ControlParameters* params,
+              const ndn::mgmt::AcceptContinuation& accept,
+              const ndn::mgmt::RejectContinuation& reject) {
+    const AuthorizedCerts& authorized = self->m_moduleAuth.at(module);
+    if (authorized.allowAny) {
+      NFD_LOG_DEBUG("accept " << interest.getName() << " allowAny");
+      accept("*");
+      return;
+    }
+
+    bool isOk = false;
+    Name keyName;
+    std::tie(isOk, keyName) = CommandAuthenticator::extractKeyName(interest);
+    if (!isOk) {
+      NFD_LOG_DEBUG("reject " << interest.getName() << " bad-KeyLocator");
+      reject(ndn::mgmt::RejectReply::SILENT);
+      return;
+    }
+
+    auto found = authorized.certs.find(keyName);
+    if (found == authorized.certs.end()) {
+      NFD_LOG_DEBUG("reject " << interest.getName() << " signer=" << keyName << " not-authorized");
+      reject(ndn::mgmt::RejectReply::STATUS403);
+      return;
+    }
+
+    bool hasGoodSig = ndn::Validator::verifySignature(interest, found->second);
+    if (!hasGoodSig) {
+      NFD_LOG_DEBUG("reject " << interest.getName() << " signer=" << keyName << " bad-sig");
+      reject(ndn::mgmt::RejectReply::STATUS403);
+      return;
+    }
+
+    self->m_validator.validate(interest,
+      bind([=] {
+        NFD_LOG_DEBUG("accept " << interest.getName() << " signer=" << keyName);
+        accept(keyName.toUri());
+      }),
+      bind([=] {
+        NFD_LOG_DEBUG("reject " << interest.getName() << " signer=" << keyName << " invalid-timestamp");
+        reject(ndn::mgmt::RejectReply::STATUS403);
+      }));
+  };
+}
+
+std::pair<bool, Name>
+CommandAuthenticator::extractKeyName(const Interest& interest)
+{
+  const Name& name = interest.getName();
+  if (name.size() < ndn::signed_interest::MIN_LENGTH) {
+    return {false, Name()};
+  }
+
+  ndn::SignatureInfo sig;
+  try {
+    sig.wireDecode(name[ndn::signed_interest::POS_SIG_INFO].blockFromValue());
+  }
+  catch (const tlv::Error&) {
+    return {false, Name()};
+  }
+
+  if (!sig.hasKeyLocator()) {
+    return {false, Name()};
+  }
+
+  const ndn::KeyLocator& keyLocator = sig.getKeyLocator();
+  if (keyLocator.getType() != ndn::KeyLocator::KeyLocator_Name) {
+    return {false, Name()};
+  }
+
+  try {
+    return {true, ndn::IdentityCertificate::certificateNameToPublicKeyName(keyLocator.getName())};
+  }
+  catch (const ndn::IdentityCertificate::Error&) {
+    return {false, Name()};
+  }
+}
+
+} // namespace nfd