security+tools: Allow user to explicitly specify the cert name prefix before 'KEY' component in ndnsec-certgen

Change-Id: I71e137e89b5ab0cd5db7001b39ff76c22a448bd2
Refs: #1659
diff --git a/docs/doxygen.conf.in b/docs/doxygen.conf.in
index 24b3de7..7b79b1e 100644
--- a/docs/doxygen.conf.in
+++ b/docs/doxygen.conf.in
@@ -1888,7 +1888,7 @@
 # The default value is: NO.
 # This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
 
-MACRO_EXPANSION        = NO
+MACRO_EXPANSION        = YES
 
 # If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
 # the macro expansion is limited to the macros specified with the PREDEFINED and
diff --git a/docs/manpages/ndnsec-cert-gen.rst b/docs/manpages/ndnsec-cert-gen.rst
index 3c2eff8..923c936 100644
--- a/docs/manpages/ndnsec-cert-gen.rst
+++ b/docs/manpages/ndnsec-cert-gen.rst
@@ -8,7 +8,7 @@
 
 ::
 
-    $ ndnsec-cert-gen [-h] [-S timestamp] [-E timestamp] [-N name] [-I info] [-s sign-id] request
+    $ ndnsec-cert-gen [-h] [-S timestamp] [-E timestamp] [-N name] [-I info] [-s sign-id] [-p cert-prefix] request
 
 Description
 -----------
@@ -45,6 +45,15 @@
   Signing identity. The default key/certificate of ``sign-id`` will be used to sign the requested
   certificate. If this option is not specified, the system default identity will be used.
 
+``-p cert-prefix``
+  The certificate prefix, which is the part of certificate name before ``KEY`` component.
+
+  By default, the certificate prefix will be inferred from the certificate name according
+  to the relation between the signing identity and the subject identity. If the signing
+  identity is a prefix of the subject identity, ``KEY`` will be inserted after the
+  signingIdentity, otherwise ``KEY`` is inserted after subject identity (i.e., before
+  ``ksk-....``).
+
 Examples
 --------
 
diff --git a/src/security/key-chain.cpp b/src/security/key-chain.cpp
index fe85472..240bab6 100644
--- a/src/security/key-chain.cpp
+++ b/src/security/key-chain.cpp
@@ -35,6 +35,9 @@
 
 namespace ndn {
 
+// Use a GUID as a magic number of KeyChain::DEFAULT_PREFIX identifier
+const Name KeyChain::DEFAULT_PREFIX("/723821fd-f534-44b3-80d9-44bf5f58bbbb");
+
 KeyChain::KeyChain()
   : m_pib(0)
   , m_tpm(0)
@@ -120,7 +123,32 @@
   const Name& signingIdentity,
   const time::system_clock::TimePoint& notBefore,
   const time::system_clock::TimePoint& notAfter,
-  const std::vector<CertificateSubjectDescription>& subjectDescription)
+  const std::vector<CertificateSubjectDescription>& subjectDescription,
+  const Name& certPrefix)
+{
+  shared_ptr<PublicKey> publicKey;
+  try
+    {
+      publicKey = m_pib->getPublicKey(keyName);
+    }
+  catch (SecPublicInfo::Error& e)
+    {
+      return shared_ptr<IdentityCertificate>();
+    }
+
+  return prepareUnsignedIdentityCertificate(keyName, *publicKey, signingIdentity,
+                                            notBefore, notAfter,
+                                            subjectDescription, certPrefix);
+}
+
+shared_ptr<IdentityCertificate>
+KeyChain::prepareUnsignedIdentityCertificate(const Name& keyName,
+  const PublicKey& publicKey,
+  const Name& signingIdentity,
+  const time::system_clock::TimePoint& notBefore,
+  const time::system_clock::TimePoint& notAfter,
+  const std::vector<CertificateSubjectDescription>& subjectDescription,
+  const Name& certPrefix)
 {
   if (keyName.size() < 1)
     return shared_ptr<IdentityCertificate>();
@@ -132,37 +160,40 @@
   shared_ptr<IdentityCertificate> certificate = make_shared<IdentityCertificate>();
   Name certName;
 
-  if (signingIdentity.isPrefixOf(keyName))
+  if (certPrefix == KeyChain::DEFAULT_PREFIX)
     {
-      certName.append(signingIdentity)
-        .append("KEY")
-        .append(keyName.getSubName(signingIdentity.size()))
-        .append("ID-CERT")
-        .appendVersion();
+      // No certificate prefix hint, infer the prefix
+      if (signingIdentity.isPrefixOf(keyName))
+        certName.append(signingIdentity)
+          .append("KEY")
+          .append(keyName.getSubName(signingIdentity.size()))
+          .append("ID-CERT")
+          .appendVersion();
+      else
+        certName.append(keyName.getPrefix(-1))
+          .append("KEY")
+          .append(keyName.get(-1))
+          .append("ID-CERT")
+          .appendVersion();
     }
   else
     {
-      certName.append(keyName.getPrefix(-1))
-        .append("KEY")
-        .append(keyName.get(-1))
-        .append("ID-CERT")
-        .appendVersion();
+      // cert prefix hint is supplied, determine the cert name.
+      if (certPrefix.isPrefixOf(keyName) && certPrefix != keyName)
+        certName.append(certPrefix)
+          .append("KEY")
+          .append(keyName.getSubName(certPrefix.size()))
+          .append("ID-CERT")
+          .appendVersion();
+      else
+        return shared_ptr<IdentityCertificate>();
     }
 
+
   certificate->setName(certName);
   certificate->setNotBefore(notBefore);
   certificate->setNotAfter(notAfter);
-
-  shared_ptr<PublicKey> publicKey;
-  try
-    {
-      publicKey = m_pib->getPublicKey(keyName);
-    }
-  catch (SecPublicInfo::Error& e)
-    {
-      return shared_ptr<IdentityCertificate>();
-    }
-  certificate->setPublicKeyInfo(*publicKey);
+  certificate->setPublicKeyInfo(publicKey);
 
   if (subjectDescription.empty())
     {
diff --git a/src/security/key-chain.hpp b/src/security/key-chain.hpp
index 7c95e14..00a2e6b 100644
--- a/src/security/key-chain.hpp
+++ b/src/security/key-chain.hpp
@@ -57,6 +57,8 @@
     }
   };
 
+  static const Name DEFAULT_PREFIX;
+
   KeyChain();
 
   template<class KeyChainTraits>
@@ -116,6 +118,11 @@
    * @param notBefore Refer to IdentityCertificate.
    * @param notAfter Refer to IdentityCertificate.
    * @param subjectDescription Refer to IdentityCertificate.
+   * @param certPrefix Prefix before `KEY` component. By default, KeyChain will infer the
+   *                   certificate name according to the relation between the signingIdentity and
+   *                   the subject identity. If signingIdentity is a prefix of the subject identity,
+   *                   `KEY` will be inserted after the signingIdentity, otherwise `KEY` is inserted
+   *                   after subject identity (i.e., before `ksk-....`).
    * @return IdentityCertificate.
    */
   shared_ptr<IdentityCertificate>
@@ -123,7 +130,33 @@
     const Name& signingIdentity,
     const time::system_clock::TimePoint& notBefore,
     const time::system_clock::TimePoint& notAfter,
-    const std::vector<CertificateSubjectDescription>& subjectDescription);
+    const std::vector<CertificateSubjectDescription>& subjectDescription,
+    const Name& certPrefix = DEFAULT_PREFIX);
+
+  /**
+   * @brief prepare an unsigned identity certificate
+   *
+   * @param keyName Key name, e.g., `/<identity_name>/ksk-123456`.
+   * @param publicKey Public key to sign.
+   * @param signingIdentity The signing identity.
+   * @param notBefore Refer to IdentityCertificate.
+   * @param notAfter Refer to IdentityCertificate.
+   * @param subjectDescription Refer to IdentityCertificate.
+   * @param certPrefix Prefix before `KEY` component. By default, KeyChain will infer the
+   *                   certificate name according to the relation between the signingIdentity and
+   *                   the subject identity. If signingIdentity is a prefix of the subject identity,
+   *                   `KEY` will be inserted after the signingIdentity, otherwise `KEY` is inserted
+   *                   after subject identity (i.e., before `ksk-....`).
+   * @return IdentityCertificate.
+   */
+  shared_ptr<IdentityCertificate>
+  prepareUnsignedIdentityCertificate(const Name& keyName,
+    const PublicKey& publicKey,
+    const Name& signingIdentity,
+    const time::system_clock::TimePoint& notBefore,
+    const time::system_clock::TimePoint& notAfter,
+    const std::vector<CertificateSubjectDescription>& subjectDescription,
+    const Name& certPrefix = DEFAULT_PREFIX);
 
   /**
    * @brief Sign packet with default identity
diff --git a/tests/unit-tests/security/test-keychain.cpp b/tests/unit-tests/security/test-keychain.cpp
index 9a2300d..1149dce 100644
--- a/tests/unit-tests/security/test-keychain.cpp
+++ b/tests/unit-tests/security/test-keychain.cpp
@@ -163,6 +163,15 @@
   BOOST_CHECK(idCert->getName().getPrefix(5) ==
               Name().append(identity).append("KEY").append("Lower"));
 
+  shared_ptr<IdentityCertificate> idCert11 =
+    keyChain.prepareUnsignedIdentityCertificate(lowerKeyName, identity,
+                                                time::system_clock::now(),
+                                                time::system_clock::now() + time::days(365),
+                                                subjectDescription,
+                                                lowerIdentity);
+  BOOST_CHECK(static_cast<bool>(idCert11));
+  BOOST_CHECK(idCert11->getName().getPrefix(6) ==
+              Name().append(lowerIdentity).append("KEY"));
 
   Name anotherIdentity("/TestKeyChain/PrepareIdentityCertificate/Another/");
   anotherIdentity.appendVersion();
diff --git a/tools/ndnsec-cert-gen.hpp b/tools/ndnsec-cert-gen.hpp
index 1b81fc5..c2b0333 100644
--- a/tools/ndnsec-cert-gen.hpp
+++ b/tools/ndnsec-cert-gen.hpp
@@ -42,19 +42,33 @@
   std::string requestFile("-");
   std::string signId;
   std::string subjectInfo;
+  std::string certPrefix;
   bool hasSignId = false;
-  bool isNack = false;
 
-  po::options_description description("General Usage\n  ndnsec cert-gen [-h] [-S date] [-E date] [-N subject-name] [-I subject-info] [-s sign-id] request\nGeneral options");
+  po::options_description description(
+    "General Usage\n"
+    "  ndnsec cert-gen [-h] [-S date] [-E date] [-N subject-name] [-I subject-info] "
+        "[-s sign-id] [-p cert-prefix] request\n"
+    "General options");
+
   description.add_options()
     ("help,h", "produce help message")
-    ("not-before,S", po::value<std::string>(&notBeforeStr), "certificate starting date, YYYYMMDDhhmmss")
-    ("not-after,E", po::value<std::string>(&notAfterStr), "certificate ending date, YYYYMMDDhhmmss")
-    ("subject-name,N", po::value<std::string>(&subjectName), "subject name")
-    ("subject-info,I", po::value<std::string>(&subjectInfo), "subject info, pairs of OID and string description: \"2.5.4.10 'University of California, Los Angeles'\"")
-    ("nack", "Generate revocation certificate (NACK)")
-    ("sign-id,s", po::value<std::string>(&signId), "signing Identity, system default identity if not specified")
-    ("request,r", po::value<std::string>(&requestFile), "request file name, - for stdin")
+    ("not-before,S",     po::value<std::string>(&notBeforeStr),
+                         "certificate starting date, YYYYMMDDhhmmss")
+    ("not-after,E",      po::value<std::string>(&notAfterStr),
+                         "certificate ending date, YYYYMMDDhhmmss")
+    ("subject-name,N",   po::value<std::string>(&subjectName),
+                         "subject name")
+    ("subject-info,I",   po::value<std::string>(&subjectInfo),
+                         "subject info, pairs of OID and string description: "
+                         "\"2.5.4.10 'University of California, Los Angeles'\"")
+    ("sign-id,s",        po::value<std::string>(&signId),
+                         "signing Identity, system default identity if not specified")
+    ("cert-prefix,p", po::value<std::string>(&certPrefix),
+                         "cert prefix, which is the part of certificate name before "
+                         "KEY component")
+    ("request,r",        po::value<std::string>(&requestFile),
+                         "request file name, - for stdin")
     ;
 
   po::positional_options_description p;
@@ -84,12 +98,15 @@
       hasSignId = true;
     }
 
-  if (vm.count("nack") != 0)
+  if (vm.count("subject-name") == 0)
     {
-      isNack = true;
+      std::cerr << "subject_name must be specified" << std::endl;
+      return 1;
     }
 
-  std::vector<CertificateSubjectDescription> otherSubDescrypt;
+  std::vector<CertificateSubjectDescription> subjectDescription;
+  subjectDescription.push_back(CertificateSubjectDescription("2.5.4.41", subjectName));
+
   tokenizer<escaped_list_separator<char> > subjectInfoItems
     (subjectInfo, escaped_list_separator<char> ("\\", " \t", "'\""));
 
@@ -109,7 +126,7 @@
 
       std::string value = *it;
 
-      otherSubDescrypt.push_back(CertificateSubjectDescription(oid, value));
+      subjectDescription.push_back(CertificateSubjectDescription(oid, value));
 
       it++;
     }
@@ -162,76 +179,23 @@
 
   Name keyName = selfSignedCertificate->getPublicKeyName();
   Name signIdName;
-  Name certName;
+  Name prefix(certPrefix);
 
   if (!hasSignId)
     signIdName = keyChain.getDefaultIdentity();
   else
     signIdName = Name(signId);
 
+  shared_ptr<IdentityCertificate> certificate =
+    keyChain.prepareUnsignedIdentityCertificate(keyName, selfSignedCertificate->getPublicKeyInfo(),
+                                                signIdName, notBefore, notAfter,
+                                                subjectDescription, prefix);
 
-  if (signIdName.isPrefixOf(keyName))
-    {
-      // if signee's namespace is a sub-namespace of signer, for example, signer's namespace is
-      // /ndn/test, signee's namespace is /ndn/test/alice, the generated certificate name is
-      // /ndn/test/KEY/alice/ksk-1234/ID-CERT/%01%02
-      certName.append(signIdName)
-        .append("KEY")
-        .append(keyName.getSubName(signIdName.size()))
-        .append("ID-CERT")
-        .appendVersion();
-    }
-  else
-    {
-      // if signee's namespace is not a sub-namespace of signer, for example, signer's namespace is
-      // /ndn/test, signee's namespace is /ndn/ucla/bob, the generated certificate name is
-      // /ndn/ucla/bob/KEY/ksk-1234/ID-CERT/%01%02
-      certName.append(keyName.getPrefix(-1))
-        .append("KEY")
-        .append(keyName.get(-1))
-        .append("ID-CERT")
-        .appendVersion();
-    }
+  keyChain.createIdentity(signIdName);
+  Name signingCertificateName = keyChain.getDefaultCertificateNameForIdentity(signIdName);
+  keyChain.sign(*certificate, signingCertificateName);
 
-  Block wire;
-
-  if (!isNack)
-    {
-      if (vm.count("subject-name") == 0)
-        {
-          std::cerr << "subject_name must be specified" << std::endl;
-          return 1;
-        }
-
-      CertificateSubjectDescription subDescryptName("2.5.4.41", subjectName);
-      IdentityCertificate certificate;
-      certificate.setName(certName);
-      certificate.setNotBefore(notBefore);
-      certificate.setNotAfter(notAfter);
-      certificate.setPublicKeyInfo(selfSignedCertificate->getPublicKeyInfo());
-      certificate.addSubjectDescription(subDescryptName);
-      for (size_t i = 0; i < otherSubDescrypt.size(); i++)
-        certificate.addSubjectDescription(otherSubDescrypt[i]);
-      certificate.encode();
-
-      keyChain.createIdentity(signIdName);
-      Name signingCertificateName = keyChain.getDefaultCertificateNameForIdentity(signIdName);
-      keyChain.sign(certificate, signingCertificateName);
-
-      wire = certificate.wireEncode();
-    }
-  else
-    {
-      Data revocationCert;
-      // revocationCert.setContent(void*, 0); // empty content
-      revocationCert.setName(certName);
-
-      keyChain.createIdentity(signIdName);
-      Name signingCertificateName = keyChain.getDefaultCertificateNameForIdentity(signIdName);
-      keyChain.sign(revocationCert, signingCertificateName);
-
-      wire = revocationCert.wireEncode();
-    }
+  Block wire = certificate->wireEncode();
 
   try
     {