Fixed several ndncert-client bugs and use lower case Challenge ID

refs: #4962
Change-Id: Id10dcc15cb718d6a55f4657884b2c6be3f653867
diff --git a/src/ca-config.cpp b/src/ca-config.cpp
index b8b0d59..3165b2d 100644
--- a/src/ca-config.cpp
+++ b/src/ca-config.cpp
@@ -66,7 +66,7 @@
   std::list<std::string> result;
   auto it = section.begin();
   for (; it != section.end(); it++) {
-    result.push_back(it->second.get<std::string>("type"));
+    result.push_back(boost::algorithm::to_lower_copy(it->second.get<std::string>("type")));
   }
   return result;
 }
diff --git a/src/ca-module.cpp b/src/ca-module.cpp
index 20761e0..89bf8a7 100644
--- a/src/ca-module.cpp
+++ b/src/ca-module.cpp
@@ -189,11 +189,9 @@
   }
   // generate salt for HKDF
   auto saltInt = random::generateSecureWord64();
-  uint8_t salt[sizeof(saltInt)];
-  std::memcpy(salt, &saltInt, sizeof(saltInt));
   // hkdf
   hkdf(m_ecdh.context->sharedSecret, m_ecdh.context->sharedSecretLen,
-       salt, sizeof(saltInt), m_aesKey, 32);
+       (uint8_t*)&saltInt, sizeof(saltInt), m_aesKey, sizeof(m_aesKey));
 
   // parse certificate request
   std::string certRequestStr = parameterJson.get(JSON_CLIENT_CERT_REQ, "");
@@ -319,7 +317,7 @@
   // decrypt the parameters
   Buffer paramJsonPayload;
   try {
-    paramJsonPayload = parseEncBlock(m_aesKey, 32,
+    paramJsonPayload = parseEncBlock(m_aesKey, sizeof(m_aesKey),
                                      request.getApplicationParameters());
   }
   catch (const std::exception& e) {
@@ -403,7 +401,7 @@
   std::stringstream ss2;
   boost::property_tree::write_json(ss2, contentJson);
   auto payload = ss2.str();
-  auto contentBlock = genEncBlock(tlv::Content, m_aesKey, 32,
+  auto contentBlock = genEncBlock(tlv::Content, m_aesKey, sizeof(m_aesKey),
                                   (const uint8_t*)payload.c_str(), payload.size());
   result.setContent(contentBlock);
   m_keyChain.sign(result, signingByIdentity(m_config.m_caName));
diff --git a/src/challenge-module/challenge-email.cpp b/src/challenge-module/challenge-email.cpp
index e475b24..b22ec93 100644
--- a/src/challenge-module/challenge-email.cpp
+++ b/src/challenge-module/challenge-email.cpp
@@ -28,7 +28,7 @@
 
 _LOG_INIT(ndncert.ChallengeEmail);
 
-NDNCERT_REGISTER_CHALLENGE(ChallengeEmail, "Email");
+NDNCERT_REGISTER_CHALLENGE(ChallengeEmail, "email");
 
 const std::string ChallengeEmail::NEED_CODE = "need-code";
 const std::string ChallengeEmail::WRONG_CODE = "wrong-code";
@@ -39,7 +39,7 @@
 ChallengeEmail::ChallengeEmail(const std::string& scriptPath,
                                const size_t& maxAttemptTimes,
                                const time::seconds secretLifetime)
-  : ChallengeModule("Email")
+  : ChallengeModule("email")
   , m_sendEmailScript(scriptPath)
   , m_maxAttemptTimes(maxAttemptTimes)
   , m_secretLifetime(secretLifetime)
diff --git a/src/challenge-module/challenge-pin.cpp b/src/challenge-module/challenge-pin.cpp
index 7de32f0..9534b21 100644
--- a/src/challenge-module/challenge-pin.cpp
+++ b/src/challenge-module/challenge-pin.cpp
@@ -27,14 +27,14 @@
 
 _LOG_INIT(ndncert.challenge-pin);
 
-NDNCERT_REGISTER_CHALLENGE(ChallengePin, "PIN");
+NDNCERT_REGISTER_CHALLENGE(ChallengePin, "pin");
 
 const std::string ChallengePin::NEED_CODE = "need-code";
 const std::string ChallengePin::WRONG_CODE = "wrong-code";
 const std::string ChallengePin::JSON_PIN_CODE = "pin-code";
 
 ChallengePin::ChallengePin(const size_t& maxAttemptTimes, const time::seconds& secretLifetime)
-  : ChallengeModule("PIN")
+  : ChallengeModule("pin")
   , m_secretLifetime(secretLifetime)
   , m_maxAttemptTimes(maxAttemptTimes)
 {
diff --git a/src/client-module.cpp b/src/client-module.cpp
index 28e535b..12d835d 100644
--- a/src/client-module.cpp
+++ b/src/client-module.cpp
@@ -194,12 +194,11 @@
   const auto& peerKeyBase64Str = contentJson.get<std::string>(JSON_CA_ECDH, "");
   const auto& saltStr = contentJson.get<std::string>(JSON_CA_SALT, "");
   uint64_t saltInt = std::stoull(saltStr);
-  uint8_t salt[sizeof(saltInt)];
-  std::memcpy(salt, &saltInt, sizeof(saltInt));
   m_ecdh.deriveSecret(peerKeyBase64Str);
 
   // HKDF
-  hkdf(m_ecdh.context->sharedSecret, m_ecdh.context->sharedSecretLen, salt, sizeof(saltInt), m_aesKey, 32);
+  hkdf(m_ecdh.context->sharedSecret, m_ecdh.context->sharedSecretLen,
+       (uint8_t*)&saltInt, sizeof(saltInt), m_aesKey, sizeof(m_aesKey));
 
   // update state
   m_status = contentJson.get<int>(JSON_CA_STATUS);
@@ -228,7 +227,7 @@
   std::stringstream ss;
   boost::property_tree::write_json(ss, paramJson);
   auto payload = ss.str();
-  auto paramBlock = genEncBlock(tlv::ApplicationParameters, m_aesKey, 32,
+  auto paramBlock = genEncBlock(tlv::ApplicationParameters, m_aesKey, sizeof(m_aesKey),
                                 (const uint8_t*)payload.c_str(), payload.size());
   interest->setApplicationParameters(paramBlock);
 
@@ -243,7 +242,7 @@
     _LOG_ERROR("Cannot verify data signature from " << m_ca.m_caName.toUri());
     return;
   }
-  auto result = parseEncBlock(m_aesKey, 32, reply.getContent());
+  auto result = parseEncBlock(m_aesKey, sizeof(m_aesKey), reply.getContent());
   std::string payload((const char*)result.data(), result.size());
   std::istringstream ss(payload);
   JsonSection contentJson;
diff --git a/tests/unit-tests/challenge-email.t.cpp b/tests/unit-tests/challenge-email.t.cpp
index e67397e..398df97 100644
--- a/tests/unit-tests/challenge-email.t.cpp
+++ b/tests/unit-tests/challenge-email.t.cpp
@@ -31,7 +31,7 @@
 BOOST_AUTO_TEST_CASE(ChallengeType)
 {
   ChallengeEmail challenge;
-  BOOST_CHECK_EQUAL(challenge.CHALLENGE_TYPE, "Email");
+  BOOST_CHECK_EQUAL(challenge.CHALLENGE_TYPE, "email");
 }
 
 BOOST_AUTO_TEST_CASE(EmailAddressChecker)
@@ -60,7 +60,7 @@
   BOOST_CHECK(request.m_remainingTime != 0);
   BOOST_CHECK(request.m_remainingTries != 0);
   BOOST_CHECK(request.m_challengeTp != "");
-  BOOST_CHECK_EQUAL(request.m_challengeType, "Email");
+  BOOST_CHECK_EQUAL(request.m_challengeType, "email");
 
   std::string line = "";
   std::string delimiter = " ";
@@ -136,7 +136,7 @@
   JsonSection json;
   json.put(ChallengeEmail::JSON_CODE, "4567");
   CertificateRequest request(Name("/ndn/site1"), "123", STATUS_CHALLENGE, ChallengeEmail::NEED_CODE,
-                             "Email", time::toIsoString(time::system_clock::now()), 3600, 3, json, cert);
+                             "email", time::toIsoString(time::system_clock::now()), 3600, 3, json, cert);
 
   JsonSection requestJson;
   requestJson.put(ChallengeEmail::JSON_CODE, "7890");
diff --git a/tests/unit-tests/challenge-pin.t.cpp b/tests/unit-tests/challenge-pin.t.cpp
index 35466ff..adac6b9 100644
--- a/tests/unit-tests/challenge-pin.t.cpp
+++ b/tests/unit-tests/challenge-pin.t.cpp
@@ -31,7 +31,7 @@
 BOOST_AUTO_TEST_CASE(ChallengeType)
 {
   ChallengePin challenge;
-  BOOST_CHECK_EQUAL(challenge.CHALLENGE_TYPE, "PIN");
+  BOOST_CHECK_EQUAL(challenge.CHALLENGE_TYPE, "pin");
 }
 
 BOOST_AUTO_TEST_CASE(OnChallengeRequestWithEmptyInfo)
@@ -46,7 +46,7 @@
 
   BOOST_CHECK_EQUAL(request.m_status, STATUS_CHALLENGE);
   BOOST_CHECK_EQUAL(request.m_challengeStatus, ChallengePin::NEED_CODE);
-  BOOST_CHECK_EQUAL(request.m_challengeType, "PIN");
+  BOOST_CHECK_EQUAL(request.m_challengeType, "pin");
 }
 
 BOOST_AUTO_TEST_CASE(OnChallengeRequestWithCode)
@@ -56,7 +56,7 @@
   auto cert = key.getDefaultCertificate();
   JsonSection secret;
   secret.add(ChallengePin::JSON_PIN_CODE, "12345");
-  CertificateRequest request(Name("/ndn/site1"), "123", STATUS_CHALLENGE, ChallengePin::NEED_CODE, "PIN",
+  CertificateRequest request(Name("/ndn/site1"), "123", STATUS_CHALLENGE, ChallengePin::NEED_CODE, "pin",
                              time::toIsoString(time::system_clock::now()), 3600, 3, secret, cert);
 
   JsonSection paramJson;
@@ -77,7 +77,7 @@
   auto cert = key.getDefaultCertificate();
   JsonSection secret;
   secret.add(ChallengePin::JSON_PIN_CODE, "12345");
-  CertificateRequest request(Name("/ndn/site1"), "123", STATUS_CHALLENGE, ChallengePin::NEED_CODE, "PIN",
+  CertificateRequest request(Name("/ndn/site1"), "123", STATUS_CHALLENGE, ChallengePin::NEED_CODE, "pin",
                              time::toIsoString(time::system_clock::now()), 3600, 3, secret, cert);
 
   JsonSection paramJson;
diff --git a/tools/ndncert-client.cpp b/tools/ndncert-client.cpp
index 66a81ef..2c47424 100644
--- a/tools/ndncert-client.cpp
+++ b/tools/ndncert-client.cpp
@@ -22,7 +22,6 @@
 #include "challenge-module.hpp"
 #include <iostream>
 #include <string>
-#include <algorithm>
 #include <boost/program_options/options_description.hpp>
 #include <boost/program_options/variables_map.hpp>
 #include <boost/program_options/parsers.hpp>
@@ -37,6 +36,7 @@
 Face face;
 security::v2::KeyChain keyChain;
 std::string challengeType;
+int validityPeriod = -1;
 ClientModule client(keyChain);
 
 static std::list<std::string>
@@ -79,6 +79,31 @@
 }
 
 static void
+captureValidityPeriod()
+{
+  if (validityPeriod <= 0) {
+    std::cerr << "Step " << nStep++
+              << ": Please type in your expected validity period of your certificate."
+              << " Type the number of hours (168 for week, 730 for month, 8760 for year). The CA may change the validity"
+              << " period if your expected period is too long." << std::endl;
+    std::string periodStr = "";
+    do {
+      getline(std::cin, periodStr);
+      try {
+        validityPeriod = std::stoi(periodStr);
+      }
+      catch (const std::exception& e) {
+        validityPeriod = -1;
+      }
+      if (validityPeriod > 0) {
+        break;
+      }
+      std::cerr << "Invalid period time. Please input the period again." << std::endl;
+    } while (true);
+  }
+}
+
+static void
 onNackCb()
 {
   std::cerr << "Got NACK\n";
@@ -111,50 +136,70 @@
   }
 
   auto challenge = ChallengeModule::createChallengeModule(challengeType);
-  auto requirement = challenge->getRequirementForChallenge(client.getApplicationStatus(), client.getChallengeStatus());
+  auto requirement = challenge->getRequirementForChallenge(client.getApplicationStatus(),
+                                                           client.getChallengeStatus());
   if (requirement.size() > 0) {
     std::cerr << "Step " << nStep++ << ": Please satisfy following instruction(s)\n";
     std::string redo = "";
     std::list<std::string> capturedParams;
     do {
       capturedParams = captureParams(requirement);
-      std::cerr << "If everything is right, please type in OK; otherwise, type in REDO" << std::endl;
+      std::cerr << "If everything is correct, please type in OK; otherwise, type in REDO" << std::endl;
       getline(std::cin, redo);
-      std::transform(redo.begin(), redo.end(), redo.begin(), ::toupper);
-    } while (redo == "REDO");
+      boost::algorithm::to_lower(redo);
+    } while (redo != "ok");
     auto it1 = capturedParams.begin();
     auto it2 = requirement.begin();
     for (; it1 != capturedParams.end() && it2 != requirement.end(); it1++, it2++) {
       it2->second.put("", *it1);
     }
   }
-  face.expressInterest(*client.generateChallengeInterest(
-                        challenge->genChallengeRequestJson(
-                                   client.getApplicationStatus(),
-                                   client.getChallengeStatus(),
-                                   requirement)),
-                       bind(&challengeCb, _2),
-                       bind(&onNackCb),
-                       bind(&timeoutCb));
+  face.expressInterest(*client.generateChallengeInterest(challenge->genChallengeRequestJson(client.getApplicationStatus(),
+                                                                                            client.getChallengeStatus(),
+                                                                                            requirement)),
+                       bind(&challengeCb, _2), bind(&onNackCb), bind(&timeoutCb));
 }
 
 static void
 newCb(const Data& reply)
 {
+  int challengeIndex = 0;
   auto challengeList = client.onNewResponse(reply);
-  std::cerr << "Step " << nStep++ << ": Please type in the challenge ID from the following challenges\n";
-  for (auto item : challengeList) {
-    std::cerr << "\t" << item << std::endl;
+  if (challengeList.size() < 1) {
+    std::cerr << "There is no available challenge provided by the CA. Exit" << std::endl;
+    return;
   }
-  std::string choice;
-  getline(std::cin, choice);
-
-  auto challenge = ChallengeModule::createChallengeModule(choice);
+  else if (challengeList.size() > 1) {
+    int count = 0;
+    std::string choice = "";
+    std::cerr << "Step " << nStep++ << ": Please type in the challenge index that you want to perform\n";
+    do {
+      count = 0;
+      for (auto item : challengeList) {
+        std::cerr << "\t" << count++ << " : "<< item << std::endl;
+      }
+      getline(std::cin, choice);
+      try {
+        challengeIndex = std::stoi(choice);
+      }
+      catch (const std::exception& e) {
+        challengeIndex = -1;
+      }
+      if (challengeIndex >= 0 && challengeIndex < count) {
+        break;
+      }
+      std::cerr << "Your input index is out of range. Please type in again." << std::endl;
+    } while (true);
+  }
+  auto it = challengeList.begin();
+  std::advance(it, challengeIndex);
+  unique_ptr<ChallengeModule> challenge = ChallengeModule::createChallengeModule(*it);
   if (challenge != nullptr) {
-    challengeType = choice;
+    challengeType = *it;
+    std::cerr << "The challenge has been selected: " << challengeType << std::endl;
   }
   else {
-    std::cerr << "Cannot recognize the specified challenge. Exit";
+    std::cerr << "Error. Cannot load selected Challenge Module. Exit." << std::endl;
     return;
   }
   auto requirement = challenge->getRequirementForChallenge(client.getApplicationStatus(),
@@ -165,24 +210,20 @@
     std::list<std::string> capturedParams;
     do {
       capturedParams = captureParams(requirement);
-      std::cerr << "If everything is right, please type in OK; otherwise, type in REDO" << std::endl;
+      std::cerr << "If everything is correct, please type in OK; otherwise, type in REDO" << std::endl;
       getline(std::cin, redo);
-      std::transform(redo.begin(), redo.end(), redo.begin(), ::toupper);
-    } while (redo == "REDO");
+      boost::algorithm::to_lower(redo);
+    } while (redo != "ok");
     auto it1 = capturedParams.begin();
     auto it2 = requirement.begin();
     for (; it1 != capturedParams.end() && it2 != requirement.end(); it1++, it2++) {
       it2->second.put("", *it1);
     }
   }
-  face.expressInterest(*client.generateChallengeInterest(
-                               challenge->genChallengeRequestJson(
-                                          client.getApplicationStatus(),
-                                          client.getChallengeStatus(),
-                                          requirement)),
-                       bind(&challengeCb, _2),
-                       bind(&onNackCb),
-                       bind(&timeoutCb));
+  face.expressInterest(*client.generateChallengeInterest(challenge->genChallengeRequestJson(client.getApplicationStatus(),
+                                                                                            client.getChallengeStatus(),
+                                                                                            requirement)),
+                       bind(&challengeCb, _2), bind(&onNackCb), bind(&timeoutCb));
 }
 
 static void
@@ -198,14 +239,14 @@
 
   std::string answer;
   getline(std::cin, answer);
-  std::transform(answer.begin(), answer.end(), answer.begin(), ::toupper);
-  if (answer == "YES") {
+  boost::algorithm::to_lower(answer);
+  if (answer == "yes") {
     client.onProbeInfoResponse(reply);
-    std::cerr << "You answered YES: new CA installed" << std::endl;
+    std::cerr << "You answered YES: new CA has been installed" << std::endl;
     startApplication();
   }
   else {
-    std::cerr << "New CA not installed" << std::endl;
+    std::cerr << "New CA will not be installed" << std::endl;
     return;
   }
 }
@@ -214,31 +255,13 @@
 probeCb(const Data& reply)
 {
   client.onProbeResponse(reply);
-  std::cerr << "Step " << nStep++
-            << ": Please type in your expected validity period of your certificate."
-            << " Type in a number in unit of hour. The CA may change the validity"
-            << " period if your expected period is too long." << std::endl;
-  std::string periodStr;
-  int hours = 0;
-  getline(std::cin, periodStr);
-  hours = std::stoi(periodStr);
-  while (hours <= 0) {
-    std::cerr << "Invalid period time: " << "Please input the period again." << std::endl;
-    getline(std::cin, periodStr);
-    try {
-      hours = std::stoi(periodStr);
-    }
-    catch (const std::exception& e) {
-      hours = -1;
-    }
-  }
+  captureValidityPeriod();
   auto probeToken = make_shared<Data>(reply);
   auto now = time::system_clock::now();
-  face.expressInterest(*client.generateNewInterest(now, now + time::hours(hours),
+  std::cerr << "The validity period of your certificate will be: " << validityPeriod << " hours" << std::endl;
+  face.expressInterest(*client.generateNewInterest(now, now + time::hours(validityPeriod),
                                                    Name(), probeToken),
-                       bind(&newCb, _2),
-                       bind(&onNackCb),
-                       bind(&timeoutCb));
+                       bind(&newCb, _2), bind(&onNackCb), bind(&timeoutCb));
 }
 
 static void
@@ -259,20 +282,28 @@
             << nStep++ << ": Please type in the CA INDEX that you want to apply"
             << " or type in NONE if your expected CA is not in the list\n";
 
-  std::string caIndexS, caIndexSUpper;
+  std::string caIndexS, caIndexSLower;
   getline(std::cin, caIndexS);
-  caIndexSUpper = caIndexS;
-  std::transform(caIndexSUpper.begin(), caIndexSUpper.end(), caIndexSUpper.begin(), ::toupper);
-  if (caIndexSUpper == "NONE") {
+  caIndexSLower = caIndexS;
+  boost::algorithm::to_lower(caIndexSLower);
+  if (caIndexSLower == "none") {
     std::cerr << "Step " << nStep << ": Please type in the CA Name\n";
     face.expressInterest(*client.generateProbeInfoInterest(Name(caIndexS)),
-                         bind(&probeInfoCb, _2),
-                         bind(&onNackCb),
-                         bind(&timeoutCb));
+                         bind(&probeInfoCb, _2), bind(&onNackCb), bind(&timeoutCb));
   }
   else {
-    int caIndex = std::stoi(caIndexS);
-    BOOST_ASSERT(caIndex <= count);
+    int caIndex;
+    try {
+      caIndex = std::stoi(caIndexS);
+    }
+    catch (const std::exception& e) {
+      std::cerr << "Your input is neither NONE nor a valid index. Exit" << std::endl;
+      return;
+    }
+    if (caIndex < 0 || caIndex >= count) {
+      std::cerr << "Your input is not an existing index. Exit" << std::endl;
+      return;
+    }
     auto targetCaItem = caVector[caIndex];
 
     if (targetCaItem.m_probe != "") {
@@ -282,10 +313,10 @@
       std::list<std::string> capturedParams;
       do {
         capturedParams = captureParams(probeFields);
-        std::cerr << "If everything is right, please type in OK; otherwise, type in REDO" << std::endl;
+        std::cerr << "If everything is correct, please type in OK; otherwise, type in REDO" << std::endl;
         getline(std::cin, redo);
-        std::transform(redo.begin(), redo.end(), redo.begin(), ::toupper);
-      } while (redo == "REDO");
+        boost::algorithm::to_lower(redo);
+      } while (redo != "ok");
       std::string probeInfo;
       for (const auto& item : capturedParams) {
         probeInfo += item;
@@ -293,27 +324,18 @@
       }
       probeInfo = probeInfo.substr(0, probeInfo.size() - 1);
       face.expressInterest(*client.generateProbeInterest(targetCaItem, probeInfo),
-                           bind(&probeCb, _2),
-                           bind(&onNackCb),
-                           bind(&timeoutCb));
+                           bind(&probeCb, _2), bind(&onNackCb), bind(&timeoutCb));
     }
     else {
       std::cerr << "Step " << nStep++ << ": Please type in the identity name you want to get (with CA prefix)\n";
       std::string identityNameStr;
       getline(std::cin, identityNameStr);
-      std::cerr << "Step "
-                << nStep++ << ": Please type in your expected validity period of your certificate."
-                << "Type in a number in unit of hour."
-                << " The CA may change the validity period if your expected period is too long.\n";
-      std::string periodStr;
-      getline(std::cin, periodStr);
-      int hours = std::stoi(periodStr);
+      captureValidityPeriod();
+      std::cerr << "The validity period of your certificate will be: " << validityPeriod << " hours" << std::endl;
       auto now = time::system_clock::now();
-      face.expressInterest(*client.generateNewInterest(now, now + time::hours(hours),
+      face.expressInterest(*client.generateNewInterest(now, now + time::hours(validityPeriod),
                                                        Name(identityNameStr)),
-                           bind(&newCb, _2),
-                           bind(&onNackCb),
-                           bind(&timeoutCb));
+                           bind(&newCb, _2), bind(&onNackCb), bind(&timeoutCb));
     }
   }
 }
@@ -324,10 +346,11 @@
 {
   namespace po = boost::program_options;
   std::string configFilePath = std::string(SYSCONFDIR) + "/ndncert/client.conf";
-  po::options_description description("General Usage\n ndncert-client [-h] [-f]\n");
+  po::options_description description("General Usage\n ndncert-client [-h] [-c] [-v]\n");
   description.add_options()
     ("help,h", "produce help message")
-    ("config-file,f", po::value<std::string>(&configFilePath), "config file name");
+    ("config-file,c", po::value<std::string>(&configFilePath), "config file name")
+    ("validity-period,v", po::value<int>(&validityPeriod)->default_value(-1), "the validity period of your certificate being requested");
   po::positional_options_description p;
 
   po::variables_map vm;