blob: fff47afbbca11e8e697f7c1a650536574f9b6c8e [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{
Zhiyi Zhang10130782018-02-01 18:28:49 -0800195 const auto& pib = m_keyChain.getPib();
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800196
197 auto state = make_shared<RequestState>();
Zhiyi Zhang10130782018-02-01 18:28:49 -0800198 try {
199 auto identity = pib.getIdentity(identityName);
200 state->m_key = m_keyChain.createKey(identity);
201 }
202 catch (const security::Pib::Error& e) {
203 auto identity = m_keyChain.createIdentity(identityName);
204 state->m_key = identity.getDefaultKey();
205 }
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800206 state->m_ca = ca;
207 state->m_isInstalled = false;
208
209 // generate certificate request
210 security::v2::Certificate certRequest;
211 certRequest.setName(Name(state->m_key.getName()).append("cert-request").appendVersion());
212 certRequest.setContentType(tlv::ContentType_Key);
213 certRequest.setFreshnessPeriod(time::hours(24));
Zhiyi Zhang576aad12017-10-03 15:41:53 -0700214 certRequest.setContent(state->m_key.getPublicKey().data(), state->m_key.getPublicKey().size());
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800215 SignatureInfo signatureInfo;
216 signatureInfo.setValidityPeriod(security::ValidityPeriod(time::system_clock::now(),
217 time::system_clock::now() + time::days(10)));
218 m_keyChain.sign(certRequest, signingByKey(state->m_key.getName()).setSignatureInfo(signatureInfo));
219
220 // generate interest
221 Interest interest(Name(ca.m_caName).append(Name("_NEW")).append(certRequest.wireEncode()));
222 m_keyChain.sign(interest, signingByKey(state->m_key.getName()));
223
Zhiyi Zhang1c0bd372017-12-18 18:32:55 +0800224 DataCallback dataCb = bind(&ClientModule::handleNewResponse,
225 this, _1, _2, state, requestCallback, errorCallback);
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800226 m_face.expressInterest(interest, dataCb,
227 bind(&ClientModule::onNack, this, _1, _2, errorCallback),
Zhiyi Zhang1c0bd372017-12-18 18:32:55 +0800228 bind(&ClientModule::onTimeout, this, _1, m_retryTimes,
229 dataCb, errorCallback));
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800230
231 _LOG_TRACE("NEW interest sent with identity " << identityName);
232}
233
234void
235ClientModule::handleNewResponse(const Interest& request, const Data& reply,
236 const shared_ptr<RequestState>& state,
237 const RequestCallback& requestCallback,
238 const ErrorCallback& errorCallback)
239{
240 if (!security::verifySignature(reply, state->m_ca.m_anchor)) {
241 errorCallback("Cannot verify data from " + state->m_ca.m_caName.toUri());
242 return;
243 }
244
245 const JsonSection& json = getJsonFromData(reply);
246 state->m_status = json.get(JSON_STATUS, "");
247 state->m_requestId = json.get(JSON_REQUEST_ID, "");
248
249 if (!checkStatus(*state, json, errorCallback)) {
250 return;
251 }
252
253 JsonSection challengesJson = json.get_child(JSON_CHALLENGES);
254 std::list<std::string> challengeList;
255 for (const auto& challengeJson : challengesJson) {
256 challengeList.push_back(challengeJson.second.get<std::string>(JSON_CHALLENGE_TYPE));
257 }
258 state->m_challengeList = challengeList;
259
260 _LOG_TRACE("Got NEW response with requestID " << state->m_requestId
261 << " with status " << state->m_status
262 << " with challenge number " << challengeList.size());
263
264 requestCallback(state);
265}
266
267void
268ClientModule::sendSelect(const shared_ptr<RequestState>& state,
269 const std::string& challengeType,
270 const JsonSection& selectParams,
271 const RequestCallback& requestCallback,
272 const ErrorCallback& errorCallback)
273{
274 JsonSection requestIdJson;
275 requestIdJson.put(JSON_REQUEST_ID, state->m_requestId);
276
277 state->m_challengeType = challengeType;
278
279 Name interestName(state->m_ca.m_caName);
280 interestName.append("_SELECT")
281 .append(nameBlockFromJson(requestIdJson))
282 .append(challengeType)
283 .append(nameBlockFromJson(selectParams));
284 Interest interest(interestName);
285 m_keyChain.sign(interest, signingByKey(state->m_key.getName()));
286
Zhiyi Zhang1c0bd372017-12-18 18:32:55 +0800287 DataCallback dataCb = bind(&ClientModule::handleSelectResponse,
288 this, _1, _2, state, requestCallback, errorCallback);
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800289 m_face.expressInterest(interest, dataCb,
290 bind(&ClientModule::onNack, this, _1, _2, errorCallback),
Zhiyi Zhang1c0bd372017-12-18 18:32:55 +0800291 bind(&ClientModule::onTimeout, this, _1, m_retryTimes,
292 dataCb, errorCallback));
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800293
294 _LOG_TRACE("SELECT interest sent with challenge type " << challengeType);
295}
296
297void
298ClientModule::handleSelectResponse(const Interest& request,
299 const Data& reply,
300 const shared_ptr<RequestState>& state,
301 const RequestCallback& requestCallback,
302 const ErrorCallback& errorCallback)
303{
304 if (!security::verifySignature(reply, state->m_ca.m_anchor)) {
305 errorCallback("Cannot verify data from " + state->m_ca.m_caName.toUri());
306 return;
307 }
308
309 JsonSection json = getJsonFromData(reply);
Zhiyi Zhange30eb352017-04-13 15:26:14 -0700310
Zhiyi Zhang1c0bd372017-12-18 18:32:55 +0800311 _LOG_TRACE("SELECT response would change the status from "
312 << state->m_status << " to " + json.get<std::string>(JSON_STATUS));
Zhiyi Zhange30eb352017-04-13 15:26:14 -0700313
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800314 state->m_status = json.get<std::string>(JSON_STATUS);
315
316 if (!checkStatus(*state, json, errorCallback)) {
317 return;
318 }
319
320 _LOG_TRACE("Got SELECT response with status " << state->m_status);
321
322 requestCallback(state);
323}
324
325void
326ClientModule::sendValidate(const shared_ptr<RequestState>& state,
327 const JsonSection& validateParams,
328 const RequestCallback& requestCallback,
329 const ErrorCallback& errorCallback)
330{
331 JsonSection requestIdJson;
332 requestIdJson.put(JSON_REQUEST_ID, state->m_requestId);
333
334 Name interestName(state->m_ca.m_caName);
335 interestName.append("_VALIDATE")
336 .append(nameBlockFromJson(requestIdJson))
337 .append(state->m_challengeType)
338 .append(nameBlockFromJson(validateParams));
339 Interest interest(interestName);
340 m_keyChain.sign(interest, signingByKey(state->m_key.getName()));
341
Zhiyi Zhang1c0bd372017-12-18 18:32:55 +0800342 DataCallback dataCb = bind(&ClientModule::handleValidateResponse,
343 this, _1, _2, state, requestCallback, errorCallback);
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800344 m_face.expressInterest(interest, dataCb,
345 bind(&ClientModule::onNack, this, _1, _2, errorCallback),
Zhiyi Zhang1c0bd372017-12-18 18:32:55 +0800346 bind(&ClientModule::onTimeout, this, _1, m_retryTimes,
347 dataCb, errorCallback));
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800348
349 _LOG_TRACE("VALIDATE interest sent");
350}
351
352void
353ClientModule::handleValidateResponse(const Interest& request,
354 const Data& reply,
355 const shared_ptr<RequestState>& state,
356 const RequestCallback& requestCallback,
357 const ErrorCallback& errorCallback)
358{
359 if (!security::verifySignature(reply, state->m_ca.m_anchor)) {
360 errorCallback("Cannot verify data from " + state->m_ca.m_caName.toUri());
361 return;
362 }
363
364 JsonSection json = getJsonFromData(reply);
365 state->m_status = json.get<std::string>(JSON_STATUS);
366
367 if (!checkStatus(*state, json, errorCallback)) {
368 return;
369 }
370
371 _LOG_TRACE("Got VALIDATE response with status " << state->m_status);
372
373 requestCallback(state);
374}
375
376
377void
378ClientModule::requestStatus(const shared_ptr<RequestState>& state,
379 const RequestCallback& requestCallback,
380 const ErrorCallback& errorCallback)
381{
382 JsonSection requestIdJson;
383 requestIdJson.put(JSON_REQUEST_ID, state->m_requestId);
384
385 Name interestName(state->m_ca.m_caName);
386 interestName.append("_STATUS").append(nameBlockFromJson(requestIdJson));
387 Interest interest(interestName);
388
389 m_keyChain.sign(interest, signingByKey(state->m_key.getName()));
390
Zhiyi Zhang1c0bd372017-12-18 18:32:55 +0800391 DataCallback dataCb = bind(&ClientModule::handleStatusResponse,
392 this, _1, _2, state, requestCallback, errorCallback);
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800393 m_face.expressInterest(interest, dataCb,
394 bind(&ClientModule::onNack, this, _1, _2, errorCallback),
Zhiyi Zhang1c0bd372017-12-18 18:32:55 +0800395 bind(&ClientModule::onTimeout, this, _1, m_retryTimes,
396 dataCb, errorCallback));
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800397
398 _LOG_TRACE("STATUS interest sent");
399}
400
401void
402ClientModule::handleStatusResponse(const Interest& request, const Data& reply,
403 const shared_ptr<RequestState>& state,
404 const RequestCallback& requestCallback,
405 const ErrorCallback& errorCallback)
406{
407 if (!security::verifySignature(reply, state->m_ca.m_anchor)) {
408 errorCallback("Cannot verify data from " + state->m_ca.m_caName.toUri());
409 return;
410 }
411
412 JsonSection json = getJsonFromData(reply);
413 state->m_status = json.get<std::string>(JSON_STATUS);
414
415 if (!checkStatus(*state, json, errorCallback)) {
416 return;
417 }
418
419 _LOG_TRACE("Got STATUS response with status " << state->m_status);
420
421 requestCallback(state);
422}
423
424void
425ClientModule::requestDownload(const shared_ptr<RequestState>& state,
426 const RequestCallback& requestCallback,
427 const ErrorCallback& errorCallback)
428{
429 JsonSection requestIdJson;
430 requestIdJson.put(JSON_REQUEST_ID, state->m_requestId);
431
432 Name interestName(state->m_ca.m_caName);
433 interestName.append("_DOWNLOAD").append(nameBlockFromJson(requestIdJson));
434 Interest interest(interestName);
435 interest.setMustBeFresh(true);
436
Zhiyi Zhang1c0bd372017-12-18 18:32:55 +0800437 DataCallback dataCb = bind(&ClientModule::handleDownloadResponse,
438 this, _1, _2, state, requestCallback, errorCallback);
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800439 m_face.expressInterest(interest, dataCb,
440 bind(&ClientModule::onNack, this, _1, _2, errorCallback),
Zhiyi Zhang1c0bd372017-12-18 18:32:55 +0800441 bind(&ClientModule::onTimeout, this, _1, m_retryTimes,
442 dataCb, errorCallback));
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800443
444 _LOG_TRACE("DOWNLOAD interest sent");
445}
446
447void
448ClientModule::handleDownloadResponse(const Interest& request, const Data& reply,
449 const shared_ptr<RequestState>& state,
450 const RequestCallback& requestCallback,
451 const ErrorCallback& errorCallback)
452{
453 if (!security::verifySignature(reply, state->m_ca.m_anchor)) {
454 errorCallback("Cannot verify data from " + state->m_ca.m_caName.toUri());
455 return;
456 }
457
458 try {
459 security::v2::Certificate cert(reply.getContent().blockFromValue());
460 m_keyChain.addCertificate(state->m_key, cert);
461
462 _LOG_TRACE("Got DOWNLOAD response and installed the cert " << cert.getName());
463 }
464 catch (const std::exception& e) {
465 errorCallback(std::string(e.what()));
466 return;
467 }
468
469 state->m_isInstalled = true;
470 requestCallback(state);
471}
472
473void
474ClientModule::onTimeout(const Interest& interest, int nRetriesLeft, const DataCallback& dataCallback,
Zhiyi Zhang1c0bd372017-12-18 18:32:55 +0800475 const ErrorCallback& errorCallback)
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800476{
477 if (nRetriesLeft > 0) {
478 m_face.expressInterest(interest, dataCallback,
479 bind(&ClientModule::onNack, this, _1, _2, errorCallback),
Zhiyi Zhang1c0bd372017-12-18 18:32:55 +0800480 bind(&ClientModule::onTimeout, this, _1, nRetriesLeft - 1,
481 dataCallback, errorCallback));
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800482 }
483 else {
484 errorCallback("Run out retries: still timeout");
485 return;
486 }
487}
488
489void
490ClientModule::onNack(const Interest& interest, const lp::Nack& nack, const ErrorCallback& errorCallback)
491{
492 errorCallback("Got Nack");
493}
494
495JsonSection
496ClientModule::getJsonFromData(const Data& data)
497{
498 Block jsonBlock = data.getContent();
499 std::string jsonString = encoding::readString(jsonBlock);
500 std::istringstream ss(jsonString);
501 JsonSection json;
502 boost::property_tree::json_parser::read_json(ss, json);
503 return json;
504}
505
506Block
507ClientModule::nameBlockFromJson(const JsonSection& json)
508{
509 std::stringstream ss;
510 boost::property_tree::write_json(ss, json);
511 return makeStringBlock(ndn::tlv::NameComponent, ss.str());
512}
513
514bool
515ClientModule::checkStatus(const RequestState& state, const JsonSection& json,
516 const ErrorCallback& errorCallback)
517{
Zhiyi Zhanga9bda732017-05-20 22:58:55 -0700518 if (state.m_status == ChallengeModule::FAILURE) {
519 errorCallback(json.get(JSON_FAILURE_INFO, ""));
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800520 return false;
521 }
522 if (state.m_requestId.empty() || state.m_status.empty()) {
Zhiyi Zhange30eb352017-04-13 15:26:14 -0700523 errorCallback("The response does not carry required fields. requestID: " + state.m_requestId
524 + " status: " + state.m_status);
Zhiyi Zhang23564c82017-03-01 10:22:22 -0800525 return false;
526 }
527 return true;
528}
529
530} // namespace ndncert
531} // namespace ndn