Add build system, basic docs and crypto helpers module

Change-Id: I761cde5adac85596c5a3a53ec3e94a9a81fb5416
diff --git a/tests/boost-test.hpp b/tests/boost-test.hpp
new file mode 100644
index 0000000..419b01e
--- /dev/null
+++ b/tests/boost-test.hpp
@@ -0,0 +1,38 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2014-2020, 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, originally written as part of NFD (Named Data Networking Forwarding Daemon),
+ * is a part of ndncert, a certificate management system based on NDN.
+ *
+ * ndncert 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.
+ *
+ * ndncert 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 copies of the GNU General Public License along with
+ * ndncert, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndncert authors and contributors.
+ */
+
+#ifndef NDNCERT_TESTS_BOOST_TEST_HPP
+#define NDNCERT_TESTS_BOOST_TEST_HPP
+
+// suppress warnings from Boost.Test
+#pragma GCC system_header
+#pragma clang system_header
+
+#define BOOST_TEST_DYN_LINK
+#include <boost/test/unit_test.hpp>
+
+#endif // NDNCERT_TESTS_BOOST_TEST_HPP
diff --git a/tests/database-fixture.hpp b/tests/database-fixture.hpp
new file mode 100644
index 0000000..598e304
--- /dev/null
+++ b/tests/database-fixture.hpp
@@ -0,0 +1,63 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2020 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 NDNCERT_TESTS_DATABASE_FIXTURE_HPP
+#define NDNCERT_TESTS_DATABASE_FIXTURE_HPP
+
+#include "identity-management-fixture.hpp"
+#include "unit-test-time-fixture.hpp"
+#include <boost/filesystem.hpp>
+
+namespace ndn {
+namespace ndncert {
+namespace tests {
+
+class IdentityManagementTimeFixture : public UnitTestTimeFixture
+                                    , public IdentityManagementFixture
+{
+};
+
+class DatabaseFixture : public IdentityManagementTimeFixture
+{
+public:
+  DatabaseFixture()
+  {
+    boost::filesystem::path parentDir{TMP_TESTS_PATH};
+    dbDir = parentDir / "test-home" / ".ndncert";
+    if (!boost::filesystem::exists(dbDir)) {
+      boost::filesystem::create_directory(dbDir);
+    }
+  }
+
+  ~DatabaseFixture()
+  {
+    boost::filesystem::remove_all(dbDir);
+  }
+
+protected:
+  boost::filesystem::path dbDir;
+};
+
+} // namespace tests
+} // namespace ndncert
+} // namespace ndn
+
+#endif // NDNCERT_TESTS_DATABASE_FIXTURE_HPP
diff --git a/tests/global-configuration.cpp b/tests/global-configuration.cpp
new file mode 100644
index 0000000..61d0cbd
--- /dev/null
+++ b/tests/global-configuration.cpp
@@ -0,0 +1,69 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2017-2020, Regents of the University of California.
+ *
+ * This file is part of ndncert, a certificate management system based on NDN.
+ *
+ * ndncert 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.
+ *
+ * ndncert 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 copies of the GNU General Public License along with
+ * ndncert, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndncert authors and contributors.
+ */
+
+#include "boost-test.hpp"
+#include <boost/filesystem.hpp>
+#include <fstream>
+#include <stdlib.h>
+
+namespace ndn {
+namespace ndncert {
+namespace tests {
+
+class GlobalConfiguration
+{
+public:
+  GlobalConfiguration()
+  {
+    const char* envHome = ::getenv("HOME");
+    if (envHome)
+      m_home = envHome;
+
+    boost::filesystem::path dir{TMP_TESTS_PATH};
+    dir /= "test-home";
+    ::setenv("HOME", dir.c_str(), 1);
+
+    boost::filesystem::create_directories(dir);
+    std::ofstream clientConf((dir / ".ndn" / "client.conf").c_str());
+    clientConf << "pib=pib-sqlite3" << std::endl
+               << "tpm=tpm-file" << std::endl;
+  }
+
+  ~GlobalConfiguration()
+  {
+    if (!m_home.empty())
+      ::setenv("HOME", m_home.data(), 1);
+  }
+
+private:
+  std::string m_home;
+};
+
+#if BOOST_VERSION >= 106500
+BOOST_TEST_GLOBAL_CONFIGURATION(GlobalConfiguration);
+#elif BOOST_VERSION >= 105900
+BOOST_GLOBAL_FIXTURE(GlobalConfiguration);
+#else
+BOOST_GLOBAL_FIXTURE(GlobalConfiguration)
+#endif
+
+} // namespace tests
+} // namespace ndncert
+} // namespace ndn
\ No newline at end of file
diff --git a/tests/identity-management-fixture.cpp b/tests/identity-management-fixture.cpp
new file mode 100644
index 0000000..8ca0545
--- /dev/null
+++ b/tests/identity-management-fixture.cpp
@@ -0,0 +1,131 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2020 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.
+ */
+
+#include "identity-management-fixture.hpp"
+#include <ndn-cxx/security/additional-description.hpp>
+#include <ndn-cxx/util/io.hpp>
+#include <boost/filesystem.hpp>
+
+namespace ndn {
+namespace ndncert {
+namespace tests {
+
+namespace v2 = security::v2;
+
+IdentityManagementBaseFixture::~IdentityManagementBaseFixture()
+{
+  boost::system::error_code ec;
+  for (const auto& certFile : m_certFiles) {
+    boost::filesystem::remove(certFile, ec); // ignore error
+  }
+}
+
+bool
+IdentityManagementBaseFixture::saveCertToFile(const Data& obj, const std::string& filename)
+{
+  m_certFiles.insert(filename);
+  try {
+    io::save(obj, filename);
+    return true;
+  }
+  catch (const io::Error&) {
+    return false;
+  }
+}
+
+IdentityManagementFixture::IdentityManagementFixture()
+  : m_keyChain("pib-memory:", "tpm-memory:")
+{
+}
+
+security::Identity
+IdentityManagementFixture::addIdentity(const Name& identityName, const KeyParams& params)
+{
+  auto identity = m_keyChain.createIdentity(identityName, params);
+  m_identities.insert(identityName);
+  return identity;
+}
+
+bool
+IdentityManagementFixture::saveCertificate(const security::Identity& identity, const std::string& filename)
+{
+  try {
+    auto cert = identity.getDefaultKey().getDefaultCertificate();
+    return saveCertToFile(cert, filename);
+  }
+  catch (const security::Pib::Error&) {
+    return false;
+  }
+}
+
+security::Identity
+IdentityManagementFixture::addSubCertificate(const Name& subIdentityName,
+                                             const security::Identity& issuer, const KeyParams& params)
+{
+  auto subIdentity = addIdentity(subIdentityName, params);
+
+  v2::Certificate request = subIdentity.getDefaultKey().getDefaultCertificate();
+
+  request.setName(request.getKeyName().append("parent").appendVersion());
+
+  SignatureInfo info;
+  auto now = time::system_clock::now();
+  info.setValidityPeriod(security::ValidityPeriod(now, now + 7300_days));
+
+  v2::AdditionalDescription description;
+  description.set("type", "sub-certificate");
+  info.addCustomTlv(description.wireEncode());
+
+  m_keyChain.sign(request, signingByIdentity(issuer).setSignatureInfo(info));
+  m_keyChain.setDefaultCertificate(subIdentity.getDefaultKey(), request);
+
+  return subIdentity;
+}
+
+v2::Certificate
+IdentityManagementFixture::addCertificate(const security::Key& key, const std::string& issuer)
+{
+  Name certificateName = key.getName();
+  certificateName
+    .append(issuer)
+    .appendVersion();
+  v2::Certificate certificate;
+  certificate.setName(certificateName);
+
+  // set metainfo
+  certificate.setContentType(ndn::tlv::ContentType_Key);
+  certificate.setFreshnessPeriod(1_h);
+
+  // set content
+  certificate.setContent(key.getPublicKey().data(), key.getPublicKey().size());
+
+  // set signature-info
+  SignatureInfo info;
+  auto now = time::system_clock::now();
+  info.setValidityPeriod(security::ValidityPeriod(now, now + 10_days));
+
+  m_keyChain.sign(certificate, signingByKey(key).setSignatureInfo(info));
+  return certificate;
+}
+
+} // namespace tests
+} // namespace ndncert
+} // namespace ndn
diff --git a/tests/identity-management-fixture.hpp b/tests/identity-management-fixture.hpp
new file mode 100644
index 0000000..9b10dbf
--- /dev/null
+++ b/tests/identity-management-fixture.hpp
@@ -0,0 +1,101 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2020 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 NDN_TESTS_IDENTITY_MANAGEMENT_FIXTURE_HPP
+#define NDN_TESTS_IDENTITY_MANAGEMENT_FIXTURE_HPP
+
+#include <ndn-cxx/security/key-chain.hpp>
+#include <ndn-cxx/security/signing-helpers.hpp>
+#include <vector>
+
+namespace ndn {
+namespace ndncert {
+namespace tests {
+
+class IdentityManagementBaseFixture
+{
+public:
+  ~IdentityManagementBaseFixture();
+
+  bool
+  saveCertToFile(const Data& obj, const std::string& filename);
+
+protected:
+  std::set<Name> m_identities;
+  std::set<std::string> m_certFiles;
+};
+
+/**
+ * @brief A test suite level fixture to help with identity management
+ *
+ * Test cases in the suite can use this fixture to create identities.  Identities,
+ * certificates, and saved certificates are automatically removed during test teardown.
+ */
+class IdentityManagementFixture : public IdentityManagementBaseFixture
+{
+public:
+  IdentityManagementFixture();
+
+  /**
+   * @brief Add identity @p identityName
+   * @return name of the created self-signed certificate
+   */
+  security::Identity
+  addIdentity(const Name& identityName,
+              const KeyParams& params = security::KeyChain::getDefaultKeyParams());
+
+  /**
+   *  @brief Save identity certificate to a file
+   *  @param identity identity
+   *  @param filename file name, should be writable
+   *  @return whether successful
+   */
+  bool
+  saveCertificate(const security::Identity& identity, const std::string& filename);
+
+  /**
+   * @brief Issue a certificate for \p subIdentityName signed by \p issuer
+   *
+   *  If identity does not exist, it is created.
+   *  A new key is generated as the default key for identity.
+   *  A default certificate for the key is signed by the issuer using its default certificate.
+   *
+   *  @return the sub identity
+   */
+  security::Identity
+  addSubCertificate(const Name& subIdentityName, const security::Identity& issuer,
+                    const KeyParams& params = security::KeyChain::getDefaultKeyParams());
+
+  /**
+   * @brief Add a self-signed certificate to @p key with issuer ID @p issuer
+   */
+  security::Certificate
+  addCertificate(const security::Key& key, const std::string& issuer);
+
+protected:
+  KeyChain m_keyChain;
+};
+
+} // namespace tests
+} // namespace ndncert
+} // namespace ndn
+
+#endif // NDN_TESTS_IDENTITY_MANAGEMENT_FIXTURE_HPP
diff --git a/tests/main.cpp b/tests/main.cpp
new file mode 100644
index 0000000..779d74a
--- /dev/null
+++ b/tests/main.cpp
@@ -0,0 +1,29 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2014-2020, 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, originally written as part of NFD (Named Data Networking Forwarding Daemon),
+ * is a part of ndncert, a certificate management system based on NDN.
+ *
+ * ndncert 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.
+ *
+ * ndncert 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 copies of the GNU General Public License along with
+ * ndncert, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndncert authors and contributors.
+ */
+
+#define BOOST_TEST_MODULE ndncert
+#include "boost-test.hpp"
\ No newline at end of file
diff --git a/tests/test-common.hpp b/tests/test-common.hpp
new file mode 100644
index 0000000..68cccc2
--- /dev/null
+++ b/tests/test-common.hpp
@@ -0,0 +1,38 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2020 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 NDNCERT_TESTS_TEST_COMMON_HPP
+#define NDNCERT_TESTS_TEST_COMMON_HPP
+
+#include "boost-test.hpp"
+#include "database-fixture.hpp"
+#include "identity-management-fixture.hpp"
+#include "unit-test-time-fixture.hpp"
+#include <ndn-cxx/metadata-object.hpp>
+#include <ndn-cxx/security/signing-helpers.hpp>
+#include <ndn-cxx/security/transform/base64-encode.hpp>
+#include <ndn-cxx/security/transform/buffer-source.hpp>
+#include <ndn-cxx/security/transform/public-key.hpp>
+#include <ndn-cxx/security/transform/stream-sink.hpp>
+#include <ndn-cxx/security/verification-helpers.hpp>
+#include <ndn-cxx/util/dummy-client-face.hpp>
+
+#endif // NDNCERT_TESTS_TEST_COMMON_HPP
diff --git a/tests/unit-test-time-fixture.hpp b/tests/unit-test-time-fixture.hpp
new file mode 100644
index 0000000..cebce38
--- /dev/null
+++ b/tests/unit-test-time-fixture.hpp
@@ -0,0 +1,107 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2020 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 NDN_TESTS_UNIT_UNIT_TEST_TIME_FIXTURE_HPP
+#define NDN_TESTS_UNIT_UNIT_TEST_TIME_FIXTURE_HPP
+
+#include <ndn-cxx/util/time-unit-test-clock.hpp>
+#include <boost/asio/io_service.hpp>
+
+namespace ndn {
+namespace ndncert {
+namespace tests {
+
+/** \brief a test fixture that overrides steady clock and system clock
+ */
+class UnitTestTimeFixture
+{
+public:
+  UnitTestTimeFixture()
+    : steadyClock(make_shared<time::UnitTestSteadyClock>())
+    , systemClock(make_shared<time::UnitTestSystemClock>())
+  {
+    time::setCustomClocks(steadyClock, systemClock);
+  }
+
+  ~UnitTestTimeFixture()
+  {
+    time::setCustomClocks(nullptr, nullptr);
+  }
+
+  /** \brief advance steady and system clocks
+   *
+   *  Clocks are advanced in increments of \p tick for \p nTicks ticks.
+   *  After each tick, io_service is polled to process pending I/O events.
+   *
+   *  Exceptions thrown during I/O events are propagated to the caller.
+   *  Clock advancing would stop in case of an exception.
+   */
+  void
+  advanceClocks(const time::nanoseconds& tick, size_t nTicks = 1)
+  {
+    this->advanceClocks(tick, tick * nTicks);
+  }
+
+  /** \brief advance steady and system clocks
+   *
+   *  Clocks are advanced in increments of \p tick for \p total time.
+   *  The last increment might be shorter than \p tick.
+   *  After each tick, io_service is polled to process pending I/O events.
+   *
+   *  Exceptions thrown during I/O events are propagated to the caller.
+   *  Clock advancing would stop in case of an exception.
+   */
+  void
+  advanceClocks(const time::nanoseconds& tick, const time::nanoseconds& total)
+  {
+    BOOST_ASSERT(tick > time::nanoseconds::zero());
+    BOOST_ASSERT(total >= time::nanoseconds::zero());
+
+    time::nanoseconds remaining = total;
+    while (remaining > time::nanoseconds::zero()) {
+      if (remaining >= tick) {
+        steadyClock->advance(tick);
+        systemClock->advance(tick);
+        remaining -= tick;
+      }
+      else {
+        steadyClock->advance(remaining);
+        systemClock->advance(remaining);
+        remaining = time::nanoseconds::zero();
+      }
+
+      if (io.stopped())
+        io.reset();
+      io.poll();
+    }
+  }
+
+public:
+  shared_ptr<time::UnitTestSteadyClock> steadyClock;
+  shared_ptr<time::UnitTestSystemClock> systemClock;
+  boost::asio::io_service io;
+};
+
+} // namespace tests
+} // namespace ndncert
+} // namespace ndn
+
+#endif // NDN_TESTS_UNIT_UNIT_TEST_TIME_FIXTURE_HPP
diff --git a/tests/unit-tests/crypto-helpers.t.cpp b/tests/unit-tests/crypto-helpers.t.cpp
new file mode 100644
index 0000000..5f29a03
--- /dev/null
+++ b/tests/unit-tests/crypto-helpers.t.cpp
@@ -0,0 +1,334 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2017-2020, Regents of the University of California.
+ *
+ * This file is part of ndncert, a certificate management system based on NDN.
+ *
+ * ndncert 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.
+ *
+ * ndncert 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 copies of the GNU General Public License along with
+ * ndncert, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndncert authors and contributors.
+ */
+
+#include "detail/crypto-helpers.hpp"
+#include "test-common.hpp"
+
+namespace ndn {
+namespace ndncert {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(TestCryptoHelpers)
+
+BOOST_AUTO_TEST_CASE(EcdhWithRawKey)
+{
+  ECDHState aliceState;
+  auto alicePub = aliceState.getSelfPubKey();
+  BOOST_CHECK(!alicePub.empty());
+
+  ECDHState bobState;
+  auto bobPub = bobState.getSelfPubKey();
+  BOOST_CHECK(!bobPub.empty());
+
+  auto aliceResult = aliceState.deriveSecret(bobPub);
+  BOOST_CHECK(!aliceResult.empty());
+  auto bobResult = bobState.deriveSecret(alicePub);
+  BOOST_CHECK(!bobResult.empty());
+  BOOST_CHECK_EQUAL_COLLECTIONS(aliceResult.begin(), aliceResult.end(), bobResult.begin(), bobResult.end());
+}
+
+BOOST_AUTO_TEST_CASE(EcdhWithRawKeyWrongInput)
+{
+  ECDHState aliceState;
+  auto alicePub = aliceState.getSelfPubKey();
+  BOOST_CHECK(!alicePub.empty());
+  std::vector<uint8_t> fakePub(10, 0x0b);
+  BOOST_CHECK_THROW(aliceState.deriveSecret(fakePub), std::runtime_error);
+}
+
+BOOST_AUTO_TEST_CASE(HmacSha256)
+{
+  const uint8_t input[] = {0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+                           0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+                           0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b};
+  const uint8_t key[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
+                          0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c};
+  const uint8_t expected[] = {0x07, 0x77, 0x09, 0x36, 0x2c, 0x2e, 0x32, 0xdf,
+                              0x0d, 0xdc, 0x3f, 0x0d, 0xc4, 0x7b, 0xba, 0x63,
+                              0x90, 0xb6, 0xc7, 0x3b, 0xb5, 0x0f, 0x9c, 0x31,
+                              0x22, 0xec, 0x84, 0x4a, 0xd7, 0xc2, 0xb3, 0xe5};
+  uint8_t result[32];
+  hmacSha256(input, sizeof(input), key, sizeof(key), result);
+  BOOST_CHECK_EQUAL_COLLECTIONS(result, result + sizeof(result), expected,
+                                expected + sizeof(expected));
+}
+
+BOOST_AUTO_TEST_CASE(Hkdf1)
+{
+  // RFC5869 appendix A.1
+  const uint8_t ikm[] = {0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+                         0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+                         0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b};
+  const uint8_t salt[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
+                          0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c};
+  const uint8_t info[] = {0xf0, 0xf1, 0xf2, 0xf3, 0xf4,
+                          0xf5, 0xf6, 0xf7, 0xf8, 0xf9};
+  const uint8_t expected[] = {
+      0x3c, 0xb2, 0x5f, 0x25, 0xfa, 0xac, 0xd5, 0x7a, 0x90, 0x43, 0x4f,
+      0x64, 0xd0, 0x36, 0x2f, 0x2a, 0x2d, 0x2d, 0x0a, 0x90, 0xcf, 0x1a,
+      0x5a, 0x4c, 0x5d, 0xb0, 0x2d, 0x56, 0xec, 0xc4, 0xc5, 0xbf, 0x34,
+      0x00, 0x72, 0x08, 0xd5, 0xb8, 0x87, 0x18, 0x58, 0x65};
+  uint8_t result[42];
+  auto resultLen = hkdf(ikm, sizeof(ikm), salt, sizeof(salt), result,
+                        sizeof(result), info, sizeof(info));
+
+  BOOST_CHECK_EQUAL(resultLen, sizeof(result));
+  BOOST_CHECK_EQUAL_COLLECTIONS(result, result + sizeof(result), expected,
+                                expected + sizeof(expected));
+}
+
+BOOST_AUTO_TEST_CASE(Hkdf2)
+{
+  // RFC5869 appendix A.2
+  const uint8_t ikm[] = {
+      0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
+      0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+      0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23,
+      0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
+      0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
+      0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
+      0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f};
+  const uint8_t salt[] = {
+      0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b,
+      0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
+      0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83,
+      0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
+      0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b,
+      0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
+      0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf};
+  const uint8_t info[] = {
+      0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb,
+      0xbc, 0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
+      0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3,
+      0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
+      0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb,
+      0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
+      0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff};
+  const uint8_t expected[] = {
+      0xb1, 0x1e, 0x39, 0x8d, 0xc8, 0x03, 0x27, 0xa1, 0xc8, 0xe7, 0xf7, 0x8c,
+      0x59, 0x6a, 0x49, 0x34, 0x4f, 0x01, 0x2e, 0xda, 0x2d, 0x4e, 0xfa, 0xd8,
+      0xa0, 0x50, 0xcc, 0x4c, 0x19, 0xaf, 0xa9, 0x7c, 0x59, 0x04, 0x5a, 0x99,
+      0xca, 0xc7, 0x82, 0x72, 0x71, 0xcb, 0x41, 0xc6, 0x5e, 0x59, 0x0e, 0x09,
+      0xda, 0x32, 0x75, 0x60, 0x0c, 0x2f, 0x09, 0xb8, 0x36, 0x77, 0x93, 0xa9,
+      0xac, 0xa3, 0xdb, 0x71, 0xcc, 0x30, 0xc5, 0x81, 0x79, 0xec, 0x3e, 0x87,
+      0xc1, 0x4c, 0x01, 0xd5, 0xc1, 0xf3, 0x43, 0x4f, 0x1d, 0x87};
+
+  uint8_t result[82];
+  auto resultLen = hkdf(ikm, sizeof(ikm), salt, sizeof(salt), result,
+                        sizeof(result), info, sizeof(info));
+
+  BOOST_CHECK_EQUAL(resultLen, sizeof(result));
+  BOOST_CHECK_EQUAL_COLLECTIONS(result, result + sizeof(result), expected,
+                                expected + sizeof(expected));
+}
+
+BOOST_AUTO_TEST_CASE(Hkdf3)
+{
+  // RFC5869 appendix A.3
+  const uint8_t ikm[] = {0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+                         0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+                         0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b};
+  const uint8_t expected[] = {
+      0x8d, 0xa4, 0xe7, 0x75, 0xa5, 0x63, 0xc1, 0x8f, 0x71, 0x5f, 0x80,
+      0x2a, 0x06, 0x3c, 0x5a, 0x31, 0xb8, 0xa1, 0x1f, 0x5c, 0x5e, 0xe1,
+      0x87, 0x9e, 0xc3, 0x45, 0x4e, 0x5f, 0x3c, 0x73, 0x8d, 0x2d, 0x9d,
+      0x20, 0x13, 0x95, 0xfa, 0xa4, 0xb6, 0x1a, 0x96, 0xc8};
+  uint8_t result[42];
+
+  auto resultLen = hkdf(ikm, sizeof(ikm), nullptr, 0, result,
+                        sizeof(result), nullptr, 0);
+
+  BOOST_CHECK_EQUAL(resultLen, sizeof(result));
+  BOOST_CHECK_EQUAL_COLLECTIONS(result, result + sizeof(result), expected,
+                                expected + sizeof(expected));
+}
+
+BOOST_AUTO_TEST_CASE(AesGcm1)
+{
+  // Test case from NIST Cryptographic Algorithm Validation Program
+  // https://csrc.nist.gov/Projects/cryptographic-algorithm-validation-program/CAVP-TESTING-BLOCK-CIPHER-MODES
+  // Count = 0
+  // Key = cf063a34d4a9a76c2c86787d3f96db71
+  // IV = 113b9785971864c83b01c787
+  // CT =
+  // AAD =
+  // Tag = 72ac8493e3a5228b5d130a69d2510e42
+  // PT =
+  const uint8_t key[] = {0xcf, 0x06, 0x3a, 0x34, 0xd4, 0xa9, 0xa7, 0x6c,
+                         0x2c, 0x86, 0x78, 0x7d, 0x3f, 0x96, 0xdb, 0x71};
+  const uint8_t iv[] = {0x11, 0x3b, 0x97, 0x85, 0x97, 0x18,
+                        0x64, 0xc8, 0x3b, 0x01, 0xc7, 0x87};
+  const uint8_t expected_tag[] = {0x72, 0xac, 0x84, 0x93, 0xe3, 0xa5,
+                                  0x22, 0x8b, 0x5d, 0x13, 0x0a, 0x69,
+                                  0xd2, 0x51, 0x0e, 0x42};
+
+  uint8_t ciphertext[256] = {0};
+  uint8_t tag[16] = {0};
+  const uint8_t empty_buffer[1] = {0};
+  int size = aesGcm128Encrypt(empty_buffer, 0, empty_buffer, 0, key, iv, ciphertext, tag);
+  BOOST_CHECK(size == 0);
+  BOOST_CHECK_EQUAL_COLLECTIONS(tag, tag + 16, expected_tag, expected_tag + sizeof(expected_tag));
+
+  uint8_t decrypted[256] = {0};
+  size = aesGcm128Decrypt(ciphertext, size, empty_buffer, 0, tag, key, iv, decrypted);
+  BOOST_CHECK(size == 0);
+}
+
+BOOST_AUTO_TEST_CASE(AesGcm2)
+{
+  // Test case from NIST Cryptographic Algorithm Validation Program
+  // https://csrc.nist.gov/Projects/cryptographic-algorithm-validation-program/CAVP-TESTING-BLOCK-CIPHER-MODES
+  // Count = 1
+  // Key = 2370e320d4344208e0ff5683f243b213
+  // IV = 04dbb82f044d30831c441228
+  // CT =
+  // AAD = d43a8e5089eea0d026c03a85178b27da
+  // Tag = 2a049c049d25aa95969b451d93c31c6e
+  // PT =
+  const uint8_t key[] = {0x23, 0x70, 0xe3, 0x20, 0xd4, 0x34, 0x42, 0x08,
+                         0xe0, 0xff, 0x56, 0x83, 0xf2, 0x43, 0xb2, 0x13};
+  const uint8_t iv[] = {0x04, 0xdb, 0xb8, 0x2f, 0x04, 0x4d,
+                        0x30, 0x83, 0x1c, 0x44, 0x12, 0x28};
+  const uint8_t aad[] = {0xd4, 0x3a, 0x8e, 0x50, 0x89, 0xee, 0xa0, 0xd0,
+                         0x26, 0xc0, 0x3a, 0x85, 0x17, 0x8b, 0x27, 0xda};
+  const uint8_t expected_tag[] = {0x2a, 0x04, 0x9c, 0x04, 0x9d, 0x25,
+                                  0xaa, 0x95, 0x96, 0x9b, 0x45, 0x1d,
+                                  0x93, 0xc3, 0x1c, 0x6e};
+
+  uint8_t ciphertext[256] = {0};
+  uint8_t tag[16] = {0};
+  const uint8_t empty_buffer[1] = {0};
+  int size = aesGcm128Encrypt(empty_buffer, 0, aad, sizeof(aad), key, iv, ciphertext, tag);
+  BOOST_CHECK(size == 0);
+  BOOST_CHECK_EQUAL_COLLECTIONS(tag, tag + 16, expected_tag, expected_tag + sizeof(expected_tag));
+
+  uint8_t decrypted[256] = {0};
+  size = aesGcm128Decrypt(ciphertext, size, aad, sizeof(aad), tag, key, iv, decrypted);
+  BOOST_CHECK(size == 0);
+}
+
+BOOST_AUTO_TEST_CASE(AesGcm3)
+{
+  // Test case from NIST Cryptographic Algorithm Validation Program
+  // https://csrc.nist.gov/Projects/cryptographic-algorithm-validation-program/CAVP-TESTING-BLOCK-CIPHER-MODES
+  // Count = 0
+  // Key = bc22f3f05cc40db9311e4192966fee92
+  // IV = 134988e662343c06d3ab83db
+  // CT = 4c0168ab95d3a10ef25e5924108389365c67d97778995892d9fd46897384af61fc559212b3267e90fe4df7bfd1fbed46f4b9ee
+  // AAD = 10087e6ed81049b509c31d12fee88c64
+  // Tag = 771357958a316f166bd0dacc98ea801a
+  // PT = 337c1bc992386cf0f957617fe4d5ec1218ae1cc40369305518eb177e9b15c1646b142ff71237efaa58790080cd82e8848b295c
+  const uint8_t key[] = {0xbc, 0x22, 0xf3, 0xf0, 0x5c, 0xc4, 0x0d, 0xb9,
+                         0x31, 0x1e, 0x41, 0x92, 0x96, 0x6f, 0xee, 0x92};
+  const uint8_t iv[] = {0x13, 0x49, 0x88, 0xe6, 0x62, 0x34,
+                        0x3c, 0x06, 0xd3, 0xab, 0x83, 0xdb};
+  const uint8_t aad[] = {0x10, 0x08, 0x7e, 0x6e, 0xd8, 0x10, 0x49, 0xb5,
+                         0x09, 0xc3, 0x1d, 0x12, 0xfe, 0xe8, 0x8c, 0x64};
+  const uint8_t expected_ciphertext[] = {
+      0x4c, 0x01, 0x68, 0xab, 0x95, 0xd3, 0xa1, 0x0e, 0xf2, 0x5e, 0x59,
+      0x24, 0x10, 0x83, 0x89, 0x36, 0x5c, 0x67, 0xd9, 0x77, 0x78, 0x99,
+      0x58, 0x92, 0xd9, 0xfd, 0x46, 0x89, 0x73, 0x84, 0xaf, 0x61, 0xfc,
+      0x55, 0x92, 0x12, 0xb3, 0x26, 0x7e, 0x90, 0xfe, 0x4d, 0xf7, 0xbf,
+      0xd1, 0xfb, 0xed, 0x46, 0xf4, 0xb9, 0xee};
+  const uint8_t expected_tag[] = {0x77, 0x13, 0x57, 0x95, 0x8a, 0x31,
+                                  0x6f, 0x16, 0x6b, 0xd0, 0xda, 0xcc,
+                                  0x98, 0xea, 0x80, 0x1a};
+  const uint8_t plaintext[] = {
+      0x33, 0x7c, 0x1b, 0xc9, 0x92, 0x38, 0x6c, 0xf0, 0xf9, 0x57, 0x61,
+      0x7f, 0xe4, 0xd5, 0xec, 0x12, 0x18, 0xae, 0x1c, 0xc4, 0x03, 0x69,
+      0x30, 0x55, 0x18, 0xeb, 0x17, 0x7e, 0x9b, 0x15, 0xc1, 0x64, 0x6b,
+      0x14, 0x2f, 0xf7, 0x12, 0x37, 0xef, 0xaa, 0x58, 0x79, 0x00, 0x80,
+      0xcd, 0x82, 0xe8, 0x84, 0x8b, 0x29, 0x5c};
+
+  uint8_t ciphertext[256] = {0};
+  uint8_t tag[16] = {0};
+  int size = aesGcm128Encrypt(plaintext, sizeof(plaintext), aad, sizeof(aad), key, iv, ciphertext, tag);
+  BOOST_CHECK_EQUAL_COLLECTIONS(ciphertext, ciphertext + size,
+                                expected_ciphertext, expected_ciphertext + sizeof(expected_ciphertext));
+  BOOST_CHECK_EQUAL_COLLECTIONS(tag, tag + 16, expected_tag, expected_tag + sizeof(expected_tag));
+
+  uint8_t decrypted[256] = {0};
+  size = aesGcm128Decrypt(ciphertext, size, aad, sizeof(aad), tag, key, iv, decrypted);
+  BOOST_CHECK_EQUAL_COLLECTIONS(decrypted, decrypted + size,
+                                plaintext, plaintext + sizeof(plaintext));
+}
+
+BOOST_AUTO_TEST_CASE(AesIV)
+{
+  const uint8_t key[] = {0xbc, 0x22, 0xf3, 0xf0, 0x5c, 0xc4, 0x0d, 0xb9,
+                         0x31, 0x1e, 0x41, 0x92, 0x96, 0x6f, 0xee, 0x92};
+  const std::string plaintext = "alongstringalongstringalongstringalongstringalongstringalongstringalongstringalongstring";
+  const std::string associatedData = "test";
+  uint32_t counter = 0;
+  auto block = encodeBlockWithAesGcm128(ndn::tlv::Content, key, (uint8_t*)plaintext.c_str(), plaintext.size(),
+                                        (uint8_t*)associatedData.c_str(), associatedData.size(), counter);
+  block.parse();
+  auto ivBlock = block.get(tlv::InitializationVector);
+  Buffer ivBuf(ivBlock.value(), ivBlock.value_size());
+  BOOST_CHECK_EQUAL(ivBuf.size(), 12);
+  BOOST_CHECK(ivBuf[0] >= 128);
+  BOOST_CHECK_EQUAL(ivBuf[8] + ivBuf[9] + ivBuf[10] + ivBuf[11], 0);
+  BOOST_CHECK_EQUAL(counter, 6);
+  counter = 300;
+  block = encodeBlockWithAesGcm128(ndn::tlv::ApplicationParameters, key, (uint8_t*)plaintext.c_str(), plaintext.size(),
+                                   (uint8_t*)associatedData.c_str(), associatedData.size(), counter);
+  block.parse();
+  ivBlock = block.get(tlv::InitializationVector);
+  Buffer ivBuf2(ivBlock.value(), ivBlock.value_size());
+  BOOST_CHECK_EQUAL(ivBuf2.size(), 12);
+  BOOST_CHECK(ivBuf2[0] < 128);
+  BOOST_CHECK_EQUAL(ivBuf2[8] + ivBuf2[9], 0);
+  BOOST_CHECK_EQUAL(ivBuf2[10], 1);
+  BOOST_CHECK_EQUAL(ivBuf2[11], 44);
+  BOOST_CHECK_EQUAL(counter, 306);
+}
+
+BOOST_AUTO_TEST_CASE(BlockEncodingDecoding)
+{
+  const uint8_t key[] = {0xbc, 0x22, 0xf3, 0xf0, 0x5c, 0xc4, 0x0d, 0xb9,
+                         0x31, 0x1e, 0x41, 0x92, 0x96, 0x6f, 0xee, 0x92};
+  const std::string plaintext = "alongstringalongstringalongstringalongstringalongstringalongstringalongstringalongstring";
+  const std::string plaintext2 = "shortstring";
+  const std::string associatedData = "right";
+  const std::string wrongAssociatedData = "wrong";
+  uint32_t counter = 0;
+  // long string encryption
+  auto block = encodeBlockWithAesGcm128(ndn::tlv::Content, key, (uint8_t*)plaintext.c_str(), plaintext.size(),
+                                        (uint8_t*)associatedData.c_str(), associatedData.size(), counter);
+  auto decoded = decodeBlockWithAesGcm128(block, key, (uint8_t*)associatedData.c_str(), associatedData.size());
+  BOOST_CHECK_EQUAL(plaintext, std::string(decoded.get<char>(), decoded.size()));
+
+  // short string encryption
+  block = encodeBlockWithAesGcm128(ndn::tlv::Content, key, (uint8_t*)plaintext2.c_str(), plaintext2.size(),
+                                   (uint8_t*)associatedData.c_str(), associatedData.size(), counter);
+  decoded = decodeBlockWithAesGcm128(block, key, (uint8_t*)associatedData.c_str(), associatedData.size());
+  BOOST_CHECK_EQUAL(plaintext2, std::string(decoded.get<char>(), decoded.size()));
+
+  // use wrong associated data
+  BOOST_CHECK_THROW(decodeBlockWithAesGcm128(block, key,
+                                             (uint8_t*)wrongAssociatedData.c_str(),
+                                             wrongAssociatedData.size()), std::runtime_error);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+} // namespace ndncert
+} // namespace ndn
diff --git a/tests/wscript b/tests/wscript
new file mode 100644
index 0000000..87151bd
--- /dev/null
+++ b/tests/wscript
@@ -0,0 +1,17 @@
+# -*- Mode: python; py-indent-offset: 4; indent-tabs-mode: nil; coding: utf-8; -*-
+top = '..'
+
+def build(bld):
+    if not bld.env.WITH_TESTS:
+        return
+
+    tmp_path = 'TMP_TESTS_PATH="%s"' % bld.bldnode.make_node('tmp-tests')
+
+    bld.program(
+        target='../unit-tests',
+        name='unit-tests',
+        source=bld.path.ant_glob(['*.cpp', 'unit-tests/**/*.cpp']),
+        use='ndn-cert BOOST',
+        includes='.',
+        defines=[tmp_path],
+        install_path=None)