blob: 163008064c0a7d73b20ac9b74b659812be9a69c7 [file] [log] [blame]
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
* Copyright (c) 2014-2020, 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/security/transform/block-cipher.hpp>
#include <ndn-cxx/security/transform/buffer-source.hpp>
#include <ndn-cxx/security/transform/stream-sink.hpp>
#include <ndn-cxx/util/exception.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) {
i.second.pendingInterest->cancel();
for (const auto& p : i.second.pendingDecrypts) {
p.onFailure(ErrorCode::CkRetrievalFailure,
"Cancel pending decrypt as ContentKey is being destroyed");
}
}
}
}
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 = nullopt;
// @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 = nullopt;
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 = nullopt;
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&, const Data& kdkData) {
ck->second.pendingInterest = nullopt;
// @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 = nullopt;
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 = nullopt;
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()) {
NDN_THROW(Error("Expecting Initialization 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