merge ca config and client config, remove old format of probe

Change-Id: I73500f532f166851d82c1bf1cc008c7ffc241ef3
diff --git a/ca.conf.sample b/ca.conf.sample
index 977005a..cdc2aab 100644
--- a/ca.conf.sample
+++ b/ca.conf.sample
@@ -1,8 +1,8 @@
 {
   "ca-prefix": "/example",
-  "issuing-freshness": "720",
-  "validity-period": "360",
   "ca-info": "An example NDNCERT CA",
+  "max-validity-period": "1296000",
+  "max-suffix-length": "2",
   "probe-parameters":
   [
     {"probe-parameter-key": "email"},
diff --git a/client.conf.sample b/client.conf.sample
index e9fe68d..4753504 100644
--- a/client.conf.sample
+++ b/client.conf.sample
@@ -4,9 +4,25 @@
     {
       "ca-prefix": "/example",
       "ca-info": "An example NDNCERT CA",
-      "probe": "email",
+      "max-validity-period": "1296000",
+      "max-suffix-length": "2",
+      "probe-parameters":
+      [
+        {"probe-parameter-key": "email"},
+        {"probe-parameter-key": "uid"}
+      ],
       "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"
+    },
+    {
+      "ca-prefix": "/example2",
+      "ca-info": "An example NDNCERT CA 2",
+      "max-validity-period": "1296000",
+      "max-suffix-length": "1",
+      "probe-parameters":
+      [
+        {"probe-parameter-key": "email"},
+      ],
+      "certificate": "Bv0CJAcsCANuZG4IBXNpdGUxCANLRVkICBG8IvRjFf8XCARzZWxmCAn9AAABWcgU2aUUCRgBAhkEADbugBX9AU8wggFLMIIBAwYHKoZIzj0CATCB9wIBATAsBgcqhkjOPQEBAiEA/////wAAAAEAAAAAAAAAAAAAAAD///////////////8wWwQg/////wAAAAEAAAAAAAAAAAAAAAD///////////////wEIFrGNdiqOpPns+u9VXaYhrxlHQawzFOw9jvOPD4n0mBLAxUAxJ02CIbnBJNqZnjhE50mt4GffpAEQQRrF9Hy4SxCR/i85uVjpEDydwN9gS3rM6D0oTlF2JjClk/jQuL+Gn+bjufrSnwPnhYrzjNXazFezsu2QGg3v1H1AiEA/////wAAAAD//////////7zm+q2nF56E87nKwvxjJVECAQEDQgAES9Cb9iANUNYmwt5bjwNW1mZgjzIkDJb6FTCdiYWnkMMIVxh2YDllphoWDEAPS6kqJczzCuhnGYpZCp9tTaYKGxZMGwEDHB0HGwgDbmRuCAVzaXRlMQgDS0VZCAgRvCL0YxX/F/0A/Sb9AP4PMTk3MDAxMDFUMDAwMDAw/QD/DzIwMzcwMTE3VDIxMjg0NhdIMEYCIQDXkR1hF3GiP7yLXq+0JBJfi9QC+hhAu/1Bykx+MWz6RAIhANwelBTxxZr2C5bD15mjfhWudK4I1tOb4b/9xWCHyM7F"
+    },
+  ]
 }
diff --git a/src/ca-config.cpp b/src/ca-config.cpp
deleted file mode 100644
index a7b3f9a..0000000
--- a/src/ca-config.cpp
+++ /dev/null
@@ -1,112 +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 "ca-config.hpp"
-#include "challenge-module.hpp"
-#include <ndn-cxx/util/io.hpp>
-#include <boost/filesystem.hpp>
-#include <boost/property_tree/json_parser.hpp>
-#include <boost/property_tree/ptree.hpp>
-
-namespace ndn {
-namespace ndncert {
-
-void
-CaConfig::load(const std::string& fileName)
-{
-  JsonSection configJson;
-  try {
-    boost::property_tree::read_json(fileName, configJson);
-  }
-  catch (const std::exception& error) {
-    BOOST_THROW_EXCEPTION(std::runtime_error("Failed to parse configuration file " + fileName + ", " + error.what()));
-  }
-  if (configJson.begin() == configJson.end()) {
-    BOOST_THROW_EXCEPTION(std::runtime_error("std::runtime_error processing configuration file: " + fileName + " no data"));
-  }
-  parse(configJson);
-}
-
-void
-CaConfig::parse(const JsonSection& configJson)
-{
-  // CA prefix
-  m_caPrefix = Name(configJson.get(CONFIG_CA_PREFIX, ""));
-  if (m_caPrefix.empty()) {
-    BOOST_THROW_EXCEPTION(std::runtime_error("Cannot parse ca-prefix from the config file"));
-  }
-  // CA info
-  m_caInfo = configJson.get(CONFIG_CA_INFO, "");
-  // CA max validity period
-  m_maxValidityPeriod = time::seconds(configJson.get(CONFIG_MAX_VALIDITY_PERIOD, 3600));
-  // CA max suffix length
-  m_maxSuffixLength = configJson.get<size_t>(CONFIG_MAX_SUFFIX_LENGTH, 1);
-  if (m_maxSuffixLength == 0) {
-      m_maxSuffixLength = 1;
-  }
-  // probe
-  m_probeParameterKeys.clear();
-  auto probeParameters = configJson.get_child_optional(CONFIG_PROBE_PARAMETERS);
-  if (probeParameters) {
-    parseProbeParameters(*probeParameters);
-  }
-  // optional supported challenges
-  m_supportedChallenges.clear();
-  auto challengeList = configJson.get_child_optional(CONFIG_SUPPORTED_CHALLENGES);
-  if (!challengeList) {
-    BOOST_THROW_EXCEPTION(std::runtime_error("Cannot parse challenge list"));
-  }
-  parseChallengeList(*challengeList);
-}
-
-void
-CaConfig::parseProbeParameters(const JsonSection& section)
-{
-  for (const auto item : section) {
-    auto probeParameter = item.second.get(CONFIG_PROBE_PARAMETER, "");
-    probeParameter = boost::algorithm::to_lower_copy(probeParameter);
-    if (probeParameter == "") {
-      BOOST_THROW_EXCEPTION(std::runtime_error("Cannot read probe-parameter-key in probe-parameters from the config file"));
-    }
-    m_probeParameterKeys.push_back(probeParameter);
-  }
-}
-
-void
-CaConfig::parseChallengeList(const JsonSection& section)
-{
-  for (const auto item : section) {
-    auto challengeType = item.second.get(CONFIG_CHALLENGE, "");
-    challengeType = boost::algorithm::to_lower_copy(challengeType);
-    if (challengeType == "") {
-      BOOST_THROW_EXCEPTION(std::runtime_error("Cannot read type in supported-challenges from the config file"));
-    }
-    if (!ChallengeModule::isChallengeSupported(challengeType)) {
-      BOOST_THROW_EXCEPTION(std::runtime_error("Does not support challenge read from the config file"));
-    }
-    m_supportedChallenges.push_back(challengeType);
-  }
-  if (m_supportedChallenges.size() == 0) {
-    BOOST_THROW_EXCEPTION(std::runtime_error("At least one challenge should be identified under supported-challenges"));
-  }
-}
-
-}  // namespace ndncert
-}  // namespace ndn
diff --git a/src/ca-module.cpp b/src/ca-module.cpp
index 00d2208..d829f63 100644
--- a/src/ca-module.cpp
+++ b/src/ca-module.cpp
@@ -75,7 +75,7 @@
   _LOG_TRACE("Prefix " << localhopInfoPrefix << " got registered");
 
   // register prefixes
-  Name prefix = m_config.m_caPrefix;
+  Name prefix = m_config.m_caItem.m_caPrefix;
   prefix.append("CA");
 
   prefixId = m_face.registerPrefix(
@@ -137,7 +137,7 @@
   Name discoveryInterestName(infoPacket->getName().getPrefix(-2));
   name::Component metadataComponent(32, reinterpret_cast<const uint8_t*>("metadata"), std::strlen("metadata"));
   discoveryInterestName.append(metadataComponent);
-  auto metadataData = metadata.makeData(discoveryInterestName, m_keyChain, signingByIdentity(m_config.m_caPrefix));
+  auto metadataData = metadata.makeData(discoveryInterestName, m_keyChain, signingByIdentity(m_config.m_caItem.m_caPrefix));
   return make_shared<Data>(metadataData);
 }
 
@@ -150,16 +150,16 @@
   // otherwise, directly reply m_infoData
 
   const auto& pib = m_keyChain.getPib();
-  const auto& identity = pib.getIdentity(m_config.m_caPrefix);
+  const auto& identity = pib.getIdentity(m_config.m_caItem.m_caPrefix);
   const auto& cert = identity.getDefaultKey().getDefaultCertificate();
-  Block contentTLV = INFO::encodeDataContent(m_config, cert);
+  Block contentTLV = INFO::encodeDataContent(m_config.m_caItem, cert);
 
-  Name infoPacketName(m_config.m_caPrefix);
+  Name infoPacketName(m_config.m_caItem.m_caPrefix);
   infoPacketName.append("CA").append("INFO").appendVersion().appendSegment(0);
   Data infoData(infoPacketName);
   infoData.setContent(contentTLV);
   infoData.setFreshnessPeriod(DEFAULT_DATA_FRESHNESS_PERIOD);
-  m_keyChain.sign(infoData, signingByIdentity(m_config.m_caPrefix));
+  m_keyChain.sign(infoData, signingByIdentity(m_config.m_caItem.m_caPrefix));
   return make_shared<Data>(infoData);
 }
 
@@ -193,9 +193,9 @@
     return;
   }
 
-  // if (m_config.m_nameAssignmentFunc) {
+  // if (m_config.m_caItem.m_nameAssignmentFunc) {
   //   try {
-  //     availableId = m_config.m_nameAssignmentFunc(parameterTLV);
+  //     availableId = m_config.m_caItem.m_nameAssignmentFunc(parameterTLV);
   //   }
   //   catch (const std::exception& e) {
   //     _LOG_TRACE("Cannot find PROBE input from PROBE parameters: " << e.what());
@@ -206,17 +206,17 @@
   //   // if there is no app-specified name lookup, use a random name id
   //   availableId = std::to_string(random::generateSecureWord64());
   // }
-  // Name newIdentityName = m_config.m_caPrefix;
+  // Name newIdentityName = m_config.m_caItem.m_caPrefix;
   // newIdentityName.append(availableId);
   // _LOG_TRACE("Handle PROBE: generate an identity " << newIdentityName);
 
-  // Block contentTLV = PROBE::encodeDataContent(newIdentityName.toUri(), m_config.m_probe, parameterTLV);
+  // Block contentTLV = PROBE::encodeDataContent(newIdentityName.toUri(), m_config.m_caItem.m_probe, parameterTLV);
 
   // Data result;
   // result.setName(request.getName());
   // result.setContent(contentTLV);
   // result.setFreshnessPeriod(DEFAULT_DATA_FRESHNESS_PERIOD);
-  // m_keyChain.sign(result, signingByIdentity(m_config.m_caPrefix));
+  // m_keyChain.sign(result, signingByIdentity(m_config.m_caItem.m_caPrefix));
   // m_face.put(result);
   // _LOG_TRACE("Handle PROBE: send out the PROBE response");
 }
@@ -284,23 +284,31 @@
     auto expectedPeriod = clientCert->getValidityPeriod().getPeriod();
     auto currentTime = time::system_clock::now();
     if (expectedPeriod.first < currentTime - REQUEST_VALIDITY_PERIOD_NOT_BEFORE_GRACE_PERIOD ||
-        expectedPeriod.second > currentTime + m_config.m_maxValidityPeriod ||
+        expectedPeriod.second > currentTime + m_config.m_caItem.m_maxValidityPeriod ||
         expectedPeriod.second <= expectedPeriod.first) {
       _LOG_ERROR("An invalid validity period is being requested.");
       m_face.put(generateErrorDataPacket(request.getName(), ErrorCode::BAD_VALIDITY_PERIOD,
                                          "An invalid validity period is being requested."));
       return;
     }
-    // verify the self-signed certificate, the request, and the token
-    if (!m_config.m_caPrefix.isPrefixOf(clientCert->getIdentity())
+    // verify identity name
+    if (!m_config.m_caItem.m_caPrefix.isPrefixOf(clientCert->getIdentity())
         || !security::v2::Certificate::isValidName(clientCert->getName())
-        || clientCert->getIdentity().size() <= m_config.m_caPrefix.size()
-        || clientCert->getIdentity().size() > m_config.m_caPrefix.size() + m_config.m_maxSuffixLength) {
+        || clientCert->getIdentity().size() <= m_config.m_caItem.m_caPrefix.size()) {
       _LOG_ERROR("An invalid certificate name is being requested " << clientCert->getName());
       m_face.put(generateErrorDataPacket(request.getName(), ErrorCode::NAME_NOT_ALLOWED,
                                          "An invalid certificate name is being requested."));
       return;
     }
+    if (m_config.m_caItem.m_maxSuffixLength) {
+      if (clientCert->getIdentity().size() > m_config.m_caItem.m_caPrefix.size() + *m_config.m_caItem.m_maxSuffixLength) {
+        _LOG_ERROR("An invalid certificate name is being requested " << clientCert->getName());
+        m_face.put(generateErrorDataPacket(request.getName(), ErrorCode::NAME_NOT_ALLOWED,
+                                           "An invalid certificate name is being requested."));
+        return;
+      }
+    }
+    // verify signature
     if (!security::verifySignature(*clientCert, *clientCert)) {
       _LOG_ERROR("Invalid signature in the self-signed certificate.");
       m_face.put(generateErrorDataPacket(request.getName(), ErrorCode::BAD_SIGNATURE,
@@ -315,17 +323,24 @@
     }
   }
   else if (requestType == RequestType::REVOKE) {
-    // verify the certificate
-    if (!m_config.m_caPrefix.isPrefixOf(clientCert->getIdentity())
+    // verify identity name
+    if (!m_config.m_caItem.m_caPrefix.isPrefixOf(clientCert->getIdentity())
         || !security::v2::Certificate::isValidName(clientCert->getName())
-        || clientCert->getIdentity().size() <= m_config.m_caPrefix.size()
-        || clientCert->getIdentity().size() > m_config.m_caPrefix.size() + m_config.m_maxSuffixLength) {
+        || clientCert->getIdentity().size() <= m_config.m_caItem.m_caPrefix.size()) {
       _LOG_ERROR("An invalid certificate name is being requested " << clientCert->getName());
       m_face.put(generateErrorDataPacket(request.getName(), ErrorCode::NAME_NOT_ALLOWED,
                                          "An invalid certificate name is being requested."));
       return;
     }
-    const auto& cert = m_keyChain.getPib().getIdentity(m_config.m_caPrefix).getDefaultKey().getDefaultCertificate();
+    if (m_config.m_caItem.m_maxSuffixLength) {
+      if (clientCert->getIdentity().size() > m_config.m_caItem.m_caPrefix.size() + *m_config.m_caItem.m_maxSuffixLength) {
+        _LOG_ERROR("An invalid certificate name is being requested " << clientCert->getName());
+        m_face.put(generateErrorDataPacket(request.getName(), ErrorCode::NAME_NOT_ALLOWED,
+                                           "An invalid certificate name is being requested."));
+        return;
+      }
+    }
+    const auto& cert = m_keyChain.getPib().getIdentity(m_config.m_caItem.m_caPrefix).getDefaultKey().getDefaultCertificate();
     if (!security::verifySignature(*clientCert, cert)) {
       _LOG_ERROR("Invalid signature in the certificate to revoke.");
       m_face.put(generateErrorDataPacket(request.getName(), ErrorCode::BAD_SIGNATURE,
@@ -336,7 +351,7 @@
 
   // create new request instance
   std::string requestId = std::to_string(random::generateWord64());
-  RequestState requestState(m_config.m_caPrefix, requestId, requestType, Status::BEFORE_CHALLENGE, *clientCert,
+  RequestState requestState(m_config.m_caItem.m_caPrefix, requestId, requestType, Status::BEFORE_CHALLENGE, *clientCert,
           makeBinaryBlock(tlv::ContentType_Key, aesKey, sizeof(aesKey)));
   m_storage->addRequest(requestState);
   Data result;
@@ -346,15 +361,15 @@
     result.setContent(NEW::encodeDataContent(myEcdhPubKeyBase64,
                                              std::to_string(saltInt),
                                              requestState,
-                                             m_config.m_supportedChallenges));
+                                             m_config.m_caItem.m_supportedChallenges));
   }
   else if (requestType == RequestType::REVOKE) {
     result.setContent(REVOKE::encodeDataContent(myEcdhPubKeyBase64,
                                                 std::to_string(saltInt),
                                                 requestState,
-                                                m_config.m_supportedChallenges));
+                                                m_config.m_caItem.m_supportedChallenges));
   }
-  m_keyChain.sign(result, signingByIdentity(m_config.m_caPrefix));
+  m_keyChain.sign(result, signingByIdentity(m_config.m_caItem.m_caPrefix));
   m_face.put(result);
   if (m_config.m_statusUpdateCallback) {
     m_config.m_statusUpdateCallback(requestState);
@@ -456,7 +471,7 @@
   auto contentBlock = encodeBlockWithAesGcm128(tlv::Content, requestState.m_encryptionKey.value(), payload.value(),
                                                payload.value_size(), (uint8_t*)"test", strlen("test"));
   result.setContent(contentBlock);
-  m_keyChain.sign(result, signingByIdentity(m_config.m_caPrefix));
+  m_keyChain.sign(result, signingByIdentity(m_config.m_caItem.m_caPrefix));
   m_face.put(result);
   if (m_config.m_statusUpdateCallback) {
     m_config.m_statusUpdateCallback(requestState);
@@ -479,7 +494,7 @@
   SignatureInfo signatureInfo;
   signatureInfo.setValidityPeriod(period);
   security::SigningInfo signingInfo(security::SigningInfo::SIGNER_TYPE_ID,
-                                    m_config.m_caPrefix, signatureInfo);
+                                    m_config.m_caItem.m_caPrefix, signatureInfo);
 
   m_keyChain.sign(newCert, signingInfo);
   _LOG_TRACE("new cert got signed" << newCert);
@@ -492,7 +507,7 @@
   std::string requestId;
   RequestState requestState;
   try {
-    requestId = readString(request.getName().at(m_config.m_caPrefix.size() + 2));
+    requestId = readString(request.getName().at(m_config.m_caItem.m_caPrefix.size() + 2));
   }
   catch (const std::exception& e) {
     _LOG_ERROR("Cannot read the request ID out from the request: " << e.what());
@@ -545,7 +560,7 @@
   result.setName(name);
   result.setFreshnessPeriod(DEFAULT_DATA_FRESHNESS_PERIOD);
   result.setContent(ErrorTLV::encodeDataContent(error, errorInfo));
-  m_keyChain.sign(result, signingByIdentity(m_config.m_caPrefix));
+  m_keyChain.sign(result, signingByIdentity(m_config.m_caItem.m_caPrefix));
   return result;
 }
 
diff --git a/src/ca-module.hpp b/src/ca-module.hpp
index c97e4f3..acc2a50 100644
--- a/src/ca-module.hpp
+++ b/src/ca-module.hpp
@@ -21,7 +21,7 @@
 #ifndef NDNCERT_CA_MODULE_HPP
 #define NDNCERT_CA_MODULE_HPP
 
-#include "ca-config.hpp"
+#include "configuration.hpp"
 #include "crypto-support/crypto-helper.hpp"
 #include "ca-storage.hpp"
 
diff --git a/src/client-config.cpp b/src/client-config.cpp
deleted file mode 100644
index 2396cab..0000000
--- a/src/client-config.cpp
+++ /dev/null
@@ -1,112 +0,0 @@
-/* -*- 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 "client-config.hpp"
-
-#include <ndn-cxx/util/io.hpp>
-#include <fstream>
-
-namespace ndn {
-namespace ndncert {
-
-void
-ClientConfig::load(const std::string& fileName)
-{
-  JsonSection config;
-  try {
-    boost::property_tree::read_json(fileName, config);
-  }
-  catch (const std::exception& error) {
-    BOOST_THROW_EXCEPTION(Error("Failed to parse configuration file " + fileName + ", " + error.what()));
-  }
-
-  if (config.begin() == config.end()) {
-    BOOST_THROW_EXCEPTION(Error("Error processing configuration file: " + fileName + ", no data"));
-  }
-
-  load(config);
-}
-
-void
-ClientConfig::load(const JsonSection& configSection)
-{
-  m_caItems.clear();
-  auto caList = configSection.get_child("ca-list");
-  auto it = caList.begin();
-  for (; it != caList.end(); it++) {
-    m_caItems.push_back(extractCaItem(it->second));
-  }
-  m_localNdncertAnchor = configSection.get("local-ndncert-anchor", "");
-}
-
-void
-ClientConfig::save(const std::string& fileName)
-{
-  JsonSection configJson;
-  JsonSection caList;
-  std::stringstream ss;
-  for (const auto& item : m_caItems) {
-    JsonSection caItem;
-    caItem.put("ca-prefix", item.m_caPrefix.toUri());
-    caItem.put("ca-info", item.m_caInfo);
-    caItem.put("probe", item.m_probe);
-    ss.str(std::string());
-    io::save(item.m_anchor, ss);
-    caItem.put("certificate", ss.str());
-    caList.push_back(std::make_pair("", caItem));
-  }
-  configJson.add_child("ca-list", caList);
-  ss.str(std::string());
-  boost::property_tree::write_json(ss, configJson);
-
-  std::ofstream configFile;
-  configFile.open(fileName, std::ios::trunc);
-  configFile << ss.str();
-  configFile.close();
-}
-
-ClientCaItem
-ClientConfig::extractCaItem(const JsonSection& configSection)
-{
-  ClientCaItem item;
-  item.m_caPrefix = Name(configSection.get("ca-prefix", ""));
-  if (item.m_caPrefix.empty()) {
-    BOOST_THROW_EXCEPTION(Error("Cannot read ca-prefix from the config file"));
-  }
-  item.m_caInfo = configSection.get("ca-info", "");
-  item.m_probe = configSection.get("probe", "");
-  std::istringstream ss(configSection.get("certificate", ""));
-  item.m_maxSuffixLength = configSection.get<size_t>(CONFIG_MAX_SUFFIX_LENGTH, 1);
-  auto anchor = io::load<security::v2::Certificate>(ss);
-  if (anchor == nullptr) {
-    BOOST_THROW_EXCEPTION(Error("Cannot load the certificate from config file"));
-  }
-  item.m_anchor = *anchor;
-  return item;
-}
-
-void
-ClientConfig::removeCaItem(const Name& caName)
-{
-  m_caItems.remove_if([&](const ClientCaItem& item) { return item.m_caPrefix == caName; });
-}
-
-}  // namespace ndncert
-}  // namespace ndn
diff --git a/src/client-config.hpp b/src/client-config.hpp
deleted file mode 100644
index 0665808..0000000
--- a/src/client-config.hpp
+++ /dev/null
@@ -1,117 +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_CONFIG_HPP
-#define NDNCERT_CLIENT_CONFIG_HPP
-
-#include "request-state.hpp"
-#include <ndn-cxx/security/v2/certificate.hpp>
-
-namespace ndn {
-namespace ndncert {
-
-/**
- * @brief The configuration for a trusted CA from a requester's perspective
- */
-class ClientCaItem {
-public:
-  /**
-   * CA Name prefix (without /CA suffix).
-   */
-  Name m_caPrefix;
-  /**
-   * CA Information.
-   */
-  std::string m_caInfo;
-  /**
-   * A list of parameter-keys for PROBE.
-   */
-  std::list<std::string> m_probeParameterKeys;
-  /**
-   * Maximum allowed validity period of the certificate being requested.
-   * The value is in the unit of second.
-   */
-  time::seconds m_maxValidityPeriod;
-  /**
-   * CA's certificate.
-   */
-  security::v2::Certificate m_anchor;
-  /**
-   * Maximum allowed suffix length of requested name.
-   * E.g., When its value is 2, at most 2 name components can be assigned after m_caPrefix.
-   */
-  size_t m_maxSuffixLength;
-
-  //=======old
-
-  // The identity name of the CA. Extracted from config field "ca-prefix"
-  Name m_caName;
-
-  // An instruction for requesters to use _PROBE. Extracted from config field "probe"
-  std::string m_probe;  // "email::uid::name"
-};
-
-/**
- * @brief Represents Client configuration
- *
- * For Client configuration format, please refer to:
- *   https://github.com/named-data/ndncert/wiki/Client-Configuration-Sample
- */
-class ClientConfig {
-public:
-  class Error : public std::runtime_error {
-  public:
-    using std::runtime_error::runtime_error;
-  };
-
-public:
-  /**
-   * @throw ClientConfig::Error when config file does not exist
-   * @throw ClientConfig::Error when the JSON text in the file cannot be parsed correctly
-   * @throw ClientConfig::Error when the ca-prefix attribute in JSON text is empty
-   * @throw ClientConfig::Error when the certificate in JSON text cannot be parsed correctly
-   */
-  void
-  load(const std::string& fileName);
-
-  void
-  load(const JsonSection& configSection);
-
-  void
-  save(const std::string& fileName);
-
-  void
-  addNewCaItem(const ClientCaItem& item);
-
-  void
-  removeCaItem(const Name& caName);
-
-  static ClientCaItem
-  extractCaItem(const JsonSection& configSection);
-
-public:
-  std::list<ClientCaItem> m_caItems;
-  std::string m_localNdncertAnchor;
-};
-
-}  // namespace ndncert
-}  // namespace ndn
-
-#endif  // NDNCERT_CLIENT_CONFIG_HPP
diff --git a/src/client-module.cpp b/src/client-module.cpp
index 0fbd07b..0e7810e 100644
--- a/src/client-module.cpp
+++ b/src/client-module.cpp
@@ -68,10 +68,10 @@
 ClientModule::verifyInfoResponse(const Data& reply)
 {
   // parse the ca item
-  auto caItem = INFO::decodeClientConfigFromContent(reply.getContent());
+  auto caItem = INFO::decodeDataContentToCaProfile(reply.getContent());
 
   // verify the probe Data's sig
-  if (!security::verifySignature(reply, caItem.m_anchor)) {
+  if (!security::verifySignature(reply, *caItem.m_cert)) {
     _LOG_ERROR("Cannot verify data signature from " << m_ca.m_caPrefix.toUri());
     return false;
   }
@@ -84,7 +84,7 @@
   const Block& contentBlock = reply.getContent();
 
   // parse the ca item
-  auto caItem = INFO::decodeClientConfigFromContent(contentBlock);
+  auto caItem = INFO::decodeDataContentToCaProfile(contentBlock);
 
   // update the local config
   bool findItem = false;
@@ -100,15 +100,15 @@
 }
 
 shared_ptr<Interest>
-ClientModule::generateProbeInterest(const ClientCaItem& ca, const std::string& probeInfo)
+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::encodeApplicationParametersFromProbeInfo(ca, probeInfo));
+  interest->setApplicationParameters(PROBE::encodeApplicationParameters(std::move(probeInfo)));
 
   // update local state
   m_ca = ca;
@@ -118,7 +118,7 @@
 void
 ClientModule::onProbeResponse(const Data& reply)
 {
-  if (!security::verifySignature(reply, m_ca.m_anchor)) {
+  if (!security::verifySignature(reply, *m_ca.m_cert)) {
     _LOG_ERROR("Cannot verify data signature from " << m_ca.m_caPrefix.toUri());
     return;
   }
@@ -213,7 +213,7 @@
 std::list<std::string>
 ClientModule::onNewRenewRevokeResponse(const Data& reply)
 {
-  if (!security::verifySignature(reply, m_ca.m_anchor)) {
+  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>();
   }
@@ -248,7 +248,7 @@
   // Name requestedName = identityName;
   bool findCa = false;
   for (const auto& caItem : m_config.m_caItems) {
-    if (caItem.m_caName.isPrefixOf(certificate.getName())) {
+    if (caItem.m_caPrefix.isPrefixOf(certificate.getName())) {
       m_ca = caItem;
       findCa = true;
     }
@@ -296,7 +296,7 @@
 void
 ClientModule::onChallengeResponse(const Data& reply)
 {
-  if (!security::verifySignature(reply, m_ca.m_anchor)) {
+  if (!security::verifySignature(reply, *m_ca.m_cert)) {
     _LOG_ERROR("Cannot verify data signature from " << m_ca.m_caPrefix.toUri());
     return;
   }
@@ -373,20 +373,5 @@
   m_status = Status::ENDED;
 }
 
-std::vector<std::string>
-ClientModule::parseProbeComponents(const std::string& probe)
-{
-  std::vector<std::string> components;
-  std::string delimiter = ":";
-  size_t last = 0;
-  size_t next = 0;
-  while ((next = probe.find(delimiter, last)) != std::string::npos) {
-    components.push_back(probe.substr(last, next - last));
-    last = next + 1;
-  }
-  components.push_back(probe.substr(last));
-  return components;
-}
-
 }  // namespace ndncert
 }  // namespace ndn
diff --git a/src/client-module.hpp b/src/client-module.hpp
index c439d3c..fd88129 100644
--- a/src/client-module.hpp
+++ b/src/client-module.hpp
@@ -21,7 +21,7 @@
 #ifndef NDNCERT_CLIENT_MODULE_HPP
 #define NDNCERT_CLIENT_MODULE_HPP
 
-#include "client-config.hpp"
+#include "configuration.hpp"
 #include "request-state.hpp"
 #include "crypto-support/crypto-helper.hpp"
 
@@ -30,7 +30,7 @@
 
 // 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
+// The validator instance should be in CaConfigItem
 
 class ClientModule : noncopyable
 {
@@ -83,7 +83,7 @@
   addCaFromInfoResponse(const Data& reply);
 
   shared_ptr<Interest>
-  generateProbeInterest(const ClientCaItem& ca, const std::string& probeInfo);
+  generateProbeInterest(const CaConfigItem& ca, std::vector<std::tuple<std::string, std::string>>&& probeInfo);
 
   void
   onProbeResponse(const Data& reply);
@@ -114,9 +114,6 @@
   void
   onCertFetchResponse(const Data& reply);
 
-  static std::vector<std::string>
-  parseProbeComponents(const std::string& probe);
-
   void
   endSession();
 
@@ -124,7 +121,7 @@
   ClientConfig m_config;
   security::v2::KeyChain& m_keyChain;
 
-  ClientCaItem m_ca;
+  CaConfigItem m_ca;
   security::Key m_key;
   Name m_identityName;
 
@@ -132,7 +129,6 @@
   Status m_status = Status::NOT_STARTED;
   std::string m_challengeStatus = "";
   std::string m_challengeType = "";
-  std::string m_certId = "";
   Name m_issuedCertName;
   std::list<std::string> m_challengeList;
   bool m_isCertInstalled = false;
diff --git a/src/configuration.cpp b/src/configuration.cpp
new file mode 100644
index 0000000..c4f2c72
--- /dev/null
+++ b/src/configuration.cpp
@@ -0,0 +1,198 @@
+/* -*- 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 "configuration.hpp"
+#include "challenge-module.hpp"
+#include <ndn-cxx/util/io.hpp>
+#include <boost/filesystem.hpp>
+
+namespace ndn {
+namespace ndncert {
+
+void
+CaConfigItem::parse(const JsonSection& configJson)
+{
+  // CA prefix
+  m_caPrefix = Name(configJson.get(CONFIG_CA_PREFIX, ""));
+  if (m_caPrefix.empty()) {
+    BOOST_THROW_EXCEPTION(std::runtime_error("Cannot parse ca-prefix from the config file"));
+  }
+  // CA info
+  m_caInfo = configJson.get(CONFIG_CA_INFO, "");
+  // CA max validity period
+  m_maxValidityPeriod = time::seconds(configJson.get(CONFIG_MAX_VALIDITY_PERIOD, 86400));
+  // CA max suffix length
+  m_maxSuffixLength = configJson.get_optional<size_t>(CONFIG_MAX_SUFFIX_LENGTH);
+  // probe parameter keys
+  m_probeParameterKeys.clear();
+  auto probeParametersJson = configJson.get_child_optional(CONFIG_PROBE_PARAMETERS);
+  if (probeParametersJson) {
+    for (const auto item : *probeParametersJson) {
+      auto probeParameter = item.second.get(CONFIG_PROBE_PARAMETER, "");
+      probeParameter = boost::algorithm::to_lower_copy(probeParameter);
+      if (probeParameter == "") {
+        BOOST_THROW_EXCEPTION(std::runtime_error("Probe parameter key cannot be empty."));
+      }
+      m_probeParameterKeys.push_back(probeParameter);
+    }
+  }
+  // supported challenges
+  m_supportedChallenges.clear();
+  auto challengeListJson = configJson.get_child_optional(CONFIG_SUPPORTED_CHALLENGES);
+  if (challengeListJson) {
+    for (const auto item : *challengeListJson) {
+      auto challengeType = item.second.get(CONFIG_CHALLENGE, "");
+      challengeType = boost::algorithm::to_lower_copy(challengeType);
+      if (challengeType == "") {
+        BOOST_THROW_EXCEPTION(std::runtime_error("Challenge type canont be empty."));
+      }
+      if (!ChallengeModule::isChallengeSupported(challengeType)) {
+        BOOST_THROW_EXCEPTION(std::runtime_error("Challenge " + challengeType + " is not supported."));
+      }
+      m_supportedChallenges.push_back(challengeType);
+    }
+  }
+  // anchor certificate
+  m_cert = nullptr;
+  auto certificateStr = configJson.get("certificate", "");
+  if (certificateStr != "") {
+    std::istringstream ss(certificateStr);
+    m_cert = io::load<security::v2::Certificate>(ss);
+  }
+}
+
+JsonSection
+CaConfigItem::toJson() const
+{
+  JsonSection caItem;
+  caItem.put(CONFIG_CA_PREFIX, m_caPrefix.toUri());
+  caItem.put(CONFIG_CA_INFO, m_caInfo);
+  caItem.put(CONFIG_MAX_VALIDITY_PERIOD, m_maxValidityPeriod.count());
+  caItem.put(CONFIG_MAX_SUFFIX_LENGTH, m_maxSuffixLength);
+  if (!m_probeParameterKeys.empty()) {
+    JsonSection probeParametersJson;
+    for (const auto& key : m_probeParameterKeys) {
+      JsonSection keyJson;
+      keyJson.put(CONFIG_PROBE_PARAMETER, key);
+      probeParametersJson.push_back(std::make_pair("", keyJson));
+    }
+    caItem.add_child("", probeParametersJson);
+  }
+  if (!m_supportedChallenges.empty()) {
+    JsonSection challengeListJson;
+    for (const auto& challenge : m_supportedChallenges) {
+      JsonSection challengeJson;
+      challengeJson.put(CONFIG_CHALLENGE, challenge);
+      challengeListJson.push_back(std::make_pair("", challengeJson));
+    }
+    caItem.add_child("", challengeListJson);
+  }
+  if (m_cert != nullptr) {
+    std::stringstream ss;
+    io::save(*m_cert, ss);
+    caItem.put("certificate", ss.str());
+  }
+  return caItem;
+}
+
+void
+CaConfig::load(const std::string& fileName)
+{
+  JsonSection configJson;
+  try {
+    boost::property_tree::read_json(fileName, configJson);
+  }
+  catch (const std::exception& error) {
+    BOOST_THROW_EXCEPTION(std::runtime_error("Failed to parse configuration file " + fileName + ", " + error.what()));
+  }
+  if (configJson.begin() == configJson.end()) {
+    BOOST_THROW_EXCEPTION(std::runtime_error("No JSON configuration found in file: " + fileName));
+  }
+  m_caItem.parse(configJson);
+  if (m_caItem.m_supportedChallenges.size() == 0) {
+    BOOST_THROW_EXCEPTION(std::runtime_error("At least one challenge should be specified."));
+  }
+}
+
+void
+CaConfig::save(const std::string& fileName) const
+{
+  std::stringstream ss;
+  boost::property_tree::write_json(ss, m_caItem.toJson());
+  std::ofstream configFile;
+  configFile.open(fileName);
+  configFile << ss.str();
+  configFile.close();
+}
+
+void
+ClientConfig::load(const std::string& fileName)
+{
+  JsonSection configJson;
+  try {
+    boost::property_tree::read_json(fileName, configJson);
+  }
+  catch (const std::exception& error) {
+    BOOST_THROW_EXCEPTION(std::runtime_error("Failed to parse configuration file " + fileName + ", " + error.what()));
+  }
+  if (configJson.begin() == configJson.end()) {
+    BOOST_THROW_EXCEPTION(std::runtime_error("No JSON configuration found in file: " + fileName));
+  }
+  load(configJson);
+}
+
+void
+ClientConfig::load(const JsonSection& configSection)
+{
+  m_caItems.clear();
+  auto caList = configSection.get_child("ca-list");
+  for (auto item : caList) {
+    CaConfigItem caItem;
+    caItem.parse(item.second);
+    if (caItem.m_cert == nullptr) {
+      BOOST_THROW_EXCEPTION(std::runtime_error("No CA certificate is loaded from JSON configuration."));
+    }
+    m_caItems.push_back(std::move(caItem));
+  }
+}
+
+void
+ClientConfig::save(const std::string& fileName) const
+{
+  JsonSection configJson;
+  for (const auto& caItem : m_caItems) {
+    configJson.push_back(std::make_pair("", caItem.toJson()));
+  }
+  std::stringstream ss;
+  boost::property_tree::write_json(ss, configJson);
+  std::ofstream configFile;
+  configFile.open(fileName);
+  configFile << ss.str();
+  configFile.close();
+}
+
+void
+ClientConfig::removeCaItem(const Name& caName)
+{
+  m_caItems.remove_if([&](const CaConfigItem& item) { return item.m_caPrefix == caName; });
+}
+
+}  // namespace ndncert
+}  // namespace ndn
diff --git a/src/ca-config.hpp b/src/configuration.hpp
similarity index 75%
rename from src/ca-config.hpp
rename to src/configuration.hpp
index bba5af4..7f4f8a4 100644
--- a/src/ca-config.hpp
+++ b/src/configuration.hpp
@@ -22,11 +22,62 @@
 #define NDNCERT_CA_CONFIG_HPP
 
 #include "request-state.hpp"
-#include "client-config.hpp"
 
 namespace ndn {
 namespace ndncert {
 
+struct CaConfigItem {
+  /**
+   * CA Name prefix (without /CA suffix).
+   */
+  Name m_caPrefix;
+  /**
+   * CA Information.
+   * Default: "".
+   */
+  std::string m_caInfo;
+  /**
+   * A list of parameter-keys for PROBE.
+   * Default: empty list.
+   */
+  std::list<std::string> m_probeParameterKeys;
+  /**
+   * Maximum allowed validity period of the certificate being requested.
+   * The value is in the unit of second.
+   * Default: one day (86400 seconds).
+   */
+  time::seconds m_maxValidityPeriod;
+  /**
+   * Maximum allowed suffix length of requested name.
+   * E.g., When its value is 2, at most 2 name components can be assigned after m_caPrefix.
+   * Default: none.
+   */
+  boost::optional<size_t> m_maxSuffixLength;
+  /**
+   * A list of supported challenges. Only CA side will have m_supportedChallenges.
+   * Default: empty list.
+   */
+  std::list<std::string> m_supportedChallenges;
+  /**
+   * CA's certificate. Only Client side will have m_cert.
+   * Default: nullptr.
+   */
+  std::shared_ptr<security::v2::Certificate> m_cert;
+
+  void
+  parse(const JsonSection& configJson);
+
+  JsonSection
+  toJson() const;
+
+private:
+  void
+  parseProbeParameters(const JsonSection& section);
+
+  void
+  parseChallengeList(const JsonSection& configSection);
+};
+
 /**
  * @brief The name assignment function provided by the CA operator to generate available
  * namecomponents.
@@ -75,53 +126,15 @@
 public:
   /**
    * Load CA configuration from the file.
-   *
-   * @param fileName, the configuration file name.
-   * @throw std::runtime_error when config file does not exist or the configuration
-   *        in the file cannot be parsed correctly.
-   * @throw std::runtime_error when the ca-prefix attribute in JSON text is empty.
-   * @throw std::runtime_error when the challenge is not specified or is not supported.
+   * @throw std::runtime_error when config file cannot be correctly parsed.
    */
   void
   load(const std::string& fileName);
 
-private:
   void
-  parse(const JsonSection& configJson);
+  save(const std::string& fileName) const;
 
-  void
-  parseProbeParameters(const JsonSection& section);
-
-  void
-  parseChallengeList(const JsonSection& configSection);
-
-public:
-  /**
-   * CA Name prefix (without /CA suffix).
-   */
-  Name m_caPrefix;
-  /**
-   * CA Information.
-   */
-  std::string m_caInfo;
-  /**
-   * A list of parameter-keys for PROBE.
-   */
-  std::list<std::string> m_probeParameterKeys;
-  /**
-   * Maximum allowed validity period of the certificate being requested.
-   * The value is in the unit of second.
-   */
-  time::seconds m_maxValidityPeriod;
-  /**
-   * Maximum allowed suffix length of requested name.
-   * E.g., When its value is 2, at most 2 name components can be assigned after m_caPrefix.
-   */
-  size_t m_maxSuffixLength;
-  /**
-   * A list of supported challenges.
-   */
-  std::list<std::string> m_supportedChallenges;
+  CaConfigItem m_caItem;
   /**
    * NameAssignmentFunc Callback function
    */
@@ -132,6 +145,35 @@
   StatusUpdateCallback m_statusUpdateCallback;
 };
 
+/**
+ * @brief Represents Client configuration
+ *
+ * For Client configuration format, please refer to:
+ *   https://github.com/named-data/ndncert/wiki/Client-Configuration-Sample
+ */
+class ClientConfig {
+public:
+  /**
+   * @throw std::runtime_error when config file cannot be correctly parsed.
+   */
+  void
+  load(const std::string& fileName);
+
+  /**
+   * @throw std::runtime_error when config file cannot be correctly parsed.
+   */
+  void
+  load(const JsonSection& configSection);
+
+  void
+  save(const std::string& fileName) const;
+
+  void
+  removeCaItem(const Name& caName);
+
+  std::list<CaConfigItem> m_caItems;
+};
+
 }  // namespace ndncert
 }  // namespace ndn
 
diff --git a/src/protocol-detail/error.hpp b/src/protocol-detail/error.hpp
index 1234233..8a2cda0 100644
--- a/src/protocol-detail/error.hpp
+++ b/src/protocol-detail/error.hpp
@@ -21,8 +21,7 @@
 #ifndef NDNCERT_PROTOCOL_DETAIL_ERROR_HPP
 #define NDNCERT_PROTOCOL_DETAIL_ERROR_HPP
 
-#include "../ca-config.hpp"
-#include "../client-config.hpp"
+#include "../configuration.hpp"
 
 namespace ndn {
 namespace ndncert {
diff --git a/src/protocol-detail/info.cpp b/src/protocol-detail/info.cpp
index 59cc48e..306d060 100644
--- a/src/protocol-detail/info.cpp
+++ b/src/protocol-detail/info.cpp
@@ -24,14 +24,15 @@
 namespace ndncert {
 
 Block
-INFO::encodeDataContent(const CaConfig& caConfig, const security::v2::Certificate& certificate)
+INFO::encodeDataContent(const CaConfigItem& caConfig, const security::v2::Certificate& certificate)
 {
   auto content = makeEmptyBlock(tlv::Content);
   content.push_back(makeNestedBlock(tlv_ca_prefix, caConfig.m_caPrefix));
   std::string caInfo = "";
   if (caConfig.m_caInfo == "") {
     caInfo = "Issued by " + certificate.getSignature().getKeyLocator().getName().toUri();
-  } else {
+  }
+  else {
     caInfo = caConfig.m_caInfo;
   }
   content.push_back(makeStringBlock(tlv_ca_info, caInfo));
@@ -41,38 +42,42 @@
   }
   content.push_back(makeNonNegativeIntegerBlock(tlv_max_validity_period, caConfig.m_maxValidityPeriod.count()));
   content.push_back(makeNestedBlock(tlv_ca_certificate, certificate));
-  content.push_back(makeNonNegativeIntegerBlock(tlv_max_suffix_length, caConfig.m_maxSuffixLength));
+  if (caConfig.m_maxSuffixLength) {
+    content.push_back(makeNonNegativeIntegerBlock(tlv_max_suffix_length, *caConfig.m_maxSuffixLength));
+  }
   content.encode();
   return content;
 }
 
-ClientCaItem
-INFO::decodeClientConfigFromContent(const Block& block)
+CaConfigItem
+INFO::decodeDataContentToCaProfile(const Block& block)
 {
-  ClientCaItem result;
+  CaConfigItem result;
   block.parse();
   for (auto const& item : block.elements()) {
-    if (item.type() == tlv_ca_prefix) {
-      item.parse();
+    item.parse();
+    switch (item.type()) {
+    case tlv_ca_prefix:
       result.m_caPrefix.wireDecode(item.get(tlv::Name));
-    }
-    else if (item.type() == tlv_ca_info) {
+      break;
+    case tlv_ca_info:
       result.m_caInfo = readString(item);
-    }
-    else if (item.type() == tlv_parameter_key) {
+      break;
+    case tlv_parameter_key:
       result.m_probeParameterKeys.push_back(readString(item));
-    }
-    else if (item.type() == tlv_max_validity_period) {
+      break;
+    case tlv_max_validity_period:
       result.m_maxValidityPeriod = time::seconds(readNonNegativeInteger(item));
-    }
-    else if (item.type() == tlv_ca_certificate) {
-      item.parse();
-      result.m_anchor.wireDecode(item.get(tlv::Data));
-    } else if (item.type() == tlv_max_suffix_length) {
+      break;
+    case tlv_max_suffix_length:
       result.m_maxSuffixLength = readNonNegativeInteger(item);
-    }
-    else {
+      break;
+    case tlv_ca_certificate:
+      result.m_cert->wireDecode(item.get(tlv::Data));
+      break;
+    default:
       continue;
+      break;
     }
   }
   return result;
diff --git a/src/protocol-detail/info.hpp b/src/protocol-detail/info.hpp
index a21040e..1c375bb 100644
--- a/src/protocol-detail/info.hpp
+++ b/src/protocol-detail/info.hpp
@@ -21,8 +21,7 @@
 #ifndef NDNCERT_PROTOCOL_DETAIL_INFO_HPP
 #define NDNCERT_PROTOCOL_DETAIL_INFO_HPP
 
-#include "../ca-config.hpp"
-#include "../client-config.hpp"
+#include "../configuration.hpp"
 
 namespace ndn {
 namespace ndncert {
@@ -33,13 +32,13 @@
    * Encode CA configuration and its certificate into a TLV block as INFO Data packet content.
    */
   static Block
-  encodeDataContent(const CaConfig& caConfig, const security::v2::Certificate& certificate);
+  encodeDataContent(const CaConfigItem& caConfig, const security::v2::Certificate& certificate);
 
   /**
    * Decode CA configuration from the TLV block of INFO Data packet content.
    */
-  static ClientCaItem
-  decodeClientConfigFromContent(const Block& block);
+  static CaConfigItem
+  decodeDataContentToCaProfile(const Block& block);
 };
 
 }  // namespace ndncert
diff --git a/src/protocol-detail/probe.cpp b/src/protocol-detail/probe.cpp
index 34b769e..93ba020 100644
--- a/src/protocol-detail/probe.cpp
+++ b/src/protocol-detail/probe.cpp
@@ -26,39 +26,13 @@
 namespace ndncert {
 
 // For Client
-std::vector<std::string>
-PROBE::parseProbeComponents(const std::string& probe)
-{
-  std::vector<std::string> components;
-  std::string delimiter = ":";
-  size_t last = 0;
-  size_t next = 0;
-  while ((next = probe.find(delimiter, last)) != std::string::npos) {
-    components.push_back(probe.substr(last, next - last));
-    last = next + 1;
-  }
-  components.push_back(probe.substr(last));
-  return components;
-}
-
 Block
-PROBE::encodeApplicationParametersFromProbeInfo(const ClientCaItem& ca, const std::string& probeInfo)
+PROBE::encodeApplicationParameters(std::vector<std::tuple<std::string, std::string>>&& parameters)
 {
   auto content = makeEmptyBlock(tlv::ApplicationParameters);
-
-  std::vector<std::string> fields = parseProbeComponents(ca.m_probe);
-  std::vector<std::string> arguments = parseProbeComponents(probeInfo);
-  ;
-
-  if (arguments.size() != fields.size()) {
-    BOOST_THROW_EXCEPTION(std::runtime_error("Error in genProbeRequestJson: argument list does not match field list in the config file."));
-  }
-
-  for (size_t i = 0; i < fields.size(); ++i) {
-    content.push_back(
-        makeStringBlock(tlv_parameter_key, fields.at(i)));
-    content.push_back(
-        makeStringBlock(tlv_parameter_value, arguments.at(i)));
+  for (size_t i = 0; i < parameters.size(); ++i) {
+    content.push_back(makeStringBlock(tlv_parameter_key, std::get<0>(parameters[i])));
+    content.push_back(makeStringBlock(tlv_parameter_value, std::get<1>(parameters[i])));
   }
   content.encode();
   return content;
@@ -66,29 +40,15 @@
 
 // For CA
 Block
-PROBE::encodeDataContent(const Name& identifier, const std::string& m_probe, const Block& parameterTLV)
+PROBE::encodeDataContent(const std::vector<Name>& identifiers, boost::optional<size_t> maxSuffixLength)
 {
-  std::vector<std::string> fields;
-  std::string delimiter = ":";
-  size_t last = 0;
-  size_t next = 0;
-  while ((next = m_probe.find(delimiter, last)) != std::string::npos) {
-    fields.push_back(m_probe.substr(last, next - last));
-    last = next + 1;
-  }
-  fields.push_back(m_probe.substr(last));
-
   Block content = makeEmptyBlock(tlv::Content);
-
-  // TODO: Currently have no mechanism to utilize the given params to determine name
-  //for (size_t i = 0; i < fields.size(); ++i) {
-  //  root.put(fields.at(i), parameterJson.get(fields.at(i), ""));
-  //}
-
-  content.push_back(makeNestedBlock(tlv_probe_response, identifier));
-
-  // TODO: Must be determined based on CA config
-  content.push_back(makeEmptyBlock(tlv_allow_longer_name));
+  for (const auto& name : identifiers) {
+    content.push_back(makeNestedBlock(tlv_probe_response, name));
+  }
+  if (maxSuffixLength) {
+    content.push_back(makeNonNegativeIntegerBlock(tlv_max_suffix_length, *maxSuffixLength));
+  }
   content.encode();
   return content;
 }
diff --git a/src/protocol-detail/probe.hpp b/src/protocol-detail/probe.hpp
index 41b73c8..126fba4 100644
--- a/src/protocol-detail/probe.hpp
+++ b/src/protocol-detail/probe.hpp
@@ -21,22 +21,18 @@
 #ifndef NDNCERT_PROTOCOL_DETAIL_PROBE_HPP
 #define NDNCERT_PROTOCOL_DETAIL_PROBE_HPP
 
-#include "../ca-config.hpp"
-#include "../client-config.hpp"
+#include "../configuration.hpp"
 
 namespace ndn {
 namespace ndncert {
 
 class PROBE {
 public:
-  static std::vector<std::string>
-  parseProbeComponents(const std::string& probe);
+  static Block
+  encodeApplicationParameters(std::vector<std::tuple<std::string, std::string>>&& parameters);
 
   static Block
-  encodeApplicationParametersFromProbeInfo(const ClientCaItem& ca, const std::string& probeInfo);
-
-  static Block
-  encodeDataContent(const Name& identifier, const std::string& m_probe, const Block& parameterTLV);
+  encodeDataContent(const std::vector<Name>& identifiers, boost::optional<size_t> maxSuffixLength);
 };
 
 }  // namespace ndncert
diff --git a/tests/unit-tests/bench.t.cpp b/tests/unit-tests/bench.t.cpp
index 2721811..4caed86 100644
--- a/tests/unit-tests/bench.t.cpp
+++ b/tests/unit-tests/bench.t.cpp
@@ -37,7 +37,7 @@
   auto cert = key.getDefaultCertificate();
 
   util::DummyClientFace face(io, m_keyChain, {true, true});
-  CaModule ca(face, m_keyChain, "tests/unit-tests/ca.conf.test", "ca-storage-memory");
+  CaModule ca(face, m_keyChain, "tests/unit-tests/config-files/config-ca-1", "ca-storage-memory");
   advanceClocks(time::milliseconds(20), 60);
 
   Interest interest = MetadataObject::makeDiscoveryInterest(Name("/ndn/CA/INFO"));
@@ -63,10 +63,10 @@
       BOOST_CHECK(security::verifySignature(response, cert));
       auto contentBlock = response.getContent();
       contentBlock.parse();
-      auto caItem = INFO::decodeClientConfigFromContent(contentBlock);
+      auto caItem = INFO::decodeDataContentToCaProfile(contentBlock);
       BOOST_CHECK_EQUAL(caItem.m_caPrefix, "/ndn");
-      BOOST_CHECK_EQUAL(caItem.m_probe, "");
-      BOOST_CHECK_EQUAL(caItem.m_anchor.wireEncode(), cert.wireEncode());
+      BOOST_CHECK_EQUAL(caItem.m_probeParameterKeys.size(), 0);
+      BOOST_CHECK_EQUAL(caItem.m_cert->wireEncode(), cert.wireEncode());
       BOOST_CHECK_EQUAL(caItem.m_caInfo, "ndn testbed ca");
     }
   });
@@ -85,14 +85,14 @@
   auto cert = key.getDefaultCertificate();
 
   util::DummyClientFace face(io, m_keyChain, {true, true});
-  CaModule ca(face, m_keyChain, "tests/unit-tests/ca.conf.test", "ca-storage-memory");
+  CaModule ca(face, m_keyChain, "tests/unit-tests/config-files/config-ca-1", "ca-storage-memory");
   advanceClocks(time::milliseconds(20), 60);
 
   // generate NEW Interest
   ClientModule client(m_keyChain);
-  ClientCaItem item;
+  CaConfigItem item;
   item.m_caPrefix = Name("/ndn");
-  item.m_anchor = cert;
+  item.m_cert = std::make_shared<security::v2::Certificate>(cert);
   client.getClientConf().m_caItems.push_back(item);
   auto newInterest = client.generateNewInterest(time::system_clock::now(),
                                                 time::system_clock::now() + time::days(1), Name("/ndn/alice"));
diff --git a/tests/unit-tests/ca-config.t.cpp b/tests/unit-tests/ca-config.t.cpp
deleted file mode 100644
index 2e054cb..0000000
--- a/tests/unit-tests/ca-config.t.cpp
+++ /dev/null
@@ -1,83 +0,0 @@
-/* -*- 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 "ca-config.hpp"
-#include "protocol-detail/info.hpp"
-#include "test-common.hpp"
-
-namespace ndn {
-namespace ndncert {
-namespace tests {
-
-BOOST_FIXTURE_TEST_SUITE(TestCaConfig, IdentityManagementFixture)
-
-BOOST_AUTO_TEST_CASE(ReadConfigFile)
-{
-  CaConfig config;
-  config.load("tests/unit-tests/ca.conf.test");
-  BOOST_CHECK_EQUAL(config.m_caPrefix, "/ndn");
-  BOOST_CHECK_EQUAL(config.m_caInfo, "ndn testbed ca");
-  BOOST_CHECK_EQUAL(config.m_maxValidityPeriod, time::seconds(86400));
-  BOOST_CHECK_EQUAL(config.m_probeParameterKeys.size(), 1);
-  BOOST_CHECK_EQUAL(config.m_probeParameterKeys.front(), "full name");
-  BOOST_CHECK_EQUAL(config.m_supportedChallenges.size(), 1);
-  BOOST_CHECK_EQUAL(config.m_supportedChallenges.front(), "pin");
-}
-
-BOOST_AUTO_TEST_CASE(ReadNonexistConfigFile)
-{
-  CaConfig config;
-  BOOST_CHECK_THROW(config.load("tests/unit-tests/Nonexist"), std::runtime_error);
-}
-
-BOOST_AUTO_TEST_CASE(ReadConfigFileWithoutCaPrefix)
-{
-  CaConfig config;
-  BOOST_CHECK_THROW(config.load("tests/unit-tests/ca.conf.test2"), std::runtime_error);
-}
-
-BOOST_AUTO_TEST_CASE(ReadConfigFileWithChallengeNotSupported)
-{
-  CaConfig config;
-  BOOST_CHECK_THROW(config.load("tests/unit-tests/ca.conf.test3"), std::runtime_error);
-}
-
-BOOST_AUTO_TEST_CASE(InfoContentEncodingDecoding)
-{
-  CaConfig config;
-  config.load("tests/unit-tests/ca.conf.test");
-
-  const auto& identity = addIdentity("/test");
-  const auto& cert = identity.getDefaultKey().getDefaultCertificate();
-  auto encoded = INFO::encodeDataContent(config, cert);
-  auto decoded = INFO::decodeClientConfigFromContent(encoded);
-  BOOST_CHECK_EQUAL(config.m_caPrefix, decoded.m_caPrefix);
-  BOOST_CHECK_EQUAL(config.m_caInfo, decoded.m_caInfo);
-  BOOST_CHECK_EQUAL(config.m_maxValidityPeriod, decoded.m_maxValidityPeriod);
-  BOOST_CHECK_EQUAL(config.m_probeParameterKeys.size(), decoded.m_probeParameterKeys.size());
-  BOOST_CHECK_EQUAL(config.m_probeParameterKeys.front(), decoded.m_probeParameterKeys.front());
-  BOOST_CHECK_EQUAL(cert.wireEncode(), decoded.m_anchor.wireEncode());
-}
-
-BOOST_AUTO_TEST_SUITE_END()  // TestCaConfig
-
-}  // namespace tests
-}  // namespace ndncert
-}  // namespace ndn
diff --git a/tests/unit-tests/ca-module.t.cpp b/tests/unit-tests/ca-module.t.cpp
index 365cb90..599b6ce 100644
--- a/tests/unit-tests/ca-module.t.cpp
+++ b/tests/unit-tests/ca-module.t.cpp
@@ -35,8 +35,8 @@
 BOOST_AUTO_TEST_CASE(Initialization)
 {
   util::DummyClientFace face(io, m_keyChain, {true, true});
-  CaModule ca(face, m_keyChain, "tests/unit-tests/ca.conf.test", "ca-storage-memory");
-  BOOST_CHECK_EQUAL(ca.getCaConf().m_caPrefix, "/ndn");
+  CaModule ca(face, m_keyChain, "tests/unit-tests/config-files/config-ca-1", "ca-storage-memory");
+  BOOST_CHECK_EQUAL(ca.getCaConf().m_caItem.m_caPrefix, "/ndn");
 
   auto identity = addIdentity(Name("/ndn/site2"));
   auto key = identity.getDefaultKey();
@@ -56,7 +56,7 @@
   auto cert = key.getDefaultCertificate();
 
   util::DummyClientFace face(io, m_keyChain, {true, true});
-  CaModule ca(face, m_keyChain, "tests/unit-tests/ca.conf.test", "ca-storage-memory");
+  CaModule ca(face, m_keyChain, "tests/unit-tests/config-files/config-ca-1", "ca-storage-memory");
   advanceClocks(time::milliseconds(20), 60);
 
   Interest interest("/ndn/CA/INFO");
@@ -68,10 +68,10 @@
     BOOST_CHECK(security::verifySignature(response, cert));
     auto contentBlock = response.getContent();
     contentBlock.parse();
-    auto caItem = INFO::decodeClientConfigFromContent(contentBlock);
+    auto caItem = INFO::decodeDataContentToCaProfile(contentBlock);
     BOOST_CHECK_EQUAL(caItem.m_caPrefix, "/ndn");
-    BOOST_CHECK_EQUAL(caItem.m_probe, "");
-    BOOST_CHECK_EQUAL(caItem.m_anchor.wireEncode(), cert.wireEncode());
+    BOOST_CHECK_EQUAL(caItem.m_probeParameterKeys.size(), 0);
+    BOOST_CHECK_EQUAL(caItem.m_cert->wireEncode(), cert.wireEncode());
     BOOST_CHECK_EQUAL(caItem.m_caInfo, "ndn testbed ca");
   });
   face.receive(interest);
@@ -87,7 +87,7 @@
   auto cert = key.getDefaultCertificate();
 
   util::DummyClientFace face(io, m_keyChain, {true, true});
-  CaModule ca(face, m_keyChain, "tests/unit-tests/ca.conf.test", "ca-storage-memory");
+  CaModule ca(face, m_keyChain, "tests/unit-tests/config-files/config-ca-1", "ca-storage-memory");
   ca.setNameAssignmentFunction([&](const std::vector<std::tuple<std::string, std::string>>) -> std::vector<std::string> {
     std::vector<std::string> result;
     result.push_back("example");
@@ -130,7 +130,7 @@
   auto cert = key.getDefaultCertificate();
 
   util::DummyClientFace face(io, m_keyChain, {true, true});
-  CaModule ca(face, m_keyChain, "tests/unit-tests/ca.conf.test", "ca-storage-memory");
+  CaModule ca(face, m_keyChain, "tests/unit-tests/config-files/config-ca-1", "ca-storage-memory");
   advanceClocks(time::milliseconds(20), 60);
 
   Interest interest("/ndn/CA/PROBE");
@@ -168,13 +168,13 @@
   auto cert = key.getDefaultCertificate();
 
   util::DummyClientFace face(io, m_keyChain, {true, true});
-  CaModule ca(face, m_keyChain, "tests/unit-tests/ca.conf.test", "ca-storage-memory");
+  CaModule ca(face, m_keyChain, "tests/unit-tests/config-files/config-ca-1", "ca-storage-memory");
   advanceClocks(time::milliseconds(20), 60);
 
   ClientModule client(m_keyChain);
-  ClientCaItem item;
+  CaConfigItem item;
   item.m_caPrefix = Name("/ndn");
-  item.m_anchor = cert;
+  item.m_cert = std::make_shared<security::v2::Certificate>(cert);
   client.getClientConf().m_caItems.push_back(item);
 
   auto interest = client.generateNewInterest(time::system_clock::now(),
@@ -219,13 +219,13 @@
   auto cert = key.getDefaultCertificate();
 
   util::DummyClientFace face(io, m_keyChain, {true, true});
-  CaModule ca(face, m_keyChain, "tests/unit-tests/ca.conf.test");
+  CaModule ca(face, m_keyChain, "tests/unit-tests/config-files/config-ca-1");
   advanceClocks(time::milliseconds(20), 60);
 
   ClientModule client(m_keyChain);
-  ClientCaItem item;
+  CaConfigItem item;
   item.m_caPrefix = Name("/ndn");
-  item.m_anchor = cert;
+  item.m_cert = std::make_shared<security::v2::Certificate>(cert);
   client.getClientConf().m_caItems.push_back(item);
   auto current_tp = time::system_clock::now();
   auto interest1 = client.generateNewInterest(current_tp, current_tp - time::hours(1),
@@ -255,13 +255,13 @@
   auto cert = key.getDefaultCertificate();
 
   util::DummyClientFace face(io, m_keyChain, {true, true});
-  CaModule ca(face, m_keyChain, "tests/unit-tests/ca.conf.test", "ca-storage-memory");
+  CaModule ca(face, m_keyChain, "tests/unit-tests/config-files/config-ca-1", "ca-storage-memory");
   advanceClocks(time::milliseconds(20), 60);
 
   ClientModule client(m_keyChain);
-  ClientCaItem item;
+  CaConfigItem item;
   item.m_caPrefix = Name("/ndn");
-  item.m_anchor = cert;
+  item.m_cert = std::make_shared<security::v2::Certificate>(cert);
   client.getClientConf().m_caItems.push_back(item);
 
   auto interest1 = client.generateNewInterest(time::system_clock::now(),
@@ -300,13 +300,13 @@
   auto cert = key.getDefaultCertificate();
 
   util::DummyClientFace face(io, m_keyChain, {true, true});
-  CaModule ca(face, m_keyChain, "tests/unit-tests/ca.conf.test");
+  CaModule ca(face, m_keyChain, "tests/unit-tests/config-files/config-ca-1");
   advanceClocks(time::milliseconds(20), 60);
 
   ClientModule client(m_keyChain);
-  ClientCaItem item;
+  CaConfigItem item;
   item.m_caPrefix = Name("/ndn");
-  item.m_anchor = cert;
+  item.m_cert = std::make_shared<security::v2::Certificate>(cert);
   client.getClientConf().m_caItems.push_back(item);
   auto current_tp = time::system_clock::now();
   auto interest1 = client.generateNewInterest(current_tp, current_tp + time::days(1), Name("/ndn"));
@@ -330,14 +330,14 @@
   auto cert = key.getDefaultCertificate();
 
   util::DummyClientFace face(io, m_keyChain, {true, true});
-  CaModule ca(face, m_keyChain, "tests/unit-tests/ca.conf.test", "ca-storage-memory");
+  CaModule ca(face, m_keyChain, "tests/unit-tests/config-files/config-ca-1", "ca-storage-memory");
   advanceClocks(time::milliseconds(20), 60);
 
   // generate NEW Interest
   ClientModule client(m_keyChain);
-  ClientCaItem item;
+  CaConfigItem item;
   item.m_caPrefix = Name("/ndn");
-  item.m_anchor = cert;
+  item.m_cert = std::make_shared<security::v2::Certificate>(cert);
   client.getClientConf().m_caItems.push_back(item);
   auto newInterest = client.generateNewInterest(time::system_clock::now(),
                                                 time::system_clock::now() + time::days(1), Name("/ndn/zhiyi"));
@@ -413,7 +413,7 @@
   auto cert = key.getDefaultCertificate();
 
   util::DummyClientFace face(io, {true, true});
-  CaModule ca(face, m_keyChain, "tests/unit-tests/ca.conf.test", "ca-storage-memory");
+  CaModule ca(face, m_keyChain, "tests/unit-tests/config-files/config-ca-1", "ca-storage-memory");
   advanceClocks(time::milliseconds(20), 60);
 
   //generate a certificate
@@ -432,9 +432,9 @@
   auto issuedCert = ca.issueCertificate(certRequest);
 
   ClientModule client(m_keyChain);
-  ClientCaItem item;
-  item.m_caName = Name("/ndn");
-  item.m_anchor = cert;
+  CaConfigItem item;
+  item.m_caPrefix = Name("/ndn");
+  item.m_cert = std::make_shared<security::v2::Certificate>(cert);
   client.getClientConf().m_caItems.push_back(item);
 
   auto interest = client.generateRevokeInterest(issuedCert);
@@ -477,7 +477,7 @@
   auto cert = key.getDefaultCertificate();
 
   util::DummyClientFace face(io, {true, true});
-  CaModule ca(face, m_keyChain, "tests/unit-tests/ca.conf.test", "ca-storage-memory");
+  CaModule ca(face, m_keyChain, "tests/unit-tests/config-files/config-ca-1", "ca-storage-memory");
   advanceClocks(time::milliseconds(20), 60);
 
   //generate a certificate
@@ -494,9 +494,9 @@
   m_keyChain.sign(clientCert, signingByKey(clientKey.getName()).setSignatureInfo(signatureInfo));
 
   ClientModule client(m_keyChain);
-  ClientCaItem item;
-  item.m_caName = Name("/ndn");
-  item.m_anchor = cert;
+  CaConfigItem item;
+  item.m_caPrefix = Name("/ndn");
+  item.m_cert = std::make_shared<security::v2::Certificate>(cert);
   client.getClientConf().m_caItems.push_back(item);
 
   auto interest = client.generateRevokeInterest(clientCert);
diff --git a/tests/unit-tests/ca.conf.test2 b/tests/unit-tests/ca.conf.test2
deleted file mode 100644
index 22cd83c..0000000
--- a/tests/unit-tests/ca.conf.test2
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-  "ca-info": "ndn testbed ca",
-  "max-validity-period": "86400",
-  "supported-challenges":
-  [
-      { "challenge": "PIN" }
-  ]
-}
\ No newline at end of file
diff --git a/tests/unit-tests/ca.conf.test3 b/tests/unit-tests/ca.conf.test3
deleted file mode 100644
index b330134..0000000
--- a/tests/unit-tests/ca.conf.test3
+++ /dev/null
@@ -1,9 +0,0 @@
-{
-  "ca-prefix": "/ndn",
-  "ca-info": "ndn testbed ca",
-  "max-validity-period": "86400",
-  "supported-challenges":
-  [
-      { "challenge": "ABC" }
-  ]
-}
\ No newline at end of file
diff --git a/tests/unit-tests/client-config.t.cpp b/tests/unit-tests/client-config.t.cpp
deleted file mode 100644
index f23f502..0000000
--- a/tests/unit-tests/client-config.t.cpp
+++ /dev/null
@@ -1,89 +0,0 @@
-/* -*- 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 "client-config.hpp"
-#include "test-common.hpp"
-
-namespace ndn {
-namespace ndncert {
-namespace tests {
-
-BOOST_AUTO_TEST_SUITE(TestClientConfig)
-
-BOOST_AUTO_TEST_CASE(ReadConfigFile)
-{
-  ClientConfig config;
-  config.load("tests/unit-tests/client.conf.test");
-  BOOST_CHECK_EQUAL(config.m_caItems.size(), 2);
-
-  const auto& item = config.m_caItems.front();
-  BOOST_CHECK_EQUAL(item.m_caPrefix, "/ndn/edu/ucla");
-  BOOST_CHECK_EQUAL(item.m_caInfo, "UCLA's ceritificate authority, located in BH4805.");
-  BOOST_CHECK_EQUAL(item.m_probe, "email");
-  BOOST_CHECK_EQUAL(item.m_anchor.getName(),
-                    "/ndn/site1/KEY/%11%BC%22%F4c%15%FF%17/self/%FD%00%00%01Y%C8%14%D9%A5");
-
-  BOOST_CHECK_EQUAL(config.m_localNdncertAnchor, "/usr/local/etc/ndncert/anchor.key");
-}
-
-BOOST_AUTO_TEST_CASE(ReadNonexistConfigFile)
-{
-  ClientConfig config;
-  BOOST_CHECK_THROW(config.load("tests/unit-tests/nonexist"), ClientConfig::Error);
-}
-
-BOOST_AUTO_TEST_CASE(ReadConfigFileWithInvalidCert)
-{
-  ClientConfig config;
-  BOOST_CHECK_THROW(config.load("tests/unit-tests/client.conf.test2"), ClientConfig::Error);
-}
-
-BOOST_AUTO_TEST_CASE(ReadConfigFileWithoutCaPrefix)
-{
-  ClientConfig config;
-  BOOST_CHECK_THROW(config.load("tests/unit-tests/client.conf.test3"), ClientConfig::Error);
-}
-
-BOOST_AUTO_TEST_CASE(AddAndRemoveCaItem)
-{
-  ClientConfig config;
-  config.load("tests/unit-tests/client.conf.test");
-
-  ClientCaItem item;
-  item.m_caPrefix = Name("/test");
-  item.m_caInfo = "test";
-  item.m_probe = "test";
-
-  config.m_caItems.push_back(item);
-  BOOST_CHECK_EQUAL(config.m_caItems.size(), 3);
-  auto lastItem = config.m_caItems.back();
-  BOOST_CHECK_EQUAL(lastItem.m_caPrefix, "/test");
-
-  config.removeCaItem(Name("/test"));
-  BOOST_CHECK_EQUAL(config.m_caItems.size(), 2);
-  lastItem = config.m_caItems.back();
-  BOOST_CHECK_EQUAL(lastItem.m_caPrefix, "/ndn/edu/ucla/zhiyi");
-}
-
-BOOST_AUTO_TEST_SUITE_END() // TestClientConfig
-
-} // namespace tests
-} // namespace ndncert
-} // namespace ndn
diff --git a/tests/unit-tests/client-module.t.cpp b/tests/unit-tests/client-module.t.cpp
index 159eaaf..e8dccaf 100644
--- a/tests/unit-tests/client-module.t.cpp
+++ b/tests/unit-tests/client-module.t.cpp
@@ -32,26 +32,32 @@
 BOOST_AUTO_TEST_CASE(ClientModuleInitialize)
 {
   ClientModule client(m_keyChain);
-  client.getClientConf().load("tests/unit-tests/client.conf.test");
+  client.getClientConf().load("tests/unit-tests/config-files/config-client-1");
   BOOST_CHECK_EQUAL(client.getClientConf().m_caItems.size(), 2);
 }
 
 BOOST_AUTO_TEST_CASE(Probe)
 {
   ClientModule client(m_keyChain);
-  client.getClientConf().load("tests/unit-tests/client.conf.test");
+  client.getClientConf().load("tests/unit-tests/config-files/config-client-1");
 
   auto identity = addIdentity(Name("/site"));
   auto key = identity.getDefaultKey();
   auto cert = key.getDefaultCertificate();
 
-  ClientCaItem item;
-  item.m_probe = "email:uid:name";
+  CaConfigItem item;
+  item.m_probeParameterKeys.push_back("email");
+  item.m_probeParameterKeys.push_back("uid");
+  item.m_probeParameterKeys.push_back("name");
   item.m_caPrefix = Name("/site");
-  item.m_anchor = cert;
+  item.m_cert = std::make_shared<security::v2::Certificate>(cert);
   client.getClientConf().m_caItems.push_back(item);
 
-  auto firstInterest = client.generateProbeInterest(item, "zhiyi@cs.ucla.edu:987654321:Zhiyi Zhang");
+  std::vector<std::tuple<std::string, std::string>> probeParams;
+  probeParams.push_back(std::make_tuple("email", "zhiyi@cs.ucla.edu"));
+  probeParams.push_back(std::make_tuple("uid", "987654321"));
+  probeParams.push_back(std::make_tuple("name", "Zhiyi Zhang"));
+  auto firstInterest = client.generateProbeInterest(item, std::move(probeParams));
   BOOST_CHECK(firstInterest->getName().at(-1).isParametersSha256Digest());
   // ignore the last name component (ParametersSha256Digest)
   BOOST_CHECK_EQUAL(firstInterest->getName().getPrefix(-1), "/site/CA/PROBE");
@@ -62,16 +68,16 @@
 // BOOST_AUTO_TEST_CASE(GenProbeRequestJson)
 // {
 //   ClientModule client(m_keyChain);
-//   client.getClientConf().load("tests/unit-tests/client.conf.test");
+//   client.getClientConf().load("tests/unit-tests/config-files/config-client-1");
 
 //   auto identity = addIdentity(Name("/site"));
 //   auto key = identity.getDefaultKey();
 //   auto cert = key.getDefaultCertificate();
 
-//   ClientCaItem item;
+//   CaConfigItem item;
 //   item.m_probe = "email:uid:name";
 //   item.m_caPrefix = Name("/site");
-//   item.m_anchor = cert;
+//   item.m_cert = std::make_shared<security::v2::Certificate>(cert);
 //   client.getClientConf().m_caItems.push_back(item);
 
 //   auto interestPacket = client.genProbeRequestJson(item, "yufeng@ucla.edu:123456789:Yufeng Zhang");
diff --git a/tests/unit-tests/client.conf.test2 b/tests/unit-tests/client.conf.test2
deleted file mode 100644
index f3d5630..0000000
--- a/tests/unit-tests/client.conf.test2
+++ /dev/null
@@ -1,18 +0,0 @@
-{
-  "ca-list":
-  [
-    {
-        "ca-prefix": "/ndn/edu/ucla",
-        "ca-info": "UCLA's ceritificate authority, located in BH4805.",
-        "probe": "email",
-        "certificate": "ANuZG4IBXNpdGUxCANLRVkICBG8IvRjFf8XCARzZWxmCAn9AAABWcgU2aUUCRgBAhkEADbugBX9AU8wggFLMIIBAwYHKoZIzj0CATCB9wIBATAsBgcqhkjOPQEBAiEA/////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-info": "Zhiyi's own ceritificate authority",
-        "probe": "email",
-        "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/client.conf.test3 b/tests/unit-tests/client.conf.test3
deleted file mode 100644
index 257850a..0000000
--- a/tests/unit-tests/client.conf.test3
+++ /dev/null
@@ -1,17 +0,0 @@
-{
-  "ca-list":
-  [
-    {
-        "ca-info": "UCLA's ceritificate authority, located in BH4805.",
-        "probe": "email",
-        "certificate": "ANuZG4IBXNpdGUxCANLRVkICBG8IvRjFf8XCARzZWxmCAn9AAABWcgU2aUUCRgBAhkEADbugBX9AU8wggFLMIIBAwYHKoZIzj0CATCB9wIBATAsBgcqhkjOPQEBAiEA/////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-info": "Zhiyi's own ceritificate authority",
-        "probe": "email",
-        "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/ca.conf.test b/tests/unit-tests/config-files/config-ca-1
similarity index 74%
rename from tests/unit-tests/ca.conf.test
rename to tests/unit-tests/config-files/config-ca-1
index bf795fd..8ea5d38 100644
--- a/tests/unit-tests/ca.conf.test
+++ b/tests/unit-tests/config-files/config-ca-1
@@ -1,7 +1,8 @@
 {
   "ca-prefix": "/ndn",
-  "max-validity-period": "86400",
   "ca-info": "ndn testbed ca",
+  "max-validity-period": "864000",
+  "max-suffix-length": 3,
   "probe-parameters":
   [
       { "probe-parameter-key": "full name" }
@@ -9,6 +10,5 @@
   "supported-challenges":
   [
       { "challenge": "PIN" }
-  ],
-  "max-suffix-length": 3
+  ]
 }
\ No newline at end of file
diff --git a/tests/unit-tests/config-files/config-ca-2 b/tests/unit-tests/config-files/config-ca-2
new file mode 100644
index 0000000..9b7c2bd
--- /dev/null
+++ b/tests/unit-tests/config-files/config-ca-2
@@ -0,0 +1,9 @@
+{
+  "ca-prefix": "/ndn",
+  "ca-info": "missing max validity period, max suffix length, and probe",
+  "supported-challenges":
+  [
+      { "challenge": "pin" },
+      { "challenge": "email" }
+  ]
+}
\ No newline at end of file
diff --git a/tests/unit-tests/config-files/config-ca-3 b/tests/unit-tests/config-files/config-ca-3
new file mode 100644
index 0000000..7946ca0
--- /dev/null
+++ b/tests/unit-tests/config-files/config-ca-3
@@ -0,0 +1,10 @@
+{
+  "ca-prefix": "/ndn",
+  "ca-info": "missing challenge",
+  "max-validity-period": "86400",
+  "max-suffix-length": 3,
+  "probe-parameters":
+  [
+      { "probe-parameter-key": "full name" }
+  ]
+}
\ No newline at end of file
diff --git a/tests/unit-tests/ca.conf.test b/tests/unit-tests/config-files/config-ca-4
similarity index 61%
copy from tests/unit-tests/ca.conf.test
copy to tests/unit-tests/config-files/config-ca-4
index bf795fd..98d6c92 100644
--- a/tests/unit-tests/ca.conf.test
+++ b/tests/unit-tests/config-files/config-ca-4
@@ -1,14 +1,14 @@
 {
   "ca-prefix": "/ndn",
+  "ca-info": "unsupported challenge",
   "max-validity-period": "86400",
-  "ca-info": "ndn testbed ca",
+  "max-suffix-length": 3,
   "probe-parameters":
   [
       { "probe-parameter-key": "full name" }
   ],
   "supported-challenges":
   [
-      { "challenge": "PIN" }
-  ],
-  "max-suffix-length": 3
+      { "challenge": "something" }
+  ]
 }
\ No newline at end of file
diff --git a/tests/unit-tests/client.conf.test b/tests/unit-tests/config-files/config-client-1
similarity index 86%
rename from tests/unit-tests/client.conf.test
rename to tests/unit-tests/config-files/config-client-1
index 903c2bd..20daad1 100644
--- a/tests/unit-tests/client.conf.test
+++ b/tests/unit-tests/config-files/config-client-1
@@ -3,16 +3,18 @@
   [
     {
         "ca-prefix": "/ndn/edu/ucla",
-        "ca-info": "UCLA's ceritificate authority, located in BH4805.",
-        "probe": "email",
+        "ca-info": "ndn testbed ca",
+        "max-validity-period": "864000",
+        "max-suffix-length": 3,
+        "probe-parameters":
+        [
+          { "probe-parameter-key": "email" }
+        ],
         "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-info": "Zhiyi's own ceritificate authority",
-        "probe": "email",
         "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/config-files/config-client-2 b/tests/unit-tests/config-files/config-client-2
new file mode 100644
index 0000000..4eb3958
--- /dev/null
+++ b/tests/unit-tests/config-files/config-client-2
@@ -0,0 +1,13 @@
+{
+  "ca-list":
+  [
+    {
+        "ca-prefix": "/ndn/edu/ucla",
+        "ca-info": "missing certificate",
+        "probe-parameters":
+        [
+          { "probe-parameter-key": "email" }
+        ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/tests/unit-tests/config-files/config-client-3 b/tests/unit-tests/config-files/config-client-3
new file mode 100644
index 0000000..66ec4b6
--- /dev/null
+++ b/tests/unit-tests/config-files/config-client-3
@@ -0,0 +1,13 @@
+{
+  "ca-list":
+  [
+    {
+        "ca-info": "missing ca prefix",
+        "probe-parameters":
+        [
+          { "probe-parameter-key": "email" }
+        ],
+        "certificate": "ANuZG4IBXNpdGUxCANLRVkICBG8IvRjFf8XCARzZWxmCAn9AAABWcgU2aUUCRgBAhkEADbugBX9AU8wggFLMIIBAwYHKoZIzj0CATCB9wIBATAsBgcqhkjOPQEBAiEA/////wAAAAEAAAAAAAAAAAAAAAD///////////////8wWwQg/////wAAAAEAAAAAAAAAAAAAAAD///////////////wEIFrGNdiqOpPns+u9VXaYhrxlHQawzFOw9jvOPD4n0mBLAxUAxJ02CIbnBJNqZnjhE50mt4GffpAEQQRrF9Hy4SxCR/i85uVjpEDydwN9gS3rM6D0oTlF2JjClk/jQuL+Gn+bjufrSnwPnhYrzjNXazFezsu2QGg3v1H1AiEA/////wAAAAD//////////7zm+q2nF56E87nKwvxjJVECAQEDQgAES9Cb9iANUNYmwt5bjwNW1mZgjzIkDJb6FTCdiYWnkMMIVxh2YDllphoWDEAPS6kqJczzCuhnGYpZCp9tTaYKGxZMGwEDHB0HGwgDbmRuCAVzaXRlMQgDS0VZCAgRvCL0YxX/F/0A/Sb9AP4PMTk3MDAxMDFUMDAwMDAw/QD/DzIwMzcwMTE3VDIxMjg0NhdIMEYCIQDXkR1hF3GiP7yLXq+0JBJfi9QC+hhAu/1Bykx+MWz6RAIhANwelBTxxZr2C5bD15mjfhWudK4I1tOb4b/9xWCHyM7F"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/tests/unit-tests/configuration.t.cpp b/tests/unit-tests/configuration.t.cpp
new file mode 100644
index 0000000..2c0ca02
--- /dev/null
+++ b/tests/unit-tests/configuration.t.cpp
@@ -0,0 +1,145 @@
+/* -*- 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 "configuration.hpp"
+#include "protocol-detail/info.hpp"
+#include "test-common.hpp"
+
+namespace ndn {
+namespace ndncert {
+namespace tests {
+
+BOOST_FIXTURE_TEST_SUITE(TestConfig, IdentityManagementFixture)
+
+BOOST_AUTO_TEST_CASE(CAConfigFile)
+{
+  CaConfig config;
+  config.load("tests/unit-tests/config-files/config-ca-1");
+  BOOST_CHECK_EQUAL(config.m_caItem.m_caPrefix, "/ndn");
+  BOOST_CHECK_EQUAL(config.m_caItem.m_caInfo, "ndn testbed ca");
+  BOOST_CHECK_EQUAL(config.m_caItem.m_maxValidityPeriod, time::seconds(864000));
+  BOOST_CHECK_EQUAL(*config.m_caItem.m_maxSuffixLength, 3);
+  BOOST_CHECK_EQUAL(config.m_caItem.m_probeParameterKeys.size(), 1);
+  BOOST_CHECK_EQUAL(config.m_caItem.m_probeParameterKeys.front(), "full name");
+  BOOST_CHECK_EQUAL(config.m_caItem.m_supportedChallenges.size(), 1);
+  BOOST_CHECK_EQUAL(config.m_caItem.m_supportedChallenges.front(), "pin");
+
+  config.load("tests/unit-tests/config-files/config-ca-2");
+  BOOST_CHECK_EQUAL(config.m_caItem.m_caPrefix, "/ndn");
+  BOOST_CHECK_EQUAL(config.m_caItem.m_caInfo, "missing max validity period, max suffix length, and probe");
+  BOOST_CHECK_EQUAL(config.m_caItem.m_maxValidityPeriod, time::seconds(86400));
+  BOOST_CHECK(!config.m_caItem.m_maxSuffixLength);
+  BOOST_CHECK_EQUAL(config.m_caItem.m_probeParameterKeys.size(), 0);
+  BOOST_CHECK_EQUAL(config.m_caItem.m_supportedChallenges.size(), 0);
+  BOOST_CHECK_EQUAL(config.m_caItem.m_supportedChallenges.front(), "pin");
+  BOOST_CHECK_EQUAL(config.m_caItem.m_supportedChallenges.back(), "email");
+}
+
+BOOST_AUTO_TEST_CASE(CAConfigFileWithErrors)
+{
+  CaConfig config;
+  // nonexistent file
+  BOOST_CHECK_THROW(config.load("tests/unit-tests/config-files/Nonexist"), std::runtime_error);
+  // missing challenge
+  BOOST_CHECK_THROW(config.load("tests/unit-tests/config-files/config-ca-3"), std::runtime_error);
+  // unsupported challenge
+  BOOST_CHECK_THROW(config.load("tests/unit-tests/config-files/config-ca-4"), std::runtime_error);
+}
+
+BOOST_AUTO_TEST_CASE(ClientConfigFile)
+{
+  ClientConfig config;
+  config.load("tests/unit-tests/config-files/config-client-1");
+  BOOST_CHECK_EQUAL(config.m_caItems.size(), 2);
+
+  auto& config1 = config.m_caItems.front();
+  BOOST_CHECK_EQUAL(config1.m_caPrefix, "/ndn");
+  BOOST_CHECK_EQUAL(config1.m_caInfo, "ndn testbed ca");
+  BOOST_CHECK_EQUAL(config1.m_maxValidityPeriod, time::seconds(864000));
+  BOOST_CHECK_EQUAL(*config1.m_maxSuffixLength, 3);
+  BOOST_CHECK_EQUAL(config1.m_probeParameterKeys.size(), 1);
+  BOOST_CHECK_EQUAL(config1.m_probeParameterKeys.front(), "email");
+  BOOST_CHECK_EQUAL(config1.m_cert->getName(),
+                    "/ndn/site1/KEY/%11%BC%22%F4c%15%FF%17/self/%FD%00%00%01Y%C8%14%D9%A5");
+
+  auto& config2 = config.m_caItems.front();
+  BOOST_CHECK_EQUAL(config2.m_caPrefix, "/ndn");
+  BOOST_CHECK_EQUAL(config2.m_caInfo, "ndn testbed ca");
+  BOOST_CHECK_EQUAL(config2.m_maxValidityPeriod, time::seconds(86400));
+  BOOST_CHECK(!config2.m_maxSuffixLength);
+  BOOST_CHECK_EQUAL(config2.m_probeParameterKeys.size(), 0);
+  BOOST_CHECK_EQUAL(config2.m_cert->getName(),
+                    "/ndn/site1/KEY/%11%BC%22%F4c%15%FF%17/self/%FD%00%00%01Y%C8%14%D9%A5");
+}
+
+BOOST_AUTO_TEST_CASE(ClientConfigFileWithErrors)
+{
+  ClientConfig config;
+  // nonexistent file
+  BOOST_CHECK_THROW(config.load("tests/unit-tests/config-files/Nonexist"), std::runtime_error);
+  // missing certificate
+  BOOST_CHECK_THROW(config.load("tests/unit-tests/config-files/config-client-2"), std::runtime_error);
+  // missing ca prefix
+  BOOST_CHECK_THROW(config.load("tests/unit-tests/config-files/config-client-3"), std::runtime_error);
+}
+
+BOOST_AUTO_TEST_CASE(ClientConfigFileAddAndRemoveCaItem)
+{
+  ClientConfig config;
+  config.load("tests/unit-tests/config-files/config-client-1");
+
+  CaConfigItem item;
+  item.m_caPrefix = Name("/test");
+  item.m_caInfo = "test";
+
+  config.m_caItems.push_back(item);
+  BOOST_CHECK_EQUAL(config.m_caItems.size(), 3);
+  auto lastItem = config.m_caItems.back();
+  BOOST_CHECK_EQUAL(lastItem.m_caPrefix, "/test");
+
+  config.removeCaItem(Name("/test"));
+  BOOST_CHECK_EQUAL(config.m_caItems.size(), 2);
+  lastItem = config.m_caItems.back();
+  BOOST_CHECK_EQUAL(lastItem.m_caPrefix, "/ndn/edu/ucla/zhiyi");
+}
+
+BOOST_AUTO_TEST_CASE(InfoEncodingDecoding)
+{
+  CaConfig config;
+  config.load("tests/unit-tests/config-files/config-ca-1");
+
+  const auto& identity = addIdentity("/test");
+  const auto& cert = identity.getDefaultKey().getDefaultCertificate();
+  auto encoded = INFO::encodeDataContent(config.m_caItem, cert);
+  auto decoded = INFO::decodeDataContentToCaProfile(encoded);
+  BOOST_CHECK_EQUAL(config.m_caItem.m_caPrefix, decoded.m_caPrefix);
+  BOOST_CHECK_EQUAL(config.m_caItem.m_caInfo, decoded.m_caInfo);
+  BOOST_CHECK_EQUAL(config.m_caItem.m_maxValidityPeriod, decoded.m_maxValidityPeriod);
+  BOOST_CHECK_EQUAL(*config.m_caItem.m_maxSuffixLength, *decoded.m_maxSuffixLength);
+  BOOST_CHECK_EQUAL(config.m_caItem.m_probeParameterKeys.size(), decoded.m_probeParameterKeys.size());
+  BOOST_CHECK_EQUAL(config.m_caItem.m_probeParameterKeys.front(), decoded.m_probeParameterKeys.front());
+  BOOST_CHECK_EQUAL(cert.wireEncode(), decoded.m_cert->wireEncode());
+}
+
+BOOST_AUTO_TEST_SUITE_END()  // TestCaConfig
+
+}  // namespace tests
+}  // namespace ndncert
+}  // namespace ndn
diff --git a/tools/ndncert-client.cpp b/tools/ndncert-client.cpp
index 73ae8c3..8e2da5e 100644
--- a/tools/ndncert-client.cpp
+++ b/tools/ndncert-client.cpp
@@ -58,21 +58,19 @@
   }
 }
 
-static std::list<std::string>
-captureParams(const std::vector<std::string>& requirement)
+static std::vector<std::tuple<std::string, std::string>>
+captureParams(const std::list<std::string>& requirement)
 {
-  std::list<std::string> results;
+  std::vector<std::tuple<std::string, std::string>> results;
   for (const auto& item : requirement) {
     std::cerr << "Please provide the argument: " << item << " : " << std::endl;
-    std::string tempParam;
-    getline(std::cin, tempParam);
-    results.push_back(tempParam);
+    std::string captured;
+    getline(std::cin, captured);
+    results.push_back(std::make_tuple(item, captured));
   }
   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 << " : " << *it1 << std::endl;
+  for (const auto& item : results) {
+    std::cerr << std::get<0>(item) << " : " << std::get<1>(item) << std::endl;
   }
   return results;
 }
@@ -203,12 +201,12 @@
     std::cerr << "The fetched CA information cannot be trusted because its integrity is broken" << std::endl;
     return;
   }
-  auto caItem = INFO::decodeClientConfigFromContent(contentBlock);
+  auto caItem = INFO::decodeDataContentToCaProfile(contentBlock);
 
   std::cerr << "Will use a new trust anchor, please double check the identity info: \n"
             << "This trust anchor information is signed by " << reply.getSignature().getKeyLocator()
             << std::endl
-            << "The certificate is " << caItem.m_anchor << std::endl
+            << "The certificate is " << *caItem.m_cert << std::endl
             << "Do you trust the information? Type in YES or NO" << std::endl;
 
   std::string answer;
@@ -259,7 +257,7 @@
               << "Introduction: " << item.m_caInfo << "\n"
               << "***************************************\n";
   }
-  std::vector<ClientCaItem> caVector{std::begin(caList), std::end(caList)};
+  std::vector<CaConfigItem> 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";
@@ -290,19 +288,10 @@
     }
     auto targetCaItem = caVector[caIndex];
 
-    if (targetCaItem.m_probe != "") {
+    if (!targetCaItem.m_probeParameterKeys.empty()) {
       std::cerr << "Step " << nStep++ << ": Please provide information for name assignment" << std::endl;
-      std::vector<std::string> probeFields = ClientModule::parseProbeComponents(targetCaItem.m_probe);
-      std::string redo = "";
-      std::list<std::string> capturedParams;
-      capturedParams = captureParams(probeFields);
-      std::string probeInfo;
-      for (const auto& item : capturedParams) {
-        probeInfo += item;
-        probeInfo += ":";
-      }
-      probeInfo = probeInfo.substr(0, probeInfo.size() - 1);
-      face.expressInterest(*client.generateProbeInterest(targetCaItem, probeInfo),
+      auto capturedParams = captureParams(targetCaItem.m_probeParameterKeys);
+      face.expressInterest(*client.generateProbeInterest(targetCaItem, std::move(capturedParams)),
                            bind(&probeCb, _2), bind(&onNackCb), bind(&timeoutCb));
     }
     else {