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