Prepare for testbed deployment: update ndncert-client

Change-Id: I0a84e2ebb913166b5fbdb16fdc0938862ba42a22
diff --git a/tools/ndncert-client.cpp b/tools/ndncert-client.cpp
index 30c7454..c99104a 100644
--- a/tools/ndncert-client.cpp
+++ b/tools/ndncert-client.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2017-2021, Regents of the University of California.
+ * Copyright (c) 2017-2022, Regents of the University of California.
  *
  * This file is part of ndncert, a certificate management system based on NDN.
  *
@@ -47,9 +47,13 @@
 runChallenge(const std::string& challengeType);
 
 static size_t nStep = 1;
+static const std::string defaultChallenge = "email";
 static ndn::Face face;
 static ndn::KeyChain keyChain;
 static std::shared_ptr<Request> requesterState;
+static std::shared_ptr<std::multimap<std::string, std::string>> capturedProbeParams;
+static Name newlyCreatedIdentityName;
+static Name newlyCreatedKeyName;
 
 static void
 captureParams(std::multimap<std::string, std::string>& requirement)
@@ -79,13 +83,14 @@
 }
 
 static int
-captureValidityPeriod()
+captureValidityPeriod(time::hours maxValidityPeriod)
 {
   std::cerr << "\n***************************************\n"
             << "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 reject your application if your expected period is too long." << std::endl;
+            << " The CA may reject your application if your expected period is too long."
+            << " The maximum validity period allowed by this CA is " << maxValidityPeriod << "."<< std::endl;
   size_t count = 0;
   while (true && count < 3) {
     std::string periodStr = "";
@@ -102,10 +107,65 @@
   exit(1);
 }
 
+static Name
+captureKeyName(ndn::security::pib::Identity& identity, ndn::security::pib::Key& defaultKey)
+{
+  size_t count = 0;
+  std::cerr << "***************************************\n"
+            << "Step " << nStep++ << ": KEY SELECTION" << std::endl;
+  for (const auto& key : identity.getKeys()) {
+    std::cerr << "> Index: " << count++ << std::endl
+              << ">> Key Name:";
+    if (key == defaultKey) {
+      std::cerr << "  +->* ";
+    }
+    else {
+      std::cerr << "  +->  ";
+    }
+    std::cerr << key.getName() << std::endl;
+  }
+
+  std::cerr << "Please type in the key's index that you want to certify or type in NEW if you want to certify a new key:\n";
+  std::string indexStr = "";
+  std::string indexStrLower = "";
+  size_t keyIndex;
+  getline(std::cin, indexStr);
+
+  indexStrLower = indexStr;
+  boost::algorithm::to_lower(indexStrLower);
+  if (indexStrLower == "new") {
+    auto newlyCreatedKey = keyChain.createKey(identity);
+    newlyCreatedKeyName = newlyCreatedKey.getName();
+    std::cerr << "New key generated: " << newlyCreatedKeyName << std::endl;
+    return newlyCreatedKeyName;
+  }
+  else {
+    try {
+      keyIndex = std::stoul(indexStr);
+    }
+    catch (const std::exception&) {
+      std::cerr << "Your input is neither NEW nor a valid index. Exit" << std::endl;
+      exit(1);
+    }
+
+    if (keyIndex >= count) {
+      std::cerr << "Your input is not an existing index. Exit" << std::endl;
+      exit(1);
+    }
+    else {
+      auto itemIterator = identity.getKeys().begin();
+      std::advance(itemIterator, keyIndex);
+      auto targetKeyItem = *itemIterator;
+      return targetKeyItem.getName();
+    }
+  }
+}
+
 static void
 onNackCb()
 {
   std::cerr << "Got NACK\n";
+  exit(1);
 }
 
 static void
@@ -123,9 +183,9 @@
   }
   std::cerr << "\n***************************************\n"
             << "Step " << nStep++
-            << ": DONE\nCertificate with Name: " << reply.getName().toUri()
-            << "has already been installed to your local keychain" << std::endl
-            << "Exit now";
+            << ": DONE\nCertificate with Name: " << reply.getName()
+            << " has been installed to your local keychain\n"
+            << "Exit now" << std::endl;
   face.getIoService().stop();
 }
 
@@ -168,43 +228,51 @@
     exit(1);
   }
   else if (challengeList.size() >= 1) {
-    std::cerr << "\n***************************************\n"
-              << "Step " << nStep++
-              << ": CHALLENGE SELECTION" << std::endl;
-    size_t count = 0;
-    std::string choice = "";
-    for (auto item : challengeList) {
-      std::cerr << "> Index: " << count++ << std::endl
-                << ">> Challenge:" << item << std::endl;
+    auto item = std::find(challengeList.begin(), challengeList.end(), defaultChallenge);
+    if (item != challengeList.end()) {
+      runChallenge(defaultChallenge);
     }
-    std::cerr << "Please type in the challenge index that you want to perform:" << std::endl;
-    size_t inputCount = 0;
-    while (inputCount < 3) {
-      getline(std::cin, choice);
-      try {
-        challengeIndex = std::stoul(choice);
+    else {
+      // default challenge not available
+      std::cerr << "\n***************************************\n"
+                << "Step " << nStep++
+                << ": CHALLENGE SELECTION" << std::endl;
+      size_t count = 0;
+      std::string choice = "";
+      for (auto item : challengeList) {
+        std::cerr << "> Index: " << count++ << std::endl
+                  << ">> Challenge: " << item << std::endl;
       }
-      catch (const std::exception&) {
-        std::cerr << "Your input is not valid. Try again:" << std::endl;
-        inputCount++;
-        continue;
+      std::cerr << "Please type in the index of the challenge that you want to perform:" << std::endl;
+      size_t inputCount = 0;
+      while (inputCount < 3) {
+        getline(std::cin, choice);
+        try {
+          challengeIndex = std::stoul(choice);
+        }
+        catch (const std::exception&) {
+          std::cerr << "Your input is not valid. Try again:" << std::endl;
+          inputCount++;
+          continue;
+        }
+        if (challengeIndex >= count) {
+          std::cerr << "Your input index is out of range. Try again:" << std::endl;
+          inputCount++;
+          continue;
+        }
+        break;
       }
-      if (challengeIndex >= count) {
-        std::cerr << "Your input index is out of range. Try again:" << std::endl;
-        inputCount++;
-        continue;
+      if (inputCount == 3) {
+        std::cerr << "Invalid input for too many times, exit. " << std::endl;
+        exit(1);
       }
-      break;
-    }
-    if (inputCount == 3) {
-      std::cerr << "Invalid input for too many times, exit. " << std::endl;
-      exit(1);
+
+      auto it = challengeList.begin();
+      std::advance(it, challengeIndex);
+      std::cerr << "The challenge has been selected: " << *it << std::endl;
+      runChallenge(*it);
     }
   }
-  auto it = challengeList.begin();
-  std::advance(it, challengeIndex);
-  std::cerr << "The challenge has been selected: " << *it << std::endl;
-  runChallenge(*it);
 }
 
 static void
@@ -226,7 +294,7 @@
   std::cerr << "\n***************************************\n"
             << "Step " << nStep++
             << ": Will use a new trust anchor, please double check the identity info:" << std::endl
-            << "> New CA name: " << profile->caPrefix.toUri() << std::endl
+            << "> New CA name: " << profile->caPrefix << std::endl
             << "> This trust anchor information is signed by: " << reply.getSignatureInfo().getKeyLocator() << std::endl
             << "> The certificate: " << *profile->cert << std::endl
             << "Do you trust the information? Type in YES or NO" << std::endl;
@@ -235,12 +303,11 @@
   getline(std::cin, answer);
   boost::algorithm::to_lower(answer);
   if (answer == "yes") {
-    std::cerr << "You answered YES: new CA " << profile->caPrefix.toUri() << " will be used" << std::endl;
+    std::cerr << "You answered YES: new CA " << profile->caPrefix << " will be used" << std::endl;
     runProbe(*profile);
-    // client.getClientConf().save(std::string(SYSCONFDIR) + "/ndncert/client.conf");
   }
   else {
-    std::cerr << "You answered NO: new CA " << profile->caPrefix.toUri() << " will not be used" << std::endl;
+    std::cerr << "You answered NO: new CA " << profile->caPrefix << " will not be used" << std::endl;
     exit(0);
   }
 }
@@ -250,74 +317,99 @@
 {
   std::vector<std::pair<Name, int>> names;
   std::vector<Name> redirects;
-  Request::onProbeResponse(reply, profile, names, redirects);
-  size_t count = 0;
-  std::cerr << "\n***************************************\n"
-            << "Step " << nStep++
-            << ": You can either select one of the following names suggested by the CA: " << std::endl;
-  for (const auto& name : names) {
-    std::cerr << "> Index: " << count++ << std::endl
-              << ">> Suggested name: " << name.first.toUri() << std::endl
-              << ">> Corresponding Max sufiix length: " << name.second << std::endl;
-  }
-  std::cerr << "\nOr choose another trusted CA suggested by the CA: " << std::endl;
-  for (const auto& redirect : redirects) {
-    std::cerr << "> Index: " << count++ << std::endl
-              << ">> Suggested CA: " << ndn::security::extractIdentityFromCertName(redirect.getPrefix(-1))
-              << std::endl;
-  }
-  std::cerr << "Please type in the index of your choice:" << std::endl;
-  size_t index = 0;
   try {
-    std::string input;
-    getline(std::cin, input);
-    index = std::stoul(input);
+    Request::onProbeResponse(reply, profile, names, redirects);
   }
-  catch (const std::exception&) {
-    std::cerr << "Your input is Invalid. Exit" << std::endl;
+  catch (const std::exception& e) {
+    std::cerr << "The probed CA response cannot be used because: " << e.what() << std::endl;
     exit(1);
   }
-  if (index >= names.size() + redirects.size()) {
-    std::cerr << "Your input is not an existing index. Exit" << std::endl;
-    exit(1);
-  }
-  if (index < names.size()) {
-    //names
-    auto selectedName = names[index].first;
-    std::cerr << "You selected name: " << selectedName.toUri() << std::endl;
-    std::cerr << "Enter Suffix if you would like one (Enter to skip): ";
-    try {
-      std::string input;
-      getline(std::cin, input);
-      auto inputName = Name(input);
-      if (!inputName.empty()) {
-        selectedName.append(inputName);
-        std::cerr << "You are applying name: " << selectedName.toUri() << std::endl;
+
+  size_t count = 0;
+  Name selectedName;
+  Name redirectedCaFullName;
+  // always prefer redirection over direct assignment
+  if (redirects.size() > 0) {
+    if (redirects.size() < 2) {
+      redirectedCaFullName = redirects.front();
+    }
+    else {
+      std::cerr << "\n***************************************\n"
+                << "Step " << nStep++
+                << " Choose another trusted CA suggested by the CA: " << std::endl;
+      for (const auto& redirect : redirects) {
+        std::cerr << "> Index: " << count++ << std::endl
+                  << ">> Suggested CA: " << ndn::security::extractIdentityFromCertName(redirect.getPrefix(-1))
+                  << std::endl;
       }
+      std::cerr << "Please type in the index of your choice:" << std::endl;
+      size_t index = 0;
+      try {
+        std::string input;
+        getline(std::cin, input);
+        index = std::stoul(input);
+      }
+      catch (const std::exception&) {
+        std::cerr << "Your input is Invalid. Exit" << std::endl;
+        exit(1);
+      }
+      if (index >= redirects.size()) {
+        std::cerr << "Your input is not an existing index. Exit" << std::endl;
+        exit(1);
+      }
+      redirectedCaFullName = redirects[index];
     }
-    catch (const std::exception&) {
-      std::cerr << "Your input is Invalid. Exit" << std::endl;
-      exit(1);
-    }
-    runNew(profile, selectedName);
-  }
-  else {
-    //redirects
-    auto redirectedCaFullName = redirects[index - names.size()];
     auto redirectedCaName = ndn::security::extractIdentityFromCertName(redirectedCaFullName.getPrefix(-1));
-    std::cerr << "You selected to be redirected to CA: " << redirectedCaName.toUri() << std::endl;
+    std::cerr << "You will be redirected to CA: " << redirectedCaName << std::endl;
     face.expressInterest(
         *Request::genCaProfileDiscoveryInterest(redirectedCaName),
-        [&] (const auto&, const auto& data) {
+        [&, redirectedCaFullName] (const auto&, const auto& data) {
           auto fetchingInterest = Request::genCaProfileInterestFromDiscoveryResponse(data);
           face.expressInterest(*fetchingInterest,
-                               [=] (const auto&, const auto& data2) { infoCb(data2, redirectedCaFullName); },
-                               [] (auto&&...) { onNackCb(); },
-                               [] (auto&&...) { timeoutCb(); });
+                              [=] (const auto&, const auto& data2) { infoCb(data2, redirectedCaFullName); },
+                              [] (auto&&...) { onNackCb(); },
+                              [] (auto&&...) { timeoutCb(); });
         },
         [] (auto&&...) { onNackCb(); },
         [] (auto&&...) { timeoutCb(); });
   }
+  else if (names.size() > 0) {
+    if (names.size() < 2) {
+      selectedName = names.front().first;
+    }
+    else {
+      std::cerr << "\n***************************************\n"
+                << "Step " << nStep++
+                << ": You can either select one of the following names suggested by the CA: " << std::endl;
+      for (const auto& name : names) {
+        std::cerr << "> Index: " << count++ << std::endl
+                  << ">> Suggested name: " << name.first << std::endl
+                  << ">> Corresponding max suffix length: " << name.second << std::endl;
+      }
+      std::cerr << "Please type in the index of your choice:" << std::endl;
+      size_t index = 0;
+      try {
+        std::string input;
+        getline(std::cin, input);
+        index = std::stoul(input);
+      }
+      catch (const std::exception&) {
+        std::cerr << "Your input is invalid. Exit" << std::endl;
+        exit(1);
+      }
+      if (index >= names.size()) {
+        std::cerr << "Your input is not an existing index. Exit" << std::endl;
+        exit(1);
+      }
+      selectedName = names[index].first;
+    }
+    std::cerr << "You are applying for name: " << selectedName << std::endl;
+    runNew(profile, selectedName);
+  }
+  else {
+    std::cerr << "Neither name assignment nor redirection is available, exit." << std::endl;
+    exit(1);
+  }
 }
 
 static void
@@ -369,11 +461,11 @@
     }
     catch (const std::exception&) {
       std::cerr << "Your input is neither NONE nor a valid index. Exit" << std::endl;
-      return;
+      exit(1);
     }
     if (caIndex >= count) {
       std::cerr << "Your input is not an existing index. Exit" << std::endl;
-      return;
+      exit(1);
     }
     auto itemIterator = profileStorage.getKnownProfiles().cbegin();
     std::advance(itemIterator, caIndex);
@@ -385,59 +477,53 @@
 static void
 runProbe(CaProfile profile)
 {
-  std::cerr << "\n***************************************\n"
-            << "Step " << nStep++
-            << ": Do you know your identity name to be certified by CA "
-            << profile.caPrefix.toUri()
-            << " already? Type in YES or NO" << std::endl;
-  bool validAnswer = false;
-  size_t count = 0;
-  while (!validAnswer && count < 3) {
-    std::string answer;
-    getline(std::cin, answer);
-    boost::algorithm::to_lower(answer);
-    if (answer == "yes") {
-      validAnswer = true;
-      std::cerr << "You answered YES" << std::endl;
-      std::cerr << "\n***************************************\n"
-                << "Step " << nStep++
-                << ": Please type in the full identity name you want to get (with CA prefix "
-                << profile.caPrefix.toUri()
-                << "):" << std::endl;
-      std::string identityNameStr;
-      getline(std::cin, identityNameStr);
-      runNew(profile, Name(identityNameStr));
-    }
-    else if (answer == "no") {
-      validAnswer = true;
-      std::cerr << "You answered NO" << std::endl;
-      std::cerr << "\n***************************************\n"
-                << "Step " << nStep++ << ": Please provide information for name assignment" << std::endl;
-      auto capturedParams = captureParams(profile.probeParameterKeys);
-      face.expressInterest(*Request::genProbeInterest(profile, std::move(capturedParams)),
-                           [profile] (const auto&, const auto& data) { probeCb(data, profile); },
-                           [] (auto&&...) { onNackCb(); },
-                           [] (auto&&...) { timeoutCb(); });
-    }
-    else {
-      std::cerr << "Invalid answer. Type in YES or NO" << std::endl;
-      count++;
-    }
+  if (!capturedProbeParams) {
+    std::cerr << "\n***************************************\n"
+          << "Step " << nStep++ << ": Please provide information for name assignment" << std::endl;
+    auto captured = captureParams(profile.probeParameterKeys);
+    capturedProbeParams = std::make_shared<std::multimap<std::string, std::string>>(captured);
   }
-  if (count == 3) {
-    std::cerr << "Invalid input for too many times, exit. " << std::endl;
-    exit(1);
-  }
+  face.expressInterest(*Request::genProbeInterest(profile, std::move(*capturedProbeParams)),
+                        [profile] (const auto&, const auto& data) { probeCb(data, profile); },
+                        [] (auto&&...) { onNackCb(); },
+                        [] (auto&&...) { timeoutCb(); });
 }
 
 static void
 runNew(CaProfile profile, Name identityName)
 {
-  int validityPeriod = captureValidityPeriod();
+  int validityPeriod = captureValidityPeriod(time::duration_cast<time::hours>(profile.maxValidityPeriod));
   auto now = time::system_clock::now();
   std::cerr << "The validity period of your certificate will be: " << validityPeriod << " hours" << std::endl;
   requesterState = std::make_shared<Request>(keyChain, profile, RequestType::NEW);
-  auto interest = requesterState->genNewInterest(identityName, now, now + time::hours(validityPeriod));
+
+  // generate a newly key pair or choose an existing key
+  const auto& pib = keyChain.getPib();
+  ndn::security::pib::Identity identity;
+  ndn::security::pib::Key defaultKey;
+  try {
+    identity = pib.getIdentity(identityName);
+  }
+  catch (const ndn::security::Pib::Error&) {
+    identity = keyChain.createIdentity(identityName);
+    newlyCreatedIdentityName = identity.getName();
+  }
+  try {
+    defaultKey = identity.getDefaultKey();
+  }
+  catch (const ndn::security::Pib::Error&) {
+    defaultKey = keyChain.createKey(identity);
+    newlyCreatedKeyName = defaultKey.getName();
+  }
+
+  Name keyName;
+  if (newlyCreatedIdentityName.empty()) {
+    keyName = captureKeyName(identity, defaultKey);
+  }
+  else {
+    keyName = defaultKey.getName();
+  }
+  auto interest = requesterState->genNewInterest(keyName, now, now + time::hours(validityPeriod));
   if (interest != nullptr) {
     face.expressInterest(*interest,
                          [] (const auto&, const auto& data) { newCb(data); },
@@ -462,10 +548,15 @@
     exit(1);
   }
   if (requirement.size() > 0) {
-    std::cerr << "\n***************************************\n"
-              << "Step " << nStep
-              << ": Please provide parameters used for Identity Verification Challenge" << std::endl;
-    captureParams(requirement);
+    if (requesterState->m_status == Status::BEFORE_CHALLENGE && challengeType == defaultChallenge) {
+      requirement.find(challengeType)->second = capturedProbeParams->find(defaultChallenge)->second;
+    }
+    else {
+      std::cerr << "\n***************************************\n"
+          << "Step " << nStep
+          << ": Please provide parameters used for Identity Verification Challenge" << std::endl;
+      captureParams(requirement);
+    }
   }
   face.expressInterest(*requesterState->genChallengeInterest(std::move(requirement)),
                        [] (const auto&, const auto& data) { challengeCb(data); },
@@ -489,7 +580,14 @@
   }
   std::cerr << std::endl;
   if (requesterState) {
-    requesterState->endSession();
+    if (!newlyCreatedIdentityName.empty()) {
+      auto identity = keyChain.getPib().getIdentity(newlyCreatedIdentityName);
+      keyChain.deleteIdentity(identity);
+    }
+    else if (!newlyCreatedKeyName.empty()) {
+      auto identity = keyChain.getPib().getIdentity(ndn::security::extractIdentityFromKeyName(newlyCreatedKeyName));
+      keyChain.deleteKey(identity, identity.getKey(newlyCreatedKeyName));
+    }
   }
   face.getIoService().stop();
   exit(1);