blob: 0d98d904a752a495f02adb218ead581a86cda5e8 [file] [log] [blame]
Zhiyi Zhang23564c82017-03-01 10:22:22 -08001/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
2/**
Davide Pesavento08994782018-01-22 12:13:41 -05003 * Copyright (c) 2017-2018, 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"
23#include "json-helper.hpp"
Zhiyi Zhanga9bda732017-05-20 22:58:55 -070024#include "challenge-module.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>
28
29namespace ndn {
30namespace ndncert {
31
32_LOG_INIT(ndncert.client);
33
34ClientModule::ClientModule(Face& face, security::v2::KeyChain& keyChain, size_t retryTimes)
35 : m_face(face)
36 , m_keyChain(keyChain)
37 , m_retryTimes(retryTimes)
38{
39}
40
Davide Pesavento08994782018-01-22 12:13:41 -050041ClientModule::~ClientModule() = default;
42
Zhiyi Zhang23564c82017-03-01 10:22:22 -080043void
Zhiyi Zhang1c0bd372017-12-18 18:32:55 +080044ClientModule::requestCaTrustAnchor(const Name& caName, const DataCallback& trustAnchorCallback,
45 const ErrorCallback& errorCallback)
46{
47 Name interestName = caName;
48 interestName.append("CA").append("_DOWNLOAD").append("ANCHOR");
49 Interest interest(interestName);
50 interest.setMustBeFresh(true);
51
52 m_face.expressInterest(interest, trustAnchorCallback,
53 bind(&ClientModule::onNack, this, _1, _2, errorCallback),
54 bind(&ClientModule::onTimeout, this, _1, m_retryTimes,
55 trustAnchorCallback, errorCallback));
56}
57
58void
59ClientModule::requestLocalhostList(const LocalhostListCallback& listCallback,
60 const ErrorCallback& errorCallback)
61{
62 Interest interest(Name("/localhost/CA/_LIST"));
63 interest.setMustBeFresh(true);
64 DataCallback dataCb = bind(&ClientModule::handleLocalhostListResponse,
65 this, _1, _2, listCallback, errorCallback);
66 m_face.expressInterest(interest, dataCb,
67 bind(&ClientModule::onNack, this, _1, _2, errorCallback),
68 bind(&ClientModule::onTimeout, this, _1, m_retryTimes,
69 dataCb, errorCallback));
70}
71
72void
73ClientModule::handleLocalhostListResponse(const Interest& request, const Data& reply,
74 const LocalhostListCallback& listCallback,
75 const ErrorCallback& errorCallback)
76{
77 // TODO: use the file path to replace the cert
78 // const auto& pib = m_keyChain.getPib();
79 // auto identity = pib.getDefaultIdentity();
80 // auto key = identity.getDefaultKey();
81 // auto cert = key.getDefaultCertificate();
82
83 auto cert = *(io::load<security::v2::Certificate>(m_config.m_localNdncertAnchor));
84
85 if (!security::verifySignature(reply, cert)) {
86 errorCallback("Cannot verify data from localhost CA");
87 return;
88 };
89
90 JsonSection contentJson = getJsonFromData(reply);
91 ClientConfig clientConf;
92 clientConf.load(contentJson);
93 listCallback(clientConf);
94}
95
96void
97ClientModule::requestList(const ClientCaItem& ca, const std::string& additionalInfo,
98 const ListCallback& listCallback, const ErrorCallback& errorCallback)
99{
100 Name requestName(ca.m_caName);
101 requestName.append("_LIST");
102 if (additionalInfo != "") {
103 requestName.append(additionalInfo);
104 }
105 Interest interest(requestName);
106 interest.setMustBeFresh(true);
107 DataCallback dataCb = bind(&ClientModule::handleListResponse,
108 this, _1, _2, ca, listCallback, errorCallback);
109 m_face.expressInterest(interest, dataCb,
110 bind(&ClientModule::onNack, this, _1, _2, errorCallback),
111 bind(&ClientModule::onTimeout, this, _1, m_retryTimes,
112 dataCb, errorCallback));
113}
114
115void
116ClientModule::handleListResponse(const Interest& request, const Data& reply,
117 const ClientCaItem& ca,
118 const ListCallback& listCallback,
119 const ErrorCallback& errorCallback)
120{
121 if (!security::verifySignature(reply, ca.m_anchor)) {
122 errorCallback("Cannot verify data from " + ca.m_caName.toUri());
123 return;
124 };
125
126 std::list<Name> caList;
127 Name assignedName;
128
129 JsonSection contentJson = getJsonFromData(reply);
130 auto recommendedName = contentJson.get("recommended-identity", "");
131 if (recommendedName == "") {
132 // without recommendation
133 auto caListJson = contentJson.get_child("ca-list");
134 auto it = caListJson.begin();
135 for(; it != caListJson.end(); it++) {
136 caList.push_back(Name(it->second.get<std::string>("ca-prefix")));
137 }
138 }
139 else {
140 // with recommendation
141 Name caName(contentJson.get<std::string>("recommended-ca"));
142 caList.push_back(caName);
143 assignedName = caName.append(recommendedName);
144 }
145 Name schemaDataName(contentJson.get("trust-schema", ""));
146 listCallback(caList, assignedName, schemaDataName);
147}
148
149void
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800150ClientModule::sendProbe(const ClientCaItem& ca, const std::string& probeInfo,
151 const RequestCallback& requestCallback,
152 const ErrorCallback& errorCallback)
153{
154 Interest interest(Name(ca.m_caName).append("_PROBE").append(probeInfo));
155 interest.setMustBeFresh(true);
Zhiyi Zhang1c0bd372017-12-18 18:32:55 +0800156 DataCallback dataCb = bind(&ClientModule::handleProbeResponse,
157 this, _1, _2, ca, requestCallback, errorCallback);
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800158 m_face.expressInterest(interest, dataCb,
159 bind(&ClientModule::onNack, this, _1, _2, errorCallback),
Zhiyi Zhang1c0bd372017-12-18 18:32:55 +0800160 bind(&ClientModule::onTimeout, this, _1, m_retryTimes,
161 dataCb, errorCallback));
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800162
163 _LOG_TRACE("PROBE interest sent with Probe info " << probeInfo);
164}
165
166void
167ClientModule::handleProbeResponse(const Interest& request, const Data& reply,
168 const ClientCaItem& ca,
169 const RequestCallback& requestCallback,
170 const ErrorCallback& errorCallback)
171{
172 if (!security::verifySignature(reply, ca.m_anchor)) {
173 errorCallback("Cannot verify data from " + ca.m_caName.toUri());
174 return;
175 };
176 JsonSection contentJson = getJsonFromData(reply);
177 std::string identityNameString = contentJson.get(JSON_IDNENTIFIER, "");
178 if (!identityNameString.empty()) {
179 Name identityName(identityNameString);
180 sendNew(ca, identityName, requestCallback, errorCallback);
181
182 _LOG_TRACE("Got PROBE response with identity " << identityName);
183 }
184 else {
185 errorCallback("The response does not carry required fields.");
186 return;
187 }
188}
189
190void
191ClientModule::sendNew(const ClientCaItem& ca, const Name& identityName,
192 const RequestCallback& requestCallback,
193 const ErrorCallback& errorCallback)
194{
195 security::Identity identity = m_keyChain.createIdentity(identityName);
196
197 auto state = make_shared<RequestState>();
198 state->m_key = m_keyChain.createKey(identity);
199 state->m_ca = ca;
200 state->m_isInstalled = false;
201
202 // generate certificate request
203 security::v2::Certificate certRequest;
204 certRequest.setName(Name(state->m_key.getName()).append("cert-request").appendVersion());
205 certRequest.setContentType(tlv::ContentType_Key);
206 certRequest.setFreshnessPeriod(time::hours(24));
Zhiyi Zhang576aad12017-10-03 15:41:53 -0700207 certRequest.setContent(state->m_key.getPublicKey().data(), state->m_key.getPublicKey().size());
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800208 SignatureInfo signatureInfo;
209 signatureInfo.setValidityPeriod(security::ValidityPeriod(time::system_clock::now(),
210 time::system_clock::now() + time::days(10)));
211 m_keyChain.sign(certRequest, signingByKey(state->m_key.getName()).setSignatureInfo(signatureInfo));
212
213 // generate interest
214 Interest interest(Name(ca.m_caName).append(Name("_NEW")).append(certRequest.wireEncode()));
215 m_keyChain.sign(interest, signingByKey(state->m_key.getName()));
216
Zhiyi Zhang1c0bd372017-12-18 18:32:55 +0800217 DataCallback dataCb = bind(&ClientModule::handleNewResponse,
218 this, _1, _2, state, requestCallback, errorCallback);
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800219 m_face.expressInterest(interest, dataCb,
220 bind(&ClientModule::onNack, this, _1, _2, errorCallback),
Zhiyi Zhang1c0bd372017-12-18 18:32:55 +0800221 bind(&ClientModule::onTimeout, this, _1, m_retryTimes,
222 dataCb, errorCallback));
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800223
224 _LOG_TRACE("NEW interest sent with identity " << identityName);
225}
226
227void
228ClientModule::handleNewResponse(const Interest& request, const Data& reply,
229 const shared_ptr<RequestState>& state,
230 const RequestCallback& requestCallback,
231 const ErrorCallback& errorCallback)
232{
233 if (!security::verifySignature(reply, state->m_ca.m_anchor)) {
234 errorCallback("Cannot verify data from " + state->m_ca.m_caName.toUri());
235 return;
236 }
237
238 const JsonSection& json = getJsonFromData(reply);
239 state->m_status = json.get(JSON_STATUS, "");
240 state->m_requestId = json.get(JSON_REQUEST_ID, "");
241
242 if (!checkStatus(*state, json, errorCallback)) {
243 return;
244 }
245
246 JsonSection challengesJson = json.get_child(JSON_CHALLENGES);
247 std::list<std::string> challengeList;
248 for (const auto& challengeJson : challengesJson) {
249 challengeList.push_back(challengeJson.second.get<std::string>(JSON_CHALLENGE_TYPE));
250 }
251 state->m_challengeList = challengeList;
252
253 _LOG_TRACE("Got NEW response with requestID " << state->m_requestId
254 << " with status " << state->m_status
255 << " with challenge number " << challengeList.size());
256
257 requestCallback(state);
258}
259
260void
261ClientModule::sendSelect(const shared_ptr<RequestState>& state,
262 const std::string& challengeType,
263 const JsonSection& selectParams,
264 const RequestCallback& requestCallback,
265 const ErrorCallback& errorCallback)
266{
267 JsonSection requestIdJson;
268 requestIdJson.put(JSON_REQUEST_ID, state->m_requestId);
269
270 state->m_challengeType = challengeType;
271
272 Name interestName(state->m_ca.m_caName);
273 interestName.append("_SELECT")
274 .append(nameBlockFromJson(requestIdJson))
275 .append(challengeType)
276 .append(nameBlockFromJson(selectParams));
277 Interest interest(interestName);
278 m_keyChain.sign(interest, signingByKey(state->m_key.getName()));
279
Zhiyi Zhang1c0bd372017-12-18 18:32:55 +0800280 DataCallback dataCb = bind(&ClientModule::handleSelectResponse,
281 this, _1, _2, state, requestCallback, errorCallback);
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800282 m_face.expressInterest(interest, dataCb,
283 bind(&ClientModule::onNack, this, _1, _2, errorCallback),
Zhiyi Zhang1c0bd372017-12-18 18:32:55 +0800284 bind(&ClientModule::onTimeout, this, _1, m_retryTimes,
285 dataCb, errorCallback));
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800286
287 _LOG_TRACE("SELECT interest sent with challenge type " << challengeType);
288}
289
290void
291ClientModule::handleSelectResponse(const Interest& request,
292 const Data& reply,
293 const shared_ptr<RequestState>& state,
294 const RequestCallback& requestCallback,
295 const ErrorCallback& errorCallback)
296{
297 if (!security::verifySignature(reply, state->m_ca.m_anchor)) {
298 errorCallback("Cannot verify data from " + state->m_ca.m_caName.toUri());
299 return;
300 }
301
302 JsonSection json = getJsonFromData(reply);
Zhiyi Zhange30eb352017-04-13 15:26:14 -0700303
Zhiyi Zhang1c0bd372017-12-18 18:32:55 +0800304 _LOG_TRACE("SELECT response would change the status from "
305 << state->m_status << " to " + json.get<std::string>(JSON_STATUS));
Zhiyi Zhange30eb352017-04-13 15:26:14 -0700306
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800307 state->m_status = json.get<std::string>(JSON_STATUS);
308
309 if (!checkStatus(*state, json, errorCallback)) {
310 return;
311 }
312
313 _LOG_TRACE("Got SELECT response with status " << state->m_status);
314
315 requestCallback(state);
316}
317
318void
319ClientModule::sendValidate(const shared_ptr<RequestState>& state,
320 const JsonSection& validateParams,
321 const RequestCallback& requestCallback,
322 const ErrorCallback& errorCallback)
323{
324 JsonSection requestIdJson;
325 requestIdJson.put(JSON_REQUEST_ID, state->m_requestId);
326
327 Name interestName(state->m_ca.m_caName);
328 interestName.append("_VALIDATE")
329 .append(nameBlockFromJson(requestIdJson))
330 .append(state->m_challengeType)
331 .append(nameBlockFromJson(validateParams));
332 Interest interest(interestName);
333 m_keyChain.sign(interest, signingByKey(state->m_key.getName()));
334
Zhiyi Zhang1c0bd372017-12-18 18:32:55 +0800335 DataCallback dataCb = bind(&ClientModule::handleValidateResponse,
336 this, _1, _2, state, requestCallback, errorCallback);
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800337 m_face.expressInterest(interest, dataCb,
338 bind(&ClientModule::onNack, this, _1, _2, errorCallback),
Zhiyi Zhang1c0bd372017-12-18 18:32:55 +0800339 bind(&ClientModule::onTimeout, this, _1, m_retryTimes,
340 dataCb, errorCallback));
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800341
342 _LOG_TRACE("VALIDATE interest sent");
343}
344
345void
346ClientModule::handleValidateResponse(const Interest& request,
347 const Data& reply,
348 const shared_ptr<RequestState>& state,
349 const RequestCallback& requestCallback,
350 const ErrorCallback& errorCallback)
351{
352 if (!security::verifySignature(reply, state->m_ca.m_anchor)) {
353 errorCallback("Cannot verify data from " + state->m_ca.m_caName.toUri());
354 return;
355 }
356
357 JsonSection json = getJsonFromData(reply);
358 state->m_status = json.get<std::string>(JSON_STATUS);
359
360 if (!checkStatus(*state, json, errorCallback)) {
361 return;
362 }
363
364 _LOG_TRACE("Got VALIDATE response with status " << state->m_status);
365
366 requestCallback(state);
367}
368
369
370void
371ClientModule::requestStatus(const shared_ptr<RequestState>& state,
372 const RequestCallback& requestCallback,
373 const ErrorCallback& errorCallback)
374{
375 JsonSection requestIdJson;
376 requestIdJson.put(JSON_REQUEST_ID, state->m_requestId);
377
378 Name interestName(state->m_ca.m_caName);
379 interestName.append("_STATUS").append(nameBlockFromJson(requestIdJson));
380 Interest interest(interestName);
381
382 m_keyChain.sign(interest, signingByKey(state->m_key.getName()));
383
Zhiyi Zhang1c0bd372017-12-18 18:32:55 +0800384 DataCallback dataCb = bind(&ClientModule::handleStatusResponse,
385 this, _1, _2, state, requestCallback, errorCallback);
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800386 m_face.expressInterest(interest, dataCb,
387 bind(&ClientModule::onNack, this, _1, _2, errorCallback),
Zhiyi Zhang1c0bd372017-12-18 18:32:55 +0800388 bind(&ClientModule::onTimeout, this, _1, m_retryTimes,
389 dataCb, errorCallback));
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800390
391 _LOG_TRACE("STATUS interest sent");
392}
393
394void
395ClientModule::handleStatusResponse(const Interest& request, const Data& reply,
396 const shared_ptr<RequestState>& state,
397 const RequestCallback& requestCallback,
398 const ErrorCallback& errorCallback)
399{
400 if (!security::verifySignature(reply, state->m_ca.m_anchor)) {
401 errorCallback("Cannot verify data from " + state->m_ca.m_caName.toUri());
402 return;
403 }
404
405 JsonSection json = getJsonFromData(reply);
406 state->m_status = json.get<std::string>(JSON_STATUS);
407
408 if (!checkStatus(*state, json, errorCallback)) {
409 return;
410 }
411
412 _LOG_TRACE("Got STATUS response with status " << state->m_status);
413
414 requestCallback(state);
415}
416
417void
418ClientModule::requestDownload(const shared_ptr<RequestState>& state,
419 const RequestCallback& requestCallback,
420 const ErrorCallback& errorCallback)
421{
422 JsonSection requestIdJson;
423 requestIdJson.put(JSON_REQUEST_ID, state->m_requestId);
424
425 Name interestName(state->m_ca.m_caName);
426 interestName.append("_DOWNLOAD").append(nameBlockFromJson(requestIdJson));
427 Interest interest(interestName);
428 interest.setMustBeFresh(true);
429
Zhiyi Zhang1c0bd372017-12-18 18:32:55 +0800430 DataCallback dataCb = bind(&ClientModule::handleDownloadResponse,
431 this, _1, _2, state, requestCallback, errorCallback);
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800432 m_face.expressInterest(interest, dataCb,
433 bind(&ClientModule::onNack, this, _1, _2, errorCallback),
Zhiyi Zhang1c0bd372017-12-18 18:32:55 +0800434 bind(&ClientModule::onTimeout, this, _1, m_retryTimes,
435 dataCb, errorCallback));
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800436
437 _LOG_TRACE("DOWNLOAD interest sent");
438}
439
440void
441ClientModule::handleDownloadResponse(const Interest& request, const Data& reply,
442 const shared_ptr<RequestState>& state,
443 const RequestCallback& requestCallback,
444 const ErrorCallback& errorCallback)
445{
446 if (!security::verifySignature(reply, state->m_ca.m_anchor)) {
447 errorCallback("Cannot verify data from " + state->m_ca.m_caName.toUri());
448 return;
449 }
450
451 try {
452 security::v2::Certificate cert(reply.getContent().blockFromValue());
453 m_keyChain.addCertificate(state->m_key, cert);
454
455 _LOG_TRACE("Got DOWNLOAD response and installed the cert " << cert.getName());
456 }
457 catch (const std::exception& e) {
458 errorCallback(std::string(e.what()));
459 return;
460 }
461
462 state->m_isInstalled = true;
463 requestCallback(state);
464}
465
466void
467ClientModule::onTimeout(const Interest& interest, int nRetriesLeft, const DataCallback& dataCallback,
Zhiyi Zhang1c0bd372017-12-18 18:32:55 +0800468 const ErrorCallback& errorCallback)
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800469{
470 if (nRetriesLeft > 0) {
471 m_face.expressInterest(interest, dataCallback,
472 bind(&ClientModule::onNack, this, _1, _2, errorCallback),
Zhiyi Zhang1c0bd372017-12-18 18:32:55 +0800473 bind(&ClientModule::onTimeout, this, _1, nRetriesLeft - 1,
474 dataCallback, errorCallback));
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800475 }
476 else {
477 errorCallback("Run out retries: still timeout");
478 return;
479 }
480}
481
482void
483ClientModule::onNack(const Interest& interest, const lp::Nack& nack, const ErrorCallback& errorCallback)
484{
485 errorCallback("Got Nack");
486}
487
488JsonSection
489ClientModule::getJsonFromData(const Data& data)
490{
491 Block jsonBlock = data.getContent();
492 std::string jsonString = encoding::readString(jsonBlock);
493 std::istringstream ss(jsonString);
494 JsonSection json;
495 boost::property_tree::json_parser::read_json(ss, json);
496 return json;
497}
498
499Block
500ClientModule::nameBlockFromJson(const JsonSection& json)
501{
502 std::stringstream ss;
503 boost::property_tree::write_json(ss, json);
504 return makeStringBlock(ndn::tlv::NameComponent, ss.str());
505}
506
507bool
508ClientModule::checkStatus(const RequestState& state, const JsonSection& json,
509 const ErrorCallback& errorCallback)
510{
Zhiyi Zhanga9bda732017-05-20 22:58:55 -0700511 if (state.m_status == ChallengeModule::FAILURE) {
512 errorCallback(json.get(JSON_FAILURE_INFO, ""));
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800513 return false;
514 }
515 if (state.m_requestId.empty() || state.m_status.empty()) {
Zhiyi Zhange30eb352017-04-13 15:26:14 -0700516 errorCallback("The response does not carry required fields. requestID: " + state.m_requestId
517 + " status: " + state.m_status);
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800518 return false;
519 }
520 return true;
521}
522
523} // namespace ndncert
524} // namespace ndn