blob: 38b23b8ac5430a3b905c4de190ddbadfddf405ea [file] [log] [blame]
Zhiyi Zhangf5246c42017-01-26 09:39:20 -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 "ca-module.hpp"
22#include "challenge-module.hpp"
23#include "logging.hpp"
24#include <ndn-cxx/face.hpp>
25#include <ndn-cxx/security/verification-helpers.hpp>
26#include <ndn-cxx/security/signing-helpers.hpp>
Junxiao Shi7c068032017-05-28 13:40:47 +000027#include <ndn-cxx/util/random.hpp>
Zhiyi Zhangf5246c42017-01-26 09:39:20 -080028
29namespace ndn {
30namespace ndncert {
31
32_LOG_INIT(ndncert.ca);
33
34CaModule::CaModule(Face& face, security::v2::KeyChain& keyChain,
35 const std::string& configPath, const std::string& storageType)
36 : m_face(face)
37 , m_keyChain(keyChain)
38{
Zhiyi Zhanga63b7372017-05-17 14:14:34 -070039 // load the config and create storage
Zhiyi Zhangf5246c42017-01-26 09:39:20 -080040 m_config.load(configPath);
41 m_storage = CaStorage::createCaStorage(storageType);
42
Zhiyi Zhanga63b7372017-05-17 14:14:34 -070043 // register prefix
Zhiyi Zhangf5246c42017-01-26 09:39:20 -080044 for (const auto& item : m_config.m_caItems) {
45 Name prefix = item.m_caName;
46 prefix.append("CA");
47 try {
48 const RegisteredPrefixId* prefixId = m_face.registerPrefix(prefix,
49 [&] (const Name& name) {
Zhiyi Zhangad6cf932017-10-26 16:19:15 -070050 // NEW
51 const InterestFilterId* filterId = m_face.setInterestFilter(Name(name).append("_NEW"),
Zhiyi Zhangf5246c42017-01-26 09:39:20 -080052 bind(&CaModule::handleNew, this, _2, item));
53 m_interestFilterIds.push_back(filterId);
Zhiyi Zhangad6cf932017-10-26 16:19:15 -070054 // SELECT
Zhiyi Zhangf5246c42017-01-26 09:39:20 -080055 filterId = m_face.setInterestFilter(Name(name).append("_SELECT"),
56 bind(&CaModule::handleSelect, this, _2, item));
57 m_interestFilterIds.push_back(filterId);
Zhiyi Zhangad6cf932017-10-26 16:19:15 -070058 // VALIDATE
Zhiyi Zhangf5246c42017-01-26 09:39:20 -080059 filterId = m_face.setInterestFilter(Name(name).append("_VALIDATE"),
60 bind(&CaModule::handleValidate, this, _2, item));
61 m_interestFilterIds.push_back(filterId);
Zhiyi Zhangad6cf932017-10-26 16:19:15 -070062 // STATUS
Zhiyi Zhangf5246c42017-01-26 09:39:20 -080063 filterId = m_face.setInterestFilter(Name(name).append("_STATUS"),
64 bind(&CaModule::handleStatus, this, _2, item));
65 m_interestFilterIds.push_back(filterId);
Zhiyi Zhangad6cf932017-10-26 16:19:15 -070066 // DOWNLOAD
Zhiyi Zhangf5246c42017-01-26 09:39:20 -080067 filterId = m_face.setInterestFilter(Name(name).append("_DOWNLOAD"),
68 bind(&CaModule::handleDownload, this, _2, item));
69 m_interestFilterIds.push_back(filterId);
Zhiyi Zhangad6cf932017-10-26 16:19:15 -070070 // PROBE
71 if (item.m_probe != "") {
72 filterId = m_face.setInterestFilter(Name(name).append("_PROBE"),
73 bind(&CaModule::handleProbe, this, _2, item));
74 m_interestFilterIds.push_back(filterId);
75 }
Zhiyi Zhangf5246c42017-01-26 09:39:20 -080076 _LOG_TRACE("Prefix " << name << " got registered");
77 },
78 bind(&CaModule::onRegisterFailed, this, _2));
79 m_registeredPrefixIds.push_back(prefixId);
80 }
81 catch (const std::exception& e) {
Zhiyi Zhang693c1272017-05-20 22:58:55 -070082 _LOG_ERROR(e.what());
Zhiyi Zhangf5246c42017-01-26 09:39:20 -080083 }
84 }
85}
86
87CaModule::~CaModule()
88{
89 for (auto prefixId : m_interestFilterIds) {
90 m_face.unsetInterestFilter(prefixId);
91 }
92 for (auto prefixId : m_registeredPrefixIds) {
93 m_face.unregisterPrefix(prefixId, nullptr, nullptr);
94 }
95}
96
97void
Zhiyi Zhang7420cf52017-12-18 18:59:21 +080098CaModule::setProbeHandler(const Name caName, const ProbeHandler& handler)
99{
100 for (auto& entry : m_config.m_caItems) {
101 if (entry.m_caName == caName) {
102 entry.m_probeHandler = handler;
103 }
104 }
105}
106
107void
108CaModule::setRecommendCaHandler(const Name caName, const RecommendCaHandler& handler)
109{
110 for (auto& entry : m_config.m_caItems) {
111 if (entry.m_caName == caName) {
112 entry.m_recommendCaHandler = handler;
113 }
114 }
115}
116
117void
118CaModule::setRequestUpdateCallback(const Name caName, const RequestUpdateCallback& onUpateCallback)
119{
120 for (auto& entry : m_config.m_caItems) {
121 if (entry.m_caName == caName) {
122 entry.m_requestUpdateCallback = onUpateCallback;
123 }
124 }
125}
126
127void
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800128CaModule::handleProbe(const Interest& request, const CaItem& caItem)
129{
Zhiyi Zhang4d89fe02017-04-28 18:51:51 -0700130 // PROBE Naming Convention: /CA-prefix/CA/_PROBE/<Probe Information>
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800131 _LOG_TRACE("Handle PROBE request");
132
133 std::string identifier;
Zhiyi Zhang7420cf52017-12-18 18:59:21 +0800134 if (caItem.m_probeHandler) {
135 try {
136 identifier = caItem.m_probeHandler(readString(request.getName().at(caItem.m_caName.size() + 2)));
137 }
138 catch (const std::exception& e) {
139 _LOG_TRACE("Cannot generate identifier for PROBE request " << e.what());
140 return;
141 }
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800142 }
Zhiyi Zhang7420cf52017-12-18 18:59:21 +0800143 else {
144 identifier = readString(request.getName().at(caItem.m_caName.size() + 2));
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800145 }
146 Name identityName = caItem.m_caName;
147 identityName.append(identifier);
148
149 Data result;
150 result.setName(request.getName());
151 result.setContent(dataContentFromJson(genResponseProbeJson(identityName, "")));
Zhiyi Zhangad6cf932017-10-26 16:19:15 -0700152 m_keyChain.sign(result, signingByIdentity(caItem.m_caName));
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800153 m_face.put(result);
154
155 _LOG_TRACE("Handle PROBE: generate identity " << identityName);
156}
157
158void
159CaModule::handleNew(const Interest& request, const CaItem& caItem)
160{
Zhiyi Zhang4d89fe02017-04-28 18:51:51 -0700161 // NEW Naming Convention: /CA-prefix/CA/_NEW/<certificate-request>/[signature]
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800162 _LOG_TRACE("Handle NEW request");
163
164 security::v2::Certificate clientCert;
165 try {
166 clientCert.wireDecode(request.getName().at(caItem.m_caName.size() + 2).blockFromValue());
167 }
168 catch (const std::exception& e) {
Zhiyi Zhang693c1272017-05-20 22:58:55 -0700169 _LOG_ERROR("Unrecognized certificate request " << e.what());
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800170 return;
171 }
Zhiyi Zhang693c1272017-05-20 22:58:55 -0700172
173 if (!security::verifySignature(clientCert, clientCert)) {
174 _LOG_TRACE("Cert request with bad signature.");
175 return;
176 }
177 if (!security::verifySignature(request, clientCert)) {
178 _LOG_TRACE("Interest with bad signature.");
179 return;
180 }
181
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800182 std::string requestId = std::to_string(random::generateWord64());
183 CertificateRequest certRequest(caItem.m_caName, requestId, clientCert);
Zhiyi Zhanga63b7372017-05-17 14:14:34 -0700184 certRequest.setStatus(ChallengeModule::WAIT_SELECTION);
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800185 try {
186 m_storage->addRequest(certRequest);
187 }
188 catch (const std::exception& e) {
189 _LOG_TRACE("Cannot add new request instance " << e.what());
190 return;
191 }
192
193 Data result;
194 result.setName(request.getName());
Zhiyi Zhanga63b7372017-05-17 14:14:34 -0700195 result.setContent(dataContentFromJson(genResponseNewJson(requestId, certRequest.getStatus(),
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800196 caItem.m_supportedChallenges)));
Zhiyi Zhangad6cf932017-10-26 16:19:15 -0700197 m_keyChain.sign(result, signingByIdentity(caItem.m_caName));
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800198 m_face.put(result);
Zhiyi Zhanga63b7372017-05-17 14:14:34 -0700199
Zhiyi Zhang7420cf52017-12-18 18:59:21 +0800200 if (caItem.m_requestUpdateCallback) {
201 caItem.m_requestUpdateCallback(certRequest);
202 }
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800203}
204
205void
206CaModule::handleSelect(const Interest& request, const CaItem& caItem)
207{
Zhiyi Zhang4d89fe02017-04-28 18:51:51 -0700208 // SELECT Naming Convention: /CA-prefix/CA/_SELECT/{Request-ID JSON}/<ChallengeID>/
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800209 // {Param JSON}/[Signature components]
210 _LOG_TRACE("Handle SELECT request");
211
212 CertificateRequest certRequest = getCertificateRequest(request, caItem.m_caName);
213 if (certRequest.getRequestId().empty()) {
214 return;
215 }
216
217 if (!security::verifySignature(request, certRequest.getCert())) {
Zhiyi Zhang693c1272017-05-20 22:58:55 -0700218 _LOG_TRACE("Interest with bad signature.");
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800219 return;
220 }
221
222 std::string challengeType;
223 try {
224 challengeType = readString(request.getName().at(caItem.m_caName.size() + 3));
225 }
226 catch (const std::exception& e) {
Zhiyi Zhang693c1272017-05-20 22:58:55 -0700227 _LOG_ERROR(e.what());
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800228 return;
229 }
Zhiyi Zhange30eb352017-04-13 15:26:14 -0700230 _LOG_TRACE("SELECT request choosing challenge " << challengeType);
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800231 auto challenge = ChallengeModule::createChallengeModule(challengeType);
232 if (challenge == nullptr) {
233 _LOG_TRACE("Unrecognized challenge type " << challengeType);
234 return;
235 }
236 JsonSection contentJson = challenge->handleChallengeRequest(request, certRequest);
Zhiyi Zhanga9bda732017-05-20 22:58:55 -0700237 if (certRequest.getStatus() == ChallengeModule::FAILURE) {
238 m_storage->deleteRequest(certRequest.getRequestId());
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800239 }
Zhiyi Zhanga9bda732017-05-20 22:58:55 -0700240 else {
241 try {
242 m_storage->updateRequest(certRequest);
243 }
244 catch (const std::exception& e) {
245 _LOG_TRACE("Cannot update request instance " << e.what());
246 return;
247 }
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800248 }
249
250 Data result;
251 result.setName(request.getName());
252 result.setContent(dataContentFromJson(contentJson));
Zhiyi Zhangad6cf932017-10-26 16:19:15 -0700253 m_keyChain.sign(result, signingByIdentity(caItem.m_caName));
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800254 m_face.put(result);
Zhiyi Zhanga63b7372017-05-17 14:14:34 -0700255
Zhiyi Zhang7420cf52017-12-18 18:59:21 +0800256 if (caItem.m_requestUpdateCallback) {
257 caItem.m_requestUpdateCallback(certRequest);
258 }
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800259}
260
261void
262CaModule::handleValidate(const Interest& request, const CaItem& caItem)
263{
Zhiyi Zhang4d89fe02017-04-28 18:51:51 -0700264 // VALIDATE Naming Convention: /CA-prefix/CA/_VALIDATE/{Request-ID JSON}/<ChallengeID>/
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800265 // {Param JSON}/[Signature components]
266 _LOG_TRACE("Handle VALIDATE request");
267
268 CertificateRequest certRequest = getCertificateRequest(request, caItem.m_caName);
269 if (certRequest.getRequestId().empty()) {
270 return;
271 }
272
273 if (!security::verifySignature(request, certRequest.getCert())) {
Zhiyi Zhang693c1272017-05-20 22:58:55 -0700274 _LOG_TRACE("Interest with bad signature.");
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800275 return;
276 }
277
278 std::string challengeType = certRequest.getChallengeType();
279 auto challenge = ChallengeModule::createChallengeModule(challengeType);
280 if (challenge == nullptr) {
281 _LOG_TRACE("Unrecognized challenge type " << challengeType);
282 return;
283 }
284 JsonSection contentJson = challenge->handleChallengeRequest(request, certRequest);
Zhiyi Zhanga9bda732017-05-20 22:58:55 -0700285 if (certRequest.getStatus() == ChallengeModule::FAILURE) {
286 m_storage->deleteRequest(certRequest.getRequestId());
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800287 }
Zhiyi Zhanga9bda732017-05-20 22:58:55 -0700288 else {
289 try {
290 m_storage->updateRequest(certRequest);
291 }
292 catch (const std::exception& e) {
293 _LOG_TRACE("Cannot update request instance " << e.what());
294 return;
295 }
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800296 }
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800297 Data result;
298 result.setName(request.getName());
299 result.setContent(dataContentFromJson(contentJson));
Zhiyi Zhangad6cf932017-10-26 16:19:15 -0700300 m_keyChain.sign(result, signingByIdentity(caItem.m_caName));
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800301 m_face.put(result);
302
Zhiyi Zhang7420cf52017-12-18 18:59:21 +0800303 if (caItem.m_requestUpdateCallback) {
304 caItem.m_requestUpdateCallback(certRequest);
305 }
Zhiyi Zhanga63b7372017-05-17 14:14:34 -0700306
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800307 if (certRequest.getStatus() == ChallengeModule::SUCCESS) {
308 issueCertificate(certRequest, caItem);
309 }
310}
311
312void
313CaModule::handleStatus(const Interest& request, const CaItem& caItem)
314{
Zhiyi Zhang4d89fe02017-04-28 18:51:51 -0700315 // STATUS Naming Convention: /CA-prefix/CA/_STATUS/{Request-ID JSON}/[Signature components]
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800316 _LOG_TRACE("Handle STATUS request");
317
318 CertificateRequest certRequest = getCertificateRequest(request, caItem.m_caName);
319 if (certRequest.getRequestId().empty()) {
320 return;
321 }
322
323 if (!security::verifySignature(request, certRequest.getCert())) {
Zhiyi Zhang693c1272017-05-20 22:58:55 -0700324 _LOG_TRACE("Interest with bad signature.");
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800325 return;
326 }
327
328 std::string challengeType = certRequest.getChallengeType();
329 auto challenge = ChallengeModule::createChallengeModule(challengeType);
330 if (challenge == nullptr) {
331 _LOG_TRACE("Unrecognized challenge type " << challengeType);
332 return;
333 }
334 JsonSection contentJson = challenge->handleChallengeRequest(request, certRequest);
335
336 Data result;
337 result.setName(request.getName());
338 result.setContent(dataContentFromJson(contentJson));
Zhiyi Zhangad6cf932017-10-26 16:19:15 -0700339 m_keyChain.sign(result, signingByIdentity(caItem.m_caName));
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800340 m_face.put(result);
341}
342
343void
344CaModule::handleDownload(const Interest& request, const CaItem& caItem)
345{
Zhiyi Zhang4d89fe02017-04-28 18:51:51 -0700346 // DOWNLOAD Naming Convention: /CA-prefix/CA/_DOWNLOAD/{Request-ID JSON}
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800347 _LOG_TRACE("Handle DOWNLOAD request");
348
349 JsonSection requestIdJson = jsonFromNameComponent(request.getName(), caItem.m_caName.size() + 2);
350 std::string requestId = requestIdJson.get(JSON_REQUEST_ID, "");
351 security::v2::Certificate signedCert;
352 try {
353 signedCert = m_storage->getCertificate(requestId);
354 }
355 catch (const std::exception& e) {
Zhiyi Zhang693c1272017-05-20 22:58:55 -0700356 _LOG_ERROR(e.what());
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800357 return;
358 }
359
360 Data result;
361 result.setName(request.getName());
362 result.setContent(signedCert.wireEncode());
Zhiyi Zhangad6cf932017-10-26 16:19:15 -0700363 m_keyChain.sign(result, signingByIdentity(caItem.m_caName));
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800364 m_face.put(result);
365}
366
367void
368CaModule::issueCertificate(const CertificateRequest& certRequest, const CaItem& caItem)
369{
370 Name certName = certRequest.getCert().getKeyName();
371 certName.append("NDNCERT").appendVersion();
372 security::v2::Certificate newCert;
373 newCert.setName(certName);
374 newCert.setContent(certRequest.getCert().getContent());
Zhiyi Zhang693c1272017-05-20 22:58:55 -0700375 _LOG_TRACE("cert request content " << certRequest.getCert());
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800376 SignatureInfo signatureInfo;
377 security::ValidityPeriod period(time::system_clock::now(),
378 time::system_clock::now() + caItem.m_validityPeriod);
379 signatureInfo.setValidityPeriod(period);
Zhiyi Zhangad6cf932017-10-26 16:19:15 -0700380 security::SigningInfo signingInfo(security::SigningInfo::SIGNER_TYPE_ID,
381 caItem.m_caName, signatureInfo);
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800382 newCert.setFreshnessPeriod(caItem.m_freshnessPeriod);
383
384 m_keyChain.sign(newCert, signingInfo);
Zhiyi Zhang693c1272017-05-20 22:58:55 -0700385 _LOG_TRACE("new cert got signed" << newCert);
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800386 try {
387 m_storage->addCertificate(certRequest.getRequestId(), newCert);
388 m_storage->deleteRequest(certRequest.getRequestId());
389 _LOG_TRACE("New Certificate Issued " << certName);
390 }
391 catch (const std::exception& e) {
Zhiyi Zhang693c1272017-05-20 22:58:55 -0700392 _LOG_ERROR("Cannot add issued cert and remove the request " << e.what());
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800393 return;
394 }
395}
396
397CertificateRequest
398CaModule::getCertificateRequest(const Interest& request, const Name& caName)
399{
400 JsonSection requestIdJson = jsonFromNameComponent(request.getName(), caName.size() + 2);
401 std::string requestId = requestIdJson.get(JSON_REQUEST_ID, "");
402 CertificateRequest certRequest;
403 try {
404 certRequest = m_storage->getRequest(requestId);
405 }
406 catch (const std::exception& e) {
Zhiyi Zhang693c1272017-05-20 22:58:55 -0700407 _LOG_ERROR(e.what());
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800408 }
409 return certRequest;
410}
411
412void
413CaModule::onRegisterFailed(const std::string& reason)
414{
Zhiyi Zhang693c1272017-05-20 22:58:55 -0700415 _LOG_ERROR("Failed to register prefix in local hub's daemon, REASON: " << reason);
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800416}
417
418Block
419CaModule::dataContentFromJson(const JsonSection& jsonSection)
420{
421 std::stringstream ss;
422 boost::property_tree::write_json(ss, jsonSection);
423 return makeStringBlock(ndn::tlv::Content, ss.str());
424}
425
426JsonSection
427CaModule::jsonFromNameComponent(const Name& name, int pos)
428{
429 std::string jsonString;
430 try {
431 jsonString = encoding::readString(name.at(pos));
432 }
433 catch (const std::exception& e) {
Zhiyi Zhang693c1272017-05-20 22:58:55 -0700434 _LOG_ERROR(e.what());
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800435 return JsonSection();
436 }
437 std::istringstream ss(jsonString);
438 JsonSection json;
439 boost::property_tree::json_parser::read_json(ss, json);
440 return json;
441}
442
443} // namespace ndncert
444} // namespace ndn