mgmt: use v2 ValidationPolicy in CommandAuthenticator
refs #4089
Change-Id: I9a1cc77a960abe1e58a744f9d065634ab7597d15
diff --git a/daemon/mgmt/command-authenticator.cpp b/daemon/mgmt/command-authenticator.cpp
index 63ab319..30ee829 100644
--- a/daemon/mgmt/command-authenticator.cpp
+++ b/daemon/mgmt/command-authenticator.cpp
@@ -26,28 +26,83 @@
#include "command-authenticator.hpp"
#include "core/logger.hpp"
-#include <ndn-cxx/security/security-common.hpp>
-#include <ndn-cxx/security/verification-helpers.hpp>
+#include <ndn-cxx/tag.hpp>
+#include <ndn-cxx/security/v2/certificate-fetcher-offline.hpp>
+#include <ndn-cxx/security/v2/certificate-request.hpp>
+#include <ndn-cxx/security/v2/validation-policy.hpp>
+#include <ndn-cxx/security/v2/validation-policy-accept-all.hpp>
+#include <ndn-cxx/security/v2/validation-policy-command-interest.hpp>
+#include <ndn-cxx/security/v2/validator.hpp>
#include <ndn-cxx/util/io.hpp>
#include <boost/filesystem.hpp>
+namespace sec2 = ndn::security::v2;
+
namespace nfd {
NFD_LOG_INIT("CommandAuthenticator");
// INFO: configuration change, etc
// DEBUG: per authentication request result
+/** \brief an Interest tag to indicate command signer
+ */
+using SignerTag = ndn::SimpleTag<Name, 20>;
+
+/** \brief obtain signer from SignerTag attached to Interest, if available
+ */
+static ndn::optional<std::string>
+getSignerFromTag(const Interest& interest)
+{
+ shared_ptr<SignerTag> signerTag = interest.getTag<SignerTag>();
+ if (signerTag == nullptr) {
+ return ndn::nullopt;
+ }
+ else {
+ return signerTag->get().toUri();
+ }
+}
+
+/** \brief a validation policy that only permits Interest signed by a trust anchor
+ */
+class CommandAuthenticatorValidationPolicy : public sec2::ValidationPolicy
+{
+public:
+ void
+ checkPolicy(const Interest& interest, const shared_ptr<sec2::ValidationState>& state,
+ const ValidationContinuation& continueValidation) final
+ {
+ Name klName = getKeyLocatorName(interest, *state);
+ if (!state->getOutcome()) { // already failed
+ return;
+ }
+
+ // SignerTag must be placed on the 'original Interest' in ValidationState to be available for
+ // InterestValidationSuccessCallback. The 'interest' parameter refers to a different instance
+ // which is copied into 'original Interest'.
+ auto state1 = dynamic_pointer_cast<sec2::InterestValidationState>(state);
+ state1->getOriginalInterest().setTag(make_shared<SignerTag>(klName));
+
+ continueValidation(make_shared<sec2::CertificateRequest>(Interest(klName)), state);
+ }
+
+ void
+ checkPolicy(const Data& data, const shared_ptr<sec2::ValidationState>& state,
+ const ValidationContinuation& continueValidation) final
+ {
+ // Non-certificate Data are not handled by CommandAuthenticator.
+ // Non-anchor certificates cannot be retrieved by offline fetcher.
+ BOOST_ASSERT_MSG(false, "Data should not be passed to this policy");
+ }
+};
+
shared_ptr<CommandAuthenticator>
CommandAuthenticator::create()
{
- return shared_ptr<CommandAuthenticator>(new CommandAuthenticator());
+ return shared_ptr<CommandAuthenticator>(new CommandAuthenticator);
}
-CommandAuthenticator::CommandAuthenticator()
-{
- NFD_LOG_WARN("Command Interest timestamp checking is currently bypassed.");
-}
+CommandAuthenticator::CommandAuthenticator() = default;
void
CommandAuthenticator::setConfigFile(ConfigFile& configFile)
@@ -61,9 +116,10 @@
{
if (!isDryRun) {
NFD_LOG_INFO("clear-authorizations");
- for (auto& kv : m_moduleAuth) {
- kv.second.allowAny = false;
- kv.second.certs.clear();
+ for (auto& kv : m_validators) {
+ kv.second = make_shared<sec2::Validator>(
+ make_unique<sec2::ValidationPolicyCommandInterest>(make_unique<CommandAuthenticatorValidationPolicy>()),
+ make_unique<sec2::CertificateFetcherOffline>());
}
}
@@ -89,7 +145,7 @@
}
bool isAny = false;
- shared_ptr<ndn::security::v2::Certificate> cert;
+ shared_ptr<sec2::Certificate> cert;
if (certfile == "any") {
isAny = true;
NFD_LOG_WARN("'certfile any' is intended for demo purposes only and "
@@ -98,7 +154,7 @@
else {
using namespace boost::filesystem;
path certfilePath = absolute(certfile, path(filename).parent_path());
- cert = ndn::io::load<ndn::security::v2::Certificate>(certfilePath.string());
+ cert = ndn::io::load<sec2::Certificate>(certfilePath.string());
if (cert == nullptr) {
BOOST_THROW_EXCEPTION(ConfigFile::Error(
"cannot load certfile " + certfilePath.string() +
@@ -120,8 +176,8 @@
}
for (const auto& kv : *privSection) {
const std::string& module = kv.first;
- auto found = m_moduleAuth.find(module);
- if (found == m_moduleAuth.end()) {
+ auto found = m_validators.find(module);
+ if (found == m_validators.end()) {
BOOST_THROW_EXCEPTION(ConfigFile::Error(
"unknown module '" + module + "' under authorize[" + to_string(authSectionIndex) + "]"));
}
@@ -131,12 +187,14 @@
}
if (isAny) {
- found->second.allowAny = true;
+ found->second = make_shared<sec2::Validator>(make_unique<sec2::ValidationPolicyAcceptAll>(),
+ make_unique<sec2::CertificateFetcherOffline>());
NFD_LOG_INFO("authorize module=" << module << " signer=any");
}
else {
const Name& keyName = cert->getKeyName();
- found->second.certs.emplace(keyName, *cert);
+ sec2::Certificate certCopy = *cert;
+ found->second->loadAnchor(certfile, std::move(certCopy));
NFD_LOG_INFO("authorize module=" << module << " signer=" << keyName <<
" certfile=" << certfile);
}
@@ -149,90 +207,46 @@
ndn::mgmt::Authorization
CommandAuthenticator::makeAuthorization(const std::string& module, const std::string& verb)
{
- m_moduleAuth[module]; // declares module, so that privilege is recognized
+ m_validators[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;
- }
+ shared_ptr<sec2::Validator> validator = self->m_validators.at(module);
+ validator->validate(interest,
+ [accept, validator] (const Interest& interest1) {
+ auto signer1 = getSignerFromTag(interest1);
+ BOOST_ASSERT(signer1 || // signer must be available unless 'certfile any'
+ dynamic_cast<sec2::ValidationPolicyAcceptAll*>(&validator->getPolicy()) != nullptr);
+ std::string signer = signer1.value_or("*");
+ NFD_LOG_DEBUG("accept " << interest1.getName() << " signer=" << signer);
+ accept(signer);
+ },
+ [reject] (const Interest& interest1, const sec2::ValidationError& err) {
+ NFD_LOG_DEBUG("reject " << interest1.getName() << " signer=" <<
+ getSignerFromTag(interest1).value_or("?") << ' ' << err);
- 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::security::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);
- // }));
-
- /// \todo restore timestamp checking
- NFD_LOG_DEBUG("accept " << interest.getName() << " signer=" << keyName);
- accept(keyName.toUri());
+ using ndn::mgmt::RejectReply;
+ RejectReply reply = RejectReply::STATUS403;
+ using ErrCode = sec2::ValidationError::Code;
+ switch (err.getCode()) {
+ case ErrCode::NO_SIGNATURE:
+ case ErrCode::INVALID_KEY_LOCATOR:
+ reply = RejectReply::SILENT;
+ break;
+ case ErrCode::POLICY_ERROR:
+ if (interest1.getName().size() < ndn::command_interest::MIN_SIZE) { // "name too short"
+ reply = RejectReply::SILENT;
+ }
+ break;
+ default:
+ break;
+ }
+ reject(reply);
+ });
};
}
-std::pair<bool, Name>
-CommandAuthenticator::extractKeyName(const Interest& interest)
-{
- const Name& name = interest.getName();
- if (name.size() < ndn::command_interest::MIN_SIZE) {
- return {false, Name()};
- }
-
- ndn::SignatureInfo sig;
- try {
- sig.wireDecode(name[ndn::command_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, keyLocator.getName()};
- }
- catch (const std::invalid_argument&) {
- return {false, Name()};
- }
-}
-
} // namespace nfd
diff --git a/daemon/mgmt/command-authenticator.hpp b/daemon/mgmt/command-authenticator.hpp
index 0924e9b..114dcd3 100644
--- a/daemon/mgmt/command-authenticator.hpp
+++ b/daemon/mgmt/command-authenticator.hpp
@@ -28,7 +28,14 @@
#include "core/config-file.hpp"
#include <ndn-cxx/mgmt/dispatcher.hpp>
-#include <ndn-cxx/security/v2/certificate.hpp>
+
+namespace ndn {
+namespace security {
+namespace v2 {
+class Validator;
+} // namespace v2
+} // namespace security
+} // namespace ndn
namespace nfd {
@@ -60,16 +67,9 @@
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::security::v2::Certificate> certs; ///< keyName => cert
- };
- std::unordered_map<std::string, AuthorizedCerts> m_moduleAuth; ///< module => certs
+ /// module => validator
+ std::unordered_map<std::string, shared_ptr<ndn::security::v2::Validator>> m_validators;
};
} // namespace nfd
diff --git a/tests/daemon/mgmt/command-authenticator.t.cpp b/tests/daemon/mgmt/command-authenticator.t.cpp
index 498f44f..153b34e 100644
--- a/tests/daemon/mgmt/command-authenticator.t.cpp
+++ b/tests/daemon/mgmt/command-authenticator.t.cpp
@@ -339,7 +339,6 @@
BOOST_CHECK(lastRejectReply == ndn::mgmt::RejectReply::STATUS403);
}
-BOOST_AUTO_TEST_CASE_EXPECTED_FAILURES(InvalidTimestamp, 2)
BOOST_AUTO_TEST_CASE(InvalidTimestamp)
{
name::Component timestampComp;