blob: 12d835d1aa2c5a7db9a08a0b8d946f86953ee7e2 [file] [log] [blame]
Zhiyi Zhang23564c82017-03-01 10:22:22 -08001/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
2/**
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -07003 * Copyright (c) 2017-2019, Regents of the University of California.
Zhiyi Zhang23564c82017-03-01 10:22:22 -08004 *
5 * This file is part of ndncert, a certificate management system based on NDN.
6 *
7 * ndncert is free software: you can redistribute it and/or modify it under the terms
8 * of the GNU General Public License as published by the Free Software Foundation, either
9 * version 3 of the License, or (at your option) any later version.
10 *
11 * ndncert is distributed in the hope that it will be useful, but WITHOUT ANY
12 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
13 * PARTICULAR PURPOSE. See the GNU General Public License for more details.
14 *
15 * You should have received copies of the GNU General Public License along with
16 * ndncert, e.g., in COPYING.md file. If not, see <http://www.gnu.org/licenses/>.
17 *
18 * See AUTHORS.md for complete list of ndncert authors and contributors.
19 */
20
21#include "client-module.hpp"
22#include "logging.hpp"
Zhiyi Zhanga9bda732017-05-20 22:58:55 -070023#include "challenge-module.hpp"
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -070024#include "crypto-support/enc-tlv.hpp"
Zhiyi Zhang1c0bd372017-12-18 18:32:55 +080025#include <ndn-cxx/util/io.hpp>
Zhiyi Zhang23564c82017-03-01 10:22:22 -080026#include <ndn-cxx/security/signing-helpers.hpp>
27#include <ndn-cxx/security/verification-helpers.hpp>
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -070028#include <ndn-cxx/util/random.hpp>
29#include <ndn-cxx/security/transform/base64-encode.hpp>
30#include <ndn-cxx/security/transform/buffer-source.hpp>
31#include <ndn-cxx/security/transform/stream-sink.hpp>
Zhiyi Zhang23564c82017-03-01 10:22:22 -080032
33namespace ndn {
34namespace ndncert {
35
36_LOG_INIT(ndncert.client);
37
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -070038ClientModule::ClientModule(security::v2::KeyChain& keyChain)
39 : m_keyChain(keyChain)
Zhiyi Zhang23564c82017-03-01 10:22:22 -080040{
41}
42
Davide Pesavento08994782018-01-22 12:13:41 -050043ClientModule::~ClientModule() = default;
44
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -070045shared_ptr<Interest>
46ClientModule::generateProbeInfoInterest(const Name& caName)
Zhiyi Zhang1c0bd372017-12-18 18:32:55 +080047{
48 Name interestName = caName;
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -070049 if (readString(caName.at(-1)) != "CA")
50 interestName.append("CA");
51 interestName.append("_PROBE").append("INFO");
52 auto interest = make_shared<Interest>(interestName);
53 interest->setMustBeFresh(true);
54 interest->setCanBePrefix(false);
55 return interest;
Zhiyi Zhang1c0bd372017-12-18 18:32:55 +080056}
57
58void
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -070059ClientModule::onProbeInfoResponse(const Data& reply)
Zhiyi Zhang1c0bd372017-12-18 18:32:55 +080060{
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -070061 // parse the ca item
62 auto contentJson = getJsonFromData(reply);
63 auto caItem = ClientConfig::extractCaItem(contentJson);
Zhiyi Zhang1c0bd372017-12-18 18:32:55 +080064
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -070065 // update the local config
66 bool findItem = false;
67 for (auto& item : m_config.m_caItems) {
68 if (item.m_caName == caItem.m_caName) {
69 findItem = true;
70 item = caItem;
Zhiyi Zhang1c0bd372017-12-18 18:32:55 +080071 }
72 }
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -070073 if (!findItem) {
74 m_config.m_caItems.push_back(caItem);
Zhiyi Zhang1c0bd372017-12-18 18:32:55 +080075 }
Zhiyi Zhang1c0bd372017-12-18 18:32:55 +080076
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -070077 // verify the probe Data's sig
78 if (!security::verifySignature(reply, caItem.m_anchor)) {
79 _LOG_ERROR("Cannot verify data signature from " << m_ca.m_caName.toUri());
Zhiyi Zhang23564c82017-03-01 10:22:22 -080080 return;
81 }
82}
83
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -070084shared_ptr<Interest>
85ClientModule::generateProbeInterest(const ClientCaItem& ca, const std::string& probeInfo)
Zhiyi Zhang23564c82017-03-01 10:22:22 -080086{
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -070087 Name interestName = ca.m_caName;
88 interestName.append("CA").append("_PROBE");
89 auto interest = make_shared<Interest>(interestName);
90 interest->setMustBeFresh(true);
91 interest->setCanBePrefix(false);
Yufeng Zhang424d0362019-06-12 16:48:27 -070092 auto paramJson = genProbeRequestJson(ca, probeInfo);
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -070093 interest->setApplicationParameters(paramFromJson(paramJson));
94
95 // update local state
96 m_ca = ca;
97 return interest;
98}
99
100void
101ClientModule::onProbeResponse(const Data& reply)
102{
103 if (!security::verifySignature(reply, m_ca.m_anchor)) {
104 _LOG_ERROR("Cannot verify data signature from " << m_ca.m_caName.toUri());
105 return;
106 }
107 auto contentJson = getJsonFromData(reply);
108
109 // read the available name and put it into the state
110 auto nameUri = contentJson.get<std::string>(JSON_CA_NAME, "");
111 if (nameUri != "") {
112 m_identityName = Name(nameUri);
113 }
Zhiyi Zhang781a5602019-06-26 19:05:04 -0700114 else {
115 NDN_LOG_TRACE("The JSON_CA_NAME is empty.");
116 }
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700117}
118
119shared_ptr<Interest>
120ClientModule::generateNewInterest(const time::system_clock::TimePoint& notBefore,
121 const time::system_clock::TimePoint& notAfter,
Zhiyi Zhang5f749a22019-06-12 17:02:33 -0700122 const Name& identityName, const shared_ptr<Data>& probeToken)
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700123{
124 // Name requestedName = identityName;
125 if (!identityName.empty()) { // if identityName is not empty, find the corresponding CA
126 bool findCa = false;
127 for (const auto& caItem : m_config.m_caItems) {
128 if (caItem.m_caName.isPrefixOf(identityName)) {
129 m_ca = caItem;
130 findCa = true;
131 }
132 }
133 if (!findCa) { // if cannot find, cannot proceed
134 return nullptr;
135 }
136 m_identityName = identityName;
137 }
138 else { // if identityName is empty, check m_identityName or generate a random name
139 if (!m_identityName.empty()) {
140 // do nothing
141 }
142 else {
Zhiyi Zhang781a5602019-06-26 19:05:04 -0700143 NDN_LOG_TRACE("Randomly create a new name because m_identityName is empty and the param is empty.");
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700144 auto id = std::to_string(random::generateSecureWord64());
145 m_identityName = m_ca.m_caName;
146 m_identityName.append(id);
147 }
148 }
149
150 // generate a newly key pair or use an existing key
Zhiyi Zhang10130782018-02-01 18:28:49 -0800151 const auto& pib = m_keyChain.getPib();
Zhiyi Zhang10130782018-02-01 18:28:49 -0800152 try {
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700153 auto identity = pib.getIdentity(m_identityName);
154 m_key = m_keyChain.createKey(identity);
Zhiyi Zhang10130782018-02-01 18:28:49 -0800155 }
156 catch (const security::Pib::Error& e) {
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700157 auto identity = m_keyChain.createIdentity(m_identityName);
158 m_key = identity.getDefaultKey();
Zhiyi Zhang10130782018-02-01 18:28:49 -0800159 }
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800160
161 // generate certificate request
162 security::v2::Certificate certRequest;
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700163 certRequest.setName(Name(m_key.getName()).append("cert-request").appendVersion());
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800164 certRequest.setContentType(tlv::ContentType_Key);
165 certRequest.setFreshnessPeriod(time::hours(24));
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700166 certRequest.setContent(m_key.getPublicKey().data(), m_key.getPublicKey().size());
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800167 SignatureInfo signatureInfo;
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700168 signatureInfo.setValidityPeriod(security::ValidityPeriod(notBefore, notAfter));
169 m_keyChain.sign(certRequest, signingByKey(m_key.getName()).setSignatureInfo(signatureInfo));
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800170
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700171 // generate Interest packet
172 Name interestName = m_ca.m_caName;
173 interestName.append("CA").append("_NEW");
174 auto interest = make_shared<Interest>(interestName);
175 interest->setMustBeFresh(true);
176 interest->setCanBePrefix(false);
Zhiyi Zhang5f749a22019-06-12 17:02:33 -0700177 interest->setApplicationParameters(paramFromJson(genNewRequestJson(m_ecdh.getBase64PubKey(), certRequest, probeToken)));
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800178
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700179 // sign the Interest packet
180 m_keyChain.sign(*interest, signingByKey(m_key.getName()));
181 return interest;
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800182}
183
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700184std::list<std::string>
185ClientModule::onNewResponse(const Data& reply)
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800186{
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700187 if (!security::verifySignature(reply, m_ca.m_anchor)) {
188 _LOG_ERROR("Cannot verify data signature from " << m_ca.m_caName.toUri());
189 return std::list<std::string>();
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800190 }
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700191 auto contentJson = getJsonFromData(reply);
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800192
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700193 // ECDH
194 const auto& peerKeyBase64Str = contentJson.get<std::string>(JSON_CA_ECDH, "");
195 const auto& saltStr = contentJson.get<std::string>(JSON_CA_SALT, "");
196 uint64_t saltInt = std::stoull(saltStr);
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700197 m_ecdh.deriveSecret(peerKeyBase64Str);
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800198
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700199 // HKDF
Zhiyi Zhang36706832019-07-04 21:33:03 -0700200 hkdf(m_ecdh.context->sharedSecret, m_ecdh.context->sharedSecretLen,
201 (uint8_t*)&saltInt, sizeof(saltInt), m_aesKey, sizeof(m_aesKey));
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800202
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700203 // update state
204 m_status = contentJson.get<int>(JSON_CA_STATUS);
Zhiyi Zhangff4bcb62019-09-08 12:57:42 -0700205 m_requestId = contentJson.get<std::string>(JSON_CA_REQUEST_ID, "");
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700206
207 auto challengesJson = contentJson.get_child(JSON_CA_CHALLENGES);
208 m_challengeList.clear();
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800209 for (const auto& challengeJson : challengesJson) {
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700210 m_challengeList.push_back(challengeJson.second.get<std::string>(JSON_CA_CHALLENGE_ID, ""));
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800211 }
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700212 return m_challengeList;
213}
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800214
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700215shared_ptr<Interest>
216ClientModule::generateChallengeInterest(const JsonSection& paramJson)
217{
218 m_challengeType = paramJson.get<std::string>(JSON_CLIENT_SELECTED_CHALLENGE);
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800219
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700220 Name interestName = m_ca.m_caName;
221 interestName.append("CA").append("_CHALLENGE").append(m_requestId);
222 auto interest = make_shared<Interest>(interestName);
223 interest->setMustBeFresh(true);
224 interest->setCanBePrefix(false);
225
226 // encrypt the Interest parameters
227 std::stringstream ss;
228 boost::property_tree::write_json(ss, paramJson);
229 auto payload = ss.str();
Zhiyi Zhang36706832019-07-04 21:33:03 -0700230 auto paramBlock = genEncBlock(tlv::ApplicationParameters, m_aesKey, sizeof(m_aesKey),
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700231 (const uint8_t*)payload.c_str(), payload.size());
232 interest->setApplicationParameters(paramBlock);
233
234 m_keyChain.sign(*interest, signingByKey(m_key.getName()));
235 return interest;
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800236}
237
238void
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700239ClientModule::onChallengeResponse(const Data& reply)
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800240{
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700241 if (!security::verifySignature(reply, m_ca.m_anchor)) {
242 _LOG_ERROR("Cannot verify data signature from " << m_ca.m_caName.toUri());
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800243 return;
244 }
Zhiyi Zhang36706832019-07-04 21:33:03 -0700245 auto result = parseEncBlock(m_aesKey, sizeof(m_aesKey), reply.getContent());
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700246 std::string payload((const char*)result.data(), result.size());
247 std::istringstream ss(payload);
248 JsonSection contentJson;
249 boost::property_tree::json_parser::read_json(ss, contentJson);
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800250
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700251 // update state
252 m_status = contentJson.get<int>(JSON_CA_STATUS);
253 m_challengeStatus = contentJson.get<std::string>(JSON_CHALLENGE_STATUS);
254 m_remainingTries = contentJson.get<int>(JSON_CHALLENGE_REMAINING_TRIES);
255 m_freshBefore = time::system_clock::now() + time::seconds(contentJson.get<int>(JSON_CHALLENGE_REMAINING_TIME));
256}
Zhiyi Zhange30eb352017-04-13 15:26:14 -0700257
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700258shared_ptr<Interest>
259ClientModule::generateDownloadInterest()
260{
261 Name interestName = m_ca.m_caName;
262 interestName.append("CA").append("_DOWNLOAD").append(m_requestId);
263 auto interest = make_shared<Interest>(interestName);
264 interest->setMustBeFresh(true);
265 interest->setCanBePrefix(false);
266 return interest;
267}
Zhiyi Zhange30eb352017-04-13 15:26:14 -0700268
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700269shared_ptr<Interest>
270ClientModule::generateCertFetchInterest()
271{
272 Name interestName = m_identityName;
273 interestName.append("KEY").append(m_certId);
274 auto interest = make_shared<Interest>(interestName);
275 interest->setMustBeFresh(true);
276 interest->setCanBePrefix(false);
277 return interest;
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800278}
279
280void
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700281ClientModule::onDownloadResponse(const Data& reply)
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800282{
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800283 try {
284 security::v2::Certificate cert(reply.getContent().blockFromValue());
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700285 m_keyChain.addCertificate(m_key, cert);
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800286 _LOG_TRACE("Got DOWNLOAD response and installed the cert " << cert.getName());
287 }
288 catch (const std::exception& e) {
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700289 _LOG_ERROR("Cannot add replied certificate into the keychain " << e.what());
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800290 return;
291 }
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700292 m_isCertInstalled = true;
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800293}
294
295void
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700296ClientModule::onCertFetchResponse(const Data& reply)
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800297{
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700298 onDownloadResponse(reply);
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800299}
300
301JsonSection
302ClientModule::getJsonFromData(const Data& data)
303{
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700304 std::istringstream ss(encoding::readString(data.getContent()));
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800305 JsonSection json;
306 boost::property_tree::json_parser::read_json(ss, json);
307 return json;
308}
309
Zhiyi Zhang547c8512019-06-18 23:46:14 -0700310std::vector<std::string>
311ClientModule::parseProbeComponents(const std::string& probe)
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700312{
Zhiyi Zhang547c8512019-06-18 23:46:14 -0700313 std::vector<std::string> components;
Yufeng Zhang424d0362019-06-12 16:48:27 -0700314 std::string delimiter = ":";
315 size_t last = 0;
316 size_t next = 0;
Zhiyi Zhang547c8512019-06-18 23:46:14 -0700317 while ((next = probe.find(delimiter, last)) != std::string::npos) {
318 components.push_back(probe.substr(last, next - last));
319 last = next + 1;
320 }
321 components.push_back(probe.substr(last));
322 return components;
323}
Yufeng Zhang424d0362019-06-12 16:48:27 -0700324
Zhiyi Zhang547c8512019-06-18 23:46:14 -0700325const JsonSection
326ClientModule::genProbeRequestJson(const ClientCaItem& ca, const std::string& probeInfo)
327{
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700328 JsonSection root;
Zhiyi Zhang547c8512019-06-18 23:46:14 -0700329 std::vector<std::string> fields = parseProbeComponents(ca.m_probe);
330 std::vector<std::string> arguments = parseProbeComponents(probeInfo);;
Yufeng Zhang424d0362019-06-12 16:48:27 -0700331
332 if (arguments.size() != fields.size()) {
333 BOOST_THROW_EXCEPTION(Error("Error in genProbeRequestJson: argument list does not match field list in the config file."));
334 }
Yufeng Zhang424d0362019-06-12 16:48:27 -0700335 for (size_t i = 0; i < fields.size(); ++i) {
336 root.put(fields.at(i), arguments.at(i));
337 }
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700338 return root;
339}
340
341const JsonSection
Zhiyi Zhang5f749a22019-06-12 17:02:33 -0700342ClientModule::genNewRequestJson(const std::string& ecdhPub, const security::v2::Certificate& certRequest,
343 const shared_ptr<Data>& probeToken)
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700344{
345 JsonSection root;
346 std::stringstream ss;
347 try {
348 security::transform::bufferSource(certRequest.wireEncode().wire(), certRequest.wireEncode().size())
349 >> security::transform::base64Encode(true)
350 >> security::transform::streamSink(ss);
351 }
352 catch (const security::transform::Error& e) {
353 _LOG_ERROR("Cannot convert self-signed cert into BASE64 string " << e.what());
354 return root;
355 }
356 root.put(JSON_CLIENT_ECDH, ecdhPub);
357 root.put(JSON_CLIENT_CERT_REQ, ss.str());
Zhiyi Zhang5f749a22019-06-12 17:02:33 -0700358 if (probeToken != nullptr) {
359 // clear the stringstream
360 ss.str("");
361 ss.clear();
362 // transform the probe data into a base64 string
363 try {
364 security::transform::bufferSource(probeToken->wireEncode().wire(), probeToken->wireEncode().size())
365 >> security::transform::base64Encode(true)
366 >> security::transform::streamSink(ss);
367 }
368 catch (const security::transform::Error& e) {
369 _LOG_ERROR("Cannot convert self-signed cert into BASE64 string " << e.what());
370 return root;
371 }
372 // add the token into the JSON
373 root.put("probe-token", ss.str());
374 }
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700375 return root;
376}
377
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800378Block
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700379ClientModule::paramFromJson(const JsonSection& json)
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800380{
381 std::stringstream ss;
382 boost::property_tree::write_json(ss, json);
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700383 return makeStringBlock(ndn::tlv::ApplicationParameters, ss.str());
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800384}
385
386} // namespace ndncert
387} // namespace ndn