security: add HMAC support in KeyChain and SigningInfo

Refs: #3075
Change-Id: Ia1f557ca7c83f4df7f9f87bbe1b4fc214940dcd8
diff --git a/tests/unit/security/signing-info.t.cpp b/tests/unit/security/signing-info.t.cpp
index 816cb52..e32d5b1 100644
--- a/tests/unit/security/signing-info.t.cpp
+++ b/tests/unit/security/signing-info.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2013-2018 Regents of the University of California.
+ * Copyright (c) 2013-2019 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -88,6 +88,16 @@
   BOOST_CHECK_EQUAL(infoSha.getSignerType(), SigningInfo::SIGNER_TYPE_SHA256);
   BOOST_CHECK_EQUAL(infoSha.getSignerName(), SigningInfo::getEmptyName());
   BOOST_CHECK_EQUAL(infoSha.getDigestAlgorithm(), DigestAlgorithm::SHA256);
+
+  std::string encodedKey("QjM3NEEyNkE3MTQ5MDQzN0FBMDI0RTRGQURENUI0OTdGRE"
+                         "ZGMUE4RUE2RkYxMkY2RkI2NUFGMjcyMEI1OUNDRg==");
+  info.setSigningHmacKey(encodedKey);
+  BOOST_CHECK_EQUAL(info.getSignerType(), SigningInfo::SIGNER_TYPE_HMAC);
+  BOOST_CHECK_EQUAL(info.getDigestAlgorithm(), DigestAlgorithm::SHA256);
+
+  SigningInfo infoHmac(SigningInfo::SIGNER_TYPE_HMAC, info.getSignerName());
+  BOOST_CHECK_EQUAL(infoHmac.getSignerType(), SigningInfo::SIGNER_TYPE_HMAC);
+  BOOST_CHECK_EQUAL(infoHmac.getDigestAlgorithm(), DigestAlgorithm::SHA256);
 }
 
 BOOST_AUTO_TEST_CASE(CustomSignatureInfo)
@@ -127,6 +137,12 @@
   BOOST_CHECK_EQUAL(infoCert.getSignerName(), "/my-cert");
   BOOST_CHECK_EQUAL(infoCert.getDigestAlgorithm(), DigestAlgorithm::SHA256);
 
+  SigningInfo infoHmac("hmac-sha256:QjM3NEEyNkE3MTQ5MDQzN0FBMDI0RTRGQURENU"
+                       "I0OTdGREZGMUE4RUE2RkYxMkY2RkI2NUFGMjcyMEI1OUNDRg==");
+  BOOST_CHECK_EQUAL(infoHmac.getSignerType(), SigningInfo::SIGNER_TYPE_HMAC);
+  BOOST_CHECK_EQUAL(infoHmac.getSignerName().getPrefix(3), SigningInfo::getHmacIdentity());
+  BOOST_CHECK_EQUAL(infoHmac.getDigestAlgorithm(), DigestAlgorithm::SHA256);
+
   SigningInfo infoSha("id:/localhost/identity/digest-sha256");
   BOOST_CHECK_EQUAL(infoSha.getSignerType(), SigningInfo::SIGNER_TYPE_SHA256);
   BOOST_CHECK_EQUAL(infoSha.getSignerName(), SigningInfo::getEmptyName());
@@ -149,6 +165,9 @@
   BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(
                     SigningInfo(SigningInfo::SIGNER_TYPE_SHA256)),
                     "id:/localhost/identity/digest-sha256");
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(
+                    SigningInfo(SigningInfo::SIGNER_TYPE_HMAC, "/localhost/identity/hmac/1234")),
+                    "id:/localhost/identity/hmac/1234");
 }
 
 BOOST_AUTO_TEST_CASE(Chaining)
diff --git a/tests/unit/security/tpm/back-end.t.cpp b/tests/unit/security/tpm/back-end.t.cpp
index 1ef374b..aee042f 100644
--- a/tests/unit/security/tpm/back-end.t.cpp
+++ b/tests/unit/security/tpm/back-end.t.cpp
@@ -87,6 +87,28 @@
   BOOST_CHECK(tpm.getKeyHandle(keyName) == nullptr);
 }
 
+BOOST_AUTO_TEST_CASE(CreateHmacKey)
+{
+  Name identity("/Test/Identity/HMAC");
+
+  BackEndWrapperMem mem;
+  BackEnd& memTpm = mem.getTpm();
+  auto key = memTpm.createKey(identity, HmacKeyParams());
+  BOOST_REQUIRE(key != nullptr);
+  BOOST_CHECK(!key->getKeyName().empty());
+  BOOST_CHECK(memTpm.hasKey(key->getKeyName()));
+
+  BackEndWrapperFile file;
+  BackEnd& fileTpm = file.getTpm();
+  BOOST_CHECK_THROW(fileTpm.createKey(identity, HmacKeyParams()), BackEnd::Error);
+
+#ifdef NDN_CXX_HAVE_OSX_FRAMEWORKS
+  BackEndWrapperOsx osx;
+  BackEnd& osxTpm = osx.getTpm();
+  BOOST_CHECK_THROW(osxTpm.createKey(identity, HmacKeyParams()), BackEnd::Error);
+#endif // NDN_CXX_HAVE_OSX_FRAMEWORKS
+}
+
 BOOST_AUTO_TEST_CASE_TEMPLATE(RsaSigning, T, TestBackEnds)
 {
   T wrapper;
diff --git a/tests/unit/security/transform/private-key.t.cpp b/tests/unit/security/transform/private-key.t.cpp
index 79b3599..2317479 100644
--- a/tests/unit/security/transform/private-key.t.cpp
+++ b/tests/unit/security/transform/private-key.t.cpp
@@ -53,6 +53,7 @@
   PrivateKey sKey;
   BOOST_CHECK_EQUAL(sKey.getKeyType(), KeyType::NONE);
   BOOST_CHECK_EQUAL(sKey.getKeySize(), 0);
+  BOOST_CHECK_THROW(sKey.getKeyDigest(DigestAlgorithm::SHA256), PrivateKey::Error);
   BOOST_CHECK_THROW(sKey.derivePublicKey(), PrivateKey::Error);
   const uint8_t theAnswer = 42;
   BOOST_CHECK_THROW(sKey.decrypt(&theAnswer, sizeof(theAnswer)), PrivateKey::Error);
@@ -62,6 +63,21 @@
   BOOST_CHECK_THROW(sKey.savePkcs8(os, passwd.data(), passwd.size()), PrivateKey::Error);
 }
 
+BOOST_AUTO_TEST_CASE(KeyDigest)
+{
+  const Buffer buf(16);
+  PrivateKey sKey;
+  sKey.loadRaw(KeyType::HMAC, buf.data(), buf.size());
+  auto digest = sKey.getKeyDigest(DigestAlgorithm::SHA256);
+
+  const uint8_t expected[] = {
+    0x37, 0x47, 0x08, 0xff, 0xf7, 0x71, 0x9d, 0xd5, 0x97, 0x9e, 0xc8, 0x75, 0xd5, 0x6c, 0xd2, 0x28,
+    0x6f, 0x6d, 0x3c, 0xf7, 0xec, 0x31, 0x7a, 0x3b, 0x25, 0x63, 0x2a, 0xab, 0x28, 0xec, 0x37, 0xbb,
+  };
+  BOOST_CHECK_EQUAL_COLLECTIONS(digest->begin(), digest->end(),
+                                expected, expected + sizeof(expected));
+}
+
 BOOST_AUTO_TEST_CASE(LoadRaw)
 {
   const Buffer buf(32);
diff --git a/tests/unit/security/v2/key-chain.t.cpp b/tests/unit/security/v2/key-chain.t.cpp
index 8f5844b..17f8569 100644
--- a/tests/unit/security/v2/key-chain.t.cpp
+++ b/tests/unit/security/v2/key-chain.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2013-2018 Regents of the University of California.
+ * Copyright (c) 2013-2019 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -22,6 +22,7 @@
 #include "ndn-cxx/security/v2/key-chain.hpp"
 #include "ndn-cxx/security/signing-helpers.hpp"
 #include "ndn-cxx/security/verification-helpers.hpp"
+#include "ndn-cxx/security/transform/private-key.hpp"
 
 #include "tests/boost-test.hpp"
 #include "tests/identity-management-fixture.hpp"
@@ -222,12 +223,11 @@
   BOOST_CHECK(id);
   BOOST_CHECK(m_keyChain.getPib().getIdentities().find(identityName) != m_keyChain.getPib().getIdentities().end());
   // The first added identity becomes the default identity
-  BOOST_REQUIRE_NO_THROW(m_keyChain.getPib().getDefaultIdentity());
+  BOOST_CHECK_NO_THROW(m_keyChain.getPib().getDefaultIdentity());
   // The default key of the added identity must exist
-  Key key;
-  BOOST_REQUIRE_NO_THROW(key = id.getDefaultKey());
+  Key key = id.getDefaultKey();
   // The default certificate of the default key must exist
-  BOOST_REQUIRE_NO_THROW(key.getDefaultCertificate());
+  BOOST_CHECK_NO_THROW(key.getDefaultCertificate());
 
   // Delete key
   Name key1Name = key.getName();
@@ -242,12 +242,11 @@
   // Create another key
   m_keyChain.createKey(id);
   // The added key becomes the default key.
-  BOOST_REQUIRE_NO_THROW(id.getDefaultKey());
   Key key2 = id.getDefaultKey();
   BOOST_REQUIRE(key2);
   BOOST_CHECK_NE(key2.getName(), key1Name);
   BOOST_CHECK_EQUAL(id.getKeys().size(), 1);
-  BOOST_REQUIRE_NO_THROW(key2.getDefaultCertificate());
+  BOOST_CHECK_NO_THROW(key2.getDefaultCertificate());
 
   // Create the third key
   Key key3 = m_keyChain.createKey(id);
@@ -255,7 +254,7 @@
   // The added key will not be the default key, because the default key already exists
   BOOST_CHECK_EQUAL(id.getDefaultKey().getName(), key2.getName());
   BOOST_CHECK_EQUAL(id.getKeys().size(), 2);
-  BOOST_REQUIRE_NO_THROW(key3.getDefaultCertificate());
+  BOOST_CHECK_NO_THROW(key3.getDefaultCertificate());
 
   // Delete cert
   BOOST_CHECK_EQUAL(key3.getCertificates().size(), 1);
@@ -268,7 +267,7 @@
   // Add cert
   m_keyChain.addCertificate(key3, key3Cert1);
   BOOST_CHECK_EQUAL(key3.getCertificates().size(), 1);
-  BOOST_REQUIRE_NO_THROW(key3.getDefaultCertificate());
+  BOOST_CHECK_NO_THROW(key3.getDefaultCertificate());
   m_keyChain.addCertificate(key3, key3Cert1); // overwriting the cert should work
   BOOST_CHECK_EQUAL(key3.getCertificates().size(), 1);
   // Add another cert
@@ -310,7 +309,10 @@
   Key key = id.getDefaultKey();
   Certificate cert = key.getDefaultCertificate();
 
-  std::list<SigningInfo> signingInfos = {
+  Name hmacKeyName = m_keyChain.createHmacKey();
+  const Tpm& tpm = m_keyChain.getTpm();
+
+  std::vector<SigningInfo> signingInfos = {
     SigningInfo(),
 
     SigningInfo(SigningInfo::SIGNER_TYPE_ID, id.getName()),
@@ -329,6 +331,9 @@
     signingByCertificate(cert.getName()),
     signingByCertificate(cert),
 
+    SigningInfo(SigningInfo::SIGNER_TYPE_HMAC, hmacKeyName),
+    SigningInfo("hmac-sha256:QjM3NEEyNkE3MTQ5MDQzN0FBMDI0RTRGQURENUI0OTdGREZGMUE4RUE2RkYxMkY2RkI2NUFGMjcyMEI1OUNDRg=="),
+
     SigningInfo(SigningInfo::SIGNER_TYPE_SHA256),
     signingWithSha256()
   };
@@ -356,6 +361,25 @@
       BOOST_CHECK(verifyDigest(data, DigestAlgorithm::SHA256));
       BOOST_CHECK(verifyDigest(interest, DigestAlgorithm::SHA256));
     }
+    else if (signingInfo.getSignerType() == SigningInfo::SIGNER_TYPE_HMAC) {
+      Name keyName = signingInfo.getSignerName();
+      BOOST_CHECK_EQUAL(data.getSignature().getType(), tlv::SignatureHmacWithSha256);
+      BOOST_CHECK_EQUAL(interestSignature.getType(), tlv::SignatureHmacWithSha256);
+
+      BOOST_CHECK(bool(tpm.verify(data.wireEncode().value(),
+                                  data.wireEncode().value_size() - data.getSignature().getValue().size(),
+                                  data.getSignature().getValue().value(),
+                                  data.getSignature().getValue().value_size(),
+                                  keyName, DigestAlgorithm::SHA256)));
+
+      const Name& interestName = interest.getName();
+      auto nameBlock = interestName.wireEncode();
+      BOOST_CHECK(bool(tpm.verify(nameBlock.value(),
+                                  nameBlock.value_size() - interestName[signed_interest::POS_SIG_VALUE].size(),
+                                  interestName[signed_interest::POS_SIG_VALUE].blockFromValue().value(),
+                                  interestName[signed_interest::POS_SIG_VALUE].blockFromValue().value_size(),
+                                  keyName, DigestAlgorithm::SHA256)));
+    }
     else {
       BOOST_CHECK_EQUAL(data.getSignature().getType(), tlv::SignatureSha256WithEcdsa);
       BOOST_CHECK_EQUAL(interestSignature.getType(), tlv::SignatureSha256WithEcdsa);
@@ -391,9 +415,21 @@
   BOOST_CHECK(id.getName().isPrefixOf(data.getSignature().getKeyLocator().getName()));
 }
 
+BOOST_FIXTURE_TEST_CASE(ImportPrivateKey, IdentityManagementFixture)
+{
+  Name keyName("/test/device2");
+  std::string rawKey("nPSNOHyZKsg2WLqHAs7MXGb0sjQb4zCT");
+  auto key = make_shared<transform::PrivateKey>();
+  key->loadRaw(KeyType::HMAC, reinterpret_cast<const uint8_t*>(rawKey.data()), rawKey.size());
+
+  m_keyChain.importPrivateKey(keyName, key);
+  BOOST_CHECK_EQUAL(m_keyChain.getTpm().hasKey(keyName), true);
+  BOOST_CHECK_THROW(m_keyChain.importPrivateKey(keyName, key), KeyChain::Error);
+}
+
 BOOST_FIXTURE_TEST_CASE(ExportImport, IdentityManagementFixture)
 {
-  Identity id = addIdentity("/TestKeyChain/ExportIdentity/");
+  Identity id = addIdentity("/TestKeyChain/ExportIdentity");
   Certificate cert = id.getDefaultKey().getDefaultCertificate();
 
   shared_ptr<SafeBag> exported = m_keyChain.exportSafeBag(cert, "1234", 4);
@@ -412,13 +448,11 @@
 
   BOOST_CHECK_EQUAL(m_keyChain.getTpm().hasKey(cert.getKeyName()), true);
   BOOST_CHECK_EQUAL(m_keyChain.getPib().getIdentities().size(), 1);
-  BOOST_REQUIRE_NO_THROW(m_keyChain.getPib().getIdentity(cert.getIdentity()));
   Identity newId = m_keyChain.getPib().getIdentity(cert.getIdentity());
   BOOST_CHECK_EQUAL(newId.getKeys().size(), 1);
-  BOOST_REQUIRE_NO_THROW(newId.getKey(cert.getKeyName()));
   Key newKey = newId.getKey(cert.getKeyName());
   BOOST_CHECK_EQUAL(newKey.getCertificates().size(), 1);
-  BOOST_REQUIRE_NO_THROW(newKey.getCertificate(cert.getName()));
+  BOOST_CHECK_NO_THROW(newKey.getCertificate(cert.getName()));
 
   m_keyChain.deleteIdentity(newId);
   BOOST_CHECK_EQUAL(m_keyChain.getPib().getIdentities().size(), 0);