ca+tools: configurable ca forwarding hint and ca-server interface improvement

Change-Id: I95cc6b2fb195f7c3625f14a4f8856abcf65022d9
diff --git a/ca.conf.sample b/ca.conf.sample
index 003da8e..393cd4a 100644
--- a/ca.conf.sample
+++ b/ca.conf.sample
@@ -19,7 +19,9 @@
   "redirect-to": [
     {
       "ca-prefix": "/ndn/edu/ucla",
-      "certificate": "Bv0BNQcwCANuZG4IA2VkdQgEdWNsYQgDS0VZCAgAdGt6D7S2VAgEc2VsZggJ/QAAAX5lZMOiFAkYAQIZBAA27oAVWzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOKmvwHmK5t+MhMPgft4qmKC7YF9I6UM/o7GFa4BjZQknsqLvxdW2zIAF+iPPHJV0eVAijX6bYrQobuomiWZAY0WUBsBAxwhBx8IA25kbggDZWR1CAR1Y2xhCANLRVkICAB0a3oPtLZU/QD9Jv0A/g8xOTcwMDEwMVQwMDAwMDD9AP8PMjA0MjAxMTJUMDAxNjQ5F0cwRQIgBF/HS0j1DMo/dIILv/6IMUmMAhVtS3m97YgS8tsBhC0CIQCgEm0e6KoBCyV6PiueN9YW9zSSkdg8MLCxsyduP8tRsQ=="
+      "certificate": "Bv0BNQcwCANuZG4IA2VkdQgEdWNsYQgDS0VZCAgAdGt6D7S2VAgEc2VsZggJ/QAAAX5lZMOiFAkYAQIZBAA27oAVWzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOKmvwHmK5t+MhMPgft4qmKC7YF9I6UM/o7GFa4BjZQknsqLvxdW2zIAF+iPPHJV0eVAijX6bYrQobuomiWZAY0WUBsBAxwhBx8IA25kbggDZWR1CAR1Y2xhCANLRVkICAB0a3oPtLZU/QD9Jv0A/g8xOTcwMDEwMVQwMDAwMDD9AP8PMjA0MjAxMTJUMDAxNjQ5F0cwRQIgBF/HS0j1DMo/dIILv/6IMUmMAhVtS3m97YgS8tsBhC0CIQCgEm0e6KoBCyV6PiueN9YW9zSSkdg8MLCxsyduP8tRsQ==",
+      "policy-type": "email",
+      "policy-param": "g.ucla.edu"
     },
     {
       "ca-prefix": "/ndn/edu/ucla/cs",
diff --git a/src/ca-module.cpp b/src/ca-module.cpp
index 7f51eba..09c0b9c 100644
--- a/src/ca-module.cpp
+++ b/src/ca-module.cpp
@@ -441,7 +441,8 @@
       requestState->status = Status::SUCCESS;
       m_storage->deleteRequest(requestState->requestId);
 
-      payload = challengetlv::encodeDataContent(*requestState, issuedCert.getName());
+      payload = challengetlv::encodeDataContent(*requestState, issuedCert.getName(),
+                                                m_config.caProfile.forwardingHint);
       NDN_LOG_TRACE("Challenge succeeded. Certificate has been issued: " << issuedCert.getName());
     }
     else if (requestState->requestType == RequestType::REVOKE) {
diff --git a/src/detail/ca-profile.cpp b/src/detail/ca-profile.cpp
index 86d5be4..06dec71 100644
--- a/src/detail/ca-profile.cpp
+++ b/src/detail/ca-profile.cpp
@@ -35,6 +35,11 @@
   if (profile.caPrefix.empty()) {
     NDN_THROW(std::runtime_error("Cannot parse ca-prefix from the config file"));
   }
+  // Forwarding hint
+  profile.forwardingHint = Name(json.get(CONFIG_FORWARDING_HINT, ""));
+  if (profile.forwardingHint.empty()) {
+    profile.forwardingHint = Name(profile.caPrefix).append("CA");
+  }
   // CA info
   profile.caInfo = json.get(CONFIG_CA_INFO, "");
   // CA max validity period
diff --git a/src/detail/ca-profile.hpp b/src/detail/ca-profile.hpp
index 6ca9b19..09e1303 100644
--- a/src/detail/ca-profile.hpp
+++ b/src/detail/ca-profile.hpp
@@ -37,6 +37,7 @@
 const std::string CONFIG_SUPPORTED_CHALLENGES = "supported-challenges";
 const std::string CONFIG_CHALLENGE = "challenge";
 const std::string CONFIG_CERTIFICATE = "certificate";
+const std::string CONFIG_FORWARDING_HINT = "forwarding-hint";
 const std::string CONFIG_REDIRECTION = "redirect-to";
 const std::string CONFIG_NAME_ASSIGNMENT = "name-assignment";
 const std::string CONFIG_REDIRECTION_POLICY_TYPE = "policy-type";
@@ -65,6 +66,10 @@
    */
   Name caPrefix;
   /**
+   * @brief Forwarding hint for requesters to retrieve issued certificates.
+   */
+  Name forwardingHint;
+  /**
    * @brief CA Information.
    */
   std::string caInfo;
diff --git a/src/detail/challenge-encoder.cpp b/src/detail/challenge-encoder.cpp
index 9f36042..f42460c 100644
--- a/src/detail/challenge-encoder.cpp
+++ b/src/detail/challenge-encoder.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2017-2022, Regents of the University of California.
+ * Copyright (c) 2017-2024, Regents of the University of California.
  *
  * This file is part of ndncert, a certificate management system based on NDN.
  *
@@ -23,7 +23,7 @@
 namespace ndncert::challengetlv {
 
 Block
-encodeDataContent(ca::RequestState& request, const Name& issuedCertName)
+encodeDataContent(ca::RequestState& request, const Name& issuedCertName, const Name& forwardingHint)
 {
   Block response(tlv::EncryptedPayload);
   response.push_back(ndn::makeNonNegativeIntegerBlock(tlv::Status, static_cast<uint64_t>(request.status)));
@@ -41,7 +41,7 @@
   }
   if (!issuedCertName.empty()) {
     response.push_back(makeNestedBlock(tlv::IssuedCertName, issuedCertName));
-    response.push_back(makeNestedBlock(ndn::tlv::ForwardingHint, Name(request.caPrefix).append("CA")));
+    response.push_back(makeNestedBlock(ndn::tlv::ForwardingHint, forwardingHint));
   }
   response.encode();
 
diff --git a/src/detail/challenge-encoder.hpp b/src/detail/challenge-encoder.hpp
index 5c502d2..2329928 100644
--- a/src/detail/challenge-encoder.hpp
+++ b/src/detail/challenge-encoder.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2017-2022, Regents of the University of California.
+ * Copyright (c) 2017-2024, Regents of the University of California.
  *
  * This file is part of ndncert, a certificate management system based on NDN.
  *
@@ -27,7 +27,8 @@
 namespace ndncert::challengetlv {
 
 Block
-encodeDataContent(ca::RequestState& request, const Name& issuedCertName = Name());
+encodeDataContent(ca::RequestState& request, const Name& issuedCertName = Name(),
+                  const Name& forwardingHint = Name());
 
 void
 decodeDataContent(const Block& contentBlock, requester::Request& state);
diff --git a/tests/unit-tests/config-files/config-ca-1 b/tests/unit-tests/config-files/config-ca-1
index 8ea5d38..044260b 100644
--- a/tests/unit-tests/config-files/config-ca-1
+++ b/tests/unit-tests/config-files/config-ca-1
@@ -1,5 +1,6 @@
 {
   "ca-prefix": "/ndn",
+  "forwarding-hint": "/repo",
   "ca-info": "ndn testbed ca",
   "max-validity-period": "864000",
   "max-suffix-length": 3,
diff --git a/tests/unit-tests/configuration.t.cpp b/tests/unit-tests/configuration.t.cpp
index ec43b17..00f8ba5 100644
--- a/tests/unit-tests/configuration.t.cpp
+++ b/tests/unit-tests/configuration.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2017-2022, Regents of the University of California.
+ * Copyright (c) 2017-2024, Regents of the University of California.
  *
  * This file is part of ndncert, a certificate management system based on NDN.
  *
@@ -33,6 +33,7 @@
   ca::CaConfig config;
   config.load("tests/unit-tests/config-files/config-ca-1");
   BOOST_CHECK_EQUAL(config.caProfile.caPrefix, "/ndn");
+  BOOST_CHECK_EQUAL(config.caProfile.forwardingHint, "/repo");
   BOOST_CHECK_EQUAL(config.caProfile.caInfo, "ndn testbed ca");
   BOOST_CHECK_EQUAL(config.caProfile.maxValidityPeriod, time::seconds(864000));
   BOOST_CHECK_EQUAL(*config.caProfile.maxSuffixLength, 3);
@@ -43,6 +44,7 @@
 
   config.load("tests/unit-tests/config-files/config-ca-2");
   BOOST_CHECK_EQUAL(config.caProfile.caPrefix, "/ndn");
+  BOOST_CHECK_EQUAL(config.caProfile.forwardingHint, "/ndn/CA");
   BOOST_CHECK_EQUAL(config.caProfile.caInfo, "missing max validity period, max suffix length, and probe");
   BOOST_CHECK_EQUAL(config.caProfile.maxValidityPeriod, time::seconds(86400));
   BOOST_CHECK(!config.caProfile.maxSuffixLength.has_value());
diff --git a/tools/ndncert-ca-server.cpp b/tools/ndncert-ca-server.cpp
index a683a9f..1bdb269 100644
--- a/tools/ndncert-ca-server.cpp
+++ b/tools/ndncert-ca-server.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2017-2023, Regents of the University of California.
+ * Copyright (c) 2017-2024, Regents of the University of California.
  *
  * This file is part of ndncert, a certificate management system based on NDN.
  *
@@ -37,7 +37,7 @@
 
 static ndn::Face face;
 static ndn::KeyChain keyChain;
-static std::string repoHost = "localhost";
+static std::string repoHost;
 static std::string repoPort = "7376";
 constexpr size_t MAX_CACHED_CERT_NUM = 100;
 
@@ -48,7 +48,7 @@
   requestStream.expires_after(std::chrono::seconds(5));
   requestStream.connect(repoHost, repoPort);
   if (!requestStream) {
-    std::cerr << "ERROR: Cannot publish the certificate to repo-ng"
+    std::cerr << "ERROR: Cannot publish the certificate to repo"
               << " (" << requestStream.error().message() << ")" << std::endl;
     return false;
   }
@@ -92,9 +92,9 @@
   optsDesc.add_options()
   ("help,h", "print this help message and exit")
   ("config-file,c", po::value<std::string>(&configFilePath)->default_value(configFilePath), "path to configuration file")
-  ("repo-output,r", po::bool_switch(&wantRepoOut), "when enabled, all issued certificates will be published to repo-ng")
-  ("repo-host,H", po::value<std::string>(&repoHost)->default_value(repoHost), "repo-ng host")
-  ("repo-port,P", po::value<std::string>(&repoPort)->default_value(repoPort), "repo-ng port");
+  ("repo-host,H", po::value<std::string>(&repoHost)->default_value(repoHost),
+   "repo host (if empty or unspecified, issued certificates will not be published to a repo)")
+  ("repo-port,P", po::value<std::string>(&repoPort)->default_value(repoPort), "repo port");
 
   po::variables_map vm;
   try {
@@ -117,6 +117,10 @@
     return 0;
   }
 
+  if (!repoHost.empty()) {
+    wantRepoOut = true;
+  }
+
   CaModule ca(face, keyChain, configFilePath);
   std::deque<Data> cachedCertificates;
   auto profileData = ca.getCaProfileData();