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
diff --git a/daemon/mgmt/command-authenticator.hpp b/daemon/mgmt/command-authenticator.hpp
new file mode 100644
index 0000000..69d0ec9
--- /dev/null
+++ b/daemon/mgmt/command-authenticator.hpp
@@ -0,0 +1,80 @@
+/* -*- 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/>.
+ */
+
+#ifndef NFD_DAEMON_MGMT_COMMAND_AUTHENTICATOR_HPP
+#define NFD_DAEMON_MGMT_COMMAND_AUTHENTICATOR_HPP
+
+#include "core/config-file.hpp"
+#include <ndn-cxx/mgmt/dispatcher.hpp>
+#include <ndn-cxx/security/command-interest-validator.hpp>
+#include <ndn-cxx/security/public-key.hpp>
+
+namespace nfd {
+
+/** \brief provides ControlCommand authorization according to NFD configuration file
+ */
+class CommandAuthenticator : public enable_shared_from_this<CommandAuthenticator>, noncopyable
+{
+public:
+ static shared_ptr<CommandAuthenticator>
+ create();
+
+ void
+ setConfigFile(ConfigFile& configFile);
+
+ /** \return an Authorization function for module/verb command
+ * \param module management module name
+ * \param verb command verb; currently it's ignored
+ * \note This must be called before parsing configuration file
+ */
+ ndn::mgmt::Authorization
+ makeAuthorization(const std::string& module, const std::string& verb);
+
+private:
+ CommandAuthenticator();
+
+ /** \brief process "authorizations" section
+ * \throw ConfigFile::Error on parse error
+ */
+ void
+ processConfig(const ConfigSection& section, bool isDryRun, const std::string& filename);
+
+ static std::pair<bool, Name>
+ extractKeyName(const Interest& interest);
+
+private:
+ struct AuthorizedCerts
+ {
+ bool allowAny = false;
+ std::unordered_map<Name, ndn::PublicKey> certs; ///< keyName => publicKey
+ };
+ std::unordered_map<std::string, AuthorizedCerts> m_moduleAuth; ///< module => certs
+
+ ndn::security::CommandInterestValidator m_validator;
+};
+
+} // namespace nfd
+
+#endif // NFD_DAEMON_MGMT_COMMAND_AUTHENTICATOR_HPP
diff --git a/tests/daemon/mgmt/command-authenticator.t.cpp b/tests/daemon/mgmt/command-authenticator.t.cpp
new file mode 100644
index 0000000..c12fcfa
--- /dev/null
+++ b/tests/daemon/mgmt/command-authenticator.t.cpp
@@ -0,0 +1,478 @@
+/* -*- 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 "mgmt/command-authenticator.hpp"
+#include <ndn-cxx/security/signing-helpers.hpp>
+#include <boost/filesystem.hpp>
+
+#include "tests/test-common.hpp"
+#include "tests/identity-management-fixture.hpp"
+
+namespace nfd {
+namespace tests {
+
+class CommandAuthenticatorFixture : public IdentityManagementTimeFixture
+{
+protected:
+ CommandAuthenticatorFixture()
+ : authenticator(CommandAuthenticator::create())
+ {
+ }
+
+ void
+ makeModules(std::initializer_list<std::string> modules)
+ {
+ for (const std::string& module : modules) {
+ authorizations.emplace(module, authenticator->makeAuthorization(module, "verb"));
+ }
+ }
+
+ void
+ loadConfig(const std::string& config)
+ {
+ boost::filesystem::path configPath = boost::filesystem::current_path() /=
+ "command-authenticator-test.conf";
+ ConfigFile cf;
+ authenticator->setConfigFile(cf);
+ cf.parse(config, false, configPath.c_str());
+ }
+
+ bool
+ authorize(const std::string& module, const Name& identity,
+ const function<void(Interest&)>& modifyInterest = nullptr)
+ {
+ auto interest = makeInterest(Name("/prefix").append(module).append("verb"));
+ m_keyChain.sign(*interest, signingByIdentity(identity));
+ if (modifyInterest != nullptr) {
+ modifyInterest(*interest);
+ }
+
+ ndn::mgmt::Authorization authorization = authorizations.at(module);
+
+ bool isAccepted = false;
+ bool isRejected = false;
+ authorization(Name("/prefix"), *interest, nullptr,
+ [this, &isAccepted, &isRejected] (const std::string& requester) {
+ BOOST_REQUIRE_MESSAGE(!isAccepted && !isRejected,
+ "authorization function should invoke only one continuation");
+ isAccepted = true;
+ lastRequester = requester;
+ },
+ [this, &isAccepted, &isRejected] (ndn::mgmt::RejectReply act) {
+ BOOST_REQUIRE_MESSAGE(!isAccepted && !isRejected,
+ "authorization function should invoke only one continuation");
+ isRejected = true;
+ lastRejectReply = act;
+ });
+
+ this->advanceClocks(time::milliseconds(1), 10);
+ BOOST_REQUIRE_MESSAGE(isAccepted || isRejected,
+ "authorization function should invoke one continuation");
+ return isAccepted;
+ }
+
+protected:
+ shared_ptr<CommandAuthenticator> authenticator;
+ std::unordered_map<std::string, ndn::mgmt::Authorization> authorizations;
+ std::string lastRequester;
+ ndn::mgmt::RejectReply lastRejectReply;
+};
+
+BOOST_AUTO_TEST_SUITE(Mgmt)
+BOOST_FIXTURE_TEST_SUITE(TestCommandAuthenticator, CommandAuthenticatorFixture)
+
+BOOST_AUTO_TEST_CASE(Certs)
+{
+ Name id0("/localhost/CommandAuthenticator/0");
+ Name id1("/localhost/CommandAuthenticator/1");
+ Name id2("/localhost/CommandAuthenticator/2");
+ BOOST_REQUIRE(addIdentity(id0));
+ BOOST_REQUIRE(saveIdentityCertificate(id1, "1.ndncert", true));
+ BOOST_REQUIRE(saveIdentityCertificate(id2, "2.ndncert", true));
+
+ makeModules({"module0", "module1", "module2", "module3", "module4", "module5", "module6", "module7"});
+ const std::string& config = R"CONFIG(
+ authorizations
+ {
+ authorize
+ {
+ certfile any
+ privileges
+ {
+ module1
+ module3
+ module5
+ module7
+ }
+ }
+ authorize
+ {
+ certfile "1.ndncert"
+ privileges
+ {
+ module2
+ module3
+ module6
+ module7
+ }
+ }
+ authorize
+ {
+ certfile "2.ndncert"
+ privileges
+ {
+ module4
+ module5
+ module6
+ module7
+ }
+ }
+ }
+ )CONFIG";
+ loadConfig(config);
+
+ // module0: none
+ BOOST_CHECK_EQUAL(authorize("module0", id0), false);
+ BOOST_CHECK_EQUAL(authorize("module0", id1), false);
+ BOOST_CHECK_EQUAL(authorize("module0", id2), false);
+
+ // module1: any
+ BOOST_CHECK_EQUAL(authorize("module1", id0), true);
+ BOOST_CHECK_EQUAL(authorize("module1", id1), true);
+ BOOST_CHECK_EQUAL(authorize("module1", id2), true);
+
+ // module2: id1
+ BOOST_CHECK_EQUAL(authorize("module2", id0), false);
+ BOOST_CHECK_EQUAL(authorize("module2", id1), true);
+ BOOST_CHECK_EQUAL(authorize("module2", id2), false);
+
+ // module3: any,id1
+ BOOST_CHECK_EQUAL(authorize("module3", id0), true);
+ BOOST_CHECK_EQUAL(authorize("module3", id1), true);
+ BOOST_CHECK_EQUAL(authorize("module3", id2), true);
+
+ // module4: id2
+ BOOST_CHECK_EQUAL(authorize("module4", id0), false);
+ BOOST_CHECK_EQUAL(authorize("module4", id1), false);
+ BOOST_CHECK_EQUAL(authorize("module4", id2), true);
+
+ // module5: any,id2
+ BOOST_CHECK_EQUAL(authorize("module5", id0), true);
+ BOOST_CHECK_EQUAL(authorize("module5", id1), true);
+ BOOST_CHECK_EQUAL(authorize("module5", id2), true);
+
+ // module6: id1,id2
+ BOOST_CHECK_EQUAL(authorize("module6", id0), false);
+ BOOST_CHECK_EQUAL(authorize("module6", id1), true);
+ BOOST_CHECK_EQUAL(authorize("module6", id2), true);
+
+ // module7: any,id1,id2
+ BOOST_CHECK_EQUAL(authorize("module7", id0), true);
+ BOOST_CHECK_EQUAL(authorize("module7", id1), true);
+ BOOST_CHECK_EQUAL(authorize("module7", id2), true);
+}
+
+BOOST_AUTO_TEST_CASE(Requester)
+{
+ Name id0("/localhost/CommandAuthenticator/0");
+ Name id1("/localhost/CommandAuthenticator/1");
+ BOOST_REQUIRE(addIdentity(id0));
+ BOOST_REQUIRE(saveIdentityCertificate(id1, "1.ndncert", true));
+
+ makeModules({"module0", "module1"});
+ const std::string& config = R"CONFIG(
+ authorizations
+ {
+ authorize
+ {
+ certfile any
+ privileges
+ {
+ module0
+ }
+ }
+ authorize
+ {
+ certfile "1.ndncert"
+ privileges
+ {
+ module1
+ }
+ }
+ }
+ )CONFIG";
+ loadConfig(config);
+
+ // module0: any
+ BOOST_CHECK_EQUAL(authorize("module0", id0), true);
+ BOOST_CHECK_EQUAL(lastRequester, "*");
+ BOOST_CHECK_EQUAL(authorize("module0", id1), true);
+ BOOST_CHECK_EQUAL(lastRequester, "*");
+
+ // module1: id1
+ BOOST_CHECK_EQUAL(authorize("module1", id0), false);
+ BOOST_CHECK_EQUAL(authorize("module1", id1), true);
+ BOOST_CHECK(id1.isPrefixOf(lastRequester));
+}
+
+class IdentityAuthorizedFixture : public CommandAuthenticatorFixture
+{
+protected:
+ IdentityAuthorizedFixture()
+ : id1("/localhost/CommandAuthenticator/1")
+ {
+ BOOST_REQUIRE(saveIdentityCertificate(id1, "1.ndncert", true));
+
+ makeModules({"module1"});
+ const std::string& config = R"CONFIG(
+ authorizations
+ {
+ authorize
+ {
+ certfile "1.ndncert"
+ privileges
+ {
+ module1
+ }
+ }
+ }
+ )CONFIG";
+ loadConfig(config);
+ }
+
+ bool
+ authorize1(const function<void(Interest&)>& modifyInterest)
+ {
+ return authorize("module1", id1, modifyInterest);
+ }
+
+protected:
+ Name id1;
+};
+
+BOOST_FIXTURE_TEST_SUITE(Rejects, IdentityAuthorizedFixture)
+
+BOOST_AUTO_TEST_CASE(BadKeyLocator_NameTooShort)
+{
+ BOOST_CHECK_EQUAL(authorize1(
+ [] (Interest& interest) {
+ interest.setName("/prefix");
+ }
+ ), false);
+ BOOST_CHECK(lastRejectReply == ndn::mgmt::RejectReply::SILENT);
+}
+
+BOOST_AUTO_TEST_CASE(BadKeyLocator_BadSigInfo)
+{
+ BOOST_CHECK_EQUAL(authorize1(
+ [] (Interest& interest) {
+ setNameComponent(interest, ndn::signed_interest::POS_SIG_INFO, "not-SignatureInfo");
+ }
+ ), false);
+ BOOST_CHECK(lastRejectReply == ndn::mgmt::RejectReply::SILENT);
+}
+
+BOOST_AUTO_TEST_CASE(BadKeyLocator_MissingKeyLocator)
+{
+ BOOST_CHECK_EQUAL(authorize1(
+ [] (Interest& interest) {
+ ndn::SignatureInfo sigInfo;
+ setNameComponent(interest, ndn::signed_interest::POS_SIG_INFO,
+ sigInfo.wireEncode().begin(), sigInfo.wireEncode().end());
+ }
+ ), false);
+ BOOST_CHECK(lastRejectReply == ndn::mgmt::RejectReply::SILENT);
+}
+
+BOOST_AUTO_TEST_CASE(BadKeyLocator_BadKeyLocatorType)
+{
+ BOOST_CHECK_EQUAL(authorize1(
+ [] (Interest& interest) {
+ ndn::KeyLocator kl;
+ kl.setKeyDigest(ndn::encoding::makeBinaryBlock(tlv::KeyDigest, "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD", 8));
+ ndn::SignatureInfo sigInfo;
+ sigInfo.setKeyLocator(kl);
+ setNameComponent(interest, ndn::signed_interest::POS_SIG_INFO,
+ sigInfo.wireEncode().begin(), sigInfo.wireEncode().end());
+ }
+ ), false);
+ BOOST_CHECK(lastRejectReply == ndn::mgmt::RejectReply::SILENT);
+}
+
+BOOST_AUTO_TEST_CASE(BadKeyLocator_BadCertName)
+{
+ BOOST_CHECK_EQUAL(authorize1(
+ [] (Interest& interest) {
+ ndn::KeyLocator kl;
+ kl.setName("/bad/cert/name");
+ ndn::SignatureInfo sigInfo;
+ sigInfo.setKeyLocator(kl);
+ setNameComponent(interest, ndn::signed_interest::POS_SIG_INFO,
+ sigInfo.wireEncode().begin(), sigInfo.wireEncode().end());
+ }
+ ), false);
+ BOOST_CHECK(lastRejectReply == ndn::mgmt::RejectReply::SILENT);
+}
+
+BOOST_AUTO_TEST_CASE(NotAuthorized)
+{
+ Name id0("/localhost/CommandAuthenticator/0");
+ BOOST_REQUIRE(addIdentity(id0));
+
+ BOOST_CHECK_EQUAL(authorize("module1", id0), false);
+ BOOST_CHECK(lastRejectReply == ndn::mgmt::RejectReply::STATUS403);
+}
+
+BOOST_AUTO_TEST_CASE(BadSig)
+{
+ BOOST_CHECK_EQUAL(authorize1(
+ [] (Interest& interest) {
+ setNameComponent(interest, ndn::signed_interest::POS_SIG_VALUE, "bad-signature-bits");
+ }
+ ), false);
+ BOOST_CHECK(lastRejectReply == ndn::mgmt::RejectReply::STATUS403);
+}
+
+BOOST_AUTO_TEST_CASE(InvalidTimestamp)
+{
+ name::Component timestampComp;
+ BOOST_CHECK_EQUAL(authorize1(
+ [×tampComp] (const Interest& interest) {
+ timestampComp = interest.getName().at(ndn::signed_interest::POS_TIMESTAMP);
+ }
+ ), true); // accept first command
+ BOOST_CHECK_EQUAL(authorize1(
+ [×tampComp] (Interest& interest) {
+ setNameComponent(interest, ndn::signed_interest::POS_TIMESTAMP, timestampComp);
+ }
+ ), false); // reject second command because timestamp equals first command
+ BOOST_CHECK(lastRejectReply == ndn::mgmt::RejectReply::STATUS403);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Rejects
+
+BOOST_AUTO_TEST_SUITE(BadConfig)
+
+BOOST_AUTO_TEST_CASE(EmptyAuthorizationsSection)
+{
+ const std::string& config = R"CONFIG(
+ authorizations
+ {
+ }
+ )CONFIG";
+
+ BOOST_CHECK_THROW(loadConfig(config), ConfigFile::Error);
+}
+
+BOOST_AUTO_TEST_CASE(UnrecognizedKey)
+{
+ const std::string& config = R"CONFIG(
+ authorizations
+ {
+ unrecognized_key
+ {
+ }
+ }
+ )CONFIG";
+
+ BOOST_CHECK_THROW(loadConfig(config), ConfigFile::Error);
+}
+
+BOOST_AUTO_TEST_CASE(CertfileMissing)
+{
+ const std::string& config = R"CONFIG(
+ authorizations
+ {
+ authorize
+ {
+ privileges
+ {
+ }
+ }
+ }
+ )CONFIG";
+
+ BOOST_CHECK_THROW(loadConfig(config), ConfigFile::Error);
+}
+
+BOOST_AUTO_TEST_CASE(CertUnreadable)
+{
+ const std::string& config = R"CONFIG(
+ authorizations
+ {
+ authorize
+ {
+ certfile "1.ndncert"
+ privileges
+ {
+ }
+ }
+ }
+ )CONFIG";
+
+ BOOST_CHECK_THROW(loadConfig(config), ConfigFile::Error);
+}
+
+BOOST_AUTO_TEST_CASE(PrivilegesMissing)
+{
+ const std::string& config = R"CONFIG(
+ authorizations
+ {
+ authorize
+ {
+ certfile any
+ }
+ }
+ )CONFIG";
+
+ BOOST_CHECK_THROW(loadConfig(config), ConfigFile::Error);
+}
+
+BOOST_AUTO_TEST_CASE(UnregisteredModule)
+{
+ const std::string& config = R"CONFIG(
+ authorizations
+ {
+ authorize
+ {
+ certfile any
+ privileges
+ {
+ nosuchmodule
+ }
+ }
+ }
+ )CONFIG";
+
+ BOOST_CHECK_THROW(loadConfig(config), ConfigFile::Error);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // BadConfig
+
+BOOST_AUTO_TEST_SUITE_END() // TestCommandAuthenticator
+BOOST_AUTO_TEST_SUITE_END() // Mgmt
+
+} // namespace tests
+} // namespace nfd
diff --git a/tests/identity-management-fixture.cpp b/tests/identity-management-fixture.cpp
index 1575fa8..3bb7252 100644
--- a/tests/identity-management-fixture.cpp
+++ b/tests/identity-management-fixture.cpp
@@ -24,6 +24,8 @@
*/
#include "identity-management-fixture.hpp"
+#include <ndn-cxx/util/io.hpp>
+#include <boost/filesystem.hpp>
namespace nfd {
namespace tests {
@@ -31,18 +33,23 @@
IdentityManagementFixture::IdentityManagementFixture()
: m_keyChain("sqlite3", "file")
{
- m_keyChain.getDefaultCertificate(); // side effect: creation of the default cert if doesn't exist
+ m_keyChain.getDefaultCertificate(); // side effect: create a default cert if it doesn't exist
}
IdentityManagementFixture::~IdentityManagementFixture()
{
- for (auto&& id : m_identities) {
+ for (const auto& id : m_identities) {
m_keyChain.deleteIdentity(id);
}
+
+ boost::system::error_code ec;
+ for (const auto& certFile : m_certFiles) {
+ boost::filesystem::remove(certFile, ec); // ignore error
+ }
}
bool
-IdentityManagementFixture::addIdentity(const ndn::Name& identity, const ndn::KeyParams& params)
+IdentityManagementFixture::addIdentity(const Name& identity, const ndn::KeyParams& params)
{
try {
m_keyChain.createIdentity(identity, params);
@@ -54,5 +61,29 @@
}
}
+bool
+IdentityManagementFixture::saveIdentityCertificate(const Name& identity, const std::string& filename, bool wantAdd)
+{
+ shared_ptr<ndn::IdentityCertificate> cert;
+ try {
+ cert = m_keyChain.getCertificate(m_keyChain.getDefaultCertificateNameForIdentity(identity));
+ }
+ catch (const ndn::SecPublicInfo::Error&) {
+ if (wantAdd && this->addIdentity(identity)) {
+ return this->saveIdentityCertificate(identity, filename, false);
+ }
+ return false;
+ }
+
+ m_certFiles.push_back(filename);
+ try {
+ ndn::io::save(*cert, filename);
+ return true;
+ }
+ catch (const ndn::io::Error&) {
+ return false;
+ }
+}
+
} // namespace tests
} // namespace nfd
diff --git a/tests/identity-management-fixture.hpp b/tests/identity-management-fixture.hpp
index 0f7d3c4..318cdd1 100644
--- a/tests/identity-management-fixture.hpp
+++ b/tests/identity-management-fixture.hpp
@@ -28,37 +28,48 @@
#include "tests/test-common.hpp"
#include <ndn-cxx/security/key-chain.hpp>
-#include <vector>
-
-#include "boost-test.hpp"
namespace nfd {
namespace tests {
-/**
- * @brief IdentityManagementFixture is a test suite level fixture.
- *
- * Test cases in the suite can use this fixture to create identities.
- * Identities added via addIdentity method are automatically deleted
- * during test teardown.
+/** \brief a fixture that cleans up KeyChain identities and certificate files upon destruction
*/
class IdentityManagementFixture : public virtual BaseFixture
{
public:
IdentityManagementFixture();
+ /** \brief deletes created identities and saved certificate files
+ */
~IdentityManagementFixture();
- // @brief add identity, return true if succeed.
+ /** \brief add identity
+ * \return whether successful
+ */
bool
- addIdentity(const ndn::Name& identity,
+ addIdentity(const Name& identity,
const ndn::KeyParams& params = ndn::KeyChain::DEFAULT_KEY_PARAMS);
+ /** \brief save identity certificate to a file
+ * \param identity identity name
+ * \param filename file name, should be writable
+ * \param wantAdd if true, add new identity when necessary
+ * \return whether successful
+ */
+ bool
+ saveIdentityCertificate(const Name& identity, const std::string& filename, bool wantAdd = false);
+
protected:
ndn::KeyChain m_keyChain;
+
+private:
std::vector<ndn::Name> m_identities;
+ std::vector<std::string> m_certFiles;
};
+/** \brief convenience base class for inheriting from both UnitTestTimeFixture
+ * and IdentityManagementFixture
+ */
class IdentityManagementTimeFixture : public UnitTestTimeFixture
, public IdentityManagementFixture
{
diff --git a/tests/test-common.hpp b/tests/test-common.hpp
index 57727da..ffff22a 100644
--- a/tests/test-common.hpp
+++ b/tests/test-common.hpp
@@ -150,6 +150,30 @@
lp::Nack
makeNack(const Name& name, uint32_t nonce, lp::NackReason reason);
+/** \brief replace a name component
+ * \param[inout] name name
+ * \param index name component index
+ * \param a arguments to name::Component constructor
+ */
+template<typename...A>
+void
+setNameComponent(Name& name, ssize_t index, const A& ...a)
+{
+ Name name2 = name.getPrefix(index);
+ name2.append(name::Component(a...));
+ name2.append(name.getSubName(name2.size()));
+ name = name2;
+}
+
+template<typename Packet, typename...A>
+void
+setNameComponent(Packet& packet, ssize_t index, const A& ...a)
+{
+ Name name = packet.getName();
+ setNameComponent(name, index, a...);
+ packet.setName(name);
+}
+
} // namespace tests
} // namespace nfd