blob: 4b3b38a8d2bf52fca9d67e0936b35c4e7c82a454 [file] [log] [blame]
Zhiyi Zhang08e0e982017-03-01 10:10:42 -08001/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
swa77020643ac2020-03-26 02:24:45 -07002/**
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
Zhiyi Zhangad9e04f2020-03-27 12:04:31 -070021#include <boost/asio.hpp>
Davide Pesaventob48bbda2020-07-27 19:41:37 -040022#include <boost/program_options/options_description.hpp>
23#include <boost/program_options/parsers.hpp>
24#include <boost/program_options/variables_map.hpp>
Zhiyi Zhang48f23782020-09-28 12:11:24 -070025#include <iostream>
Zhiyi Zhang1c0bd372017-12-18 18:32:55 +080026#include <ndn-cxx/security/verification-helpers.hpp>
Zhiyi Zhang48f23782020-09-28 12:11:24 -070027#include <string>
28
29#include "challenge-module.hpp"
30#include "client-module.hpp"
31#include "protocol-detail/info.hpp"
Zhiyi Zhang08e0e982017-03-01 10:10:42 -080032
33namespace ndn {
34namespace ndncert {
35
Zhiyi Zhang48f23782020-09-28 12:11:24 -070036static void
37startApplication();
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -070038
Zhiyi Zhang1c0bd372017-12-18 18:32:55 +080039int nStep;
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -070040Face face;
tylerliu182bc532020-09-25 01:54:45 -070041security::v2::KeyChain keyChain;
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -070042std::string challengeType;
Zhiyi Zhang36706832019-07-04 21:33:03 -070043int validityPeriod = -1;
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -070044ClientModule client(keyChain);
Zhiyi Zhang08e0e982017-03-01 10:10:42 -080045
Zhiyi Zhang46049832020-09-28 17:08:12 -070046static void
47captureParams(std::vector<std::tuple<std::string, std::string>>& requirement)
Zhiyi Zhang08e0e982017-03-01 10:10:42 -080048{
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -070049 std::list<std::string> results;
Zhiyi Zhang46049832020-09-28 17:08:12 -070050 for (auto& item : requirement) {
51 std::cerr << std::get<1>(item) << std::endl;
52 std::string captured;
53 getline(std::cin, captured);
54 std::get<1>(item) = captured;
Zhiyi Zhang08e0e982017-03-01 10:10:42 -080055 }
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -070056 std::cerr << "Got it. This is what you've provided:" << std::endl;
Zhiyi Zhang46049832020-09-28 17:08:12 -070057 for (const auto& item : requirement) {
58 std::cerr << std::get<0>(item) << " : " << std::get<1>(item) << std::endl;
Zhiyi Zhang08e0e982017-03-01 10:10:42 -080059 }
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -070060}
Zhiyi Zhang08e0e982017-03-01 10:10:42 -080061
Zhiyi Zhang547c8512019-06-18 23:46:14 -070062static std::list<std::string>
63captureParams(const std::vector<std::string>& requirement)
64{
65 std::list<std::string> results;
66 for (const auto& item : requirement) {
67 std::cerr << "Please provide the argument: " << item << " : " << std::endl;
68 std::string tempParam;
69 getline(std::cin, tempParam);
70 results.push_back(tempParam);
71 }
72 std::cerr << "Got it. This is what you've provided:" << std::endl;
73 auto it1 = results.begin();
74 auto it2 = requirement.begin();
75 for (; it1 != results.end() && it2 != requirement.end(); it1++, it2++) {
76 std::cerr << *it2 << " : " << *it1 << std::endl;
77 }
78 return results;
79}
80
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -070081static void
Zhiyi Zhang36706832019-07-04 21:33:03 -070082captureValidityPeriod()
83{
Zhiyi Zhangad9e04f2020-03-27 12:04:31 -070084 if (validityPeriod > 0) {
85 return;
86 }
87 std::cerr << "Step " << nStep++
88 << ": Please type in your expected validity period of your certificate."
89 << " Type the number of hours (168 for week, 730 for month, 8760 for year)."
90 << " The CA may reject your application if your expected period is too long." << std::endl;
91 std::string periodStr = "";
92 getline(std::cin, periodStr);
93 try {
94 validityPeriod = std::stoi(periodStr);
95 }
96 catch (const std::exception& e) {
97 validityPeriod = -1;
Zhiyi Zhang36706832019-07-04 21:33:03 -070098 }
99}
100
101static void
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700102onNackCb()
103{
104 std::cerr << "Got NACK\n";
105}
106
107static void
108timeoutCb()
109{
110 std::cerr << "Interest sent time out\n";
111}
112
113static void
swa770cf1d8f72020-04-21 23:12:39 -0700114certFetchCb(const Data& reply)
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700115{
swa770cf1d8f72020-04-21 23:12:39 -0700116 client.onCertFetchResponse(reply);
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700117 std::cerr << "Step " << nStep++
Zhiyi Zhangad9e04f2020-03-27 12:04:31 -0700118 << ": DONE! Certificate has already been installed to local keychain\n"
Zhiyi Zhangef6b36a2020-09-22 21:20:59 -0700119 << "Certificate Name: " << reply.getName().toUri() << std::endl;
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700120}
121
122static void
123challengeCb(const Data& reply)
124{
125 client.onChallengeResponse(reply);
Zhiyi Zhang48f23782020-09-28 12:11:24 -0700126 if (client.getApplicationStatus() == Status::SUCCESS) {
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700127 std::cerr << "DONE! Certificate has already been issued \n";
swa770cf1d8f72020-04-21 23:12:39 -0700128 face.expressInterest(*client.generateCertFetchInterest(), bind(&certFetchCb, _2),
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700129 bind(&onNackCb), bind(&timeoutCb));
Zhiyi Zhang4d89fe02017-04-28 18:51:51 -0700130 return;
131 }
132
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700133 auto challenge = ChallengeModule::createChallengeModule(challengeType);
Zhiyi Zhang46049832020-09-28 17:08:12 -0700134 auto requirement = challenge->getRequestedParameterList(client.getApplicationStatus(),
135 client.getChallengeStatus());
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700136 if (requirement.size() > 0) {
Zhiyi Zhang916ba2d2018-02-01 18:16:27 -0800137 std::cerr << "Step " << nStep++ << ": Please satisfy following instruction(s)\n";
Zhiyi Zhang46049832020-09-28 17:08:12 -0700138 captureParams(requirement);
Zhiyi Zhang08e0e982017-03-01 10:10:42 -0800139 }
Suyong Won19fba4d2020-05-09 13:39:46 -0700140 face.expressInterest(*client.generateChallengeInterest(challenge->genChallengeRequestTLV(client.getApplicationStatus(),
Zhiyi Zhang48f23782020-09-28 12:11:24 -0700141 client.getChallengeStatus(),
Zhiyi Zhang46049832020-09-28 17:08:12 -0700142 std::move(requirement))),
Zhiyi Zhang36706832019-07-04 21:33:03 -0700143 bind(&challengeCb, _2), bind(&onNackCb), bind(&timeoutCb));
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700144}
Zhiyi Zhang08e0e982017-03-01 10:10:42 -0800145
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700146static void
147newCb(const Data& reply)
148{
Zhiyi Zhang36706832019-07-04 21:33:03 -0700149 int challengeIndex = 0;
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700150 auto challengeList = client.onNewResponse(reply);
Zhiyi Zhang36706832019-07-04 21:33:03 -0700151 if (challengeList.size() < 1) {
152 std::cerr << "There is no available challenge provided by the CA. Exit" << std::endl;
153 return;
Zhiyi Zhang08e0e982017-03-01 10:10:42 -0800154 }
Zhiyi Zhang36706832019-07-04 21:33:03 -0700155 else if (challengeList.size() > 1) {
156 int count = 0;
157 std::string choice = "";
158 std::cerr << "Step " << nStep++ << ": Please type in the challenge index that you want to perform\n";
Zhiyi Zhangad9e04f2020-03-27 12:04:31 -0700159 count = 0;
160 for (auto item : challengeList) {
Zhiyi Zhang48f23782020-09-28 12:11:24 -0700161 std::cerr << "\t" << count++ << " : " << item << std::endl;
Zhiyi Zhangad9e04f2020-03-27 12:04:31 -0700162 }
163 getline(std::cin, choice);
164 try {
165 challengeIndex = std::stoi(choice);
166 }
167 catch (const std::exception& e) {
168 challengeIndex = -1;
169 }
170 if (challengeIndex < 0 || challengeIndex >= count) {
171 std::cerr << "Your input index is out of range. Exit." << std::endl;
172 return;
173 }
Zhiyi Zhang36706832019-07-04 21:33:03 -0700174 }
175 auto it = challengeList.begin();
176 std::advance(it, challengeIndex);
177 unique_ptr<ChallengeModule> challenge = ChallengeModule::createChallengeModule(*it);
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700178 if (challenge != nullptr) {
Zhiyi Zhang36706832019-07-04 21:33:03 -0700179 challengeType = *it;
180 std::cerr << "The challenge has been selected: " << challengeType << std::endl;
Zhiyi Zhang08e0e982017-03-01 10:10:42 -0800181 }
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700182 else {
Zhiyi Zhang36706832019-07-04 21:33:03 -0700183 std::cerr << "Error. Cannot load selected Challenge Module. Exit." << std::endl;
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700184 return;
185 }
Zhiyi Zhang46049832020-09-28 17:08:12 -0700186 auto requirement = challenge->getRequestedParameterList(client.getApplicationStatus(),
187 client.getChallengeStatus());
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700188 if (requirement.size() > 0) {
Zhiyi Zhang46049832020-09-28 17:08:12 -0700189 std::cerr << "Step " << nStep++ << ": Please provide parameters used for Identity Verification Challenge\n";
190 captureParams(requirement);
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700191 }
Suyong Won19fba4d2020-05-09 13:39:46 -0700192 face.expressInterest(*client.generateChallengeInterest(challenge->genChallengeRequestTLV(client.getApplicationStatus(),
Zhiyi Zhang48f23782020-09-28 12:11:24 -0700193 client.getChallengeStatus(),
Zhiyi Zhang46049832020-09-28 17:08:12 -0700194 std::move(requirement))),
Zhiyi Zhang36706832019-07-04 21:33:03 -0700195 bind(&challengeCb, _2), bind(&onNackCb), bind(&timeoutCb));
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700196}
Zhiyi Zhang08e0e982017-03-01 10:10:42 -0800197
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700198static void
Suyong Won19fba4d2020-05-09 13:39:46 -0700199InfoCb(const Data& reply)
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700200{
Suyong Won19fba4d2020-05-09 13:39:46 -0700201 const Block& contentBlock = reply.getContent();
202
203 if (!client.verifyInfoResponse(reply)) {
Zhiyi Zhangcaab5462019-10-18 13:41:02 -0700204 std::cerr << "The fetched CA information cannot be trusted because its integrity is broken" << std::endl;
205 return;
206 }
Suyong Won19fba4d2020-05-09 13:39:46 -0700207 auto caItem = INFO::decodeClientConfigFromContent(contentBlock);
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700208
Zhiyi Zhangcaab5462019-10-18 13:41:02 -0700209 std::cerr << "Will use a new trust anchor, please double check the identity info: \n"
Zhiyi Zhangef6b36a2020-09-22 21:20:59 -0700210 << "This trust anchor information is signed by " << reply.getSignature().getKeyLocator()
Davide Pesaventob48bbda2020-07-27 19:41:37 -0400211 << std::endl
212 << "The certificate is " << caItem.m_anchor << std::endl
213 << "Do you trust the information? Type in YES or NO" << std::endl;
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700214
215 std::string answer;
216 getline(std::cin, answer);
Zhiyi Zhang36706832019-07-04 21:33:03 -0700217 boost::algorithm::to_lower(answer);
218 if (answer == "yes") {
Zhiyi Zhangcaab5462019-10-18 13:41:02 -0700219 std::cerr << "You answered YES: new CA will be used" << std::endl;
Suyong Won19fba4d2020-05-09 13:39:46 -0700220 client.addCaFromInfoResponse(reply);
Zhiyi Zhangcaab5462019-10-18 13:41:02 -0700221 // client.getClientConf().save(std::string(SYSCONFDIR) + "/ndncert/client.conf");
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700222 startApplication();
223 }
224 else {
Zhiyi Zhangcaab5462019-10-18 13:41:02 -0700225 std::cerr << "You answered NO: new CA will not be used" << std::endl;
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700226 return;
227 }
228}
229
230static void
231probeCb(const Data& reply)
232{
Zhiyi Zhang781a5602019-06-26 19:05:04 -0700233 client.onProbeResponse(reply);
Zhiyi Zhang36706832019-07-04 21:33:03 -0700234 captureValidityPeriod();
Zhiyi Zhangad9e04f2020-03-27 12:04:31 -0700235 if (validityPeriod <= 0) {
236 std::cerr << "Invalid period time. Exit." << std::endl;
237 return;
238 }
Zhiyi Zhang781a5602019-06-26 19:05:04 -0700239 auto probeToken = make_shared<Data>(reply);
Zhiyi Zhang1a735bc2019-07-04 21:36:49 -0700240 auto now = time::system_clock::now();
Zhiyi Zhang36706832019-07-04 21:33:03 -0700241 std::cerr << "The validity period of your certificate will be: " << validityPeriod << " hours" << std::endl;
Zhiyi Zhangad9e04f2020-03-27 12:04:31 -0700242 auto interest = client.generateNewInterest(now, now + time::hours(validityPeriod), Name(), probeToken);
243 if (interest != nullptr) {
244 face.expressInterest(*interest, bind(&newCb, _2), bind(&onNackCb), bind(&timeoutCb));
245 }
246 else {
247 std::cerr << "Cannot generate the Interest for NEW step. Exit" << std::endl;
248 }
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700249}
250
251static void
252startApplication()
253{
254 nStep = 0;
255 auto caList = client.getClientConf().m_caItems;
256 int count = 0;
257 for (auto item : caList) {
258 std::cerr << "***************************************\n"
259 << "Index: " << count++ << "\n"
Suyong Won256c9062020-05-11 02:45:56 -0700260 << "CA prefix:" << item.m_caPrefix << "\n"
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700261 << "Introduction: " << item.m_caInfo << "\n"
262 << "***************************************\n";
263 }
264 std::vector<ClientCaItem> caVector{std::begin(caList), std::end(caList)};
265 std::cerr << "Step "
266 << nStep++ << ": Please type in the CA INDEX that you want to apply"
267 << " or type in NONE if your expected CA is not in the list\n";
268
Zhiyi Zhang36706832019-07-04 21:33:03 -0700269 std::string caIndexS, caIndexSLower;
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700270 getline(std::cin, caIndexS);
Zhiyi Zhang36706832019-07-04 21:33:03 -0700271 caIndexSLower = caIndexS;
272 boost::algorithm::to_lower(caIndexSLower);
273 if (caIndexSLower == "none") {
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700274 std::cerr << "Step " << nStep << ": Please type in the CA Name\n";
Zhiyi Zhangcaab5462019-10-18 13:41:02 -0700275 std::string expectedCAName;
276 getline(std::cin, expectedCAName);
swa77020643ac2020-03-26 02:24:45 -0700277 face.expressInterest(*client.generateInfoInterest(Name(expectedCAName)),
Suyong Won19fba4d2020-05-09 13:39:46 -0700278 bind(&InfoCb, _2), bind(&onNackCb), bind(&timeoutCb));
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700279 }
280 else {
Zhiyi Zhang36706832019-07-04 21:33:03 -0700281 int caIndex;
282 try {
283 caIndex = std::stoi(caIndexS);
284 }
285 catch (const std::exception& e) {
286 std::cerr << "Your input is neither NONE nor a valid index. Exit" << std::endl;
287 return;
288 }
289 if (caIndex < 0 || caIndex >= count) {
290 std::cerr << "Your input is not an existing index. Exit" << std::endl;
291 return;
292 }
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700293 auto targetCaItem = caVector[caIndex];
294
295 if (targetCaItem.m_probe != "") {
Zhiyi Zhang547c8512019-06-18 23:46:14 -0700296 std::cerr << "Step " << nStep++ << ": Please provide information for name assignment" << std::endl;
297 std::vector<std::string> probeFields = ClientModule::parseProbeComponents(targetCaItem.m_probe);
298 std::string redo = "";
299 std::list<std::string> capturedParams;
Zhiyi Zhangad9e04f2020-03-27 12:04:31 -0700300 capturedParams = captureParams(probeFields);
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700301 std::string probeInfo;
Zhiyi Zhang547c8512019-06-18 23:46:14 -0700302 for (const auto& item : capturedParams) {
303 probeInfo += item;
304 probeInfo += ":";
305 }
306 probeInfo = probeInfo.substr(0, probeInfo.size() - 1);
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700307 face.expressInterest(*client.generateProbeInterest(targetCaItem, probeInfo),
Zhiyi Zhang36706832019-07-04 21:33:03 -0700308 bind(&probeCb, _2), bind(&onNackCb), bind(&timeoutCb));
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700309 }
310 else {
Zhiyi Zhangad9e04f2020-03-27 12:04:31 -0700311 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 -0700312 std::string identityNameStr;
313 getline(std::cin, identityNameStr);
Zhiyi Zhang36706832019-07-04 21:33:03 -0700314 captureValidityPeriod();
Zhiyi Zhangad9e04f2020-03-27 12:04:31 -0700315 if (validityPeriod <= 0) {
316 std::cerr << "Invalid period time. Exit." << std::endl;
317 return;
318 }
319 Name idName(identityNameStr);
Zhiyi Zhang36706832019-07-04 21:33:03 -0700320 std::cerr << "The validity period of your certificate will be: " << validityPeriod << " hours" << std::endl;
Zhiyi Zhang1a735bc2019-07-04 21:36:49 -0700321 auto now = time::system_clock::now();
Zhiyi Zhangad9e04f2020-03-27 12:04:31 -0700322 auto interest = client.generateNewInterest(now, now + time::hours(validityPeriod), idName);
323 if (interest != nullptr) {
324 face.expressInterest(*interest, bind(&newCb, _2), bind(&onNackCb), bind(&timeoutCb));
325 }
326 else {
327 std::cerr << "Cannot generate the Interest for NEW step. Exit" << std::endl;
328 }
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700329 }
330 }
331}
332
Zhiyi Zhangad9e04f2020-03-27 12:04:31 -0700333static void
334handleSignal(const boost::system::error_code& error, int signalNum)
335{
336 if (error) {
337 return;
338 }
339 const char* signalName = ::strsignal(signalNum);
340 std::cerr << "Exiting on signal ";
341 if (signalName == nullptr) {
342 std::cerr << signalNum;
343 }
344 else {
345 std::cerr << signalName;
346 }
347 std::cerr << std::endl;
348 client.endSession();
349 face.getIoService().stop();
350}
Zhiyi Zhang08e0e982017-03-01 10:10:42 -0800351
352int
353main(int argc, char* argv[])
354{
Zhiyi Zhangad9e04f2020-03-27 12:04:31 -0700355 boost::asio::signal_set terminateSignals(face.getIoService());
356 terminateSignals.add(SIGINT);
357 terminateSignals.add(SIGTERM);
358 terminateSignals.async_wait(handleSignal);
359
Zhiyi Zhang08e0e982017-03-01 10:10:42 -0800360 namespace po = boost::program_options;
361 std::string configFilePath = std::string(SYSCONFDIR) + "/ndncert/client.conf";
Zhiyi Zhang36706832019-07-04 21:33:03 -0700362 po::options_description description("General Usage\n ndncert-client [-h] [-c] [-v]\n");
Zhiyi Zhang48f23782020-09-28 12:11:24 -0700363 description.add_options()("help,h", "produce help message")("config-file,c", po::value<std::string>(&configFilePath), "configuration file name")("validity-period,v", po::value<int>(&validityPeriod)->default_value(-1),
364 "desired validity period (hours) of the certificate being requested");
Zhiyi Zhang08e0e982017-03-01 10:10:42 -0800365 po::positional_options_description p;
366
367 po::variables_map vm;
368 try {
369 po::store(po::command_line_parser(argc, argv).options(description).positional(p).run(), vm);
370 po::notify(vm);
371 }
372 catch (const std::exception& e) {
373 std::cerr << "ERROR: " << e.what() << std::endl;
374 return 1;
375 }
376 if (vm.count("help") != 0) {
377 std::cerr << description << std::endl;
378 return 0;
379 }
Zhiyi Zhangd8993b92019-07-04 21:58:10 -0700380 try {
381 client.getClientConf().load(configFilePath);
382 }
383 catch (const std::exception& e) {
384 std::cerr << "Cannot load the configuration file: " << e.what() << std::endl;
385 return 1;
386 }
Zhiyi Zhangaf7c2902019-03-14 22:13:21 -0700387 startApplication();
Zhiyi Zhang08e0e982017-03-01 10:10:42 -0800388 face.processEvents();
389 return 0;
390}
391
Zhiyi Zhang48f23782020-09-28 12:11:24 -0700392} // namespace ndncert
393} // namespace ndn
Zhiyi Zhang08e0e982017-03-01 10:10:42 -0800394
Zhiyi Zhang48f23782020-09-28 12:11:24 -0700395int
396main(int argc, char* argv[])
Zhiyi Zhang08e0e982017-03-01 10:10:42 -0800397{
398 return ndn::ndncert::main(argc, argv);
399}