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