Zhiyi Zhang | 08e0e98 | 2017-03-01 10:10:42 -0800 | [diff] [blame] | 1 | /* -*- 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 "challenge-module.hpp" |
Zhiyi Zhang | 08e0e98 | 2017-03-01 10:10:42 -0800 | [diff] [blame] | 23 | |
Zhiyi Zhang | b6fab0f | 2017-09-21 16:26:27 -0700 | [diff] [blame] | 24 | #include <iostream> |
Zhiyi Zhang | 1c0bd37 | 2017-12-18 18:32:55 +0800 | [diff] [blame^] | 25 | #include <string> |
Zhiyi Zhang | b6fab0f | 2017-09-21 16:26:27 -0700 | [diff] [blame] | 26 | |
Zhiyi Zhang | 08e0e98 | 2017-03-01 10:10:42 -0800 | [diff] [blame] | 27 | #include <boost/program_options/options_description.hpp> |
| 28 | #include <boost/program_options/variables_map.hpp> |
| 29 | #include <boost/program_options/parsers.hpp> |
Zhiyi Zhang | 1c0bd37 | 2017-12-18 18:32:55 +0800 | [diff] [blame^] | 30 | #include <ndn-cxx/security/verification-helpers.hpp> |
Zhiyi Zhang | 08e0e98 | 2017-03-01 10:10:42 -0800 | [diff] [blame] | 31 | |
| 32 | namespace ndn { |
| 33 | namespace ndncert { |
| 34 | |
Zhiyi Zhang | 1c0bd37 | 2017-12-18 18:32:55 +0800 | [diff] [blame^] | 35 | int nStep; |
Zhiyi Zhang | 08e0e98 | 2017-03-01 10:10:42 -0800 | [diff] [blame] | 36 | |
| 37 | class ClientTool |
| 38 | { |
| 39 | public: |
| 40 | ClientTool(ClientModule& clientModule) |
| 41 | : client(clientModule) |
| 42 | { |
| 43 | } |
| 44 | |
| 45 | void |
| 46 | errorCb(const std::string& errorInfo) |
| 47 | { |
Zhiyi Zhang | 1c0bd37 | 2017-12-18 18:32:55 +0800 | [diff] [blame^] | 48 | std::cerr << "Error: " << errorInfo << std::endl; |
Zhiyi Zhang | 08e0e98 | 2017-03-01 10:10:42 -0800 | [diff] [blame] | 49 | } |
| 50 | |
| 51 | void |
Zhiyi Zhang | 1c0bd37 | 2017-12-18 18:32:55 +0800 | [diff] [blame^] | 52 | downloadCb(const shared_ptr<RequestState>& state) |
Zhiyi Zhang | 4d89fe0 | 2017-04-28 18:51:51 -0700 | [diff] [blame] | 53 | { |
Zhiyi Zhang | 1c0bd37 | 2017-12-18 18:32:55 +0800 | [diff] [blame^] | 54 | std::cerr << "Step " << nStep++ |
| 55 | << "DONE! Certificate has already been installed to local keychain" << std::endl; |
Zhiyi Zhang | 4d89fe0 | 2017-04-28 18:51:51 -0700 | [diff] [blame] | 56 | return; |
| 57 | } |
| 58 | |
| 59 | void |
Zhiyi Zhang | 1c0bd37 | 2017-12-18 18:32:55 +0800 | [diff] [blame^] | 60 | anchorCb(const Interest& request, const Data& reply, |
| 61 | const ClientCaItem& anchorItem, const Name& assignedName) |
| 62 | { |
| 63 | auto contentJson = ClientModule::getJsonFromData(reply); |
| 64 | auto caItem = ClientConfig::extractCaItem(contentJson); |
| 65 | |
| 66 | if (!security::verifySignature(caItem.m_anchor, anchorItem.m_anchor)) { |
| 67 | std::cerr << "Fail to verify fetched anchor" << std::endl; |
| 68 | return; |
| 69 | } |
| 70 | client.getClientConf().m_caItems.push_back(caItem); |
| 71 | |
| 72 | if (assignedName.toUri() != "/") { |
| 73 | client.sendNew(caItem, assignedName, |
| 74 | bind(&ClientTool::newCb, this, _1), |
| 75 | bind(&ClientTool::errorCb, this, _1)); |
| 76 | } |
| 77 | else { |
| 78 | if (caItem.m_probe != "") { |
| 79 | std::cerr <<"Step " << nStep++ << ": Probe Requirement-" << caItem.m_probe << std::endl; |
| 80 | std::string probeInfo; |
| 81 | getline(std::cin, probeInfo); |
| 82 | client.sendProbe(caItem, probeInfo, |
| 83 | bind(&ClientTool::newCb, this, _1), |
| 84 | bind(&ClientTool::errorCb, this, _1)); |
| 85 | } |
| 86 | else { |
| 87 | std::cerr <<"Step " << nStep++ << ": Please type in the identity name" << std::endl; |
| 88 | std::string nameComponent; |
| 89 | getline(std::cin, nameComponent); |
| 90 | Name identityName = caItem.m_caName.getPrefix(-1); |
| 91 | identityName.append(nameComponent); |
| 92 | client.sendNew(caItem, identityName, |
| 93 | bind(&ClientTool::newCb, this, _1), |
| 94 | bind(&ClientTool::errorCb, this, _1)); |
| 95 | } |
| 96 | } |
| 97 | } |
| 98 | |
| 99 | void |
| 100 | listCb(const std::list<Name>& caList, const Name& assignedName, const Name& schema, |
| 101 | const ClientCaItem& caItem) |
| 102 | { |
| 103 | if (assignedName.toUri() != "" && caList.size() == 1) { |
| 104 | // with recommendation |
| 105 | |
| 106 | std::cerr << "Get recommended CA: " << caList.front() |
| 107 | << "Get recommended Identity: " << assignedName << std::endl; |
| 108 | client.requestCaTrustAnchor(caList.front(), |
| 109 | bind(&ClientTool::anchorCb, this, _1, _2, caItem, assignedName), |
| 110 | bind(&ClientTool::errorCb, this, _1)); |
| 111 | } |
| 112 | else { |
| 113 | // without recommendation |
| 114 | int count = 0; |
| 115 | for (auto name : caList) { |
| 116 | std::cerr << "***************************************" << "\n" |
| 117 | << "Index: " << count++ << "\n" |
| 118 | << "CA prefix:" << name << "\n" |
| 119 | << "***************************************" << std::endl; |
| 120 | } |
| 121 | std::cerr << "Select an index to apply for a certificate."<< std::endl; |
| 122 | |
| 123 | std::string option; |
| 124 | getline(std::cin, option); |
| 125 | int caIndex = std::stoi(option); |
| 126 | |
| 127 | std::vector<Name> caVector{std::begin(caList), std::end(caList)}; |
| 128 | Name targetCaName = caVector[caIndex]; |
| 129 | |
| 130 | client.requestCaTrustAnchor(targetCaName, |
| 131 | bind(&ClientTool::anchorCb, this, _1, _2, caItem, Name("")), |
| 132 | bind(&ClientTool::errorCb, this, _1)); |
| 133 | } |
| 134 | } |
| 135 | |
| 136 | void |
| 137 | validateCb(const shared_ptr<RequestState>& state) |
Zhiyi Zhang | 08e0e98 | 2017-03-01 10:10:42 -0800 | [diff] [blame] | 138 | { |
| 139 | if (state->m_status == ChallengeModule::SUCCESS) { |
Zhiyi Zhang | 1c0bd37 | 2017-12-18 18:32:55 +0800 | [diff] [blame^] | 140 | std::cerr << "DONE! Certificate has already been issued" << std::endl; |
Zhiyi Zhang | 4d89fe0 | 2017-04-28 18:51:51 -0700 | [diff] [blame] | 141 | client.requestDownload(state, |
Zhiyi Zhang | 1c0bd37 | 2017-12-18 18:32:55 +0800 | [diff] [blame^] | 142 | bind(&ClientTool::downloadCb, this, _1), |
Zhiyi Zhang | 4d89fe0 | 2017-04-28 18:51:51 -0700 | [diff] [blame] | 143 | bind(&ClientTool::errorCb, this, _1)); |
Zhiyi Zhang | 08e0e98 | 2017-03-01 10:10:42 -0800 | [diff] [blame] | 144 | return; |
| 145 | } |
| 146 | |
| 147 | auto challenge = ChallengeModule::createChallengeModule(state->m_challengeType); |
| 148 | auto requirementList = challenge->getRequirementForValidate(state->m_status); |
| 149 | |
| 150 | std::cerr << "Step" << nStep++ << ": Please satisfy following instruction(s)" << std::endl; |
| 151 | for (auto requirement : requirementList) { |
| 152 | std::cerr << "\t" << requirement << std::endl; |
| 153 | } |
| 154 | std::list<std::string> paraList; |
| 155 | for (size_t i = 0; i < requirementList.size(); i++) { |
| 156 | std::string tempParam; |
Zhiyi Zhang | 1c0bd37 | 2017-12-18 18:32:55 +0800 | [diff] [blame^] | 157 | getline(std::cin, tempParam); |
Zhiyi Zhang | 08e0e98 | 2017-03-01 10:10:42 -0800 | [diff] [blame] | 158 | paraList.push_back(tempParam); |
| 159 | } |
| 160 | auto paramJson = challenge->genValidateParamsJson(state->m_status, paraList); |
| 161 | client.sendValidate(state, paramJson, |
Zhiyi Zhang | 1c0bd37 | 2017-12-18 18:32:55 +0800 | [diff] [blame^] | 162 | bind(&ClientTool::validateCb, this, _1), |
Zhiyi Zhang | 08e0e98 | 2017-03-01 10:10:42 -0800 | [diff] [blame] | 163 | bind(&ClientTool::errorCb, this, _1)); |
| 164 | } |
| 165 | |
| 166 | void |
Zhiyi Zhang | 1c0bd37 | 2017-12-18 18:32:55 +0800 | [diff] [blame^] | 167 | selectCb(const shared_ptr<RequestState>& state) |
Zhiyi Zhang | 08e0e98 | 2017-03-01 10:10:42 -0800 | [diff] [blame] | 168 | { |
| 169 | auto challenge = ChallengeModule::createChallengeModule(state->m_challengeType); |
| 170 | auto requirementList = challenge->getRequirementForValidate(state->m_status); |
| 171 | |
| 172 | std::cerr << "Step" << nStep++ << ": Please satisfy following instruction(s)" << std::endl; |
| 173 | for (auto item : requirementList) { |
| 174 | std::cerr << "\t" << item << std::endl; |
| 175 | } |
| 176 | std::list<std::string> paraList; |
| 177 | for (size_t i = 0; i < requirementList.size(); i++) { |
| 178 | std::string tempParam; |
Zhiyi Zhang | 1c0bd37 | 2017-12-18 18:32:55 +0800 | [diff] [blame^] | 179 | getline(std::cin, tempParam); |
Zhiyi Zhang | 08e0e98 | 2017-03-01 10:10:42 -0800 | [diff] [blame] | 180 | paraList.push_back(tempParam); |
| 181 | } |
| 182 | |
| 183 | auto paramJson = challenge->genValidateParamsJson(state->m_status, paraList); |
| 184 | client.sendValidate(state, paramJson, |
Zhiyi Zhang | 1c0bd37 | 2017-12-18 18:32:55 +0800 | [diff] [blame^] | 185 | bind(&ClientTool::validateCb, this, _1), |
Zhiyi Zhang | 08e0e98 | 2017-03-01 10:10:42 -0800 | [diff] [blame] | 186 | bind(&ClientTool::errorCb, this, _1)); |
| 187 | } |
| 188 | |
| 189 | void |
Zhiyi Zhang | 1c0bd37 | 2017-12-18 18:32:55 +0800 | [diff] [blame^] | 190 | newCb(const shared_ptr<RequestState>& state) |
Zhiyi Zhang | 08e0e98 | 2017-03-01 10:10:42 -0800 | [diff] [blame] | 191 | { |
| 192 | std::cerr << "Step" << nStep++ << ": Please select one challenge from following types." << std::endl; |
| 193 | for (auto item : state->m_challengeList) { |
| 194 | std::cerr << "\t" << item << std::endl; |
| 195 | } |
| 196 | std::string choice; |
Zhiyi Zhang | 1c0bd37 | 2017-12-18 18:32:55 +0800 | [diff] [blame^] | 197 | getline(std::cin, choice); |
Zhiyi Zhang | 08e0e98 | 2017-03-01 10:10:42 -0800 | [diff] [blame] | 198 | |
| 199 | auto challenge = ChallengeModule::createChallengeModule(choice); |
| 200 | auto requirementList = challenge->getRequirementForSelect(); |
| 201 | std::list<std::string> paraList; |
| 202 | if (requirementList.size() != 0) { |
| 203 | std::cerr << "Step" << nStep++ << ": Please satisfy following instruction(s)" << std::endl; |
| 204 | for (auto item : requirementList) { |
| 205 | std::cerr << "\t" << item << std::endl; |
| 206 | } |
| 207 | for (size_t i = 0; i < requirementList.size(); i++) { |
| 208 | std::string tempParam; |
Zhiyi Zhang | 1c0bd37 | 2017-12-18 18:32:55 +0800 | [diff] [blame^] | 209 | getline(std::cin, tempParam); |
Zhiyi Zhang | 08e0e98 | 2017-03-01 10:10:42 -0800 | [diff] [blame] | 210 | paraList.push_back(tempParam); |
| 211 | } |
| 212 | } |
| 213 | auto paramJson = challenge->genSelectParamsJson(state->m_status, paraList); |
| 214 | client.sendSelect(state, choice, paramJson, |
Zhiyi Zhang | 1c0bd37 | 2017-12-18 18:32:55 +0800 | [diff] [blame^] | 215 | bind(&ClientTool::selectCb, this, _1), |
Zhiyi Zhang | 08e0e98 | 2017-03-01 10:10:42 -0800 | [diff] [blame] | 216 | bind(&ClientTool::errorCb, this, _1)); |
| 217 | } |
| 218 | |
| 219 | public: |
| 220 | ClientModule& client; |
| 221 | }; |
| 222 | |
| 223 | int |
| 224 | main(int argc, char* argv[]) |
| 225 | { |
| 226 | namespace po = boost::program_options; |
| 227 | std::string configFilePath = std::string(SYSCONFDIR) + "/ndncert/client.conf"; |
Zhiyi Zhang | 1c0bd37 | 2017-12-18 18:32:55 +0800 | [diff] [blame^] | 228 | bool isIntra = false; |
| 229 | po::options_description description("General Usage\n ndncert-client [-h] [-i] [-f]\n"); |
Zhiyi Zhang | 08e0e98 | 2017-03-01 10:10:42 -0800 | [diff] [blame] | 230 | description.add_options() |
| 231 | ("help,h", "produce help message") |
Zhiyi Zhang | 1c0bd37 | 2017-12-18 18:32:55 +0800 | [diff] [blame^] | 232 | ("intra-node,i", "optional, if specified, switch on the intra-node mode") |
| 233 | ("config-file,f", po::value<std::string>(&configFilePath), "config file name") |
| 234 | ; |
Zhiyi Zhang | 08e0e98 | 2017-03-01 10:10:42 -0800 | [diff] [blame] | 235 | po::positional_options_description p; |
| 236 | |
| 237 | po::variables_map vm; |
| 238 | try { |
| 239 | po::store(po::command_line_parser(argc, argv).options(description).positional(p).run(), vm); |
| 240 | po::notify(vm); |
| 241 | } |
| 242 | catch (const std::exception& e) { |
| 243 | std::cerr << "ERROR: " << e.what() << std::endl; |
| 244 | return 1; |
| 245 | } |
| 246 | if (vm.count("help") != 0) { |
| 247 | std::cerr << description << std::endl; |
| 248 | return 0; |
| 249 | } |
Zhiyi Zhang | 1c0bd37 | 2017-12-18 18:32:55 +0800 | [diff] [blame^] | 250 | if (vm.count("intra-node") != 0) { |
| 251 | isIntra = true; |
| 252 | } |
Zhiyi Zhang | 08e0e98 | 2017-03-01 10:10:42 -0800 | [diff] [blame] | 253 | |
Zhiyi Zhang | 1c0bd37 | 2017-12-18 18:32:55 +0800 | [diff] [blame^] | 254 | nStep = 0; |
Zhiyi Zhang | 08e0e98 | 2017-03-01 10:10:42 -0800 | [diff] [blame] | 255 | Face face; |
| 256 | security::v2::KeyChain keyChain; |
| 257 | ClientModule client(face, keyChain); |
| 258 | client.getClientConf().load(configFilePath); |
Zhiyi Zhang | 08e0e98 | 2017-03-01 10:10:42 -0800 | [diff] [blame] | 259 | ClientTool tool(client); |
| 260 | |
Zhiyi Zhang | 1c0bd37 | 2017-12-18 18:32:55 +0800 | [diff] [blame^] | 261 | if (isIntra) { |
| 262 | client.requestLocalhostList([&](const ClientConfig& config) { |
| 263 | auto caList = config.m_caItems; |
| 264 | int count = 0; |
| 265 | for (auto item : caList) { |
| 266 | std::cerr << "***************************************" << "\n" |
| 267 | << "Index: " << count++ << "\n" |
| 268 | << "CA prefix:" << item.m_caName << "\n" |
| 269 | << "Introduction: " << item.m_caInfo << "\n" |
| 270 | << "***************************************" << std::endl; |
| 271 | } |
| 272 | std::vector<ClientCaItem> caVector{std::begin(caList), std::end(caList)}; |
| 273 | std::cerr << "Step" << nStep++ |
| 274 | << ": Please type in the CA namespace index that you want to apply" << std::endl; |
| 275 | std::string caIndexS; |
| 276 | getline(std::cin, caIndexS); |
| 277 | int caIndex = std::stoi(caIndexS); |
Zhiyi Zhang | 08e0e98 | 2017-03-01 10:10:42 -0800 | [diff] [blame] | 278 | |
Zhiyi Zhang | 1c0bd37 | 2017-12-18 18:32:55 +0800 | [diff] [blame^] | 279 | BOOST_ASSERT(caIndex <= count); |
Zhiyi Zhang | 08e0e98 | 2017-03-01 10:10:42 -0800 | [diff] [blame] | 280 | |
Zhiyi Zhang | 1c0bd37 | 2017-12-18 18:32:55 +0800 | [diff] [blame^] | 281 | auto targetCaItem = caVector[caIndex]; |
| 282 | if (targetCaItem.m_probe != "") { |
| 283 | std::cerr <<"Step" << nStep++ << ": Probe Requirement-" << targetCaItem.m_probe << std::endl; |
| 284 | std::string probeInfo; |
| 285 | getline(std::cin, probeInfo); |
| 286 | client.sendProbe(targetCaItem, probeInfo, |
| 287 | bind(&ClientTool::newCb, &tool, _1), |
| 288 | bind(&ClientTool::errorCb, &tool, _1)); |
| 289 | } |
| 290 | else { |
| 291 | std::cerr <<"Step" << nStep++ << ": Please type in the identity name" << std::endl; |
| 292 | std::string nameComponent; |
| 293 | getline(std::cin, nameComponent); |
| 294 | Name identityName = targetCaItem.m_caName.getPrefix(-1); |
| 295 | identityName.append(nameComponent); |
| 296 | client.sendNew(targetCaItem, identityName, |
| 297 | bind(&ClientTool::newCb, &tool, _1), |
| 298 | bind(&ClientTool::errorCb, &tool, _1)); |
| 299 | } |
| 300 | }, |
| 301 | bind(&ClientTool::errorCb, &tool, _1)); |
Zhiyi Zhang | 08e0e98 | 2017-03-01 10:10:42 -0800 | [diff] [blame] | 302 | } |
| 303 | else { |
Zhiyi Zhang | 1c0bd37 | 2017-12-18 18:32:55 +0800 | [diff] [blame^] | 304 | auto caList = client.getClientConf().m_caItems; |
| 305 | int count = 0; |
| 306 | for (auto item : caList) { |
| 307 | std::cerr << "***************************************" << "\n" |
| 308 | << "Index: " << count++ << "\n" |
| 309 | << "CA prefix:" << item.m_caName << "\n" |
| 310 | << "Introduction: " << item.m_caInfo << "\n" |
| 311 | << "***************************************" << std::endl; |
| 312 | } |
| 313 | std::vector<ClientCaItem> caVector{std::begin(caList), std::end(caList)}; |
| 314 | std::cerr << "Step " << nStep++ << ": Please type in the CA namespace index that you want to apply" << std::endl; |
| 315 | |
| 316 | std::string caIndexS; |
| 317 | getline(std::cin, caIndexS); |
| 318 | |
| 319 | int caIndex = std::stoi(caIndexS); |
| 320 | |
| 321 | BOOST_ASSERT(caIndex <= count); |
| 322 | |
| 323 | auto targetCaItem = caVector[caIndex]; |
| 324 | std::cerr << "You want a namespace with prefix (A) /ndn or (B) a sub-namespace of " |
| 325 | << targetCaItem.m_caName << "?" << std::endl; |
| 326 | std::string listOption; |
| 327 | getline(std::cin, listOption); |
| 328 | if (listOption == "A" || listOption == "a") { |
| 329 | // should directly send _PROBE or _NEW |
| 330 | if (targetCaItem.m_probe != "") { |
| 331 | std::cerr <<"Step " << nStep++ << ": Probe Requirement-" << targetCaItem.m_probe << std::endl; |
| 332 | std::string probeInfo; |
| 333 | getline(std::cin, probeInfo); |
| 334 | client.sendProbe(targetCaItem, probeInfo, |
| 335 | bind(&ClientTool::newCb, &tool, _1), |
| 336 | bind(&ClientTool::errorCb, &tool, _1)); |
| 337 | } |
| 338 | else { |
| 339 | std::cerr <<"Step " << nStep++ << ": Please type in the identity name" << std::endl; |
| 340 | std::string nameComponent; |
| 341 | getline(std::cin, nameComponent); |
| 342 | Name identityName = targetCaItem.m_caName.getPrefix(-1); |
| 343 | identityName.append(nameComponent); |
| 344 | client.sendNew(targetCaItem, identityName, |
| 345 | bind(&ClientTool::newCb, &tool, _1), |
| 346 | bind(&ClientTool::errorCb, &tool, _1)); |
| 347 | } |
| 348 | } |
| 349 | else if (listOption == "B" || listOption == "b") { |
| 350 | std::string additionalInfo = ""; |
| 351 | if (targetCaItem.m_targetedList != "") { |
| 352 | std::cerr <<"Step " << nStep++ |
| 353 | << ": Enter nothing if you want to see all available sub-namespace CAs" |
| 354 | << "or follow the instruction to get a recommended CA\n" |
| 355 | << "\t" << targetCaItem.m_targetedList << std::endl; |
| 356 | getline(std::cin, additionalInfo); |
| 357 | } |
| 358 | client.requestList(targetCaItem, additionalInfo, |
| 359 | bind(&ClientTool::listCb, &tool, _1, _2, _3, targetCaItem), |
| 360 | bind(&ClientTool::errorCb, &tool, _1)); |
| 361 | } |
| 362 | else { |
| 363 | std::cerr << "Your input is not an option." << std::endl; |
| 364 | } |
Zhiyi Zhang | 08e0e98 | 2017-03-01 10:10:42 -0800 | [diff] [blame] | 365 | } |
Zhiyi Zhang | 1c0bd37 | 2017-12-18 18:32:55 +0800 | [diff] [blame^] | 366 | |
Zhiyi Zhang | 08e0e98 | 2017-03-01 10:10:42 -0800 | [diff] [blame] | 367 | face.processEvents(); |
| 368 | return 0; |
| 369 | } |
| 370 | |
| 371 | } // namespace ndncert |
| 372 | } // namespace ndn |
| 373 | |
| 374 | int |
| 375 | main(int argc, char* argv[]) |
| 376 | { |
| 377 | return ndn::ndncert::main(argc, argv); |
| 378 | } |