refactor client module to requester

Change-Id: I2b9835af7f03942bfdb6a886c95cfb4b907e2068
diff --git a/src/challenge-modules/challenge-email.cpp b/src/challenge-modules/challenge-email.cpp
index 327fb79..c12ba88 100644
--- a/src/challenge-modules/challenge-email.cpp
+++ b/src/challenge-modules/challenge-email.cpp
@@ -19,7 +19,6 @@
  */
 
 #include "challenge-email.hpp"
-#include "../ca-module.hpp"
 #include <regex>
 
 namespace ndn {
diff --git a/src/challenge-modules/challenge-pin.cpp b/src/challenge-modules/challenge-pin.cpp
index 4853eed..fd5b5a5 100644
--- a/src/challenge-modules/challenge-pin.cpp
+++ b/src/challenge-modules/challenge-pin.cpp
@@ -19,7 +19,6 @@
  */
 
 #include "challenge-pin.hpp"
-
 #include <ndn-cxx/util/random.hpp>
 
 namespace ndn {
diff --git a/src/client-module.cpp b/src/client-module.cpp
deleted file mode 100644
index ffda468..0000000
--- a/src/client-module.cpp
+++ /dev/null
@@ -1,406 +0,0 @@
-/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * Copyright (c) 2017-2020, Regents of the University of California.
- *
- * This file is part of ndncert, a certificate management system based on NDN.
- *
- * ndncert is free software: you can redistribute it and/or modify it under the terms
- * of the GNU General Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * ndncert is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
- * PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received copies of the GNU General Public License along with
- * ndncert, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
- *
- * See AUTHORS.md for complete list of ndncert authors and contributors.
- */
-
-#include "client-module.hpp"
-
-#include <ndn-cxx/security/signing-helpers.hpp>
-#include <ndn-cxx/security/transform/base64-encode.hpp>
-#include <ndn-cxx/security/transform/buffer-source.hpp>
-#include <ndn-cxx/security/transform/stream-sink.hpp>
-#include <ndn-cxx/security/verification-helpers.hpp>
-#include <ndn-cxx/util/io.hpp>
-#include <ndn-cxx/util/random.hpp>
-
-#include "challenge-module.hpp"
-#include "crypto-support/enc-tlv.hpp"
-#include "protocol-detail/challenge.hpp"
-#include "protocol-detail/info.hpp"
-#include "protocol-detail/new-renew-revoke.hpp"
-#include "protocol-detail/probe.hpp"
-#include "protocol-detail/error.hpp"
-#include "ndncert-common.hpp"
-
-namespace ndn {
-namespace ndncert {
-
-_LOG_INIT(ndncert.client);
-
-ClientModule::ClientModule(security::v2::KeyChain& keyChain)
-    : m_keyChain(keyChain)
-{
-}
-
-ClientModule::~ClientModule()
-{
-  endSession();
-}
-
-shared_ptr<Interest>
-ClientModule::generateInfoInterest(const Name& caName)
-{
-  Name interestName = caName;
-  if (readString(caName.at(-1)) != "CA")
-    interestName.append("CA");
-  interestName.append("INFO");
-  auto interest = make_shared<Interest>(interestName);
-  interest->setMustBeFresh(true);
-  interest->setCanBePrefix(false);
-  return interest;
-}
-
-bool
-ClientModule::verifyInfoResponse(const Data& reply)
-{
-  // parse the ca item
-  auto caItem = INFO::decodeDataContent(reply.getContent());
-
-  // verify the probe Data's sig
-  if (!security::verifySignature(reply, *caItem.m_cert)) {
-    _LOG_ERROR("Cannot verify data signature from " << m_ca.m_caPrefix.toUri());
-    return false;
-  }
-  return true;
-}
-
-void
-ClientModule::addCaFromInfoResponse(const Data& reply)
-{
-  const Block& contentBlock = reply.getContent();
-
-  // parse the ca item
-  auto caItem = INFO::decodeDataContent(contentBlock);
-
-  // update the local config
-  bool findItem = false;
-  for (auto& item : m_config.m_caItems) {
-    if (item.m_caPrefix == caItem.m_caPrefix) {
-      findItem = true;
-      item = caItem;
-    }
-  }
-  if (!findItem) {
-    m_config.m_caItems.push_back(caItem);
-  }
-}
-
-shared_ptr<Interest>
-ClientModule::generateProbeInterest(const CaConfigItem& ca,
-                                    std::vector<std::tuple<std::string, std::string>>&& probeInfo)
-{
-  Name interestName = ca.m_caPrefix;
-  interestName.append("CA").append("PROBE");
-  auto interest = make_shared<Interest>(interestName);
-  interest->setMustBeFresh(true);
-  interest->setCanBePrefix(false);
-  interest->setApplicationParameters(PROBE::encodeApplicationParameters(std::move(probeInfo)));
-
-  // update local state
-  m_ca = ca;
-  return interest;
-}
-
-void
-ClientModule::onProbeResponse(const Data& reply)
-{
-  if (!security::verifySignature(reply, *m_ca.m_cert)) {
-    _LOG_ERROR("Cannot verify data signature from " << m_ca.m_caPrefix.toUri());
-    return;
-  }
-
-  // error handling
-  processIfError(reply);
-
-  auto contentTLV = reply.getContent();
-  contentTLV.parse();
-
-  // read the available name and put it into the state
-  if (contentTLV.get(tlv_probe_response).hasValue()) {
-    Block probeResponseBlock = contentTLV.get(tlv_probe_response);
-    probeResponseBlock.parse();
-    m_identityName.wireDecode(probeResponseBlock.get(tlv::Name));
-  }
-  else {
-    NDN_LOG_TRACE("The JSON_CA_NAME is empty.");
-  }
-}
-
-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_caPrefix.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 {
-      NDN_LOG_TRACE("Randomly create a new name because m_identityName is empty and the param is empty.");
-      auto id = std::to_string(random::generateSecureWord64());
-      m_identityName = m_ca.m_caPrefix;
-      m_identityName.append(id);
-    }
-  }
-
-  // generate a newly key pair or use an existing key
-  const auto& pib = m_keyChain.getPib();
-  security::pib::Identity identity;
-  try {
-    identity = pib.getIdentity(m_identityName);
-  }
-  catch (const security::Pib::Error& e) {
-    identity = m_keyChain.createIdentity(m_identityName);
-    m_isNewlyCreatedIdentity = true;
-    m_isNewlyCreatedKey = true;
-  }
-  try {
-    m_key = identity.getDefaultKey();
-  }
-  catch (const security::Pib::Error& e) {
-    m_key = m_keyChain.createKey(identity);
-    m_isNewlyCreatedKey = true;
-  }
-
-  // generate certificate request
-  security::v2::Certificate certRequest;
-  certRequest.setName(Name(m_key.getName()).append("cert-request").appendVersion());
-  certRequest.setContentType(tlv::ContentType_Key);
-  certRequest.setContent(m_key.getPublicKey().data(), m_key.getPublicKey().size());
-  SignatureInfo signatureInfo;
-  signatureInfo.setValidityPeriod(security::ValidityPeriod(notBefore, notAfter));
-  m_keyChain.sign(certRequest, signingByKey(m_key.getName()).setSignatureInfo(signatureInfo));
-
-  // generate Interest packet
-  Name interestName = m_ca.m_caPrefix;
-  interestName.append("CA").append("NEW");
-  auto interest = make_shared<Interest>(interestName);
-  interest->setMustBeFresh(true);
-  interest->setCanBePrefix(false);
-  interest->setApplicationParameters(
-      NEW_RENEW_REVOKE::encodeApplicationParameters(RequestType::NEW, m_ecdh.getBase64PubKey(), certRequest));
-
-  // sign the Interest packet
-  m_keyChain.sign(*interest, signingByKey(m_key.getName()));
-  return interest;
-}
-
-std::list<std::string>
-ClientModule::onNewRenewRevokeResponse(const Data& reply)
-{
-  if (!security::verifySignature(reply, *m_ca.m_cert)) {
-    _LOG_ERROR("Cannot verify data signature from " << m_ca.m_caPrefix.toUri());
-    return std::list<std::string>();
-  }
-
-  // error handling
-  processIfError(reply);
-
-  auto contentTLV = reply.getContent();
-  contentTLV.parse();
-
-  // ECDH
-  const auto& peerKeyBase64Str = readString(contentTLV.get(tlv_ecdh_pub));
-  const auto& saltStr = readString(contentTLV.get(tlv_salt));
-  uint64_t saltInt = std::stoull(saltStr);
-  m_ecdh.deriveSecret(peerKeyBase64Str);
-
-  // HKDF
-  hkdf(m_ecdh.context->sharedSecret, m_ecdh.context->sharedSecretLen,
-       (uint8_t*)&saltInt, sizeof(saltInt), m_aesKey, sizeof(m_aesKey));
-
-  // update state
-  m_status = static_cast<Status>(readNonNegativeInteger(contentTLV.get(tlv_status)));
-  m_requestId = readString(contentTLV.get(tlv_request_id));
-  m_challengeList.clear();
-  for (auto const& element : contentTLV.elements()) {
-    if (element.type() == tlv_challenge) {
-      m_challengeList.push_back(readString(element));
-    }
-  }
-  return m_challengeList;
-}
-
-shared_ptr<Interest>
-ClientModule::generateRevokeInterest(const security::v2::Certificate& certificate)
-{
-  // Name requestedName = identityName;
-  bool findCa = false;
-  for (const auto& caItem : m_config.m_caItems) {
-    if (caItem.m_caPrefix.isPrefixOf(certificate.getName())) {
-      m_ca = caItem;
-      findCa = true;
-    }
-  }
-  if (!findCa) {  // if cannot find, cannot proceed
-    _LOG_TRACE("Cannot find corresponding CA for the certificate.");
-    return nullptr;
-  }
-
-  // generate Interest packet
-  Name interestName = m_ca.m_caPrefix;
-  interestName.append("CA").append("REVOKE");
-  auto interest = make_shared<Interest>(interestName);
-  interest->setMustBeFresh(true);
-  interest->setCanBePrefix(false);
-  interest->setApplicationParameters(
-      NEW_RENEW_REVOKE::encodeApplicationParameters(RequestType::REVOKE, m_ecdh.getBase64PubKey(), certificate));
-
-  // return the Interest packet
-  return interest;
-}
-
-shared_ptr<Interest>
-ClientModule::generateChallengeInterest(const Block& challengeRequest)
-{
-  challengeRequest.parse();
-  m_challengeType = readString(challengeRequest.get(tlv_selected_challenge));
-
-  Name interestName = m_ca.m_caPrefix;
-  interestName.append("CA").append("CHALLENGE").append(m_requestId);
-  auto interest = make_shared<Interest>(interestName);
-  interest->setMustBeFresh(true);
-  interest->setCanBePrefix(false);
-
-  // encrypt the Interest parameters
-  auto paramBlock = encodeBlockWithAesGcm128(tlv::ApplicationParameters, m_aesKey,
-                                             challengeRequest.value(), challengeRequest.value_size(),
-                                             (const uint8_t*)"test", strlen("test"));
-  interest->setApplicationParameters(paramBlock);
-
-  m_keyChain.sign(*interest, signingByKey(m_key.getName()));
-  return interest;
-}
-
-void
-ClientModule::onChallengeResponse(const Data& reply)
-{
-  if (!security::verifySignature(reply, *m_ca.m_cert)) {
-    _LOG_ERROR("Cannot verify data signature from " << m_ca.m_caPrefix.toUri());
-    return;
-  }
-
-  // error handling
-  processIfError(reply);
-
-  auto result = decodeBlockWithAesGcm128(reply.getContent(), m_aesKey, (const uint8_t*)"test", strlen("test"));
-
-  Block contentTLV = makeBinaryBlock(tlv_encrypted_payload, result.data(), result.size());
-  contentTLV.parse();
-
-  // update state
-  m_status = static_cast<Status>(readNonNegativeInteger(contentTLV.get(tlv_status)));
-  m_challengeStatus = readString(contentTLV.get(tlv_challenge_status));
-  m_remainingTries = readNonNegativeInteger(contentTLV.get(tlv_remaining_tries));
-  m_freshBefore = time::system_clock::now() +
-                  time::seconds(readNonNegativeInteger(contentTLV.get(tlv_remaining_time)));
-
-  if (contentTLV.find(tlv_issued_cert_name) != contentTLV.elements_end()) {
-    Block issuedCertNameBlock = contentTLV.get(tlv_issued_cert_name);
-    issuedCertNameBlock.parse();
-    m_issuedCertName.wireDecode(issuedCertNameBlock.get(tlv::Name));
-  }
-}
-
-shared_ptr<Interest>
-ClientModule::generateDownloadInterest()
-{
-  Name interestName = m_ca.m_caPrefix;
-  interestName.append("CA").append("DOWNLOAD").append(m_requestId);
-  auto interest = make_shared<Interest>(interestName);
-  interest->setMustBeFresh(true);
-  interest->setCanBePrefix(false);
-  return interest;
-}
-
-shared_ptr<Interest>
-ClientModule::generateCertFetchInterest()
-{
-  Name interestName = m_issuedCertName;
-  auto interest = make_shared<Interest>(interestName);
-  interest->setMustBeFresh(true);
-  interest->setCanBePrefix(false);
-  return interest;
-}
-
-void
-ClientModule::onCertFetchResponse(const Data& reply)
-{
-  try {
-    security::v2::Certificate cert(reply.getContent().blockFromValue());
-    m_keyChain.addCertificate(m_key, cert);
-    _LOG_TRACE("Fetched and installed the cert " << cert.getName());
-  }
-  catch (const std::exception& e) {
-    _LOG_ERROR("Cannot add replied certificate into the keychain " << e.what());
-    return;
-  }
-}
-
-void
-ClientModule::endSession()
-{
-  if (getApplicationStatus() == Status::SUCCESS || getApplicationStatus() == Status::ENDED) {
-    return;
-  }
-  if (m_isNewlyCreatedIdentity) {
-    // put the identity into the if scope is because it may cause an error
-    // outside since when endSession is called, identity may not have been created yet.
-    auto identity = m_keyChain.getPib().getIdentity(m_identityName);
-    m_keyChain.deleteIdentity(identity);
-  }
-  else if (m_isNewlyCreatedKey) {
-    auto identity = m_keyChain.getPib().getIdentity(m_identityName);
-    m_keyChain.deleteKey(identity, m_key);
-  }
-  m_status = Status::ENDED;
-}
-
-void
-ClientModule::processIfError(const Data& data)
-{
-  auto contentTLV = data.getContent();
-  if (ErrorTLV::isErrorContent(contentTLV)) {
-  try {
-    auto error = ErrorTLV::decodefromDataContent(contentTLV);
-    _LOG_ERROR("Error data returned for " << data.getName() << ": " << std::endl <<
-               "Code: " << errorCodeToString(std::get<0>(error)) << std::endl <<
-               "Info: " << std::get<1>(error) << std::endl);
-    } catch (const std::exception& e) {
-      _LOG_ERROR("Cannot parse error data content for " << data.getName());
-      return;
-    }
-  }
-}
-
-}  // namespace ndncert
-}  // namespace ndn
diff --git a/src/client-module.hpp b/src/client-module.hpp
deleted file mode 100644
index b9c0f35..0000000
--- a/src/client-module.hpp
+++ /dev/null
@@ -1,151 +0,0 @@
-/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * Copyright (c) 2017-2020, Regents of the University of California.
- *
- * This file is part of ndncert, a certificate management system based on NDN.
- *
- * ndncert is free software: you can redistribute it and/or modify it under the terms
- * of the GNU General Public License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * ndncert is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
- * PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received copies of the GNU General Public License along with
- * ndncert, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
- *
- * See AUTHORS.md for complete list of ndncert authors and contributors.
- */
-
-#ifndef NDNCERT_CLIENT_MODULE_HPP
-#define NDNCERT_CLIENT_MODULE_HPP
-
-#include "configuration.hpp"
-#include "request-state.hpp"
-#include "crypto-support/crypto-helper.hpp"
-
-namespace ndn {
-namespace ndncert {
-
-// 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 CaConfigItem
-
-class ClientModule : noncopyable
-{
-public:
-  /**
-   * @brief Error that can be thrown from ClientModule
-   */
-  class Error : public std::runtime_error
-  {
-  public:
-    using std::runtime_error::runtime_error;
-  };
-
-public:
-  ClientModule(security::v2::KeyChain& keyChain);
-
-  ~ClientModule();
-
-  ClientConfig&
-  getClientConf()
-  {
-    return m_config;
-  }
-
-  Status
-  getApplicationStatus() const
-  {
-    return m_status;
-  }
-
-  std::string
-  getChallengeStatus() const
-  {
-    return m_challengeStatus;
-  }
-
-  shared_ptr<Interest>
-  generateInfoInterest(const Name& caName);
-
-  bool
-  verifyInfoResponse(const Data& reply);
-
-  /**
-   * @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
-  addCaFromInfoResponse(const Data& reply);
-
-  shared_ptr<Interest>
-  generateProbeInterest(const CaConfigItem& ca, std::vector<std::tuple<std::string, std::string>>&& probeInfo);
-
-  void
-  onProbeResponse(const Data& reply);
-
-  shared_ptr<Interest>
-  generateNewInterest(const time::system_clock::TimePoint& notBefore,
-                      const time::system_clock::TimePoint& notAfter,
-                      const Name& identityName = Name());
-
-  shared_ptr<Interest>
-  generateRevokeInterest(const security::v2::Certificate& certificate);
-
-  std::list<std::string>
-  onNewRenewRevokeResponse(const Data& reply);
-
-  shared_ptr<Interest>
-  generateChallengeInterest(const Block& paramTLV);
-
-  void
-  onChallengeResponse(const Data& reply);
-
-  shared_ptr<Interest>
-  generateDownloadInterest();
-
-  shared_ptr<Interest>
-  generateCertFetchInterest();
-
-  void
-  onCertFetchResponse(const Data& reply);
-
-  void
-  endSession();
-
-  static void
-  processIfError(const Data& data);
-
-PUBLIC_WITH_TESTS_ELSE_PRIVATE:
-  ClientConfig m_config;
-  security::v2::KeyChain& m_keyChain;
-
-  CaConfigItem m_ca;
-  security::Key m_key;
-  Name m_identityName;
-
-  std::string m_requestId = "";
-  Status m_status = Status::NOT_STARTED;
-  std::string m_challengeStatus = "";
-  std::string m_challengeType = "";
-  Name m_issuedCertName;
-  std::list<std::string> m_challengeList;
-  bool m_isCertInstalled = false;
-  bool m_isNewlyCreatedIdentity = false;
-  bool m_isNewlyCreatedKey = false;
-
-  int m_remainingTries = 0;
-  time::system_clock::TimePoint m_freshBefore;
-
-  ECDHState m_ecdh;
-  uint8_t m_aesKey[16] = {0};
-};
-
-} // namespace ndncert
-} // namespace ndn
-
-#endif // NDNCERT_CLIENT_MODULE_HPP
diff --git a/src/configuration.cpp b/src/configuration.cpp
index 8d1d397..10fe352 100644
--- a/src/configuration.cpp
+++ b/src/configuration.cpp
@@ -27,7 +27,7 @@
 namespace ndncert {
 
 void
-CaConfigItem::parse(const JsonSection& configJson)
+CaProfile::parse(const JsonSection& configJson)
 {
   // CA prefix
   m_caPrefix = Name(configJson.get(CONFIG_CA_PREFIX, ""));
@@ -79,7 +79,7 @@
 }
 
 JsonSection
-CaConfigItem::toJson() const
+CaProfile::toJson() const
 {
   JsonSection caItem;
   caItem.put(CONFIG_CA_PREFIX, m_caPrefix.toUri());
@@ -150,7 +150,7 @@
 }
 
 void
-ClientConfig::load(const std::string& fileName)
+RequesterCaCache::load(const std::string& fileName)
 {
   JsonSection configJson;
   try {
@@ -166,12 +166,12 @@
 }
 
 void
-ClientConfig::load(const JsonSection& configSection)
+RequesterCaCache::load(const JsonSection& configSection)
 {
   m_caItems.clear();
   auto caList = configSection.get_child("ca-list");
   for (auto item : caList) {
-    CaConfigItem caItem;
+    CaProfile caItem;
     caItem.parse(item.second);
     if (caItem.m_cert == nullptr) {
       BOOST_THROW_EXCEPTION(std::runtime_error("No CA certificate is loaded from JSON configuration."));
@@ -181,7 +181,7 @@
 }
 
 void
-ClientConfig::save(const std::string& fileName) const
+RequesterCaCache::save(const std::string& fileName) const
 {
   JsonSection configJson;
   for (const auto& caItem : m_caItems) {
@@ -196,9 +196,21 @@
 }
 
 void
-ClientConfig::removeCaItem(const Name& caName)
+RequesterCaCache::removeCaProfile(const Name& caName)
 {
-  m_caItems.remove_if([&](const CaConfigItem& item) { return item.m_caPrefix == caName; });
+  m_caItems.remove_if([&](const CaProfile& item) { return item.m_caPrefix == caName; });
+}
+
+void
+RequesterCaCache::addCaProfile(const CaProfile& profile)
+{
+  for (auto& item : m_caItems) {
+    if (item.m_caPrefix == profile.m_caPrefix) {
+      item = profile;
+      return;
+    }
+  }
+  m_caItems.push_back(profile);
 }
 
 }  // namespace ndncert
diff --git a/src/configuration.hpp b/src/configuration.hpp
index 892c056..6b9edeb 100644
--- a/src/configuration.hpp
+++ b/src/configuration.hpp
@@ -26,7 +26,7 @@
 namespace ndn {
 namespace ndncert {
 
-struct CaConfigItem {
+struct CaProfile {
   /**
    * CA Name prefix (without /CA suffix).
    */
@@ -131,7 +131,7 @@
   void
   load(const std::string& fileName);
 
-  CaConfigItem m_caItem;
+  CaProfile m_caItem;
   /**
    * Used for CA redirection as specified in
    * https://github.com/named-data/ndncert/wiki/NDNCERT-Protocol-0.3-PROBE-Extensions#probe-extension-for-redirection
@@ -153,7 +153,7 @@
  * For Client configuration format, please refer to:
  *   https://github.com/named-data/ndncert/wiki/Client-Configuration-Sample
  */
-class ClientConfig {
+class RequesterCaCache {
 public:
   /**
    * @throw std::runtime_error when config file cannot be correctly parsed.
@@ -171,9 +171,15 @@
   save(const std::string& fileName) const;
 
   void
-  removeCaItem(const Name& caName);
+  removeCaProfile(const Name& caName);
 
-  std::list<CaConfigItem> m_caItems;
+  /**
+   * Be cautious. This will add a new trust anchor for requesters.
+   */
+  void
+  addCaProfile(const CaProfile& profile);
+
+  std::list<CaProfile> m_caItems;
 };
 
 }  // namespace ndncert
diff --git a/src/protocol-detail/challenge.cpp b/src/protocol-detail/challenge.cpp
index 61a3519..85875fd 100644
--- a/src/protocol-detail/challenge.cpp
+++ b/src/protocol-detail/challenge.cpp
@@ -19,8 +19,6 @@
  */
 
 #include "challenge.hpp"
-#include "../ndncert-common.hpp"
-#include "../request-state.hpp"
 
 namespace ndn {
 namespace ndncert {
diff --git a/src/protocol-detail/error.cpp b/src/protocol-detail/error.cpp
index b74180b..dabfa6a 100644
--- a/src/protocol-detail/error.cpp
+++ b/src/protocol-detail/error.cpp
@@ -37,16 +37,11 @@
 ErrorTLV::decodefromDataContent(const Block& block)
 {
   block.parse();
+  if (block.find(tlv_error_code) == block.elements_end()) {
+    return std::make_tuple(ErrorCode::NO_ERROR, "");
+  }
   ErrorCode error = static_cast<ErrorCode>(readNonNegativeInteger(block.get(tlv_error_code)));
-  auto description = readString(block.get(tlv_error_info));
-  return std::make_tuple(error, description);
-}
-
-bool
-ErrorTLV::isErrorContent(const Block& block)
-{
-  block.parse();
-  return block.find(tlv_error_code) != block.elements_end();
+  return std::make_tuple(error, readString(block.get(tlv_error_info)));
 }
 
 }  // namespace ndncert
diff --git a/src/protocol-detail/error.hpp b/src/protocol-detail/error.hpp
index d39fe1a..8a2cda0 100644
--- a/src/protocol-detail/error.hpp
+++ b/src/protocol-detail/error.hpp
@@ -39,9 +39,6 @@
    */
   static std::tuple<ErrorCode, std::string>
   decodefromDataContent(const Block& block);
-
-  static bool
-  isErrorContent(const Block& block);
 };
 
 }  // namespace ndncert
diff --git a/src/protocol-detail/info.cpp b/src/protocol-detail/info.cpp
index 06eba0e..7c0e449 100644
--- a/src/protocol-detail/info.cpp
+++ b/src/protocol-detail/info.cpp
@@ -24,7 +24,7 @@
 namespace ndncert {
 
 Block
-INFO::encodeDataContent(const CaConfigItem& caConfig, const security::v2::Certificate& certificate)
+INFO::encodeDataContent(const CaProfile& caConfig, const security::v2::Certificate& certificate)
 {
   auto content = makeEmptyBlock(tlv::Content);
   content.push_back(makeNestedBlock(tlv_ca_prefix, caConfig.m_caPrefix));
@@ -48,10 +48,10 @@
   return content;
 }
 
-CaConfigItem
+CaProfile
 INFO::decodeDataContent(const Block& block)
 {
-  CaConfigItem result;
+  CaProfile result;
   block.parse();
   for (auto const& item : block.elements()) {
     switch (item.type()) {
diff --git a/src/protocol-detail/info.hpp b/src/protocol-detail/info.hpp
index 03f0d61..571d120 100644
--- a/src/protocol-detail/info.hpp
+++ b/src/protocol-detail/info.hpp
@@ -32,12 +32,12 @@
    * Encode CA configuration and its certificate into a TLV block as INFO Data packet content.
    */
   static Block
-  encodeDataContent(const CaConfigItem& caConfig, const security::v2::Certificate& certificate);
+  encodeDataContent(const CaProfile& caConfig, const security::v2::Certificate& certificate);
 
   /**
    * Decode CA configuration from the TLV block of INFO Data packet content.
    */
-  static CaConfigItem
+  static CaProfile
   decodeDataContent(const Block& block);
 };
 
diff --git a/src/requester.cpp b/src/requester.cpp
new file mode 100644
index 0000000..7bf9594
--- /dev/null
+++ b/src/requester.cpp
@@ -0,0 +1,327 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2017-2020, Regents of the University of California.
+ *
+ * This file is part of ndncert, a certificate management system based on NDN.
+ *
+ * ndncert is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * ndncert is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License along with
+ * ndncert, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndncert authors and contributors.
+ */
+
+#include "requester.hpp"
+#include "challenge-module.hpp"
+#include "crypto-support/enc-tlv.hpp"
+#include "protocol-detail/challenge.hpp"
+#include "protocol-detail/error.hpp"
+#include "protocol-detail/info.hpp"
+#include "protocol-detail/new-renew-revoke.hpp"
+#include "protocol-detail/probe.hpp"
+#include <ndn-cxx/security/signing-helpers.hpp>
+#include <ndn-cxx/security/transform/base64-encode.hpp>
+#include <ndn-cxx/security/transform/buffer-source.hpp>
+#include <ndn-cxx/security/transform/stream-sink.hpp>
+#include <ndn-cxx/security/verification-helpers.hpp>
+#include <ndn-cxx/util/io.hpp>
+#include <ndn-cxx/util/random.hpp>
+
+namespace ndn {
+namespace ndncert {
+
+_LOG_INIT(ndncert.client);
+
+RequesterState::RequesterState(security::v2::KeyChain& keyChain, const CaProfile& caItem, RequestType requestType)
+  : m_caItem(caItem)
+  , m_keyChain(keyChain)
+  , m_type(requestType)
+{
+}
+
+shared_ptr<Interest>
+Requester::genCaProfileInterest(const Name& caName)
+{
+  Name interestName = caName;
+  if (readString(caName.at(-1)) != "CA")
+    interestName.append("CA");
+  interestName.append("INFO");
+  auto interest = make_shared<Interest>(interestName);
+  interest->setMustBeFresh(true);
+  interest->setCanBePrefix(false);
+  return interest;
+}
+
+boost::optional<CaProfile>
+Requester::onCaProfileResponse(const Data& reply)
+{
+  auto caItem = INFO::decodeDataContent(reply.getContent());
+  if (!security::verifySignature(reply, *caItem.m_cert)) {
+    _LOG_ERROR("Cannot verify replied Data packet signature.");
+    return boost::none;
+  }
+  return caItem;
+}
+
+shared_ptr<Interest>
+Requester::genProbeInterest(const CaProfile& ca, std::vector<std::tuple<std::string, std::string>>&& probeInfo)
+{
+  Name interestName = ca.m_caPrefix;
+  interestName.append("CA").append("PROBE");
+  auto interest = make_shared<Interest>(interestName);
+  interest->setMustBeFresh(true);
+  interest->setCanBePrefix(false);
+  interest->setApplicationParameters(PROBE::encodeApplicationParameters(std::move(probeInfo)));
+  return interest;
+}
+
+void
+Requester::onProbeResponse(const Data& reply, const CaProfile& ca,
+                           std::vector<Name>& identityNames, std::vector<Name>& otherCas)
+{
+  if (!security::verifySignature(reply, *ca.m_cert)) {
+    _LOG_ERROR("Cannot verify replied Data packet signature.");
+    return;
+  }
+  processIfError(reply);
+  PROBE::decodeDataContent(reply.getContent(), identityNames, otherCas);
+}
+
+shared_ptr<Interest>
+Requester::genNewInterest(RequesterState& state, const Name& identityName,
+                          const time::system_clock::TimePoint& notBefore,
+                          const time::system_clock::TimePoint& notAfter)
+{
+  if (!state.m_caItem.m_caPrefix.isPrefixOf(identityName)) {
+    return nullptr;
+  }
+  if (identityName.empty()) {
+    NDN_LOG_TRACE("Randomly create a new name because identityName is empty and the param is empty.");
+    state.m_identityName = state.m_caItem.m_caPrefix;
+    state.m_identityName.append(std::to_string(random::generateSecureWord64()));
+  }
+  else {
+    state.m_identityName = identityName;
+  }
+
+  // generate a newly key pair or use an existing key
+  const auto& pib = state.m_keyChain.getPib();
+  security::pib::Identity identity;
+  try {
+    identity = pib.getIdentity(state.m_identityName);
+  }
+  catch (const security::Pib::Error& e) {
+    identity = state.m_keyChain.createIdentity(state.m_identityName);
+    state.m_isNewlyCreatedIdentity = true;
+    state.m_isNewlyCreatedKey = true;
+  }
+  try {
+    state.m_keyPair = identity.getDefaultKey();
+  }
+  catch (const security::Pib::Error& e) {
+    state.m_keyPair = state.m_keyChain.createKey(identity);
+    state.m_isNewlyCreatedKey = true;
+  }
+  auto& keyName = state.m_keyPair.getName();
+
+  // generate certificate request
+  security::v2::Certificate certRequest;
+  certRequest.setName(Name(keyName).append("cert-request").appendVersion());
+  certRequest.setContentType(tlv::ContentType_Key);
+  certRequest.setContent(state.m_keyPair.getPublicKey().data(), state.m_keyPair.getPublicKey().size());
+  SignatureInfo signatureInfo;
+  signatureInfo.setValidityPeriod(security::ValidityPeriod(notBefore, notAfter));
+  state.m_keyChain.sign(certRequest, signingByKey(keyName).setSignatureInfo(signatureInfo));
+
+  // generate Interest packet
+  Name interestName = state.m_caItem.m_caPrefix;
+  interestName.append("CA").append("NEW");
+  auto interest = make_shared<Interest>(interestName);
+  interest->setMustBeFresh(true);
+  interest->setCanBePrefix(false);
+  interest->setApplicationParameters(
+      NEW_RENEW_REVOKE::encodeApplicationParameters(RequestType::NEW, state.m_ecdh.getBase64PubKey(), certRequest));
+
+  // sign the Interest packet
+  state.m_keyChain.sign(*interest, signingByKey(keyName));
+  return interest;
+}
+
+shared_ptr<Interest>
+Requester::genRevokeInterest(RequesterState& state, const security::v2::Certificate& certificate)
+{
+  if (!state.m_caItem.m_caPrefix.isPrefixOf(certificate.getName())) {
+    return nullptr;
+  }
+  // generate Interest packet
+  Name interestName = state.m_caItem.m_caPrefix;
+  interestName.append("CA").append("REVOKE");
+  auto interest = make_shared<Interest>(interestName);
+  interest->setMustBeFresh(true);
+  interest->setCanBePrefix(false);
+  interest->setApplicationParameters(
+      NEW_RENEW_REVOKE::encodeApplicationParameters(RequestType::REVOKE, state.m_ecdh.getBase64PubKey(), certificate));
+  return interest;
+}
+
+std::list<std::string>
+Requester::onNewRenewRevokeResponse(RequesterState& state, const Data& reply)
+{
+  if (!security::verifySignature(reply, *state.m_caItem.m_cert)) {
+    _LOG_ERROR("Cannot verify replied Data packet signature.");
+    return std::list<std::string>();
+  }
+  processIfError(reply);
+
+  auto contentTLV = reply.getContent();
+  contentTLV.parse();
+
+  // ECDH
+  const auto& peerKeyBase64Str = readString(contentTLV.get(tlv_ecdh_pub));
+  const auto& saltStr = readString(contentTLV.get(tlv_salt));
+  uint64_t saltInt = std::stoull(saltStr);
+  state.m_ecdh.deriveSecret(peerKeyBase64Str);
+
+  // HKDF
+  hkdf(state.m_ecdh.context->sharedSecret, state.m_ecdh.context->sharedSecretLen,
+       (uint8_t*)&saltInt, sizeof(saltInt), state.m_aesKey, sizeof(state.m_aesKey));
+
+  // update state
+  state.m_status = static_cast<Status>(readNonNegativeInteger(contentTLV.get(tlv_status)));
+  state.m_requestId = readString(contentTLV.get(tlv_request_id));
+  std::list<std::string> challengeList;
+  for (auto const& element : contentTLV.elements()) {
+    if (element.type() == tlv_challenge) {
+      challengeList.push_back(readString(element));
+    }
+  }
+  return challengeList;
+}
+
+std::vector<std::tuple<std::string, std::string>>
+Requester::selectOrContinueChallenge(RequesterState& state, const std::string& challengeSelected)
+{
+  auto challenge = ChallengeModule::createChallengeModule(challengeSelected);
+  if (challenge == nullptr) {
+    BOOST_THROW_EXCEPTION(std::runtime_error("The challenge selected is not supported by your current version of NDNCERT."));
+  }
+  state.m_challengeType = challengeSelected;
+  return challenge->getRequestedParameterList(state.m_status, state.m_challengeStatus);
+}
+
+shared_ptr<Interest>
+Requester::genChallengeInterest(const RequesterState& state,
+                                std::vector<std::tuple<std::string, std::string>>&& parameters)
+{
+  if (state.m_challengeType == "") {
+    BOOST_THROW_EXCEPTION(std::runtime_error("The challenge has not been selected."));
+  }
+  auto challenge = ChallengeModule::createChallengeModule(state.m_challengeType);
+  if (challenge == nullptr) {
+    BOOST_THROW_EXCEPTION(std::runtime_error("The challenge selected is not supported by your current version of NDNCERT."));
+  }
+  auto challengeParams = challenge->genChallengeRequestTLV(state.m_status, state.m_challengeStatus, std::move(parameters));
+
+  Name interestName = state.m_caItem.m_caPrefix;
+  interestName.append("CA").append("CHALLENGE").append(state.m_requestId);
+  auto interest = make_shared<Interest>(interestName);
+  interest->setMustBeFresh(true);
+  interest->setCanBePrefix(false);
+
+  // encrypt the Interest parameters
+  auto paramBlock = encodeBlockWithAesGcm128(tlv::ApplicationParameters, state.m_aesKey,
+                                             challengeParams.value(), challengeParams.value_size(),
+                                             (const uint8_t*)"test", strlen("test"));
+  interest->setApplicationParameters(paramBlock);
+  state.m_keyChain.sign(*interest, signingByKey(state.m_keyPair.getName()));
+  return interest;
+}
+
+void
+Requester::onChallengeResponse(RequesterState& state, const Data& reply)
+{
+  if (!security::verifySignature(reply, *state.m_caItem.m_cert)) {
+    _LOG_ERROR("Cannot verify replied Data packet signature.");
+    return;
+  }
+  processIfError(reply);
+  auto result = decodeBlockWithAesGcm128(reply.getContent(), state.m_aesKey, (const uint8_t*)"test", strlen("test"));
+  Block contentTLV = makeBinaryBlock(tlv_encrypted_payload, result.data(), result.size());
+  contentTLV.parse();
+
+  // update state
+  state.m_status = static_cast<Status>(readNonNegativeInteger(contentTLV.get(tlv_status)));
+  state.m_challengeStatus = readString(contentTLV.get(tlv_challenge_status));
+  state.m_remainingTries = readNonNegativeInteger(contentTLV.get(tlv_remaining_tries));
+  state.m_freshBefore = time::system_clock::now() +
+                  time::seconds(readNonNegativeInteger(contentTLV.get(tlv_remaining_time)));
+
+  if (contentTLV.find(tlv_issued_cert_name) != contentTLV.elements_end()) {
+    Block issuedCertNameBlock = contentTLV.get(tlv_issued_cert_name);
+    issuedCertNameBlock.parse();
+    state.m_issuedCertName.wireDecode(issuedCertNameBlock.get(tlv::Name));
+  }
+}
+
+shared_ptr<Interest>
+Requester::genCertFetchInterest(const RequesterState& state)
+{
+  Name interestName = state.m_issuedCertName;
+  auto interest = make_shared<Interest>(interestName);
+  interest->setMustBeFresh(false);
+  interest->setCanBePrefix(false);
+  return interest;
+}
+
+shared_ptr<security::v2::Certificate>
+Requester::onCertFetchResponse(const Data& reply)
+{
+  try {
+    return std::make_shared<security::v2::Certificate>(reply.getContent().blockFromValue());
+  }
+  catch (const std::exception& e) {
+    _LOG_ERROR("Cannot parse replied certificate ");
+    return nullptr;
+  }
+}
+
+void
+Requester::endSession(RequesterState& state)
+{
+  if (state.m_status == Status::SUCCESS || state.m_status == Status::ENDED) {
+    return;
+  }
+  if (state.m_isNewlyCreatedIdentity) {
+    // put the identity into the if scope is because it may cause an error
+    // outside since when endSession is called, identity may not have been created yet.
+    auto identity = state.m_keyChain.getPib().getIdentity(state.m_identityName);
+    state.m_keyChain.deleteIdentity(identity);
+  }
+  else if (state.m_isNewlyCreatedKey) {
+    auto identity = state.m_keyChain.getPib().getIdentity(state.m_identityName);
+    state.m_keyChain.deleteKey(identity, state.m_keyPair);
+  }
+  state.m_status = Status::ENDED;
+}
+
+void
+Requester::processIfError(const Data& data)
+{
+  auto errorInfo = ErrorTLV::decodefromDataContent(data.getContent());
+  if (std::get<0>(errorInfo) == ErrorCode::NO_ERROR) {
+    return;
+  }
+  BOOST_THROW_EXCEPTION(std::runtime_error("Error info replied from the CA with Error code: " +
+                                           errorCodeToString(std::get<0>(errorInfo)) +
+                                           " and Error Info: " + std::get<1>(errorInfo)));
+}
+
+}  // namespace ndncert
+}  // namespace ndn
diff --git a/src/requester.hpp b/src/requester.hpp
new file mode 100644
index 0000000..44dd7ee
--- /dev/null
+++ b/src/requester.hpp
@@ -0,0 +1,123 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2017-2020, Regents of the University of California.
+ *
+ * This file is part of ndncert, a certificate management system based on NDN.
+ *
+ * ndncert is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * ndncert is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License along with
+ * ndncert, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndncert authors and contributors.
+ */
+
+#ifndef NDNCERT_CLIENT_MODULE_HPP
+#define NDNCERT_CLIENT_MODULE_HPP
+
+#include "configuration.hpp"
+#include "request-state.hpp"
+#include "crypto-support/crypto-helper.hpp"
+
+namespace ndn {
+namespace ndncert {
+
+// TODO
+// For each RequesterState, create a validator instance and initialize it with CA's cert
+// The validator instance should be in CaProfile
+
+struct RequesterState {
+  explicit
+  RequesterState(security::v2::KeyChain& keyChain, const CaProfile& caItem, RequestType requestType);
+
+  CaProfile m_caItem;
+  security::v2::KeyChain& m_keyChain;
+  RequestType m_type;
+
+  Name m_identityName;
+  security::Key m_keyPair;
+  std::string m_requestId;
+  Status m_status = Status::NOT_STARTED;
+  std::string m_challengeType;
+  std::string m_challengeStatus;
+  int m_remainingTries = 0;
+  time::system_clock::TimePoint m_freshBefore;
+  Name m_issuedCertName;
+
+  ECDHState m_ecdh;
+  uint8_t m_aesKey[16] = {0};
+
+  bool m_isCertInstalled = false;
+  bool m_isNewlyCreatedIdentity = false;
+  bool m_isNewlyCreatedKey = false;
+};
+
+class Requester : noncopyable
+{
+public:
+  // INFO related helpers
+  static shared_ptr<Interest>
+  genCaProfileInterest(const Name& caName);
+
+  /**
+   * Will first verify the signature of the packet using the key provided inside the profile.
+   * The application should be cautious whether to add CaProfile into the RequesterCaCache.
+   */
+  static boost::optional<CaProfile>
+  onCaProfileResponse(const Data& reply);
+
+  // PROBE related helpers
+  static shared_ptr<Interest>
+  genProbeInterest(const CaProfile& ca, std::vector<std::tuple<std::string, std::string>>&& probeInfo);
+
+  static void
+  onProbeResponse(const Data& reply, const CaProfile& ca,
+                  std::vector<Name>& identityNames, std::vector<Name>& otherCas);
+
+  // NEW/REVOKE/RENEW related helpers
+  static shared_ptr<Interest>
+  genNewInterest(RequesterState& state, const Name& identityName,
+                      const time::system_clock::TimePoint& notBefore,
+                      const time::system_clock::TimePoint& notAfter);
+
+  static shared_ptr<Interest>
+  genRevokeInterest(RequesterState& state, const security::v2::Certificate& certificate);
+
+  static std::list<std::string>
+  onNewRenewRevokeResponse(RequesterState& state, const Data& reply);
+
+  // CHALLENGE helpers
+  static std::vector<std::tuple<std::string, std::string>>
+  selectOrContinueChallenge(RequesterState& state, const std::string& challengeSelected);
+
+  static shared_ptr<Interest>
+  genChallengeInterest(const RequesterState& state,
+                       std::vector<std::tuple<std::string, std::string>>&& parameters);
+
+  static void
+  onChallengeResponse(RequesterState& state, const Data& reply);
+
+  static shared_ptr<Interest>
+  genCertFetchInterest(const RequesterState& state);
+
+  shared_ptr<security::v2::Certificate>
+  onCertFetchResponse(const Data& reply);
+
+  void
+  endSession(RequesterState& state);
+
+private:
+  static void
+  processIfError(const Data& data);
+};
+
+} // namespace ndncert
+} // namespace ndn
+
+#endif // NDNCERT_CLIENT_MODULE_HPP