add Validator
Change-Id: Ib8ce9023aad0782f934e8e6e559840b559d68208
diff --git a/tests/unit/dummy-client-face.hpp b/tests/unit/dummy-client-face.hpp
new file mode 100644
index 0000000..649e714
--- /dev/null
+++ b/tests/unit/dummy-client-face.hpp
@@ -0,0 +1,236 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014, Regents of the University of California.
+ *
+ * This file is part of NDNS (Named Data Networking Domain Name Service).
+ * See AUTHORS.md for complete list of NDNS authors and contributors.
+ *
+ * NDNS 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.
+ *
+ * NDNS 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
+ * NDNS, e.g., in COPYING.md file. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * Copyright (c) 2013-2014 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.
+ */
+
+#ifndef NDNS_TESTS_UNIT_DUMMY_CLIENT_FACE_HPP
+#define NDNS_TESTS_UNIT_DUMMY_CLIENT_FACE_HPP
+
+#include <ndn-cxx/face.hpp>
+#include <ndn-cxx/transport/transport.hpp>
+#include <ndn-cxx/management/nfd-controller.hpp>
+#include <ndn-cxx/management/nfd-control-response.hpp>
+#include <ndn-cxx/util/event-emitter.hpp>
+
+
+namespace ndn {
+namespace tests {
+
+class DummyClientTransport : public ndn::Transport
+{
+public:
+ void
+ receive(const Block& block)
+ {
+ if (static_cast<bool>(m_receiveCallback))
+ m_receiveCallback(block);
+ }
+
+ virtual void
+ close()
+ {
+ }
+
+ virtual void
+ pause()
+ {
+ }
+
+ virtual void
+ resume()
+ {
+ }
+
+ virtual void
+ send(const Block& wire)
+ {
+ if (wire.type() == tlv::Interest) {
+ shared_ptr<Interest> interest = make_shared<Interest>(wire);
+ (*m_onInterest)(*interest, this);
+ }
+ else if (wire.type() == tlv::Data) {
+ shared_ptr<Data> data = make_shared<Data>(wire);
+ (*m_onData)(*data, this);
+ }
+ }
+
+ virtual void
+ send(const Block& header, const Block& payload)
+ {
+ this->send(payload);
+ }
+
+ boost::asio::io_service&
+ getIoService()
+ {
+ return *m_ioService;
+ }
+
+private:
+ friend class DummyClientFace;
+ util::EventEmitter<Interest, DummyClientTransport*>* m_onInterest;
+ util::EventEmitter<Data, DummyClientTransport*>* m_onData;
+};
+
+
+/** \brief Callback to connect
+ */
+inline void
+replyNfdRibCommands(const Interest& interest, DummyClientTransport* transport)
+{
+ static const Name localhostRegistration("/localhost/nfd/rib");
+ if (localhostRegistration.isPrefixOf(interest.getName())) {
+ shared_ptr<Data> okResponse = make_shared<Data>(interest.getName());
+ nfd::ControlParameters params(interest.getName().get(-5).blockFromValue());
+ params.setFaceId(1);
+ params.setOrigin(0);
+ if (interest.getName().get(3) == name::Component("register")) {
+ params.setCost(0);
+ }
+ nfd::ControlResponse resp;
+ resp.setCode(200);
+ resp.setBody(params.wireEncode());
+ okResponse->setContent(resp.wireEncode());
+ KeyChain keyChain;
+ keyChain.signWithSha256(*okResponse);
+
+ transport->getIoService().post(bind(&DummyClientTransport::receive, transport,
+ okResponse->wireEncode()));
+ }
+}
+
+/** \brief a client-side face for unit testing
+ */
+class DummyClientFace : public ndn::Face
+{
+public:
+ explicit
+ DummyClientFace(shared_ptr<DummyClientTransport> transport)
+ : Face(transport)
+ , m_transport(transport)
+ {
+ m_transport->m_onInterest = &onInterest;
+ m_transport->m_onData = &onData;
+
+ enablePacketLogging();
+ }
+
+ DummyClientFace(shared_ptr<DummyClientTransport> transport, boost::asio::io_service& ioService)
+ : Face(transport, ioService)
+ , m_transport(transport)
+ {
+ m_transport->m_onInterest = &onInterest;
+ m_transport->m_onData = &onData;
+
+ enablePacketLogging();
+ }
+
+ /** \brief cause the Face to receive a packet
+ */
+ template<typename Packet>
+ void
+ receive(const Packet& packet)
+ {
+ m_transport->receive(packet.wireEncode());
+ }
+
+ void
+ enablePacketLogging()
+ {
+ // @todo Replace with C++11 lambdas
+
+ onInterest += bind(static_cast<void(std::vector<Interest>::*)(const Interest&)>(
+ &std::vector<Interest>::push_back),
+ &m_sentInterests, _1);
+
+ onData += bind(static_cast<void(std::vector<Data>::*)(const Data&)>(
+ &std::vector<Data>::push_back),
+ &m_sentDatas, _1);
+ }
+
+ void
+ enableRegistrationReply()
+ {
+ onInterest += &replyNfdRibCommands;
+ }
+
+public:
+ /** \brief sent Interests
+ * \note After .expressInterest, .processEvents must be called before
+ * the Interest would show up here.
+ */
+ std::vector<Interest> m_sentInterests;
+ /** \brief sent Datas
+ * \note After .put, .processEvents must be called before
+ * the Interest would show up here.
+ */
+ std::vector<Data> m_sentDatas;
+
+public:
+ /** \brief Event to be called whenever an Interest is received
+ * \note After .expressInterest, .processEvents must be called before
+ * the Interest would show up here.
+ */
+ util::EventEmitter<Interest, DummyClientTransport*> onInterest;
+
+ /** \brief Event to be called whenever a Data packet is received
+ * \note After .put, .processEvents must be called before
+ * the Interest would show up here.
+ */
+ util::EventEmitter<Data, DummyClientTransport*> onData;
+
+private:
+ shared_ptr<DummyClientTransport> m_transport;
+};
+
+inline shared_ptr<DummyClientFace>
+makeDummyClientFace()
+{
+ return make_shared<DummyClientFace>(make_shared<DummyClientTransport>());
+}
+
+inline shared_ptr<DummyClientFace>
+makeDummyClientFace(boost::asio::io_service& ioService)
+{
+ return make_shared<DummyClientFace>(make_shared<DummyClientTransport>(), ref(ioService));
+}
+
+
+} // namespace tests
+} // namespace ndn
+
+#endif // NDNS_TESTS_UNIT_DUMMY_CLIENT_FACE_HPP
diff --git a/tests/unit/validator.cpp b/tests/unit/validator.cpp
new file mode 100644
index 0000000..26c25ac
--- /dev/null
+++ b/tests/unit/validator.cpp
@@ -0,0 +1,264 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014, Regents of the University of California.
+ *
+ * This file is part of NDNS (Named Data Networking Domain Name Service).
+ * See AUTHORS.md for complete list of NDNS authors and contributors.
+ *
+ * NDNS 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.
+ *
+ * NDNS 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
+ * NDNS, e.g., in COPYING.md file. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "validator.hpp"
+#include "dummy-client-face.hpp"
+#include <ndn-cxx/security/key-chain.hpp>
+#include "../boost-test.hpp"
+
+namespace ndn {
+namespace ndns {
+namespace tests {
+NDNS_LOG_INIT("ValidatorTest");
+
+BOOST_AUTO_TEST_SUITE(Validator)
+
+class Fixture
+{
+public:
+ Fixture()
+ : m_testId1("/test02")
+ , m_testId2("/test02/ndn")
+ , m_testId3("/test02/ndn/edu")
+ , m_randomId("/test03")
+ , m_version(name::Component::fromVersion(0))
+ , m_face(::ndn::tests::makeDummyClientFace())
+ {
+ m_keyChain.deleteIdentity(m_testId1);
+ m_keyChain.deleteIdentity(m_testId2);
+ m_keyChain.deleteIdentity(m_testId3);
+ m_keyChain.deleteIdentity(m_randomId);
+
+ m_randomDsk = createRoot(m_randomId); // generate a root cert
+
+ m_dsk1 = createRoot(m_testId1); // replace to root cert
+ m_dsk2 = createIdentity(m_testId2, m_dsk1);
+ m_dsk3 = createIdentity(m_testId3, m_dsk2);
+
+ m_selfSignCert = m_keyChain.generateRsaKeyPair(m_testId3, false);
+ shared_ptr<IdentityCertificate> cert = m_keyChain.selfSign(m_selfSignCert);
+ m_selfSignCert = cert->getName();
+ m_keyChain.addCertificate(*cert);
+ NDNS_LOG_TRACE("add cert: " << cert->getName() << " to KeyChain");
+
+ m_face->onInterest += bind(&Fixture::respondInterest, this, _1, _2);
+ }
+
+ ~Fixture()
+ {
+ m_face->getIoService().stop();
+ m_face->shutdown();
+ m_keyChain.deleteIdentity(m_testId1);
+ m_keyChain.deleteIdentity(m_testId2);
+ m_keyChain.deleteIdentity(m_testId3);
+ m_keyChain.deleteIdentity(m_randomId);
+ }
+
+ const Name
+ createIdentity(const Name& id, const Name& parentCertName)
+ {
+ Name kskCertName = m_keyChain.createIdentity(id);
+ Name kskName = m_keyChain.getDefaultKeyNameForIdentity(id);
+ m_keyChain.deleteCertificate(kskCertName);
+ auto kskCert = createCertificate(kskName, parentCertName);
+
+ Name dskName = m_keyChain.generateRsaKeyPair(id, false);
+ auto dskCert = createCertificate(dskName, kskCert);
+ return dskCert;
+ }
+
+ const Name
+ createRoot(const Name& root)
+ {
+ m_rootCert = m_keyChain.createIdentity(root);
+ ndn::io::save(*(m_keyChain.getCertificate(m_rootCert)), TEST_CONFIG_PATH "/anchors/root.cert");
+ NDNS_LOG_TRACE("save root cert "<< m_rootCert <<
+ " to: " << TEST_CONFIG_PATH "/anchors/root.cert");
+ Name dsk = m_keyChain.generateRsaKeyPair(root, false);
+ auto cert = createCertificate(dsk, m_rootCert);
+ return cert;
+ }
+
+
+ const Name
+ createCertificate(const Name& keyName, const Name& parentCertName)
+ {
+ std::vector<CertificateSubjectDescription> desc;
+ time::system_clock::TimePoint notBefore = time::system_clock::now();
+ time::system_clock::TimePoint notAfter = notBefore + time::days(365);
+ desc.push_back(CertificateSubjectDescription(oid::ATTRIBUTE_NAME,
+ "Signer: " + parentCertName.toUri()));
+ shared_ptr<IdentityCertificate> cert =
+ m_keyChain.prepareUnsignedIdentityCertificate(keyName, parentCertName,
+ notBefore, notAfter, desc);
+
+ Name tmp = cert->getName().getPrefix(-1).append(m_version);
+ cert->setName(tmp);
+ m_keyChain.sign(*cert, parentCertName);
+ m_keyChain.addCertificateAsKeyDefault(*cert);
+ NDNS_LOG_TRACE("add cert: " << cert->getName() << " to KeyChain");
+ return cert->getName();
+ }
+
+
+ void
+ respondInterest(const Interest& interest, ndn::tests::DummyClientTransport* transport)
+ {
+ Name certName = interest.getName();
+ if (certName.isPrefixOf(m_selfSignCert)) {
+ // self-sign cert's version number is not m_version
+ certName = m_selfSignCert;
+ } else {
+ certName.append(m_version);
+ }
+ NDNS_LOG_TRACE("validator needs: " << certName);
+ BOOST_CHECK_EQUAL(m_keyChain.doesCertificateExist(certName), true);
+ auto cert = m_keyChain.getCertificate(certName);
+ transport->receive(cert->wireEncode());
+ }
+
+public:
+ Name m_testId1;
+ Name m_testId2;
+ Name m_testId3;
+ Name m_randomId;
+
+ Name m_rootCert;
+
+ KeyChain m_keyChain;
+
+ Name m_dsk1;
+ Name m_dsk2;
+ Name m_dsk3;
+
+ Name m_selfSignCert;
+
+ Name m_randomDsk;
+
+ name::Component m_version;
+
+ shared_ptr<ndn::tests::DummyClientFace> m_face;
+};
+
+
+BOOST_FIXTURE_TEST_CASE(Basic, Fixture)
+{
+ // validator must be created after root key is saved to the target
+ ndns::Validator validator(*m_face, TEST_CONFIG_PATH "/" "validator.conf");
+
+ Name dataName(m_testId3);
+ dataName.append("NDNS")
+ .append("rrLabel")
+ .append("rrType")
+ .appendVersion();
+ shared_ptr<Data> data = make_shared<Data>(dataName);
+ m_keyChain.sign(*data, m_dsk3);
+
+ bool hasValidated = false;
+ validator.validate(*data,
+ [&] (const shared_ptr<const Data>& data) {
+ hasValidated = true;
+ BOOST_CHECK(true);
+ },
+ [&] (const shared_ptr<const Data>& data, const std::string& str) {
+ hasValidated = true;
+ BOOST_CHECK(false);
+ });
+
+ m_face->processEvents(time::milliseconds(-1));
+
+ BOOST_CHECK_EQUAL(hasValidated, true);
+
+
+ dataName = m_testId2;
+ dataName.append("KEY")
+ .append("rrLabel")
+ .append("ID-CERT")
+ .appendVersion();
+ data = make_shared<Data>(dataName);
+ m_keyChain.sign(*data, m_dsk3); // key's owner's name is longer than data owner's
+
+ hasValidated = false;
+ validator.validate(*data,
+ [&] (const shared_ptr<const Data>& data) {
+ hasValidated = true;
+ BOOST_CHECK(false);
+ },
+ [&] (const shared_ptr<const Data>& data, const std::string& str) {
+ hasValidated = true;
+ BOOST_CHECK(true);
+ });
+
+ m_face->processEvents(time::milliseconds(-1));
+ // cannot pass verification due to key's owner's name is longer than data owner's
+ BOOST_CHECK_EQUAL(hasValidated, true);
+
+
+ dataName = m_testId3;
+ dataName.append("KEY")
+ .append("rrLabel")
+ .append("ID-CERT")
+ .appendVersion();
+ data = make_shared<Data>(dataName);
+ m_keyChain.sign(*data, m_selfSignCert);
+
+ hasValidated = false;
+ validator.validate(*data,
+ [&] (const shared_ptr<const Data>& data) {
+ hasValidated = true;
+ BOOST_CHECK(false);
+ },
+ [&] (const shared_ptr<const Data>& data, const std::string& str) {
+ hasValidated = true;
+ BOOST_CHECK(true);
+ });
+
+ m_face->processEvents(time::milliseconds(-1));
+ // cannot pass due to self-sign cert is used
+ BOOST_CHECK_EQUAL(hasValidated, true);
+
+ dataName = m_testId2;
+ dataName.append("KEY")
+ .append("rrLabel")
+ .append("ID-CERT")
+ .appendVersion();
+ data = make_shared<Data>(dataName);
+ m_keyChain.sign(*data, m_randomDsk);
+
+ hasValidated = false;
+ validator.validate(*data,
+ [&] (const shared_ptr<const Data>& data) {
+ hasValidated = true;
+ BOOST_CHECK(false);
+ },
+ [&] (const shared_ptr<const Data>& data, const std::string& str) {
+ hasValidated = true;
+ BOOST_CHECK(true);
+ });
+
+ m_face->processEvents(time::milliseconds(-1));
+ // cannot pass due to a totally mismatched key
+ BOOST_CHECK_EQUAL(hasValidated, true);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+} // namespace ndns
+} // namespace ndn
diff --git a/tests/wscript b/tests/wscript
index 0407962..fa36e78 100644
--- a/tests/wscript
+++ b/tests/wscript
@@ -5,12 +5,33 @@
top = '..'
def build(bld):
- if bld.env['WITH_TESTS']:
- unit_tests = bld.program(
- target='../unit-tests',
- features='cxx cxxprogram',
- source=bld.path.ant_glob(['**/*.cpp']),
- use='ndns-objects',
- install_path=None,
- defines="BUILDDIR=\"%s\"" % Context.out_dir,
- )
+ if not bld.env['WITH_TESTS']:
+ return
+ dst = bld.bldnode.make_node("conf-test/anchors")
+ dst.mkdir()
+
+ bld(features = "subst",
+ name = 'test-validator-conf',
+ source = '../validator.conf.sample.in',
+ target = '../conf-test/validator.conf',
+ use = 'validator-sample',
+ ANCHORPATH='\"anchors/root.cert\"',
+ RELATION='is-prefix-of',
+ )
+
+ bld(features = "subst",
+ name = 'test-logger-conf',
+ source = '../log4cxx.properties.sample.in',
+ target = '../conf-test/log4cxx.properties.sample',
+ is_copy = True,
+ use = 'log4cxx-sample',
+ )
+
+ unit_tests = bld.program(
+ target='../unit-tests',
+ features='cxx cxxprogram',
+ source=bld.path.ant_glob(['**/*.cpp']),
+ use='ndns-objects',
+ install_path=None,
+ defines='TEST_CONFIG_PATH=\"%s/conf-test\"' %(bld.bldnode)
+ )