Encryptor

Change-Id: Ie9d12038160ec17489a6dae5e6002728d6815ddf
diff --git a/src/access-manager.cpp b/src/access-manager.cpp
index 056ef33..3ab89f0 100644
--- a/src/access-manager.cpp
+++ b/src/access-manager.cpp
@@ -47,6 +47,7 @@
 
   auto kek = make_shared<Data>(m_nacKey.getDefaultCertificate());
   kek->setName(Name(kekPrefix).append(nacKeyId));
+  kek->setFreshnessPeriod(DEFAULT_KEK_FRESHNESS_PERIOD);
   m_keyChain.sign(*kek, signingByIdentity(m_identity));
   // kek looks like a cert, but doesn't have ValidityPeriod
   m_ims.insert(*kek);
@@ -112,7 +113,8 @@
 
   auto kdk = make_shared<Data>(kdkName);
   kdk->setContent(content.wireEncode());
-  kdk->setFreshnessPeriod(1_h); // this can serve as soft access control for revoking access
+  // FreshnessPeriod can serve as a soft access control for revoking access
+  kdk->setFreshnessPeriod(DEFAULT_KDK_FRESHNESS_PERIOD);
   m_keyChain.sign(*kdk, signingByIdentity(m_identity));
 
   m_ims.insert(*kdk);
diff --git a/src/access-manager.hpp b/src/access-manager.hpp
index 4ee592f..6f77d4c 100644
--- a/src/access-manager.hpp
+++ b/src/access-manager.hpp
@@ -32,9 +32,9 @@
  *
  * 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.
+ * encrypted private key) key pair.
  *
- * TODO Allow rolling KEK
+ * @todo Rolling KEK
  */
 class AccessManager
 {
@@ -54,11 +54,11 @@
    * @param keyChain KeyChain
    * @param face Face that will be used to publish KEK and KDKs
    *
-   * Additional info:
+   * KEK and KDK naming:
    *
-   *     [identity]/NAC/[dataset]/KEK            || /[key-id]                           (== KEK, public key)
+   *     [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)
+   *     [identity]/NAC/[dataset]/KDK/[key-id]   /ENCRYPTED-BY/[user]/KEY/[key-id]   (== KDK, encrypted private key)
    *
    *     \_____________  ______________/
    *                   \/
@@ -101,10 +101,10 @@
     return m_ims.size();
   }
 
-  /** @brief Returns begin iterator of the in-memory storage ordering by
+  /** @brief Returns begin iterator of the in-memory storage ordered by
    *  name with digest
    *
-   *  @return{ const_iterator pointing to the beginning of the m_cache }
+   *  @return{ const_iterator pointing to the beginning of m_cache }
    */
   InMemoryStorage::const_iterator
   begin() const
@@ -112,10 +112,10 @@
     return m_ims.begin();
   }
 
-  /** @brief Returns end iterator of the in-memory storage ordering by
+  /** @brief Returns end iterator of the in-memory storage ordered by
    *  name with digest
    *
-   *  @return{ const_iterator pointing to the end of the m_cache }
+   *  @return{ const_iterator pointing to the end of m_cache }
    */
   InMemoryStorage::const_iterator
   end() const
@@ -129,27 +129,7 @@
   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;
+  InMemoryStoragePersistent m_ims; // for KEK and KDKs
   const RegisteredPrefixId* m_kekRegId;
   const RegisteredPrefixId* m_kdkRegId;
 };
diff --git a/src/common.hpp b/src/common.hpp
index b37f50c..b8523e9 100644
--- a/src/common.hpp
+++ b/src/common.hpp
@@ -90,7 +90,7 @@
 enum {
   EncryptedContent = 130,
   EncryptedPayload = 132,
-  InitialVector = 133,
+  InitializationVector = 133,
   EncryptedPayloadKey = 134,
 };
 
@@ -105,6 +105,10 @@
 const size_t AES_KEY_SIZE = 32;
 const size_t AES_IV_SIZE = 16;
 
+const time::seconds DEFAULT_KEK_FRESHNESS_PERIOD = 1_h;
+const time::seconds DEFAULT_KDK_FRESHNESS_PERIOD = 1_h;
+const time::seconds DEFAULT_CK_FRESHNESS_PERIOD = 1_h;
+
 enum class ErrorCode {
   KekRetrievalFailure = 1,
   KekRetrievalTimeout = 2,
@@ -120,7 +124,8 @@
   CkInvalidName = 23,
 
   MissingRequiredKeyLocator = 101,
-  TpmKeyNotFound = 102
+  TpmKeyNotFound = 102,
+  EncryptionFailure = 103
 };
 
 using ErrorCallback = std::function<void (const ErrorCode&, const std::string&)>;
diff --git a/src/encrypted-content.cpp b/src/encrypted-content.cpp
index a2ca2a4..f9738bd 100644
--- a/src/encrypted-content.cpp
+++ b/src/encrypted-content.cpp
@@ -61,8 +61,8 @@
 EncryptedContent::setIv(Block iv)
 {
   m_wire.reset();
-  if (iv.type() != tlv::InitialVector) {
-    m_iv = Block(tlv::InitialVector, std::move(iv));
+  if (iv.type() != tlv::InitializationVector) {
+    m_iv = Block(tlv::InitializationVector, std::move(iv));
   }
   else {
     m_iv = std::move(iv);
@@ -74,7 +74,7 @@
 EncryptedContent::setIv(ConstBufferPtr iv)
 {
   m_wire.reset();
-  m_iv = Block(tlv::InitialVector, iv);
+  m_iv = Block(tlv::InitializationVector, iv);
   return *this;
 }
 
@@ -204,7 +204,7 @@
     BOOST_THROW_EXCEPTION(Error("Required EncryptedPayload not found in EncryptedContent"));
   }
 
-  block = m_wire.find(tlv::InitialVector);
+  block = m_wire.find(tlv::InitializationVector);
   if (block != m_wire.elements_end()) {
     m_iv = *block;
   }
diff --git a/src/encrypted-content.hpp b/src/encrypted-content.hpp
index e14db62..90118d4 100644
--- a/src/encrypted-content.hpp
+++ b/src/encrypted-content.hpp
@@ -32,15 +32,14 @@
  *
  * <code>
  *     EncryptedContent ::= ENCRYPTED-CONTENT-TYPE TLV-LENGTH
- *                            InitialVector
+ *                            InitializationVector
  *                            EncryptedPayload
  *                            EncryptedPayloadKey
  *                            Name
  *
- *     InitialVector ::= INITIAL-VECTOR-TYPE TLV-LENGTH(=N) BYTE{N}
+ *     InitializationVector ::= INITIALIZATION-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}
  * </code>
  */
 class EncryptedContent
diff --git a/src/encryptor.cpp b/src/encryptor.cpp
new file mode 100644
index 0000000..26b9a73
--- /dev/null
+++ b/src/encryptor.cpp
@@ -0,0 +1,189 @@
+/* -*- 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 "encryptor.hpp"
+
+#include <ndn-cxx/util/logger.hpp>
+#include <boost/lexical_cast.hpp>
+
+namespace ndn {
+namespace nac {
+
+NDN_LOG_INIT(nac.Encryptor);
+
+const size_t N_RETRIES = 3;
+
+Encryptor::Encryptor(const Name& accessPrefix,
+                     const Name& ckPrefix, SigningInfo ckDataSigningInfo,
+                     const ErrorCallback& onFailure,
+                     Validator& validator, KeyChain& keyChain, Face& face)
+  : m_accessPrefix{accessPrefix}
+  , m_ckPrefix{ckPrefix}
+  , m_ckBits{AES_KEY_SIZE}
+  , m_ckDataSigningInfo{std::move(ckDataSigningInfo)}
+  , m_isKekRetrievalInProgress(false)
+  , m_ckRegId{nullptr}
+  , m_keyChain{keyChain}
+  , m_face{face}
+{
+  regenerateCk(onFailure);
+
+  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 CK 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_ckRegId = m_face.setInterestFilter(Name(ckPrefix).append(CK), serveFromIms, handleError);
+}
+
+Encryptor::~Encryptor()
+{
+  m_face.unsetInterestFilter(m_ckRegId);
+  if (m_kekPendingInterest != nullptr) {
+    m_face.removePendingInterest(m_kekPendingInterest);
+  }
+}
+
+void
+Encryptor::regenerateCk(const ErrorCallback& onFailure)
+{
+  m_ckName = m_ckPrefix;
+  m_ckName
+    .append(CK)
+    .appendVersion(); // version = ID of CK
+  random::generateSecureBytes(m_ckBits.data(), m_ckBits.size());
+
+  NDN_LOG_DEBUG("Generating new CK: " << m_ckName);
+
+  // one implication: if CK updated before KEK fetched, KDK for the old CK will not be published
+  if (!m_kek) {
+    m_isKekRetrievalInProgress = true;
+    fetchKekAndPublishCkData([&] {
+        NDN_LOG_DEBUG("KEK retrieved and published");
+        m_isKekRetrievalInProgress = false;
+      },
+      [&] (const ErrorCode&, const std::string& msg) {
+        NDN_LOG_ERROR("Failed to retrieved KEK: " + msg);
+        m_isKekRetrievalInProgress = false;
+      },
+      N_RETRIES);
+  }
+  else {
+    makeAndPublishCkData(onFailure);
+  }
+}
+
+EncryptedContent
+Encryptor::encrypt(const uint8_t* data, size_t size)
+{
+  // Generate initial vector
+  auto iv = make_shared<Buffer>(AES_IV_SIZE);
+  random::generateSecureBytes(iv->data(), iv->size());
+
+  OBufferStream os;
+  security::transform::bufferSource(data, size)
+    >> security::transform::blockCipher(BlockCipherAlgorithm::AES_CBC,
+                                        CipherOperator::ENCRYPT,
+                                        m_ckBits.data(), m_ckBits.size(), iv->data(), iv->size())
+    >> security::transform::streamSink(os);
+
+  EncryptedContent content;
+  content.setIv(iv);
+  content.setPayload(os.buf());
+  content.setKeyLocator(m_ckName);
+
+  return content;
+}
+
+void
+Encryptor::fetchKekAndPublishCkData(const std::function<void()>& onReady,
+                                    const ErrorCallback& onFailure,
+                                    size_t nTriesLeft)
+{
+  // interest for <access-prefix>/KEK to retrieve  <access-prefix>/KEK/<key-id> KekData
+
+  NDN_LOG_DEBUG("Fetching KEK " << Name(m_accessPrefix).append(KEK));
+
+  BOOST_ASSERT(m_kekPendingInterest == nullptr);
+  m_kekPendingInterest =
+    m_face.expressInterest(Interest(Name(m_accessPrefix).append(KEK))
+                             .setMustBeFresh(true)
+                             .setCanBePrefix(true),
+                           [=] (const Interest&, const Data& kek) {
+                             m_kekPendingInterest = nullptr;
+                             // @todo verify if the key is legit
+                             m_kek = kek;
+                             makeAndPublishCkData(onFailure);
+                             // otherwise, failure has been already declared
+                           },
+                           [=] (const Interest& i, const lp::Nack& nack) {
+                             m_kekPendingInterest = nullptr;
+                             onFailure(ErrorCode::KekRetrievalFailure,
+                                       "Retrieval of KEK [" + i.getName().toUri() + "] failed. "
+                                       "Got NACK (" + boost::lexical_cast<std::string>(nack.getReason()) + ")");
+                           },
+                           [=] (const Interest& i) {
+                             m_kekPendingInterest = nullptr;
+                             if (nTriesLeft > 1) {
+                               fetchKekAndPublishCkData(onReady, onFailure, nTriesLeft - 1);
+                             }
+                             else {
+                               onFailure(ErrorCode::KekRetrievalTimeout,
+                                         "Retrieval of KEK [" + i.getName().toUri() + "] timed out");
+                             }
+                           });
+}
+
+void
+Encryptor::makeAndPublishCkData(const ErrorCallback& onFailure)
+{
+  try {
+    PublicKey kek;
+    kek.loadPkcs8(m_kek->getContent().value(), m_kek->getContent().value_size());
+
+    EncryptedContent content;
+    content.setPayload(kek.encrypt(m_ckBits.data(), m_ckBits.size()));
+
+    auto ckData = make_shared<Data>(Name(m_ckName).append(ENCRYPTED_BY).append(m_kek->getName()));
+    ckData->setContent(content.wireEncode());
+    // FreshnessPeriod can serve as a soft access control for revoking access
+    ckData->setFreshnessPeriod(DEFAULT_CK_FRESHNESS_PERIOD);
+    m_keyChain.sign(*ckData, m_ckDataSigningInfo);
+    m_ims.insert(*ckData);
+
+    NDN_LOG_DEBUG("Publishing CK data: " << ckData->getName());
+  }
+  catch (const std::runtime_error& error) {
+    onFailure(ErrorCode::EncryptionFailure, "Failed to encrypt generated CK with KEK " + m_kek->getName().toUri());
+  }
+}
+
+} // namespace nac
+} // namespace ndn
diff --git a/src/encryptor.hpp b/src/encryptor.hpp
new file mode 100644
index 0000000..679f6d4
--- /dev/null
+++ b/src/encryptor.hpp
@@ -0,0 +1,145 @@
+/* -*- 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_ENCRYPTOR_HPP
+#define NDN_NAC_ENCRYPTOR_HPP
+
+#include "common.hpp"
+#include "encrypted-content.hpp"
+
+namespace ndn {
+namespace nac {
+
+/**
+ * @brief NAC Encryptor
+ *
+ * Encryptor encrypts the requested content and returns ``EncryptedContent`` element.
+ */
+class Encryptor
+{
+public:
+  /**
+   * @param accessPrefix  NAC prefix to fetch KEK (e.g., /access/prefix/NAC/data/subset)
+   * @param ckPrefix      Prefix under which Content Keys will be generated
+   *                      (each will have unique version appended)
+   * @param ckDataSigningInfo  SigningInfo parameters to sign CK Data
+   * @param onFailure     Callback to notify application of a failure to create CK data
+   *                      (failed to fetch KEK, failed to encrypt with KEK, etc.)
+   * @param validator     Validation policy to ensure correctness of KEK
+   * @param keyChain      KeyChain
+   * @param face          Face that will be used to fetch KEK and publish CK data
+   */
+  Encryptor(const Name& accessPrefix,
+            const Name& ckPrefix, SigningInfo ckDataSigningInfo,
+            const ErrorCallback& onFailure,
+            Validator& validator, KeyChain& keyChain, Face& face);
+
+  ~Encryptor();
+
+  /**
+   * Synchronously encrypt supplied data
+   *
+   * If KEK has not been fetched already, this method will trigger async fetching of it.
+   * After KEK successfully fetched, CK data will be automatically published.
+   *
+   * @todo For now, CK is being published in InMemoryStorage and can be fetched only while
+   *       Encryptor instance is alive.
+   *
+   * The actual encryption is done synchronously, but the exact KDK name is not known
+   * until KEK is fetched.
+   *
+   * Note that if the KDK name is already known, this method will call onReady right away.
+   *
+   * @return Encrypted content
+   */
+  EncryptedContent
+  encrypt(const uint8_t* data, size_t size);
+
+  /**
+   * @brief Create a new content key and publish the corresponding CK data
+   *
+   * @todo Ensure that CK data packet for the old CK is published, when CK updated
+   *       before KEK fetched
+   */
+  void
+  regenerateCk(const ErrorCallback& onFailure);
+
+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 ordered by
+   *  name with digest
+   *
+   *  @return{ const_iterator pointing to the beginning of m_cache }
+   */
+  InMemoryStorage::const_iterator
+  begin() const
+  {
+    return m_ims.begin();
+  }
+
+  /** @brief Returns end iterator of the in-memory storage ordered by
+   *  name with digest
+   *
+   *  @return{ const_iterator pointing to the end of m_cache }
+   */
+  InMemoryStorage::const_iterator
+  end() const
+  {
+    return m_ims.end();
+  }
+
+private:
+  void
+  fetchKekAndPublishCkData(const std::function<void()>& onReady,
+                           const ErrorCallback& onFailure,
+                           size_t nTriesLeft);
+
+  void
+  makeAndPublishCkData(const ErrorCallback& onFailure);
+
+private:
+  Name m_accessPrefix;
+  Name m_ckPrefix;
+  Name m_ckName;
+  Buffer m_ckBits;
+  SigningInfo m_ckDataSigningInfo;
+
+  bool m_isKekRetrievalInProgress;
+  optional<Data> m_kek;
+
+  InMemoryStoragePersistent m_ims; // for encrypted CKs
+  const RegisteredPrefixId* m_ckRegId = nullptr;
+  const PendingInterestId* m_kekPendingInterest = nullptr;
+
+  KeyChain& m_keyChain;
+  Face& m_face;
+};
+
+} // namespace nac
+} // namespace ndn
+
+#endif // NDN_NAC_ENCRYPTOR_HPP