tools: export non-default key/cert in ndnsec export

refs #5043

Change-Id: Ida6e3c041b850a132160660a58d1f219defedf22
diff --git a/docs/manpages/ndnsec-export.rst b/docs/manpages/ndnsec-export.rst
index 4d7e277..de232a6 100644
--- a/docs/manpages/ndnsec-export.rst
+++ b/docs/manpages/ndnsec-export.rst
@@ -4,18 +4,32 @@
 Synopsis
 --------
 
-**ndnsec-export** [**-h**] [**-o** *file*] [**-P** *passphrase*] *identity*
+**ndnsec-export** [**-h**] [**-o** *file*] [**-P** *passphrase*]
+[**-i**\|\ **-k**\|\ **-c**] *name*
 
 Description
 -----------
 
-:program:`ndnsec-export` exports the default certificate of *identity* and its
-private key to a file. It will ask for a passphrase to encrypt the private key.
+:program:`ndnsec-export` exports a certificate from the **Public Info Base (PIB)** and
+its private key to a file. It will ask for a passphrase to encrypt the private key.
 The resulting file can be imported again using :program:`ndnsec-import`.
 
 Options
 -------
 
+.. option:: -i, --identity
+
+   Interpret *name* as an identity name. The default certificate of the identity will be exported.
+   This is the default unless **-k** or **-c** is specified.
+
+.. option:: -k, --key
+
+   Interpret *name* as a key name. The default certificate of the key will be exported.
+
+.. option:: -c, --cert
+
+   Interpret *name* as a certificate name.
+
 .. option:: -o <file>, --output <file>
 
    Write to the specified output file instead of the standard output.
@@ -34,6 +48,6 @@
 
     $ ndnsec-export -o alice.ndnkey /ndn/test/alice
 
-Export an identity's default certificate and private key to the standard output::
+Export a specific certificate and its private key to the standard output::
 
-    $ ndnsec-export /ndn/test/alice
+    $ ndnsec-export -c /ndn/edu/ucla/alice/KEY/1%5D%A7g%90%B2%CF%AA/self/%FD%00%00%01r-%D3%DC%2A
diff --git a/tools/ndnsec/cert-dump.cpp b/tools/ndnsec/cert-dump.cpp
index 38d8ed5..d553822 100644
--- a/tools/ndnsec/cert-dump.cpp
+++ b/tools/ndnsec/cert-dump.cpp
@@ -95,7 +95,8 @@
     return 2;
   }
 
-  if (isIdentityName + isKeyName + isFileName > 1) {
+  int nIsNameOptions = isIdentityName + isKeyName + isFileName;
+  if (nIsNameOptions > 1) {
     std::cerr << "ERROR: at most one of '--identity', '--key', "
                  "or '--file' may be specified" << std::endl;
     return 2;
@@ -106,35 +107,20 @@
     return 2;
   }
 
-  security::v2::KeyChain keyChain;
-
   security::v2::Certificate certificate;
-  try {
-    if (isIdentityName) {
-      certificate = keyChain.getPib()
-                    .getIdentity(name)
-                    .getDefaultKey()
-                    .getDefaultCertificate();
-    }
-    else if (isKeyName) {
-      certificate = keyChain.getPib()
-                    .getIdentity(security::v2::extractIdentityFromKeyName(name))
-                    .getKey(name)
-                    .getDefaultCertificate();
-    }
-    else if (isFileName) {
+  if (isFileName) {
+    try {
       certificate = loadCertificate(name);
     }
-    else {
-      certificate = keyChain.getPib()
-                    .getIdentity(security::v2::extractIdentityFromCertName(name))
-                    .getKey(security::v2::extractKeyNameFromCertName(name))
-                    .getCertificate(name);
+    catch (const CannotLoadCertificate&) {
+      std::cerr << "ERROR: Cannot load the certificate from `" << name << "`" << std::endl;
+      return 1;
     }
   }
-  catch (const CannotLoadCertificate&) {
-    std::cerr << "ERROR: Cannot load the certificate from `" << name << "`" << std::endl;
-    return 1;
+  else {
+    security::v2::KeyChain keyChain;
+    certificate = getCertificateFromPib(keyChain.getPib(), name,
+                                        isIdentityName, isKeyName, nIsNameOptions == 0);
   }
 
   if (isPretty) {
diff --git a/tools/ndnsec/export.cpp b/tools/ndnsec/export.cpp
index 4429dd4..79bea3d 100644
--- a/tools/ndnsec/export.cpp
+++ b/tools/ndnsec/export.cpp
@@ -35,7 +35,10 @@
 {
   namespace po = boost::program_options;
 
-  Name identityName;
+  Name name;
+  bool isIdentityName = false;
+  bool isKeyName = false;
+  bool isCertName = false;
   std::string output;
   std::string password;
 
@@ -45,46 +48,69 @@
     OPENSSL_cleanse(&password.front(), password.size());
   } BOOST_SCOPE_EXIT_END
 
-  po::options_description description(
-    "Usage: ndnsec export [-h] [-o FILE] [-P PASSPHRASE] [-i] IDENTITY\n"
+  po::options_description visibleOptDesc(
+    "Usage: ndnsec export [-h] [-o FILE] [-P PASSPHRASE] [-i|-k|-c] NAME\n"
     "\n"
     "Options");
-  description.add_options()
+  visibleOptDesc.add_options()
     ("help,h", "produce help message")
-    ("identity,i", po::value<Name>(&identityName), "name of the identity to export")
+    ("identity,i", po::bool_switch(&isIdentityName),
+                   "treat the NAME argument as an identity name (e.g., /ndn/edu/ucla/alice)")
+    ("key,k",      po::bool_switch(&isKeyName),
+                   "treat the NAME argument as a key name (e.g., /ndn/edu/ucla/alice/KEY/1%5D%A7g%90%B2%CF%AA)")
+    ("cert,c",     po::bool_switch(&isCertName),
+                   "treat the NAME argument as a certificate name "
+                   "(e.g., /ndn/edu/ucla/alice/KEY/1%5D%A7g%90%B2%CF%AA/self/%FD%00%00%01r-%D3%DC%2A)")
     ("output,o",   po::value<std::string>(&output)->default_value("-"),
                    "output file, '-' for stdout (the default)")
     ("password,P", po::value<std::string>(&password),
                    "passphrase, will prompt if empty or not specified")
     ;
 
-  po::positional_options_description p;
-  p.add("identity", 1);
+  po::options_description hiddenOptDesc;
+  hiddenOptDesc.add_options()
+    ("name", po::value<Name>(&name));
+
+  po::options_description optDesc;
+  optDesc.add(visibleOptDesc).add(hiddenOptDesc);
+
+  po::positional_options_description optPos;
+  optPos.add("name", 1);
 
   po::variables_map vm;
   try {
-    po::store(po::command_line_parser(argc, argv).options(description).positional(p).run(), vm);
+    po::store(po::command_line_parser(argc, argv).options(optDesc).positional(optPos).run(), vm);
     po::notify(vm);
   }
   catch (const std::exception& e) {
     std::cerr << "ERROR: " << e.what() << "\n\n"
-              << description << std::endl;
+              << visibleOptDesc << std::endl;
     return 2;
   }
 
   if (vm.count("help") > 0) {
-    std::cout << description << std::endl;
+    std::cout << visibleOptDesc << std::endl;
     return 0;
   }
 
-  if (vm.count("identity") == 0) {
-    std::cerr << "ERROR: you must specify an identity" << std::endl;
+  if (vm.count("name") == 0) {
+    std::cerr << "ERROR: you must specify a name" << std::endl;
     return 2;
   }
 
-  security::v2::KeyChain keyChain;
+  int nIsNameOptions = isIdentityName + isKeyName + isCertName;
+  if (nIsNameOptions > 1) {
+    std::cerr << "ERROR: at most one of '--identity', '--key', "
+                 "or '--cert' may be specified" << std::endl;
+    return 2;
+  }
+  else if (nIsNameOptions == 0) {
+    isIdentityName = true;
+  }
 
-  auto id = keyChain.getPib().getIdentity(identityName);
+  security::v2::KeyChain keyChain;
+  auto cert = getCertificateFromPib(keyChain.getPib(), name,
+                                    isIdentityName, isKeyName, isCertName);
 
   if (password.empty()) {
     int count = 3;
@@ -97,9 +123,7 @@
     }
   }
 
-  // TODO: export all certificates, selected key pair, selected certificate
-  auto safeBag = keyChain.exportSafeBag(id.getDefaultKey().getDefaultCertificate(),
-                                        password.data(), password.size());
+  auto safeBag = keyChain.exportSafeBag(cert, password.data(), password.size());
 
   if (output == "-")
     io::save(*safeBag, std::cout);
diff --git a/tools/ndnsec/util.cpp b/tools/ndnsec/util.cpp
index cb35b52..c07b509 100644
--- a/tools/ndnsec/util.cpp
+++ b/tools/ndnsec/util.cpp
@@ -69,6 +69,28 @@
 }
 
 security::v2::Certificate
+getCertificateFromPib(const security::pib::Pib& pib, const Name& name,
+                      bool isIdentityName, bool isKeyName, bool isCertName)
+{
+  if (isIdentityName) {
+    return pib.getIdentity(name)
+           .getDefaultKey()
+           .getDefaultCertificate();
+  }
+  else if (isKeyName) {
+    return pib.getIdentity(security::v2::extractIdentityFromKeyName(name))
+           .getKey(name)
+           .getDefaultCertificate();
+  }
+  else if (isCertName) {
+    return pib.getIdentity(security::v2::extractIdentityFromCertName(name))
+           .getKey(security::v2::extractKeyNameFromCertName(name))
+           .getCertificate(name);
+  }
+  NDN_CXX_UNREACHABLE;
+}
+
+security::v2::Certificate
 loadCertificate(const std::string& fileName)
 {
   shared_ptr<security::v2::Certificate> cert;
diff --git a/tools/ndnsec/util.hpp b/tools/ndnsec/util.hpp
index 873f6b0..f069c34 100644
--- a/tools/ndnsec/util.hpp
+++ b/tools/ndnsec/util.hpp
@@ -33,6 +33,22 @@
 namespace ndn {
 namespace ndnsec {
 
+/**
+ * @brief Get certificate of given name from PIB.
+ * @param pib PIB instance, obtained from keyChain.getPib().
+ * @param name identity name, key name, or cert name.
+ * @param isIdentityName interpret @p name as identity name.
+ * @param isKeyName interpret @p name as key name.
+ * @param isCertName interpret @p name as certificate name.
+ * @pre exactly one of @p isIdentityName , @p isKeyName , @p isCertName must be true.
+ * @return a certificate.
+ * @throw std::invalid_argument name is invalid.
+ * @throw Pib::Error certificate does not exist.
+ */
+security::v2::Certificate
+getCertificateFromPib(const security::pib::Pib& pib, const Name& name,
+                      bool isIdentityName, bool isKeyName, bool isCertName);
+
 class CannotLoadCertificate : public std::runtime_error
 {
 public: