blob: 15f28e0eb08265b5374ec444f335ab26b18d221f [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 // set default handler and callback
44 m_probeHandler = [&] (const std::string& probeInfo) {
45 return probeInfo;
46 };
47 m_requestUpdateCallback = [&] (const CertificateRequest& CertRequest) {
48 // do nothing
49 };
50
51 // register prefix
Zhiyi Zhangf5246c42017-01-26 09:39:20 -080052 for (const auto& item : m_config.m_caItems) {
53 Name prefix = item.m_caName;
54 prefix.append("CA");
55 try {
56 const RegisteredPrefixId* prefixId = m_face.registerPrefix(prefix,
57 [&] (const Name& name) {
58 const InterestFilterId* filterId = m_face.setInterestFilter(Name(name).append("_PROBE"),
59 bind(&CaModule::handleProbe, this, _2, item));
60 m_interestFilterIds.push_back(filterId);
61
62 filterId = m_face.setInterestFilter(Name(name).append("_NEW"),
63 bind(&CaModule::handleNew, this, _2, item));
64 m_interestFilterIds.push_back(filterId);
65
66 filterId = m_face.setInterestFilter(Name(name).append("_SELECT"),
67 bind(&CaModule::handleSelect, this, _2, item));
68 m_interestFilterIds.push_back(filterId);
69
70 filterId = m_face.setInterestFilter(Name(name).append("_VALIDATE"),
71 bind(&CaModule::handleValidate, this, _2, item));
72 m_interestFilterIds.push_back(filterId);
73
74 filterId = m_face.setInterestFilter(Name(name).append("_STATUS"),
75 bind(&CaModule::handleStatus, this, _2, item));
76 m_interestFilterIds.push_back(filterId);
77
78 filterId = m_face.setInterestFilter(Name(name).append("_DOWNLOAD"),
79 bind(&CaModule::handleDownload, this, _2, item));
80 m_interestFilterIds.push_back(filterId);
81
82 _LOG_TRACE("Prefix " << name << " got registered");
83 },
84 bind(&CaModule::onRegisterFailed, this, _2));
85 m_registeredPrefixIds.push_back(prefixId);
86 }
87 catch (const std::exception& e) {
88 _LOG_TRACE("Error: " << e.what());
89 }
90 }
91}
92
93CaModule::~CaModule()
94{
95 for (auto prefixId : m_interestFilterIds) {
96 m_face.unsetInterestFilter(prefixId);
97 }
98 for (auto prefixId : m_registeredPrefixIds) {
99 m_face.unregisterPrefix(prefixId, nullptr, nullptr);
100 }
101}
102
103void
104CaModule::handleProbe(const Interest& request, const CaItem& caItem)
105{
Zhiyi Zhang4d89fe02017-04-28 18:51:51 -0700106 // PROBE Naming Convention: /CA-prefix/CA/_PROBE/<Probe Information>
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800107 _LOG_TRACE("Handle PROBE request");
108
109 std::string identifier;
110 try {
111 identifier = m_probeHandler(readString(request.getName().at(caItem.m_caName.size() + 2)));
112 }
113 catch (const std::exception& e) {
114 _LOG_TRACE("Cannot generate identifier for PROBE request " << e.what());
115 return;
116 }
117 Name identityName = caItem.m_caName;
118 identityName.append(identifier);
119
120 Data result;
121 result.setName(request.getName());
122 result.setContent(dataContentFromJson(genResponseProbeJson(identityName, "")));
123 m_keyChain.sign(result, signingByCertificate(caItem.m_anchor));
124 m_face.put(result);
125
126 _LOG_TRACE("Handle PROBE: generate identity " << identityName);
127}
128
129void
130CaModule::handleNew(const Interest& request, const CaItem& caItem)
131{
Zhiyi Zhang4d89fe02017-04-28 18:51:51 -0700132 // NEW Naming Convention: /CA-prefix/CA/_NEW/<certificate-request>/[signature]
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800133 _LOG_TRACE("Handle NEW request");
134
135 security::v2::Certificate clientCert;
136 try {
137 clientCert.wireDecode(request.getName().at(caItem.m_caName.size() + 2).blockFromValue());
138 }
139 catch (const std::exception& e) {
140 _LOG_TRACE("Unrecognized certificate request " << e.what());
141 return;
142 }
143 std::string requestId = std::to_string(random::generateWord64());
144 CertificateRequest certRequest(caItem.m_caName, requestId, clientCert);
Zhiyi Zhanga63b7372017-05-17 14:14:34 -0700145 certRequest.setStatus(ChallengeModule::WAIT_SELECTION);
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800146 try {
147 m_storage->addRequest(certRequest);
148 }
149 catch (const std::exception& e) {
150 _LOG_TRACE("Cannot add new request instance " << e.what());
151 return;
152 }
153
154 Data result;
155 result.setName(request.getName());
Zhiyi Zhanga63b7372017-05-17 14:14:34 -0700156 result.setContent(dataContentFromJson(genResponseNewJson(requestId, certRequest.getStatus(),
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800157 caItem.m_supportedChallenges)));
158 m_keyChain.sign(result, signingByCertificate(caItem.m_anchor));
159 m_face.put(result);
Zhiyi Zhanga63b7372017-05-17 14:14:34 -0700160
161 m_requestUpdateCallback(certRequest);
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800162}
163
164void
165CaModule::handleSelect(const Interest& request, const CaItem& caItem)
166{
Zhiyi Zhang4d89fe02017-04-28 18:51:51 -0700167 // SELECT Naming Convention: /CA-prefix/CA/_SELECT/{Request-ID JSON}/<ChallengeID>/
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800168 // {Param JSON}/[Signature components]
169 _LOG_TRACE("Handle SELECT request");
170
171 CertificateRequest certRequest = getCertificateRequest(request, caItem.m_caName);
172 if (certRequest.getRequestId().empty()) {
173 return;
174 }
175
176 if (!security::verifySignature(request, certRequest.getCert())) {
177 _LOG_TRACE("Error: Interest with bad signature.");
178 return;
179 }
180
181 std::string challengeType;
182 try {
183 challengeType = readString(request.getName().at(caItem.m_caName.size() + 3));
184 }
185 catch (const std::exception& e) {
186 _LOG_TRACE(e.what());
187 return;
188 }
Zhiyi Zhange30eb352017-04-13 15:26:14 -0700189 _LOG_TRACE("SELECT request choosing challenge " << challengeType);
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800190 auto challenge = ChallengeModule::createChallengeModule(challengeType);
191 if (challenge == nullptr) {
192 _LOG_TRACE("Unrecognized challenge type " << challengeType);
193 return;
194 }
195 JsonSection contentJson = challenge->handleChallengeRequest(request, certRequest);
Zhiyi Zhanga9bda732017-05-20 22:58:55 -0700196 if (certRequest.getStatus() == ChallengeModule::FAILURE) {
197 m_storage->deleteRequest(certRequest.getRequestId());
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800198 }
Zhiyi Zhanga9bda732017-05-20 22:58:55 -0700199 else {
200 try {
201 m_storage->updateRequest(certRequest);
202 }
203 catch (const std::exception& e) {
204 _LOG_TRACE("Cannot update request instance " << e.what());
205 return;
206 }
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800207 }
208
209 Data result;
210 result.setName(request.getName());
211 result.setContent(dataContentFromJson(contentJson));
212 m_keyChain.sign(result, signingByCertificate(caItem.m_anchor));
213 m_face.put(result);
Zhiyi Zhanga63b7372017-05-17 14:14:34 -0700214
215 m_requestUpdateCallback(certRequest);
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800216}
217
218void
219CaModule::handleValidate(const Interest& request, const CaItem& caItem)
220{
Zhiyi Zhang4d89fe02017-04-28 18:51:51 -0700221 // VALIDATE Naming Convention: /CA-prefix/CA/_VALIDATE/{Request-ID JSON}/<ChallengeID>/
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800222 // {Param JSON}/[Signature components]
223 _LOG_TRACE("Handle VALIDATE request");
224
225 CertificateRequest certRequest = getCertificateRequest(request, caItem.m_caName);
226 if (certRequest.getRequestId().empty()) {
227 return;
228 }
229
230 if (!security::verifySignature(request, certRequest.getCert())) {
231 _LOG_TRACE("Error: Interest with bad signature.");
232 return;
233 }
234
235 std::string challengeType = certRequest.getChallengeType();
236 auto challenge = ChallengeModule::createChallengeModule(challengeType);
237 if (challenge == nullptr) {
238 _LOG_TRACE("Unrecognized challenge type " << challengeType);
239 return;
240 }
241 JsonSection contentJson = challenge->handleChallengeRequest(request, certRequest);
Zhiyi Zhanga9bda732017-05-20 22:58:55 -0700242 if (certRequest.getStatus() == ChallengeModule::FAILURE) {
243 m_storage->deleteRequest(certRequest.getRequestId());
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800244 }
Zhiyi Zhanga9bda732017-05-20 22:58:55 -0700245 else {
246 try {
247 m_storage->updateRequest(certRequest);
248 }
249 catch (const std::exception& e) {
250 _LOG_TRACE("Cannot update request instance " << e.what());
251 return;
252 }
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800253 }
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800254 Data result;
255 result.setName(request.getName());
256 result.setContent(dataContentFromJson(contentJson));
257 m_keyChain.sign(result, signingByCertificate(caItem.m_anchor));
258 m_face.put(result);
259
Zhiyi Zhanga63b7372017-05-17 14:14:34 -0700260 m_requestUpdateCallback(certRequest);
261
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800262 if (certRequest.getStatus() == ChallengeModule::SUCCESS) {
263 issueCertificate(certRequest, caItem);
264 }
265}
266
267void
268CaModule::handleStatus(const Interest& request, const CaItem& caItem)
269{
Zhiyi Zhang4d89fe02017-04-28 18:51:51 -0700270 // STATUS Naming Convention: /CA-prefix/CA/_STATUS/{Request-ID JSON}/[Signature components]
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800271 _LOG_TRACE("Handle STATUS request");
272
273 CertificateRequest certRequest = getCertificateRequest(request, caItem.m_caName);
274 if (certRequest.getRequestId().empty()) {
275 return;
276 }
277
278 if (!security::verifySignature(request, certRequest.getCert())) {
279 _LOG_TRACE("Error: Interest with bad signature.");
280 return;
281 }
282
283 std::string challengeType = certRequest.getChallengeType();
284 auto challenge = ChallengeModule::createChallengeModule(challengeType);
285 if (challenge == nullptr) {
286 _LOG_TRACE("Unrecognized challenge type " << challengeType);
287 return;
288 }
289 JsonSection contentJson = challenge->handleChallengeRequest(request, certRequest);
290
291 Data result;
292 result.setName(request.getName());
293 result.setContent(dataContentFromJson(contentJson));
294 m_keyChain.sign(result, signingByCertificate(caItem.m_anchor));
295 m_face.put(result);
296}
297
298void
299CaModule::handleDownload(const Interest& request, const CaItem& caItem)
300{
Zhiyi Zhang4d89fe02017-04-28 18:51:51 -0700301 // DOWNLOAD Naming Convention: /CA-prefix/CA/_DOWNLOAD/{Request-ID JSON}
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800302 _LOG_TRACE("Handle DOWNLOAD request");
303
304 JsonSection requestIdJson = jsonFromNameComponent(request.getName(), caItem.m_caName.size() + 2);
305 std::string requestId = requestIdJson.get(JSON_REQUEST_ID, "");
306 security::v2::Certificate signedCert;
307 try {
308 signedCert = m_storage->getCertificate(requestId);
309 }
310 catch (const std::exception& e) {
311 _LOG_TRACE("Error: " << e.what());
312 return;
313 }
314
315 Data result;
316 result.setName(request.getName());
317 result.setContent(signedCert.wireEncode());
Zhiyi Zhang4d89fe02017-04-28 18:51:51 -0700318 m_keyChain.sign(result, signingByCertificate(caItem.m_anchor));
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800319 m_face.put(result);
320}
321
322void
323CaModule::issueCertificate(const CertificateRequest& certRequest, const CaItem& caItem)
324{
325 Name certName = certRequest.getCert().getKeyName();
326 certName.append("NDNCERT").appendVersion();
327 security::v2::Certificate newCert;
328 newCert.setName(certName);
329 newCert.setContent(certRequest.getCert().getContent());
330 SignatureInfo signatureInfo;
331 security::ValidityPeriod period(time::system_clock::now(),
332 time::system_clock::now() + caItem.m_validityPeriod);
333 signatureInfo.setValidityPeriod(period);
334 security::SigningInfo signingInfo(security::SigningInfo::SIGNER_TYPE_CERT,
335 caItem.m_anchor, signatureInfo);
336 newCert.setFreshnessPeriod(caItem.m_freshnessPeriod);
337
338 m_keyChain.sign(newCert, signingInfo);
339 try {
340 m_storage->addCertificate(certRequest.getRequestId(), newCert);
341 m_storage->deleteRequest(certRequest.getRequestId());
342 _LOG_TRACE("New Certificate Issued " << certName);
343 }
344 catch (const std::exception& e) {
345 _LOG_TRACE("Error: Cannot add issued cert and remove the request " << e.what());
346 return;
347 }
348}
349
350CertificateRequest
351CaModule::getCertificateRequest(const Interest& request, const Name& caName)
352{
353 JsonSection requestIdJson = jsonFromNameComponent(request.getName(), caName.size() + 2);
354 std::string requestId = requestIdJson.get(JSON_REQUEST_ID, "");
355 CertificateRequest certRequest;
356 try {
357 certRequest = m_storage->getRequest(requestId);
358 }
359 catch (const std::exception& e) {
360 _LOG_TRACE("Error: " << e.what());
361 }
362 return certRequest;
363}
364
365void
366CaModule::onRegisterFailed(const std::string& reason)
367{
368 _LOG_TRACE("Error: failed to register prefix in local hub's daemon, REASON: " << reason);
369}
370
371Block
372CaModule::dataContentFromJson(const JsonSection& jsonSection)
373{
374 std::stringstream ss;
375 boost::property_tree::write_json(ss, jsonSection);
376 return makeStringBlock(ndn::tlv::Content, ss.str());
377}
378
379JsonSection
380CaModule::jsonFromNameComponent(const Name& name, int pos)
381{
382 std::string jsonString;
383 try {
384 jsonString = encoding::readString(name.at(pos));
385 }
386 catch (const std::exception& e) {
387 _LOG_TRACE(e.what());
388 return JsonSection();
389 }
390 std::istringstream ss(jsonString);
391 JsonSection json;
392 boost::property_tree::json_parser::read_json(ss, json);
393 return json;
394}
395
396} // namespace ndncert
397} // namespace ndn