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