Update the NDNCERT library to version NDNCERT v2

spec:[https://github.com/named-data/ndncert/wiki/NDNCERT-Protocol-new]

Change-Id: Ia480a8e70c4b38ca170dfe2fcf50d1265ab65f46
diff --git a/.waf-tools/openssl.py b/.waf-tools/openssl.py
new file mode 100644
index 0000000..de0f9dc
--- /dev/null
+++ b/.waf-tools/openssl.py
@@ -0,0 +1,105 @@
+#! /usr/bin/env python
+# encoding: utf-8
+# Yingdi Yu (UCLA) 2016
+
+'''
+
+When using this tool, the wscript will look like:
+
+    def options(opt):
+        opt.load('openssl')
+
+    def configure(conf):
+        conf.load('compiler_cxx openssl')
+        conf.check_openssl()
+
+    def build(bld):
+        bld(source='main.cpp', target='app', use='OPENSSL')
+
+'''
+
+import re
+from waflib import Utils, Logs
+from waflib.Configure import conf
+
+OPENSSL_VERSION_FILE = 'opensslv.h'
+OPENSSL_DIR_OSX = ['/usr/local', '/opt/local', '/usr/local/opt/openssl']
+OPENSSL_DIR = ['/usr', '/usr/local', '/opt/local', '/sw']
+
+def options(opt):
+    opt.add_option('--with-openssl', type='string', default=None, dest='openssl_dir',
+                   help='''Path to where OpenSSL is installed, e.g., /usr/local''')
+
+
+@conf
+def __openssl_get_version_file(self, dir):
+    try:
+        return self.root.find_dir(dir).find_node('%s/%s' % ('include/openssl',
+                                                            OPENSSL_VERSION_FILE))
+    except:
+        return None
+
+@conf
+def __openssl_find_root_and_version_file(self, *k, **kw):
+    root = k and k[0] or kw.get('path', self.options.openssl_dir)
+
+    file = self.__openssl_get_version_file(root)
+    if root and file:
+        return (root, file)
+
+    openssl_dir = [];
+    if Utils.unversioned_sys_platform() == "darwin":
+        openssl_dir = OPENSSL_DIR_OSX
+    else:
+        openssl_dir = OPENSSL_DIR
+
+    if not root:
+        for dir in openssl_dir:
+            file = self.__openssl_get_version_file(dir)
+            if file:
+                return (dir, file)
+
+    if root:
+        self.fatal('OpenSSL not found in %s' % root)
+    else:
+        self.fatal('OpenSSL not found, please provide a --with-openssl=PATH argument (see help)')
+
+@conf
+def check_openssl(self, *k, **kw):
+    atleast_version = kw.get('atleast_version', 0)
+    var = kw.get('uselib_store', 'OPENSSL')
+
+    self.start_msg('Checking for OpenSSL lib')
+    (root, file) = self.__openssl_find_root_and_version_file(*k, **kw)
+
+    try:
+        txt = file.read()
+        re_version = re.compile('^#\\s*define\\s+OPENSSL_VERSION_NUMBER\\s+(.*)L', re.M)
+        version_number = re_version.search(txt)
+
+        re_version_text = re.compile('^#\\s*define\\s+OPENSSL_VERSION_TEXT\\s+(.*)', re.M)
+        version_text = re_version_text.search(txt)
+
+        if version_number and version_text:
+            version = version_number.group(1)
+            self.end_msg(version_text.group(1))
+        else:
+            self.fatal('OpenSSL version file is present, but is not recognizable')
+    except:
+        self.fatal('OpenSSL version file is not found or is not usable')
+
+    if int(version, 16) < atleast_version:
+        self.fatal("The version of OpenSSL is too old\n" +
+                   "Please upgrade your distribution or install newer version of OpenSSL library")
+
+    kwargs = {}
+    if root:
+        kwargs['includes'] = "%s/include" % root
+        kwargs['libpath'] = "%s/lib" % root
+
+    libcrypto = self.check_cxx(lib=['ssl', 'crypto'],
+                               msg='Checking if OpenSSL library works',
+                               define_name='HAVE_%s' % var,
+                               uselib_store=var,
+                               mandatory=True,
+                               **kwargs)
diff --git a/AUTHORS.md b/AUTHORS.md
index 5c29bdf..2a0da5c 100644
--- a/AUTHORS.md
+++ b/AUTHORS.md
@@ -3,7 +3,7 @@
 
 ## The primary authors are (and/or have been):
 
-* Zhiyi Zhang            <http://irl.cs.ucla.edu/~zhiyi/>
+* Zhiyi Zhang            <https://zhiyi-zhang.com>
 * Alexander Afanasyev    <http://lasr.cs.ucla.edu/afanasyev/index.html>
 * Lixia Zhang            <http://web.cs.ucla.edu/~lixia/>
 
diff --git a/src/ca-config.cpp b/src/ca-config.cpp
index 06b5c68..b8b0d59 100644
--- a/src/ca-config.cpp
+++ b/src/ca-config.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2017, Regents of the University of California.
+ * Copyright (c) 2017-2019, Regents of the University of California.
  *
  * This file is part of ndncert, a certificate management system based on NDN.
  *
@@ -40,41 +40,24 @@
   if (configJson.begin() == configJson.end()) {
     BOOST_THROW_EXCEPTION(Error("Error processing configuration file: " + fileName + " no data"));
   }
-
   parse(configJson);
 }
 
 void
 CaConfig::parse(const JsonSection& configJson)
 {
-  m_caItems.clear();
-  auto caList = configJson.get_child("ca-list");
-  auto it = caList.begin();
-  for (; it != caList.end(); it++) {
-    CaItem item;
-
     // essential info
-    item.m_caName = Name(it->second.get<std::string>("ca-prefix"));
-    item.m_freshnessPeriod = time::seconds(it->second.get("issuing-freshness", 720));
-    item.m_validityPeriod = time::days(it->second.get("validity-period", 360));
+    m_caName = Name(configJson.get<std::string>("ca-prefix"));
+    m_freshnessPeriod = time::seconds(configJson.get("issuing-freshness", 720));
+    m_validityPeriod = time::days(configJson.get("max-validity-period", 360));
 
     // optional info
-    item.m_probe = it->second.get("probe", "");
-    item.m_caInfo = it->second.get("ca-info", "");
-    item.m_targetedList = it->second.get("targeted-list", "");
+    m_probe = configJson.get("probe", "");
+    m_caInfo = configJson.get("ca-info", "");
 
     // optional supported challenges
-    auto challengeList = it->second.get_child("supported-challenges");
-    item.m_supportedChallenges = parseChallengeList(challengeList);
-
-    // related cas
-    auto relatedCaList = it->second.get_child_optional("related-ca-list");
-    if (relatedCaList) {
-      item.m_relatedCaList = parseRelatedCaList(*relatedCaList);
-    }
-
-    m_caItems.push_back(item);
-  }
+    auto challengeList = configJson.get_child("supported-challenges");
+    m_supportedChallenges = parseChallengeList(challengeList);
 }
 
 std::list<std::string>
@@ -88,17 +71,5 @@
   return result;
 }
 
-std::list<Name>
-CaConfig::parseRelatedCaList(const JsonSection& section)
-{
-  std::list<Name> result;
-  auto it = section.begin();
-  for (; it != section.end(); it++) {
-    Name item(it->second.get<std::string>("ca-prefix"));
-    result.push_back(item);
-  }
-  return result;
-}
-
 } // namespace ndncert
 } // namespace ndn
diff --git a/src/ca-config.hpp b/src/ca-config.hpp
index a0e66ae..83bd821 100644
--- a/src/ca-config.hpp
+++ b/src/ca-config.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2017-2018, Regents of the University of California.
+ * Copyright (c) 2017-2019, Regents of the University of California.
  *
  * This file is part of ndncert, a certificate management system based on NDN.
  *
@@ -36,16 +36,6 @@
 using ProbeHandler = function<std::string/*identity name*/ (const std::string&/*requester input*/)>;
 
 /**
- * @brief The function should recommend a CA plus an identity name from the given list
- *        based on LIST additional info
- *
- * The function should throw exceptions when there is an unexpected input.
- */
-using RecommendCaHandler = function<std::tuple<Name/*CA name*/, std::string/*identity*/>
-                                    (const std::string&/*requester input*/,
-                                     const std::list<Name>&/*related CA list*/)>;
-
-/**
  * @brief The function would be invoked whenever the certificate request status gets update
  *
  * The callback is used to notice the CA application or CA command line tool. The callback is
@@ -54,31 +44,6 @@
  */
 using StatusUpdateCallback = function<void (const CertificateRequest&/*the latest request info*/)>;
 
-class CaItem
-{
-public:
-  // basic info
-  Name m_caName;
-
-  // related CAs
-  std::list<Name> m_relatedCaList;
-
-  // essential config
-  time::seconds m_freshnessPeriod;
-  time::days m_validityPeriod;
-  std::list<std::string> m_supportedChallenges;
-
-  // optional parameters
-  std::string m_probe;
-  std::string m_targetedList;
-  std::string m_caInfo;
-
-  // callbacks
-  ProbeHandler m_probeHandler;
-  RecommendCaHandler m_recommendCaHandler;
-  StatusUpdateCallback m_statusUpdateCallback;
-};
-
 /**
  * @brief Represents a CA configuration instance
  *
@@ -110,11 +75,22 @@
   std::list<std::string>
   parseChallengeList(const JsonSection& configSection);
 
-  std::list<Name>
-  parseRelatedCaList(const JsonSection& section);
-
 public:
-  std::list<CaItem> m_caItems;
+  // basic info
+  Name m_caName;
+
+  // essential config
+  time::seconds m_freshnessPeriod;
+  time::days m_validityPeriod;
+  std::list<std::string> m_supportedChallenges;
+
+  // optional parameters
+  std::string m_probe;
+  std::string m_caInfo;
+
+  // callbacks
+  ProbeHandler m_probeHandler;
+  StatusUpdateCallback m_statusUpdateCallback;
 };
 
 } // namespace ndncert
diff --git a/src/ca-detail/ca-memory.cpp b/src/ca-detail/ca-memory.cpp
index 566e08c..ce5bd98 100644
--- a/src/ca-detail/ca-memory.cpp
+++ b/src/ca-detail/ca-memory.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2017, Regents of the University of California.
+ * Copyright (c) 2017-2019, Regents of the University of California.
  *
  * This file is part of ndncert, a certificate management system based on NDN.
  *
@@ -43,32 +43,32 @@
 {
   for (auto& entry : m_requests) {
     const auto& existingRequest = entry.second;
-    if (existingRequest.getCert().getKeyName() == request.getCert().getKeyName()) {
-      BOOST_THROW_EXCEPTION(Error("Request for " + request.getCert().getKeyName().toUri() + " already exists"));
+    if (existingRequest.m_cert.getKeyName() == request.m_cert.getKeyName()) {
+      BOOST_THROW_EXCEPTION(Error("Request for " + request.m_cert.getKeyName().toUri() + " already exists"));
       return;
     }
   }
   for (auto& entry : m_issuedCerts) {
     const auto& cert = entry.second;
-    if (cert.getKeyName() == request.getCert().getKeyName()) {
-      BOOST_THROW_EXCEPTION(Error("Cert for " + request.getCert().getKeyName().toUri() + " already exists"));
+    if (cert.getKeyName() == request.m_cert.getKeyName()) {
+      BOOST_THROW_EXCEPTION(Error("Cert for " + request.m_cert.getKeyName().toUri() + " already exists"));
       return;
     }
   }
 
-  auto search = m_requests.find(request.getRequestId());
+  auto search = m_requests.find(request.m_requestId);
   if (search == m_requests.end()) {
-    m_requests[request.getRequestId()] = request;
+    m_requests[request.m_requestId] = request;
   }
   else {
-    BOOST_THROW_EXCEPTION(Error("Request " + request.getRequestId() + " already exists"));
+    BOOST_THROW_EXCEPTION(Error("Request " + request.m_requestId + " already exists"));
   }
 }
 
 void
 CaMemory::updateRequest(const CertificateRequest& request)
 {
-  m_requests[request.getRequestId()] = request;
+  m_requests[request.m_requestId] = request;
 }
 
 void
@@ -95,7 +95,7 @@
 {
   std::list<CertificateRequest> result;
   for (const auto& entry : m_requests) {
-    if (entry.second.getCaName() == caName) {
+    if (entry.second.m_caName == caName) {
       result.push_back(entry.second);
     }
   }
diff --git a/src/ca-detail/ca-sqlite.cpp b/src/ca-detail/ca-sqlite.cpp
index 34f5d19..bb88cc7 100644
--- a/src/ca-detail/ca-sqlite.cpp
+++ b/src/ca-detail/ca-sqlite.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2017, Regents of the University of California.
+ * Copyright (c) 2017-2019, Regents of the University of California.
  *
  * This file is part of ndncert, a certificate management system based on NDN.
  *
@@ -40,11 +40,15 @@
     id INTEGER PRIMARY KEY,
     request_id TEXT NOT NULL,
     ca_name BLOB NOT NULL,
-    status TEXT NOT NULL,
+    status INTEGER NOT NULL,
+    challenge_status TEXT,
     cert_key_name BLOB NOT NULL,
     cert_request BLOB NOT NULL,
     challenge_type TEXT,
-    challenge_secrets TEXT
+    challenge_secrets TEXT,
+    challenge_tp TEXT,
+    remaining_tries INTEGER,
+    remaining_time INTEGER
   );
 CREATE UNIQUE INDEX IF NOT EXISTS
   CertRequestIdIndex ON CertRequests(request_id);
@@ -117,11 +121,17 @@
 
   if (statement.step() == SQLITE_ROW) {
     Name caName(statement.getBlock(2));
-    std::string status = statement.getString(3);
-    security::v2::Certificate cert(statement.getBlock(5));
-    std::string challengeType = statement.getString(6);
-    std::string challengeSecrets = statement.getString(7);
-    return CertificateRequest(caName, requestId, status, challengeType, challengeSecrets, cert);
+    int status = statement.getInt(3);
+    std::string challengeStatus = statement.getString(4);
+    security::v2::Certificate cert(statement.getBlock(6));
+    std::string challengeType = statement.getString(7);
+    std::string challengeSecrets = statement.getString(8);
+    std::string challengeTp = statement.getString(9);
+    int remainingTries = statement.getInt(10);
+    int remainingTime = statement.getInt(11);
+    return CertificateRequest(caName, requestId, status, challengeStatus, challengeType,
+                              challengeTp, remainingTime, remainingTries,
+                              convertString2Json(challengeSecrets), cert);
   }
   else {
     BOOST_THROW_EXCEPTION(Error("Request " + requestId + " cannot be fetched from database"));
@@ -132,35 +142,40 @@
 CaSqlite::addRequest(const CertificateRequest& request)
 {
   Sqlite3Statement statement1(m_database,
-                             R"_SQLTEXT_(SELECT * FROM CertRequests where cert_key_name = ?)_SQLTEXT_");
-  statement1.bind(1, request.getCert().getKeyName().wireEncode(), SQLITE_TRANSIENT);
+                              R"_SQLTEXT_(SELECT * FROM CertRequests where cert_key_name = ?)_SQLTEXT_");
+  statement1.bind(1, request.m_cert.getKeyName().wireEncode(), SQLITE_TRANSIENT);
   if (statement1.step() == SQLITE_ROW) {
-    BOOST_THROW_EXCEPTION(Error("Request for " + request.getCert().getKeyName().toUri() + " already exists"));
+    BOOST_THROW_EXCEPTION(Error("Request for " + request.m_cert.getKeyName().toUri() + " already exists"));
     return;
   }
 
   Sqlite3Statement statement2(m_database,
-                             R"_SQLTEXT_(SELECT * FROM IssuedCerts where cert_key_name = ?)_SQLTEXT_");
-  statement2.bind(1, request.getCert().getKeyName().wireEncode(), SQLITE_TRANSIENT);
+                              R"_SQLTEXT_(SELECT * FROM IssuedCerts where cert_key_name = ?)_SQLTEXT_");
+  statement2.bind(1, request.m_cert.getKeyName().wireEncode(), SQLITE_TRANSIENT);
   if (statement2.step() == SQLITE_ROW) {
-    BOOST_THROW_EXCEPTION(Error("Cert for " + request.getCert().getKeyName().toUri() + " already exists"));
+    BOOST_THROW_EXCEPTION(Error("Cert for " + request.m_cert.getKeyName().toUri() + " already exists"));
     return;
   }
 
   Sqlite3Statement statement(m_database,
                              R"_SQLTEXT_(INSERT INTO CertRequests (request_id, ca_name, status,
-                             cert_key_name, cert_request, challenge_type, challenge_secrets)
-                             values (?, ?, ?, ?, ?, ?, ?))_SQLTEXT_");
-  statement.bind(1, request.getRequestId(), SQLITE_TRANSIENT);
-  statement.bind(2, request.getCaName().wireEncode(), SQLITE_TRANSIENT);
-  statement.bind(3, request.getStatus(), SQLITE_TRANSIENT);
-  statement.bind(4, request.getCert().getKeyName().wireEncode(), SQLITE_TRANSIENT);
-  statement.bind(5, request.getCert().wireEncode(), SQLITE_TRANSIENT);
-  statement.bind(6, request.getChallengeType(), SQLITE_TRANSIENT);
-  statement.bind(7, convertJson2String(request.getChallengeSecrets()), SQLITE_TRANSIENT);
+                             challenge_status, cert_key_name, cert_request, challenge_type, challenge_secrets,
+                             challenge_tp, remaining_tries, remaining_time)
+                             values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?))_SQLTEXT_");
+  statement.bind(1, request.m_requestId, SQLITE_TRANSIENT);
+  statement.bind(2, request.m_caName.wireEncode(), SQLITE_TRANSIENT);
+  statement.bind(3, request.m_status);
+  statement.bind(4, request.m_challengeStatus, SQLITE_TRANSIENT);
+  statement.bind(5, request.m_cert.getKeyName().wireEncode(), SQLITE_TRANSIENT);
+  statement.bind(6, request.m_cert.wireEncode(), SQLITE_TRANSIENT);
+  statement.bind(7, request.m_challengeType, SQLITE_TRANSIENT);
+  statement.bind(8, convertJson2String(request.m_challengeSecrets), SQLITE_TRANSIENT);
+  statement.bind(9, request.m_challengeTp, SQLITE_TRANSIENT);
+  statement.bind(10, request.m_remainingTries);
+  statement.bind(11, request.m_remainingTime);
 
   if (statement.step() != SQLITE_DONE) {
-    BOOST_THROW_EXCEPTION(Error("Request " + request.getRequestId() + " cannot be added to database"));
+    BOOST_THROW_EXCEPTION(Error("Request " + request.m_requestId + " cannot be added to database"));
   }
 }
 
@@ -169,12 +184,17 @@
 {
   Sqlite3Statement statement(m_database,
                              R"_SQLTEXT_(UPDATE CertRequests
-                             SET status = ?, challenge_type = ?, challenge_secrets = ?
+                             SET status = ?, challenge_status = ?, challenge_type = ?, challenge_secrets = ?,
+                             challenge_tp = ?, remaining_tries = ?, remaining_time = ?
                              WHERE request_id = ?)_SQLTEXT_");
-  statement.bind(1, request.getStatus(), SQLITE_TRANSIENT);
-  statement.bind(2, request.getChallengeType(), SQLITE_TRANSIENT);
-  statement.bind(3, convertJson2String(request.getChallengeSecrets()), SQLITE_TRANSIENT);
-  statement.bind(4, request.getRequestId(), SQLITE_TRANSIENT);
+  statement.bind(1, request.m_status);
+  statement.bind(2, request.m_challengeStatus, SQLITE_TRANSIENT);
+  statement.bind(3, request.m_challengeType, SQLITE_TRANSIENT);
+  statement.bind(4, convertJson2String(request.m_challengeSecrets), SQLITE_TRANSIENT);
+  statement.bind(5, request.m_challengeTp, SQLITE_TRANSIENT);
+  statement.bind(6, request.m_remainingTries);
+  statement.bind(7, request.m_remainingTime);
+  statement.bind(8, request.m_requestId, SQLITE_TRANSIENT);
 
   if (statement.step() != SQLITE_DONE) {
     addRequest(request);
@@ -190,11 +210,17 @@
   while(statement.step() == SQLITE_ROW) {
     std::string requestId = statement.getString(1);
     Name caName(statement.getBlock(2));
-    std::string status = statement.getString(3);
-    security::v2::Certificate cert(statement.getBlock(5));
-    std::string challengeType = statement.getString(6);
-    std::string challengeSecrets = statement.getString(7);
-    CertificateRequest entry(caName, requestId, status, challengeType, challengeSecrets, cert);
+    int status = statement.getInt(3);
+    std::string challengeStatus = statement.getString(4);
+    security::v2::Certificate cert(statement.getBlock(6));
+    std::string challengeType = statement.getString(7);
+    std::string challengeSecrets = statement.getString(8);
+    std::string challengeTp = statement.getString(9);
+    int remainingTries = statement.getInt(10);
+    int remainingTime = statement.getInt(11);
+    CertificateRequest entry(caName, requestId, status, challengeStatus, challengeType,
+                             challengeTp, remainingTime, remainingTries,
+                             convertString2Json(challengeSecrets), cert);
     result.push_back(entry);
   }
   return result;
@@ -210,11 +236,18 @@
 
   while(statement.step() == SQLITE_ROW) {
     std::string requestId = statement.getString(1);
-    std::string status = statement.getString(3);
-    security::v2::Certificate cert(statement.getBlock(5));
-    std::string challengeType = statement.getString(6);
-    std::string challengeSecrets = statement.getString(7);
-    CertificateRequest entry(caName, requestId, status, challengeType, challengeSecrets, cert);
+    Name caName(statement.getBlock(2));
+    int status = statement.getInt(3);
+    std::string challengeStatus = statement.getString(4);
+    security::v2::Certificate cert(statement.getBlock(6));
+    std::string challengeType = statement.getString(7);
+    std::string challengeSecrets = statement.getString(8);
+    std::string challengeTp = statement.getString(9);
+    int remainingTries = statement.getInt(10);
+    int remainingTime = statement.getInt(11);
+    CertificateRequest entry(caName, requestId, status, challengeStatus, challengeType,
+                             challengeTp, remainingTime, remainingTries,
+                             convertString2Json(challengeSecrets), cert);
     result.push_back(entry);
   }
   return result;
diff --git a/src/ca-module.cpp b/src/ca-module.cpp
index a6954a8..7a575a9 100644
--- a/src/ca-module.cpp
+++ b/src/ca-module.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2017-2018, Regents of the University of California.
+ * Copyright (c) 2017-2019, Regents of the University of California.
  *
  * This file is part of ndncert, a certificate management system based on NDN.
  *
@@ -21,6 +21,7 @@
 #include "ca-module.hpp"
 #include "challenge-module.hpp"
 #include "logging.hpp"
+#include "crypto-support/enc-tlv.hpp"
 #include <ndn-cxx/util/io.hpp>
 #include <ndn-cxx/security/verification-helpers.hpp>
 #include <ndn-cxx/security/signing-helpers.hpp>
@@ -29,6 +30,8 @@
 namespace ndn {
 namespace ndncert {
 
+static const int IS_SUBNAME_MIN_OFFSET = 5;
+
 _LOG_INIT(ndncert.ca);
 
 CaModule::CaModule(Face& face, security::v2::KeyChain& keyChain,
@@ -45,525 +48,336 @@
 
 CaModule::~CaModule()
 {
-  for (auto prefixId : m_interestFilterIds) {
-    m_face.unsetInterestFilter(prefixId);
+  for (auto handle : m_interestFilterHandles) {
+    handle.cancel();
   }
-  for (auto prefixId : m_registeredPrefixIds) {
-    m_face.unregisterPrefix(prefixId, nullptr, nullptr);
+  for (auto handle : m_registeredPrefixHandles) {
+    handle.unregister();
   }
 }
 
 void
 CaModule::registerPrefix()
 {
-  // register localhost list prefix
-  Name localProbePrefix("/localhost/CA/_LIST");
-  auto prefixId = m_face.setInterestFilter(InterestFilter(localProbePrefix),
-                                           bind(&CaModule::handleLocalhostList, this, _2),
+  // register localhop discovery prefix
+  Name localhopProbePrefix("/localhop/CA/PROBE/INFO");
+  auto prefixId = m_face.setInterestFilter(InterestFilter(localhopProbePrefix),
+                                           bind(&CaModule::onProbe, this, _2),
                                            bind(&CaModule::onRegisterFailed, this, _2));
-  m_registeredPrefixIds.push_back(prefixId);
-  _LOG_TRACE("Prefix " << localProbePrefix << " got registered");
+  m_registeredPrefixHandles.push_back(prefixId);
+  _LOG_TRACE("Prefix " << localhopProbePrefix << " got registered");
 
-  // register prefixes for each CA
-  for (const auto& item : m_config.m_caItems) {
-    Name prefix = item.m_caName;
-    prefix.append("CA");
+  // register prefixes
+  Name prefix = m_config.m_caName;
+  prefix.append("CA");
 
-    prefixId = m_face.registerPrefix(prefix,
-      [&] (const Name& name) {
-        // NEW
-        auto filterId = m_face.setInterestFilter(Name(name).append("_NEW"),
-                                                 bind(&CaModule::handleNew, this, _2, item));
-        m_interestFilterIds.push_back(filterId);
-        // SELECT
-        filterId = m_face.setInterestFilter(Name(name).append("_SELECT"),
-                                            bind(&CaModule::handleSelect, this, _2, item));
-        m_interestFilterIds.push_back(filterId);
-        // VALIDATE
-        filterId = m_face.setInterestFilter(Name(name).append("_VALIDATE"),
-                                            bind(&CaModule::handleValidate, this, _2, item));
-        m_interestFilterIds.push_back(filterId);
-        // STATUS
-        filterId = m_face.setInterestFilter(Name(name).append("_STATUS"),
-                                            bind(&CaModule::handleStatus, this, _2, item));
-        m_interestFilterIds.push_back(filterId);
-        // DOWNLOAD
-        filterId = m_face.setInterestFilter(Name(name).append("_DOWNLOAD"),
-                                            bind(&CaModule::handleDownload, this, _2, item));
-        m_interestFilterIds.push_back(filterId);
-        // PROBE
-        if (item.m_probe != "") {
-          filterId = m_face.setInterestFilter(Name(name).append("_PROBE"),
-                                              bind(&CaModule::handleProbe, this, _2, item));
-          m_interestFilterIds.push_back(filterId);
-        }
-        // LIST
-        if (item.m_relatedCaList.size() > 0) {
-          filterId = m_face.setInterestFilter(Name(name).append("_LIST"),
-                                              bind(&CaModule::handleList, this, _2, item));
-          m_interestFilterIds.push_back(filterId);
-        }
-        _LOG_TRACE("Prefix " << name << " got registered");
-      },
-      bind(&CaModule::onRegisterFailed, this, _2));
-    m_registeredPrefixIds.push_back(prefixId);
-  }
+  prefixId = m_face.registerPrefix(prefix,
+    [&] (const Name& name) {
+      // register PROBE prefix
+      auto filterId = m_face.setInterestFilter(Name(name).append("_PROBE"),
+                                               bind(&CaModule::onProbe, this, _2));
+      m_interestFilterHandles.push_back(filterId);
+
+      // register NEW prefix
+      filterId = m_face.setInterestFilter(Name(name).append("_NEW"),
+                                          bind(&CaModule::onNew, this, _2));
+      m_interestFilterHandles.push_back(filterId);
+
+      // register SELECT prefix
+      filterId = m_face.setInterestFilter(Name(name).append("_CHALLENGE"),
+                                          bind(&CaModule::onChallenge, this, _2));
+      m_interestFilterHandles.push_back(filterId);
+
+      // register DOWNLOAD prefix
+      filterId = m_face.setInterestFilter(Name(name).append("_DOWNLOAD"),
+                                          bind(&CaModule::onDownload, this, _2));
+      m_interestFilterHandles.push_back(filterId);
+      _LOG_TRACE("Prefix " << name << " got registered");
+    },
+    bind(&CaModule::onRegisterFailed, this, _2));
+  m_registeredPrefixHandles.push_back(prefixId);
 }
 
 bool
-CaModule::setProbeHandler(const Name caName, const ProbeHandler& handler)
+CaModule::setProbeHandler(const ProbeHandler& handler)
 {
-  for (auto& entry : m_config.m_caItems) {
-    if (entry.m_caName == caName) {
-      entry.m_probeHandler = handler;
-      return true;
-    }
-  }
+  m_config.m_probeHandler = handler;
   return false;
 }
 
 bool
-CaModule::setRecommendCaHandler(const Name caName, const RecommendCaHandler& handler)
+CaModule::setStatusUpdateCallback(const StatusUpdateCallback& onUpdateCallback)
 {
-  for (auto& entry : m_config.m_caItems) {
-    if (entry.m_caName == caName) {
-      entry.m_recommendCaHandler = handler;
-      return true;
-    }
-  }
-  return false;
-}
-
-bool
-CaModule::setStatusUpdateCallback(const Name caName, const StatusUpdateCallback& onUpateCallback)
-{
-  for (auto& entry : m_config.m_caItems) {
-    if (entry.m_caName == caName) {
-      entry.m_statusUpdateCallback = onUpateCallback;
-      return true;
-    }
-  }
+  m_config.m_statusUpdateCallback = onUpdateCallback;
   return false;
 }
 
 void
-CaModule::handleLocalhostList(const Interest& request)
+CaModule::onProbe(const Interest& request)
 {
-  _LOG_TRACE("Got Localhost LIST request");
+  // PROBE Naming Convention: /<CA-Prefix>/CA/PROBE/[ParametersSha256DigestComponent|INFO]
+  _LOG_TRACE("Receive PROBE request");
+  JsonSection contentJson;
 
-  JsonSection root;
-  JsonSection caListSection;
-
-  for (const auto& entry : m_config.m_caItems) {
-    JsonSection caItem;
-
-    const auto& pib = m_keyChain.getPib();
-    auto identity = pib.getIdentity(entry.m_caName);
-    auto cert = identity.getDefaultKey().getDefaultCertificate();
-
-    // ca-prefix
-    Name caName = entry.m_caName;
-    caName.append("CA");
-    caItem.put("ca-prefix", caName.toUri());
-
-    // ca-info
-    std::string caInfo;
-    if (entry.m_caInfo == "") {
-      caInfo = "Issued by " + cert.getSignature().getKeyLocator().getName().toUri();
+  // process PROBE INFO requests
+  if (readString(request.getName().at(-1)) == "INFO") {
+    contentJson = genProbeResponseJson();
+  }
+  else {
+    // if not a PROBE INFO, find an available name
+    std::string availableId = "";
+    const auto& parameterJson = jsonFromBlock(request.getApplicationParameters());
+    std::string probeInfoStr = parameterJson.get(JSON_CLIENT_PROBE_INFO, "");
+    if (m_config.m_probeHandler) {
+      try {
+        availableId = m_config.m_probeHandler(probeInfoStr);
+      }
+      catch (const std::exception& e) {
+        _LOG_TRACE("Cannot find PROBE input from PROBE parameters " << e.what());
+        return;
+      }
     }
     else {
-      caInfo = entry.m_caInfo;
+      // if there is no app-specified name lookup, use a random name id
+      availableId = std::to_string(random::generateSecureWord64());
     }
-    caItem.put("ca-info", caInfo);
-
-    // probe is always false for local client
-
-    // ca-target list
-    caItem.put("target-list", entry.m_targetedList);
-
-    // certificate
-    std::stringstream ss;
-    io::save(cert, ss);
-    caItem.put("certificate", ss.str());
-
-    caListSection.push_back(std::make_pair("", caItem));
+    Name newIdentityName = m_config.m_caName;
+    _LOG_TRACE("Handle PROBE: generate an identity " << newIdentityName);
+    newIdentityName.append(availableId);
+    contentJson = genProbeResponseJson(newIdentityName.toUri());
   }
-  root.add_child("ca-list", caListSection);
-
-  Data result;
-  Name dataName = request.getName();
-  dataName.appendTimestamp();
-  result.setName(dataName);
-  result.setContent(dataContentFromJson(root));
-  m_keyChain.sign(result, signingByIdentity(m_keyChain.getPib().getDefaultIdentity().getName()));
-  m_face.put(result);
-}
-
-void
-CaModule::handleList(const Interest& request, const CaItem& caItem)
-{
-  _LOG_TRACE("Got LIST request");
-
-  bool getRecommendation = false;
-  Name recommendedCaName;
-  std::string identityName;
-
-  // LIST naming convention: /CA-prefix/CA/_LIST/[optional info]
-  if (readString(request.getName().at(-1)) != "_LIST" && caItem.m_recommendCaHandler) {
-    const auto& additionInfo = readString(request.getName().at(-1));
-    try {
-      std::tie(recommendedCaName, identityName) = caItem.m_recommendCaHandler(additionInfo, caItem.m_relatedCaList);
-      getRecommendation = true;
-    }
-    catch (const std::exception& e) {
-      _LOG_TRACE("Cannot recommend CA for LIST request. Degrade to non-target list." << e.what());
-    }
-  }
-
-  JsonSection root;
-  JsonSection caListSection;
-  if (getRecommendation) {
-    // JSON format
-    // {
-    //   "recommended-ca": "/ndn/edu/ucla"
-    //   "recommended-identity": "something"
-    //   "trust-schema": "schema Data packet name"
-    // }
-    root.put("recommended-ca", recommendedCaName.toUri());
-    root.put("recommended-identity", identityName);
-  }
-  else {
-    // JSON format
-    // {
-    //   "ca-list": [
-    //     {"ca-prefix": "/ndn/edu/ucla"},
-    //     {"ca-prefix": "/ndn/edu/memphis"},
-    //     ...
-    //   ]
-    //   "trust-schema": "schema Data packet name"
-    // }
-    for (const auto& entry : caItem.m_relatedCaList) {
-      JsonSection caItem;
-      caItem.put("ca-prefix", entry.toUri());
-      caListSection.push_back(std::make_pair("", caItem));
-    }
-    root.add_child("ca-list", caListSection);
-  }
-
-  // TODO: add trust schema
-  std::string schemaDataName = "TODO: add trust schema";
-  root.put("trust-schema", schemaDataName);
-
-  Data result;
-  Name dataName = request.getName();
-  dataName.appendTimestamp();
-  result.setName(dataName);
-  result.setContent(dataContentFromJson(root));
-  m_keyChain.sign(result, signingByIdentity(caItem.m_caName));
-  m_face.put(result);
-}
-
-void
-CaModule::handleProbe(const Interest& request, const CaItem& caItem)
-{
-  // PROBE Naming Convention: /CA-prefix/CA/_PROBE/<Probe Information>
-  _LOG_TRACE("Handle PROBE request");
-
-  std::string identifier;
-  if (caItem.m_probeHandler) {
-    try {
-      identifier = caItem.m_probeHandler(readString(request.getName().at(caItem.m_caName.size() + 2)));
-    }
-    catch (const std::exception& e) {
-      _LOG_TRACE("Cannot generate identifier for PROBE request " << e.what());
-      return;
-    }
-  }
-  else {
-    identifier = readString(request.getName().at(caItem.m_caName.size() + 2));
-  }
-  Name identityName = caItem.m_caName;
-  identityName.append(identifier);
 
   Data result;
   result.setName(request.getName());
-  result.setContent(dataContentFromJson(genResponseProbeJson(identityName, "")));
-  m_keyChain.sign(result, signingByIdentity(caItem.m_caName));
+  result.setContent(dataContentFromJson(contentJson));
+  m_keyChain.sign(result, signingByIdentity(m_config.m_caName));
   m_face.put(result);
-
-  _LOG_TRACE("Handle PROBE: generate identity " << identityName);
+  _LOG_TRACE("Handle PROBE: send out the PROBE response");
 }
 
 void
-CaModule::handleNew(const Interest& request, const CaItem& caItem)
+CaModule::onNew(const Interest& request)
 {
-  // NEW Naming Convention: /CA-prefix/CA/_NEW/<certificate-request>/[signature]
-  _LOG_TRACE("Handle NEW request");
+  // NEW Naming Convention: /<CA-prefix>/CA/NEW/[SignedInterestParameters_Digest]
 
-  security::v2::Certificate clientCert;
+  // get ECDH pub key and cert request
+  const auto& parameterJson = jsonFromBlock(request.getApplicationParameters());
+  std::string peerKeyBase64 = parameterJson.get(JSON_CLIENT_ECDH, "");
+
+  // get server's ECDH pub key
+  auto myEcdhPubKeyBase64 = m_ecdh.getBase64PubKey();
+  m_ecdh.deriveSecret(peerKeyBase64);
+  // generate salt for HKDF
+  auto saltInt = random::generateSecureWord64();
+  uint8_t salt[sizeof(saltInt)];
+  std::memcpy(salt, &saltInt, sizeof(saltInt));
+  // hkdf
+  hkdf(m_ecdh.context->sharedSecret, m_ecdh.context->sharedSecretLen,
+       salt, sizeof(saltInt), m_aesKey, 32);
+
+  // parse certificate request
+  std::string certRequestStr = parameterJson.get(JSON_CLIENT_CERT_REQ, "");
+  shared_ptr<security::v2::Certificate> clientCert = nullptr;
   try {
-    clientCert.wireDecode(request.getName().at(caItem.m_caName.size() + 2).blockFromValue());
+    std::stringstream ss(certRequestStr);
+    clientCert = io::load<security::v2::Certificate>(ss);
   }
   catch (const std::exception& e) {
     _LOG_ERROR("Unrecognized certificate request " << e.what());
     return;
   }
 
-  if (!security::verifySignature(clientCert, clientCert)) {
+  // verify the self-signed certificate and the request
+  if (!m_config.m_caName.isPrefixOf(clientCert->getName()) // under ca prefix
+      || !security::v2::Certificate::isValidName(clientCert->getName()) // is valid cert name
+      || clientCert->getName().size() != m_config.m_caName.size() + IS_SUBNAME_MIN_OFFSET) {
+    _LOG_ERROR("Invalid self-signed certificate name " << clientCert->getName());
+    return;
+  }
+  if (!security::verifySignature(*clientCert, *clientCert)) {
     _LOG_TRACE("Cert request with bad signature.");
     return;
   }
-  if (!security::verifySignature(request, clientCert)) {
+  if (!security::verifySignature(request, *clientCert)) {
     _LOG_TRACE("Interest with bad signature.");
     return;
   }
 
+  // create new request instance
   std::string requestId = std::to_string(random::generateWord64());
-  CertificateRequest certRequest(caItem.m_caName, requestId, clientCert);
-  certRequest.setStatus(ChallengeModule::WAIT_SELECTION);
+  CertificateRequest certRequest(m_config.m_caName, requestId, STATUS_BEFORE_CHALLENGE, *clientCert);
   try {
     m_storage->addRequest(certRequest);
   }
   catch (const std::exception& e) {
-    _LOG_TRACE("Cannot add new request instance " << e.what());
+    _LOG_TRACE("Cannot add new request instance into the storage" << e.what());
     return;
   }
 
   Data result;
   result.setName(request.getName());
-  result.setContent(dataContentFromJson(genResponseNewJson(requestId, certRequest.getStatus(),
-                                                           caItem.m_supportedChallenges)));
-  m_keyChain.sign(result, signingByIdentity(caItem.m_caName));
+  result.setContent(dataContentFromJson(genNewResponseJson(myEcdhPubKeyBase64,
+                                                           std::to_string(saltInt),
+                                                           certRequest,
+                                                           m_config.m_supportedChallenges)));
+  m_keyChain.sign(result, signingByIdentity(m_config.m_caName));
   m_face.put(result);
 
-  if (caItem.m_statusUpdateCallback) {
-    caItem.m_statusUpdateCallback(certRequest);
+  if (m_config.m_statusUpdateCallback) {
+    m_config.m_statusUpdateCallback(certRequest);
   }
 }
 
 void
-CaModule::handleSelect(const Interest& request, const CaItem& caItem)
+CaModule::onChallenge(const Interest& request)
 {
-  // SELECT Naming Convention: /CA-prefix/CA/_SELECT/{Request-ID JSON}/<ChallengeID>/
-  // {Param JSON}/[Signature components]
-  _LOG_TRACE("Handle SELECT request");
-
-  CertificateRequest certRequest = getCertificateRequest(request, caItem.m_caName);
-  if (certRequest.getRequestId().empty()) {
+  // get certificate request state
+  CertificateRequest certRequest = getCertificateRequest(request);
+  if (certRequest.m_requestId == "") {
+    // cannot get the request state
     return;
   }
-
-  if (!security::verifySignature(request, certRequest.getCert())) {
+  // verify signature
+  if (!security::verifySignature(request, certRequest.m_cert)) {
     _LOG_TRACE("Interest with bad signature.");
     return;
   }
+  // decrypt the parameters
+  auto paramJsonPayload = parseEncBlock(m_ecdh.context->sharedSecret,
+                                        m_ecdh.context->sharedSecretLen,
+                                        request.getApplicationParameters());
+  std::string paramJsonStr((const char*)paramJsonPayload.data(), paramJsonPayload.size());
+  std::istringstream ss(paramJsonStr);
+  JsonSection paramJson;
+  boost::property_tree::json_parser::read_json(ss, paramJson);
 
-  std::string challengeType;
-  try {
-    challengeType = readString(request.getName().at(caItem.m_caName.size() + 3));
-  }
-  catch (const std::exception& e) {
-    _LOG_ERROR(e.what());
-    return;
-  }
-  _LOG_TRACE("SELECT request choosing challenge " << challengeType);
+  // load the corresponding challenge module
+  std::string challengeType = paramJson.get<std::string>(JSON_CLIENT_SELECTED_CHALLENGE);
   auto challenge = ChallengeModule::createChallengeModule(challengeType);
+  JsonSection contentJson;
   if (challenge == nullptr) {
     _LOG_TRACE("Unrecognized challenge type " << challengeType);
-    return;
-  }
-  JsonSection contentJson = challenge->handleChallengeRequest(request, certRequest);
-  if (certRequest.getStatus() == ChallengeModule::FAILURE) {
-    m_storage->deleteRequest(certRequest.getRequestId());
+    certRequest.m_status = STATUS_FAILURE;
+    certRequest.m_challengeStatus = CHALLENGE_STATUS_UNKNOWN_CHALLENGE;
+    contentJson = genChallengeResponseJson(certRequest);
   }
   else {
-    try {
-      m_storage->updateRequest(certRequest);
+    _LOG_TRACE("CHALLENGE module to be load: " << challengeType);
+    // let challenge module handle the request
+    challenge->handleChallengeRequest(paramJson, certRequest);
+    if (certRequest.m_status == STATUS_FAILURE) {
+      // if challenge failed
+      m_storage->deleteRequest(certRequest.m_requestId);
+      contentJson = genChallengeResponseJson(certRequest);
+      _LOG_TRACE("Challenge failed");
     }
-    catch (const std::exception& e) {
-      _LOG_TRACE("Cannot update request instance " << e.what());
-      return;
-    }
-  }
-
-  Data result;
-  result.setName(request.getName());
-  result.setContent(dataContentFromJson(contentJson));
-  m_keyChain.sign(result, signingByIdentity(caItem.m_caName));
-  m_face.put(result);
-
-  if (caItem.m_statusUpdateCallback) {
-    caItem.m_statusUpdateCallback(certRequest);
-  }
-}
-
-void
-CaModule::handleValidate(const Interest& request, const CaItem& caItem)
-{
-  // VALIDATE Naming Convention: /CA-prefix/CA/_VALIDATE/{Request-ID JSON}/<ChallengeID>/
-  // {Param JSON}/[Signature components]
-  _LOG_TRACE("Handle VALIDATE request");
-
-  CertificateRequest certRequest = getCertificateRequest(request, caItem.m_caName);
-  if (certRequest.getRequestId().empty()) {
-    return;
-  }
-
-  if (!security::verifySignature(request, certRequest.getCert())) {
-    _LOG_TRACE("Interest with bad signature.");
-    return;
-  }
-
-  std::string challengeType = certRequest.getChallengeType();
-  auto challenge = ChallengeModule::createChallengeModule(challengeType);
-  if (challenge == nullptr) {
-    _LOG_TRACE("Unrecognized challenge type " << challengeType);
-    return;
-  }
-  JsonSection contentJson = challenge->handleChallengeRequest(request, certRequest);
-  if (certRequest.getStatus() == ChallengeModule::FAILURE) {
-    m_storage->deleteRequest(certRequest.getRequestId());
-  }
-  else {
-    try {
-      m_storage->updateRequest(certRequest);
-    }
-    catch (const std::exception& e) {
-      _LOG_TRACE("Cannot update request instance " << e.what());
-      return;
-    }
-  }
-  Data result;
-  result.setName(request.getName());
-  result.setContent(dataContentFromJson(contentJson));
-  m_keyChain.sign(result, signingByIdentity(caItem.m_caName));
-  m_face.put(result);
-
-  if (certRequest.getStatus() == ChallengeModule::SUCCESS) {
-    auto issuedCert = issueCertificate(certRequest, caItem);
-    if (caItem.m_statusUpdateCallback) {
-      certRequest.setCert(issuedCert);
-      caItem.m_statusUpdateCallback(certRequest);
-    }
-    try {
-      m_storage->addCertificate(certRequest.getRequestId(), issuedCert);
-      m_storage->deleteRequest(certRequest.getRequestId());
-      _LOG_TRACE("New Certificate Issued " << issuedCert.getName());
-    }
-    catch (const std::exception& e) {
-      _LOG_ERROR("Cannot add issued cert and remove the request " << e.what());
-      return;
-    }
-  }
-}
-
-void
-CaModule::handleStatus(const Interest& request, const CaItem& caItem)
-{
-  // STATUS Naming Convention: /CA-prefix/CA/_STATUS/{Request-ID JSON}/[Signature components]
-  _LOG_TRACE("Handle STATUS request");
-
-  CertificateRequest certRequest = getCertificateRequest(request, caItem.m_caName);
-  if (certRequest.getRequestId().empty()) {
-    return;
-  }
-
-  if (!security::verifySignature(request, certRequest.getCert())) {
-    _LOG_TRACE("Interest with bad signature.");
-    return;
-  }
-
-  std::string challengeType = certRequest.getChallengeType();
-  auto challenge = ChallengeModule::createChallengeModule(challengeType);
-  if (challenge == nullptr) {
-    _LOG_TRACE("Unrecognized challenge type " << challengeType);
-    return;
-  }
-  JsonSection contentJson = challenge->handleChallengeRequest(request, certRequest);
-
-  Data result;
-  result.setName(request.getName());
-  result.setContent(dataContentFromJson(contentJson));
-  m_keyChain.sign(result, signingByIdentity(caItem.m_caName));
-  m_face.put(result);
-}
-
-void
-CaModule::handleDownload(const Interest& request, const CaItem& caItem)
-{
-  // DOWNLOAD Naming Convention: /CA-prefix/CA/_DOWNLOAD/{Request-ID JSON}
-  _LOG_TRACE("Handle DOWNLOAD request");
-
-  Data result;
-  result.setName(request.getName());
-  if (readString(request.getName().at(-1)) == "ANCHOR") {
-    JsonSection contentJson;
-
-    const auto& pib = m_keyChain.getPib();
-    auto identity = pib.getIdentity(caItem.m_caName);
-    auto cert = identity.getDefaultKey().getDefaultCertificate();
-
-    // ca-prefix
-    Name caName = caItem.m_caName;
-    caName.append("CA");
-    contentJson.put("ca-prefix", caName.toUri());
-
-    // ca-info
-    std::string caInfo;
-    if (caItem.m_caInfo == "") {
-      caInfo = "Issued by " + cert.getSignature().getKeyLocator().getName().toUri();
+    else if (certRequest.m_status == STATUS_PENDING) {
+      // if challenge succeeded
+      auto issuedCert = issueCertificate(certRequest);
+      certRequest.m_cert = issuedCert;
+      certRequest.m_status = STATUS_SUCCESS;
+      try {
+        m_storage->addCertificate(certRequest.m_requestId, issuedCert);
+        m_storage->deleteRequest(certRequest.m_requestId);
+        _LOG_TRACE("New Certificate Issued " << issuedCert.getName());
+      }
+      catch (const std::exception& e) {
+        _LOG_ERROR("Cannot add issued cert and remove the request " << e.what());
+        return;
+      }
+      if (m_config.m_statusUpdateCallback) {
+        m_config.m_statusUpdateCallback(certRequest);
+      }
+      contentJson = genChallengeResponseJson(certRequest);
+      contentJson.add(JSON_CA_CERT_ID, readString(issuedCert.getName().at(-1)));
+      _LOG_TRACE("Challenge succeeded. Certificate has been issued");
     }
     else {
-      caInfo = caItem.m_caInfo;
+      try {
+        m_storage->updateRequest(certRequest);
+      }
+      catch (const std::exception& e) {
+        _LOG_TRACE("Cannot update request instance " << e.what());
+        return;
+      }
+      contentJson = genChallengeResponseJson(certRequest);
+      _LOG_TRACE("No failure no success. Challenge moves on");
     }
-    contentJson.put("ca-info", caInfo);
-
-    // probe
-    contentJson.put("probe", caItem.m_probe);
-
-    // ca-target list
-    contentJson.put("target-list", caItem.m_targetedList);
-
-    // certificate
-    std::stringstream ss;
-    io::save(cert, ss);
-    contentJson.put("certificate", ss.str());
-
-    result.setContent(dataContentFromJson(contentJson));
   }
-  else {
-    JsonSection requestIdJson = jsonFromNameComponent(request.getName(), caItem.m_caName.size() + 2);
-    std::string requestId = requestIdJson.get(JSON_REQUEST_ID, "");
-    security::v2::Certificate signedCert;
-    try {
-      signedCert = m_storage->getCertificate(requestId);
-    }
-    catch (const std::exception& e) {
-      _LOG_ERROR(e.what());
-      return;
-    }
-    result.setContent(signedCert.wireEncode());
+
+  Data result;
+  result.setName(request.getName());
+
+  // encrypt the content
+  std::stringstream ss2;
+  boost::property_tree::write_json(ss2, contentJson);
+  auto payload = ss2.str();
+  auto contentBlock = genEncBlock(tlv::Content, m_ecdh.context->sharedSecret,
+                                  m_ecdh.context->sharedSecretLen,
+                                  (const uint8_t*)payload.c_str(), payload.size());
+  result.setContent(contentBlock);
+  m_keyChain.sign(result, signingByIdentity(m_config.m_caName));
+  m_face.put(result);
+
+  if (m_config.m_statusUpdateCallback) {
+    m_config.m_statusUpdateCallback(certRequest);
   }
-  m_keyChain.sign(result, signingByIdentity(caItem.m_caName));
+}
+
+void
+CaModule::onDownload(const Interest& request)
+{
+  auto requestId = readString(request.getName().at(-1));
+  security::v2::Certificate signedCert;
+  try {
+    signedCert = m_storage->getCertificate(requestId);
+  }
+  catch (const std::exception& e) {
+    _LOG_ERROR("Cannot read signed cert " << requestId << " from ca database " << e.what());
+    return;
+  }
+  Data result;
+  result.setName(request.getName());
+  result.setContent(signedCert.wireEncode());
+  m_keyChain.sign(result, signingByIdentity(m_config.m_caName));
   m_face.put(result);
 }
 
 security::v2::Certificate
-CaModule::issueCertificate(const CertificateRequest& certRequest, const CaItem& caItem)
+CaModule::issueCertificate(const CertificateRequest& certRequest)
 {
-  Name certName = certRequest.getCert().getKeyName();
-  certName.append("NDNCERT").appendVersion();
+  auto expectedPeriod =
+    certRequest.m_cert.getValidityPeriod().getPeriod();
+
+  time::system_clock::TimePoint startingTime, endingTime;
+  if (expectedPeriod.first > time::system_clock::now()
+      && expectedPeriod.first <  time::system_clock::now()
+      + m_config.m_validityPeriod)
+    {
+      startingTime = expectedPeriod.first;
+    }
+  else {
+    startingTime = time::system_clock::now();
+  }
+  if (expectedPeriod.second < time::system_clock::now() + m_config.m_validityPeriod) {
+    endingTime = expectedPeriod.second;
+  }
+  else {
+    endingTime = time::system_clock::now() + m_config.m_validityPeriod;
+  }
+  security::ValidityPeriod period(startingTime, endingTime);
   security::v2::Certificate newCert;
+
+  Name certName = certRequest.m_cert.getKeyName();
+  certName.append("NDNCERT").append(std::to_string(random::generateSecureWord64()));
   newCert.setName(certName);
-  newCert.setContent(certRequest.getCert().getContent());
-  _LOG_TRACE("cert request content " << certRequest.getCert());
+  newCert.setContent(certRequest.m_cert.getContent());
+  _LOG_TRACE("cert request content " << certRequest.m_cert);
   SignatureInfo signatureInfo;
-  security::ValidityPeriod period(time::system_clock::now(),
-                                  time::system_clock::now() + caItem.m_validityPeriod);
   signatureInfo.setValidityPeriod(period);
   security::SigningInfo signingInfo(security::SigningInfo::SIGNER_TYPE_ID,
-                                    caItem.m_caName, signatureInfo);
-  newCert.setFreshnessPeriod(caItem.m_freshnessPeriod);
+                                    m_config.m_caName, signatureInfo);
+  newCert.setFreshnessPeriod(m_config.m_freshnessPeriod);
 
   m_keyChain.sign(newCert, signingInfo);
   _LOG_TRACE("new cert got signed" << newCert);
@@ -571,10 +385,10 @@
 }
 
 CertificateRequest
-CaModule::getCertificateRequest(const Interest& request, const Name& caName)
+CaModule::getCertificateRequest(const Interest& request)
 {
-  JsonSection requestIdJson = jsonFromNameComponent(request.getName(), caName.size() + 2);
-  std::string requestId = requestIdJson.get(JSON_REQUEST_ID, "");
+  std::string requestId = readString(request.getName().at(m_config.m_caName.size() + 2));
+  _LOG_TRACE("Requet Id to query the database " << requestId);
   CertificateRequest certRequest;
   try {
     certRequest = m_storage->getRequest(requestId);
@@ -585,6 +399,108 @@
   return certRequest;
 }
 
+/**
+ * @brief Generate JSON file to response PROBE insterest
+ *
+ * PROBE response JSON format:
+ * {
+ *   "name": "@p identifier",
+ *   "ca-config": "@p caInformation"
+ * }
+ */
+const JsonSection
+CaModule::genProbeResponseJson(const Name& identifier)
+{
+  JsonSection root;
+  root.put(JSON_CA_NAME, identifier.toUri());
+  return root;
+}
+
+/**
+ * @brief Generate JSON file to response NEW interest
+ *
+ * Target JSON format:
+ * {
+ *   "ecdh-pub": "@p echdPub",
+ *   "salt": "@p salt"
+ *   "request-id": "@p requestId",
+ *   "status": "@p status",
+ *   "challenges": [
+ *     {
+ *       "challenge-id": ""
+ *     },
+ *     {
+ *       "challenge-id": ""
+ *     },
+ *     ...
+ *   ]
+ * }
+ */
+const JsonSection
+CaModule::genProbeResponseJson()
+{
+  JsonSection root;
+  // ca-prefix
+  Name caName = m_config.m_caName;
+  root.put("ca-prefix", caName.toUri());
+
+  // ca-info
+  const auto& pib = m_keyChain.getPib();
+  auto identity = pib.getIdentity(m_config.m_caName);
+  auto cert = identity.getDefaultKey().getDefaultCertificate();
+  std::string caInfo = "";
+  if (m_config.m_caInfo == "") {
+    caInfo = "Issued by " + cert.getSignature().getKeyLocator().getName().toUri();
+  }
+  else {
+    caInfo = m_config.m_caInfo;
+  }
+  root.put("ca-info", caInfo);
+
+  // probe
+  root.put("probe", m_config.m_probe);
+
+  // certificate
+  std::stringstream ss;
+  io::save(cert, ss);
+  root.put("certificate", ss.str());
+
+  return root;
+}
+
+const JsonSection
+CaModule::genNewResponseJson(const std::string& ecdhKey, const std::string& salt,
+                             const CertificateRequest& request,
+                             const std::list<std::string>& challenges)
+{
+  JsonSection root;
+  JsonSection challengesSection;
+  root.put(JSON_CA_ECDH, ecdhKey);
+  root.put(JSON_CA_SALT, salt);
+  root.put(JSON_CA_EQUEST_ID, request.m_requestId);
+  root.put(JSON_CA_STATUS, std::to_string(request.m_status));
+
+  for (const auto& entry : challenges) {
+    JsonSection challenge;
+    challenge.put(JSON_CA_CHALLENGE_ID, entry);
+    challengesSection.push_back(std::make_pair("", challenge));
+  }
+  root.add_child(JSON_CA_CHALLENGES, challengesSection);
+  return root;
+}
+
+const JsonSection
+CaModule::genChallengeResponseJson(const CertificateRequest& request)
+{
+  JsonSection root;
+  JsonSection challengesSection;
+  root.put(JSON_CA_STATUS, request.m_status);
+  root.put(JSON_CHALLENGE_STATUS, request.m_challengeStatus);
+  root.put(JSON_CHALLENGE_REMAINING_TRIES, std::to_string(request.m_remainingTries));
+  root.put(JSON_CHALLENGE_REMAINING_TIME, std::to_string(request.m_remainingTime));
+  return root;
+}
+
 void
 CaModule::onRegisterFailed(const std::string& reason)
 {
@@ -600,14 +516,14 @@
 }
 
 JsonSection
-CaModule::jsonFromNameComponent(const Name& name, int pos)
+CaModule::jsonFromBlock(const Block& block)
 {
   std::string jsonString;
   try {
-    jsonString = encoding::readString(name.at(pos));
+    jsonString = encoding::readString(block);
   }
   catch (const std::exception& e) {
-    _LOG_ERROR(e.what());
+    _LOG_ERROR("Cannot read JSON string from TLV Value" << e.what());
     return JsonSection();
   }
   std::istringstream ss(jsonString);
diff --git a/src/ca-module.hpp b/src/ca-module.hpp
index 23386a2..47e30c9 100644
--- a/src/ca-module.hpp
+++ b/src/ca-module.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2017-2018, Regents of the University of California.
+ * Copyright (c) 2017-2019, Regents of the University of California.
  *
  * This file is part of ndncert, a certificate management system based on NDN.
  *
@@ -22,8 +22,8 @@
 #define NDNCERT_CA_MODULE_HPP
 
 #include "ca-config.hpp"
+#include "crypto-support/crypto-helper.hpp"
 #include "ca-storage.hpp"
-#include "json-helper.hpp"
 
 namespace ndn {
 namespace ndncert {
@@ -59,50 +59,32 @@
   }
 
   bool
-  setProbeHandler(const Name caName, const ProbeHandler& handler);
+  setProbeHandler(const ProbeHandler& handler);
 
   bool
-  setRecommendCaHandler(const Name caName, const RecommendCaHandler& handler);
-
-  bool
-  setStatusUpdateCallback(const Name caName, const StatusUpdateCallback& onUpateCallback);
+  setStatusUpdateCallback(const StatusUpdateCallback& onUpdateCallback);
 
 PUBLIC_WITH_TESTS_ELSE_PRIVATE:
   void
-  handleLocalhostList(const Interest& query);
+  onProbe(const Interest& request);
 
   void
-  handleList(const Interest& request, const CaItem& caItem);
+  onNew(const Interest& request);
 
   void
-  handleProbe(const Interest& request, const CaItem& caItem);
+  onChallenge(const Interest& request);
 
   void
-  handleNew(const Interest& request, const CaItem& caItem);
-
-  void
-  handleSelect(const Interest& request, const CaItem& caItem);
-
-  void
-  handleValidate(const Interest& request, const CaItem& caItem);
-
-  void
-  handleStatus(const Interest& request, const CaItem& caItem);
-
-  void
-  handleDownload(const Interest& request, const CaItem& caItem);
+  onDownload(const Interest& request);
 
   void
   onRegisterFailed(const std::string& reason);
 
   CertificateRequest
-  getCertificateRequest(const Interest& request, const Name& caName);
+  getCertificateRequest(const Interest& request);
 
   security::v2::Certificate
-  issueCertificate(const CertificateRequest& certRequest, const CaItem& caItem);
-
-  static JsonSection
-  jsonFromNameComponent(const Name& name, int pos);
+  issueCertificate(const CertificateRequest& certRequest);
 
   static Block
   dataContentFromJson(const JsonSection& jsonSection);
@@ -110,14 +92,34 @@
   void
   registerPrefix();
 
+  static JsonSection
+  jsonFromBlock(const Block& block);
+
+PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+  const JsonSection
+  genProbeResponseJson(const Name& identifier);
+
+  const JsonSection
+  genProbeResponseJson();
+
+  const JsonSection
+  genNewResponseJson(const std::string& ecdhKey, const std::string& salt,
+                     const CertificateRequest& request, const std::list<std::string>& challenges);
+
+  const JsonSection
+  genChallengeResponseJson(const CertificateRequest& request);
+
 PUBLIC_WITH_TESTS_ELSE_PRIVATE:
   Face& m_face;
   CaConfig m_config;
   unique_ptr<CaStorage> m_storage;
   security::v2::KeyChain& m_keyChain;
 
-  std::list<const RegisteredPrefixId*> m_registeredPrefixIds;
-  std::list<const InterestFilterId*> m_interestFilterIds;
+  std::list<RegisteredPrefixHandle> m_registeredPrefixHandles;
+  std::list<InterestFilterHandle> m_interestFilterHandles;
+
+  ECDHState m_ecdh;
+  uint8_t m_aesKey[32] = {0};
 };
 
 } // namespace ndncert
diff --git a/src/ca-storage.cpp b/src/ca-storage.cpp
index ebe5c98..fbdae64 100644
--- a/src/ca-storage.cpp
+++ b/src/ca-storage.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2017-2018, Regents of the University of California.
+ * Copyright (c) 2017-2019, Regents of the University of California.
  *
  * This file is part of ndncert, a certificate management system based on NDN.
  *
diff --git a/src/ca-storage.hpp b/src/ca-storage.hpp
index ab5e38d..2cf7b7e 100644
--- a/src/ca-storage.hpp
+++ b/src/ca-storage.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2017-2018, Regents of the University of California.
+ * Copyright (c) 2017-2019, Regents of the University of California.
  *
  * This file is part of ndncert, a certificate management system based on NDN.
  *
diff --git a/src/certificate-request.cpp b/src/certificate-request.cpp
index ac1a216..8db1f6f 100644
--- a/src/certificate-request.cpp
+++ b/src/certificate-request.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2017, Regents of the University of California.
+ * Copyright (c) 2017-2019, Regents of the University of California.
  *
  * This file is part of ndncert, a certificate management system based on NDN.
  *
@@ -26,49 +26,54 @@
 
 CertificateRequest::CertificateRequest() = default;
 
-CertificateRequest::CertificateRequest(const Name& caName,
-                                       const std::string& requestId,
-                                       const security::v2::Certificate& cert)
-  : m_caName(caName)
-  , m_requestId(requestId)
-  , m_cert(static_cast<const Data&>(cert))
-{
-}
-
-CertificateRequest::CertificateRequest(const Name& caName,
-                                       const std::string& requestId,
-                                       const std::string& status,
-                                       const std::string& challengeType,
-                                       const std::string& challengeSecrets,
+CertificateRequest::CertificateRequest(const Name& caName, const std::string& requestId, int status,
                                        const security::v2::Certificate& cert)
   : m_caName(caName)
   , m_requestId(requestId)
   , m_status(status)
-  , m_challengeType(challengeType)
-  , m_cert(static_cast<const Data&>(cert))
+  , m_cert(cert)
 {
-  std::istringstream ss(challengeSecrets);
-  boost::property_tree::json_parser::read_json(ss, m_challengeSecrets);
+}
+
+CertificateRequest::CertificateRequest(const Name& caName, const std::string& requestId, int status,
+                                       const std::string& challengeStatus, const std::string& challengeType,
+                                       const std::string& challengeTp, int remainingTime, int remainingTries,
+                                       const JsonSection& challengeSecrets, const security::v2::Certificate& cert)
+  : m_caName(caName)
+  , m_requestId(requestId)
+  , m_status(status)
+  , m_cert(cert)
+  , m_challengeStatus(challengeStatus)
+  , m_challengeType(challengeType)
+  , m_challengeTp(challengeTp)
+  , m_remainingTime(remainingTime)
+  , m_remainingTries(remainingTries)
+  , m_challengeSecrets(challengeSecrets)
+{
 }
 
 std::ostream&
 operator<<(std::ostream& os, const CertificateRequest& request)
 {
   os << "Request CA name:\n";
-  os << "  " << request.getCaName() << "\n";
+  os << "  " << request.m_caName << "\n";
   os << "Request ID:\n";
-  os << "  " << request.getRequestId() << "\n";
-  if (request.getStatus() != "") {
+  os << "  " << request.m_requestId << "\n";
+  if (request.m_status != -1) {
     os << "Request Status:\n";
-    os << "  " << request.getStatus() << "\n";
+    os << "  " << request.m_status << "\n";
   }
-  if (request.getChallengeType() != "") {
+  if (request.m_challengeStatus != "") {
+    os << "Challenge Status:\n";
+    os << "  " << request.m_challengeStatus << "\n";
+  }
+  if (request.m_challengeType != "") {
     os << "Request Challenge Type:\n";
-    os << "  " << request.getChallengeType() << "\n";
+    os << "  " << request.m_challengeType << "\n";
   }
   os << "Certificate:\n";
   util::IndentedStream os2(os, "  ");
-  os2 << request.getCert();
+  os2 << request.m_cert;
   return os;
 }
 
diff --git a/src/certificate-request.hpp b/src/certificate-request.hpp
index d14b74d..8fa5a0d 100644
--- a/src/certificate-request.hpp
+++ b/src/certificate-request.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2017-2018, Regents of the University of California.
+ * Copyright (c) 2017-2019, Regents of the University of California.
  *
  * This file is part of ndncert, a certificate management system based on NDN.
  *
@@ -40,95 +40,23 @@
 {
 public:
   CertificateRequest();
-
-  CertificateRequest(const Name& caName, const std::string& requestId,
-                     const security::v2::Certificate& cert);
-
-  CertificateRequest(const Name& caName, const std::string& requestId,
-                     const std::string& status, const std::string& challengeType,
-                     const std::string& challengeSecrets,
-                     const security::v2::Certificate& cert);
-
-  const Name&
-  getCaName() const
-  {
-    return m_caName;
-  }
-
-  const std::string&
-  getRequestId() const
-  {
-    return m_requestId;
-  }
-
-  const std::string&
-  getStatus() const
-  {
-    return m_status;
-  }
-
-  const std::string&
-  getChallengeType() const
-  {
-    return m_challengeType;
-  }
-
-  const JsonSection&
-  getChallengeSecrets() const
-  {
-    return m_challengeSecrets;
-  }
-
-  const security::v2::Certificate&
-  getCert() const
-  {
-    return m_cert;
-  }
-
-  void
-  setCert(security::v2::Certificate cert)
-  {
-    m_cert = std::move(cert);
-  }
-
-  void
-  setStatus(const std::string& status)
-  {
-    m_status = status;
-  }
-
-  void
-  setChallengeType(const std::string& challengeType)
-  {
-    m_challengeType = challengeType;
-  }
-
-  void
-  setChallengeSecrets(const JsonSection& challengeSecrets)
-  {
-    m_challengeSecrets = challengeSecrets;
-  }
-
-  bool
-  isEmpty()
-  {
-    return m_requestId == "";
-  }
-
-private:
+  CertificateRequest(const Name& caName, const std::string& requestId, int status, const security::v2::Certificate& cert);
+  CertificateRequest(const Name& caName, const std::string& requestId, int status,
+                     const std::string& challengeStatus, const std::string& challengeType,
+                     const std::string& challengeTp, int remainingTime, int remainingTries,
+                     const JsonSection& challengeSecrets, const security::v2::Certificate& cert);
+public:
   Name m_caName;
-  std::string m_requestId;
-  std::string m_status;
-  std::string m_challengeType;
-
-  /**
-   * @brief Defined by ChallengeModule to store secret information.
-   *
-   * This field will be stored by CA.
-   */
-  JsonSection m_challengeSecrets;
-
+  std::string m_requestId = "";
+  int m_status = -1;
   security::v2::Certificate m_cert;
+
+  std::string m_challengeStatus = "";
+  std::string m_challengeType = "";
+  std::string m_challengeTp = "";
+  int m_remainingTime = 0;
+  int m_remainingTries = 0;
+  JsonSection m_challengeSecrets;
 };
 
 std::ostream&
diff --git a/src/challenge-module.cpp b/src/challenge-module.cpp
index f40e5b9..8c332cb 100644
--- a/src/challenge-module.cpp
+++ b/src/challenge-module.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2017-2018, Regents of the University of California.
+ * Copyright (c) 2017-2019, Regents of the University of California.
  *
  * This file is part of ndncert, a certificate management system based on NDN.
  *
@@ -19,19 +19,11 @@
  */
 
 #include "challenge-module.hpp"
-#include "logging.hpp"
 #include <ndn-cxx/util/random.hpp>
 
 namespace ndn {
 namespace ndncert {
 
-_LOG_INIT(ndncert.challenge-module);
-
-const std::string ChallengeModule::WAIT_SELECTION = "wait-selection";
-const std::string ChallengeModule::SUCCESS = "success";
-const std::string ChallengeModule::PENDING = "pending";
-const std::string ChallengeModule::FAILURE = "failure";
-
 ChallengeModule::ChallengeModule(const std::string& uniqueType)
   : CHALLENGE_TYPE(uniqueType)
 {
@@ -47,89 +39,6 @@
   return i == factory.end() ? nullptr : i->second();
 }
 
-JsonSection
-ChallengeModule::handleChallengeRequest(const Interest& interest, CertificateRequest& request)
-{
-  int pos = request.getCaName().size() + 1;
-  const Name& interestName = interest.getName();
-  std::string interestType = interestName.get(pos).toUri();
-
-  _LOG_TRACE("Incoming challenge request. type: " << interestType);
-
-  if (interestType == "_SELECT") {
-    return processSelectInterest(interest, request);
-  }
-  else if (interestType == "_VALIDATE"){
-    return processValidateInterest(interest, request);
-  }
-  else {
-    return processStatusInterest(interest, request);
-  }
-}
-
-std::list<std::string>
-ChallengeModule::getRequirementForSelect()
-{
-  return getSelectRequirements();
-}
-
-std::list<std::string>
-ChallengeModule::getRequirementForValidate(const std::string& status)
-{
-  return getValidateRequirements(status);
-}
-
-JsonSection
-ChallengeModule::genSelectParamsJson(const std::string& status,
-                                     const std::list<std::string>& paramList)
-{
-  return doGenSelectParamsJson(status, paramList);
-}
-
-JsonSection
-ChallengeModule::genValidateParamsJson(const std::string& status,
-                                     const std::list<std::string>& paramList)
-{
-  return doGenValidateParamsJson(status, paramList);
-}
-
-JsonSection
-ChallengeModule::processStatusInterest(const Interest& interest, const CertificateRequest& request)
-{
-  // interest format: /CA/_STATUS/{"request-id":"id"}/<signature>
-  if (request.getStatus() == SUCCESS) {
-    Name downloadName = genDownloadName(request.getCaName(), request.getStatus());
-    return genResponseChallengeJson(request.getRequestId(), request.getChallengeType(),
-                                    SUCCESS, downloadName);
-  }
-  else
-    return genResponseChallengeJson(request.getRequestId(), request.getChallengeType(),
-                                    request.getStatus());
-}
-
-JsonSection
-ChallengeModule::getJsonFromNameComponent(const Name& name, int pos)
-{
-  std::string jsonString = encoding::readString(name.get(pos));
-  std::istringstream ss(jsonString);
-  JsonSection json;
-  boost::property_tree::json_parser::read_json(ss, json);
-  return json;
-}
-
-Name
-ChallengeModule::genDownloadName(const Name& caName, const std::string& requestId)
-{
-  JsonSection json;
-  json.put(JSON_REQUEST_ID, requestId);
-  std::stringstream ss;
-  boost::property_tree::write_json(ss, json);
-  Block jsonBlock = makeStringBlock(ndn::tlv::GenericNameComponent, ss.str());
-  Name name = caName;
-  name.append("_DOWNLOAD").append(jsonBlock);
-  return name;
-}
-
 ChallengeModule::ChallengeFactory&
 ChallengeModule::getFactory()
 {
@@ -153,5 +62,16 @@
   return result;
 }
 
+void
+ChallengeModule::updateRequestOnChallengeEnd(CertificateRequest& request)
+{
+  request.m_challengeSecrets = JsonSection();
+  request.m_challengeTp = "";
+  request.m_challengeType = "";
+  request.m_remainingTime = 0;
+  request.m_remainingTries = 0;
+}
+
+
 } // namespace ndncert
 } // namespace ndn
diff --git a/src/challenge-module.hpp b/src/challenge-module.hpp
index b2d39ad..ab15be8 100644
--- a/src/challenge-module.hpp
+++ b/src/challenge-module.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2017-2018, Regents of the University of California.
+ * Copyright (c) 2017-2019, Regents of the University of California.
  *
  * This file is part of ndncert, a certificate management system based on NDN.
  *
@@ -23,7 +23,6 @@
 
 #include "ndncert-common.hpp"
 #include "certificate-request.hpp"
-#include "json-helper.hpp"
 
 namespace ndn {
 namespace ndncert {
@@ -63,121 +62,27 @@
   createChallengeModule(const std::string& ChallengeType);
 
   // For CA
-  /**
-   * @brief Handle the challenge related interest and update certificate request.
-   * @note Should be used by CA Module
-   * @note Signature of interest should already be validated by CA Module
-   *
-   * When CA receives a SELECT or a VALIDATE or a STATUS interest, CA should invoke the function
-   * to enable selected challenge to go on the verification process.
-   *
-   * @param interest The request interest packet
-   * @param request The CertificateRequest instance
-   * @return the JSON file as the response data content
-   */
-  JsonSection
-  handleChallengeRequest(const Interest& interest, CertificateRequest& request);
+  virtual void
+  handleChallengeRequest(const JsonSection& params, CertificateRequest& request) = 0;
 
   // For Client
-  /**
-   * @brief Get requirements for requester before sending SELECT interest.
-   * @note Should be used by Client Module
-   *
-   * Before requester sends a USE interest, client should invoke the function to
-   * get input instruction and expose the instruction to requester.
-   *
-   * Every item in the return list requires a input from requester. The item itself is
-   * an instruction for requester.
-   *
-   * @return the input instruction for requester
-   */
-  std::list<std::string>
-  getRequirementForSelect();
-
-  /**
-   * @brief Get requirements for requester before sending VALIDATE interest.
-   * @note Should be used by Client Module
-   *
-   * Before requester sends a POLL interest, client should invoke the function to
-   * get input instruction and expose the instruction to requester.
-   *
-   * Every item in the return list requires a input from requester. The item itself is
-   * an instruction for requester.
-   *
-   * @param status of the challenge
-   * @return the input instruction for requester
-   */
-  std::list<std::string>
-  getRequirementForValidate(const std::string& status);
-
-  /**
-   * @brief Generate ChallengeInfo part for SELECT interest.
-   * @note Should be used by Client Module
-   *
-   * After requester provides required information, client should invoke the function to
-   * generate the ChallengeInfo part of the interest.
-   *
-   * @param status of the challenge
-   * @param paramList contains all the input from requester
-   * @return the JSON file of ChallengeInfo
-   */
-  JsonSection
-  genSelectParamsJson(const std::string& status, const std::list<std::string>& paramList);
-
-  /**
-   * @brief Generate ChallengeInfo part for VALIDATE interest.
-   * @note Should be used by Client Module
-   *
-   * After requester provides required information, client should invoke the function to
-   * generate the ChallengeInfo part of the interest.
-   *
-   * @param status of the challenge
-   * @param paramList contains all the input from requester
-   * @return the JSON file of ChallengeInfo
-   */
-  JsonSection
-  genValidateParamsJson(const std::string& status, const std::list<std::string>& paramList);
-
-PUBLIC_WITH_TESTS_ELSE_PROTECTED:
-  // For CA
   virtual JsonSection
-  processSelectInterest(const Interest& interest, CertificateRequest& request) = 0;
+  getRequirementForChallenge(int status, const std::string& challengeStatus) = 0;
 
   virtual JsonSection
-  processValidateInterest(const Interest& interest, CertificateRequest& request) = 0;
+  genChallengeRequestJson(int status, const std::string& challengeStatus, const JsonSection& params) = 0;
 
-  virtual JsonSection
-  processStatusInterest(const Interest& interest, const CertificateRequest& request);
-
-  // For Client
-  virtual std::list<std::string>
-  getSelectRequirements() = 0;
-
-  virtual std::list<std::string>
-  getValidateRequirements(const std::string& status) = 0;
-
-  virtual JsonSection
-  doGenSelectParamsJson(const std::string& status, const std::list<std::string>& paramList) = 0;
-
-  virtual JsonSection
-  doGenValidateParamsJson(const std::string& status, const std::list<std::string>& paramList) = 0;
-
-  // Helpers
-  static JsonSection
-  getJsonFromNameComponent(const Name& name, int pos);
-
-  static Name
-  genDownloadName(const Name& caName, const std::string& requestId);
-
+  // helpers
   static std::string
   generateSecretCode();
 
+protected:
+
+  void
+  updateRequestOnChallengeEnd(CertificateRequest& request);
+
 public:
   const std::string CHALLENGE_TYPE;
-  static const std::string WAIT_SELECTION;
-  static const std::string SUCCESS;
-  static const std::string PENDING;
-  static const std::string FAILURE;
 
 private:
   typedef function<unique_ptr<ChallengeModule> ()> ChallengeCreateFunc;
diff --git a/src/challenge-module/challenge-credential.cpp b/src/challenge-module/challenge-credential.cpp
index 2a47e7a..839ccae 100644
--- a/src/challenge-module/challenge-credential.cpp
+++ b/src/challenge-module/challenge-credential.cpp
@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2017, Regents of the University of California.
+ * Copyright (c) 2017-2019, Regents of the University of California.
  *
  * This file is part of ndncert, a certificate management system based on NDN.
  *
@@ -29,9 +29,9 @@
 
 NDNCERT_REGISTER_CHALLENGE(ChallengeCredential, "Credential");
 
-const std::string ChallengeCredential::FAILURE_INVALID_FORMAT = "failure-invalid-format";
+const std::string ChallengeCredential::FAILURE_INVALID_FORMAT_CREDENTIAL = "failure-cannot-parse-credential";
+const std::string ChallengeCredential::FAILURE_INVALID_FORMAT_SELF_SIGNED = "failure-cannot-parse-self-signed";
 const std::string ChallengeCredential::FAILURE_INVALID_CREDENTIAL = "failure-invalid-credential";
-
 const std::string ChallengeCredential::JSON_CREDENTIAL_CERT = "issued-cert";
 const std::string ChallengeCredential::JSON_CREDENTIAL_SELF = "self-signed";
 
@@ -71,101 +71,91 @@
   }
 }
 
-JsonSection
-ChallengeCredential::processSelectInterest(const Interest& interest, CertificateRequest& request)
+// For CA
+void
+ChallengeCredential::handleChallengeRequest(const JsonSection& params, CertificateRequest& request)
 {
   if (m_trustAnchors.empty()) {
     parseConfigFile();
   }
-
-  // interest format: /caName/CA/_SELECT/{"request-id":"id"}/CREDENTIAL/{"credential":"..."}/<signature>
-  request.setChallengeType(CHALLENGE_TYPE);
-  JsonSection credentialJson = getJsonFromNameComponent(interest.getName(), request.getCaName().size() + 4);
-
-  // load credential parameters
-  std::istringstream ss1(credentialJson.get<std::string>(JSON_CREDENTIAL_CERT));
+  // load credential parameter
+  std::istringstream ss1(params.get<std::string>(JSON_CREDENTIAL_CERT));
   security::v2::Certificate cert;
   try {
     cert = *(io::load<security::v2::Certificate>(ss1));
   }
   catch (const std::exception& e) {
-    _LOG_TRACE("Cannot load credential parameter: cert" << e.what());
-    request.setStatus(FAILURE_INVALID_FORMAT);
-    return genFailureJson(request.getRequestId(), CHALLENGE_TYPE, FAILURE, FAILURE_INVALID_FORMAT);
+    _LOG_ERROR("Cannot load credential parameter: cert" << e.what());
+    request.m_status = STATUS_FAILURE;
+    request.m_challengeStatus = FAILURE_INVALID_FORMAT_CREDENTIAL;
+    updateRequestOnChallengeEnd(request);
+    return;
   }
   ss1.str("");
   ss1.clear();
-
-  std::istringstream ss2(credentialJson.get<std::string>(JSON_CREDENTIAL_SELF));
-  security::v2::Certificate self;
+  // load self-signed data
+  std::istringstream ss2(params.get<std::string>(JSON_CREDENTIAL_SELF));
+  Data self;
   try {
-    self = *(io::load<security::v2::Certificate>(ss2));
+    self = *(io::load<Data>(ss2));
   }
   catch (const std::exception& e) {
     _LOG_TRACE("Cannot load credential parameter: self-signed cert" << e.what());
-    request.setStatus(FAILURE_INVALID_FORMAT);
-    return genFailureJson(request.getRequestId(), CHALLENGE_TYPE, FAILURE, FAILURE_INVALID_FORMAT);
+    request.m_status = STATUS_FAILURE;
+    request.m_challengeStatus = FAILURE_INVALID_FORMAT_SELF_SIGNED;
+    updateRequestOnChallengeEnd(request);
+    return;
   }
   ss2.str("");
   ss2.clear();
 
-  // verify two parameters
+  // verify the credential and the self-signed cert
   Name signingKeyName = cert.getSignature().getKeyLocator().getName();
   for (auto anchor : m_trustAnchors) {
     if (anchor.getKeyName() == signingKeyName) {
-      if (security::verifySignature(cert, anchor) && security::verifySignature(self, cert)) {
-        request.setStatus(SUCCESS);
-        return genResponseChallengeJson(request.getRequestId(), CHALLENGE_TYPE, SUCCESS);
+      if (security::verifySignature(cert, anchor) && security::verifySignature(self, cert)
+          && readString(self.getContent()) == request.m_requestId) {
+        request.m_status = STATUS_PENDING;
+        request.m_challengeStatus = CHALLENGE_STATUS_SUCCESS;
+        updateRequestOnChallengeEnd(request);
+        return;
       }
     }
   }
 
-  request.setStatus(FAILURE_INVALID_CREDENTIAL);
-  return genResponseChallengeJson(request.getRequestId(), CHALLENGE_TYPE, FAILURE_INVALID_CREDENTIAL);
+  _LOG_TRACE("Cannot verify the credential + self-signed Data + data content");
+  request.m_status = STATUS_FAILURE;
+  request.m_challengeStatus = FAILURE_INVALID_CREDENTIAL;
+  updateRequestOnChallengeEnd(request);
+  return;
 }
 
+// For Client
 JsonSection
-ChallengeCredential::processValidateInterest(const Interest& interest, CertificateRequest& request)
-{
-  // there is no validate request here, do nothing
-  return genFailureJson(request.getRequestId(), CHALLENGE_TYPE, FAILURE, FAILURE_INVALID_FORMAT);
-}
-
-std::list<std::string>
-ChallengeCredential::getSelectRequirements()
-{
-  std::list<std::string> result;
-  result.push_back("Please input the bytes of a certificate issued by the trusted CA");
-  result.push_back("Please input the bytes of a self-signed certificate for the corresponding key");
-  return result;
-}
-
-std::list<std::string>
-ChallengeCredential::getValidateRequirements(const std::string& status)
-{
-  // there is no validate request here, do nothing
-  std::list<std::string> result;
-  return result;
-}
-
-JsonSection
-ChallengeCredential::doGenSelectParamsJson(const std::string& status,
-                                           const std::list<std::string>& paramList)
+ChallengeCredential::getRequirementForChallenge(int status, const std::string& challengeStatus)
 {
   JsonSection result;
-  BOOST_ASSERT(status == WAIT_SELECTION);
-  BOOST_ASSERT(paramList.size() == 2);
-  result.put(JSON_CREDENTIAL_CERT, paramList.front());
-  result.put(JSON_CREDENTIAL_SELF, paramList.back());
+  if (status == STATUS_BEFORE_CHALLENGE && challengeStatus == "") {
+    result.put(JSON_CREDENTIAL_CERT, "Please_copy_anchor_signed_cert_here");
+    result.put(JSON_CREDENTIAL_SELF, "Please_copy_key_signed_request_id_data_here");
+  }
+  else {
+    _LOG_ERROR("Client's status and challenge status are wrong");
+  }
   return result;
 }
 
 JsonSection
-ChallengeCredential::doGenValidateParamsJson(const std::string& status,
-                                             const std::list<std::string>& paramList)
+ChallengeCredential::genChallengeRequestJson(int status, const std::string& challengeStatus, const JsonSection& params)
 {
   JsonSection result;
-  BOOST_ASSERT(paramList.size() == 0);
+  if (status == STATUS_BEFORE_CHALLENGE && challengeStatus == "") {
+    result.put(JSON_CREDENTIAL_CERT, params.get<std::string>(JSON_CREDENTIAL_CERT, ""));
+    result.put(JSON_CREDENTIAL_SELF, params.get<std::string>(JSON_CREDENTIAL_SELF, ""));
+  }
+  else {
+    _LOG_ERROR("Client's status and challenge status are wrong");
+  }
   return result;
 }
 
diff --git a/src/challenge-module/challenge-credential.hpp b/src/challenge-module/challenge-credential.hpp
index bcd8b5d..5d4a93c 100644
--- a/src/challenge-module/challenge-credential.hpp
+++ b/src/challenge-module/challenge-credential.hpp
@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2017, Regents of the University of California.
+ * Copyright (c) 2017-2019, Regents of the University of California.
  *
  * This file is part of ndncert, a certificate management system based on NDN.
  *
@@ -29,16 +29,17 @@
  * @brief Provide Credential based challenge
  *
  * Credential here means the certificate issued by a trust anchor. Once the requester
- * could proof his/her possession of an existing certificate from other certificate, th
- * requester could finish the challenge.
+ * could proof his/her possession of an existing certificate from other certificate issuer,
+ * the requester could finish the challenge.
  *
  * The requester needs to provide the proof of the possession of a certificate issued by
  * a trust anchor. The challenge require the requester to pass the BASE64 certificate and
- * a BASE64 self-signed certificate whose key is the same as the key in certificate.
+ * a BASE64 Data packet signed by the credential pub key and whose content is the request id.
  *
  * The main process of this challenge module is:
  *   1. Requester provides a certificate signed by that trusted certificate as credential.
  *   2. The challenge module will verify the signature of the credential.
+ *   3. The content of the signed Data is the request id
  *
  * Failure info when application fails:
  *   FAILURE_INVALID_CREDENTIAL: When the cert issued from trust anchor or self-signed cert
@@ -50,26 +51,16 @@
 public:
   ChallengeCredential(const std::string& configPath = "");
 
-PUBLIC_WITH_TESTS_ELSE_PROTECTED:
+  // For CA
+  void
+  handleChallengeRequest(const JsonSection& params, CertificateRequest& request) override;
+
+  // For Client
   JsonSection
-  processSelectInterest(const Interest& interest, CertificateRequest& request) override;
+  getRequirementForChallenge(int status, const std::string& challengeStatus) override;
 
   JsonSection
-  processValidateInterest(const Interest& interest, CertificateRequest& request) override;
-
-  std::list<std::string>
-  getSelectRequirements() override;
-
-  std::list<std::string>
-  getValidateRequirements(const std::string& status) override;
-
-  JsonSection
-  doGenSelectParamsJson(const std::string& status,
-                        const std::list<std::string>& paramList) override;
-
-  JsonSection
-  doGenValidateParamsJson(const std::string& status,
-                          const std::list<std::string>& paramList) override;
+  genChallengeRequestJson(int status, const std::string& challengeStatus, const JsonSection& params) override;
 
 PUBLIC_WITH_TESTS_ELSE_PRIVATE:
   void
@@ -77,8 +68,8 @@
 
 PUBLIC_WITH_TESTS_ELSE_PRIVATE:
   static const std::string FAILURE_INVALID_CREDENTIAL;
-  static const std::string FAILURE_INVALID_FORMAT;
-
+  static const std::string FAILURE_INVALID_FORMAT_CREDENTIAL;
+  static const std::string FAILURE_INVALID_FORMAT_SELF_SIGNED;
   static const std::string JSON_CREDENTIAL_CERT;
   static const std::string JSON_CREDENTIAL_SELF;
 
diff --git a/src/challenge-module/challenge-email.cpp b/src/challenge-module/challenge-email.cpp
index b65d331..dbd3b2c 100644
--- a/src/challenge-module/challenge-email.cpp
+++ b/src/challenge-module/challenge-email.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2017-2018, Regents of the University of California.
+ * Copyright (c) 2017-2019, Regents of the University of California.
  *
  * This file is part of ndncert, a certificate management system based on NDN.
  *
@@ -31,15 +31,9 @@
 
 const std::string ChallengeEmail::NEED_CODE = "need-code";
 const std::string ChallengeEmail::WRONG_CODE = "wrong-code";
-
 const std::string ChallengeEmail::FAILURE_INVALID_EMAIL = "failure-invalid-email";
-const std::string ChallengeEmail::FAILURE_TIMEOUT = "timeout";
-const std::string ChallengeEmail::FAILURE_MAXRETRY = "max-retry";
-
 const std::string ChallengeEmail::JSON_EMAIL = "email";
-const std::string ChallengeEmail::JSON_CODE_TP = "code-timepoint";
 const std::string ChallengeEmail::JSON_CODE = "code";
-const std::string ChallengeEmail::JSON_ATTEMPT_TIMES = "attempt-times";
 
 ChallengeEmail::ChallengeEmail(const std::string& scriptPath,
                                const size_t& maxAttemptTimes,
@@ -51,131 +45,123 @@
 {
 }
 
-JsonSection
-ChallengeEmail::processSelectInterest(const Interest& interest, CertificateRequest& request)
+// For CA
+void
+ChallengeEmail::handleChallengeRequest(const JsonSection& params, CertificateRequest& request)
 {
-  // interest format: /caName/CA/_SELECT/{"request-id":"id"}/EMAIL/{"Email":"email"}/<signature>
-  JsonSection emailJson = getJsonFromNameComponent(interest.getName(),
-                                                   request.getCaName().size() + 4);
-  std::string emailAddress = emailJson.get<std::string>(JSON_EMAIL);
-  if (!isValidEmailAddress(emailAddress)) {
-    request.setStatus(FAILURE_INVALID_EMAIL);
-    request.setChallengeType(CHALLENGE_TYPE);
-    return genFailureJson(request.getRequestId(), CHALLENGE_TYPE, FAILURE, FAILURE_INVALID_EMAIL);
+  if (request.m_challengeStatus == "") {
+    // for the first time, init the challenge
+    std::string emailAddress = params.get<std::string>(JSON_EMAIL);
+    if (!isValidEmailAddress(emailAddress)) {
+      request.m_status = STATUS_FAILURE;
+      request.m_challengeStatus = FAILURE_INVALID_EMAIL;
+      return;
+    }
+    request.m_status = STATUS_CHALLENGE;
+    request.m_challengeStatus = NEED_CODE;
+    request.m_challengeType = CHALLENGE_TYPE;
+    std::string emailCode = generateSecretCode();
+    JsonSection secretJson;
+    secretJson.add(JSON_CODE, emailCode);
+    request.m_challengeSecrets = secretJson;
+    request.m_challengeTp = time::toIsoString(time::system_clock::now());
+    request.m_remainingTime = m_secretLifetime.count();
+    request.m_remainingTries = m_maxAttemptTimes;
+    // send out the email
+    sendEmail(emailAddress, emailCode, request);
+    _LOG_TRACE("Secret for request " << request.m_requestId << " : " << emailCode);
+    return;
   }
-
-  std::string emailCode = generateSecretCode();
-  sendEmail(emailAddress, emailCode, request.getCaName().toUri());
-
-  request.setStatus(NEED_CODE);
-  request.setChallengeType(CHALLENGE_TYPE);
-  request.setChallengeSecrets(generateStoredSecrets(time::system_clock::now(),
-                                                    emailCode, m_maxAttemptTimes));
-  return genResponseChallengeJson(request.getRequestId(), CHALLENGE_TYPE, NEED_CODE);
-}
-
-JsonSection
-ChallengeEmail::processValidateInterest(const Interest& interest, CertificateRequest& request)
-{
-  // interest format: /caName/CA/_VALIDATION/{"request-id":"id"}/EMAIL/{"code":"code"}/<signature>
-  JsonSection infoJson = getJsonFromNameComponent(interest.getName(), request.getCaName().size() + 4);
-  std::string givenCode = infoJson.get<std::string>(JSON_CODE);
-
-  const auto parsedSecret = parseStoredSecrets(request.getChallengeSecrets());
-  if (time::system_clock::now() - std::get<0>(parsedSecret) >= m_secretLifetime) {
-    // secret expires
-    request.setStatus(FAILURE_TIMEOUT);
-    request.setChallengeSecrets(JsonSection());
-    return genFailureJson(request.getRequestId(), CHALLENGE_TYPE, FAILURE, FAILURE_TIMEOUT);
-  }
-  else if (givenCode == std::get<1>(parsedSecret)) {
-    request.setStatus(SUCCESS);
-    request.setChallengeSecrets(JsonSection());
-    Name downloadName = genDownloadName(request.getCaName(), request.getRequestId());
-    return genResponseChallengeJson(request.getRequestId(), CHALLENGE_TYPE, SUCCESS, downloadName);
-  }
-  else {
-    // check rest attempt times
-    if (std::get<2>(parsedSecret) > 1) {
-      int restAttemptTimes = std::get<2>(parsedSecret) - 1;
-      request.setStatus(WRONG_CODE);
-      request.setChallengeSecrets(generateStoredSecrets(std::get<0>(parsedSecret),
-                                                        std::get<1>(parsedSecret),
-                                                        restAttemptTimes));
-      return genResponseChallengeJson(request.getRequestId(), CHALLENGE_TYPE, WRONG_CODE);
+  else if (request.m_challengeStatus == NEED_CODE || request.m_challengeStatus == WRONG_CODE) {
+    _LOG_TRACE("Challenge Interest arrives. Challenge Status: " << request.m_challengeStatus);
+    // the incoming interest should bring the pin code
+    std::string givenCode = params.get<std::string>(JSON_CODE);
+    const auto realCode = request.m_challengeSecrets.get<std::string>(JSON_CODE);
+    if (time::system_clock::now() - time::fromIsoString(request.m_challengeTp) >= m_secretLifetime) {
+      // secret expires
+      request.m_status = STATUS_FAILURE;
+      request.m_challengeStatus = CHALLENGE_STATUS_FAILURE_TIMEOUT;
+      updateRequestOnChallengeEnd(request);
+      _LOG_TRACE("Secret expired. Challenge failed.");
+      return;
+    }
+    else if (givenCode == realCode) {
+      // the code is correct
+      request.m_status = STATUS_PENDING;
+      request.m_challengeStatus = CHALLENGE_STATUS_SUCCESS;
+      updateRequestOnChallengeEnd(request);
+      _LOG_TRACE("Secret code matched. Challenge succeeded.");
+      return;
     }
     else {
-      // run out times
-      request.setStatus(FAILURE_MAXRETRY);
-      request.setChallengeSecrets(JsonSection());
-      return genFailureJson(request.getRequestId(), CHALLENGE_TYPE, FAILURE, FAILURE_MAXRETRY);
+      // check rest attempt times
+      if (request.m_remainingTries > 1) {
+        request.m_challengeStatus = WRONG_CODE;
+        request.m_remainingTries = request.m_remainingTries - 1;
+        auto remainTime = m_secretLifetime - (time::system_clock::now() - time::fromIsoString(request.m_challengeTp));
+        request.m_remainingTime = remainTime.count();
+        _LOG_TRACE("Secret code didn't match. Remaining Tries - 1.");
+        return;
+      }
+      else {
+        // run out times
+        request.m_status = STATUS_FAILURE;
+        request.m_challengeStatus = CHALLENGE_STATUS_FAILURE_MAXRETRY;
+        updateRequestOnChallengeEnd(request);
+        _LOG_TRACE("Secret code didn't match. Ran out tires. Challenge failed.");
+        return;
+      }
     }
   }
-}
-
-std::list<std::string>
-ChallengeEmail::getSelectRequirements()
-{
-  std::list<std::string> result;
-  result.push_back("Please input your email address:");
-  return result;
-}
-
-std::list<std::string>
-ChallengeEmail::getValidateRequirements(const std::string& status)
-{
-  std::list<std::string> result;
-  if (status == NEED_CODE) {
-    result.push_back("Please input your verification code:");
+  else {
+    _LOG_ERROR("The challenge status is wrong");
+    request.m_status = STATUS_FAILURE;
+    return;
   }
-  else if (status == WRONG_CODE) {
-    result.push_back("Incorrect PIN code, please try again and input your verification code:");
+}
+
+// For Client
+JsonSection
+ChallengeEmail::getRequirementForChallenge(int status, const std::string& challengeStatus)
+{
+  JsonSection result;
+  if (status == STATUS_BEFORE_CHALLENGE && challengeStatus == "") {
+    result.put(JSON_EMAIL, "Please_input_your_email_address");
+  }
+  else if (status == STATUS_CHALLENGE && challengeStatus == NEED_CODE) {
+    result.put(JSON_CODE, "Please_input_your_verification_code");
+  }
+  else if (status == STATUS_CHALLENGE && challengeStatus == WRONG_CODE) {
+    result.put(JSON_CODE, "Incorrect_code_please_try_again");
+  }
+  else {
+    _LOG_ERROR("CA's status and challenge status are wrong");
   }
   return result;
 }
 
 JsonSection
-ChallengeEmail::doGenSelectParamsJson(const std::string& status,
-                                      const std::list<std::string>& paramList)
+ChallengeEmail::genChallengeRequestJson(int status, const std::string& challengeStatus, const JsonSection& params)
 {
   JsonSection result;
-  BOOST_ASSERT(status == WAIT_SELECTION);
-  BOOST_ASSERT(paramList.size() == 1);
-  result.put(JSON_EMAIL, paramList.front());
+  if (status == STATUS_BEFORE_CHALLENGE && challengeStatus == "") {
+    result.put(JSON_CLIENT_SELECTED_CHALLENGE, CHALLENGE_TYPE);
+    result.put(JSON_EMAIL, params.get<std::string>(JSON_EMAIL, ""));
+  }
+  else if (status == STATUS_CHALLENGE && challengeStatus == NEED_CODE) {
+    result.put(JSON_CLIENT_SELECTED_CHALLENGE, CHALLENGE_TYPE);
+    result.put(JSON_CODE, params.get<std::string>(JSON_CODE, ""));
+  }
+  else if (status == STATUS_CHALLENGE && challengeStatus == WRONG_CODE) {
+    result.put(JSON_CLIENT_SELECTED_CHALLENGE, CHALLENGE_TYPE);
+    result.put(JSON_CODE, params.get<std::string>(JSON_CODE, ""));
+  }
+  else {
+    _LOG_ERROR("Client's status and challenge status are wrong");
+  }
   return result;
 }
 
-JsonSection
-ChallengeEmail::doGenValidateParamsJson(const std::string& status,
-                                        const std::list<std::string>& paramList)
-{
-  JsonSection result;
-  BOOST_ASSERT(paramList.size() == 1);
-  result.put(JSON_CODE, paramList.front());
-  return result;
-}
-
-std::tuple<time::system_clock::TimePoint, std::string, int>
-ChallengeEmail::parseStoredSecrets(const JsonSection& storedSecrets)
-{
-  auto tp = time::fromIsoString(storedSecrets.get<std::string>(JSON_CODE_TP));
-  std::string rightCode= storedSecrets.get<std::string>(JSON_CODE);
-  int attemptTimes = std::stoi(storedSecrets.get<std::string>(JSON_ATTEMPT_TIMES));
-
-  return std::make_tuple(tp, rightCode, attemptTimes);
-}
-
-JsonSection
-ChallengeEmail::generateStoredSecrets(const time::system_clock::TimePoint& tp,
-                                    const std::string& secretCode, int attempTimes)
-{
-  JsonSection json;
-  json.put(JSON_CODE_TP, time::toIsoString(tp));
-  json.put(JSON_CODE, secretCode);
-  json.put(JSON_ATTEMPT_TIMES, std::to_string(attempTimes));
-  return json;
-}
-
 bool
 ChallengeEmail::isValidEmailAddress(const std::string& emailAddress)
 {
@@ -186,10 +172,10 @@
 
 void
 ChallengeEmail::sendEmail(const std::string& emailAddress, const std::string& secret,
-                          const std::string& caName) const
+                          const CertificateRequest& request) const
 {
   std::string command = m_sendEmailScript;
-  command += " \"" + emailAddress + "\" \"" + secret + "\" \"" + caName + "\"";
+  command += " \"" + emailAddress + "\" \"" + secret + "\" \"" + request.m_caName.toUri() + "\"";
   int result = system(command.c_str());
   if (result == -1) {
     _LOG_TRACE("EmailSending Script " + m_sendEmailScript + " fails.");
diff --git a/src/challenge-module/challenge-email.hpp b/src/challenge-module/challenge-email.hpp
index a014d00..2f40f1f 100644
--- a/src/challenge-module/challenge-email.hpp
+++ b/src/challenge-module/challenge-email.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2017, Regents of the University of California.
+ * Copyright (c) 2017-2019, Regents of the University of California.
  *
  * This file is part of ndncert, a certificate management system based on NDN.
  *
@@ -56,26 +56,16 @@
                  const size_t& maxAttemptTimes = 3,
                  const time::seconds secretLifetime = time::minutes(20));
 
-PUBLIC_WITH_TESTS_ELSE_PROTECTED:
+  // For CA
+  void
+  handleChallengeRequest(const JsonSection& params, CertificateRequest& request) override;
+
+  // For Client
   JsonSection
-  processSelectInterest(const Interest& interest, CertificateRequest& request) override;
+  getRequirementForChallenge(int status, const std::string& challengeStatus) override;
 
   JsonSection
-  processValidateInterest(const Interest& interest, CertificateRequest& request) override;
-
-  std::list<std::string>
-  getSelectRequirements() override;
-
-  std::list<std::string>
-  getValidateRequirements(const std::string& status) override;
-
-  JsonSection
-  doGenSelectParamsJson(const std::string& status,
-                        const std::list<std::string>& paramList) override;
-
-  JsonSection
-  doGenValidateParamsJson(const std::string& status,
-                          const std::list<std::string>& paramList) override;
+  genChallengeRequestJson(int status, const std::string& challengeStatus, const JsonSection& params) override;
 
 PUBLIC_WITH_TESTS_ELSE_PRIVATE:
   static bool
@@ -83,28 +73,16 @@
 
   void
   sendEmail(const std::string& emailAddress, const std::string& secret,
-            const std::string& caName) const;
+            const CertificateRequest& request) const;
 
 PUBLIC_WITH_TESTS_ELSE_PRIVATE:
-  static std::tuple<time::system_clock::TimePoint, std::string, int>
-  parseStoredSecrets(const JsonSection& storedSecret);
-
-  static JsonSection
-  generateStoredSecrets(const time::system_clock::TimePoint& tp, const std::string& secretCode,
-                        int attempTimes);
-
-PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+  // challenge status
   static const std::string NEED_CODE;
   static const std::string WRONG_CODE;
-
-  static const std::string FAILURE_TIMEOUT;
   static const std::string FAILURE_INVALID_EMAIL;
-  static const std::string FAILURE_MAXRETRY;
-
+  // JSON attribute
   static const std::string JSON_EMAIL;
-  static const std::string JSON_CODE_TP;
   static const std::string JSON_CODE;
-  static const std::string JSON_ATTEMPT_TIMES;
 
 private:
   std::string m_sendEmailScript;
diff --git a/src/challenge-module/challenge-pin.cpp b/src/challenge-module/challenge-pin.cpp
index 2bb81ea..7de32f0 100644
--- a/src/challenge-module/challenge-pin.cpp
+++ b/src/challenge-module/challenge-pin.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2017, Regents of the University of California.
+ * Copyright (c) 2017-2019, Regents of the University of California.
  *
  * This file is part of ndncert, a certificate management system based on NDN.
  *
@@ -20,7 +20,6 @@
 
 #include "challenge-pin.hpp"
 #include "logging.hpp"
-#include "json-helper.hpp"
 #include <ndn-cxx/util/random.hpp>
 
 namespace ndn {
@@ -32,13 +31,7 @@
 
 const std::string ChallengePin::NEED_CODE = "need-code";
 const std::string ChallengePin::WRONG_CODE = "wrong-code";
-
-const std::string ChallengePin::FAILURE_TIMEOUT = "failure-timeout";
-const std::string ChallengePin::FAILURE_MAXRETRY = "failure-max-retry";
-
-const std::string ChallengePin::JSON_CODE_TP = "code-timepoint";
-const std::string ChallengePin::JSON_PIN_CODE = "code";
-const std::string ChallengePin::JSON_ATTEMPT_TIMES = "attempt-times";
+const std::string ChallengePin::JSON_PIN_CODE = "pin-code";
 
 ChallengePin::ChallengePin(const size_t& maxAttemptTimes, const time::seconds& secretLifetime)
   : ChallengeModule("PIN")
@@ -47,119 +40,115 @@
 {
 }
 
-JsonSection
-ChallengePin::processSelectInterest(const Interest& interest, CertificateRequest& request)
+// For CA
+void
+ChallengePin::handleChallengeRequest(const JsonSection& params, CertificateRequest& request)
 {
-  // interest format: /caName/CA/_SELECT/{"request-id":"id"}/PIN/<signature>
-  request.setStatus(NEED_CODE);
-  request.setChallengeType(CHALLENGE_TYPE);
-  std::string secretCode = generateSecretCode();
-  request.setChallengeSecrets(generateStoredSecrets(time::system_clock::now(),
-                                                    secretCode,
-                                                    m_maxAttemptTimes));
-  _LOG_TRACE("Secret for request " << request.getRequestId() << " : " << secretCode);
-  return genResponseChallengeJson(request.getRequestId(), CHALLENGE_TYPE, NEED_CODE);
-}
-
-JsonSection
-ChallengePin::processValidateInterest(const Interest& interest, CertificateRequest& request)
-{
-  // interest format: /caName/CA/_VALIDATION/{"request-id":"id"}/PIN/{"code":"code"}/<signature>
-  JsonSection infoJson = getJsonFromNameComponent(interest.getName(), request.getCaName().size() + 4);
-  std::string givenCode = infoJson.get<std::string>(JSON_PIN_CODE);
-
-  const auto parsedSecret = parseStoredSecrets(request.getChallengeSecrets());
-  if (time::system_clock::now() - std::get<0>(parsedSecret) >= m_secretLifetime) {
-    // secret expires
-    request.setStatus(FAILURE_TIMEOUT);
-    request.setChallengeSecrets(JsonSection());
-    return genFailureJson(request.getRequestId(), CHALLENGE_TYPE, FAILURE, FAILURE_TIMEOUT);
+  if (request.m_challengeStatus == "") {
+    _LOG_TRACE("Challenge Interest arrives. Init the challenge");
+    // for the first time, init the challenge
+    request.m_status = STATUS_CHALLENGE;
+    request.m_challengeStatus = NEED_CODE;
+    request.m_challengeType = CHALLENGE_TYPE;
+    std::string secretCode = generateSecretCode();
+    JsonSection secretJson;
+    secretJson.add(JSON_PIN_CODE, secretCode);
+    request.m_challengeSecrets = secretJson;
+    request.m_challengeTp = time::toIsoString(time::system_clock::now());
+    request.m_remainingTime = m_secretLifetime.count();
+    request.m_remainingTries = m_maxAttemptTimes;
+    _LOG_TRACE("Secret for request " << request.m_requestId << " : " << secretCode);
+    return;
   }
-  else if (givenCode == std::get<1>(parsedSecret)) {
-    request.setStatus(SUCCESS);
-    request.setChallengeSecrets(JsonSection());
-    Name downloadName = genDownloadName(request.getCaName(), request.getRequestId());
-    return genResponseChallengeJson(request.getRequestId(), CHALLENGE_TYPE, SUCCESS, downloadName);
-  }
-  else {
-    // check rest attempt times
-    if (std::get<2>(parsedSecret) > 1) {
-      int restAttemptTimes = std::get<2>(parsedSecret) - 1;
-      request.setStatus(WRONG_CODE);
-      request.setChallengeSecrets(generateStoredSecrets(std::get<0>(parsedSecret),
-                                                        std::get<1>(parsedSecret),
-                                                        restAttemptTimes));
-      return genResponseChallengeJson(request.getRequestId(), CHALLENGE_TYPE, WRONG_CODE);
+  else if (request.m_challengeStatus == NEED_CODE || request.m_challengeStatus == WRONG_CODE) {
+    _LOG_TRACE("Challenge Interest arrives. Challenge Status: " << request.m_challengeStatus);
+    // the incoming interest should bring the pin code
+    std::string givenCode = params.get<std::string>(JSON_PIN_CODE);
+    const auto realCode = request.m_challengeSecrets.get<std::string>(JSON_PIN_CODE);
+    if (time::system_clock::now() - time::fromIsoString(request.m_challengeTp) >= m_secretLifetime) {
+      // secret expires
+      request.m_status = STATUS_FAILURE;
+      request.m_challengeStatus = CHALLENGE_STATUS_FAILURE_TIMEOUT;
+      updateRequestOnChallengeEnd(request);
+      _LOG_TRACE("Secret expired. Challenge failed.");
+      return;
+    }
+    else if (givenCode == realCode) {
+      // the code is correct
+      request.m_status = STATUS_PENDING;
+      request.m_challengeStatus = CHALLENGE_STATUS_SUCCESS;
+      updateRequestOnChallengeEnd(request);
+      _LOG_TRACE("PIN code matched. Challenge succeeded.");
+      return;
     }
     else {
-      // run out times
-      request.setStatus(FAILURE_MAXRETRY);
-      request.setChallengeSecrets(JsonSection());
-      return genFailureJson(request.getRequestId(), CHALLENGE_TYPE, FAILURE, FAILURE_MAXRETRY);
+      // check rest attempt times
+      if (request.m_remainingTries > 1) {
+        request.m_challengeStatus = WRONG_CODE;
+        request.m_remainingTries = request.m_remainingTries - 1;
+        auto remainTime = m_secretLifetime - (time::system_clock::now() - time::fromIsoString(request.m_challengeTp));
+        request.m_remainingTime = remainTime.count();
+        _LOG_TRACE("PIN code didn't match. Remaining Tries - 1.");
+        return;
+      }
+      else {
+        // run out times
+        request.m_status = STATUS_FAILURE;
+        request.m_challengeStatus = CHALLENGE_STATUS_FAILURE_MAXRETRY;
+        updateRequestOnChallengeEnd(request);
+        _LOG_TRACE("PIN code didn't match. Ran out tires. Challenge failed.");
+        return;
+      }
     }
   }
-}
-
-std::list<std::string>
-ChallengePin::getSelectRequirements()
-{
-  std::list<std::string> result;
-  return result;
-}
-
-std::list<std::string>
-ChallengePin::getValidateRequirements(const std::string& status)
-{
-  std::list<std::string> result;
-  if (status == NEED_CODE) {
-    result.push_back("Please input your verification code:");
+  else {
+    _LOG_ERROR("The challenge status is wrong");
+    request.m_status = STATUS_FAILURE;
+    return;
   }
-  else if (status == WRONG_CODE) {
-    result.push_back("Incorrect PIN code, please try again and input your verification code:");
+}
+
+// For Client
+JsonSection
+ChallengePin::getRequirementForChallenge(int status, const std::string& challengeStatus)
+{
+  JsonSection result;
+  if (status == STATUS_BEFORE_CHALLENGE && challengeStatus == "") {
+    // do nothing
+  }
+  else if (status == STATUS_CHALLENGE && challengeStatus == NEED_CODE) {
+    result.put(JSON_PIN_CODE, "Please_input_your_verification_code");
+  }
+  else if (status == STATUS_CHALLENGE && challengeStatus == WRONG_CODE) {
+    result.put(JSON_PIN_CODE, "Incorrect_PIN_code_please_try_again");
+  }
+  else {
+    _LOG_ERROR("Client's status and challenge status are wrong");
   }
   return result;
 }
 
 JsonSection
-ChallengePin::doGenSelectParamsJson(const std::string& status,
-                                    const std::list<std::string>& paramList)
+ChallengePin::genChallengeRequestJson(int status, const std::string& challengeStatus, const JsonSection& params)
 {
   JsonSection result;
-  BOOST_ASSERT(status == WAIT_SELECTION);
-  BOOST_ASSERT(paramList.size() == 0);
+  if (status == STATUS_BEFORE_CHALLENGE && challengeStatus == "") {
+    // do nothing
+    result.put(JSON_CLIENT_SELECTED_CHALLENGE, CHALLENGE_TYPE);
+  }
+  else if (status == STATUS_CHALLENGE && challengeStatus == NEED_CODE) {
+    result.put(JSON_CLIENT_SELECTED_CHALLENGE, CHALLENGE_TYPE);
+    result.put(JSON_PIN_CODE, params.get<std::string>(JSON_PIN_CODE, ""));
+  }
+  else if (status == STATUS_CHALLENGE && challengeStatus == WRONG_CODE) {
+    result.put(JSON_CLIENT_SELECTED_CHALLENGE, CHALLENGE_TYPE);
+    result.put(JSON_PIN_CODE, params.get<std::string>(JSON_PIN_CODE, ""));
+  }
+  else {
+    _LOG_ERROR("Client's status and challenge status are wrong");
+  }
   return result;
 }
 
-JsonSection
-ChallengePin::doGenValidateParamsJson(const std::string& status,
-                                      const std::list<std::string>& paramList)
-{
-  JsonSection result;
-  BOOST_ASSERT(paramList.size() == 1);
-  result.put(JSON_PIN_CODE, paramList.front());
-  return result;
-}
-
-std::tuple<time::system_clock::TimePoint, std::string, int>
-ChallengePin::parseStoredSecrets(const JsonSection& storedSecrets)
-{
-  auto tp = time::fromIsoString(storedSecrets.get<std::string>(JSON_CODE_TP));
-  std::string rightCode= storedSecrets.get<std::string>(JSON_PIN_CODE);
-  int attemptTimes = std::stoi(storedSecrets.get<std::string>(JSON_ATTEMPT_TIMES));
-
-  return std::make_tuple(tp, rightCode, attemptTimes);
-}
-
-JsonSection
-ChallengePin::generateStoredSecrets(const time::system_clock::TimePoint& tp,
-                                    const std::string& secretCode, int attempTimes)
-{
-  JsonSection json;
-  json.put(JSON_CODE_TP, time::toIsoString(tp));
-  json.put(JSON_PIN_CODE, secretCode);
-  json.put(JSON_ATTEMPT_TIMES, std::to_string(attempTimes));
-  return json;
-}
-
 } // namespace ndncert
 } // namespace ndn
diff --git a/src/challenge-module/challenge-pin.hpp b/src/challenge-module/challenge-pin.hpp
index 982ed4e..8eef944 100644
--- a/src/challenge-module/challenge-pin.hpp
+++ b/src/challenge-module/challenge-pin.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2017, Regents of the University of California.
+ * Copyright (c) 2017-2019, Regents of the University of California.
  *
  * This file is part of ndncert, a certificate management system based on NDN.
  *
@@ -51,45 +51,23 @@
   ChallengePin(const size_t& maxAttemptTimes = 3,
                const time::seconds& secretLifetime = time::seconds(3600));
 
-PUBLIC_WITH_TESTS_ELSE_PROTECTED:
+  // For CA
+  void
+  handleChallengeRequest(const JsonSection& params, CertificateRequest& request) override;
+
+  // For Client
   JsonSection
-  processSelectInterest(const Interest& interest, CertificateRequest& request) override;
+  getRequirementForChallenge(int status, const std::string& challengeStatus) override;
 
   JsonSection
-  processValidateInterest(const Interest& interest, CertificateRequest& request) override;
-
-  std::list<std::string>
-  getSelectRequirements() override;
-
-  std::list<std::string>
-  getValidateRequirements(const std::string& status) override;
-
-  JsonSection
-  doGenSelectParamsJson(const std::string& status,
-                        const std::list<std::string>& paramList) override;
-
-  JsonSection
-  doGenValidateParamsJson(const std::string& status,
-                          const std::list<std::string>& paramList) override;
+  genChallengeRequestJson(int status, const std::string& challengeStatus, const JsonSection& params) override;
 
 PUBLIC_WITH_TESTS_ELSE_PRIVATE:
-  static std::tuple<time::system_clock::TimePoint, std::string, int>
-  parseStoredSecrets(const JsonSection& storedSecret);
-
-  static JsonSection
-  generateStoredSecrets(const time::system_clock::TimePoint& tp, const std::string& secretCode,
-                        int attempTimes);
-
-PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+  // challenge status
   static const std::string NEED_CODE;
   static const std::string WRONG_CODE;
-
-  static const std::string FAILURE_TIMEOUT;
-  static const std::string FAILURE_MAXRETRY;
-
-  static const std::string JSON_CODE_TP;
+  // JSON attribute
   static const std::string JSON_PIN_CODE;
-  static const std::string JSON_ATTEMPT_TIMES;
 
 private:
   time::seconds m_secretLifetime;
diff --git a/src/client-config.cpp b/src/client-config.cpp
index e28407c..47b75da 100644
--- a/src/client-config.cpp
+++ b/src/client-config.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2017-2018, Regents of the University of California.
+ * Copyright (c) 2017-2019, Regents of the University of California.
  *
  * This file is part of ndncert, a certificate management system based on NDN.
  *
@@ -61,19 +61,9 @@
   ClientCaItem item;
   item.m_caName = Name(configSection.get<std::string>("ca-prefix"));
   item.m_caInfo = configSection.get<std::string>("ca-info");
-  item.m_probe = configSection.get("probe", "");
-  std::string listEnabledField = configSection.get("is-list-enabled", "false");
-  if (listEnabledField == "true") {
-    item.m_isListEnabled = true;
-  }
-  else {
-    item.m_isListEnabled = false;
-  }
-  item.m_targetedList = configSection.get("target-list", "");
-
+  item.m_probe = configSection.get<std::string>("probe");
   std::istringstream ss(configSection.get<std::string>("certificate"));
   item.m_anchor = *(io::load<security::v2::Certificate>(ss));
-
   return item;
 }
 
diff --git a/src/client-config.hpp b/src/client-config.hpp
index 72dd78f..b3aa0fc 100644
--- a/src/client-config.hpp
+++ b/src/client-config.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2017-2018, Regents of the University of California.
+ * Copyright (c) 2017-2019, Regents of the University of California.
  *
  * This file is part of ndncert, a certificate management system based on NDN.
  *
@@ -40,10 +40,6 @@
   std::string m_caInfo;
   // An instruction for requesters to use _PROBE. Extracted from config field "probe"
   std::string m_probe;
-  // Whether support list function
-  bool m_isListEnabled;
-  // An instruction for requesters to get a recommended CA. Extracted from config field "target-list"
-  std::string m_targetedList;
 
   // CA's certificate
   security::v2::Certificate m_anchor;
diff --git a/src/client-module.cpp b/src/client-module.cpp
index 0c9565e..5b03a23 100644
--- a/src/client-module.cpp
+++ b/src/client-module.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2017-2018, Regents of the University of California.
+ * Copyright (c) 2017-2019, Regents of the University of California.
  *
  * This file is part of ndncert, a certificate management system based on NDN.
  *
@@ -20,511 +20,323 @@
 
 #include "client-module.hpp"
 #include "logging.hpp"
-#include "json-helper.hpp"
 #include "challenge-module.hpp"
+#include "crypto-support/enc-tlv.hpp"
 #include <ndn-cxx/util/io.hpp>
 #include <ndn-cxx/security/signing-helpers.hpp>
 #include <ndn-cxx/security/verification-helpers.hpp>
+#include <ndn-cxx/util/random.hpp>
+#include <ndn-cxx/security/transform/base64-encode.hpp>
+#include <ndn-cxx/security/transform/buffer-source.hpp>
+#include <ndn-cxx/security/transform/stream-sink.hpp>
 
 namespace ndn {
 namespace ndncert {
 
 _LOG_INIT(ndncert.client);
 
-ClientModule::ClientModule(Face& face, security::v2::KeyChain& keyChain, size_t retryTimes)
-  : m_face(face)
-  , m_keyChain(keyChain)
-  , m_retryTimes(retryTimes)
+ClientModule::ClientModule(security::v2::KeyChain& keyChain)
+  : m_keyChain(keyChain)
 {
 }
 
 ClientModule::~ClientModule() = default;
 
-void
-ClientModule::requestCaTrustAnchor(const Name& caName, const DataCallback& trustAnchorCallback,
-                                   const ErrorCallback& errorCallback)
+shared_ptr<Interest>
+ClientModule::generateProbeInfoInterest(const Name& caName)
 {
   Name interestName = caName;
-  interestName.append("CA").append("_DOWNLOAD").append("ANCHOR");
-  Interest interest(interestName);
-  interest.setMustBeFresh(true);
-
-  m_face.expressInterest(interest, trustAnchorCallback,
-                         bind(&ClientModule::onNack, this, _1, _2, errorCallback),
-                         bind(&ClientModule::onTimeout, this, _1, m_retryTimes,
-                              trustAnchorCallback, errorCallback));
+  if (readString(caName.at(-1)) != "CA")
+    interestName.append("CA");
+  interestName.append("_PROBE").append("INFO");
+  auto interest = make_shared<Interest>(interestName);
+  interest->setMustBeFresh(true);
+  interest->setCanBePrefix(false);
+  return interest;
 }
 
 void
-ClientModule::requestLocalhostList(const LocalhostListCallback& listCallback,
-                                   const ErrorCallback& errorCallback)
+ClientModule::onProbeInfoResponse(const Data& reply)
 {
-  Interest interest(Name("/localhost/CA/_LIST"));
-  interest.setMustBeFresh(true);
-  DataCallback dataCb = bind(&ClientModule::handleLocalhostListResponse,
-                             this, _1, _2, listCallback, errorCallback);
-  m_face.expressInterest(interest, dataCb,
-                         bind(&ClientModule::onNack, this, _1, _2, errorCallback),
-                         bind(&ClientModule::onTimeout, this, _1, m_retryTimes,
-                              dataCb, errorCallback));
-}
+  // parse the ca item
+  auto contentJson = getJsonFromData(reply);
+  auto caItem = ClientConfig::extractCaItem(contentJson);
 
-void
-ClientModule::handleLocalhostListResponse(const Interest& request, const Data& reply,
-                                          const LocalhostListCallback& listCallback,
-                                          const ErrorCallback& errorCallback)
-{
-  // TODO: use the file path to replace the cert
-  // const auto& pib = m_keyChain.getPib();
-  // auto identity = pib.getDefaultIdentity();
-  // auto key = identity.getDefaultKey();
-  // auto cert = key.getDefaultCertificate();
-
-  auto cert = *(io::load<security::v2::Certificate>(m_config.m_localNdncertAnchor));
-
-  if (!security::verifySignature(reply, cert)) {
-    errorCallback("Cannot verify data from localhost CA");
-    return;
-  };
-
-  JsonSection contentJson = getJsonFromData(reply);
-  ClientConfig clientConf;
-  clientConf.load(contentJson);
-  listCallback(clientConf);
-}
-
-void
-ClientModule::requestList(const ClientCaItem& ca, const std::string& additionalInfo,
-                          const ListCallback& listCallback, const ErrorCallback& errorCallback)
-{
-  Name requestName(ca.m_caName);
-  requestName.append("_LIST");
-  if (additionalInfo != "") {
-    requestName.append(additionalInfo);
-  }
-  Interest interest(requestName);
-  interest.setMustBeFresh(true);
-  DataCallback dataCb = bind(&ClientModule::handleListResponse,
-                             this, _1, _2, ca, listCallback, errorCallback);
-  m_face.expressInterest(interest, dataCb,
-                         bind(&ClientModule::onNack, this, _1, _2, errorCallback),
-                         bind(&ClientModule::onTimeout, this, _1, m_retryTimes,
-                              dataCb, errorCallback));
-}
-
-void
-ClientModule::handleListResponse(const Interest& request, const Data& reply,
-                                 const ClientCaItem& ca,
-                                 const ListCallback& listCallback,
-                                 const ErrorCallback& errorCallback)
-{
-  if (!security::verifySignature(reply, ca.m_anchor)) {
-    errorCallback("Cannot verify data from " + ca.m_caName.toUri());
-    return;
-  };
-
-  std::list<Name> caList;
-  Name assignedName;
-
-  JsonSection contentJson = getJsonFromData(reply);
-  auto recommendedName = contentJson.get("recommended-identity", "");
-  if (recommendedName == "") {
-    // without recommendation
-    auto caListJson = contentJson.get_child("ca-list");
-    auto it = caListJson.begin();
-    for(; it != caListJson.end(); it++) {
-      caList.push_back(Name(it->second.get<std::string>("ca-prefix")));
+  // update the local config
+  bool findItem = false;
+  for (auto& item : m_config.m_caItems) {
+    if (item.m_caName == caItem.m_caName) {
+      findItem = true;
+      item = caItem;
     }
   }
-  else {
-    // with recommendation
-    Name caName(contentJson.get<std::string>("recommended-ca"));
-    caList.push_back(caName);
-    assignedName = caName.append(recommendedName);
+  if (!findItem) {
+    m_config.m_caItems.push_back(caItem);
   }
-  Name schemaDataName(contentJson.get("trust-schema", ""));
-  listCallback(caList, assignedName, schemaDataName);
-}
 
-void
-ClientModule::sendProbe(const ClientCaItem& ca, const std::string& probeInfo,
-                        const RequestCallback& requestCallback,
-                        const ErrorCallback& errorCallback)
-{
-  Interest interest(Name(ca.m_caName).append("_PROBE").append(probeInfo));
-  interest.setMustBeFresh(true);
-  DataCallback dataCb = bind(&ClientModule::handleProbeResponse,
-                             this, _1, _2, ca, requestCallback, errorCallback);
-  m_face.expressInterest(interest, dataCb,
-                         bind(&ClientModule::onNack, this, _1, _2, errorCallback),
-                         bind(&ClientModule::onTimeout, this, _1, m_retryTimes,
-                              dataCb, errorCallback));
-
-  _LOG_TRACE("PROBE interest sent with Probe info " << probeInfo);
-}
-
-void
-ClientModule::handleProbeResponse(const Interest& request, const Data& reply,
-                                  const ClientCaItem& ca,
-                                  const RequestCallback& requestCallback,
-                                  const ErrorCallback& errorCallback)
-{
-  if (!security::verifySignature(reply, ca.m_anchor)) {
-    errorCallback("Cannot verify data from " + ca.m_caName.toUri());
-    return;
-  };
-  JsonSection contentJson = getJsonFromData(reply);
-  std::string identityNameString = contentJson.get(JSON_IDNENTIFIER, "");
-  if (!identityNameString.empty()) {
-    Name identityName(identityNameString);
-    sendNew(ca, identityName, requestCallback, errorCallback);
-
-    _LOG_TRACE("Got PROBE response with identity " << identityName);
-  }
-  else {
-    errorCallback("The response does not carry required fields.");
+  // verify the probe Data's sig
+  if (!security::verifySignature(reply, caItem.m_anchor)) {
+    _LOG_ERROR("Cannot verify data signature from " << m_ca.m_caName.toUri());
     return;
   }
 }
 
-void
-ClientModule::sendNew(const ClientCaItem& ca, const Name& identityName,
-                      const RequestCallback& requestCallback,
-                      const ErrorCallback& errorCallback)
+shared_ptr<Interest>
+ClientModule::generateProbeInterest(const ClientCaItem& ca, const std::string& probeInfo)
 {
+  Name interestName = ca.m_caName;
+  interestName.append("CA").append("_PROBE");
+  auto interest = make_shared<Interest>(interestName);
+  interest->setMustBeFresh(true);
+  interest->setCanBePrefix(false);
+  auto paramJson = genProbeRequestJson(probeInfo);
+  interest->setApplicationParameters(paramFromJson(paramJson));
+
+  // update local state
+  m_ca = ca;
+  return interest;
+}
+
+void
+ClientModule::onProbeResponse(const Data& reply)
+{
+  if (!security::verifySignature(reply, m_ca.m_anchor)) {
+    _LOG_ERROR("Cannot verify data signature from " << m_ca.m_caName.toUri());
+    return;
+  }
+  auto contentJson = getJsonFromData(reply);
+
+  // read the available name and put it into the state
+  auto nameUri = contentJson.get<std::string>(JSON_CA_NAME, "");
+  if (nameUri != "") {
+    m_identityName = Name(nameUri);
+  }
+}
+
+shared_ptr<Interest>
+ClientModule::generateNewInterest(const time::system_clock::TimePoint& notBefore,
+                                  const time::system_clock::TimePoint& notAfter,
+                                  const Name& identityName)
+{
+  // Name requestedName = identityName;
+  if (!identityName.empty()) { // if identityName is not empty, find the corresponding CA
+    bool findCa = false;
+    for (const auto& caItem : m_config.m_caItems) {
+      if (caItem.m_caName.isPrefixOf(identityName)) {
+        m_ca = caItem;
+        findCa = true;
+      }
+    }
+    if (!findCa) { // if cannot find, cannot proceed
+      return nullptr;
+    }
+    m_identityName = identityName;
+  }
+  else { // if identityName is empty, check m_identityName or generate a random name
+    if (!m_identityName.empty()) {
+      // do nothing
+    }
+    else {
+      auto id = std::to_string(random::generateSecureWord64());
+      m_identityName = m_ca.m_caName;
+      m_identityName.append(id);
+    }
+  }
+
+  // generate a newly key pair or use an existing key
   const auto& pib = m_keyChain.getPib();
-
-  auto state = make_shared<RequestState>();
   try {
-    auto identity = pib.getIdentity(identityName);
-    state->m_key = m_keyChain.createKey(identity);
+    auto identity = pib.getIdentity(m_identityName);
+    m_key = m_keyChain.createKey(identity);
   }
   catch (const security::Pib::Error& e) {
-    auto identity = m_keyChain.createIdentity(identityName);
-    state->m_key = identity.getDefaultKey();
+    auto identity = m_keyChain.createIdentity(m_identityName);
+    m_key = identity.getDefaultKey();
   }
-  state->m_ca = ca;
-  state->m_isInstalled = false;
 
   // generate certificate request
   security::v2::Certificate certRequest;
-  certRequest.setName(Name(state->m_key.getName()).append("cert-request").appendVersion());
+  certRequest.setName(Name(m_key.getName()).append("cert-request").appendVersion());
   certRequest.setContentType(tlv::ContentType_Key);
   certRequest.setFreshnessPeriod(time::hours(24));
-  certRequest.setContent(state->m_key.getPublicKey().data(), state->m_key.getPublicKey().size());
+  certRequest.setContent(m_key.getPublicKey().data(), m_key.getPublicKey().size());
   SignatureInfo signatureInfo;
-  signatureInfo.setValidityPeriod(security::ValidityPeriod(time::system_clock::now(),
-                                                           time::system_clock::now() + time::days(10)));
-  m_keyChain.sign(certRequest, signingByKey(state->m_key.getName()).setSignatureInfo(signatureInfo));
+  signatureInfo.setValidityPeriod(security::ValidityPeriod(notBefore, notAfter));
+  m_keyChain.sign(certRequest, signingByKey(m_key.getName()).setSignatureInfo(signatureInfo));
 
-  // generate interest
-  Interest interest(Name(ca.m_caName).append(Name("_NEW")).append(certRequest.wireEncode()));
-  m_keyChain.sign(interest, signingByKey(state->m_key.getName()));
+  // generate Interest packet
+  Name interestName = m_ca.m_caName;
+  interestName.append("CA").append("_NEW");
+  auto interest = make_shared<Interest>(interestName);
+  interest->setMustBeFresh(true);
+  interest->setCanBePrefix(false);
+  interest->setApplicationParameters(paramFromJson(genNewRequestJson(m_ecdh.getBase64PubKey(), certRequest)));
 
-  DataCallback dataCb = bind(&ClientModule::handleNewResponse,
-                             this, _1, _2, state, requestCallback, errorCallback);
-  m_face.expressInterest(interest, dataCb,
-                         bind(&ClientModule::onNack, this, _1, _2, errorCallback),
-                         bind(&ClientModule::onTimeout, this, _1, m_retryTimes,
-                              dataCb, errorCallback));
-
-  _LOG_TRACE("NEW interest sent with identity " << identityName);
+  // sign the Interest packet
+  m_keyChain.sign(*interest, signingByKey(m_key.getName()));
+  return interest;
 }
 
-void
-ClientModule::handleNewResponse(const Interest& request, const Data& reply,
-                                const shared_ptr<RequestState>& state,
-                                const RequestCallback& requestCallback,
-                                const ErrorCallback& errorCallback)
+std::list<std::string>
+ClientModule::onNewResponse(const Data& reply)
 {
-  if (!security::verifySignature(reply, state->m_ca.m_anchor)) {
-    errorCallback("Cannot verify data from " + state->m_ca.m_caName.toUri());
-    return;
+  if (!security::verifySignature(reply, m_ca.m_anchor)) {
+    _LOG_ERROR("Cannot verify data signature from " << m_ca.m_caName.toUri());
+    return std::list<std::string>();
   }
+  auto contentJson = getJsonFromData(reply);
 
-  const JsonSection& json = getJsonFromData(reply);
-  state->m_status = json.get(JSON_STATUS, "");
-  state->m_requestId = json.get(JSON_REQUEST_ID, "");
+  // ECDH
+  const auto& peerKeyBase64Str = contentJson.get<std::string>(JSON_CA_ECDH, "");
+  const auto& saltStr = contentJson.get<std::string>(JSON_CA_SALT, "");
+  uint64_t saltInt = std::stoull(saltStr);
+  uint8_t salt[sizeof(saltInt)];
+  std::memcpy(salt, &saltInt, sizeof(saltInt));
+  m_ecdh.deriveSecret(peerKeyBase64Str);
 
-  if (!checkStatus(*state, json, errorCallback)) {
-    return;
-  }
+  // HKDF
+  hkdf(m_ecdh.context->sharedSecret, m_ecdh.context->sharedSecretLen, salt, sizeof(saltInt), m_aesKey, 32);
 
-  JsonSection challengesJson = json.get_child(JSON_CHALLENGES);
-  std::list<std::string> challengeList;
+  // update state
+  m_status = contentJson.get<int>(JSON_CA_STATUS);
+  m_requestId = contentJson.get<std::string>(JSON_CA_EQUEST_ID, "");
+
+  auto challengesJson = contentJson.get_child(JSON_CA_CHALLENGES);
+  m_challengeList.clear();
   for (const auto& challengeJson : challengesJson) {
-    challengeList.push_back(challengeJson.second.get<std::string>(JSON_CHALLENGE_TYPE));
+    m_challengeList.push_back(challengeJson.second.get<std::string>(JSON_CA_CHALLENGE_ID, ""));
   }
-  state->m_challengeList = challengeList;
+  return m_challengeList;
+}
 
-  _LOG_TRACE("Got NEW response with requestID " << state->m_requestId
-             << " with status " << state->m_status
-             << " with challenge number " << challengeList.size());
+shared_ptr<Interest>
+ClientModule::generateChallengeInterest(const JsonSection& paramJson)
+{
+  m_challengeType = paramJson.get<std::string>(JSON_CLIENT_SELECTED_CHALLENGE);
 
-  requestCallback(state);
+  Name interestName = m_ca.m_caName;
+  interestName.append("CA").append("_CHALLENGE").append(m_requestId);
+  auto interest = make_shared<Interest>(interestName);
+  interest->setMustBeFresh(true);
+  interest->setCanBePrefix(false);
+
+  // encrypt the Interest parameters
+  std::stringstream ss;
+  boost::property_tree::write_json(ss, paramJson);
+  auto payload = ss.str();
+  auto paramBlock = genEncBlock(tlv::ApplicationParameters, m_ecdh.context->sharedSecret, m_ecdh.context->sharedSecretLen,
+                                (const uint8_t*)payload.c_str(), payload.size());
+  interest->setApplicationParameters(paramBlock);
+
+  m_keyChain.sign(*interest, signingByKey(m_key.getName()));
+  return interest;
 }
 
 void
-ClientModule::sendSelect(const shared_ptr<RequestState>& state,
-                         const std::string& challengeType,
-                         const JsonSection& selectParams,
-                         const RequestCallback& requestCallback,
-                         const ErrorCallback& errorCallback)
+ClientModule::onChallengeResponse(const Data& reply)
 {
-  JsonSection requestIdJson;
-  requestIdJson.put(JSON_REQUEST_ID, state->m_requestId);
-
-  state->m_challengeType = challengeType;
-
-  Name interestName(state->m_ca.m_caName);
-  interestName.append("_SELECT")
-    .append(nameBlockFromJson(requestIdJson))
-    .append(challengeType)
-    .append(nameBlockFromJson(selectParams));
-  Interest interest(interestName);
-  m_keyChain.sign(interest, signingByKey(state->m_key.getName()));
-
-  DataCallback dataCb = bind(&ClientModule::handleSelectResponse,
-                             this, _1, _2, state, requestCallback, errorCallback);
-  m_face.expressInterest(interest, dataCb,
-                         bind(&ClientModule::onNack, this, _1, _2, errorCallback),
-                         bind(&ClientModule::onTimeout, this, _1, m_retryTimes,
-                              dataCb, errorCallback));
-
-   _LOG_TRACE("SELECT interest sent with challenge type " << challengeType);
-}
-
-void
-ClientModule::handleSelectResponse(const Interest& request,
-                                   const Data& reply,
-                                   const shared_ptr<RequestState>& state,
-                                   const RequestCallback& requestCallback,
-                                   const ErrorCallback& errorCallback)
-{
-  if (!security::verifySignature(reply, state->m_ca.m_anchor)) {
-    errorCallback("Cannot verify data from " + state->m_ca.m_caName.toUri());
+  if (!security::verifySignature(reply, m_ca.m_anchor)) {
+    _LOG_ERROR("Cannot verify data signature from " << m_ca.m_caName.toUri());
     return;
   }
+  auto result = parseEncBlock(m_ecdh.context->sharedSecret, m_ecdh.context->sharedSecretLen, reply.getContent());
+  std::string payload((const char*)result.data(), result.size());
+  std::istringstream ss(payload);
+  JsonSection contentJson;
+  boost::property_tree::json_parser::read_json(ss, contentJson);
 
-  JsonSection json = getJsonFromData(reply);
+  // update state
+  m_status = contentJson.get<int>(JSON_CA_STATUS);
+  m_challengeStatus = contentJson.get<std::string>(JSON_CHALLENGE_STATUS);
+  m_remainingTries = contentJson.get<int>(JSON_CHALLENGE_REMAINING_TRIES);
+  m_freshBefore = time::system_clock::now() + time::seconds(contentJson.get<int>(JSON_CHALLENGE_REMAINING_TIME));
+}
 
-  _LOG_TRACE("SELECT response would change the status from "
-             << state->m_status << " to " + json.get<std::string>(JSON_STATUS));
+shared_ptr<Interest>
+ClientModule::generateDownloadInterest()
+{
+  Name interestName = m_ca.m_caName;
+  interestName.append("CA").append("_DOWNLOAD").append(m_requestId);
+  auto interest = make_shared<Interest>(interestName);
+  interest->setMustBeFresh(true);
+  interest->setCanBePrefix(false);
+  return interest;
+}
 
-  state->m_status = json.get<std::string>(JSON_STATUS);
-
-  if (!checkStatus(*state, json, errorCallback)) {
-    return;
-  }
-
-  _LOG_TRACE("Got SELECT response with status " << state->m_status);
-
-  requestCallback(state);
+shared_ptr<Interest>
+ClientModule::generateCertFetchInterest()
+{
+  Name interestName = m_identityName;
+  interestName.append("KEY").append(m_certId);
+  auto interest = make_shared<Interest>(interestName);
+  interest->setMustBeFresh(true);
+  interest->setCanBePrefix(false);
+  return interest;
 }
 
 void
-ClientModule::sendValidate(const shared_ptr<RequestState>& state,
-                           const JsonSection& validateParams,
-                           const RequestCallback& requestCallback,
-                           const ErrorCallback& errorCallback)
+ClientModule::onDownloadResponse(const Data& reply)
 {
-  JsonSection requestIdJson;
-  requestIdJson.put(JSON_REQUEST_ID, state->m_requestId);
-
-  Name interestName(state->m_ca.m_caName);
-  interestName.append("_VALIDATE")
-    .append(nameBlockFromJson(requestIdJson))
-    .append(state->m_challengeType)
-    .append(nameBlockFromJson(validateParams));
-  Interest interest(interestName);
-  m_keyChain.sign(interest, signingByKey(state->m_key.getName()));
-
-  DataCallback dataCb = bind(&ClientModule::handleValidateResponse,
-                             this, _1, _2, state, requestCallback, errorCallback);
-  m_face.expressInterest(interest, dataCb,
-                         bind(&ClientModule::onNack, this, _1, _2, errorCallback),
-                         bind(&ClientModule::onTimeout, this, _1, m_retryTimes,
-                              dataCb, errorCallback));
-
-  _LOG_TRACE("VALIDATE interest sent");
-}
-
-void
-ClientModule::handleValidateResponse(const Interest& request,
-                                     const Data& reply,
-                                     const shared_ptr<RequestState>& state,
-                                     const RequestCallback& requestCallback,
-                                     const ErrorCallback& errorCallback)
-{
-  if (!security::verifySignature(reply, state->m_ca.m_anchor)) {
-    errorCallback("Cannot verify data from " + state->m_ca.m_caName.toUri());
-    return;
-  }
-
-  JsonSection json = getJsonFromData(reply);
-  state->m_status = json.get<std::string>(JSON_STATUS);
-
-  if (!checkStatus(*state, json, errorCallback)) {
-    return;
-  }
-
-  _LOG_TRACE("Got VALIDATE response with status " << state->m_status);
-
-  requestCallback(state);
-}
-
-
-void
-ClientModule::requestStatus(const shared_ptr<RequestState>& state,
-                            const RequestCallback& requestCallback,
-                            const ErrorCallback& errorCallback)
-{
-  JsonSection requestIdJson;
-  requestIdJson.put(JSON_REQUEST_ID, state->m_requestId);
-
-  Name interestName(state->m_ca.m_caName);
-  interestName.append("_STATUS").append(nameBlockFromJson(requestIdJson));
-  Interest interest(interestName);
-
-  m_keyChain.sign(interest, signingByKey(state->m_key.getName()));
-
-  DataCallback dataCb = bind(&ClientModule::handleStatusResponse,
-                             this, _1, _2, state, requestCallback, errorCallback);
-  m_face.expressInterest(interest, dataCb,
-                         bind(&ClientModule::onNack, this, _1, _2, errorCallback),
-                         bind(&ClientModule::onTimeout, this, _1, m_retryTimes,
-                              dataCb, errorCallback));
-
-  _LOG_TRACE("STATUS interest sent");
-}
-
-void
-ClientModule::handleStatusResponse(const Interest& request, const Data& reply,
-                                   const shared_ptr<RequestState>& state,
-                                   const RequestCallback& requestCallback,
-                                   const ErrorCallback& errorCallback)
-{
-  if (!security::verifySignature(reply, state->m_ca.m_anchor)) {
-    errorCallback("Cannot verify data from " + state->m_ca.m_caName.toUri());
-    return;
-  }
-
-  JsonSection json = getJsonFromData(reply);
-  state->m_status = json.get<std::string>(JSON_STATUS);
-
-  if (!checkStatus(*state, json, errorCallback)) {
-    return;
-  }
-
-  _LOG_TRACE("Got STATUS response with status " << state->m_status);
-
-  requestCallback(state);
-}
-
-void
-ClientModule::requestDownload(const shared_ptr<RequestState>& state,
-                              const RequestCallback& requestCallback,
-                              const ErrorCallback& errorCallback)
-{
-  JsonSection requestIdJson;
-  requestIdJson.put(JSON_REQUEST_ID, state->m_requestId);
-
-  Name interestName(state->m_ca.m_caName);
-  interestName.append("_DOWNLOAD").append(nameBlockFromJson(requestIdJson));
-  Interest interest(interestName);
-  interest.setMustBeFresh(true);
-
-  DataCallback dataCb = bind(&ClientModule::handleDownloadResponse,
-                             this, _1, _2, state, requestCallback, errorCallback);
-  m_face.expressInterest(interest, dataCb,
-                         bind(&ClientModule::onNack, this, _1, _2, errorCallback),
-                         bind(&ClientModule::onTimeout, this, _1, m_retryTimes,
-                              dataCb, errorCallback));
-
-  _LOG_TRACE("DOWNLOAD interest sent");
-}
-
-void
-ClientModule::handleDownloadResponse(const Interest& request, const Data& reply,
-                                     const shared_ptr<RequestState>& state,
-                                     const RequestCallback& requestCallback,
-                                     const ErrorCallback& errorCallback)
-{
-  if (!security::verifySignature(reply, state->m_ca.m_anchor)) {
-    errorCallback("Cannot verify data from " + state->m_ca.m_caName.toUri());
-    return;
-  }
-
   try {
     security::v2::Certificate cert(reply.getContent().blockFromValue());
-    m_keyChain.addCertificate(state->m_key, cert);
-
+    m_keyChain.addCertificate(m_key, cert);
     _LOG_TRACE("Got DOWNLOAD response and installed the cert " << cert.getName());
   }
   catch (const std::exception& e) {
-    errorCallback(std::string(e.what()));
+    _LOG_ERROR("Cannot add replied certificate into the keychain " << e.what());
     return;
   }
-
-  state->m_isInstalled = true;
-  requestCallback(state);
+  m_isCertInstalled = true;
 }
 
 void
-ClientModule::onTimeout(const Interest& interest, int nRetriesLeft, const DataCallback& dataCallback,
-                        const ErrorCallback& errorCallback)
+ClientModule::onCertFetchResponse(const Data& reply)
 {
-  if (nRetriesLeft > 0) {
-    m_face.expressInterest(interest, dataCallback,
-                           bind(&ClientModule::onNack, this, _1, _2, errorCallback),
-                           bind(&ClientModule::onTimeout, this, _1, nRetriesLeft - 1,
-                                dataCallback, errorCallback));
-  }
-  else {
-    errorCallback("Run out retries: still timeout");
-    return;
-  }
-}
-
-void
-ClientModule::onNack(const Interest& interest, const lp::Nack& nack, const ErrorCallback& errorCallback)
-{
-  errorCallback("Got Nack");
+  onDownloadResponse(reply);
 }
 
 JsonSection
 ClientModule::getJsonFromData(const Data& data)
 {
-  Block jsonBlock = data.getContent();
-  std::string jsonString = encoding::readString(jsonBlock);
-  std::istringstream ss(jsonString);
+  std::istringstream ss(encoding::readString(data.getContent()));
   JsonSection json;
   boost::property_tree::json_parser::read_json(ss, json);
   return json;
 }
 
+const JsonSection
+ClientModule::genProbeRequestJson(const std::string& probeInfo)
+{
+  JsonSection root;
+  root.put(JSON_CLIENT_PROBE_INFO, probeInfo);
+  return root;
+}
+
+const JsonSection
+ClientModule::genNewRequestJson(const std::string& ecdhPub, const security::v2::Certificate& certRequest)
+{
+  JsonSection root;
+  std::stringstream ss;
+  try {
+    security::transform::bufferSource(certRequest.wireEncode().wire(), certRequest.wireEncode().size())
+    >> security::transform::base64Encode(true)
+    >> security::transform::streamSink(ss);
+  }
+  catch (const security::transform::Error& e) {
+    _LOG_ERROR("Cannot convert self-signed cert into BASE64 string " << e.what());
+    return root;
+  }
+  root.put(JSON_CLIENT_ECDH, ecdhPub);
+  root.put(JSON_CLIENT_CERT_REQ, ss.str());
+  return root;
+}
+
 Block
-ClientModule::nameBlockFromJson(const JsonSection& json)
+ClientModule::paramFromJson(const JsonSection& json)
 {
   std::stringstream ss;
   boost::property_tree::write_json(ss, json);
-  return makeStringBlock(ndn::tlv::GenericNameComponent, ss.str());
-}
-
-bool
-ClientModule::checkStatus(const RequestState& state, const JsonSection& json,
-                          const ErrorCallback& errorCallback)
-{
-  if (state.m_status == ChallengeModule::FAILURE) {
-    errorCallback(json.get(JSON_FAILURE_INFO, ""));
-    return false;
-  }
-  if (state.m_requestId.empty() || state.m_status.empty()) {
-    errorCallback("The response does not carry required fields. requestID: " + state.m_requestId
-                  + " status: " + state.m_status);
-    return false;
-  }
-  return true;
+  return makeStringBlock(ndn::tlv::ApplicationParameters, ss.str());
 }
 
 } // namespace ndncert
diff --git a/src/client-module.hpp b/src/client-module.hpp
index df2a43a..bbf124c 100644
--- a/src/client-module.hpp
+++ b/src/client-module.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2017-2018, Regents of the University of California.
+ * Copyright (c) 2017-2019, Regents of the University of California.
  *
  * This file is part of ndncert, a certificate management system based on NDN.
  *
@@ -22,26 +22,12 @@
 #define NDNCERT_CLIENT_MODULE_HPP
 
 #include "client-config.hpp"
+#include "crypto-support/crypto-helper.hpp"
 #include "certificate-request.hpp"
 
 namespace ndn {
 namespace ndncert {
 
-class RequestState
-{
-
-public:
-  ClientCaItem m_ca;
-  security::Key m_key;
-
-  std::string m_requestId;
-  std::string m_status;
-  std::string m_challengeType;
-  std::list<std::string> m_challengeList;
-
-  bool m_isInstalled = false;
-};
-
 // TODO
 // For each CA item in Client.Conf, create a validator instance and initialize it with CA's cert
 // The validator instance should be in ClientCaItem
@@ -58,13 +44,8 @@
     using std::runtime_error::runtime_error;
   };
 
-  using LocalhostListCallback = function<void (const ClientConfig&)>;
-  using ListCallback = function<void (const std::list<Name>&, const Name&, const Name&)>;
-  using RequestCallback = function<void (const shared_ptr<RequestState>&)>;
-  using ErrorCallback = function<void (const std::string&)>;
-
 public:
-  ClientModule(Face& face, security::v2::KeyChain& keyChain, size_t retryTimes = 2);
+  ClientModule(security::v2::KeyChain& keyChain);
 
   virtual
   ~ClientModule();
@@ -75,114 +56,97 @@
     return m_config;
   }
 
-  /**
-   * @brief Send /CA-prefix/CA/_DOWNLOAD/ANCHOR to get CA's latest anchor with the config
-   */
-  void
-  requestCaTrustAnchor(const Name& caName, const DataCallback& trustAnchorCallback,
-                       const ErrorCallback& errorCallback);
+  int
+  getApplicationStatus() const
+  {
+    return m_status;
+  }
+
+  std::string
+  getChallengeStatus() const
+  {
+    return m_challengeStatus;
+  }
+
+  shared_ptr<Interest>
+  generateProbeInfoInterest(const Name& caName);
 
   /**
-   * @brief Send /localhost/CA/List to query local available CAs
-   *
-   * For more information:
-   *   https://github.com/named-data/ndncert/wiki/Intra-Node-Design
+   * @brief Process the replied PROBE INFO Data packet
+   * Warning: this function will add a new trust anchor into the application.
+   * Please invoke this function only when reply can be fully trusted or the CA
+   * can be verified in later challenge phase.
    */
   void
-  requestLocalhostList(const LocalhostListCallback& listCallback, const ErrorCallback& errorCallback);
+  onProbeInfoResponse(const Data& reply);
 
-  /**
-   * @brief Handle the list request response
-   */
-  void
-  handleLocalhostListResponse(const Interest& request, const Data& reply,
-                              const LocalhostListCallback& listCallback, const ErrorCallback& errorCallback);
+  shared_ptr<Interest>
+  generateProbeInterest(const ClientCaItem& ca, const std::string& probeInfo);
 
   void
-  requestList(const ClientCaItem& ca, const std::string& additionalInfo,
-              const ListCallback& listCallback, const ErrorCallback& errorCallback);
+  onProbeResponse(const Data& reply);
+
+  shared_ptr<Interest>
+  generateNewInterest(const time::system_clock::TimePoint& notBefore,
+                      const time::system_clock::TimePoint& notAfter,
+                      const Name& identityName = Name());
+
+  std::list<std::string>
+  onNewResponse(const Data& reply);
+
+  shared_ptr<Interest>
+  generateChallengeInterest(const JsonSection& paramJson);
 
   void
-  handleListResponse(const Interest& request, const Data& reply, const ClientCaItem& ca,
-                     const ListCallback& listCallback, const ErrorCallback& errorCallback);
+  onChallengeResponse(const Data& reply);
+
+  shared_ptr<Interest>
+  generateDownloadInterest();
+
+  shared_ptr<Interest>
+  generateCertFetchInterest();
 
   void
-  sendProbe(const ClientCaItem& ca, const std::string& probeInfo,
-            const RequestCallback& requestCallback, const ErrorCallback& errorCallback);
+  onDownloadResponse(const Data& reply);
 
   void
-  handleProbeResponse(const Interest& request, const Data& reply, const ClientCaItem& ca,
-                      const RequestCallback& requestCallback, const ErrorCallback& errorCallback);
-
-  void
-  sendNew(const ClientCaItem& ca, const Name& identityName,
-          const RequestCallback& requestCallback, const ErrorCallback& errorCallback);
-
-  void
-  handleNewResponse(const Interest& request, const Data& reply,
-                    const shared_ptr<RequestState>& state,
-                    const RequestCallback& requestCallback, const ErrorCallback& errorCallback);
-
-  void
-  sendSelect(const shared_ptr<RequestState>& state, const std::string& challengeType,
-             const JsonSection& selectParams,
-             const RequestCallback& requestCallback, const ErrorCallback& errorCallback);
-
-  void
-  handleSelectResponse(const Interest& request, const Data& reply,
-                       const shared_ptr<RequestState>& state,
-                       const RequestCallback& requestCallback, const ErrorCallback& errorCallback);
-
-  void
-  sendValidate(const shared_ptr<RequestState>& state, const JsonSection& validateParams,
-               const RequestCallback& requestCallback, const ErrorCallback& errorCallback);
-
-  void
-  handleValidateResponse(const Interest& request, const Data& reply,
-                         const shared_ptr<RequestState>& state,
-                         const RequestCallback& requestCallback, const ErrorCallback& errorCallback);
-
-  void
-  requestStatus(const shared_ptr<RequestState>& state,
-                const RequestCallback& requestCallback, const ErrorCallback& errorCallback);
-
-  void
-  handleStatusResponse(const Interest& request, const Data& reply,
-                       const shared_ptr<RequestState>& state,
-                       const RequestCallback& requestCallback, const ErrorCallback& errorCallback);
-
-  void
-  requestDownload(const shared_ptr<RequestState>& state, const RequestCallback& requestCallback,
-                  const ErrorCallback& errorCallback);
-
-  void
-  handleDownloadResponse(const Interest& request, const Data& reply,
-                         const shared_ptr<RequestState>& state,
-                         const RequestCallback& requestCallback, const ErrorCallback& errorCallback);
+  onCertFetchResponse(const Data& reply);
 
   // helper functions
   static JsonSection
   getJsonFromData(const Data& data);
 
   static Block
-  nameBlockFromJson(const JsonSection& json);
+  paramFromJson(const JsonSection& json);
 
-  static bool
-  checkStatus(const RequestState& state, const JsonSection& json, const ErrorCallback& errorCallback);
+PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+  const JsonSection
+  genProbeRequestJson(const std::string& probeInfo);
 
-protected:
-  virtual void
-  onTimeout(const Interest& interest, int nRetriesLeft,
-            const DataCallback& dataCallback, const ErrorCallback& errorCallback);
+  const JsonSection
+  genNewRequestJson(const std::string& ecdhPub, const security::v2::Certificate& certRequest);
 
-  virtual void
-  onNack(const Interest& interest, const lp::Nack& nack, const ErrorCallback& errorCallback);
-
-protected:
+PUBLIC_WITH_TESTS_ELSE_PRIVATE:
   ClientConfig m_config;
-  Face& m_face;
   security::v2::KeyChain& m_keyChain;
-  size_t m_retryTimes;
+
+  ClientCaItem m_ca;
+  security::Key m_key;
+  Name m_identityName;
+
+  std::string m_requestId = "";
+  int m_status = STATUS_NOT_STARTED;
+  std::string m_challengeStatus = "";
+  std::string m_challengeType = "";
+  std::string m_certId = "";
+  std::list<std::string> m_challengeList;
+  bool m_isCertInstalled = false;
+
+  int m_remainingTries = 0;
+  time::system_clock::TimePoint m_freshBefore;
+
+  ECDHState m_ecdh;
+  uint8_t m_aesKey[32] = {0};
 };
 
 } // namespace ndncert
diff --git a/src/crypto-support/crypto-helper.cpp b/src/crypto-support/crypto-helper.cpp
new file mode 100644
index 0000000..915f17c
--- /dev/null
+++ b/src/crypto-support/crypto-helper.cpp
@@ -0,0 +1,256 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2017-2019, 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 "crypto-helper.hpp"
+#include "../logging.hpp"
+#include <openssl/pem.h>
+#include <openssl/rand.h>
+#include <openssl/err.h>
+#include <ndn-cxx/security/transform/block-cipher.hpp>
+#include <ndn-cxx/security/transform/base64-decode.hpp>
+#include <ndn-cxx/security/transform/base64-encode.hpp>
+#include <ndn-cxx/security/transform/buffer-source.hpp>
+#include <ndn-cxx/security/transform/step-source.hpp>
+#include <ndn-cxx/security/transform/stream-sink.hpp>
+#include <ndn-cxx/util/random.hpp>
+#include <ndn-cxx/encoding/buffer-stream.hpp>
+#include <ndn-cxx/security/transform/hmac-filter.hpp>
+
+namespace ndn {
+namespace ndncert {
+
+const size_t HASH_SIZE = 32;
+
+_LOG_INIT(crypto-support);
+
+ECDHState::ECDHState()
+{
+  OpenSSL_add_all_algorithms();
+  context = std::make_unique<ECDH_CTX>();
+  context->EC_NID = NID_X9_62_prime256v1;
+
+  // Create the context for parameter generation
+  if (nullptr == (context->ctx_params = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, nullptr))) {
+    handleErrors("Could not create context contexts.");
+    return;
+  }
+
+  // Initialise the parameter generation
+  if (EVP_PKEY_paramgen_init(context->ctx_params) != 1) {
+    handleErrors("Could not initialize parameter generation.");
+    return;
+  }
+
+  // We're going to use the ANSI X9.62 Prime 256v1 curve
+  if (1 != EVP_PKEY_CTX_set_ec_paramgen_curve_nid(context->ctx_params, context->EC_NID)) {
+    handleErrors("Likely unknown elliptical curve ID specified.");
+    return;
+  }
+
+  // Create the parameter object params
+  if (!EVP_PKEY_paramgen(context->ctx_params, &context->params)) {
+    // the generated key is written to context->params
+    handleErrors("Could not create parameter object parameters.");
+    return;
+  }
+
+  // Create the context for the key generation
+  if (nullptr == (context->ctx_keygen = EVP_PKEY_CTX_new(context->params, nullptr))) {
+    //The EVP_PKEY_CTX_new() function allocates public key algorithm context using
+    //the algorithm specified in pkey and ENGINE e (in this case nullptr).
+    handleErrors("Could not create the context for the key generation");
+    return;
+  }
+
+  // initializes a public key algorithm context
+  if (1 != EVP_PKEY_keygen_init(context->ctx_keygen)){
+    handleErrors("Could not init context for key generation.");
+    return;
+  }
+  if (1 != EVP_PKEY_keygen(context->ctx_keygen, &context->privkey)) {
+    //performs a key generation operation, the generated key is written to context->privkey.
+    handleErrors("Could not generate DHE keys in final step");
+    return;
+  }
+}
+
+ECDHState::~ECDHState()
+{
+  // Contexts
+  if(context->ctx_params != nullptr){
+    EVP_PKEY_CTX_free(context->ctx_params);
+  }
+  if(context->ctx_keygen != nullptr){
+    EVP_PKEY_CTX_free(context->ctx_keygen);
+  }
+
+  // Keys
+  if(context->privkey != nullptr){
+    EVP_PKEY_free(context->privkey);
+  }
+  if(context->peerkey != nullptr){
+    EVP_PKEY_free(context->peerkey);
+  }
+  if(context->params != nullptr){
+    EVP_PKEY_free(context->params);
+  }
+}
+
+uint8_t*
+ECDHState::getRawSelfPubKey()
+{
+  auto privECKey = EVP_PKEY_get1_EC_KEY(context->privkey);
+
+  if (privECKey == nullptr) {
+    handleErrors("Could not get referenced key when calling EVP_PKEY_get1_EC_KEY().");
+    return nullptr;
+  }
+
+  auto ecPoint = EC_KEY_get0_public_key(privECKey);
+  const EC_GROUP* group = EC_KEY_get0_group(privECKey);
+  context->publicKeyLen = EC_POINT_point2oct(group, ecPoint, POINT_CONVERSION_COMPRESSED,
+                                             context->publicKey, 256, nullptr);
+  EC_KEY_free(privECKey);
+  if (context->publicKeyLen == 0) {
+    handleErrors("Could not convert EC_POINTS to octet string when calling EC_POINT_point2oct.");
+    return nullptr;
+  }
+
+  return context->publicKey;
+}
+
+std::string
+ECDHState::getBase64PubKey()
+{
+  if (context->publicKeyLen == 0) {
+    this->getRawSelfPubKey();
+  }
+  std::stringstream os;
+  security::transform::bufferSource(context->publicKey, context->publicKeyLen)
+    >> security::transform::base64Encode() >> security::transform::streamSink(os);
+  return os.str();
+}
+
+uint8_t*
+ECDHState::deriveSecret(const uint8_t* peerkey, int peerKeySize)
+{
+  auto privECKey = EVP_PKEY_get1_EC_KEY(context->privkey);
+
+  if (privECKey == nullptr) {
+    handleErrors("Could not get referenced key when calling EVP_PKEY_get1_EC_KEY().");
+    return nullptr;
+  }
+
+  auto group = EC_KEY_get0_group(privECKey);
+  auto peerPoint = EC_POINT_new(group);
+  EC_POINT_oct2point(group, peerPoint, peerkey, peerKeySize, nullptr);
+
+  if (0 == (context->sharedSecretLen = ECDH_compute_key(context->sharedSecret, 256,
+                                                        peerPoint, privECKey, nullptr))) {
+    EC_POINT_free(peerPoint);
+    EC_KEY_free(privECKey);
+    handleErrors("Cannot generate ECDH secret with ECDH_compute_key");
+  }
+  EC_POINT_free(peerPoint);
+  EC_KEY_free(privECKey);
+  return context->sharedSecret;
+}
+
+uint8_t*
+ECDHState::deriveSecret(const std::string& peerKeyStr)
+{
+  namespace t = ndn::security::transform;
+  OBufferStream os;
+  security::transform::bufferSource(peerKeyStr)
+    >> security::transform::base64Decode()
+    >> security::transform::streamSink(os);
+  ConstBufferPtr result = os.buf();
+  return this->deriveSecret(result->data(), result->size());
+}
+
+int ndn_compute_hmac_sha256 (const uint8_t *data, const unsigned  data_length,
+                             const uint8_t *key, const unsigned key_length,
+                             uint8_t *prk) {
+  OBufferStream os;
+
+  security::transform::bufferSource(data, data_length) >>
+    security::transform::hmacFilter(
+                                    DigestAlgorithm::SHA256, key, key_length) >>
+    security::transform::streamSink(os);
+
+  auto result = os.buf();
+  memcpy(prk, result->data(), HASH_SIZE);
+  return 0;
+}
+
+//removed dependency of OpenSSL@1.1
+int
+hkdf(const uint8_t* secret, int secretLen, const uint8_t* salt,
+     int saltLen, uint8_t* okm, int okm_len,
+     const uint8_t* info, int info_len)
+{
+  // hkdf generate prk
+  uint8_t prk[HASH_SIZE];
+  ndn_compute_hmac_sha256(salt, saltLen, secret, secretLen, prk);
+
+  // hkdf expand
+  uint8_t prev[HASH_SIZE] = {0};
+  int done_len = 0, dig_len = HASH_SIZE, n = okm_len / dig_len;
+  if (okm_len % dig_len) n++;
+  if (n > 255 || okm == nullptr) return 0;
+  for (int i = 1; i <= n; i++) {
+    size_t copy_len;
+    const uint8_t ctr = i;
+    OBufferStream os;
+    security::transform::StepSource source;
+
+    source >> security::transform::hmacFilter(DigestAlgorithm::SHA256, prk, dig_len)
+           >> security::transform::streamSink(os);
+
+    if (i > 1) {
+      source.write(prev, dig_len);
+    }
+
+    source.write(info, info_len);
+    source.write(&ctr, 1);
+    source.end();
+
+    auto result = os.buf();
+    memcpy(prev, result->data(), dig_len);
+
+    copy_len = (done_len + dig_len > okm_len) ?
+      okm_len - done_len :
+      dig_len;
+
+    memcpy(okm + done_len, prev, copy_len);
+    done_len += copy_len;
+  }
+  return done_len;
+}
+
+void
+handleErrors(const std::string& errorInfo)
+{
+  _LOG_DEBUG("Error in CRYPTO SUPPORT " << errorInfo);
+  BOOST_THROW_EXCEPTION(CryptoError("Error in CRYPTO SUPPORT: " + errorInfo));
+}
+
+} // namespace ndncert
+} // namespace ndn
diff --git a/src/crypto-support/crypto-helper.hpp b/src/crypto-support/crypto-helper.hpp
new file mode 100644
index 0000000..7bf6572
--- /dev/null
+++ b/src/crypto-support/crypto-helper.hpp
@@ -0,0 +1,90 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2017-2019, 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.
+ */
+
+#ifndef NDNCERT_CRYPTO_SUPPORT_CRYPTO_HELPER_HPP
+#define NDNCERT_CRYPTO_SUPPORT_CRYPTO_HELPER_HPP
+
+#include "certificate-request.hpp"
+#include <openssl/evp.h>
+#include <openssl/ec.h>
+
+static const int INFO_LEN = 10;
+static const uint8_t INFO[] = {0xf0,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9};
+
+namespace ndn {
+namespace ndncert {
+
+struct ECDH_CTX{
+  int EC_NID;
+  EVP_PKEY_CTX *ctx_params;
+  EVP_PKEY_CTX *ctx_keygen;
+  EVP_PKEY *privkey;
+  EVP_PKEY *peerkey;
+  EVP_PKEY *params;
+  uint8_t publicKey[256];
+  int publicKeyLen;
+  uint8_t sharedSecret[256];
+  int sharedSecretLen;
+};
+
+class ECDHState
+{
+public:
+  ECDHState();
+  ~ECDHState();
+
+  std::string
+  getBase64PubKey();
+
+  uint8_t*
+  deriveSecret(const std::string& peerKeyStr);
+  //unique_ptr<ECDH_CTX_T> context;
+  unique_ptr<ECDH_CTX> context;
+
+PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+  uint8_t*
+  deriveSecret(const uint8_t* peerkey, int peerKeySize);
+
+  uint8_t*
+  getRawSelfPubKey();
+};
+
+int
+hkdf(const uint8_t* secret, int secretLen, const uint8_t* salt,
+     int saltLen, uint8_t* okm, int okm_len,
+     const uint8_t* info=INFO, int info_len=INFO_LEN);
+
+int ndn_compute_hmac_sha256 (const uint8_t *data, const unsigned  data_length,
+                                    const uint8_t *key, const unsigned key_length,
+                                    uint8_t *prk);
+
+void
+handleErrors(const std::string& errorInfo);
+
+class CryptoError : public std::runtime_error
+{
+public:
+  using std::runtime_error::runtime_error;
+};
+
+} // namespace ndncert
+} // namespace ndn
+
+#endif // NDNCERT_CRYPTO_SUPPORT_CRYPTO_HELPER_HPP
diff --git a/src/crypto-support/enc-tlv.cpp b/src/crypto-support/enc-tlv.cpp
new file mode 100644
index 0000000..8260957
--- /dev/null
+++ b/src/crypto-support/enc-tlv.cpp
@@ -0,0 +1,78 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2017-2019, 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 "enc-tlv.hpp"
+#include "crypto-helper.hpp"
+#include <ndn-cxx/util/random.hpp>
+#include <ndn-cxx/security/transform/stream-sink.hpp>
+#include <ndn-cxx/encoding/buffer-stream.hpp>
+#include <ndn-cxx/security/transform/buffer-source.hpp>
+#include <ndn-cxx/security/transform/block-cipher.hpp>
+
+namespace ndn {
+namespace ndncert {
+
+const size_t DEFAULT_IV_SIZE = 16;
+
+Block
+genEncBlock(uint32_t tlv_type, const uint8_t* key, size_t keyLen, const uint8_t* payload, size_t payloadSize)
+{
+  Buffer iv;
+  iv.resize(DEFAULT_IV_SIZE);
+  random::generateSecureBytes(iv.data(), iv.size());
+
+  OBufferStream os;
+  security::transform::bufferSource(payload, payloadSize)
+    >> security::transform::blockCipher(BlockCipherAlgorithm::AES_CBC,
+                                        CipherOperator::ENCRYPT,
+                                        key, keyLen, iv.data(), iv.size())
+    >> security::transform::streamSink(os);
+    auto encryptedPayload = *os.buf();
+
+  // create the content block
+  auto content = makeEmptyBlock(tlv_type);
+  content.push_back(makeBinaryBlock(ENCRYPTED_PAYLOAD, encryptedPayload.data(), encryptedPayload.size()));
+  content.push_back(makeBinaryBlock(INITIAL_VECTOR, iv.data(), iv.size()));
+  content.encode();
+  return content;
+}
+
+Buffer
+parseEncBlock(const uint8_t* key, size_t keyLen, const Block& block)
+{
+  block.parse();
+  Buffer iv(block.get(INITIAL_VECTOR).value(),
+            block.get(INITIAL_VECTOR).value_size());
+  Buffer encryptedPayload(block.get(ENCRYPTED_PAYLOAD).value(),
+                          block.get(ENCRYPTED_PAYLOAD).value_size());
+
+  OBufferStream os;
+  security::transform::bufferSource(encryptedPayload.data(), encryptedPayload.size())
+    >> security::transform::blockCipher(BlockCipherAlgorithm::AES_CBC,
+                                        CipherOperator::DECRYPT,
+                                        key, keyLen, iv.data(), iv.size())
+    >> security::transform::streamSink(os);
+
+  auto payload = *os.buf();
+  return payload;
+}
+
+} // namespace ndncert
+} // namespace ndn
diff --git a/src/crypto-support/enc-tlv.hpp b/src/crypto-support/enc-tlv.hpp
new file mode 100644
index 0000000..07f17a5
--- /dev/null
+++ b/src/crypto-support/enc-tlv.hpp
@@ -0,0 +1,44 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2017-2019, 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.
+ */
+
+#ifndef NDNCERT_CRYPTO_SUPPORT_ENC_TLV_HPP
+#define NDNCERT_CRYPTO_SUPPORT_ENC_TLV_HPP
+
+#include <ndn-cxx/encoding/block-helpers.hpp>
+
+namespace ndn {
+namespace ndncert {
+
+enum {
+  ENCRYPTED_PAYLOAD = 630,
+  INITIAL_VECTOR = 632,
+};
+
+Block
+genEncBlock(uint32_t tlv_type, const uint8_t* key, size_t keyLen, const uint8_t* payload, size_t payloadSize);
+
+Buffer
+parseEncBlock(const uint8_t* key, size_t keyLen, const Block& block);
+
+
+} // namespace ndncert
+} // namespace ndn
+
+#endif // NDNCERT_CRYPTO_SUPPORT_ENC_TLV_HPP
diff --git a/src/json-helper.cpp b/src/json-helper.cpp
deleted file mode 100644
index 81fa829..0000000
--- a/src/json-helper.cpp
+++ /dev/null
@@ -1,84 +0,0 @@
-/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * Copyright (c) 2017, 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 "json-helper.hpp"
-#include <boost/lexical_cast.hpp>
-
-namespace ndn {
-namespace ndncert {
-
-const JsonSection
-genResponseProbeJson(const Name& identifier, const Name& caInformation)
-{
-  JsonSection root;
-
-  root.put(JSON_IDNENTIFIER, identifier.toUri());
-  root.put(JSON_CA_INFO, caInformation.toUri());
-
-  return root;
-}
-
-const JsonSection
-genResponseNewJson(const std::string& requestId, const std::string& status,
-                   const std::list<std::string>& challenges)
-{
-  JsonSection root;
-  JsonSection challengesSection;
-  root.put(JSON_REQUEST_ID, requestId);
-  root.put(JSON_STATUS, status);
-
-  for (const auto& entry : challenges) {
-    JsonSection challenge;
-    challenge.put(JSON_CHALLENGE_TYPE, entry);
-    challengesSection.push_back(std::make_pair("", challenge));
-  }
-  root.add_child(JSON_CHALLENGES, challengesSection);
-
-  return root;
-}
-
-const JsonSection
-genResponseChallengeJson(const std::string& requestId, const std::string& challengeType,
-                         const std::string& status, const Name& name)
-{
-  JsonSection root;
-  root.put(JSON_REQUEST_ID, requestId);
-  root.put(JSON_CHALLENGE_TYPE, challengeType);
-  root.put(JSON_STATUS, status);
-  if (name.toUri() != "") {
-    root.put(JSON_CERTIFICATE, name.toUri());
-  }
-  return root;
-}
-
-const JsonSection
-genFailureJson(const std::string& requestId, const std::string& challengeType,
-               const std::string& status, const std::string& failureInfo)
-{
-  JsonSection root;
-  root.put(JSON_REQUEST_ID, requestId);
-  root.put(JSON_CHALLENGE_TYPE, challengeType);
-  root.put(JSON_STATUS, status);
-  root.put(JSON_FAILURE_INFO, failureInfo);
-  return root;
-}
-
-} // namespace ndncert
-} // namespace ndn
diff --git a/src/json-helper.hpp b/src/json-helper.hpp
deleted file mode 100644
index 4540596..0000000
--- a/src/json-helper.hpp
+++ /dev/null
@@ -1,116 +0,0 @@
-/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * Copyright (c) 2017, 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.
- */
-
-#ifndef NDNCERT_JSON_HELPER_HPP
-#define NDNCERT_JSON_HELPER_HPP
-
-#include "certificate-request.hpp"
-
-namespace ndn {
-namespace ndncert {
-
-const std::string JSON_IDNENTIFIER = "identifier";
-const std::string JSON_CA_INFO = "ca-info";
-const std::string JSON_STATUS = "status";
-const std::string JSON_REQUEST_ID = "request-id";
-const std::string JSON_CHALLENGES = "challenges";
-const std::string JSON_CHALLENGE_TYPE = "challenge-type";
-const std::string JSON_FAILURE_INFO = "failure-info";
-const std::string JSON_CERTIFICATE = "certificate";
-
-/**
- * @brief Generate JSON file to response PROBE insterest
- *
- * Target JSON format:
- * {
- *   "identifier": "@p identifier",
- *   "ca-info": "@p caInformation"
- * }
- */
-const JsonSection
-genResponseProbeJson(const Name& identifier, const Name& caInformation);
-
-/**
- * @brief Generate JSON file to response NEW interest
- *
- * Target JSON format:
- * {
- *   "request-id": "@p requestId",
- *   "status": "@p status",
- *   "challenges": [
- *     {
- *       "challenge-type": ""
- *     },
- *     {
- *       "challenge-type": ""
- *     },
- *     ...
- *   ]
- * }
- */
-const JsonSection
-genResponseNewJson(const std::string& requestId, const std::string& status,
-                   const std::list<std::string>& challenges);
-
-/**
- * @brief Generate JSON file to response _SELECT, _VALIDATE, and _STATUS interest
- *
- * if certificate name is not present:
- *
- * Target JSON format:
- * {
- *   "request-id": "@p requestId",
- *   "challenge-type": "@p challengeType",
- *   "status": "@p status"
- * }
- *
- * if certificate name is present:
- *
- * Target JSON format:
- * {
- *   "request-id": "@p requestId",
- *   "challenge-type": "@p challengeType",
- *   "status": "@p status",
- *   "certificate":"@p name"
- * }
- */
-const JsonSection
-genResponseChallengeJson(const std::string& requestId, const std::string& challengeType,
-                         const std::string& status, const Name& name = Name());
-
-/**
- * @brief Generate JSON file when there is an Error
- *
- * Target JSON format:
- * {
- *   "request-id": "@p requestId",
- *   "challenge-type": "@p challengeType",
- *   "status": "failure",
- *   "failure-info": "@p errorInfo",
- * }
- */
-const JsonSection
-genFailureJson(const std::string& requestId, const std::string& challengeType,
-               const std::string& status, const std::string& failureInfo);
-
-} // namespace ndncert
-} // namespace ndn
-
-#endif // NDNCERT_JSON_HELPER_HPP
diff --git a/src/ndncert-common.hpp b/src/ndncert-common.hpp
index b7e4a60..bf314cd 100644
--- a/src/ndncert-common.hpp
+++ b/src/ndncert-common.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2017, Regents of the University of California.
+ * Copyright (c) 2017-2019, Regents of the University of California.
  *
  * This file is part of ndncert, a certificate management system based on NDN.
  *
@@ -70,19 +70,15 @@
 namespace ndncert {
 
 using std::size_t;
-
 using boost::noncopyable;
-
 using std::shared_ptr;
 using std::unique_ptr;
 using std::weak_ptr;
 using std::make_shared;
 using ndn::make_unique;
 using std::enable_shared_from_this;
-
 using std::function;
 using std::bind;
-
 using ndn::Interest;
 using ndn::Data;
 using ndn::Name;
@@ -91,6 +87,44 @@
 using ndn::time::system_clock;
 using ndn::time::toUnixTimestamp;
 
+// JSON format for Certificate Issuer (CA)
+const std::string JSON_CA_NAME = "name";
+const std::string JSON_CA_CONFIG = "ca-config";
+const std::string JSON_CA_ECDH = "ecdh-pub";
+const std::string JSON_CA_SALT = "salt";
+const std::string JSON_CA_EQUEST_ID = "request-id";
+const std::string JSON_CA_STATUS = "status";
+const std::string JSON_CA_CHALLENGES = "challenges";
+const std::string JSON_CA_CHALLENGE_ID = "challenge-id";
+const std::string JSON_CA_CERT_ID = "certificate-id";
+
+// JSON format for Challenge Module
+const std::string JSON_CHALLENGE_STATUS = "challenge-status";
+const std::string JSON_CHALLENGE_REMAINING_TRIES = "remaining-tries";
+const std::string JSON_CHALLENGE_REMAINING_TIME = "remaining-time";
+
+// JSON format for Certificate Requester
+const std::string JSON_CLIENT_PROBE_INFO = "probe-info";
+const std::string JSON_CLIENT_ECDH = "ecdh-pub";
+const std::string JSON_CLIENT_CERT_REQ = "cert-request";
+const std::string JSON_CLIENT_SELECTED_CHALLENGE = "selected-challenge";
+
+// NDNCERT Status Enum
+enum {
+  STATUS_BEFORE_CHALLENGE = 0,
+  STATUS_CHALLENGE = 1,
+  STATUS_PENDING = 2,
+  STATUS_SUCCESS = 3,
+  STATUS_FAILURE = 4,
+  STATUS_NOT_STARTED = 5
+};
+
+// Pre-defined challenge status
+const std::string CHALLENGE_STATUS_SUCCESS = "success";
+const std::string CHALLENGE_STATUS_FAILURE_TIMEOUT = "failure-timeout";
+const std::string CHALLENGE_STATUS_FAILURE_MAXRETRY = "failure-max-retry";
+const std::string CHALLENGE_STATUS_UNKNOWN_CHALLENGE = "unknown-challenge";
+
 } // namespace ndncert
 } // namespace ndn
 
diff --git a/tests/boost-test.hpp b/tests/boost-test.hpp
index 220a481..bb32232 100644
--- a/tests/boost-test.hpp
+++ b/tests/boost-test.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2014-2017, Regents of the University of California,
+ * Copyright (c) 2014-2019, Regents of the University of California,
  *                          Arizona Board of Regents,
  *                          Colorado State University,
  *                          University Pierre & Marie Curie, Sorbonne University,
@@ -25,15 +25,16 @@
  * See AUTHORS.md for complete list of ndncert authors and contributors.
  */
 
-#ifndef CERT_TESTS_BOOST_TEST_HPP
-#define CERT_TESTS_BOOST_TEST_HPP
+#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>
 #include <boost/concept_check.hpp>
 #include <boost/test/output_test_stream.hpp>
 
-#endif // CERT_TESTS_BOOST_TEST_HPP
+#endif // NDNCERT_TESTS_BOOST_TEST_HPP
diff --git a/tests/database-fixture.hpp b/tests/database-fixture.hpp
index fc9da55..ffe00b0 100644
--- a/tests/database-fixture.hpp
+++ b/tests/database-fixture.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2014-2017, Regents of the University of California,
+ * Copyright (c) 2014-2019, Regents of the University of California,
  *                          Arizona Board of Regents,
  *                          Colorado State University,
  *                          University Pierre & Marie Curie, Sorbonne University,
@@ -36,7 +36,7 @@
 namespace ndncert {
 namespace tests {
 
-class DatabaseFixture : public IdentityManagementV2TimeFixture
+class DatabaseFixture : public IdentityManagementTimeFixture
 {
 public:
   DatabaseFixture()
diff --git a/tests/global-configuration.cpp b/tests/global-configuration.cpp
deleted file mode 100644
index 76de542..0000000
--- a/tests/global-configuration.cpp
+++ /dev/null
@@ -1,70 +0,0 @@
-/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * Copyright (c) 2017-2018, 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
diff --git a/tests/identity-management-fixture.cpp b/tests/identity-management-fixture.cpp
index aeaab40..a152f73 100644
--- a/tests/identity-management-fixture.cpp
+++ b/tests/identity-management-fixture.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2014-2017, Regents of the University of California,
+ * Copyright (c) 2014-2019, Regents of the University of California,
  *                          Arizona Board of Regents,
  *                          Colorado State University,
  *                          University Pierre & Marie Curie, Sorbonne University,
@@ -34,13 +34,36 @@
 namespace ndncert {
 namespace tests {
 
-IdentityManagementV2Fixture::IdentityManagementV2Fixture()
+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
-IdentityManagementV2Fixture::addIdentity(const Name& identityName, const KeyParams& params)
+IdentityManagementFixture::addIdentity(const Name& identityName, const KeyParams& params)
 {
   auto identity = m_keyChain.createIdentity(identityName, params);
   m_identities.insert(identityName);
@@ -48,8 +71,7 @@
 }
 
 bool
-IdentityManagementV2Fixture::saveIdentityCertificate(const security::Identity& identity,
-                                                     const std::string& filename)
+IdentityManagementFixture::saveCertificate(const security::Identity& identity, const std::string& filename)
 {
   try {
     auto cert = identity.getDefaultKey().getDefaultCertificate();
@@ -61,20 +83,20 @@
 }
 
 security::Identity
-IdentityManagementV2Fixture::addSubCertificate(const Name& subIdentityName,
-                                               const security::Identity& issuer, const KeyParams& params)
+IdentityManagementFixture::addSubCertificate(const Name& subIdentityName,
+                                             const security::Identity& issuer, const KeyParams& params)
 {
   auto subIdentity = addIdentity(subIdentityName, params);
 
-  security::v2::Certificate request = subIdentity.getDefaultKey().getDefaultCertificate();
+  v2::Certificate request = subIdentity.getDefaultKey().getDefaultCertificate();
 
   request.setName(request.getKeyName().append("parent").appendVersion());
 
   SignatureInfo info;
-  info.setValidityPeriod(security::ValidityPeriod(time::system_clock::now(),
-                                                  time::system_clock::now() + time::days(7300)));
+  auto now = time::system_clock::now();
+  info.setValidityPeriod(security::ValidityPeriod(now, now + 7300_days));
 
-  security::v2::AdditionalDescription description;
+  v2::AdditionalDescription description;
   description.set("type", "sub-certificate");
   info.appendTypeSpecificTlv(description.wireEncode());
 
@@ -84,45 +106,32 @@
   return subIdentity;
 }
 
-security::v2::Certificate
-IdentityManagementV2Fixture::addCertificate(const security::Key& key, const std::string& issuer)
+v2::Certificate
+IdentityManagementFixture::addCertificate(const security::Key& key, const std::string& issuer)
 {
   Name certificateName = key.getName();
   certificateName
     .append(issuer)
     .appendVersion();
-  security::v2::Certificate certificate;
+  v2::Certificate certificate;
   certificate.setName(certificateName);
 
   // set metainfo
   certificate.setContentType(tlv::ContentType_Key);
-  certificate.setFreshnessPeriod(time::hours(1));
+  certificate.setFreshnessPeriod(1_h);
 
   // set content
   certificate.setContent(key.getPublicKey().data(), key.getPublicKey().size());
 
   // set signature-info
   SignatureInfo info;
-  info.setValidityPeriod(security::ValidityPeriod(time::system_clock::now(),
-                                                  time::system_clock::now() + time::days(10)));
+  auto now = time::system_clock::now();
+  info.setValidityPeriod(security::ValidityPeriod(now, now + 10_days));
 
   m_keyChain.sign(certificate, signingByKey(key).setSignatureInfo(info));
   return certificate;
 }
 
-bool
-IdentityManagementV2Fixture::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;
-  }
-}
-
 } // namespace tests
 } // namespace ndncert
 } // namespace ndn
diff --git a/tests/identity-management-fixture.hpp b/tests/identity-management-fixture.hpp
index 68a6d14..d287fc9 100644
--- a/tests/identity-management-fixture.hpp
+++ b/tests/identity-management-fixture.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2014-2017, Regents of the University of California,
+ * Copyright (c) 2014-2019, Regents of the University of California,
  *                          Arizona Board of Regents,
  *                          Colorado State University,
  *                          University Pierre & Marie Curie, Sorbonne University,
@@ -29,6 +29,7 @@
 #define NDNCERT_TESTS_IDENTITY_MANAGEMENT_FIXTURE_HPP
 
 #include "test-common.hpp"
+#include "test-home-fixture.hpp"
 #include <ndn-cxx/security/v2/key-chain.hpp>
 #include <ndn-cxx/security/v2/additional-description.hpp>
 #include <ndn-cxx/security/signing-helpers.hpp>
@@ -37,23 +38,38 @@
 namespace ndncert {
 namespace tests {
 
+class IdentityManagementBaseFixture : public TestHomeFixture<DefaultPibDir>
+{
+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 IdentityManagementV2Fixture
+class IdentityManagementFixture : public IdentityManagementBaseFixture
 {
 public:
-  IdentityManagementV2Fixture();
+  IdentityManagementFixture();
 
   /**
    * @brief Add identity @p identityName
    * @return name of the created self-signed certificate
    */
   security::Identity
-  addIdentity(const Name& identityName, const KeyParams& params = security::v2::KeyChain::getDefaultKeyParams());
+  addIdentity(const Name& identityName,
+              const KeyParams& params = security::v2::KeyChain::getDefaultKeyParams());
 
   /**
    *  @brief Save identity certificate to a file
@@ -62,7 +78,7 @@
    *  @return whether successful
    */
   bool
-  saveIdentityCertificate(const security::Identity& identity, const std::string& filename);
+  saveCertificate(const security::Identity& identity, const std::string& filename);
 
   /**
    * @brief Issue a certificate for \p subIdentityName signed by \p issuer
@@ -83,20 +99,15 @@
   security::v2::Certificate
   addCertificate(const security::Key& key, const std::string& issuer);
 
-  bool
-  saveCertToFile(const Data& obj, const std::string& filename);
-
 protected:
-  std::set<Name> m_identities;
-  std::set<std::string> m_certFiles;
-  security::v2::KeyChain m_keyChain;
+  KeyChain m_keyChain;
 };
 
 /** \brief convenience base class for inheriting from both UnitTestTimeFixture
  *         and IdentityManagementV2Fixture
  */
-class IdentityManagementV2TimeFixture : public UnitTestTimeFixture
-                                      , public IdentityManagementV2Fixture
+class IdentityManagementTimeFixture : public UnitTestTimeFixture
+                                    , public IdentityManagementFixture
 {
 };
 
diff --git a/tests/main.cpp b/tests/main.cpp
index d6380c6..96026e9 100644
--- a/tests/main.cpp
+++ b/tests/main.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2014-2017, Regents of the University of California,
+ * Copyright (c) 2014-2019, Regents of the University of California,
  *                          Arizona Board of Regents,
  *                          Colorado State University,
  *                          University Pierre & Marie Curie, Sorbonne University,
@@ -114,4 +114,4 @@
   return ::boost::unit_test::unit_test_main(&init_tests, argc, argv);
 }
 
-#endif // BOOST_VERSION >= 106200
+#endif // BOOST_VERSION >= 106200
\ No newline at end of file
diff --git a/tests/test-common.cpp b/tests/test-common.cpp
deleted file mode 100644
index 42cbf37..0000000
--- a/tests/test-common.cpp
+++ /dev/null
@@ -1,136 +0,0 @@
-/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * Copyright (c) 2014-2017, 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.
- */
-
-#include "test-common.hpp"
-
-#include <ndn-cxx/util/sha256.hpp>
-#include <ndn-cxx/security/signature-sha256-with-rsa.hpp>
-
-namespace ndn {
-namespace ndncert {
-namespace tests {
-
-UnitTestTimeFixture::UnitTestTimeFixture()
-  : steadyClock(make_shared<time::UnitTestSteadyClock>())
-  , systemClock(make_shared<time::UnitTestSystemClock>())
-{
-  time::setCustomClocks(steadyClock, systemClock);
-}
-
-UnitTestTimeFixture::~UnitTestTimeFixture()
-{
-  time::setCustomClocks(nullptr, nullptr);
-}
-
-void
-UnitTestTimeFixture::advanceClocks(const time::nanoseconds& tick, size_t nTicks)
-{
-  this->advanceClocks(tick, tick * nTicks);
-}
-
-void
-UnitTestTimeFixture::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 (m_io.stopped())
-      m_io.reset();
-    m_io.poll();
-  }
-}
-
-shared_ptr<Interest>
-makeInterest(const Name& name, uint32_t nonce)
-{
-  auto interest = make_shared<Interest>(name);
-  if (nonce != 0) {
-    interest->setNonce(nonce);
-  }
-  return interest;
-}
-
-shared_ptr<Data>
-makeData(const Name& name)
-{
-  auto data = make_shared<Data>(name);
-  return signData(data);
-}
-
-Data&
-signData(Data& data)
-{
-  ndn::SignatureSha256WithRsa fakeSignature;
-  fakeSignature.setValue(ndn::encoding::makeEmptyBlock(tlv::SignatureValue));
-  data.setSignature(fakeSignature);
-  data.wireEncode();
-
-  return data;
-}
-
-shared_ptr<Link>
-makeLink(const Name& name, std::initializer_list<Delegation> delegations)
-{
-  auto link = make_shared<Link>(name, delegations);
-  signData(link);
-  return link;
-}
-
-lp::Nack
-makeNack(const Name& name, uint32_t nonce, lp::NackReason reason)
-{
-  Interest interest(name);
-  interest.setNonce(nonce);
-  lp::Nack nack(std::move(interest));
-  nack.setReason(reason);
-  return nack;
-}
-
-ConstBufferPtr
-digestFromFile(const boost::filesystem::path& filename)
-{
-  boost::filesystem::ifstream iff(filename, std::ios::in | std::ios::binary);
-  util::Sha256 digest(iff);
-  return digest.computeDigest();
-}
-
-} // namespace tests
-} // namespace ndncert
-} // namespace ndn
diff --git a/tests/test-common.hpp b/tests/test-common.hpp
index 3d5dec1..11d7feb 100644
--- a/tests/test-common.hpp
+++ b/tests/test-common.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2014-2017, Regents of the University of California,
+ * Copyright (c) 2014-2019, Regents of the University of California,
  *                          Arizona Board of Regents,
  *                          Colorado State University,
  *                          University Pierre & Marie Curie, Sorbonne University,
@@ -28,150 +28,85 @@
 #ifndef NDNCERT_TESTS_TEST_COMMON_HPP
 #define NDNCERT_TESTS_TEST_COMMON_HPP
 
-#include "logging.hpp"
-
 #include "boost-test.hpp"
-
 #include <boost/asio/io_service.hpp>
-#include <boost/filesystem.hpp>
-#include <boost/filesystem/fstream.hpp>
-
-#include <ndn-cxx/link.hpp>
-#include <ndn-cxx/name.hpp>
-#include <ndn-cxx/data.hpp>
-#include <ndn-cxx/lp/nack.hpp>
 #include <ndn-cxx/util/time-unit-test-clock.hpp>
-#include <ndn-cxx/util/string-helper.hpp>
 
 namespace ndn {
 namespace ndncert {
 namespace tests {
 
-/** \brief base test fixture
- *
- *  Every test case should be based on this fixture,
- *  to have per test case io_service initialization.
+/** \brief a test fixture that overrides steady clock and system clock
  */
-class BaseFixture
+class UnitTestTimeFixture
 {
-protected:
-  /** \brief reference to global io_service
-   */
-  boost::asio::io_service m_io;
-};
+public:
+  UnitTestTimeFixture()
+    : steadyClock(make_shared<time::UnitTestSteadyClock>())
+    , systemClock(make_shared<time::UnitTestSystemClock>())
+  {
+    time::setCustomClocks(steadyClock, systemClock);
+  }
 
-/** \brief a base test fixture that overrides steady clock and system clock
- */
-class UnitTestTimeFixture : public virtual BaseFixture
-{
-protected:
-  UnitTestTimeFixture();
-
-  ~UnitTestTimeFixture();
+  ~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, global io_service is polled to process pending I/O events.
+   *  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);
+  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, global io_service is polled to process pending I/O events.
+   *  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);
+  advanceClocks(const time::nanoseconds& tick, const time::nanoseconds& total)
+  {
+    BOOST_ASSERT(tick > time::nanoseconds::zero());
+    BOOST_ASSERT(total >= time::nanoseconds::zero());
 
-protected:
+    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 (m_io.stopped())
+        m_io.reset();
+      m_io.poll();
+    }
+  }
+
+public:
   shared_ptr<time::UnitTestSteadyClock> steadyClock;
   shared_ptr<time::UnitTestSystemClock> systemClock;
-
-  friend class LimitedIo;
+  boost::asio::io_service m_io;
 };
 
-/** \brief create an Interest
- *  \param name Interest name
- *  \param nonce if non-zero, set Nonce to this value
- *               (useful for creating Nack with same Nonce)
- */
-shared_ptr<Interest>
-makeInterest(const Name& name, uint32_t nonce = 0);
-
-/** \brief create a Data with fake signature
- *  \note Data may be modified afterwards without losing the fake signature.
- *        If a real signature is desired, sign again with KeyChain.
- */
-shared_ptr<Data>
-makeData(const Name& name);
-
-/** \brief add a fake signature to Data
- */
-Data&
-signData(Data& data);
-
-/** \brief add a fake signature to Data
- */
-inline shared_ptr<Data>
-signData(shared_ptr<Data> data)
-{
-  signData(*data);
-  return data;
-}
-
-/** \brief create a Link object with fake signature
- *  \note Link may be modified afterwards without losing the fake signature.
- *        If a real signature is desired, sign again with KeyChain.
- */
-shared_ptr<Link>
-makeLink(const Name& name, std::initializer_list<Delegation> delegations);
-
-/** \brief create a Nack
- *  \param name Interest name
- *  \param nonce Interest nonce
- *  \param reason Nack reason
- */
-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);
-}
-
-/** \brief convert file to digest
- */
-ndn::ConstBufferPtr
-digestFromFile(const boost::filesystem::path& filename);
-
 } // namespace tests
 } // namespace ndncert
 } // namespace ndn
diff --git a/tests/test-home-fixture.hpp b/tests/test-home-fixture.hpp
new file mode 100644
index 0000000..775e38a
--- /dev/null
+++ b/tests/test-home-fixture.hpp
@@ -0,0 +1,130 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2019 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_TEST_HOME_FIXTURE_HPP
+#define NDN_TESTS_TEST_HOME_FIXTURE_HPP
+
+#include "ndn-cxx/security/v2/key-chain.hpp"
+
+#include <cstdlib>
+#include <fstream>
+#include <initializer_list>
+
+#include <boost/algorithm/string.hpp>
+#include <boost/filesystem.hpp>
+
+namespace ndn {
+namespace ndncert {
+namespace tests {
+
+/**
+ * @brief Fixture to adjust/restore NDN_CLIENT_PIB and NDN_CLIENT_TPM paths
+ *
+ * Note that the specified PATH will be removed after fixture is destroyed.
+ * **Do not specify non-temporary paths.**
+ */
+template<class Path>
+class PibDirFixture
+{
+public:
+  PibDirFixture()
+    : m_pibDir(Path().PATH)
+  {
+    if (std::getenv("NDN_CLIENT_PIB") != nullptr) {
+      m_oldPib = std::getenv("NDN_CLIENT_PIB");
+    }
+    if (std::getenv("NDN_CLIENT_TPM") != nullptr) {
+      m_oldTpm = std::getenv("NDN_CLIENT_TPM");
+    }
+
+    /// @todo Consider change to an in-memory PIB/TPM
+    setenv("NDN_CLIENT_PIB", ("pib-sqlite3:" + m_pibDir).c_str(), true);
+    setenv("NDN_CLIENT_TPM", ("tpm-file:" + m_pibDir).c_str(), true);
+  }
+
+  ~PibDirFixture()
+  {
+    if (!m_oldPib.empty()) {
+      setenv("NDN_CLIENT_PIB", m_oldPib.data(), true);
+    }
+    else {
+      unsetenv("NDN_CLIENT_PIB");
+    }
+
+    if (!m_oldTpm.empty()) {
+      setenv("NDN_CLIENT_TPM", m_oldTpm.data(), true);
+    }
+    else {
+      unsetenv("NDN_CLIENT_TPM");
+    }
+
+    boost::filesystem::remove_all(m_pibDir);
+    // const_cast<std::string&>(security::v2::KeyChain::getDefaultPibLocator()).clear();
+    // const_cast<std::string&>(security::v2::KeyChain::getDefaultTpmLocator()).clear();
+  }
+
+protected:
+  const std::string m_pibDir;
+
+private:
+  std::string m_oldPib;
+  std::string m_oldTpm;
+};
+
+/**
+ * @brief Extension of PibDirFixture to set TEST_HOME variable and allow config file creation
+ */
+template<class Path>
+class TestHomeFixture : public PibDirFixture<Path>
+{
+public:
+  TestHomeFixture()
+  {
+    setenv("TEST_HOME", this->m_pibDir.c_str(), true);
+  }
+
+  ~TestHomeFixture()
+  {
+    unsetenv("TEST_HOME");
+  }
+
+  void
+  createClientConf(std::initializer_list<std::string> lines) const
+  {
+    boost::filesystem::create_directories(boost::filesystem::path(this->m_pibDir) / ".ndn");
+    std::ofstream of((boost::filesystem::path(this->m_pibDir) / ".ndn" / "client.conf").c_str());
+    for (auto line : lines) {
+      boost::replace_all(line, "%PATH%", this->m_pibDir);
+      of << line << std::endl;
+    }
+  }
+};
+
+struct DefaultPibDir
+{
+  const std::string PATH = "build/keys";
+};
+
+} // namespace tests
+} // namespace ndncert
+} // namespace ndn
+
+#endif // NDN_TESTS_TEST_HOME_FIXTURE_HPP
diff --git a/tests/unit-tests/ca-config.t.cpp b/tests/unit-tests/ca-config.t.cpp
index 1039e97..78aa9da 100644
--- a/tests/unit-tests/ca-config.t.cpp
+++ b/tests/unit-tests/ca-config.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2017, Regents of the University of California.
+ * Copyright (c) 2017-2019, Regents of the University of California.
  *
  * This file is part of ndncert, a certificate management system based on NDN.
  *
@@ -29,51 +29,17 @@
 namespace ndncert {
 namespace tests {
 
-BOOST_FIXTURE_TEST_SUITE(TestCaConfig, IdentityManagementV2Fixture)
+BOOST_FIXTURE_TEST_SUITE(TestCaConfig, IdentityManagementFixture)
 
 BOOST_AUTO_TEST_CASE(ReadConfigFileWithFileAnchor)
 {
   CaConfig config;
   config.load("tests/unit-tests/ca.conf.test");
-
-  int count = 0;
-  for (auto item : config.m_caItems) {
-    if (item.m_caName.toUri() == "/ndn") {
-      BOOST_CHECK_EQUAL(item.m_freshnessPeriod, time::seconds(720));
-      BOOST_CHECK_EQUAL(item.m_validityPeriod, time::days(360));
-      BOOST_CHECK_EQUAL(item.m_probe, "input email address");
-      BOOST_CHECK_EQUAL(item.m_caInfo, "ndn testbed ca");
-      BOOST_CHECK_EQUAL(item.m_targetedList,
-                        "Use your email address (edu preferred) as input");
-      BOOST_CHECK_EQUAL(item.m_relatedCaList.size(), 2);
-
-      // check related ca
-      auto relatedCaA = item.m_relatedCaList.front();
-      BOOST_CHECK_EQUAL(relatedCaA.toUri(), "/ndn/edu/arizona");
-      auto relatedCaB = item.m_relatedCaList.back();
-      BOOST_CHECK_EQUAL(relatedCaB.toUri(), "/ndn/edu/memphis");
-
-      BOOST_CHECK_EQUAL(count, 0);
-      count++;
-    }
-    else if (item.m_caName.toUri() == "/ndn/edu/ucla/cs/zhiyi") {
-      BOOST_CHECK_EQUAL(item.m_probe, "");
-      BOOST_CHECK_EQUAL(item.m_freshnessPeriod, time::seconds(720));
-      BOOST_CHECK_EQUAL(item.m_validityPeriod, time::days(360));
-      BOOST_CHECK_EQUAL(item.m_supportedChallenges.size(), 1);
-
-      BOOST_CHECK_EQUAL(count, 1);
-      count++;
-    }
-    else if (item.m_caName.toUri() == "/ndn/site1") {
-      BOOST_CHECK(item.m_probe != "");
-      BOOST_CHECK_EQUAL(item.m_freshnessPeriod, time::seconds(720));
-      BOOST_CHECK_EQUAL(item.m_validityPeriod, time::days(360));
-      BOOST_CHECK_EQUAL(item.m_supportedChallenges.size(), 1);
-
-      BOOST_CHECK_EQUAL(count, 2);
-    }
-  }
+  BOOST_CHECK_EQUAL(config.m_caName.toUri(), "/ndn");
+  BOOST_CHECK_EQUAL(config.m_freshnessPeriod, time::seconds(720));
+  BOOST_CHECK_EQUAL(config.m_validityPeriod, time::days(360));
+  BOOST_CHECK_EQUAL(config.m_probe, "input email address");
+  BOOST_CHECK_EQUAL(config.m_caInfo, "ndn testbed ca");
 }
 
 BOOST_AUTO_TEST_SUITE_END() // TestCaConfig
diff --git a/tests/unit-tests/ca-memory.t.cpp b/tests/unit-tests/ca-memory.t.cpp
index a717e31..3a9c2c4 100644
--- a/tests/unit-tests/ca-memory.t.cpp
+++ b/tests/unit-tests/ca-memory.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2017, Regents of the University of California.
+ * Copyright (c) 2017-2019, Regents of the University of California.
  *
  * This file is part of ndncert, a certificate management system based on NDN.
  *
@@ -18,15 +18,15 @@
  * See AUTHORS.md for complete list of ndncert authors and contributors.
  */
 
-#include "identity-management-fixture.hpp"
 #include "ca-detail/ca-memory.hpp"
+#include "identity-management-fixture.hpp"
 #include "ca-detail/ca-sqlite.hpp"
 
 namespace ndn {
 namespace ndncert {
 namespace tests {
 
-BOOST_FIXTURE_TEST_SUITE(TestCaMemory, IdentityManagementV2TimeFixture)
+BOOST_FIXTURE_TEST_SUITE(TestCaMemory, IdentityManagementFixture)
 
 BOOST_AUTO_TEST_CASE(Initialization)
 {
@@ -82,31 +82,31 @@
   auto cert1 = key1.getDefaultCertificate();
 
   // add operation
-  CertificateRequest request1(Name("/ndn/site1"), "123", cert1);
+  CertificateRequest request1(Name("/ndn/site1"), "123", STATUS_BEFORE_CHALLENGE, cert1);
   BOOST_CHECK_NO_THROW(storage.addRequest(request1));
 
   // get operation
   auto result = storage.getRequest("123");
-  BOOST_CHECK_EQUAL(request1.getCert(), result.getCert());
-  BOOST_CHECK_EQUAL(request1.getStatus(), result.getStatus());
-  BOOST_CHECK_EQUAL(request1.getCaName(), result.getCaName());
+  BOOST_CHECK_EQUAL(request1.m_cert, result.m_cert);
+  BOOST_CHECK_EQUAL(request1.m_status, result.m_status);
+  BOOST_CHECK_EQUAL(request1.m_caName, result.m_caName);
 
   JsonSection json;
   json.put("code", "1234");
 
   // update operation
-  CertificateRequest request2(Name("/ndn/site1"), "123", "need-verify", "EMAIL",
-                              CaSqlite::convertJson2String(json), cert1);
+  CertificateRequest request2(Name("/ndn/site1"), "123", STATUS_CHALLENGE, CHALLENGE_STATUS_SUCCESS,
+                             "Email", time::toIsoString(time::system_clock::now()), 3600, 3, json, cert1);
   storage.updateRequest(request2);
   result = storage.getRequest("123");
-  BOOST_CHECK_EQUAL(request2.getCert(), result.getCert());
-  BOOST_CHECK_EQUAL(request2.getStatus(), result.getStatus());
-  BOOST_CHECK_EQUAL(request2.getCaName(), result.getCaName());
+  BOOST_CHECK_EQUAL(request2.m_cert, result.m_cert);
+  BOOST_CHECK_EQUAL(request2.m_status, result.m_status);
+  BOOST_CHECK_EQUAL(request2.m_caName, result.m_caName);
 
   auto identity2 = addIdentity(Name("/ndn/site2"));
   auto key2 = identity2.getDefaultKey();
   auto cert2 = key2.getDefaultCertificate();
-  CertificateRequest request3(Name("/ndn/site2"), "456", cert2);
+  CertificateRequest request3(Name("/ndn/site2"), "456", STATUS_BEFORE_CHALLENGE, cert2);
   storage.addRequest(request3);
 
   // list operation
diff --git a/tests/unit-tests/ca-module.t.cpp b/tests/unit-tests/ca-module.t.cpp
index 5d23a9f..34e2721 100644
--- a/tests/unit-tests/ca-module.t.cpp
+++ b/tests/unit-tests/ca-module.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2017, Regents of the University of California.
+ * Copyright (c) 2017-2019, Regents of the University of California.
  *
  * This file is part of ndncert, a certificate management system based on NDN.
  *
@@ -19,15 +19,16 @@
  */
 
 #include "ca-module.hpp"
-
 #include "database-fixture.hpp"
 #include "client-module.hpp"
 #include "challenge-module.hpp"
-
+#include "challenge-module/challenge-pin.hpp"
+#include "challenge-module/challenge-email.hpp"
 #include <ndn-cxx/util/dummy-client-face.hpp>
 #include <ndn-cxx/security/signing-helpers.hpp>
 #include <ndn-cxx/security/transform/public-key.hpp>
 #include <ndn-cxx/security/verification-helpers.hpp>
+#include <iostream>
 
 namespace ndn {
 namespace ndncert {
@@ -39,8 +40,7 @@
 {
   util::DummyClientFace face(m_io, {true, true});
   CaModule ca(face, m_keyChain, "tests/unit-tests/ca.conf.test");
-  BOOST_CHECK_EQUAL(ca.getCaConf().m_caItems.front().m_caName.toUri(), "/ndn");
-  BOOST_CHECK_EQUAL(ca.getCaConf().m_caItems.back().m_caName.toUri(), "/ndn/site1");
+  BOOST_CHECK_EQUAL(ca.getCaConf().m_caName.toUri(), "/ndn");
 
   auto identity = addIdentity(Name("/ndn/site2"));
   auto key = identity.getDefaultKey();
@@ -49,34 +49,68 @@
   BOOST_CHECK_EQUAL(ca.getCaStorage()->getCertificate("111").getIdentity(), Name("/ndn/site2"));
 
   advanceClocks(time::milliseconds(20), 60);
-  BOOST_CHECK_EQUAL(ca.m_registeredPrefixIds.size(), 4);
-  BOOST_CHECK_EQUAL(ca.m_interestFilterIds.size(), 18);
+  BOOST_CHECK_EQUAL(ca.m_registeredPrefixHandles.size(), 2);
+  BOOST_CHECK_EQUAL(ca.m_interestFilterHandles.size(), 4);
 }
 
 BOOST_AUTO_TEST_CASE(HandleProbe)
 {
-  auto identity = addIdentity(Name("/ndn/site1"));
+  auto identity = addIdentity(Name("/ndn"));
   auto key = identity.getDefaultKey();
   auto cert = key.getDefaultCertificate();
 
   util::DummyClientFace face(m_io, {true, true});
   CaModule ca(face, m_keyChain, "tests/unit-tests/ca.conf.test");
-  ca.setProbeHandler(Name("/ndn/site1"), [&] (const std::string& probeInfo) {
+  ca.setProbeHandler([&] (const std::string& probeInfo) {
       return probeInfo + "example";
     });
-
   advanceClocks(time::milliseconds(20), 60);
 
-  Name interestName("/ndn/site1/CA");
-  interestName.append("_PROBE").append("zhiyi");
-  Interest interest(interestName);
+  Interest interest("/ndn/CA/_PROBE");
+  interest.setCanBePrefix(false);
+  JsonSection paramJson;
+  paramJson.add(JSON_CLIENT_PROBE_INFO, "zhiyi");
+  interest.setApplicationParameters(ClientModule::paramFromJson(paramJson));
 
   int count = 0;
   face.onSendData.connect([&] (const Data& response) {
       count++;
       BOOST_CHECK(security::verifySignature(response, cert));
-      JsonSection contentJson = ClientModule::getJsonFromData(response);
-      BOOST_CHECK_EQUAL(contentJson.get(JSON_IDNENTIFIER, ""), "/ndn/site1/zhiyiexample");
+      auto contentJson = ClientModule::getJsonFromData(response);
+      BOOST_CHECK_EQUAL(contentJson.get<std::string>(JSON_CA_NAME), "/ndn/zhiyiexample");
+    });
+  face.receive(interest);
+
+  advanceClocks(time::milliseconds(20), 60);
+  BOOST_CHECK_EQUAL(count, 1);
+}
+
+BOOST_AUTO_TEST_CASE(HandleProbeInfo)
+{
+  auto identity = addIdentity(Name("/ndn"));
+  auto key = identity.getDefaultKey();
+  auto cert = key.getDefaultCertificate();
+
+  util::DummyClientFace face(m_io, {true, true});
+  CaModule ca(face, m_keyChain, "tests/unit-tests/ca.conf.test");
+  ca.setProbeHandler([&] (const std::string& probeInfo) {
+      return probeInfo + "example";
+    });
+  advanceClocks(time::milliseconds(20), 60);
+
+  Interest interest("/ndn/CA/_PROBE/INFO");
+  interest.setCanBePrefix(false);
+
+  int count = 0;
+  face.onSendData.connect([&] (const Data& response) {
+      count++;
+      BOOST_CHECK(security::verifySignature(response, cert));
+      auto contentJson = ClientModule::getJsonFromData(response);
+      auto caItem = ClientConfig::extractCaItem(contentJson);
+      BOOST_CHECK_EQUAL(caItem.m_caName.toUri(), "/ndn");
+      BOOST_CHECK_EQUAL(caItem.m_probe, "input email address");
+      BOOST_CHECK_EQUAL(caItem.m_anchor.wireEncode(), cert.wireEncode());
+      BOOST_CHECK_EQUAL(caItem.m_caInfo, "ndn testbed ca");
     });
   face.receive(interest);
 
@@ -86,25 +120,26 @@
 
 BOOST_AUTO_TEST_CASE(HandleProbeUsingDefaultHandler)
 {
-  auto identity = addIdentity(Name("/ndn/site1"));
+  auto identity = addIdentity(Name("/ndn"));
   auto key = identity.getDefaultKey();
   auto cert = key.getDefaultCertificate();
 
   util::DummyClientFace face(m_io, {true, true});
   CaModule ca(face, m_keyChain, "tests/unit-tests/ca.conf.test");
-
   advanceClocks(time::milliseconds(20), 60);
 
-  Name interestName("/ndn/site1/CA");
-  interestName.append("_PROBE").append("zhiyi");
-  Interest interest(interestName);
+  Interest interest("/ndn/CA/_PROBE");
+  interest.setCanBePrefix(false);
+  JsonSection paramJson;
+  paramJson.add(JSON_CLIENT_PROBE_INFO, "zhiyi");
+  interest.setApplicationParameters(ClientModule::paramFromJson(paramJson));
 
   int count = 0;
   face.onSendData.connect([&] (const Data& response) {
       count++;
       BOOST_CHECK(security::verifySignature(response, cert));
-      JsonSection contentJson = ClientModule::getJsonFromData(response);
-      BOOST_CHECK_EQUAL(contentJson.get(JSON_IDNENTIFIER, ""), "/ndn/site1/zhiyi");
+      auto contentJson = ClientModule::getJsonFromData(response);
+      BOOST_CHECK(contentJson.get<std::string>(JSON_CA_NAME) != "");
     });
   face.receive(interest);
 
@@ -114,140 +149,137 @@
 
 BOOST_AUTO_TEST_CASE(HandleNew)
 {
-  auto identity = addIdentity(Name("/ndn/site1"));
+  auto identity = addIdentity(Name("/ndn"));
   auto key = identity.getDefaultKey();
   auto cert = key.getDefaultCertificate();
 
   util::DummyClientFace face(m_io, {true, true});
-  util::DummyClientFace face2(m_io, {true, true});
-
   CaModule ca(face, m_keyChain, "tests/unit-tests/ca.conf.test");
   advanceClocks(time::milliseconds(20), 60);
 
-  Name identityName("/ndn/site1");
-  identityName.append("zhiyi");
-  ClientModule client(face2, m_keyChain);
+  ClientModule client(m_keyChain);
   ClientCaItem item;
-  item.m_caName = Name("/ndn/site1/CA");
+  item.m_caName = Name("/ndn");
   item.m_anchor = cert;
   client.getClientConf().m_caItems.push_back(item);
-
-  int nClientInterest = 0;
-  int nCaData = 0;
-  int nClientCallback = 0;
-
-  face.onSendData.connect([&] (const Data& data) {
-      nCaData++;
-      JsonSection contentJson = ClientModule::getJsonFromData(data);
-      BOOST_CHECK(!contentJson.get(JSON_REQUEST_ID, "").empty());
-      face2.receive(data);
-    });
-  face2.onSendInterest.connect([&] (const Interest& interest) {
-      nClientInterest++;
-      face.receive(interest);
-    });
-
-  client.sendNew(item, identityName,
-                 [&] (const shared_ptr<RequestState> state) {
-                   nClientCallback++;
-                   BOOST_CHECK(state->m_requestId != "");
-                 },
-                 [] (const std::string& s) { BOOST_CHECK(false); });
-
-  advanceClocks(time::milliseconds(20), 60);
-
-  BOOST_CHECK_EQUAL(nClientCallback, 1);
-  BOOST_CHECK_EQUAL(nCaData, 1);
-  BOOST_CHECK_EQUAL(nClientInterest, 1);
-}
-
-BOOST_AUTO_TEST_CASE(HandleLocalhostList)
-{
-  auto identity0 = addIdentity(Name("/ndn"));
-  auto identity1 = addIdentity(Name("/ndn/edu/ucla/cs/zhiyi"));
-  auto identity2 = addIdentity(Name("/ndn/site1"));
-  m_keyChain.setDefaultIdentity(identity0);
-
-  util::DummyClientFace face(m_io, {true, true});
-  CaModule ca(face, m_keyChain, "tests/unit-tests/ca.conf.test");
-
-  advanceClocks(time::milliseconds(20), 60);
-  Interest interest(Name("/localhost/CA/_LIST"));
+  auto interest = client.generateNewInterest(time::system_clock::now(),
+                                             time::system_clock::now() + time::days(10), Name("/ndn/zhiyi"));
 
   int count = 0;
   face.onSendData.connect([&] (const Data& response) {
       count++;
-      JsonSection contentJson = ClientModule::getJsonFromData(response);
-      ClientConfig clientConf;
-      clientConf.load(contentJson);
-      BOOST_CHECK_EQUAL(clientConf.m_caItems.size(), 3);
+      BOOST_CHECK(security::verifySignature(response, cert));
+      auto contentJson = ClientModule::getJsonFromData(response);
+      BOOST_CHECK(contentJson.get<std::string>(JSON_CA_ECDH) != "");
+      BOOST_CHECK(contentJson.get<std::string>(JSON_CA_SALT) != "");
+      BOOST_CHECK(contentJson.get<std::string>(JSON_CA_EQUEST_ID) != "");
+      auto challengesJson = contentJson.get_child(JSON_CA_CHALLENGES);
+      BOOST_CHECK(challengesJson.size() != 0);
+
+      client.onNewResponse(response);
+      BOOST_CHECK_EQUAL_COLLECTIONS(client.m_aesKey, client.m_aesKey + 32, ca.m_aesKey, ca.m_aesKey + 32);
     });
-  face.receive(interest);
+  face.receive(*interest);
 
   advanceClocks(time::milliseconds(20), 60);
   BOOST_CHECK_EQUAL(count, 1);
 }
 
-BOOST_AUTO_TEST_CASE(HandleList)
+BOOST_AUTO_TEST_CASE(HandleChallenge)
 {
-  auto identity0 = addIdentity(Name("/ndn"));
+  auto identity = addIdentity(Name("/ndn"));
+  auto key = identity.getDefaultKey();
+  auto cert = key.getDefaultCertificate();
+
   util::DummyClientFace face(m_io, {true, true});
   CaModule ca(face, m_keyChain, "tests/unit-tests/ca.conf.test");
-
   advanceClocks(time::milliseconds(20), 60);
-  Interest interest(Name("/ndn/CA/_LIST"));
+
+  // generate NEW Interest
+  ClientModule client(m_keyChain);
+  ClientCaItem item;
+  item.m_caName = Name("/ndn");
+  item.m_anchor = cert;
+  client.getClientConf().m_caItems.push_back(item);
+  auto newInterest = client.generateNewInterest(time::system_clock::now(),
+                                                time::system_clock::now() + time::days(10), Name("/ndn/zhiyi"));
+
+  // generate CHALLENGE Interest
+  ChallengePin pinChallenge;
+  shared_ptr<Interest> challengeInterest = nullptr;
+  shared_ptr<Interest> challengeInterest2 = nullptr;
+  shared_ptr<Interest> challengeInterest3 = nullptr;
 
   int count = 0;
   face.onSendData.connect([&] (const Data& response) {
-      count++;
-      JsonSection contentJson = ClientModule::getJsonFromData(response);
-      BOOST_CHECK_EQUAL(contentJson.get_child("ca-list").size(), 2);
-      std::string schemaDataName = contentJson.get<std::string>("trust-schema");
-      BOOST_CHECK_EQUAL(schemaDataName, "TODO: add trust schema");
-    });
-  face.receive(interest);
+    if (Name("/ndn/CA/_NEW").isPrefixOf(response.getName())) {
+      auto contentJson = ClientModule::getJsonFromData(response);
+      std::cout << "Request ID " << contentJson.get<std::string>(JSON_CA_EQUEST_ID) << std::endl;
+      client.onNewResponse(response);
 
-  advanceClocks(time::milliseconds(20), 60);
-  BOOST_CHECK_EQUAL(count, 1);
-}
-
-BOOST_AUTO_TEST_CASE(HandleTargetList)
-{
-  auto identity0 = addIdentity(Name("/ndn"));
-  util::DummyClientFace face(m_io, {true, true});
-  CaModule ca(face, m_keyChain, "tests/unit-tests/ca.conf.test");
-  ca.setRecommendCaHandler(Name("/ndn"),
-    [] (const std::string& input, const std::list<Name>& list) -> std::tuple<Name, std::string> {
-      Name recommendedCa;
-      std::string identity;
-      for (auto caName : list) {
-        std::string univName = readString(caName.get(-1));
-        if (input.find(univName) != std::string::npos) {
-          recommendedCa = caName;
-          identity = input.substr(0, input.find("@"));
-        }
+      auto paramJson = pinChallenge.getRequirementForChallenge(client.m_status, client.m_challengeStatus);
+      for (auto& item : paramJson) {
+        std::cout << "JSON attribute" << item.first;
+        std::cout << " : " << item.second.get<std::string>("") << std::endl;
       }
-      return std::make_tuple(recommendedCa, identity);
-    });
-
-  advanceClocks(time::milliseconds(20), 60);
-  Interest interest(Name("/ndn/CA/_LIST/example@memphis.edu"));
-
-  int count = 0;
-  face.onSendData.connect([&] (const Data& response) {
+      challengeInterest = client.generateChallengeInterest(pinChallenge.genChallengeRequestJson(client.m_status,
+                                                                                                client.m_challengeStatus,
+                                                                                                paramJson));
+    }
+    else if (Name("/ndn/CA/_CHALLENGE").isPrefixOf(response.getName()) && count == 0) {
       count++;
-      JsonSection contentJson = ClientModule::getJsonFromData(response);
-      std::string recommendedCA = contentJson.get<std::string>("recommended-ca");
-      std::string recommendedIdentity = contentJson.get<std::string>("recommended-identity");
-      std::string schemaDataName = contentJson.get<std::string>("trust-schema");
-      BOOST_CHECK_EQUAL(recommendedCA, "/ndn/edu/memphis");
-      BOOST_CHECK_EQUAL(recommendedIdentity, "example");
-      BOOST_CHECK_EQUAL(schemaDataName, "TODO: add trust schema");
-    });
-  face.receive(interest);
+      BOOST_CHECK(security::verifySignature(response, cert));
 
+      client.onChallengeResponse(response);
+      BOOST_CHECK_EQUAL(client.m_status, STATUS_CHALLENGE);
+      BOOST_CHECK_EQUAL(client.m_challengeStatus, ChallengePin::NEED_CODE);
+      auto paramJson = pinChallenge.getRequirementForChallenge(client.m_status, client.m_challengeStatus);
+      for (auto& item : paramJson) {
+        std::cout << "JSON attribute" << item.first;
+        std::cout << " : " << item.second.get<std::string>("") << std::endl;
+      }
+      challengeInterest2 = client.generateChallengeInterest(pinChallenge.genChallengeRequestJson(client.m_status,
+                                                                                                 client.m_challengeStatus,
+                                                                                                 paramJson));
+    }
+    else if (Name("/ndn/CA/_CHALLENGE").isPrefixOf(response.getName()) && count == 1) {
+      count++;
+      BOOST_CHECK(security::verifySignature(response, cert));
+
+      client.onChallengeResponse(response);
+      BOOST_CHECK_EQUAL(client.m_status, STATUS_CHALLENGE);
+      BOOST_CHECK_EQUAL(client.m_challengeStatus, ChallengePin::WRONG_CODE);
+      auto paramJson = pinChallenge.getRequirementForChallenge(client.m_status, client.m_challengeStatus);
+      auto request = ca.getCertificateRequest(*challengeInterest2);
+      auto secret = request.m_challengeSecrets.get(ChallengePin::JSON_PIN_CODE, "");
+      for (auto& item : paramJson) {
+        std::cout << "JSON attribute" << item.first;
+        std::cout << " : " << item.second.get<std::string>("") << std::endl;
+        if (item.first == ChallengePin::JSON_PIN_CODE)
+          item.second.put("", secret);
+      }
+      challengeInterest3 = client.generateChallengeInterest(pinChallenge.genChallengeRequestJson(client.m_status,
+                                                                                                 client.m_challengeStatus,
+                                                                                                 paramJson));
+    }
+    else if (Name("/ndn/CA/_CHALLENGE").isPrefixOf(response.getName()) && count == 2) {
+      count++;
+      BOOST_CHECK(security::verifySignature(response, cert));
+
+      client.onChallengeResponse(response);
+      BOOST_CHECK_EQUAL(client.m_status, STATUS_SUCCESS);
+      BOOST_CHECK_EQUAL(client.m_challengeStatus, CHALLENGE_STATUS_SUCCESS);
+    }
+    });
+  face.receive(*newInterest);
   advanceClocks(time::milliseconds(20), 60);
-  BOOST_CHECK_EQUAL(count, 1);
+  face.receive(*challengeInterest);
+  advanceClocks(time::milliseconds(20), 60);
+  face.receive(*challengeInterest2);
+  advanceClocks(time::milliseconds(20), 60);
+  face.receive(*challengeInterest3);
+  advanceClocks(time::milliseconds(20), 60);
+  BOOST_CHECK_EQUAL(count, 3);
 }
 
 BOOST_AUTO_TEST_SUITE_END() // TestCaModule
diff --git a/tests/unit-tests/ca-sqlite.t.cpp b/tests/unit-tests/ca-sqlite.t.cpp
index be4b54d..e1c8495 100644
--- a/tests/unit-tests/ca-sqlite.t.cpp
+++ b/tests/unit-tests/ca-sqlite.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2017, Regents of the University of California.
+ * Copyright (c) 2017-2019, Regents of the University of California.
  *
  * This file is part of ndncert, a certificate management system based on NDN.
  *
@@ -18,8 +18,8 @@
  * See AUTHORS.md for complete list of ndncert authors and contributors.
  */
 
-#include "database-fixture.hpp"
 #include "ca-detail/ca-sqlite.hpp"
+#include "database-fixture.hpp"
 
 namespace ndn {
 namespace ndncert {
@@ -81,31 +81,30 @@
   auto cert1 = key1.getDefaultCertificate();
 
   // add operation
-  CertificateRequest request1(Name("/ndn/site1"), "123", cert1);
+  CertificateRequest request1(Name("/ndn/site1"), "123", STATUS_BEFORE_CHALLENGE, cert1);
   BOOST_CHECK_NO_THROW(storage.addRequest(request1));
 
   // get operation
   auto result = storage.getRequest("123");
-  BOOST_CHECK_EQUAL(request1.getCert(), result.getCert());
-  BOOST_CHECK_EQUAL(request1.getStatus(), result.getStatus());
-  BOOST_CHECK_EQUAL(request1.getCaName(), result.getCaName());
-
-  JsonSection json;
-  json.put("code", "1234");
+  BOOST_CHECK_EQUAL(request1.m_cert, result.m_cert);
+  BOOST_CHECK_EQUAL(request1.m_status, result.m_status);
+  BOOST_CHECK_EQUAL(request1.m_caName, result.m_caName);
 
   // update operation
-  CertificateRequest request2(Name("/ndn/site1"), "123", "need-verify", "EMAIL",
-                              CaSqlite::convertJson2String(json), cert1);
+  JsonSection json;
+  json.put("test", "4567");
+  CertificateRequest request2(Name("/ndn/site1"), "123", STATUS_CHALLENGE, CHALLENGE_STATUS_SUCCESS,
+                             "Email", time::toIsoString(time::system_clock::now()), 3600, 3, json, cert1);
   storage.updateRequest(request2);
   result = storage.getRequest("123");
-  BOOST_CHECK_EQUAL(request2.getCert(), result.getCert());
-  BOOST_CHECK_EQUAL(request2.getStatus(), result.getStatus());
-  BOOST_CHECK_EQUAL(request2.getCaName(), result.getCaName());
+  BOOST_CHECK_EQUAL(request2.m_cert, result.m_cert);
+  BOOST_CHECK_EQUAL(request2.m_status, result.m_status);
+  BOOST_CHECK_EQUAL(request2.m_caName, result.m_caName);
 
   auto identity2 = addIdentity(Name("/ndn/site2"));
   auto key2 = identity2.getDefaultKey();
   auto cert2 = key2.getDefaultCertificate();
-  CertificateRequest request3(Name("/ndn/site2"), "456", cert2);
+  CertificateRequest request3(Name("/ndn/site2"), "456", STATUS_BEFORE_CHALLENGE, cert2);
   storage.addRequest(request3);
 
   // list operation
diff --git a/tests/unit-tests/ca.conf.test b/tests/unit-tests/ca.conf.test
index 5809613..15b497f 100644
--- a/tests/unit-tests/ca.conf.test
+++ b/tests/unit-tests/ca.conf.test
@@ -1,44 +1,12 @@
 {
-  "ca-list":
+  "ca-prefix": "/ndn",
+  "issuing-freshness": "720",
+  "validity-period": "360",
+  "ca-info": "ndn testbed ca",
+
+  "probe": "input email address",
+  "supported-challenges":
   [
-    {
-        "ca-prefix": "/ndn",
-        "issuing-freshness": "720",
-        "validity-period": "360",
-        "ca-info": "ndn testbed ca",
-
-        "probe": "input email address",
-
-        "targeted-list": "Use your email address (edu preferred) as input",
-        "related-ca-list":
-        [
-          { "ca-prefix": "/ndn/edu/arizona" },
-          { "ca-prefix": "/ndn/edu/memphis" }
-        ],
-
-        "supported-challenges":
-        [
-            { "type": "PIN" }
-        ]
-    },
-    {
-        "ca-prefix": "/ndn/edu/ucla/cs/zhiyi",
-        "issuing-freshness": "720",
-        "validity-period": "360",
-        "supported-challenges":
-        [
-            { "type": "PIN" }
-        ]
-    },
-    {
-        "ca-prefix": "/ndn/site1",
-        "probe": "input email address",
-        "issuing-freshness": "720",
-        "validity-period": "360",
-        "supported-challenges":
-        [
-            { "type": "PIN" }
-        ]
-    }
+      { "type": "PIN" }
   ]
 }
\ No newline at end of file
diff --git a/tests/unit-tests/certificate-request.t.cpp b/tests/unit-tests/certificate-request.t.cpp
deleted file mode 100644
index 3282ee9..0000000
--- a/tests/unit-tests/certificate-request.t.cpp
+++ /dev/null
@@ -1,126 +0,0 @@
-/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * Copyright (c) 2017, 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 "identity-management-fixture.hpp"
-#include "certificate-request.hpp"
-#include <boost/lexical_cast.hpp>
-#include <ndn-cxx/util/io.hpp>
-
-namespace ndn {
-namespace ndncert {
-namespace tests {
-
-BOOST_FIXTURE_TEST_SUITE(TestCertificateRequest, IdentityManagementV2Fixture)
-
-BOOST_AUTO_TEST_CASE(Constructor)
-{
-  auto identity = addIdentity(Name("/ndn/site1"));
-  auto key = identity.getDefaultKey();
-  auto cert = key.getDefaultCertificate();
-
-  CertificateRequest request1(Name("/ndn/site1"), "123", cert);
-  BOOST_CHECK_EQUAL(request1.getCaName().toUri(), "/ndn/site1");
-  BOOST_CHECK_EQUAL(request1.getRequestId(), "123");
-  BOOST_CHECK_EQUAL(request1.getStatus(), "");
-  BOOST_CHECK_EQUAL(request1.getChallengeSecrets().empty(), true);
-  BOOST_CHECK_EQUAL(request1.getCert(), cert);
-  BOOST_CHECK_EQUAL(request1.getCert(), cert);
-
-  JsonSection json;
-  json.put("code", "1234");
-  std::stringstream ss;
-  boost::property_tree::write_json(ss, json);
-  std::string jsonValue = ss.str();
-
-  CertificateRequest request2(Name("/ndn/site1"), "123", "need-verify", "EMAIL", jsonValue, cert);
-  BOOST_CHECK_EQUAL(request2.getCaName().toUri(), "/ndn/site1");
-  BOOST_CHECK_EQUAL(request2.getRequestId(), "123");
-  BOOST_CHECK_EQUAL(request2.getStatus(), "need-verify");
-  BOOST_CHECK_EQUAL(request2.getChallengeType(), "EMAIL");
-  BOOST_CHECK(request2.getChallengeSecrets() == json);
-  BOOST_CHECK_EQUAL(request2.getCert(), cert);
-}
-
-BOOST_AUTO_TEST_CASE(GetterSetter)
-{
-  auto identity = addIdentity(Name("/ndn/site1"));
-  auto key = identity.getDefaultKey();
-  auto cert = key.getDefaultCertificate();
-
-  JsonSection json;
-  json.put("code", "1234");
-
-  CertificateRequest request(Name("/ndn/site1"), "123", cert);
-  request.setStatus("need-verify");
-  request.setChallengeType("EMAIL");
-  request.setChallengeSecrets(json);
-
-  BOOST_CHECK_EQUAL(request.getStatus(), "need-verify");
-  BOOST_CHECK_EQUAL(request.getChallengeType(), "EMAIL");
-  BOOST_CHECK(request.getChallengeSecrets() == json);
-}
-
-BOOST_AUTO_TEST_CASE(GetCertificateRequestOutput)
-{
-  const std::string certString = R"_CERT_(
-Bv0BuwczCANuZG4IBXNpdGUxCANLRVkIEWtzay0xNDE2NDI1Mzc3MDk0CAQwMTIz
-CAf9AAABScmLFAkYAQIZBAA27oAVoDCBnTANBgkqhkiG9w0BAQEFAAOBiwAwgYcC
-gYEAngY+R4WyNDeqhUesAySDtZyoBTokHuuJAbvpm7LDIqxo4/BsAs5opsTQpwaQ
-nKobCB2LQ5ozZ0RtIaMbiJqXXlnEFQvZLL1RB2GCrcG417+bz30kwmPzlxfr/mIl
-ultNisJ6vUOKj7jy8cVqMNNQjMia3+/tNed6Yup2fLsIJscCAREWVRsBARwmByQI
-A25kbggFc2l0ZTEIA0tFWQgRa3NrLTI1MTY0MjUzNzcwOTT9AP0m/QD+DzIwMTUw
-ODE0VDIyMzczOf0A/w8yMDE1MDgxOFQyMjM3MzgXgP//////////////////////
-////////////////////////////////////////////////////////////////
-////////////////////////////////////////////////////////////////
-////////////////////)_CERT_";
-
-  const std::string expectedString = R"_REQUEST_(Request CA name:
-  /ndn/site1
-Request ID:
-  123
-Certificate:
-  Certificate name:
-    /ndn/site1/KEY/ksk-1416425377094/0123/%FD%00%00%01I%C9%8B
-  Validity:
-    NotBefore: 20150814T223739
-    NotAfter: 20150818T223738
-  Public key bits:
-    MIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQCeBj5HhbI0N6qFR6wDJIO1nKgF
-    OiQe64kBu+mbssMirGjj8GwCzmimxNCnBpCcqhsIHYtDmjNnRG0hoxuImpdeWcQV
-    C9ksvVEHYYKtwbjXv5vPfSTCY/OXF+v+YiW6W02Kwnq9Q4qPuPLxxWow01CMyJrf
-    7+0153pi6nZ8uwgmxwIBEQ==
-  Signature Information:
-    Signature Type: SignatureSha256WithRsa
-    Key Locator: Name=/ndn/site1/KEY/ksk-2516425377094
-)_REQUEST_";
-
-  std::stringstream ss;
-  ss << certString;
-  auto cert = io::load<security::v2::Certificate>(ss);
-  CertificateRequest request(Name("/ndn/site1"), "123", *cert);
-
-  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(request), expectedString);
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-} // namespace tests
-} // namespace ndncert
-} // namespace ndn
diff --git a/tests/unit-tests/challenge-credential.t.cpp b/tests/unit-tests/challenge-credential.t.cpp
index 772c57f..2455e73 100644
--- a/tests/unit-tests/challenge-credential.t.cpp
+++ b/tests/unit-tests/challenge-credential.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2017, Regents of the University of California.
+ * Copyright (c) 2017-2019, Regents of the University of California.
  *
  * This file is part of ndncert, a certificate management system based on NDN.
  *
@@ -18,8 +18,8 @@
  * See AUTHORS.md for complete list of ndncert authors and contributors.
  */
 
-#include "identity-management-fixture.hpp"
 #include "challenge-module/challenge-credential.hpp"
+#include "identity-management-fixture.hpp"
 #include <ndn-cxx/security/signing-helpers.hpp>
 #include <ndn-cxx/util/io.hpp>
 
@@ -27,7 +27,7 @@
 namespace ndncert {
 namespace tests {
 
-BOOST_FIXTURE_TEST_SUITE(TestChallengeCredential, IdentityManagementV2Fixture)
+BOOST_FIXTURE_TEST_SUITE(TestChallengeCredential, IdentityManagementFixture)
 
 BOOST_AUTO_TEST_CASE(LoadConfig)
 {
@@ -41,7 +41,7 @@
                     "/ndn/site1/KEY/%11%BC%22%F4c%15%FF%17/self/%FD%00%00%01Y%C8%14%D9%A5");
 }
 
-BOOST_AUTO_TEST_CASE(HandleSelect)
+BOOST_AUTO_TEST_CASE(HandleChallengeRequest)
 {
   // create trust anchor
   ChallengeCredential challenge("./tests/unit-tests/challenge-credential.conf.test");
@@ -55,7 +55,7 @@
   auto identityA = addIdentity(Name("/example"));
   auto keyA = identityA.getDefaultKey();
   auto certA = key.getDefaultCertificate();
-  CertificateRequest request(Name("/example"), "123", certA);
+  CertificateRequest request(Name("/example"), "123", STATUS_BEFORE_CHALLENGE, certA);
 
   // create requester's existing cert
   auto identityB = addIdentity(Name("/trust/cert"));
@@ -65,43 +65,35 @@
   // using trust anchor to sign cert request to get credential
   Name credentialName = certB.getKeyName();
   credentialName.append("Credential").appendVersion();
-  security::v2::Certificate credential = certB;
-  credential.setName(credentialName);
-  credential.setContent(certB.getContent());
-  m_keyChain.sign(credential, signingByCertificate(trustAnchor));
+  security::v2::Certificate selfSigned;
+  selfSigned.setName(credentialName);
+  selfSigned.setContent(makeStringBlock(tlv::Content, "123"));
+  m_keyChain.sign(selfSigned, signingByCertificate(trustAnchor));
 
   // generate SELECT interest
   std::stringstream ss;
-  io::save<security::v2::Certificate>(credential, ss);
+  io::save<security::v2::Certificate>(selfSigned, ss);
   auto checkCert = *(io::load<security::v2::Certificate>(ss));
-  BOOST_CHECK_EQUAL(checkCert, credential);
+  BOOST_CHECK_EQUAL(checkCert, selfSigned);
   ss.str("");
   ss.clear();
 
-  std::list<std::string> paramList;
-  io::save<security::v2::Certificate>(credential, ss);
-  std::string paramString = ss.str();
-  paramList.push_back(paramString);
+  JsonSection params;
+  io::save<security::v2::Certificate>(selfSigned, ss);
+  std::string selfSignedStr = ss.str();
+  params.add(ChallengeCredential::JSON_CREDENTIAL_SELF, selfSignedStr);
   ss.str("");
   ss.clear();
 
   io::save<security::v2::Certificate>(certB, ss);
-  paramString = ss.str();
-  paramList.push_back(paramString);
+  std::string credentialStr = ss.str();
+  params.add(ChallengeCredential::JSON_CREDENTIAL_CERT, credentialStr);
   ss.str("");
   ss.clear();
-  JsonSection credentialJson = challenge.genSelectParamsJson(ChallengeModule::WAIT_SELECTION, paramList);
 
-  boost::property_tree::write_json(ss, credentialJson);
-  Block jsonContent = makeStringBlock(ndn::tlv::NameComponent, ss.str());
-
-  Name interestName("/example/CA");
-  interestName.append("_SELECT").append("Fake-Request-ID").append("CREDENTIAL").append(jsonContent);
-  Interest interest(interestName);
-
-  challenge.processSelectInterest(interest, request);
-  BOOST_CHECK_EQUAL(request.getStatus(), ChallengeModule::SUCCESS);
-  BOOST_CHECK_EQUAL(request.getChallengeSecrets().empty(), true);
+  challenge.handleChallengeRequest(params, request);
+  BOOST_CHECK_EQUAL(request.m_status, STATUS_PENDING);
+  BOOST_CHECK_EQUAL(request.m_challengeStatus, CHALLENGE_STATUS_SUCCESS);
 }
 
 BOOST_AUTO_TEST_SUITE_END()
diff --git a/tests/unit-tests/challenge-email.t.cpp b/tests/unit-tests/challenge-email.t.cpp
index f723f6a..3b9850c 100644
--- a/tests/unit-tests/challenge-email.t.cpp
+++ b/tests/unit-tests/challenge-email.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2017-2018, Regents of the University of California.
+ * Copyright (c) 2017-2019, Regents of the University of California.
  *
  * This file is part of ndncert, a certificate management system based on NDN.
  *
@@ -25,7 +25,7 @@
 namespace ndncert {
 namespace tests {
 
-BOOST_FIXTURE_TEST_SUITE(TestChallengeEmail, IdentityManagementV2Fixture)
+BOOST_FIXTURE_TEST_SUITE(TestChallengeEmail, IdentityManagementFixture)
 
 BOOST_AUTO_TEST_CASE(TestChallengeType)
 {
@@ -33,20 +33,6 @@
   BOOST_CHECK_EQUAL(challenge.CHALLENGE_TYPE, "Email");
 }
 
-BOOST_AUTO_TEST_CASE(ParseStoredSecret)
-{
-  time::system_clock::TimePoint tp = time::fromIsoString("20170207T120000");
-  JsonSection json;
-  json.put(ChallengeEmail::JSON_CODE_TP, time::toIsoString(tp));
-  json.put(ChallengeEmail::JSON_CODE, "1234");
-  json.put(ChallengeEmail::JSON_ATTEMPT_TIMES, std::to_string(3));
-
-  auto result = ChallengeEmail::parseStoredSecrets(json);
-  BOOST_CHECK_EQUAL(std::get<0>(result), tp);
-  BOOST_CHECK_EQUAL(std::get<1>(result), "1234");
-  BOOST_CHECK_EQUAL(std::get<2>(result), 3);
-}
-
 BOOST_AUTO_TEST_CASE(EmailAddressChecker)
 {
   BOOST_CHECK_EQUAL(ChallengeEmail::isValidEmailAddress("zhiyi@cs.ucla.edu"), true);
@@ -54,28 +40,26 @@
   BOOST_CHECK_EQUAL(ChallengeEmail::isValidEmailAddress("zhiyi.ucla.edu"), false);
 }
 
-BOOST_AUTO_TEST_CASE(OnSelectInterestComingWithEmail)
+BOOST_AUTO_TEST_CASE(OnChallengeRequestWithEmail)
 {
   auto identity = addIdentity(Name("/ndn/site1"));
   auto key = identity.getDefaultKey();
   auto cert = key.getDefaultCertificate();
-  CertificateRequest request(Name("/ndn/site1"), "123", cert);
+  CertificateRequest request(Name("/ndn/site1"), "123", STATUS_BEFORE_CHALLENGE, cert);
 
   JsonSection emailJson;
   emailJson.put(ChallengeEmail::JSON_EMAIL, "zhiyi@cs.ucla.edu");
-  std::stringstream ss;
-  boost::property_tree::write_json(ss, emailJson);
-  Block jsonContent = makeStringBlock(ndn::tlv::GenericNameComponent, ss.str());
-
-  Name interestName("/ndn/site1/CA");
-  interestName.append("_SELECT").append("Fake-Request-ID").append("EMAIL").append(jsonContent);
-  Interest interest(interestName);
 
   ChallengeEmail challenge("./tests/unit-tests/test-send-email.sh");
-  challenge.handleChallengeRequest(interest, request);
+  challenge.handleChallengeRequest(emailJson, request);
 
-  BOOST_CHECK_EQUAL(request.getStatus(), ChallengeEmail::NEED_CODE);
-  BOOST_CHECK_EQUAL(request.getChallengeType(), "Email");
+  BOOST_CHECK_EQUAL(request.m_status, STATUS_CHALLENGE);
+  BOOST_CHECK_EQUAL(request.m_challengeStatus, ChallengeEmail::NEED_CODE);
+  BOOST_CHECK(request.m_challengeSecrets.get<std::string>(ChallengeEmail::JSON_CODE) != "");
+  BOOST_CHECK(request.m_remainingTime != 0);
+  BOOST_CHECK(request.m_remainingTries != 0);
+  BOOST_CHECK(request.m_challengeTp != "");
+  BOOST_CHECK_EQUAL(request.m_challengeType, "Email");
 
   std::string line = "";
   std::string delimiter = " ";
@@ -89,68 +73,47 @@
   std::string secret = line.substr(line.find(delimiter) + 1);
 
   BOOST_CHECK_EQUAL(recipientEmail, "zhiyi@cs.ucla.edu");
-  auto stored_secret = request.getChallengeSecrets().get<std::string>(ChallengeEmail::JSON_CODE);
+  auto stored_secret = request.m_challengeSecrets.get<std::string>(ChallengeEmail::JSON_CODE);
   BOOST_CHECK_EQUAL(secret, stored_secret);
-
   std::remove("tmp.txt");
 }
 
-BOOST_AUTO_TEST_CASE(OnSelectInterestComingWithInvalidEmail)
+BOOST_AUTO_TEST_CASE(OnChallengeRequestWithInvalidEmail)
 {
   auto identity = addIdentity(Name("/ndn/site1"));
   auto key = identity.getDefaultKey();
   auto cert = key.getDefaultCertificate();
-  CertificateRequest request(Name("/ndn/site1"), "123", cert);
+  CertificateRequest request(Name("/ndn/site1"), "123", STATUS_BEFORE_CHALLENGE, cert);
 
   JsonSection emailJson;
   emailJson.put(ChallengeEmail::JSON_EMAIL, "zhiyi@cs");
-  std::stringstream ss;
-  boost::property_tree::write_json(ss, emailJson);
-  Block jsonContent = makeStringBlock(ndn::tlv::GenericNameComponent, ss.str());
-
-  Name interestName("/ndn/site1/CA");
-  interestName.append("_SELECT").append("Fake-Request-ID").append("EMAIL").append(jsonContent);
-  Interest interest(interestName);
 
   ChallengeEmail challenge;
-  challenge.handleChallengeRequest(interest, request);
+  challenge.handleChallengeRequest(emailJson, request);
 
-  BOOST_CHECK_EQUAL(request.getStatus(), ChallengeEmail::FAILURE_INVALID_EMAIL);
-  BOOST_CHECK_EQUAL(request.getChallengeType(), "Email");
+  BOOST_CHECK_EQUAL(request.m_challengeStatus, ChallengeEmail::FAILURE_INVALID_EMAIL);
+  BOOST_CHECK_EQUAL(request.m_status, STATUS_FAILURE);
 }
 
-BOOST_AUTO_TEST_CASE(OnValidateInterestComingWithCode)
+BOOST_AUTO_TEST_CASE(OnChallengeRequestWithCode)
 {
   auto identity = addIdentity(Name("/ndn/site1"));
   auto key = identity.getDefaultKey();
   auto cert = key.getDefaultCertificate();
-  CertificateRequest request(Name("/ndn/site1"), "123", cert);
-  request.setChallengeType("EMAIL");
-  request.setStatus(ChallengeEmail::NEED_CODE);
-
-  time::system_clock::TimePoint tp = time::system_clock::now();
   JsonSection json;
-  json.put(ChallengeEmail::JSON_CODE_TP, time::toIsoString(tp));
   json.put(ChallengeEmail::JSON_CODE, "4567");
-  json.put(ChallengeEmail::JSON_ATTEMPT_TIMES, std::to_string(3));
+  CertificateRequest request(Name("/ndn/site1"), "123", STATUS_CHALLENGE, ChallengeEmail::NEED_CODE,
+                             "Email", time::toIsoString(time::system_clock::now()), 3600, 3, json, cert);
 
-  request.setChallengeSecrets(json);
-
-  JsonSection infoJson;
-  infoJson.put(ChallengeEmail::JSON_CODE, "4567");
-  std::stringstream ss;
-  boost::property_tree::write_json(ss, infoJson);
-  Block jsonContent = makeStringBlock(ndn::tlv::GenericNameComponent, ss.str());
-
-  Name interestName("/ndn/site1/CA");
-  interestName.append("_VALIDATE").append("Fake-Request-ID").append("EMAIL").append(jsonContent);
-  Interest interest(interestName);
+  JsonSection requestJson;
+  requestJson.put(ChallengeEmail::JSON_CODE, "4567");
 
   ChallengeEmail challenge;
-  challenge.handleChallengeRequest(interest, request);
+  challenge.handleChallengeRequest(requestJson, request);
 
-  BOOST_CHECK_EQUAL(request.getStatus(), ChallengeModule::SUCCESS);
-  BOOST_CHECK_EQUAL(request.getChallengeSecrets().empty(), true);
+  BOOST_CHECK_EQUAL(request.m_challengeStatus, CHALLENGE_STATUS_SUCCESS);
+  BOOST_CHECK_EQUAL(request.m_status, STATUS_PENDING);
+  BOOST_CHECK_EQUAL(request.m_challengeSecrets.empty(), true);
 }
 
 BOOST_AUTO_TEST_CASE(OnValidateInterestComingWithWrongCode)
@@ -158,61 +121,20 @@
   auto identity = addIdentity(Name("/ndn/site1"));
   auto key = identity.getDefaultKey();
   auto cert = key.getDefaultCertificate();
-  CertificateRequest request(Name("/ndn/site1"), "123", cert);
-  request.setChallengeType("EMAIL");
-  request.setStatus(ChallengeEmail::NEED_CODE);
-
-  time::system_clock::TimePoint tp = time::system_clock::now();
   JsonSection json;
-  json.put(ChallengeEmail::JSON_CODE_TP, time::toIsoString(tp));
   json.put(ChallengeEmail::JSON_CODE, "4567");
-  json.put(ChallengeEmail::JSON_ATTEMPT_TIMES, std::to_string(3));
+  CertificateRequest request(Name("/ndn/site1"), "123", STATUS_CHALLENGE, ChallengeEmail::NEED_CODE,
+                             "Email", time::toIsoString(time::system_clock::now()), 3600, 3, json, cert);
 
-  request.setChallengeSecrets(json);
-
-  JsonSection infoJson;
-  infoJson.put(ChallengeEmail::JSON_CODE, "1234");
-  std::stringstream ss;
-  boost::property_tree::write_json(ss, infoJson);
-  Block jsonContent = makeStringBlock(ndn::tlv::GenericNameComponent, ss.str());
-
-  Name interestName("/ndn/site1/CA");
-  interestName.append("_VALIDATE").append("Fake-Request-ID").append("EMAIL").append(jsonContent);
-  Interest interest(interestName);
+  JsonSection requestJson;
+  requestJson.put(ChallengeEmail::JSON_CODE, "7890");
 
   ChallengeEmail challenge;
-  challenge.handleChallengeRequest(interest, request);
+  challenge.handleChallengeRequest(requestJson, request);
 
-  BOOST_CHECK_EQUAL(request.getStatus(), ChallengeEmail::WRONG_CODE);
-  BOOST_CHECK_EQUAL(request.getChallengeSecrets().empty(), false);
-}
-
-BOOST_AUTO_TEST_CASE(ClientSendSelect)
-{
-  ChallengeEmail challenge;
-  auto requirementList = challenge.getSelectRequirements();
-  BOOST_CHECK_EQUAL(requirementList.size(), 1);
-
-  requirementList.clear();
-  requirementList.push_back("zhiyi@cs.ucla.edu");
-
-  auto json = challenge.genSelectParamsJson(ChallengeModule::WAIT_SELECTION, requirementList);
-  BOOST_CHECK_EQUAL(json.empty(), false);
-  BOOST_CHECK_EQUAL(json.get<std::string>(ChallengeEmail::JSON_EMAIL), "zhiyi@cs.ucla.edu");
-}
-
-BOOST_AUTO_TEST_CASE(ClientSendValidate)
-{
-  ChallengeEmail challenge;
-  auto requirementList = challenge.getValidateRequirements(ChallengeEmail::NEED_CODE);
-  BOOST_CHECK_EQUAL(requirementList.size(), 1);
-
-  requirementList.clear();
-  requirementList.push_back("123");
-
-  auto json = challenge.genValidateParamsJson(ChallengeEmail::NEED_CODE, requirementList);
-  BOOST_CHECK_EQUAL(json.empty(), false);
-  BOOST_CHECK_EQUAL(json.get<std::string>(ChallengeEmail::JSON_CODE), "123");
+  BOOST_CHECK_EQUAL(request.m_challengeStatus, ChallengeEmail::WRONG_CODE);
+  BOOST_CHECK_EQUAL(request.m_status, STATUS_CHALLENGE);
+  BOOST_CHECK_EQUAL(request.m_challengeSecrets.empty(), false);
 }
 
 BOOST_AUTO_TEST_SUITE_END()
diff --git a/tests/unit-tests/challenge-module.t.cpp b/tests/unit-tests/challenge-module.t.cpp
deleted file mode 100644
index 570aab1..0000000
--- a/tests/unit-tests/challenge-module.t.cpp
+++ /dev/null
@@ -1,59 +0,0 @@
-/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * Copyright (c) 2017, 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 "challenge-module.hpp"
-
-namespace ndn {
-namespace ndncert {
-namespace tests {
-
-BOOST_AUTO_TEST_SUITE(TestChallengeModule)
-
-BOOST_AUTO_TEST_CASE(GetJsonFromNameComponent)
-{
-  JsonSection json;
-  json.put("test", "123");
-  std::stringstream ss;
-  boost::property_tree::write_json(ss, json);
-  std::string jsonString = ss.str();
-  Block jsonContent = makeStringBlock(ndn::tlv::NameComponent, ss.str());
-
-  Name name("ndn");
-  name.append(jsonContent);
-  BOOST_CHECK(ChallengeModule::getJsonFromNameComponent(name, 1) == json);
-}
-
-BOOST_AUTO_TEST_CASE(GenDownloadName)
-{
-  Name interestName = ChallengeModule::genDownloadName(Name("ca"), "123");
-  BOOST_CHECK_EQUAL(interestName.getSubName(0, 1), Name("ca"));
-  BOOST_CHECK_EQUAL(interestName.getSubName(1, 1), Name("_DOWNLOAD"));
-
-  JsonSection json;
-  json.put(JSON_REQUEST_ID, "123");
-  BOOST_CHECK(ChallengeModule::getJsonFromNameComponent(interestName, 2) == json);
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-} // namespace tests
-} // namespace ndncert
-} // namespace ndn
diff --git a/tests/unit-tests/challenge-pin.t.cpp b/tests/unit-tests/challenge-pin.t.cpp
index 2b7e377..95c7259 100644
--- a/tests/unit-tests/challenge-pin.t.cpp
+++ b/tests/unit-tests/challenge-pin.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2017-2018, Regents of the University of California.
+ * Copyright (c) 2017-2019, Regents of the University of California.
  *
  * This file is part of ndncert, a certificate management system based on NDN.
  *
@@ -25,7 +25,7 @@
 namespace ndncert {
 namespace tests {
 
-BOOST_FIXTURE_TEST_SUITE(TestChallengePin, IdentityManagementV2Fixture)
+BOOST_FIXTURE_TEST_SUITE(TestChallengePin, IdentityManagementFixture)
 
 BOOST_AUTO_TEST_CASE(TestGetInitInfo)
 {
@@ -33,130 +33,61 @@
   BOOST_CHECK_EQUAL(challenge.CHALLENGE_TYPE, "PIN");
 }
 
-BOOST_AUTO_TEST_CASE(ParseStoredSecret)
-{
-  time::system_clock::TimePoint tp = time::fromIsoString("20170207T120000");
-  JsonSection json;
-  json.put(ChallengePin::JSON_CODE_TP, time::toIsoString(tp));
-  json.put(ChallengePin::JSON_PIN_CODE, "1234");
-  json.put(ChallengePin::JSON_ATTEMPT_TIMES, std::to_string(3));
-
-  auto result = ChallengePin::parseStoredSecrets(json);
-  BOOST_CHECK_EQUAL(std::get<0>(result), tp);
-  BOOST_CHECK_EQUAL(std::get<1>(result), "1234");
-  BOOST_CHECK_EQUAL(std::get<2>(result), 3);
-}
-
-BOOST_AUTO_TEST_CASE(OnSelectInterestComingWithEmptyInfo)
+BOOST_AUTO_TEST_CASE(OnChallengeRequestWithEmptyInfo)
 {
   auto identity = addIdentity(Name("/ndn/site1"));
   auto key = identity.getDefaultKey();
   auto cert = key.getDefaultCertificate();
-  CertificateRequest request(Name("/ndn/site1"), "123", cert);
-
-  Name interestName("/ndn/site1/CA");
-  interestName.append("_SELECT").append("Fake-Request-ID").append("PIN");
-  Interest interest(interestName);
+  CertificateRequest request(Name("/ndn/site1"), "123", STATUS_BEFORE_CHALLENGE, cert);
 
   ChallengePin challenge;
-  challenge.handleChallengeRequest(interest, request);
+  challenge.handleChallengeRequest(JsonSection(), request);
 
-  BOOST_CHECK_EQUAL(request.getStatus(), ChallengePin::NEED_CODE);
-  BOOST_CHECK_EQUAL(request.getChallengeType(), "PIN");
+  BOOST_CHECK_EQUAL(request.m_status, STATUS_CHALLENGE);
+  BOOST_CHECK_EQUAL(request.m_challengeStatus, ChallengePin::NEED_CODE);
+  BOOST_CHECK_EQUAL(request.m_challengeType, "PIN");
 }
 
-BOOST_AUTO_TEST_CASE(OnValidateInterestComingWithCode)
+BOOST_AUTO_TEST_CASE(OnChallengeRequestWithCode)
 {
   auto identity = addIdentity(Name("/ndn/site1"));
   auto key = identity.getDefaultKey();
   auto cert = key.getDefaultCertificate();
-  CertificateRequest request(Name("/ndn/site1"), "123", cert);
-  request.setChallengeType("PIN");
-  request.setStatus(ChallengePin::NEED_CODE);
+  JsonSection secret;
+  secret.add(ChallengePin::JSON_PIN_CODE, "12345");
+  CertificateRequest request(Name("/ndn/site1"), "123", STATUS_CHALLENGE, ChallengePin::NEED_CODE, "PIN",
+                             time::toIsoString(time::system_clock::now()), 3600, 3, secret, cert);
 
-  time::system_clock::TimePoint tp = time::system_clock::now();
-  JsonSection json;
-  json.put(ChallengePin::JSON_CODE_TP, time::toIsoString(tp));
-  json.put(ChallengePin::JSON_PIN_CODE, "1234");
-  json.put(ChallengePin::JSON_ATTEMPT_TIMES, std::to_string(3));
-
-  request.setChallengeSecrets(json);
-
-  JsonSection infoJson;
-  infoJson.put(ChallengePin::JSON_PIN_CODE, "1234");
-  std::stringstream ss;
-  boost::property_tree::write_json(ss, infoJson);
-  std::string jsonString = ss.str();
-  Block jsonContent = makeStringBlock(ndn::tlv::GenericNameComponent, ss.str());
-
-  Name interestName("/ndn/site1/CA");
-  interestName.append("_VALIDATE").append("Fake-Request-ID").append("PIN").append(jsonContent);
-  Interest interest(interestName);
+  JsonSection paramJson;
+  paramJson.put(ChallengePin::JSON_PIN_CODE, "12345");
 
   ChallengePin challenge;
-  challenge.handleChallengeRequest(interest, request);
+  challenge.handleChallengeRequest(paramJson, request);
 
-  BOOST_CHECK_EQUAL(request.getStatus(), ChallengeModule::SUCCESS);
-  BOOST_CHECK_EQUAL(request.getChallengeSecrets().empty(), true);
+  BOOST_CHECK_EQUAL(request.m_status, STATUS_PENDING);
+  BOOST_CHECK_EQUAL(request.m_challengeStatus, CHALLENGE_STATUS_SUCCESS);
+  BOOST_CHECK_EQUAL(request.m_challengeSecrets.empty(), true);
 }
 
-BOOST_AUTO_TEST_CASE(OnValidateInterestComingWithWrongCode)
+BOOST_AUTO_TEST_CASE(OnChallengeRequestWithWrongCode)
 {
   auto identity = addIdentity(Name("/ndn/site1"));
   auto key = identity.getDefaultKey();
   auto cert = key.getDefaultCertificate();
-  CertificateRequest request(Name("/ndn/site1"), "123", cert);
-  request.setChallengeType("PIN");
-  request.setStatus(ChallengePin::NEED_CODE);
+  JsonSection secret;
+  secret.add(ChallengePin::JSON_PIN_CODE, "12345");
+  CertificateRequest request(Name("/ndn/site1"), "123", STATUS_CHALLENGE, ChallengePin::NEED_CODE, "PIN",
+                             time::toIsoString(time::system_clock::now()), 3600, 3, secret, cert);
 
-  time::system_clock::TimePoint tp = time::system_clock::now();
-  JsonSection json;
-  json.put(ChallengePin::JSON_CODE_TP, time::toIsoString(tp));
-  json.put(ChallengePin::JSON_PIN_CODE, "1234");
-  json.put(ChallengePin::JSON_ATTEMPT_TIMES, std::to_string(3));
-
-  request.setChallengeSecrets(json);
-
-  JsonSection infoJson;
-  infoJson.put(ChallengePin::JSON_PIN_CODE, "4567");
-  std::stringstream ss;
-  boost::property_tree::write_json(ss, infoJson);
-  std::string jsonString = ss.str();
-  Block jsonContent = makeStringBlock(ndn::tlv::GenericNameComponent, ss.str());
-
-  Name interestName("/ndn/site1/CA");
-  interestName.append("_VALIDATE").append("Fake-Request-ID").append("PIN").append(jsonContent);
-  Interest interest(interestName);
+  JsonSection paramJson;
+  paramJson.put(ChallengePin::JSON_PIN_CODE, "45678");
 
   ChallengePin challenge;
-  challenge.handleChallengeRequest(interest, request);
+  challenge.handleChallengeRequest(paramJson, request);
 
-  BOOST_CHECK_EQUAL(request.getStatus(), ChallengePin::WRONG_CODE);
-  BOOST_CHECK_EQUAL(request.getChallengeSecrets().empty(), false);
-}
-
-BOOST_AUTO_TEST_CASE(ClientSendSelect)
-{
-  ChallengePin challenge;
-  auto requirementList = challenge.getSelectRequirements();
-  BOOST_CHECK_EQUAL(requirementList.size(), 0);
-
-  auto json = challenge.doGenSelectParamsJson(ChallengeModule::WAIT_SELECTION, requirementList);
-  BOOST_CHECK_EQUAL(json.empty(), true);
-}
-
-BOOST_AUTO_TEST_CASE(ClientSendValidate)
-{
-  ChallengePin challenge;
-  auto requirementList = challenge.getValidateRequirements(ChallengePin::NEED_CODE);
-  BOOST_CHECK_EQUAL(requirementList.size(), 1);
-
-  requirementList.clear();
-  requirementList.push_back("123");
-
-  auto json = challenge.doGenValidateParamsJson(ChallengePin::NEED_CODE, requirementList);
-  BOOST_CHECK_EQUAL(json.empty(), false);
-  BOOST_CHECK_EQUAL(json.get<std::string>(ChallengePin::JSON_PIN_CODE), "123");
+  BOOST_CHECK_EQUAL(request.m_status, STATUS_CHALLENGE);
+  BOOST_CHECK_EQUAL(request.m_challengeStatus, ChallengePin::WRONG_CODE);
+  BOOST_CHECK_EQUAL(request.m_challengeSecrets.empty(), false);
 }
 
 BOOST_AUTO_TEST_SUITE_END()
diff --git a/tests/unit-tests/client-config.t.cpp b/tests/unit-tests/client-config.t.cpp
index c057023..5f7c3a5 100644
--- a/tests/unit-tests/client-config.t.cpp
+++ b/tests/unit-tests/client-config.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2017-2018, Regents of the University of California.
+ * Copyright (c) 2017-2019, Regents of the University of California.
  *
  * This file is part of ndncert, a certificate management system based on NDN.
  *
@@ -38,8 +38,6 @@
   BOOST_CHECK_EQUAL(item.m_caName.toUri(), "/ndn/edu/ucla/CA");
   BOOST_CHECK_EQUAL(item.m_caInfo, "UCLA's ceritificate authority, located in BH4805.");
   BOOST_CHECK_EQUAL(item.m_probe, "Please use your email address to apply a namespace first. UCLA email is preferred.");
-  BOOST_CHECK_EQUAL(item.m_targetedList, "Use your email address (edu preferred) as input");
-  BOOST_CHECK_EQUAL(item.m_isListEnabled, true);
   BOOST_CHECK_EQUAL(item.m_anchor.getName().toUri(),
                     "/ndn/site1/KEY/%11%BC%22%F4c%15%FF%17/self/%FD%00%00%01Y%C8%14%D9%A5");
 
@@ -55,7 +53,6 @@
   item.m_caName = Name("/test");
   item.m_caInfo = "test";
   item.m_probe = "test";
-  item.m_isListEnabled = false;
 
   config.m_caItems.push_back(item);
   BOOST_CHECK_EQUAL(config.m_caItems.size(), 3);
diff --git a/tests/unit-tests/client-module.t.cpp b/tests/unit-tests/client-module.t.cpp
index b65b09b..da08d9b 100644
--- a/tests/unit-tests/client-module.t.cpp
+++ b/tests/unit-tests/client-module.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2017-2018, Regents of the University of California.
+ * Copyright (c) 2017-2019, Regents of the University of California.
  *
  * This file is part of ndncert, a certificate management system based on NDN.
  *
@@ -21,6 +21,7 @@
 #include "client-module.hpp"
 #include "identity-management-fixture.hpp"
 #include "challenge-module.hpp"
+#include "ca-module.hpp"
 #include <ndn-cxx/util/dummy-client-face.hpp>
 #include <ndn-cxx/security/signing-helpers.hpp>
 #include <ndn-cxx/security/transform/public-key.hpp>
@@ -30,20 +31,18 @@
 namespace ndncert {
 namespace tests {
 
-BOOST_FIXTURE_TEST_SUITE(TestClientModule, IdentityManagementV2TimeFixture)
+BOOST_FIXTURE_TEST_SUITE(TestClientModule, IdentityManagementTimeFixture)
 
 BOOST_AUTO_TEST_CASE(ClientModuleInitialize)
 {
-  util::DummyClientFace face(m_io, {true, true});
-  ClientModule client(face, m_keyChain);
+  ClientModule client(m_keyChain);
   client.getClientConf().load("tests/unit-tests/client.conf.test");
   BOOST_CHECK_EQUAL(client.getClientConf().m_caItems.size(), 2);
 }
 
-BOOST_AUTO_TEST_CASE(ProbeAndNew)
+BOOST_AUTO_TEST_CASE(Probe)
 {
-  util::DummyClientFace face(m_io, {true, true});
-  ClientModule client(face, m_keyChain);
+  ClientModule client(m_keyChain);
   client.getClientConf().load("tests/unit-tests/client.conf.test");
 
   auto identity = addIdentity(Name("/site"));
@@ -51,71 +50,14 @@
   auto cert = key.getDefaultCertificate();
 
   ClientCaItem item;
-  item.m_caName = Name("/site/CA");
+  item.m_caName = Name("/site");
   item.m_anchor = cert;
   client.getClientConf().m_caItems.push_back(item);
 
-  int nInterest = 0;
-  auto processInterest = [&] (const Interest& interest) {
-    nInterest++;
-    if (nInterest == 1) {
-      // PROBE interest and return identifier
-      BOOST_CHECK_EQUAL(interest.getName().toUri(), "/site/CA/_PROBE/zhiyi%40cs.ucla.edu");
-      BOOST_CHECK_EQUAL(interest.getMustBeFresh(), 1);
-
-      auto data = make_shared<Data>();
-      data->setName(interest.getName());
-      JsonSection json = genResponseProbeJson(Name("/site/ucla-cs-zhiyi"), Name(""));
-      std::stringstream ss;
-      boost::property_tree::write_json(ss, json);
-      Block dataContent = makeStringBlock(ndn::tlv::Content, ss.str());
-      data->setContent(dataContent);
-      m_keyChain.sign(*data, signingByCertificate(cert));
-      face.receive(*data);
-    }
-    else {
-      // NEW interest and return challenge list, request ID
-      BOOST_CHECK_EQUAL(interest.getName().getPrefix(3).toUri(), "/site/CA/_NEW");
-      BOOST_CHECK_EQUAL(interest.getName().size(), 6);
-
-      auto data = make_shared<Data>();
-      data->setName(interest.getName());
-      std::list<std::string> challenges;
-      challenges.push_back("EMAIL");
-      challenges.push_back("PIN");
-      JsonSection json = genResponseNewJson("1234", ChallengeModule::WAIT_SELECTION, challenges);
-      std::stringstream ss;
-      boost::property_tree::write_json(ss, json);
-      Block dataContent = makeStringBlock(ndn::tlv::Content, ss.str());
-      data->setContent(dataContent);
-      m_keyChain.sign(*data, signingByCertificate(cert));
-
-      face.receive(*data);
-    }
-  };
-  face.onSendInterest.connect([=] (const Interest& interest) { m_io.post([=] { processInterest(interest); }); });
-
-  int nCallback = 0;
-  shared_ptr<RequestState> requestState = nullptr;
-  ClientModule::RequestCallback requestCallback = [&] (shared_ptr<RequestState> state) {
-    nCallback++;
-    BOOST_CHECK_EQUAL(state->m_requestId, "1234");
-    BOOST_CHECK_EQUAL(state->m_challengeList.size(), 2);
-    requestState = state;
-  };
-  client.sendProbe(item, "zhiyi@cs.ucla.edu", requestCallback, ClientModule::ErrorCallback());
-
-  advanceClocks(time::milliseconds(200), 20);
-
-  BOOST_CHECK_EQUAL(nInterest, 2);
-  BOOST_CHECK_EQUAL(nCallback, 1);
-  BOOST_CHECK_EQUAL(requestState->m_ca.m_caName.toUri(), "/site/CA");
-  BOOST_CHECK_EQUAL(requestState->m_key.getName().getPrefix(3).toUri(), "/site/ucla-cs-zhiyi/KEY");
-
-  // make sure the client did not generate duplicated new keys
-  auto clientIdentity = m_keyChain.getPib().getIdentity(Name("/site/ucla-cs-zhiyi"));
-  const auto& clientKeys = clientIdentity.getKeys();
-  BOOST_CHECK_EQUAL(clientKeys.size(), 1);
+  auto firstInterest = client.generateProbeInterest(item, "zhiyi@cs.ucla.edu");
+  BOOST_CHECK_EQUAL(firstInterest->getName().toUri(), "/site/CA/_PROBE");
+  BOOST_CHECK_EQUAL(CaModule::jsonFromBlock(firstInterest->getApplicationParameters()).get<std::string>(JSON_CLIENT_PROBE_INFO),
+                    "zhiyi@cs.ucla.edu");
 }
 
 BOOST_AUTO_TEST_SUITE_END() // TestClientModule
diff --git a/tests/unit-tests/client.conf.test b/tests/unit-tests/client.conf.test
index 7cd85e8..c75d4d1 100644
--- a/tests/unit-tests/client.conf.test
+++ b/tests/unit-tests/client.conf.test
@@ -1,20 +1,18 @@
 {
   "ca-list":
   [
-    {
+    {   
         "ca-prefix": "/ndn/edu/ucla/CA",
         "ca-info": "UCLA's ceritificate authority, located in BH4805.",
-        "target-list": "Use your email address (edu preferred) as input",
         "probe": "Please use your email address to apply a namespace first. UCLA email is preferred.",
-        "is-list-enabled": "true",
         "certificate": "Bv0CJAcsCANuZG4IBXNpdGUxCANLRVkICBG8IvRjFf8XCARzZWxmCAn9AAABWcgU2aUUCRgBAhkEADbugBX9AU8wggFLMIIBAwYHKoZIzj0CATCB9wIBATAsBgcqhkjOPQEBAiEA/////wAAAAEAAAAAAAAAAAAAAAD///////////////8wWwQg/////wAAAAEAAAAAAAAAAAAAAAD///////////////wEIFrGNdiqOpPns+u9VXaYhrxlHQawzFOw9jvOPD4n0mBLAxUAxJ02CIbnBJNqZnjhE50mt4GffpAEQQRrF9Hy4SxCR/i85uVjpEDydwN9gS3rM6D0oTlF2JjClk/jQuL+Gn+bjufrSnwPnhYrzjNXazFezsu2QGg3v1H1AiEA/////wAAAAD//////////7zm+q2nF56E87nKwvxjJVECAQEDQgAES9Cb9iANUNYmwt5bjwNW1mZgjzIkDJb6FTCdiYWnkMMIVxh2YDllphoWDEAPS6kqJczzCuhnGYpZCp9tTaYKGxZMGwEDHB0HGwgDbmRuCAVzaXRlMQgDS0VZCAgRvCL0YxX/F/0A/Sb9AP4PMTk3MDAxMDFUMDAwMDAw/QD/DzIwMzcwMTE3VDIxMjg0NhdIMEYCIQDXkR1hF3GiP7yLXq+0JBJfi9QC+hhAu/1Bykx+MWz6RAIhANwelBTxxZr2C5bD15mjfhWudK4I1tOb4b/9xWCHyM7F"
-    },
-    {
+    },  
+        {   
         "ca-prefix": "/ndn/edu/ucla/zhiyi/CA",
         "ca-info": "Zhiyi's own ceritificate authority",
-        "is-list-enabled": "false",
+        "probe": "true",
         "certificate": "Bv0CJAcsCANuZG4IBXNpdGUxCANLRVkICBG8IvRjFf8XCARzZWxmCAn9AAABWcgU2aUUCRgBAhkEADbugBX9AU8wggFLMIIBAwYHKoZIzj0CATCB9wIBATAsBgcqhkjOPQEBAiEA/////wAAAAEAAAAAAAAAAAAAAAD///////////////8wWwQg/////wAAAAEAAAAAAAAAAAAAAAD///////////////wEIFrGNdiqOpPns+u9VXaYhrxlHQawzFOw9jvOPD4n0mBLAxUAxJ02CIbnBJNqZnjhE50mt4GffpAEQQRrF9Hy4SxCR/i85uVjpEDydwN9gS3rM6D0oTlF2JjClk/jQuL+Gn+bjufrSnwPnhYrzjNXazFezsu2QGg3v1H1AiEA/////wAAAAD//////////7zm+q2nF56E87nKwvxjJVECAQEDQgAES9Cb9iANUNYmwt5bjwNW1mZgjzIkDJb6FTCdiYWnkMMIVxh2YDllphoWDEAPS6kqJczzCuhnGYpZCp9tTaYKGxZMGwEDHB0HGwgDbmRuCAVzaXRlMQgDS0VZCAgRvCL0YxX/F/0A/Sb9AP4PMTk3MDAxMDFUMDAwMDAw/QD/DzIwMzcwMTE3VDIxMjg0NhdIMEYCIQDXkR1hF3GiP7yLXq+0JBJfi9QC+hhAu/1Bykx+MWz6RAIhANwelBTxxZr2C5bD15mjfhWudK4I1tOb4b/9xWCHyM7F"
-    }
+    }   
   ],
   "local-ndncert-anchor": "/usr/local/etc/ndncert/anchor.key"
 }
\ No newline at end of file
diff --git a/tests/unit-tests/crypto-helper.t.cpp b/tests/unit-tests/crypto-helper.t.cpp
new file mode 100644
index 0000000..4576942
--- /dev/null
+++ b/tests/unit-tests/crypto-helper.t.cpp
@@ -0,0 +1,217 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2017-2019, 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 "crypto-support/crypto-helper.hpp"
+#include "test-common.hpp"
+#include <iostream>
+
+namespace ndn {
+namespace ndncert {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(TestCryptoHelper)
+
+BOOST_AUTO_TEST_CASE(Test0)
+{
+  ECDHState aliceState;
+  auto alicePub = aliceState.getRawSelfPubKey();
+  BOOST_CHECK(aliceState.context->publicKeyLen != 0);
+
+  ECDHState bobState;
+  auto bobPub = bobState.getRawSelfPubKey();
+  BOOST_CHECK(bobState.context->publicKeyLen != 0);
+
+  auto aliceResult = aliceState.deriveSecret(
+        bobPub, bobState.context->publicKeyLen);
+
+  BOOST_CHECK(aliceState.context->sharedSecretLen != 0);
+
+  auto bobResult = bobState.deriveSecret(
+        alicePub, aliceState.context->publicKeyLen);
+
+  BOOST_CHECK(bobState.context->sharedSecretLen != 0);
+
+  BOOST_CHECK_EQUAL_COLLECTIONS(aliceResult, aliceResult + 32,
+                                bobResult, bobResult + 32);
+}
+
+BOOST_AUTO_TEST_CASE(Test1)
+{
+  ECDHState aliceState;
+  auto alicePub = aliceState.getBase64PubKey();
+  BOOST_CHECK(alicePub != "");
+
+  ECDHState bobState;
+  auto bobPub = bobState.getBase64PubKey();
+  BOOST_CHECK(bobPub != "");
+
+  auto aliceResult = aliceState.deriveSecret(bobPub);
+  BOOST_CHECK(aliceState.context->sharedSecretLen != 0);
+
+  auto bobResult = bobState.deriveSecret(alicePub);
+  BOOST_CHECK(bobState.context->sharedSecretLen != 0);
+
+  BOOST_CHECK_EQUAL_COLLECTIONS(aliceResult, aliceResult + 32,
+                                bobResult, bobResult + 32);
+}
+
+
+BOOST_AUTO_TEST_CASE(NDN_COMPUTE_HMAC_SHA256_TEST1)
+{
+  uint8_t secret[] = {0x00,0x01,0x02,0x03,0x04,0x05,
+                 0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c};
+
+  uint8_t salt[] = {0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,
+                    0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,
+                    0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,
+                    0x0b};
+
+  uint8_t result[32];
+
+  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};
+
+  ndn_compute_hmac_sha256(salt, sizeof(salt), secret, sizeof(secret), result);
+  BOOST_CHECK(memcmp(expected, result, sizeof(result)) == 0);
+}
+
+BOOST_AUTO_TEST_CASE(IETF_TEST_1)
+{
+  uint8_t secret[] = {0x00,0x01,0x02,0x03,0x04,0x05,
+                      0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c};
+
+  uint8_t salt[] = {0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,
+                    0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,
+                    0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,
+                    0x0b,0x0b,0x0b,0x0b};
+
+  uint8_t info[] = {0xf0,0xf1,0xf2,0xf3,0xf4,
+                    0xf5,0xf6,0xf7,0xf8,0xf9};
+
+  uint8_t result[42];
+  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};
+
+  auto resultLen = hkdf(secret, sizeof(secret),
+                        salt,sizeof(salt),
+                        result, sizeof(result),
+                        info, sizeof(info));
+
+  BOOST_CHECK(resultLen != 0);
+  BOOST_CHECK(memcmp(expected, result, sizeof(result)) == 0);
+}
+
+BOOST_AUTO_TEST_CASE(IETF_TEST_2)
+{
+  uint8_t secret[] = {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};
+
+  uint8_t salt[] = {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};
+
+  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};
+
+  uint8_t result[82];
+  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};
+
+  auto resultLen = hkdf(secret, sizeof(secret),
+                        salt,sizeof(salt),
+                        result, sizeof(result),
+                        info, sizeof(info));
+
+  BOOST_CHECK(resultLen != 0);
+  BOOST_CHECK(memcmp(expected, result, 42) == 0);
+}
+
+BOOST_AUTO_TEST_CASE(IETF_TEST_3)
+{
+  //uint8_t secret[] = {}; secret is empty in this test
+
+  uint8_t salt[] = {0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,
+                    0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,
+                    0x0b,0x0b,0x0b,0x0b,0x0b,0x0b};
+
+  //uint8_t info[] = {}; info is empty in this test
+
+  uint8_t result[42];
+  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};
+
+  auto resultLen = hkdf(nullptr, 0,
+                        salt,sizeof(salt),
+                        result, sizeof(result),
+                        nullptr, 0);
+
+  BOOST_CHECK(resultLen != 0);
+  BOOST_CHECK(memcmp(expected, result, sizeof(result)) == 0);
+}
+
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+} // namespace ndncert
+} // namespace ndn
diff --git a/tests/unit-tests/dummy-test.t.cpp b/tests/unit-tests/dummy-test.t.cpp
index d6ccd1f..70d3f50 100644
--- a/tests/unit-tests/dummy-test.t.cpp
+++ b/tests/unit-tests/dummy-test.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2017, Regents of the University of California.
+ * Copyright (c) 2017-2019, Regents of the University of California.
  *
  * This file is part of ndncert, a certificate management system based on NDN.
  *
@@ -24,7 +24,7 @@
 namespace ndncert {
 namespace tests {
 
-// See http://redmine.named-data.net/projects/nfd/wiki/UnitTesting on how to name a test suite.
+// See https://redmine.named-data.net/projects/nfd/wiki/UnitTesting on how to name a test suite.
 BOOST_AUTO_TEST_SUITE(TestSkeleton)
 
 BOOST_AUTO_TEST_CASE(Test1)
diff --git a/tests/unit-tests/enc-tlv.t.cpp b/tests/unit-tests/enc-tlv.t.cpp
new file mode 100644
index 0000000..9ba62e7
--- /dev/null
+++ b/tests/unit-tests/enc-tlv.t.cpp
@@ -0,0 +1,55 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2017-2019, 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 "crypto-support/enc-tlv.hpp"
+#include "crypto-support/crypto-helper.hpp"
+#include "test-common.hpp"
+
+namespace ndn {
+namespace ndncert {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(TestEncTlv)
+
+BOOST_AUTO_TEST_CASE(Test0)
+{
+  ECDHState aliceState;
+  auto alicePub = aliceState.getRawSelfPubKey();
+  BOOST_CHECK(aliceState.context->publicKeyLen != 0);
+
+  ECDHState bobState;
+  auto bobPub = bobState.getRawSelfPubKey();
+  BOOST_CHECK(bobState.context->publicKeyLen != 0);
+
+  auto aliceResult = aliceState.deriveSecret(bobPub, bobState.context->publicKeyLen);
+  BOOST_CHECK(aliceState.context->sharedSecretLen != 0);
+
+  auto bobResult = bobState.deriveSecret(alicePub, aliceState.context->publicKeyLen);
+  BOOST_CHECK(bobState.context->sharedSecretLen != 0);
+
+  BOOST_CHECK_EQUAL_COLLECTIONS(aliceResult, aliceResult + 32,
+                                bobResult, bobResult + 32);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+} // namespace ndncert
+} // namespace ndn
diff --git a/tests/unit-tests/json-helper.t.cpp b/tests/unit-tests/json-helper.t.cpp
deleted file mode 100644
index b089e4e..0000000
--- a/tests/unit-tests/json-helper.t.cpp
+++ /dev/null
@@ -1,83 +0,0 @@
-/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * Copyright (c) 2017, 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 "json-helper.hpp"
-
-namespace ndn {
-namespace ndncert {
-namespace tests {
-
-BOOST_AUTO_TEST_SUITE(TestJsonHelper)
-
-BOOST_AUTO_TEST_CASE(GenerateProbeJson)
-{
-  auto result = genResponseProbeJson(Name("/ndn/edu/ucla/cs/zhiyi/macbook"),
-                                     Name("/ndn/edu/ucla/cs/zhiyi/ca-info"));
-  BOOST_CHECK_EQUAL(result.get<std::string>(JSON_IDNENTIFIER), "/ndn/edu/ucla/cs/zhiyi/macbook");
-  BOOST_CHECK_EQUAL(result.get<std::string>(JSON_CA_INFO), "/ndn/edu/ucla/cs/zhiyi/ca-info");
-}
-
-BOOST_AUTO_TEST_CASE(GenerateNewResponseJson)
-{
-  std::list<std::string> challenges;
-  challenges.push_back("PIN");
-  challenges.push_back("EMAIL");
-  auto result = genResponseNewJson("598234759", "wait-selection", challenges);
-
-  BOOST_CHECK_EQUAL(result.get<std::string>(JSON_REQUEST_ID), "598234759");
-  BOOST_CHECK_EQUAL(result.get<std::string>(JSON_STATUS), "wait-selection");
-  auto child = result.get_child(JSON_CHALLENGES);
-  auto it = child.begin();
-  BOOST_CHECK_EQUAL(it->second.get<std::string>(JSON_CHALLENGE_TYPE), "PIN");
-  it++;
-  BOOST_CHECK_EQUAL(it->second.get<std::string>(JSON_CHALLENGE_TYPE), "EMAIL");
-}
-
-BOOST_AUTO_TEST_CASE(GenerateChallengeResponseJson)
-{
-  auto result = genResponseChallengeJson("598234759", "EMAIL", "need-code");
-
-  BOOST_CHECK_EQUAL(result.get<std::string>(JSON_REQUEST_ID), "598234759");
-  BOOST_CHECK_EQUAL(result.get<std::string>(JSON_CHALLENGE_TYPE), "EMAIL");
-  BOOST_CHECK_EQUAL(result.get<std::string>(JSON_STATUS), "need-code");
-
-  result = genResponseChallengeJson("598234759", "EMAIL", "need-code", Name("/ndn/test"));
-
-  BOOST_CHECK_EQUAL(result.get<std::string>(JSON_REQUEST_ID), "598234759");
-  BOOST_CHECK_EQUAL(result.get<std::string>(JSON_CHALLENGE_TYPE), "EMAIL");
-  BOOST_CHECK_EQUAL(result.get<std::string>(JSON_STATUS), "need-code");
-  BOOST_CHECK_EQUAL(result.get<std::string>(JSON_CERTIFICATE), "/ndn/test");
-}
-
-BOOST_AUTO_TEST_CASE(GenerateFailureJson)
-{
-  auto result = genFailureJson("598234759", "EMAIL", "failure",
-                               "The certificate name already exists");
-  BOOST_CHECK_EQUAL(result.get<std::string>(JSON_STATUS), "failure");
-  BOOST_CHECK_EQUAL(result.get<std::string>(JSON_FAILURE_INFO),
-                    "The certificate name already exists");
-}
-
-BOOST_AUTO_TEST_SUITE_END() // TestJsonHelper
-
-} // namespace tests
-} // namespace ndncert
-} // namespace ndn
diff --git a/tools/ndncert-ca-server.cpp b/tools/ndncert-ca-server.cpp
index 4dd518d..7e40d78 100644
--- a/tools/ndncert-ca-server.cpp
+++ b/tools/ndncert-ca-server.cpp
@@ -81,27 +81,10 @@
   security::v2::KeyChain keyChain;
   CaModule ca(face, keyChain, configFilePath);
 
-  ca.setRecommendCaHandler(Name("/ndn"),
-    [] (const std::string& input, const std::list<Name>& list) -> std::tuple<Name, std::string> {
-      Name recommendedCa;
-      std::string identity;
-      for (auto caName : list) {
-        std::string univName = readString(caName.get(-1));
-        if (input.find(univName) != std::string::npos) {
-          recommendedCa = caName;
-          identity = input.substr(0, input.find("@"));
-          break;
-        }
-      }
-      return std::make_tuple(recommendedCa, identity);
-    });
-
   if (wantRepoOut) {
-    for (const auto& caItem : ca.getCaConf().m_caItems) {
-      ca.setStatusUpdateCallback(caItem.m_caName,
-        [&] (const CertificateRequest& request) {
-          if (request.getStatus() == ChallengeModule::SUCCESS) {
-            auto issuedCert = request.getCert();
+      ca.setStatusUpdateCallback([&] (const CertificateRequest& request) {
+          if (request.m_status == STATUS_SUCCESS) {
+            auto issuedCert = request.m_cert;
             boost::asio::ip::tcp::iostream requestStream;
 #if BOOST_VERSION >= 106700
             requestStream.expires_after(std::chrono::seconds(3));
@@ -118,7 +101,6 @@
                                 issuedCert.wireEncode().size());
           }
       });
-    }
   }
 
   face.processEvents();
diff --git a/tools/ndncert-ca-status.cpp b/tools/ndncert-ca-status.cpp
index a55812e..d426535 100644
--- a/tools/ndncert-ca-status.cpp
+++ b/tools/ndncert-ca-status.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2017, Regents of the University of California.
+ * Copyright (c) 2017-2019, Regents of the University of California.
  *
  * This file is part of ndncert, a certificate management system based on NDN.
  *
@@ -20,9 +20,7 @@
 
 #include "ca-module.hpp"
 #include "ca-detail/ca-sqlite.hpp"
-
 #include <iostream>
-
 #include <boost/program_options/options_description.hpp>
 #include <boost/program_options/variables_map.hpp>
 #include <boost/program_options/parsers.hpp>
@@ -77,11 +75,12 @@
   std::cerr << "The pending requests :" << std::endl;
 
   for (const auto& entry : requestList) {
-    std::cerr << "Request ID: " << entry.getRequestId() << "\t"
-              << "Current Status: " << entry.getStatus() << std::endl
-              << "Applying CA: " << entry.getCaName().toUri() << std::endl
-              << "Applying for key: " << entry.getCert().getKeyName().toUri() << std::endl
-              << "Challenge Secret: " << convertJson2String(entry.getChallengeSecrets()) << std::endl;
+    std::cerr << "Request ID: " << entry.m_requestId << "\t"
+              << "Current Status: " << entry.m_status << std::endl
+              << "Applying CA: " << entry.m_caName << std::endl
+              << "Applying for key: " << entry.m_cert.getName() << std::endl
+              << "Challenge remaining tries: " << entry.m_remainingTries << std::endl
+              << "Challenge Secret: " << convertJson2String(entry.m_challengeSecrets) << std::endl;
   }
 
   std::cerr << "\n\n" << "The issued certs :" << std::endl;
diff --git a/tools/ndncert-client.cpp b/tools/ndncert-client.cpp
index 3ad53aa..ea38694 100644
--- a/tools/ndncert-client.cpp
+++ b/tools/ndncert-client.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2017-2018, Regents of the University of California.
+ * Copyright (c) 2017-2019, Regents of the University of California.
  *
  * This file is part of ndncert, a certificate management system based on NDN.
  *
@@ -20,10 +20,8 @@
 
 #include "client-module.hpp"
 #include "challenge-module.hpp"
-
 #include <iostream>
 #include <string>
-
 #include <boost/program_options/options_description.hpp>
 #include <boost/program_options/variables_map.hpp>
 #include <boost/program_options/parsers.hpp>
@@ -32,208 +30,252 @@
 namespace ndn {
 namespace ndncert {
 
+static void startApplication();
+
 int nStep;
+Face face;
+security::v2::KeyChain keyChain;
+std::string challengeType;
+ClientModule client(keyChain);
 
-class ClientTool
+static std::list<std::string>
+captureParams(const JsonSection& requirement)
 {
-public:
-  ClientTool(ClientModule& clientModule)
-    : client(clientModule)
-  {
+  std::list<std::string> results;
+  for (auto& item : requirement) {
+    std::cerr << item.second.get<std::string>("") << std::endl;
+    std::cerr << "Please provide the argument: " << item.first << " : " << std::endl;
+    std::string tempParam;
+    getline(std::cin, tempParam);
+    results.push_back(tempParam);
   }
-
-  void
-  errorCb(const std::string& errorInfo)
-  {
-    std::cerr << "Error: " << errorInfo << std::endl;
+  std::cerr << "Got it. This is what you've provided:" << std::endl;
+  auto it1 = results.begin();
+  auto it2 = requirement.begin();
+  for (; it1 != results.end() && it2 != requirement.end(); it1++, it2++) {
+    std::cerr << it2->first << " : " << *it1 << std::endl;
   }
+  return results;
+}
 
-  void
-  downloadCb(const shared_ptr<RequestState>& state)
-  {
-    std::cerr << "Step " << nStep++
-              << "DONE! Certificate has already been installed to local keychain\n";
+static void
+onNackCb()
+{
+  std::cerr << "Got NACK\n";
+}
+
+static void
+timeoutCb()
+{
+  std::cerr << "Interest sent time out\n";
+}
+
+static void
+downloadCb(const Data& reply)
+{
+  client.onDownloadResponse(reply);
+  std::cerr << "Step " << nStep++
+            << ": DONE! Certificate has already been installed to local keychain\n";
+  return;
+}
+
+static void
+challengeCb(const Data& reply)
+{
+  client.onChallengeResponse(reply);
+  if (client.getApplicationStatus() == STATUS_SUCCESS) {
+    std::cerr << "DONE! Certificate has already been issued \n";
+    face.expressInterest(*client.generateDownloadInterest(), bind(&downloadCb, _2),
+                         bind(&onNackCb), bind(&timeoutCb));
     return;
   }
 
-  void
-  anchorCb(const Interest& request, const Data& reply,
-           const ClientCaItem& anchorItem, const Name& assignedName)
-  {
-    auto contentJson = ClientModule::getJsonFromData(reply);
-    auto caItem = ClientConfig::extractCaItem(contentJson);
-
-    if (!security::verifySignature(caItem.m_anchor, anchorItem.m_anchor)) {
-      std::cerr << "Fail to verify fetched anchor" << std::endl;
-      return;
-    }
-    client.getClientConf().m_caItems.push_back(caItem);
-
-    if (assignedName.toUri() != "/") {
-      client.sendNew(caItem, assignedName,
-                     bind(&ClientTool::newCb, this, _1),
-                     bind(&ClientTool::errorCb, this, _1));
-    }
-    else {
-      if (caItem.m_probe != "") {
-        std::cerr << "Step " << nStep++ << ": Probe Requirement-" << caItem.m_probe << std::endl;
-        std::string probeInfo;
-        getline(std::cin, probeInfo);
-        client.sendProbe(caItem, probeInfo,
-                         bind(&ClientTool::newCb, this, _1),
-                         bind(&ClientTool::errorCb, this, _1));
-      }
-      else {
-        std::cerr << "Step " << nStep++ << ": Please type in the identity name\n";
-        std::string nameComponent;
-        getline(std::cin, nameComponent);
-        Name identityName = caItem.m_caName.getPrefix(-1);
-        identityName.append(nameComponent);
-        client.sendNew(caItem, identityName,
-                       bind(&ClientTool::newCb, this, _1),
-                       bind(&ClientTool::errorCb, this, _1));
-      }
-    }
-  }
-
-  void
-  listCb(const std::list<Name>& caList, const Name& assignedName, const Name& schema,
-         const ClientCaItem& caItem)
-  {
-    if (assignedName.toUri() != "" && caList.size() == 1) {
-      // with recommendation
-
-      std::cerr << "Get recommended CA: " << caList.front()
-                << "Get recommended Identity: " << assignedName << std::endl;
-      client.requestCaTrustAnchor(caList.front(),
-                                  bind(&ClientTool::anchorCb, this, _1, _2, caItem, assignedName),
-                                  bind(&ClientTool::errorCb, this, _1));
-    }
-    else {
-      // without recommendation
-      int count = 0;
-      for (auto name : caList) {
-        std::cerr << "***************************************\n"
-                  << "Index: " << count++ << "\n"
-                  << "CA prefix:" << name << "\n"
-                  << "***************************************\n";
-      }
-      std::cerr << "Select an index to apply for a certificate\n";
-
-      std::string option;
-      getline(std::cin, option);
-      int caIndex = std::stoi(option);
-
-      std::vector<Name> caVector{std::begin(caList), std::end(caList)};
-      Name targetCaName = caVector[caIndex];
-
-      client.requestCaTrustAnchor(targetCaName,
-                                  bind(&ClientTool::anchorCb, this, _1, _2, caItem, Name("")),
-                                  bind(&ClientTool::errorCb, this, _1));
-    }
-  }
-
-  void
-  validateCb(const shared_ptr<RequestState>& state)
-  {
-    if (state->m_status == ChallengeModule::SUCCESS) {
-      std::cerr << "DONE! Certificate has already been issued \n";
-      client.requestDownload(state,
-                             bind(&ClientTool::downloadCb, this, _1),
-                             bind(&ClientTool::errorCb, this, _1));
-      return;
-    }
-
-    auto challenge = ChallengeModule::createChallengeModule(state->m_challengeType);
-    auto requirementList = challenge->getRequirementForValidate(state->m_status);
-
+  auto challenge = ChallengeModule::createChallengeModule(challengeType);
+  auto requirement = challenge->getRequirementForChallenge(client.getApplicationStatus(), client.getChallengeStatus());
+  if (requirement.size() > 0) {
     std::cerr << "Step " << nStep++ << ": Please satisfy following instruction(s)\n";
-    for (auto requirement : requirementList) {
-      std::cerr << "\t" << requirement << std::endl;
+    std::string redo = "";
+    std::list<std::string> capturedParams;
+    do {
+      capturedParams = captureParams(requirement);
+      std::cerr << "If anything is wrong, please type in OK; otherwise, type in REDO" << std::endl;
+      getline(std::cin, redo);
+    } while (redo == "REDO");
+    auto it1 = capturedParams.begin();
+    auto it2 = requirement.begin();
+    for (; it1 != capturedParams.end() && it2 != requirement.end(); it1++, it2++) {
+      it2->second.put("", *it1);
     }
-    std::list<std::string> paraList;
-    for (size_t i = 0; i < requirementList.size(); i++) {
-      std::string tempParam;
-      getline(std::cin, tempParam);
-      paraList.push_back(tempParam);
-    }
-    auto paramJson = challenge->genValidateParamsJson(state->m_status, paraList);
-    client.sendValidate(state, paramJson,
-                        bind(&ClientTool::validateCb, this, _1),
-                        bind(&ClientTool::errorCb, this, _1));
   }
+  face.expressInterest(*client.generateChallengeInterest(
+                        challenge->genChallengeRequestJson(
+                                   client.getApplicationStatus(),
+                                   client.getChallengeStatus(),
+                                   requirement)),
+                       bind(&challengeCb, _2),
+                       bind(&onNackCb),
+                       bind(&timeoutCb));
+}
 
-  void
-  selectCb(const shared_ptr<RequestState>& state)
-  {
-    auto challenge = ChallengeModule::createChallengeModule(state->m_challengeType);
-    auto requirementList = challenge->getRequirementForValidate(state->m_status);
-
-    std::cerr << "Step " << nStep++ << ": Please satisfy following instruction(s)" << std::endl;
-    for (auto item : requirementList) {
-      std::cerr << "\t" << item << std::endl;
-    }
-    std::list<std::string> paraList;
-    for (size_t i = 0; i < requirementList.size(); i++) {
-      std::string tempParam;
-      getline(std::cin, tempParam);
-      paraList.push_back(tempParam);
-    }
-
-    auto paramJson = challenge->genValidateParamsJson(state->m_status, paraList);
-    client.sendValidate(state, paramJson,
-                        bind(&ClientTool::validateCb, this, _1),
-                        bind(&ClientTool::errorCb, this, _1));
+static void
+newCb(const Data& reply)
+{
+  auto challengeList = client.onNewResponse(reply);
+  std::cerr << "Step " << nStep++ << ": Please type in the challenge ID from the following challenges\n";
+  for (auto item : challengeList) {
+    std::cerr << "\t" << item << std::endl;
   }
+  std::string choice;
+  getline(std::cin, choice);
 
-  void
-  newCb(const shared_ptr<RequestState>& state)
-  {
-    std::cerr << "Step " << nStep++ << ": Please select one challenge from following types\n";
-    for (auto item : state->m_challengeList) {
-      std::cerr << "\t" << item << std::endl;
-    }
-    std::string choice;
-    getline(std::cin, choice);
-
-    auto challenge = ChallengeModule::createChallengeModule(choice);
-    auto requirementList = challenge->getRequirementForSelect();
-    std::list<std::string> paraList;
-    if (requirementList.size() != 0) {
-      std::cerr << "Step " << nStep++ << ": Please satisfy following instruction(s)\n";
-      for (auto item : requirementList) {
-        std::cerr << "\t" << item << std::endl;
-      }
-      for (size_t i = 0; i < requirementList.size(); i++) {
-        std::string tempParam;
-        getline(std::cin, tempParam);
-        paraList.push_back(tempParam);
-      }
-    }
-    auto paramJson = challenge->genSelectParamsJson(state->m_status, paraList);
-    client.sendSelect(state, choice, paramJson,
-                      bind(&ClientTool::selectCb, this, _1),
-                      bind(&ClientTool::errorCb, this, _1));
+  auto challenge = ChallengeModule::createChallengeModule(choice);
+  if (challenge != nullptr) {
+    challengeType = choice;
   }
+  else {
+    std::cerr << "Cannot recognize the specified challenge. Exit";
+    return;
+  }
+  auto requirement = challenge->getRequirementForChallenge(client.getApplicationStatus(),
+                                                           client.getChallengeStatus());
+  if (requirement.size() > 0) {
+    std::cerr << "Step " << nStep++ << ": Please satisfy following instruction(s)\n";
+    std::string redo = "";
+    std::list<std::string> capturedParams;
+    do {
+      capturedParams = captureParams(requirement);
+      std::cerr << "If anything is wrong, please type in OK; otherwise, type in REDO" << std::endl;
+      getline(std::cin, redo);
+    } while (redo == "REDO");
+    auto it1 = capturedParams.begin();
+    auto it2 = requirement.begin();
+    for (; it1 != capturedParams.end() && it2 != requirement.end(); it1++, it2++) {
+      it2->second.put("", *it1);
+    }
+  }
+  face.expressInterest(*client.generateChallengeInterest(
+                               challenge->genChallengeRequestJson(
+                                          client.getApplicationStatus(),
+                                          client.getChallengeStatus(),
+                                          requirement)),
+                       bind(&challengeCb, _2),
+                       bind(&onNackCb),
+                       bind(&timeoutCb));
+}
 
-public:
-  ClientModule& client;
-};
+static void
+probeInfoCb(const Data& reply)
+{
+  auto contentJson = ClientModule::getJsonFromData(reply);
+  auto caItem = ClientConfig::extractCaItem(contentJson);
+
+  std::cerr << "Will install new trust anchor, please double check the identity info: \n"
+            << "This trust anchor packet is signed by " << reply.getSignature().getKeyLocator() << std::endl
+            << "The signing certificate is " << caItem.m_anchor << std::endl;
+  std::cerr << "Do you trust the information? Type in YES or NO" << std::endl;
+
+  std::string answer;
+  getline(std::cin, answer);
+  if (answer == "YES") {
+    client.onProbeInfoResponse(reply);
+    std::cerr << "You answered YES: new CA installed" << std::endl;
+    startApplication();
+  }
+  else {
+    std::cerr << "New CA not installed" << std::endl;
+    return;
+  }
+}
+
+static void
+probeCb(const Data& reply)
+{
+  std::cerr << "Step " << nStep++
+            << ": Please type in your expected validity period of your certificate."
+            << " Type in a number in unit of hour. The CA may change the validity"
+            << " period if your expected period is too long." << std::endl;
+  std::string periodStr;
+  getline(std::cin, periodStr);
+  int hours = std::stoi(periodStr);
+  face.expressInterest(*client.generateNewInterest(time::system_clock::now(),
+                                                   time::system_clock::now() + time::hours(hours)),
+                       bind(&newCb, _2),
+                       bind(&onNackCb),
+                       bind(&timeoutCb));
+}
+
+static void
+startApplication()
+{
+  nStep = 0;
+  auto caList = client.getClientConf().m_caItems;
+  int count = 0;
+  for (auto item : caList) {
+    std::cerr << "***************************************\n"
+              << "Index: " << count++ << "\n"
+              << "CA prefix:" << item.m_caName << "\n"
+              << "Introduction: " << item.m_caInfo << "\n"
+              << "***************************************\n";
+  }
+  std::vector<ClientCaItem> caVector{std::begin(caList), std::end(caList)};
+  std::cerr << "Step "
+            << nStep++ << ": Please type in the CA INDEX that you want to apply"
+            << " or type in NONE if your expected CA is not in the list\n";
+
+  std::string caIndexS;
+  getline(std::cin, caIndexS);
+  if (caIndexS == "NONE") {
+    std::cerr << "Step " << nStep << ": Please type in the CA Name\n";
+    face.expressInterest(*client.generateProbeInfoInterest(Name(caIndexS)),
+                         bind(&probeInfoCb, _2),
+                         bind(&onNackCb),
+                         bind(&timeoutCb));
+  }
+  else {
+    int caIndex = std::stoi(caIndexS);
+    BOOST_ASSERT(caIndex <= count);
+    auto targetCaItem = caVector[caIndex];
+
+    if (targetCaItem.m_probe != "") {
+      std::cerr << "Step " << nStep++ << ": Probe Requirement-" << targetCaItem.m_probe << std::endl;
+      std::string probeInfo;
+      getline(std::cin, probeInfo);
+      face.expressInterest(*client.generateProbeInterest(targetCaItem, probeInfo),
+                           bind(&probeCb, _2),
+                           bind(&onNackCb),
+                           bind(&timeoutCb));
+    }
+    else {
+      std::cerr << "Step " << nStep++ << ": Please type in the identity name you want to get (with CA prefix)\n";
+      std::string identityNameStr;
+      getline(std::cin, identityNameStr);
+      std::cerr << "Step "
+                << nStep++ << ": Please type in your expected validity period of your certificate."
+                << "Type in a number in unit of hour."
+                << " The CA may change the validity period if your expected period is too long.\n";
+      std::string periodStr;
+      getline(std::cin, periodStr);
+      int hours = std::stoi(periodStr);
+      face.expressInterest(*client.generateNewInterest(time::system_clock::now(),
+                                                       time::system_clock::now() + time::hours(hours),
+                                                       Name(identityNameStr)),
+                           bind(&newCb, _2),
+                           bind(&onNackCb),
+                           bind(&timeoutCb));
+    }
+  }
+}
+
 
 int
 main(int argc, char* argv[])
 {
   namespace po = boost::program_options;
   std::string configFilePath = std::string(SYSCONFDIR) + "/ndncert/client.conf";
-  bool isIntra = false;
-  po::options_description description("General Usage\n ndncert-client [-h] [-i] [-f]\n");
+  po::options_description description("General Usage\n ndncert-client [-h] [-f]\n");
   description.add_options()
-    ("help,h",
-     "produce help message")
-    ("intra-node,i",
-     "optional, if specified, switch on the intra-node mode")
-    ("config-file,f", po::value<std::string>(&configFilePath),
-     "config file name");
+    ("help,h", "produce help message")
+    ("config-file,f", po::value<std::string>(&configFilePath), "config file name");
   po::positional_options_description p;
 
   po::variables_map vm;
@@ -249,131 +291,8 @@
     std::cerr << description << std::endl;
     return 0;
   }
-  if (vm.count("intra-node") != 0) {
-    isIntra = true;
-  }
-
-  nStep = 0;
-  Face face;
-  security::v2::KeyChain keyChain;
-  ClientModule client(face, keyChain);
   client.getClientConf().load(configFilePath);
-  ClientTool tool(client);
-
-  if (isIntra) {
-    client.requestLocalhostList([&](const ClientConfig& config) {
-        auto caList = config.m_caItems;
-        int count = 0;
-        for (auto item : caList) {
-          std::cerr << "***************************************\n"
-                    << "Index: " << count++ << "\n"
-                    << "CA prefix:" << item.m_caName << "\n"
-                    << "Introduction: " << item.m_caInfo << "\n"
-                    << "***************************************\n";
-        }
-        std::vector<ClientCaItem> caVector{std::begin(caList), std::end(caList)};
-        std::cerr << "Step " << nStep++
-                  << ": Please type in the CA namespace index that you want to apply\n";
-        std::string caIndexS;
-        getline(std::cin, caIndexS);
-        int caIndex = std::stoi(caIndexS);
-
-        BOOST_ASSERT(caIndex <= count);
-
-        auto targetCaItem = caVector[caIndex];
-        if (targetCaItem.m_probe != "") {
-          std::cerr << "Step " << nStep++ << ": Probe Requirement-" << targetCaItem.m_probe << std::endl;
-          std::string probeInfo;
-          getline(std::cin, probeInfo);
-          client.sendProbe(targetCaItem, probeInfo,
-                           bind(&ClientTool::newCb, &tool, _1),
-                           bind(&ClientTool::errorCb, &tool, _1));
-        }
-        else {
-          std::cerr << "Step " << nStep++ << ": Please type in the identity name\n";
-          std::string nameComponent;
-          getline(std::cin, nameComponent);
-          Name identityName = targetCaItem.m_caName.getPrefix(-1);
-          identityName.append(nameComponent);
-          client.sendNew(targetCaItem, identityName,
-                         bind(&ClientTool::newCb, &tool, _1),
-                         bind(&ClientTool::errorCb, &tool, _1));
-        }
-      },
-      bind(&ClientTool::errorCb, &tool, _1));
-  }
-  else {
-    // Inter-node Application
-    bool listFirst = false;
-    auto caList = client.getClientConf().m_caItems;
-    int count = 0;
-    for (auto item : caList) {
-      std::cerr << "***************************************\n"
-                << "Index: " << count++ << "\n"
-                << "CA prefix:" << item.m_caName << "\n"
-                << "Introduction: " << item.m_caInfo << "\n"
-                << "***************************************\n";
-    }
-    std::vector<ClientCaItem> caVector{std::begin(caList), std::end(caList)};
-    std::cerr << "Step " << nStep++ << ": Please type in the CA namespace index that you want to apply\n";
-
-    std::string caIndexS;
-    getline(std::cin, caIndexS);
-    int caIndex = std::stoi(caIndexS);
-    BOOST_ASSERT(caIndex <= count);
-    auto targetCaItem = caVector[caIndex];
-
-    if (targetCaItem.m_isListEnabled) {
-      std::cerr << "This CA provides several sub-namepace CAs \n"
-                << "Do you want to (A) get a certificate from " << targetCaItem.m_caName << " directly? \n"
-                << "Or (B) get a certificate from one of its sub-namespace CAs? \n"
-                << "Please type in your choice (A or B) \n";
-      std::string listOption;
-      getline(std::cin, listOption);
-      if (listOption == "A" || listOption == "a") {
-        listFirst = false;
-      }
-      else if (listOption == "B" || listOption == "b") {
-        listFirst = true;
-        std::string additionalInfo = "";
-        if (targetCaItem.m_targetedList != "") {
-          std::cerr << "Step " << nStep++
-                    << ": Enter nothing if you want to see all available sub-namespace CAs"
-                    << " or follow the instruction to get a recommended CA\n"
-                    << "\t" << targetCaItem.m_targetedList << std::endl;
-          getline(std::cin, additionalInfo);
-        }
-        client.requestList(targetCaItem, additionalInfo,
-                           bind(&ClientTool::listCb, &tool, _1, _2, _3, targetCaItem),
-                           bind(&ClientTool::errorCb, &tool, _1));
-      }
-      else {
-        std::cerr << "Your input is not an option." << std::endl;
-        return 1;
-      }
-    }
-    if (!listFirst) {
-      if (targetCaItem.m_probe != "") {
-        std::cerr << "Step " << nStep++ << ": Probe Requirement-" << targetCaItem.m_probe << std::endl;
-        std::string probeInfo;
-        getline(std::cin, probeInfo);
-        client.sendProbe(targetCaItem, probeInfo,
-                         bind(&ClientTool::newCb, &tool, _1),
-                         bind(&ClientTool::errorCb, &tool, _1));
-      }
-      else {
-        std::cerr << "Step " << nStep++ << ": Please type in the identity name\n";
-        std::string nameComponent;
-        getline(std::cin, nameComponent);
-        Name identityName = targetCaItem.m_caName.getPrefix(-1);
-        identityName.append(nameComponent);
-        client.sendNew(targetCaItem, identityName,
-                       bind(&ClientTool::newCb, &tool, _1),
-                       bind(&ClientTool::errorCb, &tool, _1));
-      }
-    }
-  }
-
+  startApplication();
   face.processEvents();
   return 0;
 }
diff --git a/wscript b/wscript
index dab4d7a..85fe3d5 100644
--- a/wscript
+++ b/wscript
@@ -10,7 +10,7 @@
 
 def options(opt):
     opt.load(['compiler_cxx', 'gnu_dirs'])
-    opt.load(['boost', 'default-compiler-flags', 'sqlite3',
+    opt.load(['boost', 'default-compiler-flags', 'sqlite3', 'openssl',
               'coverage', 'sanitizers',
               'doxygen', 'sphinx_build'],
              tooldir=['.waf-tools'])
@@ -21,7 +21,7 @@
 
 def configure(conf):
     conf.load(['compiler_cxx', 'gnu_dirs',
-               'boost', 'default-compiler-flags', 'sqlite3',
+               'boost', 'default-compiler-flags', 'sqlite3', 'openssl',
                'doxygen', 'sphinx_build'])
 
     if 'PKG_CONFIG_PATH' not in os.environ:
@@ -44,6 +44,7 @@
                    ' (https://redmine.named-data.net/projects/nfd/wiki/Boost_FAQ)')
 
     conf.check_compiler_flags()
+    conf.check_openssl(mandatory=True, atleast_version=0x1000200f) # 1.1.1b
 
     # Loading "late" to prevent tests from being compiled with profiling flags
     conf.load('coverage')