Decryptor
Change-Id: I542c0e5b937f9b31bfbe2dee2e5b589cf0f6cec9
diff --git a/src/decryptor.cpp b/src/decryptor.cpp
new file mode 100644
index 0000000..25043d5
--- /dev/null
+++ b/src/decryptor.cpp
@@ -0,0 +1,269 @@
+/* -*- 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 "decryptor.hpp"
+
+#include <ndn-cxx/util/logger.hpp>
+
+namespace ndn {
+namespace nac {
+
+NDN_LOG_INIT(nac.Decryptor);
+
+const size_t N_RETRIES = 3;
+
+Decryptor::Decryptor(const Key& credentialsKey, Validator& validator, KeyChain& keyChain, Face& face)
+ : m_credentialsKey(credentialsKey)
+ // , m_validator(validator)
+ , m_face(face)
+ , m_keyChain(keyChain)
+ , m_internalKeyChain("pib-memory:", "tpm-memory:")
+{
+}
+
+Decryptor::~Decryptor()
+{
+ for (auto& i : m_cks) {
+ if (i.second.pendingInterest != nullptr) {
+ m_face.removePendingInterest(i.second.pendingInterest);
+ i.second.pendingInterest = nullptr;
+
+ for (const auto& p : i.second.pendingDecrypts) {
+ p.onFailure(ErrorCode::CkRetrievalFailure, "Cancel pending decrypt as ContentKey is being destroyed");
+ }
+ i.second.pendingDecrypts.clear(); // not really necessary, but just in case
+ }
+ }
+}
+
+void
+Decryptor::decrypt(const Block& encryptedContent, const DecryptSuccessCallback& onSuccess, const ErrorCallback& onFailure)
+{
+ EncryptedContent ec(encryptedContent);
+ if (!ec.hasKeyLocator()) {
+ NDN_LOG_INFO("Missing required KeyLocator in the supplied EncryptedContent block");
+ return onFailure(ErrorCode::MissingRequiredKeyLocator,
+ "Missing required KeyLocator in the supplied EncryptedContent block");
+ }
+
+ if (!ec.hasIv()) {
+ NDN_LOG_INFO("Missing required InitialVector in the supplied EncryptedContent block");
+ return onFailure(ErrorCode::MissingRequiredKeyLocator,
+ "Missing required InitialVector in the supplied EncryptedContent block");
+ }
+
+ ContentKeys::iterator ck;
+ bool isNew = false;
+ std::tie(ck, isNew) = m_cks.emplace(ec.getKeyLocator(), ContentKey{});
+
+ if (ck->second.isRetrieved) {
+ doDecrypt(ec, ck->second.bits, onSuccess, onFailure);
+ }
+ else {
+ NDN_LOG_DEBUG("CK " << ec.getKeyLocator() << " not yet available, adding decrypt to the pending queue");
+ ck->second.pendingDecrypts.push_back({ec, onSuccess, onFailure});
+ }
+
+ if (isNew) {
+ fetchCk(ck, onFailure, N_RETRIES);
+ }
+}
+
+void
+Decryptor::fetchCk(ContentKeys::iterator ck, const ErrorCallback& onFailure, size_t nTriesLeft)
+{
+ // full name of CK is
+
+ // <whatever-prefix>/CK/<ck-id> /ENCRYPTED-BY /<kek-prefix>/KEK/<key-id>
+ // \ / \ /
+ // ----------- ------------- ----------- -----------
+ // \/ \/
+ // from the encrypted data unknown (name in retrieved CK is used to determine KDK)
+
+ const Name& ckName = ck->first;
+
+ NDN_LOG_DEBUG("Fetching CK " << ckName);
+
+ ck->second.pendingInterest = m_face.expressInterest(Interest(ckName)
+ .setMustBeFresh(false) // ?
+ .setCanBePrefix(true),
+ [=] (const Interest& ckInterest, const Data& ckData) {
+ ck->second.pendingInterest = nullptr;
+ // @TODO verify if the key is legit
+ Name kdkPrefix, kdkIdentity, kdkKeyName;
+ std::tie(kdkPrefix, kdkIdentity, kdkKeyName) =
+ extractKdkInfoFromCkName(ckData.getName(), ckInterest.getName(), onFailure);
+ if (kdkPrefix.empty()) {
+ return; // error has been already reported
+ }
+
+ // check if KDK already exists (there is a corresponding
+ auto kdkIdentityIt = m_internalKeyChain.getPib().getIdentities().find(kdkIdentity);
+ if (kdkIdentityIt != m_internalKeyChain.getPib().getIdentities().end()) {
+ auto kdkKeyIt = (*kdkIdentityIt).getKeys().find(kdkKeyName);
+ if (kdkKeyIt != (*kdkIdentityIt).getKeys().end()) {
+ // KDK was already fetched and imported
+ NDN_LOG_DEBUG("KDK " << kdkKeyName << " already exists, directly using it to decrypt CK");
+ return decryptCkAndProcessPendingDecrypts(ck, ckData, kdkKeyName, onFailure);
+ }
+ }
+
+ fetchKdk(ck, kdkPrefix, ckData, onFailure, N_RETRIES);
+ },
+ [=] (const Interest& i, const lp::Nack& nack) {
+ ck->second.pendingInterest = nullptr;
+ onFailure(ErrorCode::CkRetrievalFailure,
+ "Retrieval of CK [" + i.getName().toUri() + "] failed. "
+ "Got NACK (" + boost::lexical_cast<std::string>(nack.getReason()) + ")");
+ },
+ [=] (const Interest& i) {
+ ck->second.pendingInterest = nullptr;
+ if (nTriesLeft > 1) {
+ fetchCk(ck, onFailure, nTriesLeft - 1);
+ }
+ else {
+ onFailure(ErrorCode::CkRetrievalTimeout,
+ "Retrieval of CK [" + i.getName().toUri() + "] timed out");
+ }
+ });
+}
+
+void
+Decryptor::fetchKdk(ContentKeys::iterator ck, const Name& kdkPrefix, const Data& ckData,
+ const ErrorCallback& onFailure, size_t nTriesLeft)
+{
+ // <kdk-prefix>/KDK/<kdk-id> /ENCRYPTED-BY /<credential-identity>/KEY/<key-id>
+ // \ / \ /
+ // ----------- ------------- --------------- ---------------
+ // \/ \/
+ // from the CK data from configuration
+
+ Name kdkName = kdkPrefix;
+ kdkName
+ .append(ENCRYPTED_BY)
+ .append(m_credentialsKey.getName());
+
+ NDN_LOG_DEBUG("Fetching KDK " << kdkName);
+
+ ck->second.pendingInterest = m_face.expressInterest(Interest(kdkName)
+ .setMustBeFresh(true)
+ .setCanBePrefix(false),
+ [=] (const Interest& ckInterest, const Data& kdkData) {
+ ck->second.pendingInterest = nullptr;
+ // @TODO verify if the key is legit
+
+ bool isOk = decryptAndImportKdk(kdkData, onFailure);
+ if (!isOk)
+ return;
+ decryptCkAndProcessPendingDecrypts(ck, ckData,
+ kdkPrefix.getPrefix(-2).append("KEY").append(kdkPrefix.get(-1)), // a bit hacky
+ onFailure);
+ },
+ [=] (const Interest& i, const lp::Nack& nack) {
+ ck->second.pendingInterest = nullptr;
+ onFailure(ErrorCode::KdkRetrievalFailure,
+ "Retrieval of KDK [" + i.getName().toUri() + "] failed. "
+ "Got NACK (" + boost::lexical_cast<std::string>(nack.getReason()) + ")");
+ },
+ [=] (const Interest& i) {
+ ck->second.pendingInterest = nullptr;
+ if (nTriesLeft > 1) {
+ fetchKdk(ck, kdkPrefix, ckData, onFailure, nTriesLeft - 1);
+ }
+ else {
+ onFailure(ErrorCode::KdkRetrievalTimeout,
+ "Retrieval of KDK [" + i.getName().toUri() + "] timed out");
+ }
+ });
+}
+
+bool
+Decryptor::decryptAndImportKdk(const Data& kdkData, const ErrorCallback& onFailure)
+{
+ try {
+ NDN_LOG_DEBUG("Decrypting and importing KDK " << kdkData.getName());
+ EncryptedContent content(kdkData.getContent().blockFromValue());
+
+ SafeBag safeBag(content.getPayload().blockFromValue());
+ auto secret = m_keyChain.getTpm().decrypt(content.getPayloadKey().value(),
+ content.getPayloadKey().value_size(),
+ m_credentialsKey.getName());
+ if (secret == nullptr) {
+ onFailure(ErrorCode::TpmKeyNotFound,
+ "Could not decrypt secret, " + m_credentialsKey.getName().toUri() + " not found in TPM");
+ return false;
+ }
+
+ m_internalKeyChain.importSafeBag(safeBag, reinterpret_cast<const char*>(secret->data()), secret->size());
+ return true;
+ }
+ catch (const std::runtime_error& e) {
+ // can be tlv::Error, pib::Error, tpm::Error, and bunch of other runtime-derived errors
+ onFailure(ErrorCode::KdkDecryptionFailure,
+ "Failed to decrypt KDK [" + kdkData.getName().toUri() + "]: " + e.what());
+ return false;
+ }
+}
+
+void
+Decryptor::decryptCkAndProcessPendingDecrypts(ContentKeys::iterator ck, const Data& ckData, const Name& kdkKeyName,
+ const ErrorCallback& onFailure)
+{
+ NDN_LOG_DEBUG("Decrypting CK data " << ckData.getName());
+
+ EncryptedContent content(ckData.getContent().blockFromValue());
+
+ auto ckBits = m_internalKeyChain.getTpm().decrypt(content.getPayload().value(), content.getPayload().value_size(),
+ kdkKeyName);
+ if (ckBits == nullptr) {
+ onFailure(ErrorCode::TpmKeyNotFound, "Could not decrypt secret, " + kdkKeyName.toUri() + " not found in TPM");
+ return;
+ }
+
+ ck->second.bits = *ckBits;
+ ck->second.isRetrieved = true;
+
+ for (const auto& item : ck->second.pendingDecrypts) {
+ doDecrypt(item.encryptedContent, ck->second.bits, item.onSuccess, item.onFailure);
+ }
+ ck->second.pendingDecrypts.clear();
+}
+
+void
+Decryptor::doDecrypt(const EncryptedContent& content, const Buffer& ckBits,
+ const DecryptSuccessCallback& onSuccess,
+ const ErrorCallback& onFailure)
+{
+ if (!content.hasIv()) {
+ BOOST_THROW_EXCEPTION(Error("Expecting Initial Vector in the encrypted content, but it is not present"));
+ }
+
+ OBufferStream os;
+ security::transform::bufferSource(content.getPayload().value(), content.getPayload().value_size())
+ >> security::transform::blockCipher(BlockCipherAlgorithm::AES_CBC,
+ CipherOperator::DECRYPT,
+ ckBits.data(), ckBits.size(),
+ content.getIv().value(), content.getIv().value_size())
+ >> security::transform::streamSink(os);
+
+ onSuccess(os.buf());
+}
+
+} // namespace nac
+} // namespace ndn