security: improve pretty-printing of certificates

Change-Id: Ib81a39bab35f3537df086d47d185907daaa1f426
diff --git a/docs/manpages/ndnsec-cert-dump.rst b/docs/manpages/ndnsec-cert-dump.rst
index 04e0107..c5ca0d9 100644
--- a/docs/manpages/ndnsec-cert-dump.rst
+++ b/docs/manpages/ndnsec-cert-dump.rst
@@ -11,7 +11,7 @@
 -----------
 
 :program:`ndnsec-cert-dump` reads a certificate from the **Public Info Base (PIB)**
-or from a file, and prints it on the standard output.
+or from a file, and prints it on the standard output in Base64 encoding.
 
 By default, *name* is interpreted as a certificate name.
 
@@ -44,22 +44,21 @@
 
     $ ndnsec-cert-dump /ndn/test/david/KEY/ksk-1396913058196/ID-CERT/%00%00%01E%3E%9D%A0%DE
 
-Dump a certificate in human-readable format::
+Print the NDN testbed root certificate in human-readable format::
 
-    $ ndnsec-cert-dump -p /ndn/test/david/KEY/ksk-1396913058196/ID-CERT/%00%00%01E%3E%9D%A0%DE
-    Certificate name:
-      /ndn/test/david/KEY/ksk-1396913058196/ID-CERT/%00%00%01E%3E%9D%A0%DE
+    $ curl -A ndnsec -fsLS https://named-data.net/ndnsec/ndn-testbed-root.ndncert.x3.base64 | ndnsec-cert-dump -fp -
+    Certificate Name:
+      /ndn/KEY/%EC%F1L%8EQ%23%15%E0/ndn/%FD%00%00%01u%E6%7F2%10
+    Additional Description:
+      fullname: NDN Testbed Root X3
+    Public Key:
+      Key Type: 256-bit EC
+      MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEGx+3Y4FvYo1eScIvgD74lQhQdzN4
+      zq021dop8t7kGfEpfGdKf2HGpnn4/qoF9iJ1yUZE/7Na8zzO4xT6RpIM0Q==
     Validity:
-      NotBefore: 20140401T000000
-      NotAfter: 20150331T235959
-    Subject Description:
-      2.5.4.41: David
-      2.5.4.10: Some Organization
-    Public key bits:
-    MIIBIDANBgkqhkiG9w0BAQEFAAOCAQ0AMIIBCAKCAQEAtLq50tpynJ15zExEh9l6
-    jvh/HOmwhOppr/hTEPYUn2VTh21+JJIg5pHAAH/DQr8Bq5BGrdDUCIShxSxbaHwu
-    a1y4XgKP1MYBQP/WzcIhIzB/3PBDdmE0jM3Qg6yuk8BOwett3C07GtwW9dfgacwt
-    aC29xIHnYKfryH/gBSIDUIb38M7ILIDgQeIiQcHGHoFO8CbDtKY2OvVkFNgxowAR
-    Xn+gtkIfMdE77Z8p0S21pYfdvPuIpVVLy7lnFdwGzyCi3nmbtd/r9NSTepBczWQz
-    zBbThT7yfeNyHWLu0PZPdz84UpNPsBad3Bx9tT31noIVnt8yyBEfEU1jyEIVkm8U
-    VQIB
+      Not Before: 2020-11-20T16:31:37
+      Not After: 2024-12-31T23:59:59
+    Signature Information:
+      Signature Type: SignatureSha256WithEcdsa
+      Key Locator: Name=/ndn/KEY/%EC%F1L%8EQ%23%15%E0
+      Self-Signed: yes
diff --git a/ndn-cxx/key-locator.cpp b/ndn-cxx/key-locator.cpp
index f29b623..78eed07 100644
--- a/ndn-cxx/key-locator.cpp
+++ b/ndn-cxx/key-locator.cpp
@@ -34,7 +34,7 @@
 static_assert(std::is_base_of<tlv::Error, KeyLocator::Error>::value,
               "KeyLocator::Error must inherit from tlv::Error");
 
-const size_t MAX_KEY_DIGEST_OCTETS_TO_SHOW = 5;
+const size_t MAX_KEY_DIGEST_OCTETS_TO_SHOW = 8;
 
 KeyLocator::KeyLocator() = default;
 
diff --git a/ndn-cxx/security/certificate.cpp b/ndn-cxx/security/certificate.cpp
index de46a44..9b33cdd 100644
--- a/ndn-cxx/security/certificate.cpp
+++ b/ndn-cxx/security/certificate.cpp
@@ -25,7 +25,6 @@
 #include "ndn-cxx/security/certificate.hpp"
 #include "ndn-cxx/security/additional-description.hpp"
 #include "ndn-cxx/security/transform.hpp"
-#include "ndn-cxx/encoding/block-helpers.hpp"
 #include "ndn-cxx/util/indented-stream.hpp"
 
 namespace ndn {
@@ -35,8 +34,7 @@
 BOOST_CONCEPT_ASSERT((WireEncodable<Certificate>));
 BOOST_CONCEPT_ASSERT((WireDecodable<Certificate>));
 
-// /<NameSpace>/KEY/[KeyId]/[IssuerId]/[Version]
-
+// /<IdentityName>/KEY/<KeyId>/<IssuerId>/<Version>
 const ssize_t Certificate::VERSION_OFFSET = -1;
 const ssize_t Certificate::ISSUER_ID_OFFSET = -2;
 const ssize_t Certificate::KEY_ID_OFFSET = -3;
@@ -135,48 +133,72 @@
 bool
 Certificate::isValidName(const Name& certName)
 {
-  // /<NameSpace>/KEY/[KeyId]/[IssuerId]/[Version]
-  return (certName.size() >= Certificate::MIN_CERT_NAME_LENGTH &&
-          certName.get(Certificate::KEY_COMPONENT_OFFSET) == Certificate::KEY_COMPONENT);
+  // /<IdentityName>/KEY/<KeyId>/<IssuerId>/<Version>
+  return certName.size() >= Certificate::MIN_CERT_NAME_LENGTH &&
+         certName[Certificate::KEY_COMPONENT_OFFSET] == Certificate::KEY_COMPONENT;
 }
 
 std::ostream&
 operator<<(std::ostream& os, const Certificate& cert)
 {
-  os << "Certificate name:\n";
-  os << "  " << cert.getName() << "\n";
-  os << "Validity:\n";
-  {
-    os << "  NotBefore: " << time::toIsoString(cert.getValidityPeriod().getPeriod().first) << "\n";
-    os << "  NotAfter: "  << time::toIsoString(cert.getValidityPeriod().getPeriod().second)  << "\n";
-  }
+  os << "Certificate Name:\n"
+     << "  " << cert.getName() << "\n";
 
-  auto additionalDescription = cert.getSignatureInfo().getCustomTlv(tlv::AdditionalDescription);
-  if (additionalDescription) {
+  auto optAddlDesc = cert.getSignatureInfo().getCustomTlv(tlv::AdditionalDescription);
+  if (optAddlDesc) {
     os << "Additional Description:\n";
-    for (const auto& item : AdditionalDescription(*additionalDescription)) {
-      os << "  " << item.first << ": " << item.second << "\n";
+    try {
+      AdditionalDescription additionalDesc(*optAddlDesc);
+      for (const auto& item : additionalDesc) {
+        os << "  " << item.first << ": " << item.second << "\n";
+      }
+    }
+    catch (const tlv::Error&) {
+      using namespace transform;
+      util::IndentedStream os2(os, "  ");
+      bufferSource(optAddlDesc->value_bytes()) >> base64Encode() >> streamSink(os2);
     }
   }
 
-  os << "Public key bits:\n";
+  os << "Public Key:\n";
   {
     using namespace transform;
-    util::IndentedStream os2(os, "  ");
-    bufferSource(cert.getPublicKey()) >> base64Encode() >> streamSink(os2);
+
+    os << "  Key Type: ";
+    try {
+      PublicKey key;
+      key.loadPkcs8(cert.getPublicKey());
+      os << key.getKeySize() << "-bit " << key.getKeyType();
+    }
+    catch (const std::runtime_error&) {
+      os << "Unknown (" << cert.getContent().value_size() << " bytes)";
+    }
+    os << "\n";
+
+    if (cert.getContent().value_size() > 0) {
+      util::IndentedStream os2(os, "  ");
+      bufferSource(cert.getPublicKey()) >> base64Encode() >> streamSink(os2);
+    }
   }
 
-  os << "Signature Information:\n";
-  {
-    os << "  Signature Type: " << static_cast<tlv::SignatureTypeValue>(cert.getSignatureType()) << "\n";
+  try {
+    const auto& validityPeriod = cert.getValidityPeriod().getPeriod();
+    os << "Validity:\n"
+       << "  Not Before: " << time::toIsoExtendedString(validityPeriod.first) << "\n"
+       << "  Not After: "  << time::toIsoExtendedString(validityPeriod.second)  << "\n";
+  }
+  catch (const tlv::Error&) {
+    // ignore
+  }
 
-    auto keyLoc = cert.getKeyLocator();
-    if (keyLoc) {
-      os << "  Key Locator: ";
-      if (keyLoc->getType() == tlv::Name && keyLoc->getName() == cert.getKeyName()) {
-        os << "Self-Signed ";
-      }
-      os << *keyLoc << "\n";
+  os << "Signature Information:\n"
+     << "  Signature Type: " << static_cast<tlv::SignatureTypeValue>(cert.getSignatureType()) << "\n";
+
+  auto keyLoc = cert.getKeyLocator();
+  if (keyLoc) {
+    os << "  Key Locator: " << *keyLoc << "\n";
+    if (keyLoc->getType() == tlv::Name && keyLoc->getName() == cert.getKeyName()) {
+      os << "  Self-Signed: yes\n";
     }
   }
 
diff --git a/ndn-cxx/security/transform/public-key.cpp b/ndn-cxx/security/transform/public-key.cpp
index 7312d26..6427f4d 100644
--- a/ndn-cxx/security/transform/public-key.cpp
+++ b/ndn-cxx/security/transform/public-key.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2013-2021 Regents of the University of California.
+ * Copyright (c) 2013-2022 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -84,6 +84,18 @@
   }
 }
 
+size_t
+PublicKey::getKeySize() const
+{
+  switch (getKeyType()) {
+  case KeyType::RSA:
+  case KeyType::EC:
+    return static_cast<size_t>(EVP_PKEY_bits(m_impl->key));
+  default:
+    return 0;
+  }
+}
+
 void
 PublicKey::loadPkcs8(span<const uint8_t> buf)
 {
diff --git a/ndn-cxx/security/transform/public-key.hpp b/ndn-cxx/security/transform/public-key.hpp
index fb7b457..bd1a0c8 100644
--- a/ndn-cxx/security/transform/public-key.hpp
+++ b/ndn-cxx/security/transform/public-key.hpp
@@ -43,21 +43,27 @@
 
 public:
   /**
-   * @brief Create an empty public key instance
+   * @brief Create an empty public key instance.
    *
-   * One must call loadXXXX(...) to load a public key.
+   * One must call `loadXXXX(...)` to load a public key.
    */
   PublicKey();
 
   ~PublicKey();
 
   /**
-   * @brief Get the type of the public key
+   * @brief Return the type of the public key.
    */
   KeyType
   getKeyType() const;
 
   /**
+   * @brief Return the size of the public key in bits.
+   */
+  size_t
+  getKeySize() const;
+
+  /**
    * @brief Load the public key in PKCS#8 format from a buffer @p buf
    */
   void
diff --git a/tests/unit/key-locator.t.cpp b/tests/unit/key-locator.t.cpp
index cfcda4d..d7dfe9f 100644
--- a/tests/unit/key-locator.t.cpp
+++ b/tests/unit/key-locator.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2013-2021 Regents of the University of California.
+ * Copyright (c) 2013-2022 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -123,13 +123,13 @@
   BOOST_CHECK_EQUAL(b.getType(), tlv::KeyDigest);
   BOOST_CHECK_EQUAL(b.getKeyDigest(), expectedDigestBlock);
   BOOST_CHECK_THROW(b.getName(), KeyLocator::Error);
-  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(b), "KeyDigest=123456789A...");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(b), "KeyDigest=123456789ABCDEF1...");
 
-  b.setKeyDigest("1D03BCDEF1"_block);
+  b.setKeyDigest("1D050123456789"_block);
   BOOST_CHECK_EQUAL(b.getType(), tlv::KeyDigest);
-  BOOST_CHECK_EQUAL(b.getKeyDigest(), "1D03BCDEF1"_block);
+  BOOST_CHECK_EQUAL(b.getKeyDigest(), "1D050123456789"_block);
   BOOST_CHECK_THROW(b.getName(), KeyLocator::Error);
-  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(b), "KeyDigest=BCDEF1");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(b), "KeyDigest=0123456789");
 }
 
 BOOST_AUTO_TEST_CASE(TypeUnknown)
@@ -178,8 +178,8 @@
   BOOST_CHECK_EQUAL(a == b, true);
   BOOST_CHECK_EQUAL(a != b, false);
 
-  const char digestOctets[] = "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD";
-  auto digestBuffer = make_shared<Buffer>(digestOctets, 8);
+  const uint8_t digestOctets[] = "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD";
+  auto digestBuffer = std::make_shared<Buffer>(digestOctets, sizeof(digestOctets) - 1);
 
   a.setKeyDigest(digestBuffer);
   BOOST_CHECK_EQUAL(a == b, false);
diff --git a/tests/unit/security/certificate.t.cpp b/tests/unit/security/certificate.t.cpp
index 63b29a6..d0633f0 100644
--- a/tests/unit/security/certificate.t.cpp
+++ b/tests/unit/security/certificate.t.cpp
@@ -22,6 +22,8 @@
  */
 
 #include "ndn-cxx/security/certificate.hpp"
+#include "ndn-cxx/encoding/block-helpers.hpp"
+#include "ndn-cxx/util/io.hpp"
 
 #include "tests/boost-test.hpp"
 #include "tests/unit/clock-fixture.hpp"
@@ -145,12 +147,11 @@
   BOOST_CHECK_EQUAL(certificate.getIdentity(), "/ndn/site1");
   BOOST_CHECK_EQUAL(certificate.getIssuerId(), name::Component("0123"));
   BOOST_CHECK_EQUAL(certificate.getKeyId(), name::Component("ksk-1416425377094"));
-  BOOST_REQUIRE_EQUAL(certificate.getKeyLocator().has_value(), true);
-  BOOST_CHECK_EQUAL(certificate.getKeyLocator()->getName(), "/ndn/site1/KEY/ksk-2516425377094");
+  BOOST_CHECK_EQUAL(certificate.getKeyLocator().value().getName(), "/ndn/site1/KEY/ksk-2516425377094");
   BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(certificate.getValidityPeriod()),
                     "(20150814T223739, 20150818T223738)");
 
-  BOOST_CHECK_THROW(certificate.getExtension(12345), ndn::Data::Error);
+  BOOST_CHECK_THROW(certificate.getExtension(12345), Data::Error);
   BOOST_CHECK_NO_THROW(certificate.getPublicKey());
 
   Data data(block);
@@ -171,12 +172,11 @@
   BOOST_CHECK_EQUAL(certificate.getIdentity(), "/ndn/site1");
   BOOST_CHECK_EQUAL(certificate.getIssuerId(), name::Component("0123"));
   BOOST_CHECK_EQUAL(certificate.getKeyId(), name::Component("ksk-1416425377094"));
-  BOOST_REQUIRE_EQUAL(certificate.getKeyLocator().has_value(), true);
-  BOOST_CHECK_EQUAL(certificate.getKeyLocator()->getName(), "/ndn/site1/KEY/ksk-2516425377094");
+  BOOST_CHECK_EQUAL(certificate.getKeyLocator().value().getName(), "/ndn/site1/KEY/ksk-2516425377094");
   BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(certificate.getValidityPeriod()),
                     "(20141111T050000, 20141111T060000)");
 
-  BOOST_CHECK_THROW(certificate.getExtension(12345), ndn::Data::Error);
+  BOOST_CHECK_THROW(certificate.getExtension(12345), Data::Error);
   BOOST_CHECK_NO_THROW(certificate.getPublicKey());
 }
 
@@ -202,15 +202,13 @@
   InvalidCertFixture()
   {
     Certificate certBase(Block{CERT});
-    BOOST_CHECK_NO_THROW((Certificate(certBase)));
-
     m_certBase = Data(certBase);
     generateFakeSignature(m_certBase);
 
-    BOOST_CHECK_NO_THROW((Certificate(m_certBase)));
+    BOOST_REQUIRE_NO_THROW(Certificate{m_certBase});
   }
 
-public:
+protected:
   Data m_certBase;
 };
 
@@ -249,28 +247,129 @@
   BOOST_CHECK_THROW(cert.getPublicKey(), Certificate::Error);
 }
 
-BOOST_AUTO_TEST_CASE(PrintCertificateInfo)
+BOOST_AUTO_TEST_CASE(Print)
 {
-  const std::string expectedCertificateInfo = std::string(R"INFO(
-Certificate name:
+  const std::string expected1(
+R"TXT(Certificate Name:
+  /
+Public Key:
+  Key Type: Unknown (0 bytes)
+Signature Information:
+  Signature Type: Unknown(65535)
+)TXT");
+
+  Certificate cert1;
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(cert1), expected1);
+
+  const std::string expected2(
+R"TXT(Certificate Name:
   /ndn/site1/KEY/ksk-1416425377094/0123/%FD%00%00%01I%C9%8B
-Validity:
-  NotBefore: 20150814T223739
-  NotAfter: 20150818T223738
-Public key bits:
+Public Key:
+  Key Type: 1024-bit RSA
   MIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQCeBj5HhbI0N6qFR6wDJIO1nKgF
   OiQe64kBu+mbssMirGjj8GwCzmimxNCnBpCcqhsIHYtDmjNnRG0hoxuImpdeWcQV
   C9ksvVEHYYKtwbjXv5vPfSTCY/OXF+v+YiW6W02Kwnq9Q4qPuPLxxWow01CMyJrf
   7+0153pi6nZ8uwgmxwIBEQ==
+Validity:
+  Not Before: 2015-08-14T22:37:39
+  Not After: 2015-08-18T22:37:38
 Signature Information:
   Signature Type: SignatureSha256WithRsa
   Key Locator: Name=/ndn/site1/KEY/ksk-2516425377094
-)INFO").substr(1);
+)TXT");
 
-  Certificate certificate(Block{CERT});
-  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(certificate), expectedCertificateInfo);
+  Certificate cert2(Block{CERT});
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(cert2), expected2);
 
-  // TODO: Check output formats of other certificates
+  const std::string expected3(
+R"TXT(Certificate Name:
+  /ndn/test/identity/KEY/%C7G%3A%D6%12P%B5%F0/self/v=1650251820652
+Public Key:
+  Key Type: 256-bit EC
+  MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEnoGVuhO+9JnIRo6QSgJin8RgA8Gh
+  RN9oVbnXi2rAJa4jq1yuCKaLeOt2sdXlkW6uBByOhbTuDdovlbIUsJ8bhg==
+Validity:
+  Not Before: 1970-01-01T00:00:00
+  Not After: 2042-04-13T03:17:00
+Signature Information:
+  Signature Type: SignatureSha256WithEcdsa
+  Key Locator: Name=/ndn/test/identity/KEY/%C7G%3A%D6%12P%B5%F0
+  Self-Signed: yes
+)TXT");
+
+  std::istringstream is(
+R"BASE64(Bv0BPgc0CANuZG4IBHRlc3QICGlkZW50aXR5CANLRVkICMdHOtYSULXwCARzZWxm
+NggAAAGAOqxubBQJGAECGQQANu6AFVswWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC
+AASegZW6E770mchGjpBKAmKfxGADwaFE32hVudeLasAlriOrXK4Ipot463ax1eWR
+bq4EHI6FtO4N2i+VshSwnxuGFlUbAQMcJgckCANuZG4IBHRlc3QICGlkZW50aXR5
+CANLRVkICMdHOtYSULXw/QD9Jv0A/g8xOTcwMDEwMVQwMDAwMDD9AP8PMjA0MjA0
+MTNUMDMxNzAwF0cwRQIgFRnwthtzKdqRgO3cZMNA1hfT3QcNu/+xjo7hUy+UvdsC
+IQCz3DHoRtKl7uZoJOgQsZP1/CGkNjlGZE3EQ+Ylwiprrw==)BASE64");
+  Certificate cert3 = io::loadTlv<Certificate>(is, io::BASE64);
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(cert3), expected3);
+
+  const std::string expected4(
+R"TXT(Certificate Name:
+  /ndn/test/identity/KEY/%C7G%3A%D6%12P%B5%F0/self/v=1650251820652
+Public Key:
+  Key Type: Unknown (23 bytes)
+  bm90IGEgdmFsaWQgcHVibGljIGtleQA=
+Validity:
+  Not Before: 1970-01-01T00:00:00
+  Not After: 2042-04-13T03:17:00
+Signature Information:
+  Signature Type: SignatureSha256WithEcdsa
+  Key Locator: Name=/ndn/test/identity/KEY/%C7G%3A%D6%12P%B5%F0
+  Self-Signed: yes
+)TXT");
+
+  const uint8_t notAKey[] = "not a valid public key";
+  Certificate cert4(cert3);
+  cert4.setContent(notAKey);
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(cert4), expected4);
+
+  const std::string expected5(
+R"TXT(Certificate Name:
+  /ndn/test/identity/KEY/%C7G%3A%D6%12P%B5%F0/self/v=1650251820652
+Additional Description:
+  bWFsZm9ybWVk
+Public Key:
+  Key Type: 256-bit EC
+  MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEnoGVuhO+9JnIRo6QSgJin8RgA8Gh
+  RN9oVbnXi2rAJa4jq1yuCKaLeOt2sdXlkW6uBByOhbTuDdovlbIUsJ8bhg==
+Validity:
+  Not Before: 1970-01-01T00:00:00
+  Not After: 2042-04-13T03:17:00
+Signature Information:
+  Signature Type: SignatureSha256WithEcdsa
+  Key Locator: KeyDigest=0000000000000000
+)TXT");
+
+  auto sigInfo = cert3.getSignatureInfo();
+  sigInfo.addCustomTlv(makeStringBlock(tlv::AdditionalDescription, "malformed"));
+  sigInfo.setKeyLocator(KeyLocator().setKeyDigest(std::make_shared<Buffer>(8)));
+  Certificate cert5(cert3);
+  cert5.setSignatureInfo(sigInfo);
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(cert5), expected5);
+
+  const std::string expected6(
+R"TXT(Certificate Name:
+  /ndn/test/identity/KEY/%C7G%3A%D6%12P%B5%F0/self/v=1650251820652
+Public Key:
+  Key Type: 256-bit EC
+  MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEnoGVuhO+9JnIRo6QSgJin8RgA8Gh
+  RN9oVbnXi2rAJa4jq1yuCKaLeOt2sdXlkW6uBByOhbTuDdovlbIUsJ8bhg==
+Signature Information:
+  Signature Type: DigestSha256
+)TXT");
+
+  sigInfo.removeCustomTlv(tlv::AdditionalDescription);
+  sigInfo.addCustomTlv(makeStringBlock(tlv::ValidityPeriod, "malformed"));
+  sigInfo.setSignatureType(tlv::DigestSha256);
+  sigInfo.setKeyLocator(nullopt);
+  Certificate cert6(cert3);
+  cert6.setSignatureInfo(sigInfo);
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(cert6), expected6);
 }
 
 BOOST_AUTO_TEST_SUITE_END() // TestCertificate
diff --git a/tests/unit/security/transform/public-key.t.cpp b/tests/unit/security/transform/public-key.t.cpp
index c888482..e844293 100644
--- a/tests/unit/security/transform/public-key.t.cpp
+++ b/tests/unit/security/transform/public-key.t.cpp
@@ -43,7 +43,9 @@
 
 struct RsaKeyTestData
 {
-  const std::string publicKeyPkcs8 =
+  static constexpr KeyType type = KeyType::RSA;
+  static constexpr size_t size = 2048;
+  const std::string pkcs8Base64 =
       "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw0WM1/WhAxyLtEqsiAJg\n"
       "WDZWuzkYpeYVdeeZcqRZzzfRgBQTsNozS5t4HnwTZhwwXbH7k3QN0kRTV826Xobw\n"
       "s3iigohnM9yTK+KKiayPhIAm/+5HGT6SgFJhYhqo1/upWdueojil6RP4/AgavHho\n"
@@ -52,10 +54,14 @@
       "9rH58ynaAix0tcR/nBMRLUX+e3rURHg6UbSjJbdb9qmKM1fTGHKUzL/5pMG6uBU0\n"
       "ywIDAQAB\n";
 };
+constexpr KeyType RsaKeyTestData::type;
+constexpr size_t RsaKeyTestData::size;
 
 struct EcKeyTestData
 {
-  const std::string publicKeyPkcs8 =
+  static constexpr KeyType type = KeyType::EC;
+  static constexpr size_t size = 256;
+  const std::string pkcs8Base64 =
       "MIIBSzCCAQMGByqGSM49AgEwgfcCAQEwLAYHKoZIzj0BAQIhAP////8AAAABAAAA\n"
       "AAAAAAAAAAAA////////////////MFsEIP////8AAAABAAAAAAAAAAAAAAAA////\n"
       "///////////8BCBaxjXYqjqT57PrvVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMVAMSd\n"
@@ -64,23 +70,28 @@
       "//////////+85vqtpxeehPO5ysL8YyVRAgEBA0IABGhuFibgwLdEJBDOLdvSg1Hc\n"
       "5EJTDxq6ls5FoYLfThp8HOjuwGSz0qw8ocMqyku1y0V5peQ4rEPd0bwcpZd9svA=\n";
 };
+constexpr KeyType EcKeyTestData::type;
+constexpr size_t EcKeyTestData::size;
 
 using KeyTestDataSets = boost::mpl::vector<RsaKeyTestData, EcKeyTestData>;
 
-BOOST_AUTO_TEST_CASE_TEMPLATE(SaveLoad, T, KeyTestDataSets)
+BOOST_AUTO_TEST_CASE_TEMPLATE(LoadAndSave, T, KeyTestDataSets)
 {
   T dataSet;
 
-  auto pKeyPkcs8Base64 = make_span(reinterpret_cast<const uint8_t*>(dataSet.publicKeyPkcs8.data()),
-                                   dataSet.publicKeyPkcs8.size());
+  auto pKeyPkcs8Base64 = make_span(reinterpret_cast<const uint8_t*>(dataSet.pkcs8Base64.data()),
+                                   dataSet.pkcs8Base64.size());
   OBufferStream os;
   bufferSource(pKeyPkcs8Base64) >> base64Decode() >> streamSink(os);
   auto pKeyPkcs8 = os.buf();
 
+  // Load
   PublicKey pKey1;
   BOOST_CHECK_NO_THROW(pKey1.loadPkcs8Base64(pKeyPkcs8Base64));
+  BOOST_TEST(pKey1.getKeyType() == T::type);
+  BOOST_TEST(pKey1.getKeySize() == T::size);
 
-  std::stringstream ss2(dataSet.publicKeyPkcs8);
+  std::stringstream ss2(dataSet.pkcs8Base64);
   PublicKey pKey2;
   BOOST_CHECK_NO_THROW(pKey2.loadPkcs8Base64(ss2));
 
@@ -92,15 +103,14 @@
   PublicKey pKey4;
   BOOST_CHECK_NO_THROW(pKey4.loadPkcs8(ss4));
 
+  // Save
   OBufferStream os5;
   BOOST_REQUIRE_NO_THROW(pKey1.savePkcs8Base64(os5));
-  BOOST_CHECK_EQUAL_COLLECTIONS(pKeyPkcs8Base64.begin(), pKeyPkcs8Base64.end(),
-                                os5.buf()->begin(), os5.buf()->end());
+  BOOST_TEST(*os5.buf() == pKeyPkcs8Base64, boost::test_tools::per_element());
 
   OBufferStream os6;
   BOOST_REQUIRE_NO_THROW(pKey1.savePkcs8(os6));
-  BOOST_CHECK_EQUAL_COLLECTIONS(pKeyPkcs8->begin(), pKeyPkcs8->end(),
-                                os6.buf()->begin(), os6.buf()->end());
+  BOOST_TEST(*os6.buf() == *pKeyPkcs8, boost::test_tools::per_element());
 }
 
 // NOTE: We cannot test RSA encryption by comparing the computed ciphertext to
@@ -113,9 +123,8 @@
   EcKeyTestData dataSet;
 
   PublicKey pKey;
-  pKey.loadPkcs8Base64({reinterpret_cast<const uint8_t*>(dataSet.publicKeyPkcs8.data()),
-                        dataSet.publicKeyPkcs8.size()});
-  BOOST_CHECK_EQUAL(pKey.getKeyType(), KeyType::EC);
+  pKey.loadPkcs8Base64({reinterpret_cast<const uint8_t*>(dataSet.pkcs8Base64.data()),
+                        dataSet.pkcs8Base64.size()});
 
   OBufferStream os;
   bufferSource("Y2lhbyFob2xhIWhlbGxvIQ==") >> base64Decode() >> streamSink(os);