blob: bc5bb5a3efdbe0e6fe3df0b14a32442b84ad19c8 [file] [log] [blame]
Zhiyi Zhang08e0e982017-03-01 10:10:42 -08001/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
2/**
Zhiyi Zhangad9e04f2020-03-27 12:04:31 -07003 * Copyright (c) 2017-2020, Regents of the University of California.
Zhiyi Zhang08e0e982017-03-01 10:10:42 -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 "challenge-module.hpp"
Zhiyi Zhangb6fab0f2017-09-21 16:26:27 -070023#include <iostream>
Zhiyi Zhang1c0bd372017-12-18 18:32:55 +080024#include <string>
Zhiyi Zhangad9e04f2020-03-27 12:04:31 -070025#include <csignal>
Zhiyi Zhang08e0e982017-03-01 10:10:42 -080026#include <boost/program_options/options_description.hpp>
27#include <boost/program_options/variables_map.hpp>
28#include <boost/program_options/parsers.hpp>
Zhiyi Zhangad9e04f2020-03-27 12:04:31 -070029#include <boost/asio.hpp>
Zhiyi Zhang1c0bd372017-12-18 18:32:55 +080030#include <ndn-cxx/security/verification-helpers.hpp>
Zhiyi Zhang08e0e982017-03-01 10:10:42 -080031
32namespace ndn {
33namespace ndncert {
34
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -070035static void startApplication();
36
Zhiyi Zhang1c0bd372017-12-18 18:32:55 +080037int nStep;
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -070038Face face;
39security::v2::KeyChain keyChain;
40std::string challengeType;
Zhiyi Zhang36706832019-07-04 21:33:03 -070041int validityPeriod = -1;
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -070042ClientModule client(keyChain);
Zhiyi Zhang08e0e982017-03-01 10:10:42 -080043
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -070044static std::list<std::string>
45captureParams(const JsonSection& requirement)
Zhiyi Zhang08e0e982017-03-01 10:10:42 -080046{
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -070047 std::list<std::string> results;
Zhiyi Zhang547c8512019-06-18 23:46:14 -070048 for (const auto& item : requirement) {
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -070049 std::cerr << item.second.get<std::string>("") << std::endl;
50 std::cerr << "Please provide the argument: " << item.first << " : " << std::endl;
51 std::string tempParam;
52 getline(std::cin, tempParam);
53 results.push_back(tempParam);
Zhiyi Zhang08e0e982017-03-01 10:10:42 -080054 }
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -070055 std::cerr << "Got it. This is what you've provided:" << std::endl;
56 auto it1 = results.begin();
57 auto it2 = requirement.begin();
58 for (; it1 != results.end() && it2 != requirement.end(); it1++, it2++) {
59 std::cerr << it2->first << " : " << *it1 << std::endl;
Zhiyi Zhang08e0e982017-03-01 10:10:42 -080060 }
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -070061 return results;
62}
Zhiyi Zhang08e0e982017-03-01 10:10:42 -080063
Zhiyi Zhang547c8512019-06-18 23:46:14 -070064static std::list<std::string>
65captureParams(const std::vector<std::string>& requirement)
66{
67 std::list<std::string> results;
68 for (const auto& item : requirement) {
69 std::cerr << "Please provide the argument: " << item << " : " << std::endl;
70 std::string tempParam;
71 getline(std::cin, tempParam);
72 results.push_back(tempParam);
73 }
74 std::cerr << "Got it. This is what you've provided:" << std::endl;
75 auto it1 = results.begin();
76 auto it2 = requirement.begin();
77 for (; it1 != results.end() && it2 != requirement.end(); it1++, it2++) {
78 std::cerr << *it2 << " : " << *it1 << std::endl;
79 }
80 return results;
81}
82
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -070083static void
Zhiyi Zhang36706832019-07-04 21:33:03 -070084captureValidityPeriod()
85{
Zhiyi Zhangad9e04f2020-03-27 12:04:31 -070086 if (validityPeriod > 0) {
87 return;
88 }
89 std::cerr << "Step " << nStep++
90 << ": Please type in your expected validity period of your certificate."
91 << " Type the number of hours (168 for week, 730 for month, 8760 for year)."
92 << " The CA may reject your application if your expected period is too long." << std::endl;
93 std::string periodStr = "";
94 getline(std::cin, periodStr);
95 try {
96 validityPeriod = std::stoi(periodStr);
97 }
98 catch (const std::exception& e) {
99 validityPeriod = -1;
Zhiyi Zhang36706832019-07-04 21:33:03 -0700100 }
101}
102
103static void
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700104onNackCb()
105{
106 std::cerr << "Got NACK\n";
107}
108
109static void
110timeoutCb()
111{
112 std::cerr << "Interest sent time out\n";
113}
114
115static void
116downloadCb(const Data& reply)
117{
Zhiyi Zhangad9e04f2020-03-27 12:04:31 -0700118 auto cert = client.onDownloadResponse(reply);
119 if (cert == nullptr) {
120 std::cerr << "Certificate cannot be installed to your local keychain" << std::endl;
121 return;
122 }
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700123 std::cerr << "Step " << nStep++
Zhiyi Zhangad9e04f2020-03-27 12:04:31 -0700124 << ": DONE! Certificate has already been installed to local keychain\n"
125 << "Certificate Name: " << cert->getName().toUri() << std::endl;
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700126}
127
128static void
129challengeCb(const Data& reply)
130{
131 client.onChallengeResponse(reply);
132 if (client.getApplicationStatus() == STATUS_SUCCESS) {
133 std::cerr << "DONE! Certificate has already been issued \n";
134 face.expressInterest(*client.generateDownloadInterest(), bind(&downloadCb, _2),
135 bind(&onNackCb), bind(&timeoutCb));
Zhiyi Zhang4d89fe02017-04-28 18:51:51 -0700136 return;
137 }
138
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700139 auto challenge = ChallengeModule::createChallengeModule(challengeType);
Zhiyi Zhang36706832019-07-04 21:33:03 -0700140 auto requirement = challenge->getRequirementForChallenge(client.getApplicationStatus(),
141 client.getChallengeStatus());
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700142 if (requirement.size() > 0) {
Zhiyi Zhang916ba2d2018-02-01 18:16:27 -0800143 std::cerr << "Step " << nStep++ << ": Please satisfy following instruction(s)\n";
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700144 std::string redo = "";
145 std::list<std::string> capturedParams;
Zhiyi Zhangad9e04f2020-03-27 12:04:31 -0700146 capturedParams = captureParams(requirement);
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700147 auto it1 = capturedParams.begin();
148 auto it2 = requirement.begin();
149 for (; it1 != capturedParams.end() && it2 != requirement.end(); it1++, it2++) {
150 it2->second.put("", *it1);
Zhiyi Zhang08e0e982017-03-01 10:10:42 -0800151 }
Zhiyi Zhang08e0e982017-03-01 10:10:42 -0800152 }
Zhiyi Zhang36706832019-07-04 21:33:03 -0700153 face.expressInterest(*client.generateChallengeInterest(challenge->genChallengeRequestJson(client.getApplicationStatus(),
154 client.getChallengeStatus(),
155 requirement)),
156 bind(&challengeCb, _2), bind(&onNackCb), bind(&timeoutCb));
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700157}
Zhiyi Zhang08e0e982017-03-01 10:10:42 -0800158
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700159static void
160newCb(const Data& reply)
161{
Zhiyi Zhang36706832019-07-04 21:33:03 -0700162 int challengeIndex = 0;
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700163 auto challengeList = client.onNewResponse(reply);
Zhiyi Zhang36706832019-07-04 21:33:03 -0700164 if (challengeList.size() < 1) {
165 std::cerr << "There is no available challenge provided by the CA. Exit" << std::endl;
166 return;
Zhiyi Zhang08e0e982017-03-01 10:10:42 -0800167 }
Zhiyi Zhang36706832019-07-04 21:33:03 -0700168 else if (challengeList.size() > 1) {
169 int count = 0;
170 std::string choice = "";
171 std::cerr << "Step " << nStep++ << ": Please type in the challenge index that you want to perform\n";
Zhiyi Zhangad9e04f2020-03-27 12:04:31 -0700172 count = 0;
173 for (auto item : challengeList) {
174 std::cerr << "\t" << count++ << " : "<< item << std::endl;
175 }
176 getline(std::cin, choice);
177 try {
178 challengeIndex = std::stoi(choice);
179 }
180 catch (const std::exception& e) {
181 challengeIndex = -1;
182 }
183 if (challengeIndex < 0 || challengeIndex >= count) {
184 std::cerr << "Your input index is out of range. Exit." << std::endl;
185 return;
186 }
Zhiyi Zhang36706832019-07-04 21:33:03 -0700187 }
188 auto it = challengeList.begin();
189 std::advance(it, challengeIndex);
190 unique_ptr<ChallengeModule> challenge = ChallengeModule::createChallengeModule(*it);
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700191 if (challenge != nullptr) {
Zhiyi Zhang36706832019-07-04 21:33:03 -0700192 challengeType = *it;
193 std::cerr << "The challenge has been selected: " << challengeType << std::endl;
Zhiyi Zhang08e0e982017-03-01 10:10:42 -0800194 }
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700195 else {
Zhiyi Zhang36706832019-07-04 21:33:03 -0700196 std::cerr << "Error. Cannot load selected Challenge Module. Exit." << std::endl;
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700197 return;
198 }
199 auto requirement = challenge->getRequirementForChallenge(client.getApplicationStatus(),
200 client.getChallengeStatus());
201 if (requirement.size() > 0) {
202 std::cerr << "Step " << nStep++ << ": Please satisfy following instruction(s)\n";
203 std::string redo = "";
204 std::list<std::string> capturedParams;
Zhiyi Zhangad9e04f2020-03-27 12:04:31 -0700205 capturedParams = captureParams(requirement);
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700206 auto it1 = capturedParams.begin();
207 auto it2 = requirement.begin();
208 for (; it1 != capturedParams.end() && it2 != requirement.end(); it1++, it2++) {
209 it2->second.put("", *it1);
210 }
211 }
Zhiyi Zhang36706832019-07-04 21:33:03 -0700212 face.expressInterest(*client.generateChallengeInterest(challenge->genChallengeRequestJson(client.getApplicationStatus(),
213 client.getChallengeStatus(),
214 requirement)),
215 bind(&challengeCb, _2), bind(&onNackCb), bind(&timeoutCb));
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700216}
Zhiyi Zhang08e0e982017-03-01 10:10:42 -0800217
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700218static void
219probeInfoCb(const Data& reply)
220{
Zhiyi Zhangcaab5462019-10-18 13:41:02 -0700221 if (!client.verifyProbeInfoResponse(reply)) {
222 std::cerr << "The fetched CA information cannot be trusted because its integrity is broken" << std::endl;
223 return;
224 }
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700225 auto contentJson = ClientModule::getJsonFromData(reply);
226 auto caItem = ClientConfig::extractCaItem(contentJson);
227
Zhiyi Zhangcaab5462019-10-18 13:41:02 -0700228 std::cerr << "Will use a new trust anchor, please double check the identity info: \n"
229 << "This trust anchor information is signed by " << reply.getSignature().getKeyLocator() << std::endl
230 << "The certificate is " << caItem.m_anchor << std::endl;
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700231 std::cerr << "Do you trust the information? Type in YES or NO" << std::endl;
232
233 std::string answer;
234 getline(std::cin, answer);
Zhiyi Zhang36706832019-07-04 21:33:03 -0700235 boost::algorithm::to_lower(answer);
236 if (answer == "yes") {
Zhiyi Zhangcaab5462019-10-18 13:41:02 -0700237 std::cerr << "You answered YES: new CA will be used" << std::endl;
238 client.addCaFromProbeInfoResponse(reply);
239 // client.getClientConf().save(std::string(SYSCONFDIR) + "/ndncert/client.conf");
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700240 startApplication();
241 }
242 else {
Zhiyi Zhangcaab5462019-10-18 13:41:02 -0700243 std::cerr << "You answered NO: new CA will not be used" << std::endl;
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700244 return;
245 }
246}
247
248static void
249probeCb(const Data& reply)
250{
Zhiyi Zhang781a5602019-06-26 19:05:04 -0700251 client.onProbeResponse(reply);
Zhiyi Zhang36706832019-07-04 21:33:03 -0700252 captureValidityPeriod();
Zhiyi Zhangad9e04f2020-03-27 12:04:31 -0700253 if (validityPeriod <= 0) {
254 std::cerr << "Invalid period time. Exit." << std::endl;
255 return;
256 }
Zhiyi Zhang781a5602019-06-26 19:05:04 -0700257 auto probeToken = make_shared<Data>(reply);
Zhiyi Zhang1a735bc2019-07-04 21:36:49 -0700258 auto now = time::system_clock::now();
Zhiyi Zhang36706832019-07-04 21:33:03 -0700259 std::cerr << "The validity period of your certificate will be: " << validityPeriod << " hours" << std::endl;
Zhiyi Zhangad9e04f2020-03-27 12:04:31 -0700260 auto interest = client.generateNewInterest(now, now + time::hours(validityPeriod), Name(), probeToken);
261 if (interest != nullptr) {
262 face.expressInterest(*interest, bind(&newCb, _2), bind(&onNackCb), bind(&timeoutCb));
263 }
264 else {
265 std::cerr << "Cannot generate the Interest for NEW step. Exit" << std::endl;
266 }
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700267}
268
269static void
270startApplication()
271{
272 nStep = 0;
273 auto caList = client.getClientConf().m_caItems;
274 int count = 0;
275 for (auto item : caList) {
276 std::cerr << "***************************************\n"
277 << "Index: " << count++ << "\n"
278 << "CA prefix:" << item.m_caName << "\n"
279 << "Introduction: " << item.m_caInfo << "\n"
280 << "***************************************\n";
281 }
282 std::vector<ClientCaItem> caVector{std::begin(caList), std::end(caList)};
283 std::cerr << "Step "
284 << nStep++ << ": Please type in the CA INDEX that you want to apply"
285 << " or type in NONE if your expected CA is not in the list\n";
286
Zhiyi Zhang36706832019-07-04 21:33:03 -0700287 std::string caIndexS, caIndexSLower;
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700288 getline(std::cin, caIndexS);
Zhiyi Zhang36706832019-07-04 21:33:03 -0700289 caIndexSLower = caIndexS;
290 boost::algorithm::to_lower(caIndexSLower);
291 if (caIndexSLower == "none") {
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700292 std::cerr << "Step " << nStep << ": Please type in the CA Name\n";
Zhiyi Zhangcaab5462019-10-18 13:41:02 -0700293 std::string expectedCAName;
294 getline(std::cin, expectedCAName);
295 face.expressInterest(*client.generateProbeInfoInterest(Name(expectedCAName)),
Zhiyi Zhang36706832019-07-04 21:33:03 -0700296 bind(&probeInfoCb, _2), bind(&onNackCb), bind(&timeoutCb));
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700297 }
298 else {
Zhiyi Zhang36706832019-07-04 21:33:03 -0700299 int caIndex;
300 try {
301 caIndex = std::stoi(caIndexS);
302 }
303 catch (const std::exception& e) {
304 std::cerr << "Your input is neither NONE nor a valid index. Exit" << std::endl;
305 return;
306 }
307 if (caIndex < 0 || caIndex >= count) {
308 std::cerr << "Your input is not an existing index. Exit" << std::endl;
309 return;
310 }
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700311 auto targetCaItem = caVector[caIndex];
312
313 if (targetCaItem.m_probe != "") {
Zhiyi Zhang547c8512019-06-18 23:46:14 -0700314 std::cerr << "Step " << nStep++ << ": Please provide information for name assignment" << std::endl;
315 std::vector<std::string> probeFields = ClientModule::parseProbeComponents(targetCaItem.m_probe);
316 std::string redo = "";
317 std::list<std::string> capturedParams;
Zhiyi Zhangad9e04f2020-03-27 12:04:31 -0700318 capturedParams = captureParams(probeFields);
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700319 std::string probeInfo;
Zhiyi Zhang547c8512019-06-18 23:46:14 -0700320 for (const auto& item : capturedParams) {
321 probeInfo += item;
322 probeInfo += ":";
323 }
324 probeInfo = probeInfo.substr(0, probeInfo.size() - 1);
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700325 face.expressInterest(*client.generateProbeInterest(targetCaItem, probeInfo),
Zhiyi Zhang36706832019-07-04 21:33:03 -0700326 bind(&probeCb, _2), bind(&onNackCb), bind(&timeoutCb));
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700327 }
328 else {
Zhiyi Zhangad9e04f2020-03-27 12:04:31 -0700329 std::cerr << "Step " << nStep++ << ": Please type in the full identity name you want to get (with CA prefix)\n";
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700330 std::string identityNameStr;
331 getline(std::cin, identityNameStr);
Zhiyi Zhang36706832019-07-04 21:33:03 -0700332 captureValidityPeriod();
Zhiyi Zhangad9e04f2020-03-27 12:04:31 -0700333 if (validityPeriod <= 0) {
334 std::cerr << "Invalid period time. Exit." << std::endl;
335 return;
336 }
337 Name idName(identityNameStr);
Zhiyi Zhang36706832019-07-04 21:33:03 -0700338 std::cerr << "The validity period of your certificate will be: " << validityPeriod << " hours" << std::endl;
Zhiyi Zhang1a735bc2019-07-04 21:36:49 -0700339 auto now = time::system_clock::now();
Zhiyi Zhangad9e04f2020-03-27 12:04:31 -0700340 auto interest = client.generateNewInterest(now, now + time::hours(validityPeriod), idName);
341 if (interest != nullptr) {
342 face.expressInterest(*interest, bind(&newCb, _2), bind(&onNackCb), bind(&timeoutCb));
343 }
344 else {
345 std::cerr << "Cannot generate the Interest for NEW step. Exit" << std::endl;
346 }
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700347 }
348 }
349}
350
Zhiyi Zhangad9e04f2020-03-27 12:04:31 -0700351static void
352handleSignal(const boost::system::error_code& error, int signalNum)
353{
354 if (error) {
355 return;
356 }
357 const char* signalName = ::strsignal(signalNum);
358 std::cerr << "Exiting on signal ";
359 if (signalName == nullptr) {
360 std::cerr << signalNum;
361 }
362 else {
363 std::cerr << signalName;
364 }
365 std::cerr << std::endl;
366 client.endSession();
367 face.getIoService().stop();
368}
Zhiyi Zhang08e0e982017-03-01 10:10:42 -0800369
370int
371main(int argc, char* argv[])
372{
Zhiyi Zhangad9e04f2020-03-27 12:04:31 -0700373 boost::asio::signal_set terminateSignals(face.getIoService());
374 terminateSignals.add(SIGINT);
375 terminateSignals.add(SIGTERM);
376 terminateSignals.async_wait(handleSignal);
377
Zhiyi Zhang08e0e982017-03-01 10:10:42 -0800378 namespace po = boost::program_options;
379 std::string configFilePath = std::string(SYSCONFDIR) + "/ndncert/client.conf";
Zhiyi Zhang36706832019-07-04 21:33:03 -0700380 po::options_description description("General Usage\n ndncert-client [-h] [-c] [-v]\n");
Zhiyi Zhang08e0e982017-03-01 10:10:42 -0800381 description.add_options()
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700382 ("help,h", "produce help message")
Zhiyi Zhang36706832019-07-04 21:33:03 -0700383 ("config-file,c", po::value<std::string>(&configFilePath), "config file name")
384 ("validity-period,v", po::value<int>(&validityPeriod)->default_value(-1), "the validity period of your certificate being requested");
Zhiyi Zhang08e0e982017-03-01 10:10:42 -0800385 po::positional_options_description p;
386
387 po::variables_map vm;
388 try {
389 po::store(po::command_line_parser(argc, argv).options(description).positional(p).run(), vm);
390 po::notify(vm);
391 }
392 catch (const std::exception& e) {
393 std::cerr << "ERROR: " << e.what() << std::endl;
394 return 1;
395 }
396 if (vm.count("help") != 0) {
397 std::cerr << description << std::endl;
398 return 0;
399 }
Zhiyi Zhangd8993b92019-07-04 21:58:10 -0700400 try {
401 client.getClientConf().load(configFilePath);
402 }
403 catch (const std::exception& e) {
404 std::cerr << "Cannot load the configuration file: " << e.what() << std::endl;
405 return 1;
406 }
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700407 startApplication();
Zhiyi Zhang08e0e982017-03-01 10:10:42 -0800408 face.processEvents();
409 return 0;
410}
411
412} // namespace ndncert
413} // namespace ndn
414
Zhiyi Zhang916ba2d2018-02-01 18:16:27 -0800415int main(int argc, char* argv[])
Zhiyi Zhang08e0e982017-03-01 10:10:42 -0800416{
417 return ndn::ndncert::main(argc, argv);
418}