ndnsec: improve error reporting when loading a Certificate or SafeBag fails

Refs: #5164
Change-Id: I6f594a921bb063ad808f311d8ff978bf0f7d528d
diff --git a/ndn-cxx/util/io.cpp b/ndn-cxx/util/io.cpp
index a385eea..0fff144 100644
--- a/ndn-cxx/util/io.cpp
+++ b/ndn-cxx/util/io.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2013-2019 Regents of the University of California.
+ * Copyright (c) 2013-2021 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -52,27 +52,13 @@
         return os.buf();
     }
   }
-  catch (const t::Error& e) {
+  catch (const std::runtime_error& e) {
     NDN_THROW_NESTED(Error(e.what()));
   }
 
   NDN_THROW(std::invalid_argument("Unknown IoEncoding " + to_string(encoding)));
 }
 
-optional<Block>
-loadBlock(std::istream& is, IoEncoding encoding)
-{
-  try {
-    return make_optional<Block>(loadBuffer(is, encoding));
-  }
-  catch (const std::invalid_argument&) {
-    return nullopt;
-  }
-  catch (const std::runtime_error&) {
-    return nullopt;
-  }
-}
-
 void
 saveBuffer(const uint8_t* buf, size_t size, std::ostream& os, IoEncoding encoding)
 {
@@ -91,18 +77,12 @@
         return;
     }
   }
-  catch (const t::Error& e) {
+  catch (const std::runtime_error& e) {
     NDN_THROW_NESTED(Error(e.what()));
   }
 
   NDN_THROW(std::invalid_argument("Unknown IoEncoding " + to_string(encoding)));
 }
 
-void
-saveBlock(const Block& block, std::ostream& os, IoEncoding encoding)
-{
-  saveBuffer(block.wire(), block.size(), os, encoding);
-}
-
 } // namespace io
 } // namespace ndn
diff --git a/ndn-cxx/util/io.hpp b/ndn-cxx/util/io.hpp
index 99b798d..7a2afd6 100644
--- a/ndn-cxx/util/io.hpp
+++ b/ndn-cxx/util/io.hpp
@@ -62,7 +62,7 @@
 
 template<typename T>
 static void
-checkInnerError(typename T::Error*)
+checkNestedError(typename T::Error*)
 {
   static_assert(std::is_convertible<typename T::Error*, tlv::Error*>::value,
                 "T::Error, if defined, must be a subclass of ndn::tlv::Error");
@@ -70,56 +70,83 @@
 
 template<typename T>
 static void
-checkInnerError(...)
+checkNestedError(...)
 {
   // T::Error is not defined
 }
 
 } // namespace detail
 
-/** \brief Reads bytes from a stream until EOF.
- *  \return a Buffer containing the bytes read from the stream
- *  \throw Error error during loading
- *  \throw std::invalid_argument the specified encoding is not supported
+/**
+ * \brief Reads bytes from a stream until EOF.
+ * \return a Buffer containing the bytes read from the stream
+ * \throw Error An error occurred, e.g., malformed input.
+ * \throw std::invalid_argument The specified encoding is not supported.
  */
 shared_ptr<Buffer>
 loadBuffer(std::istream& is, IoEncoding encoding = BASE64);
 
-/** \brief Reads a TLV block from a stream.
- *  \return a Block, or nullopt if an error occurs
+/**
+ * \brief Reads a TLV element of type `T` from a stream.
+ * \tparam T Class type representing the TLV element; must be WireDecodable.
+ * \return the parsed TLV element
+ * \throw Error An error occurred, e.g., malformed input.
+ * \throw std::invalid_argument The specified encoding is not supported.
  */
-optional<Block>
-loadBlock(std::istream& is, IoEncoding encoding = BASE64);
+template<typename T>
+T
+loadTlv(std::istream& is, IoEncoding encoding = BASE64)
+{
+  BOOST_CONCEPT_ASSERT((WireDecodable<T>));
 
-/** \brief Reads a TLV element from a stream.
- *  \tparam T type of TLV element; `T` must be WireDecodable and the nested type
- *            `T::Error`, if defined, must be a subclass of ndn::tlv::Error
- *  \return the TLV element, or nullptr if an error occurs
+  auto buf = loadBuffer(is, encoding);
+  try {
+    return T(Block(buf));
+  }
+  catch (const std::exception& e) {
+    NDN_THROW_NESTED(Error("Decode error during load: "s + e.what()));
+  }
+}
+
+/**
+ * \brief Reads a TLV element from a stream.
+ * \tparam T Type of TLV element; `T` must be WireDecodable and the nested type
+ *           `T::Error`, if defined, must be a subclass of ndn::tlv::Error.
+ * \return the TLV element, or nullptr if an error occurs
+ * \note This function has a peculiar error handling behavior. Consider using loadTlv() instead.
  */
 template<typename T>
 shared_ptr<T>
 load(std::istream& is, IoEncoding encoding = BASE64)
 {
   BOOST_CONCEPT_ASSERT((WireDecodable<T>));
-  detail::checkInnerError<T>(nullptr);
+  detail::checkNestedError<T>(nullptr);
 
-  auto block = loadBlock(is, encoding);
-  if (!block) {
+  Block block;
+  try {
+    block = Block(loadBuffer(is, encoding));
+  }
+  catch (const std::invalid_argument&) {
+    return nullptr;
+  }
+  catch (const std::runtime_error&) {
     return nullptr;
   }
 
   try {
-    return make_shared<T>(*block);
+    return make_shared<T>(block);
   }
   catch (const tlv::Error&) {
     return nullptr;
   }
 }
 
-/** \brief Reads a TLV element from a file.
- *  \tparam T type of TLV element; `T` must be WireDecodable and the nested type
- *            `T::Error`, if defined, must be a subclass of ndn::tlv::Error
- *  \return the TLV element, or nullptr if an error occurs
+/**
+ * \brief Reads a TLV element from a file.
+ * \tparam T Type of TLV element; `T` must be WireDecodable and the nested type
+ *           `T::Error`, if defined, must be a subclass of ndn::tlv::Error.
+ * \return the TLV element, or nullptr if an error occurs
+ * \note This function has a peculiar error handling behavior. Consider using loadTlv() instead.
  */
 template<typename T>
 shared_ptr<T>
@@ -136,13 +163,6 @@
 void
 saveBuffer(const uint8_t* buf, size_t size, std::ostream& os, IoEncoding encoding = BASE64);
 
-/** \brief Writes a TLV block to a stream.
- *  \throw Error error during saving
- *  \throw std::invalid_argument the specified encoding is not supported
- */
-void
-saveBlock(const Block& block, std::ostream& os, IoEncoding encoding = BASE64);
-
 /** \brief Writes a TLV element to a stream.
  *  \tparam T type of TLV element; `T` must be WireEncodable and the nested type
  *            `T::Error`, if defined, must be a subclass of ndn::tlv::Error
@@ -154,17 +174,17 @@
 save(const T& obj, std::ostream& os, IoEncoding encoding = BASE64)
 {
   BOOST_CONCEPT_ASSERT((WireEncodable<T>));
-  detail::checkInnerError<T>(nullptr);
+  detail::checkNestedError<T>(nullptr);
 
   Block block;
   try {
     block = obj.wireEncode();
   }
-  catch (const tlv::Error&) {
-    NDN_THROW_NESTED(Error("Encode error during save"));
+  catch (const tlv::Error& e) {
+    NDN_THROW_NESTED(Error("Encode error during save: "s + e.what()));
   }
 
-  saveBlock(block, os, encoding);
+  saveBuffer(block.wire(), block.size(), os, encoding);
 }
 
 /** \brief Writes a TLV element to a file.
diff --git a/tests/unit/util/io.t.cpp b/tests/unit/util/io.t.cpp
index 1ebaf61..8b19fe3 100644
--- a/tests/unit/util/io.t.cpp
+++ b/tests/unit/util/io.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2013-2020 Regents of the University of California.
+ * Copyright (c) 2013-2021 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -92,6 +92,7 @@
 BOOST_AUTO_TEST_CASE(UnknownIoEncoding)
 {
   std::stringstream ss;
+  BOOST_CHECK_THROW(io::loadTlv<Name>(ss, static_cast<io::IoEncoding>(5)), std::invalid_argument);
   BOOST_CHECK_THROW(io::loadBuffer(ss, static_cast<io::IoEncoding>(5)), std::invalid_argument);
   BOOST_CHECK_THROW(io::saveBuffer(nullptr, 0, ss, static_cast<io::IoEncoding>(5)), std::invalid_argument);
 }
@@ -112,7 +113,8 @@
     boost::filesystem::remove(filepath, ec); // ignore error
   }
 
-  /** \brief create a directory at filename, so that it's neither readable nor writable as a file
+  /**
+   * \brief create a directory at `filepath`, so that it's neither readable nor writable as a file
    */
   void
   mkdir() const
@@ -171,25 +173,20 @@
   bool shouldThrow = false;
 };
 
-template<bool SHOULD_THROW>
-class DecodableTypeTpl
+class DecodableType
 {
 public:
-  DecodableTypeTpl() = default;
+  DecodableType() = default;
 
   explicit
-  DecodableTypeTpl(const Block& block)
+  DecodableType(const Block& block)
   {
-    this->wireDecode(block);
+    wireDecode(block);
   }
 
   void
   wireDecode(const Block& block)
   {
-    if (SHOULD_THROW) {
-      NDN_THROW(tlv::Error("decode error"));
-    }
-
     // block must be 0xBB, 0x01, 0xEE
     BOOST_CHECK_EQUAL(block.type(), 0xBB);
     BOOST_REQUIRE_EQUAL(block.value_size(), 1);
@@ -197,14 +194,32 @@
   }
 };
 
-using DecodableType = DecodableTypeTpl<false>;
-using DecodableTypeThrow = DecodableTypeTpl<true>;
+class DecodableTypeThrow
+{
+public:
+  DecodableTypeThrow() = default;
+
+  explicit
+  DecodableTypeThrow(const Block& block)
+  {
+    wireDecode(block);
+  }
+
+  void
+  wireDecode(const Block&)
+  {
+    NDN_THROW(tlv::Error("decode error"));
+  }
+};
 
 BOOST_AUTO_TEST_CASE(LoadNoEncoding)
 {
   this->writeFile<std::vector<uint8_t>>({0xBB, 0x01, 0xEE});
   shared_ptr<DecodableType> decoded = io::load<DecodableType>(filename, io::NO_ENCODING);
   BOOST_CHECK(decoded != nullptr);
+
+  std::ifstream ifs(filename);
+  BOOST_CHECK_NO_THROW(io::loadTlv<DecodableType>(ifs, io::NO_ENCODING));
 }
 
 BOOST_AUTO_TEST_CASE(LoadBase64)
@@ -212,6 +227,9 @@
   this->writeFile<std::string>("uwHu\n"); // printf '\xBB\x01\xEE' | base64
   shared_ptr<DecodableType> decoded = io::load<DecodableType>(filename, io::BASE64);
   BOOST_CHECK(decoded != nullptr);
+
+  std::ifstream ifs(filename);
+  BOOST_CHECK_NO_THROW(io::loadTlv<DecodableType>(ifs, io::BASE64));
 }
 
 BOOST_AUTO_TEST_CASE(LoadBase64Newline64)
@@ -225,6 +243,9 @@
   //         \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' | base64
   shared_ptr<name::Component> decoded = io::load<name::Component>(filename, io::BASE64);
   BOOST_CHECK(decoded != nullptr);
+
+  std::ifstream ifs(filename);
+  BOOST_CHECK_NO_THROW(io::loadTlv<name::Component>(ifs, io::BASE64));
 }
 
 BOOST_AUTO_TEST_CASE(LoadBase64Newline32)
@@ -235,6 +256,9 @@
     "AAAAAAAAAAAA\n");
   shared_ptr<name::Component> decoded = io::load<name::Component>(filename, io::BASE64);
   BOOST_CHECK(decoded != nullptr);
+
+  std::ifstream ifs(filename);
+  BOOST_CHECK_NO_THROW(io::loadTlv<name::Component>(ifs, io::BASE64));
 }
 
 BOOST_AUTO_TEST_CASE(LoadBase64NewlineEnd)
@@ -243,6 +267,9 @@
     "CEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n");
   shared_ptr<name::Component> decoded = io::load<name::Component>(filename, io::BASE64);
   BOOST_CHECK(decoded != nullptr);
+
+  std::ifstream ifs(filename);
+  BOOST_CHECK_NO_THROW(io::loadTlv<name::Component>(ifs, io::BASE64));
 }
 
 BOOST_AUTO_TEST_CASE(LoadBase64NoNewline)
@@ -251,6 +278,9 @@
     "CEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
   shared_ptr<name::Component> decoded = io::load<name::Component>(filename, io::BASE64);
   BOOST_CHECK(decoded != nullptr);
+
+  std::ifstream ifs(filename);
+  BOOST_CHECK_NO_THROW(io::loadTlv<name::Component>(ifs, io::BASE64));
 }
 
 BOOST_AUTO_TEST_CASE(LoadHex)
@@ -258,14 +288,20 @@
   this->writeFile<std::string>("BB01EE");
   shared_ptr<DecodableType> decoded = io::load<DecodableType>(filename, io::HEX);
   BOOST_CHECK(decoded != nullptr);
+
+  std::ifstream ifs(filename);
+  BOOST_CHECK_NO_THROW(io::loadTlv<DecodableType>(ifs, io::HEX));
 }
 
-BOOST_AUTO_TEST_CASE(LoadException)
+BOOST_AUTO_TEST_CASE(LoadDecodeException)
 {
   this->writeFile<std::vector<uint8_t>>({0xBB, 0x01, 0xEE});
   shared_ptr<DecodableTypeThrow> decoded;
   BOOST_CHECK_NO_THROW(decoded = io::load<DecodableTypeThrow>(filename, io::NO_ENCODING));
   BOOST_CHECK(decoded == nullptr);
+
+  std::ifstream ifs(filename);
+  BOOST_CHECK_THROW(io::loadTlv<DecodableTypeThrow>(ifs, io::NO_ENCODING), io::Error);
 }
 
 BOOST_AUTO_TEST_CASE(LoadNotHex)
@@ -274,6 +310,20 @@
   shared_ptr<DecodableType> decoded;
   BOOST_CHECK_NO_THROW(decoded = io::load<DecodableType>(filename, io::HEX));
   BOOST_CHECK(decoded == nullptr);
+
+  std::ifstream ifs(filename);
+  BOOST_CHECK_THROW(io::loadTlv<DecodableType>(ifs, io::HEX), io::Error);
+}
+
+BOOST_AUTO_TEST_CASE(LoadEmpty)
+{
+  this->writeFile<std::vector<uint8_t>>({});
+  shared_ptr<DecodableType> decoded;
+  BOOST_CHECK_NO_THROW(decoded = io::load<DecodableType>(filename, io::NO_ENCODING));
+  BOOST_CHECK(decoded == nullptr);
+
+  std::ifstream ifs(filename);
+  BOOST_CHECK_THROW(io::loadTlv<DecodableType>(ifs, io::NO_ENCODING), io::Error);
 }
 
 BOOST_AUTO_TEST_CASE(LoadFileNotReadable)
diff --git a/tools/ndnsec/cert-dump.cpp b/tools/ndnsec/cert-dump.cpp
index 5a388d7..9f86f1c 100644
--- a/tools/ndnsec/cert-dump.cpp
+++ b/tools/ndnsec/cert-dump.cpp
@@ -22,8 +22,6 @@
 #include "ndnsec.hpp"
 #include "util.hpp"
 
-#include "ndn-cxx/util/io.hpp"
-
 #include <boost/asio/ip/tcp.hpp>
 #if BOOST_VERSION < 106700
 #include <boost/date_time/posix_time/posix_time_duration.hpp>
@@ -109,13 +107,7 @@
 
   security::Certificate certificate;
   if (isFileName) {
-    try {
-      certificate = loadCertificate(name);
-    }
-    catch (const CannotLoadCertificate&) {
-      std::cerr << "ERROR: Cannot load the certificate from `" << name << "`" << std::endl;
-      return 1;
-    }
+    certificate = loadFromFile<security::Certificate>(name);
   }
   else {
     KeyChain keyChain;
diff --git a/tools/ndnsec/cert-gen.cpp b/tools/ndnsec/cert-gen.cpp
index 9c11ca4..8772062 100644
--- a/tools/ndnsec/cert-gen.cpp
+++ b/tools/ndnsec/cert-gen.cpp
@@ -124,14 +124,7 @@
 
   KeyChain keyChain;
 
-  security::Certificate certRequest;
-  try {
-    certRequest = loadCertificate(requestFile);
-  }
-  catch (const CannotLoadCertificate&) {
-    std::cerr << "ERROR: Cannot load the request from `" << requestFile << "`" << std::endl;
-    return 1;
-  }
+  auto certRequest = loadFromFile<security::Certificate>(requestFile);
 
   // validate that the content is a public key
   Buffer keyContent = certRequest.getPublicKey();
@@ -165,9 +158,9 @@
 
   keyChain.sign(cert, security::SigningInfo(identity).setSignatureInfo(signatureInfo));
 
-  const Block& wire = cert.wireEncode();
   {
     using namespace security::transform;
+    const auto& wire = cert.wireEncode();
     bufferSource(wire.wire(), wire.size()) >> base64Encode(true) >> streamSink(std::cout);
   }
 
diff --git a/tools/ndnsec/cert-install.cpp b/tools/ndnsec/cert-install.cpp
index 7a6afa2..caa10b6 100644
--- a/tools/ndnsec/cert-install.cpp
+++ b/tools/ndnsec/cert-install.cpp
@@ -159,8 +159,8 @@
   }
 
   security::Certificate cert;
-  try {
-    if (certFile.find("http://") == 0) {
+  if (certFile.find("http://") == 0) {
+    try {
       std::string host;
       std::string port;
       std::string path;
@@ -172,7 +172,6 @@
         NDN_THROW(HttpException("Request line is not correctly formatted"));
 
       size_t posPort = certFile.find(':', pos);
-
       if (posPort != std::string::npos && posPort < posSlash) {
         // port is specified
         port = certFile.substr(posPort + 1, posSlash - posPort - 1);
@@ -187,13 +186,14 @@
 
       cert = getCertificateHttp(host, port, path);
     }
-    else {
-      cert = loadCertificate(certFile);
+    catch (const std::runtime_error& e) {
+      std::cerr << "ERROR: Cannot download the certificate from '" << certFile
+                << "': " << e.what() << std::endl;
+      return 1;
     }
   }
-  catch (const CannotLoadCertificate&) {
-    std::cerr << "ERROR: Cannot load the certificate from `" << certFile << "`" << std::endl;
-    return 1;
+  else {
+    cert = loadFromFile<security::Certificate>(certFile);
   }
 
   KeyChain keyChain;
diff --git a/tools/ndnsec/export.cpp b/tools/ndnsec/export.cpp
index f0a9395..f11cc96 100644
--- a/tools/ndnsec/export.cpp
+++ b/tools/ndnsec/export.cpp
@@ -23,7 +23,6 @@
 #include "util.hpp"
 
 #include "ndn-cxx/security/impl/openssl.hpp"
-#include "ndn-cxx/util/io.hpp"
 #include "ndn-cxx/util/scope.hpp"
 
 namespace ndn {
diff --git a/tools/ndnsec/import.cpp b/tools/ndnsec/import.cpp
index 7e27bee..2fb6755 100644
--- a/tools/ndnsec/import.cpp
+++ b/tools/ndnsec/import.cpp
@@ -23,7 +23,6 @@
 #include "util.hpp"
 
 #include "ndn-cxx/security/impl/openssl.hpp"
-#include "ndn-cxx/util/io.hpp"
 #include "ndn-cxx/util/scope.hpp"
 
 namespace ndn {
@@ -74,11 +73,7 @@
 
   KeyChain keyChain;
 
-  shared_ptr<security::SafeBag> safeBag;
-  if (input == "-")
-    safeBag = io::load<security::SafeBag>(std::cin);
-  else
-    safeBag = io::load<security::SafeBag>(input);
+  auto safeBag = loadFromFile<security::SafeBag>(input);
 
   if (password.empty()) {
     int count = 3;
@@ -91,7 +86,7 @@
     }
   }
 
-  keyChain.importSafeBag(*safeBag, password.data(), password.size());
+  keyChain.importSafeBag(safeBag, password.data(), password.size());
 
   return 0;
 }
diff --git a/tools/ndnsec/key-gen.cpp b/tools/ndnsec/key-gen.cpp
index 2090f74..6773e72 100644
--- a/tools/ndnsec/key-gen.cpp
+++ b/tools/ndnsec/key-gen.cpp
@@ -22,8 +22,6 @@
 #include "ndnsec.hpp"
 #include "util.hpp"
 
-#include "ndn-cxx/util/io.hpp"
-
 namespace ndn {
 namespace ndnsec {
 
diff --git a/tools/ndnsec/main.cpp b/tools/ndnsec/main.cpp
index 024e5d5..e731b5c 100644
--- a/tools/ndnsec/main.cpp
+++ b/tools/ndnsec/main.cpp
@@ -22,6 +22,7 @@
 #include "ndnsec.hpp"
 
 #include "ndn-cxx/util/logger.hpp"
+#include "ndn-cxx/util/logging.hpp"
 #include "ndn-cxx/version.hpp"
 
 #include <boost/exception/diagnostic_information.hpp>
@@ -111,6 +112,7 @@
   catch (const std::exception& e) {
     std::cerr << "ERROR: " << e.what() << std::endl;
     NDN_LOG_ERROR(boost::diagnostic_information(e));
+    ndn::util::Logging::flush();
     return 1;
   }
 
diff --git a/tools/ndnsec/ndnsec-pch.hpp b/tools/ndnsec/ndnsec-pch.hpp
index c72b650..c059d36 100644
--- a/tools/ndnsec/ndnsec-pch.hpp
+++ b/tools/ndnsec/ndnsec-pch.hpp
@@ -24,8 +24,6 @@
 
 #include "util.hpp"
 
-#include "ndn-cxx/util/io.hpp"
-
 #include <boost/asio/ip/tcp.hpp>
 
 #endif // NDN_CXX_TOOLS_NDNSEC_NDNSEC_PCH_HPP
diff --git a/tools/ndnsec/sign-req.cpp b/tools/ndnsec/sign-req.cpp
index 3548228..6151714 100644
--- a/tools/ndnsec/sign-req.cpp
+++ b/tools/ndnsec/sign-req.cpp
@@ -22,8 +22,6 @@
 #include "ndnsec.hpp"
 #include "util.hpp"
 
-#include "ndn-cxx/util/io.hpp"
-
 namespace ndn {
 namespace ndnsec {
 
diff --git a/tools/ndnsec/util.cpp b/tools/ndnsec/util.cpp
index 0ff7e74..d79df47 100644
--- a/tools/ndnsec/util.cpp
+++ b/tools/ndnsec/util.cpp
@@ -22,7 +22,6 @@
 #include "util.hpp"
 
 #include "ndn-cxx/security/impl/openssl.hpp"
-#include "ndn-cxx/util/io.hpp"
 
 #include <unistd.h>
 
@@ -90,20 +89,5 @@
   NDN_CXX_UNREACHABLE;
 }
 
-security::Certificate
-loadCertificate(const std::string& fileName)
-{
-  shared_ptr<security::Certificate> cert;
-  if (fileName == "-")
-    cert = io::load<security::Certificate>(std::cin);
-  else
-    cert = io::load<security::Certificate>(fileName);
-
-  if (cert == nullptr) {
-    NDN_THROW(CannotLoadCertificate(fileName));
-  }
-  return *cert;
-}
-
 } // namespace ndnsec
 } // namespace ndn
diff --git a/tools/ndnsec/util.hpp b/tools/ndnsec/util.hpp
index 4ce3c04..913198c 100644
--- a/tools/ndnsec/util.hpp
+++ b/tools/ndnsec/util.hpp
@@ -23,6 +23,7 @@
 #define NDN_CXX_TOOLS_NDNSEC_UTIL_HPP
 
 #include "ndn-cxx/security/key-chain.hpp"
+#include "ndn-cxx/util/io.hpp"
 
 #include <iostream>
 
@@ -49,17 +50,29 @@
 getCertificateFromPib(const security::pib::Pib& pib, const Name& name,
                       bool isIdentityName, bool isKeyName, bool isCertName);
 
-class CannotLoadCertificate : public std::runtime_error
+/**
+ * @brief Load a TLV-encoded, base64-armored object from a file named @p filename.
+ */
+template<typename T>
+T
+loadFromFile(const std::string& filename)
 {
-public:
-  CannotLoadCertificate(const std::string& msg)
-    : std::runtime_error(msg)
-  {
-  }
-};
+  try {
+    if (filename == "-") {
+      return io::loadTlv<T>(std::cin, io::BASE64);
+    }
 
-security::Certificate
-loadCertificate(const std::string& fileName);
+    std::ifstream file(filename);
+    if (!file) {
+      NDN_THROW(std::runtime_error("Cannot open '" + filename + "'"));
+    }
+    return io::loadTlv<T>(file, io::BASE64);
+  }
+  catch (const io::Error& e) {
+    NDN_THROW_NESTED(std::runtime_error("Cannot load '" + filename +
+                                        "': malformed TLV or not in base64 format (" + e.what() + ")"));
+  }
+}
 
 bool
 getPassword(std::string& password, const std::string& prompt, bool shouldConfirm = true);
diff --git a/wscript b/wscript
index 966268c..67cd3a9 100644
--- a/wscript
+++ b/wscript
@@ -80,7 +80,6 @@
     conf.env.WITH_TOOLS = conf.options.with_tools
     conf.env.WITH_EXAMPLES = conf.options.with_examples
 
-    conf.find_program('sh', var='SH')
     conf.find_program('dot', var='DOT', mandatory=False)
 
     conf.check_cxx(lib='atomic', uselib_store='ATOMIC', define_name='HAVE_ATOMIC', mandatory=False)