Access Manager

Change-Id: I5febe225dc8ba2f7967dabb5e1bf0bab6428d6c2
diff --git a/docs/_static/access-manager.png b/docs/_static/access-manager.png
new file mode 100644
index 0000000..6875598
--- /dev/null
+++ b/docs/_static/access-manager.png
Binary files differ
diff --git a/docs/_static/nac-overview.png b/docs/_static/nac-overview.png
new file mode 100644
index 0000000..25a6b8d
--- /dev/null
+++ b/docs/_static/nac-overview.png
Binary files differ
diff --git a/docs/figures/access-manager.pptx b/docs/figures/access-manager.pptx
new file mode 100644
index 0000000..033cc5a
--- /dev/null
+++ b/docs/figures/access-manager.pptx
Binary files differ
diff --git a/docs/figures/nac-overview.pptx b/docs/figures/nac-overview.pptx
new file mode 100644
index 0000000..9656160
--- /dev/null
+++ b/docs/figures/nac-overview.pptx
Binary files differ
diff --git a/docs/spec.rst b/docs/spec.rst
index 30e398d..9701dd6 100644
--- a/docs/spec.rst
+++ b/docs/spec.rst
@@ -1,7 +1,9 @@
 NAC Specification
 =================
 
-TBD
+.. figure:: _static/nac-overview.png
+   :alt: Overview of NAC entities
+   :align: center
 
 Terminology
 -----------
@@ -25,7 +27,7 @@
 +-----------------+------------------------------------------------------------------------------------------+
 
 EncryptedContent
------------------
+----------------
 
 The ``EncryptedContent`` element contains encrypted blob, optional Initial Vector (for AES CBC encryption),
 optional EncryptedPayloadKey, and Name elements.
@@ -34,11 +36,54 @@
 
      EncryptedContent ::= ENCRYPTED-CONTENT-TYPE TLV-LENGTH
                             EncryptedPayload
-                            InitialVector
-                            EncryptedPayloadKey
-                            Name
+                            InitialVector?
+                            EncryptedPayloadKey?
+                            Name?
 
      InitialVector ::= INITIAL-VECTOR-TYPE TLV-LENGTH(=N) BYTE{N}
      EncryptedPayload ::= ENCRYPTED-PAYLOAD-TYPE TLV-LENGTH(=N) BYTE{N}
      EncryptedPayloadKey ::= ENCRYPTED-PAYLOAD-KEY-TYPE TLV-LENGTH(=N) BYTE{N}
      InitialVector ::= INITIAL-VECTOR-TYPE TLV-LENGTH(=N) BYTE{N}
+
+
+Access Manager
+--------------
+
+.. figure:: _static/access-manager.png
+   :alt: Access Manager
+   :align: center
+
+Access Manager controls decryption policy by publishing granular per-namespace access policies in the form of key encryption (KEK, plaintext public) and key decryption (KDK, encrypted private keys) key pairs.
+
+KEK is published as a single data packet with name ``/[access-namespace]/NAC/[dataset]/KEK/[key-id]``, following the following format:
+
+::
+
+   Kek ::= DATA-TYPE TLV-LENGTH
+             Name (= /[access-namespace]/NAC/[dataset]/KEK/[key-id])
+             MetaInfo (= .ContentType = KEY, .FreshnessPeriod = 1 hour)
+             KekContent
+             SignatureInfo
+             SignatureValue
+
+   KekContent ::= CONTENT-TYPE-TLV TLV-LENGTH
+                    BYTE* (= BER of public key /[access-namespace]/NAC/[dataset]/KEY/[key-id])
+
+
+Different versions of KDK are published, encrypted by the public key of the individual authorized member, following naming convention: ``/[access-namespace]/NAC/[dataset]/KDK/[key-id]/ENCRYPTED-BY/<authorized-member>/KEY/[member-key-id]``.  KDK is published in the following format:
+
+::
+
+   Kdk ::= DATA-TYPE TLV-LENGTH
+             Name (= /[access-namespace]/NAC/[dataset]/KDK/[key-id]/ENCRYPTED-BY/<authorized-member>/KEY/[member-key-id])
+             MetaInfo (= .ContentType = KEY, .FreshnessPeriod = 1 hour)
+             KdkContent
+             SignatureInfo
+             SignatureValue
+
+   KdkContent ::= CONTENT-TYPE-TLV TLV-LENGTH
+                    EncryptedContent (=
+                      .EncryptedPayload = SafeBag with private key /[access-namespace]/NAC/[dataset]/KEY/[key-id]
+                      .EncryptedPayloadKey = password for SafeBag, encrypted by public key /<authorized-member>/KEY/[member-key-id]
+                      // .InitialVector and .Name not set
+                    )
diff --git a/src/access-manager.cpp b/src/access-manager.cpp
new file mode 100644
index 0000000..056ef33
--- /dev/null
+++ b/src/access-manager.cpp
@@ -0,0 +1,128 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2018, Regents of the University of California
+ *
+ * NAC library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * NAC library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of NAC library authors and contributors.
+ */
+
+#include "access-manager.hpp"
+#include "encrypted-content.hpp"
+
+#include <ndn-cxx/util/logger.hpp>
+
+NDN_LOG_INIT(nac.AccessManager);
+
+namespace ndn {
+namespace nac {
+
+AccessManager::AccessManager(const Identity& identity, const Name& dataset,
+                             KeyChain& keyChain, Face& face)
+  : m_identity(identity)
+  , m_keyChain(keyChain)
+  , m_face(face)
+{
+  // NAC Identity: <identity>/NAC/<dataset>
+  // generate NAC key
+  auto nacId = m_keyChain.createIdentity(Name(identity.getName()).append(NAC).append(dataset), RsaKeyParams());
+  m_nacKey = nacId.getDefaultKey();
+  if (m_nacKey.getKeyType() != KeyType::RSA) {
+    NDN_LOG_INFO("Cannot re-use existing KEK/KDK pair, as it is not an RSA key, regenerating");
+    m_nacKey = m_keyChain.createKey(nacId, RsaKeyParams());
+  }
+  auto nacKeyId = m_nacKey.getName().at(-1);
+
+  auto kekPrefix = Name(m_nacKey.getIdentity()).append(KEK);
+
+  auto kek = make_shared<Data>(m_nacKey.getDefaultCertificate());
+  kek->setName(Name(kekPrefix).append(nacKeyId));
+  m_keyChain.sign(*kek, signingByIdentity(m_identity));
+  // kek looks like a cert, but doesn't have ValidityPeriod
+  m_ims.insert(*kek);
+
+  auto serveFromIms = [this] (const Name& prefix, const Interest& interest) {
+    auto data = m_ims.find(interest);
+    if (data != nullptr) {
+      NDN_LOG_DEBUG("Serving " << data->getName() << " from InMemoryStorage");
+      m_face.put(*data);
+    }
+    else {
+      NDN_LOG_DEBUG("Didn't find data for " << interest.getName());
+      // send NACK?
+    }
+  };
+
+  auto handleError = [] (const Name& prefix, const std::string& msg) {
+    NDN_LOG_ERROR("Failed to register prefix " << prefix << ": " << msg);
+  };
+
+  m_kekRegId = m_face.setInterestFilter(kekPrefix, serveFromIms, handleError);
+
+  auto kdkPrefix = Name(m_nacKey.getIdentity()).append(KDK).append(nacKeyId);
+  m_kdkRegId = m_face.setInterestFilter(kdkPrefix, serveFromIms, handleError);
+}
+
+AccessManager::~AccessManager()
+{
+  m_face.unsetInterestFilter(m_kekRegId);
+  m_face.unsetInterestFilter(m_kdkRegId);
+}
+
+void
+AccessManager::addMember(const Certificate& memberCert)
+{
+  Name kdkName(m_nacKey.getIdentity());
+  kdkName
+    .append(KDK)
+    .append(m_nacKey.getName().at(-1)) // key-id
+    .append(ENCRYPTED_BY)
+    .append(memberCert.getKeyName());
+
+  const size_t secretLength = 32;
+  uint8_t secret[secretLength + 1];
+  random::generateSecureBytes(secret, secretLength);
+  // because of stupid bug in ndn-cxx, remove all \0 in generated secret, replace with 1
+  for (size_t i = 0; i < secretLength; ++i) {
+    if (secret[i] == 0) {
+      secret[i] = 1;
+    }
+  }
+  secret[secretLength] = 0;
+
+  auto kdkData = m_keyChain.exportSafeBag(m_nacKey.getDefaultCertificate(),
+                                          reinterpret_cast<const char*>(secret), secretLength);
+
+  PublicKey memberKey;
+  memberKey.loadPkcs8(memberCert.getPublicKey().data(), memberCert.getPublicKey().size());
+
+  EncryptedContent content;
+  content.setPayload(kdkData->wireEncode());
+  content.setPayloadKey(memberKey.encrypt(secret, secretLength));
+
+  auto kdk = make_shared<Data>(kdkName);
+  kdk->setContent(content.wireEncode());
+  kdk->setFreshnessPeriod(1_h); // this can serve as soft access control for revoking access
+  m_keyChain.sign(*kdk, signingByIdentity(m_identity));
+
+  m_ims.insert(*kdk);
+}
+
+void
+AccessManager::removeMember(const Name& identity)
+{
+  m_ims.erase(Name(m_nacKey.getName()).append(KDK).append(ENCRYPTED_BY).append(identity));
+}
+
+} // namespace nac
+} // namespace ndn
diff --git a/src/access-manager.hpp b/src/access-manager.hpp
new file mode 100644
index 0000000..4ee592f
--- /dev/null
+++ b/src/access-manager.hpp
@@ -0,0 +1,160 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2018, Regents of the University of California
+ *
+ * NAC library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * NAC library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of NAC library authors and contributors.
+ */
+
+#ifndef NDN_NAC_ACCESS_MANAGER_HPP
+#define NDN_NAC_ACCESS_MANAGER_HPP
+
+#include "common.hpp"
+
+#include <ndn-cxx/face.hpp>
+
+namespace ndn {
+namespace nac {
+
+/**
+ * @brief Access Manager
+ *
+ * Access Manager controls decryption policy by publishing granular per-namespace access
+ * policies in the form of key encryption (KEK, plaintext public) and key decryption (KDK,
+ * encrypted private keys) key pairs.
+ *
+ * TODO Allow rolling KEK
+ */
+class AccessManager
+{
+public:
+  class Error : public std::runtime_error
+  {
+  public:
+    using std::runtime_error::runtime_error;
+  };
+
+public:
+  /**
+   * @param identity Identity of the namespace (i.e., public and private keys)
+   *
+   * @param identity Data owner's namespace identity (will be used to sign KEK and KDK)
+   * @param dataset Name of dataset that this manager is controlling
+   * @param keyChain KeyChain
+   * @param face Face that will be used to publish KEK and KDKs
+   *
+   * Additional info:
+   *
+   *     [identity]/NAC/[dataset]/KEK            || /[key-id]                           (== KEK, public key)
+   *
+   *     [identity]/NAC/[dataset]/KDK/[key-id]   || /ENCRYPTED-BY/[user]/KEY/[key-id]   (== KDK, encrypted private key)
+   *
+   *     \_____________  ______________/
+   *                   \/
+   *          registered with NFD
+   *
+   * AccessManager serves NAC public key for data producers to fetch and encrypted versions of
+   * private keys (as safe bags) for authorized consumers to fetch.
+   */
+  AccessManager(const Identity& identity, const Name& dataset,
+                KeyChain& keyChain, Face& face);
+
+  ~AccessManager();
+
+  /**
+   * @brief Authorize a member identified by its certificate @p memberCert to decrypt data
+   *        under the policy
+   */
+  void
+  addMember(const Certificate& memberCert);
+
+  // void
+  // addMemberWithKey(const Name& keyName);
+
+  // void
+  // addMemberWithIdentity(const Name& identityName);
+
+  /**
+   * @brief Remove member with name @p identity from the group
+   */
+  void
+  removeMember(const Name& identity);
+
+public: // accessor interface for published data packets
+
+  /** @return{ number of packets stored in in-memory storage }
+   */
+  size_t
+  size() const
+  {
+    return m_ims.size();
+  }
+
+  /** @brief Returns begin iterator of the in-memory storage ordering by
+   *  name with digest
+   *
+   *  @return{ const_iterator pointing to the beginning of the m_cache }
+   */
+  InMemoryStorage::const_iterator
+  begin() const
+  {
+    return m_ims.begin();
+  }
+
+  /** @brief Returns end iterator of the in-memory storage ordering by
+   *  name with digest
+   *
+   *  @return{ const_iterator pointing to the end of the m_cache }
+   */
+  InMemoryStorage::const_iterator
+  end() const
+  {
+    return m_ims.end();
+  }
+
+private:
+  Identity m_identity;
+  Key m_nacKey;
+  KeyChain& m_keyChain;
+  Face& m_face;
+
+  // this interface should be general enough to allow plugging in other things
+
+  // /**
+  //  * Should be interface (persisent or in memory) for storing and serving encrypted KDKs
+  //  */
+  // class KdkStorage
+  // {
+  // public:
+  //   void
+  //   addKdk(Data kdk);
+
+  //   void
+  //   removeKdk(const Name& kdkName);
+
+  //   // stuff to serve
+
+  // private:
+  //   InMemoryStorage m_ims;
+  // };
+
+  InMemoryStoragePersistent m_ims;
+  const RegisteredPrefixId* m_kekRegId;
+  const RegisteredPrefixId* m_kdkRegId;
+};
+
+} // namespace nac
+} // namespace ndn
+
+#endif // NDN_NAC_ACCESS_MANAGER_HPP
diff --git a/tests/tests/access-manager.t.cpp b/tests/tests/access-manager.t.cpp
new file mode 100644
index 0000000..f97514a
--- /dev/null
+++ b/tests/tests/access-manager.t.cpp
@@ -0,0 +1,159 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2018, Regents of the University of California
+ *
+ * NAC library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * NAC library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of NAC library authors and contributors.
+ */
+
+#include "access-manager.hpp"
+
+#include "tests-common.hpp"
+#include "dummy-forwarder.hpp"
+
+#include <iostream>
+#include <ndn-cxx/util/string-helper.hpp>
+
+namespace ndn {
+namespace nac {
+namespace tests {
+
+class AccessManagerFixture : public UnitTestTimeFixture
+{
+public:
+  AccessManagerFixture()
+    : fw(m_io, m_keyChain)
+    , face(static_cast<util::DummyClientFace&>(fw.addFace()))
+    , accessIdentity(addIdentity("/access/policy/identity"))
+    , nacIdentity(addIdentity("/access/policy/identity/NAC/dataset", RsaKeyParams())) // hack to get access to KEK key-id
+    , userIdentities{addIdentity("/first/user", RsaKeyParams()),
+                     addIdentity("/second/user", RsaKeyParams())}
+    , manager(accessIdentity, Name("/dataset"), m_keyChain, face)
+  {
+    advanceClocks(1_ms, 10);
+
+    for (auto& user : userIdentities) {
+      manager.addMember(user.getDefaultKey().getDefaultCertificate());
+    }
+  }
+
+public:
+  DummyForwarder fw;
+  util::DummyClientFace& face;
+  Identity accessIdentity;
+  Identity nacIdentity;
+  std::vector<Identity> userIdentities;
+  AccessManager manager;
+};
+
+BOOST_FIXTURE_TEST_SUITE(TestAccessManager, AccessManagerFixture)
+
+BOOST_AUTO_TEST_CASE(PublishedKek)
+{
+  face.receive(Interest(Name("/access/policy/identity/NAC/dataset/KEK"))
+               .setCanBePrefix(true).setMustBeFresh(true));
+  advanceClocks(1_ms, 10);
+
+  BOOST_CHECK_EQUAL(face.sentData.at(0).getName().getPrefix(-1), "/access/policy/identity/NAC/dataset/KEK");
+  BOOST_CHECK_EQUAL(face.sentData.at(0).getName().get(-1), nacIdentity.getDefaultKey().getName().get(-1));
+}
+
+BOOST_AUTO_TEST_CASE(PublishedKdks)
+{
+  for (auto& user : userIdentities) {
+    Name kdk("/access/policy/identity/NAC/dataset/KDK");
+    kdk
+      .append(nacIdentity.getDefaultKey().getName().get(-1))
+      .append("ENCRYPTED-BY")
+      .append(user.getDefaultKey().getName());
+
+    face.receive(Interest(kdk)
+                 .setCanBePrefix(true).setMustBeFresh(true));
+    advanceClocks(1_ms, 10);
+
+    BOOST_TEST_MESSAGE(kdk);
+    BOOST_CHECK_EQUAL(face.sentData.at(0).getName(), kdk);
+    face.sentData.clear();
+  }
+}
+
+BOOST_AUTO_TEST_CASE(EnumerateDataFromIms)
+{
+  BOOST_CHECK_EQUAL(manager.size(), 3);
+  size_t nKek = 0;
+  size_t nKdk = 0;
+  for (const auto& data : manager) {
+    BOOST_TEST_MESSAGE(data.getName());
+    if (data.getName().at(5) == KEK) {
+      ++nKek;
+    }
+    else if (data.getName().at(5) == KDK) {
+      ++nKdk;
+    }
+  }
+  BOOST_CHECK_EQUAL(nKek, 1);
+  BOOST_CHECK_EQUAL(nKdk, 2);
+}
+
+BOOST_AUTO_TEST_CASE(DumpPackets) // use this to update content of other test cases
+{
+  if (std::getenv("NAC_DUMP_PACKETS") == nullptr) {
+    return;
+  }
+
+  std::cerr << "Block nacIdentity = \"";
+  auto block = m_keyChain.exportSafeBag(nacIdentity.getDefaultKey().getDefaultCertificate(),
+                                        "password", strlen("password"))->wireEncode();
+  printHex(std::cerr, block.wire(), block.size(), true);
+  std::cerr << "\"_block;\n\n";
+
+  std::cerr << "std::vector<Block> userIdentities = {\n";
+  for (size_t i = 0; i < userIdentities.size(); ++i) {
+    std::cerr << "    \"";
+
+    block = m_keyChain.exportSafeBag(userIdentities[i].getDefaultKey().getDefaultCertificate(),
+                                     "password", strlen("password"))->wireEncode();
+    printHex(std::cerr, block.wire(), block.size(), true);
+    if (i < userIdentities.size() - 1) {
+      std::cerr << "\"_block,\n";
+    }
+    else {
+      std::cerr << "\"_block\n";
+    }
+  }
+  std::cerr  << "  };\n\n";
+
+  std::cerr << "std::vector<Block> managerPackets = {\n";
+  size_t i = 0;
+  for (const auto& data : manager) {
+    std::cerr << "    \"";
+    printHex(std::cerr, data.wireEncode().wire(), data.wireEncode().size(), true);
+
+    if (i < manager.size() - 1) {
+      std::cerr << "\"_block,\n";
+    }
+    else {
+      std::cerr << "\"_block\n";
+    }
+
+    ++i;
+  }
+  std::cerr  << "  };\n\n";
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace tests
+} // namespace nac
+} // namespace ndn