blob: 74290c89aa68c8a08108d84eae7aa99a38b5d2e3 [file] [log] [blame]
Alexander Afanasyevff3ee9f2018-06-13 20:33:30 -04001/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
Davide Pesaventod51d9602019-07-20 23:33:06 -04002/*
Davide Pesavento714dba02022-03-17 20:46:28 -04003 * Copyright (c) 2014-2022, Regents of the University of California
Alexander Afanasyevff3ee9f2018-06-13 20:33:30 -04004 *
5 * NAC library is free software: you can redistribute it and/or modify it under the
6 * terms of the GNU Lesser General Public License as published by the Free Software
7 * Foundation, either version 3 of the License, or (at your option) any later version.
8 *
9 * NAC library is distributed in the hope that it will be useful, but WITHOUT ANY
10 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
11 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
12 *
13 * You should have received copies of the GNU General Public License and GNU Lesser
14 * General Public License along with ndn-cxx, e.g., in COPYING.md file. If not, see
15 * <http://www.gnu.org/licenses/>.
16 *
17 * See AUTHORS.md for complete list of NAC library authors and contributors.
18 */
19
20#include "decryptor.hpp"
21
Davide Pesaventoc2649492020-12-22 21:43:35 -050022#include <ndn-cxx/security/transform/block-cipher.hpp>
23#include <ndn-cxx/security/transform/buffer-source.hpp>
24#include <ndn-cxx/security/transform/stream-sink.hpp>
25#include <ndn-cxx/util/exception.hpp>
Alexander Afanasyevff3ee9f2018-06-13 20:33:30 -040026#include <ndn-cxx/util/logger.hpp>
27
28namespace ndn {
29namespace nac {
30
31NDN_LOG_INIT(nac.Decryptor);
32
33const size_t N_RETRIES = 3;
34
35Decryptor::Decryptor(const Key& credentialsKey, Validator& validator, KeyChain& keyChain, Face& face)
36 : m_credentialsKey(credentialsKey)
37 // , m_validator(validator)
38 , m_face(face)
39 , m_keyChain(keyChain)
40 , m_internalKeyChain("pib-memory:", "tpm-memory:")
41{
42}
43
44Decryptor::~Decryptor()
45{
46 for (auto& i : m_cks) {
Davide Pesaventod51d9602019-07-20 23:33:06 -040047 if (i.second.pendingInterest) {
48 i.second.pendingInterest->cancel();
Alexander Afanasyevff3ee9f2018-06-13 20:33:30 -040049 for (const auto& p : i.second.pendingDecrypts) {
Davide Pesaventod51d9602019-07-20 23:33:06 -040050 p.onFailure(ErrorCode::CkRetrievalFailure,
51 "Cancel pending decrypt as ContentKey is being destroyed");
Alexander Afanasyevff3ee9f2018-06-13 20:33:30 -040052 }
Alexander Afanasyevff3ee9f2018-06-13 20:33:30 -040053 }
54 }
55}
56
57void
Davide Pesaventod51d9602019-07-20 23:33:06 -040058Decryptor::decrypt(const Block& encryptedContent,
59 const DecryptSuccessCallback& onSuccess,
60 const ErrorCallback& onFailure)
Alexander Afanasyevff3ee9f2018-06-13 20:33:30 -040061{
62 EncryptedContent ec(encryptedContent);
63 if (!ec.hasKeyLocator()) {
64 NDN_LOG_INFO("Missing required KeyLocator in the supplied EncryptedContent block");
65 return onFailure(ErrorCode::MissingRequiredKeyLocator,
66 "Missing required KeyLocator in the supplied EncryptedContent block");
67 }
68
69 if (!ec.hasIv()) {
70 NDN_LOG_INFO("Missing required InitialVector in the supplied EncryptedContent block");
71 return onFailure(ErrorCode::MissingRequiredKeyLocator,
72 "Missing required InitialVector in the supplied EncryptedContent block");
73 }
74
75 ContentKeys::iterator ck;
76 bool isNew = false;
77 std::tie(ck, isNew) = m_cks.emplace(ec.getKeyLocator(), ContentKey{});
78
79 if (ck->second.isRetrieved) {
80 doDecrypt(ec, ck->second.bits, onSuccess, onFailure);
81 }
82 else {
83 NDN_LOG_DEBUG("CK " << ec.getKeyLocator() << " not yet available, adding decrypt to the pending queue");
84 ck->second.pendingDecrypts.push_back({ec, onSuccess, onFailure});
85 }
86
87 if (isNew) {
88 fetchCk(ck, onFailure, N_RETRIES);
89 }
90}
91
92void
93Decryptor::fetchCk(ContentKeys::iterator ck, const ErrorCallback& onFailure, size_t nTriesLeft)
94{
95 // full name of CK is
96
97 // <whatever-prefix>/CK/<ck-id> /ENCRYPTED-BY /<kek-prefix>/KEK/<key-id>
98 // \ / \ /
99 // ----------- ------------- ----------- -----------
100 // \/ \/
101 // from the encrypted data unknown (name in retrieved CK is used to determine KDK)
102
103 const Name& ckName = ck->first;
104
105 NDN_LOG_DEBUG("Fetching CK " << ckName);
106
107 ck->second.pendingInterest = m_face.expressInterest(Interest(ckName)
108 .setMustBeFresh(false) // ?
109 .setCanBePrefix(true),
110 [=] (const Interest& ckInterest, const Data& ckData) {
Davide Pesaventod51d9602019-07-20 23:33:06 -0400111 ck->second.pendingInterest = nullopt;
Davide Pesavento714dba02022-03-17 20:46:28 -0400112 // TODO: verify that the key is legit
Alexander Afanasyevff3ee9f2018-06-13 20:33:30 -0400113 Name kdkPrefix, kdkIdentity, kdkKeyName;
114 std::tie(kdkPrefix, kdkIdentity, kdkKeyName) =
115 extractKdkInfoFromCkName(ckData.getName(), ckInterest.getName(), onFailure);
116 if (kdkPrefix.empty()) {
117 return; // error has been already reported
118 }
119
120 // check if KDK already exists (there is a corresponding
121 auto kdkIdentityIt = m_internalKeyChain.getPib().getIdentities().find(kdkIdentity);
122 if (kdkIdentityIt != m_internalKeyChain.getPib().getIdentities().end()) {
123 auto kdkKeyIt = (*kdkIdentityIt).getKeys().find(kdkKeyName);
124 if (kdkKeyIt != (*kdkIdentityIt).getKeys().end()) {
125 // KDK was already fetched and imported
126 NDN_LOG_DEBUG("KDK " << kdkKeyName << " already exists, directly using it to decrypt CK");
127 return decryptCkAndProcessPendingDecrypts(ck, ckData, kdkKeyName, onFailure);
128 }
129 }
130
131 fetchKdk(ck, kdkPrefix, ckData, onFailure, N_RETRIES);
132 },
133 [=] (const Interest& i, const lp::Nack& nack) {
Davide Pesaventod51d9602019-07-20 23:33:06 -0400134 ck->second.pendingInterest = nullopt;
Alexander Afanasyevff3ee9f2018-06-13 20:33:30 -0400135 onFailure(ErrorCode::CkRetrievalFailure,
136 "Retrieval of CK [" + i.getName().toUri() + "] failed. "
137 "Got NACK (" + boost::lexical_cast<std::string>(nack.getReason()) + ")");
138 },
139 [=] (const Interest& i) {
Davide Pesaventod51d9602019-07-20 23:33:06 -0400140 ck->second.pendingInterest = nullopt;
Alexander Afanasyevff3ee9f2018-06-13 20:33:30 -0400141 if (nTriesLeft > 1) {
142 fetchCk(ck, onFailure, nTriesLeft - 1);
143 }
144 else {
145 onFailure(ErrorCode::CkRetrievalTimeout,
146 "Retrieval of CK [" + i.getName().toUri() + "] timed out");
147 }
148 });
149}
150
151void
152Decryptor::fetchKdk(ContentKeys::iterator ck, const Name& kdkPrefix, const Data& ckData,
153 const ErrorCallback& onFailure, size_t nTriesLeft)
154{
155 // <kdk-prefix>/KDK/<kdk-id> /ENCRYPTED-BY /<credential-identity>/KEY/<key-id>
156 // \ / \ /
157 // ----------- ------------- --------------- ---------------
158 // \/ \/
159 // from the CK data from configuration
160
161 Name kdkName = kdkPrefix;
162 kdkName
163 .append(ENCRYPTED_BY)
164 .append(m_credentialsKey.getName());
165
166 NDN_LOG_DEBUG("Fetching KDK " << kdkName);
167
Davide Pesavento714dba02022-03-17 20:46:28 -0400168 ck->second.pendingInterest = m_face.expressInterest(Interest(kdkName).setMustBeFresh(true),
Davide Pesaventod51d9602019-07-20 23:33:06 -0400169 [=] (const Interest&, const Data& kdkData) {
170 ck->second.pendingInterest = nullopt;
Davide Pesavento714dba02022-03-17 20:46:28 -0400171 // TODO: verify that the key is legit
Alexander Afanasyevff3ee9f2018-06-13 20:33:30 -0400172
173 bool isOk = decryptAndImportKdk(kdkData, onFailure);
174 if (!isOk)
175 return;
176 decryptCkAndProcessPendingDecrypts(ck, ckData,
177 kdkPrefix.getPrefix(-2).append("KEY").append(kdkPrefix.get(-1)), // a bit hacky
178 onFailure);
179 },
180 [=] (const Interest& i, const lp::Nack& nack) {
Davide Pesaventod51d9602019-07-20 23:33:06 -0400181 ck->second.pendingInterest = nullopt;
Alexander Afanasyevff3ee9f2018-06-13 20:33:30 -0400182 onFailure(ErrorCode::KdkRetrievalFailure,
183 "Retrieval of KDK [" + i.getName().toUri() + "] failed. "
184 "Got NACK (" + boost::lexical_cast<std::string>(nack.getReason()) + ")");
185 },
186 [=] (const Interest& i) {
Davide Pesaventod51d9602019-07-20 23:33:06 -0400187 ck->second.pendingInterest = nullopt;
Alexander Afanasyevff3ee9f2018-06-13 20:33:30 -0400188 if (nTriesLeft > 1) {
189 fetchKdk(ck, kdkPrefix, ckData, onFailure, nTriesLeft - 1);
190 }
191 else {
192 onFailure(ErrorCode::KdkRetrievalTimeout,
193 "Retrieval of KDK [" + i.getName().toUri() + "] timed out");
194 }
195 });
196}
197
198bool
199Decryptor::decryptAndImportKdk(const Data& kdkData, const ErrorCallback& onFailure)
200{
201 try {
202 NDN_LOG_DEBUG("Decrypting and importing KDK " << kdkData.getName());
203 EncryptedContent content(kdkData.getContent().blockFromValue());
204
205 SafeBag safeBag(content.getPayload().blockFromValue());
Davide Pesavento714dba02022-03-17 20:46:28 -0400206 auto secret = m_keyChain.getTpm().decrypt(content.getPayloadKey().value_bytes(),
Alexander Afanasyevff3ee9f2018-06-13 20:33:30 -0400207 m_credentialsKey.getName());
208 if (secret == nullptr) {
209 onFailure(ErrorCode::TpmKeyNotFound,
210 "Could not decrypt secret, " + m_credentialsKey.getName().toUri() + " not found in TPM");
211 return false;
212 }
213
214 m_internalKeyChain.importSafeBag(safeBag, reinterpret_cast<const char*>(secret->data()), secret->size());
215 return true;
216 }
217 catch (const std::runtime_error& e) {
218 // can be tlv::Error, pib::Error, tpm::Error, and bunch of other runtime-derived errors
219 onFailure(ErrorCode::KdkDecryptionFailure,
220 "Failed to decrypt KDK [" + kdkData.getName().toUri() + "]: " + e.what());
221 return false;
222 }
223}
224
225void
226Decryptor::decryptCkAndProcessPendingDecrypts(ContentKeys::iterator ck, const Data& ckData, const Name& kdkKeyName,
227 const ErrorCallback& onFailure)
228{
229 NDN_LOG_DEBUG("Decrypting CK data " << ckData.getName());
230
231 EncryptedContent content(ckData.getContent().blockFromValue());
232
Davide Pesavento714dba02022-03-17 20:46:28 -0400233 auto ckBits = m_internalKeyChain.getTpm().decrypt(content.getPayload().value_bytes(), kdkKeyName);
Alexander Afanasyevff3ee9f2018-06-13 20:33:30 -0400234 if (ckBits == nullptr) {
235 onFailure(ErrorCode::TpmKeyNotFound, "Could not decrypt secret, " + kdkKeyName.toUri() + " not found in TPM");
236 return;
237 }
238
239 ck->second.bits = *ckBits;
240 ck->second.isRetrieved = true;
241
242 for (const auto& item : ck->second.pendingDecrypts) {
243 doDecrypt(item.encryptedContent, ck->second.bits, item.onSuccess, item.onFailure);
244 }
245 ck->second.pendingDecrypts.clear();
246}
247
248void
249Decryptor::doDecrypt(const EncryptedContent& content, const Buffer& ckBits,
250 const DecryptSuccessCallback& onSuccess,
251 const ErrorCallback& onFailure)
252{
253 if (!content.hasIv()) {
Davide Pesaventoc2649492020-12-22 21:43:35 -0500254 NDN_THROW(Error("Expecting Initialization Vector in the encrypted content, but it is not present"));
Alexander Afanasyevff3ee9f2018-06-13 20:33:30 -0400255 }
256
257 OBufferStream os;
Davide Pesavento714dba02022-03-17 20:46:28 -0400258 security::transform::bufferSource(content.getPayload().value_bytes())
Alexander Afanasyevff3ee9f2018-06-13 20:33:30 -0400259 >> security::transform::blockCipher(BlockCipherAlgorithm::AES_CBC,
260 CipherOperator::DECRYPT,
Davide Pesavento714dba02022-03-17 20:46:28 -0400261 ckBits, content.getIv().value_bytes())
Alexander Afanasyevff3ee9f2018-06-13 20:33:30 -0400262 >> security::transform::streamSink(os);
263
264 onSuccess(os.buf());
265}
266
267} // namespace nac
268} // namespace ndn