blob: 904886d906fffd244cd94cca00a85f2eebbc4439 [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) {
Zhiyi Zhangad6cf932017-10-26 16:19:15 -070058 // NEW
59 const InterestFilterId* filterId = m_face.setInterestFilter(Name(name).append("_NEW"),
Zhiyi Zhangf5246c42017-01-26 09:39:20 -080060 bind(&CaModule::handleNew, this, _2, item));
61 m_interestFilterIds.push_back(filterId);
Zhiyi Zhangad6cf932017-10-26 16:19:15 -070062 // SELECT
Zhiyi Zhangf5246c42017-01-26 09:39:20 -080063 filterId = m_face.setInterestFilter(Name(name).append("_SELECT"),
64 bind(&CaModule::handleSelect, this, _2, item));
65 m_interestFilterIds.push_back(filterId);
Zhiyi Zhangad6cf932017-10-26 16:19:15 -070066 // VALIDATE
Zhiyi Zhangf5246c42017-01-26 09:39:20 -080067 filterId = m_face.setInterestFilter(Name(name).append("_VALIDATE"),
68 bind(&CaModule::handleValidate, this, _2, item));
69 m_interestFilterIds.push_back(filterId);
Zhiyi Zhangad6cf932017-10-26 16:19:15 -070070 // STATUS
Zhiyi Zhangf5246c42017-01-26 09:39:20 -080071 filterId = m_face.setInterestFilter(Name(name).append("_STATUS"),
72 bind(&CaModule::handleStatus, this, _2, item));
73 m_interestFilterIds.push_back(filterId);
Zhiyi Zhangad6cf932017-10-26 16:19:15 -070074 // DOWNLOAD
Zhiyi Zhangf5246c42017-01-26 09:39:20 -080075 filterId = m_face.setInterestFilter(Name(name).append("_DOWNLOAD"),
76 bind(&CaModule::handleDownload, this, _2, item));
77 m_interestFilterIds.push_back(filterId);
Zhiyi Zhangad6cf932017-10-26 16:19:15 -070078 // PROBE
79 if (item.m_probe != "") {
80 filterId = m_face.setInterestFilter(Name(name).append("_PROBE"),
81 bind(&CaModule::handleProbe, this, _2, item));
82 m_interestFilterIds.push_back(filterId);
83 }
Zhiyi Zhangf5246c42017-01-26 09:39:20 -080084 _LOG_TRACE("Prefix " << name << " got registered");
85 },
86 bind(&CaModule::onRegisterFailed, this, _2));
87 m_registeredPrefixIds.push_back(prefixId);
88 }
89 catch (const std::exception& e) {
Zhiyi Zhang693c1272017-05-20 22:58:55 -070090 _LOG_ERROR(e.what());
Zhiyi Zhangf5246c42017-01-26 09:39:20 -080091 }
92 }
93}
94
95CaModule::~CaModule()
96{
97 for (auto prefixId : m_interestFilterIds) {
98 m_face.unsetInterestFilter(prefixId);
99 }
100 for (auto prefixId : m_registeredPrefixIds) {
101 m_face.unregisterPrefix(prefixId, nullptr, nullptr);
102 }
103}
104
105void
106CaModule::handleProbe(const Interest& request, const CaItem& caItem)
107{
Zhiyi Zhang4d89fe02017-04-28 18:51:51 -0700108 // PROBE Naming Convention: /CA-prefix/CA/_PROBE/<Probe Information>
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800109 _LOG_TRACE("Handle PROBE request");
110
111 std::string identifier;
112 try {
113 identifier = m_probeHandler(readString(request.getName().at(caItem.m_caName.size() + 2)));
114 }
115 catch (const std::exception& e) {
116 _LOG_TRACE("Cannot generate identifier for PROBE request " << e.what());
117 return;
118 }
119 Name identityName = caItem.m_caName;
120 identityName.append(identifier);
121
122 Data result;
123 result.setName(request.getName());
124 result.setContent(dataContentFromJson(genResponseProbeJson(identityName, "")));
Zhiyi Zhangad6cf932017-10-26 16:19:15 -0700125 m_keyChain.sign(result, signingByIdentity(caItem.m_caName));
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800126 m_face.put(result);
127
128 _LOG_TRACE("Handle PROBE: generate identity " << identityName);
129}
130
131void
132CaModule::handleNew(const Interest& request, const CaItem& caItem)
133{
Zhiyi Zhang4d89fe02017-04-28 18:51:51 -0700134 // NEW Naming Convention: /CA-prefix/CA/_NEW/<certificate-request>/[signature]
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800135 _LOG_TRACE("Handle NEW request");
136
137 security::v2::Certificate clientCert;
138 try {
139 clientCert.wireDecode(request.getName().at(caItem.m_caName.size() + 2).blockFromValue());
140 }
141 catch (const std::exception& e) {
Zhiyi Zhang693c1272017-05-20 22:58:55 -0700142 _LOG_ERROR("Unrecognized certificate request " << e.what());
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800143 return;
144 }
Zhiyi Zhang693c1272017-05-20 22:58:55 -0700145
146 if (!security::verifySignature(clientCert, clientCert)) {
147 _LOG_TRACE("Cert request with bad signature.");
148 return;
149 }
150 if (!security::verifySignature(request, clientCert)) {
151 _LOG_TRACE("Interest with bad signature.");
152 return;
153 }
154
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800155 std::string requestId = std::to_string(random::generateWord64());
156 CertificateRequest certRequest(caItem.m_caName, requestId, clientCert);
Zhiyi Zhanga63b7372017-05-17 14:14:34 -0700157 certRequest.setStatus(ChallengeModule::WAIT_SELECTION);
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800158 try {
159 m_storage->addRequest(certRequest);
160 }
161 catch (const std::exception& e) {
162 _LOG_TRACE("Cannot add new request instance " << e.what());
163 return;
164 }
165
166 Data result;
167 result.setName(request.getName());
Zhiyi Zhanga63b7372017-05-17 14:14:34 -0700168 result.setContent(dataContentFromJson(genResponseNewJson(requestId, certRequest.getStatus(),
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800169 caItem.m_supportedChallenges)));
Zhiyi Zhangad6cf932017-10-26 16:19:15 -0700170 m_keyChain.sign(result, signingByIdentity(caItem.m_caName));
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800171 m_face.put(result);
Zhiyi Zhanga63b7372017-05-17 14:14:34 -0700172
173 m_requestUpdateCallback(certRequest);
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800174}
175
176void
177CaModule::handleSelect(const Interest& request, const CaItem& caItem)
178{
Zhiyi Zhang4d89fe02017-04-28 18:51:51 -0700179 // SELECT Naming Convention: /CA-prefix/CA/_SELECT/{Request-ID JSON}/<ChallengeID>/
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800180 // {Param JSON}/[Signature components]
181 _LOG_TRACE("Handle SELECT request");
182
183 CertificateRequest certRequest = getCertificateRequest(request, caItem.m_caName);
184 if (certRequest.getRequestId().empty()) {
185 return;
186 }
187
188 if (!security::verifySignature(request, certRequest.getCert())) {
Zhiyi Zhang693c1272017-05-20 22:58:55 -0700189 _LOG_TRACE("Interest with bad signature.");
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800190 return;
191 }
192
193 std::string challengeType;
194 try {
195 challengeType = readString(request.getName().at(caItem.m_caName.size() + 3));
196 }
197 catch (const std::exception& e) {
Zhiyi Zhang693c1272017-05-20 22:58:55 -0700198 _LOG_ERROR(e.what());
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800199 return;
200 }
Zhiyi Zhange30eb352017-04-13 15:26:14 -0700201 _LOG_TRACE("SELECT request choosing challenge " << challengeType);
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800202 auto challenge = ChallengeModule::createChallengeModule(challengeType);
203 if (challenge == nullptr) {
204 _LOG_TRACE("Unrecognized challenge type " << challengeType);
205 return;
206 }
207 JsonSection contentJson = challenge->handleChallengeRequest(request, certRequest);
Zhiyi Zhanga9bda732017-05-20 22:58:55 -0700208 if (certRequest.getStatus() == ChallengeModule::FAILURE) {
209 m_storage->deleteRequest(certRequest.getRequestId());
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800210 }
Zhiyi Zhanga9bda732017-05-20 22:58:55 -0700211 else {
212 try {
213 m_storage->updateRequest(certRequest);
214 }
215 catch (const std::exception& e) {
216 _LOG_TRACE("Cannot update request instance " << e.what());
217 return;
218 }
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800219 }
220
221 Data result;
222 result.setName(request.getName());
223 result.setContent(dataContentFromJson(contentJson));
Zhiyi Zhangad6cf932017-10-26 16:19:15 -0700224 m_keyChain.sign(result, signingByIdentity(caItem.m_caName));
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800225 m_face.put(result);
Zhiyi Zhanga63b7372017-05-17 14:14:34 -0700226
227 m_requestUpdateCallback(certRequest);
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800228}
229
230void
231CaModule::handleValidate(const Interest& request, const CaItem& caItem)
232{
Zhiyi Zhang4d89fe02017-04-28 18:51:51 -0700233 // VALIDATE Naming Convention: /CA-prefix/CA/_VALIDATE/{Request-ID JSON}/<ChallengeID>/
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800234 // {Param JSON}/[Signature components]
235 _LOG_TRACE("Handle VALIDATE request");
236
237 CertificateRequest certRequest = getCertificateRequest(request, caItem.m_caName);
238 if (certRequest.getRequestId().empty()) {
239 return;
240 }
241
242 if (!security::verifySignature(request, certRequest.getCert())) {
Zhiyi Zhang693c1272017-05-20 22:58:55 -0700243 _LOG_TRACE("Interest with bad signature.");
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800244 return;
245 }
246
247 std::string challengeType = certRequest.getChallengeType();
248 auto challenge = ChallengeModule::createChallengeModule(challengeType);
249 if (challenge == nullptr) {
250 _LOG_TRACE("Unrecognized challenge type " << challengeType);
251 return;
252 }
253 JsonSection contentJson = challenge->handleChallengeRequest(request, certRequest);
Zhiyi Zhanga9bda732017-05-20 22:58:55 -0700254 if (certRequest.getStatus() == ChallengeModule::FAILURE) {
255 m_storage->deleteRequest(certRequest.getRequestId());
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800256 }
Zhiyi Zhanga9bda732017-05-20 22:58:55 -0700257 else {
258 try {
259 m_storage->updateRequest(certRequest);
260 }
261 catch (const std::exception& e) {
262 _LOG_TRACE("Cannot update request instance " << e.what());
263 return;
264 }
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800265 }
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800266 Data result;
267 result.setName(request.getName());
268 result.setContent(dataContentFromJson(contentJson));
Zhiyi Zhangad6cf932017-10-26 16:19:15 -0700269 m_keyChain.sign(result, signingByIdentity(caItem.m_caName));
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800270 m_face.put(result);
271
Zhiyi Zhanga63b7372017-05-17 14:14:34 -0700272 m_requestUpdateCallback(certRequest);
273
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800274 if (certRequest.getStatus() == ChallengeModule::SUCCESS) {
275 issueCertificate(certRequest, caItem);
276 }
277}
278
279void
280CaModule::handleStatus(const Interest& request, const CaItem& caItem)
281{
Zhiyi Zhang4d89fe02017-04-28 18:51:51 -0700282 // STATUS Naming Convention: /CA-prefix/CA/_STATUS/{Request-ID JSON}/[Signature components]
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800283 _LOG_TRACE("Handle STATUS request");
284
285 CertificateRequest certRequest = getCertificateRequest(request, caItem.m_caName);
286 if (certRequest.getRequestId().empty()) {
287 return;
288 }
289
290 if (!security::verifySignature(request, certRequest.getCert())) {
Zhiyi Zhang693c1272017-05-20 22:58:55 -0700291 _LOG_TRACE("Interest with bad signature.");
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800292 return;
293 }
294
295 std::string challengeType = certRequest.getChallengeType();
296 auto challenge = ChallengeModule::createChallengeModule(challengeType);
297 if (challenge == nullptr) {
298 _LOG_TRACE("Unrecognized challenge type " << challengeType);
299 return;
300 }
301 JsonSection contentJson = challenge->handleChallengeRequest(request, certRequest);
302
303 Data result;
304 result.setName(request.getName());
305 result.setContent(dataContentFromJson(contentJson));
Zhiyi Zhangad6cf932017-10-26 16:19:15 -0700306 m_keyChain.sign(result, signingByIdentity(caItem.m_caName));
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800307 m_face.put(result);
308}
309
310void
311CaModule::handleDownload(const Interest& request, const CaItem& caItem)
312{
Zhiyi Zhang4d89fe02017-04-28 18:51:51 -0700313 // DOWNLOAD Naming Convention: /CA-prefix/CA/_DOWNLOAD/{Request-ID JSON}
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800314 _LOG_TRACE("Handle DOWNLOAD request");
315
316 JsonSection requestIdJson = jsonFromNameComponent(request.getName(), caItem.m_caName.size() + 2);
317 std::string requestId = requestIdJson.get(JSON_REQUEST_ID, "");
318 security::v2::Certificate signedCert;
319 try {
320 signedCert = m_storage->getCertificate(requestId);
321 }
322 catch (const std::exception& e) {
Zhiyi Zhang693c1272017-05-20 22:58:55 -0700323 _LOG_ERROR(e.what());
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800324 return;
325 }
326
327 Data result;
328 result.setName(request.getName());
329 result.setContent(signedCert.wireEncode());
Zhiyi Zhangad6cf932017-10-26 16:19:15 -0700330 m_keyChain.sign(result, signingByIdentity(caItem.m_caName));
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800331 m_face.put(result);
332}
333
334void
335CaModule::issueCertificate(const CertificateRequest& certRequest, const CaItem& caItem)
336{
337 Name certName = certRequest.getCert().getKeyName();
338 certName.append("NDNCERT").appendVersion();
339 security::v2::Certificate newCert;
340 newCert.setName(certName);
341 newCert.setContent(certRequest.getCert().getContent());
Zhiyi Zhang693c1272017-05-20 22:58:55 -0700342 _LOG_TRACE("cert request content " << certRequest.getCert());
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800343 SignatureInfo signatureInfo;
344 security::ValidityPeriod period(time::system_clock::now(),
345 time::system_clock::now() + caItem.m_validityPeriod);
346 signatureInfo.setValidityPeriod(period);
Zhiyi Zhangad6cf932017-10-26 16:19:15 -0700347 security::SigningInfo signingInfo(security::SigningInfo::SIGNER_TYPE_ID,
348 caItem.m_caName, signatureInfo);
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800349 newCert.setFreshnessPeriod(caItem.m_freshnessPeriod);
350
351 m_keyChain.sign(newCert, signingInfo);
Zhiyi Zhang693c1272017-05-20 22:58:55 -0700352 _LOG_TRACE("new cert got signed" << newCert);
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800353 try {
354 m_storage->addCertificate(certRequest.getRequestId(), newCert);
355 m_storage->deleteRequest(certRequest.getRequestId());
356 _LOG_TRACE("New Certificate Issued " << certName);
357 }
358 catch (const std::exception& e) {
Zhiyi Zhang693c1272017-05-20 22:58:55 -0700359 _LOG_ERROR("Cannot add issued cert and remove the request " << e.what());
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800360 return;
361 }
362}
363
364CertificateRequest
365CaModule::getCertificateRequest(const Interest& request, const Name& caName)
366{
367 JsonSection requestIdJson = jsonFromNameComponent(request.getName(), caName.size() + 2);
368 std::string requestId = requestIdJson.get(JSON_REQUEST_ID, "");
369 CertificateRequest certRequest;
370 try {
371 certRequest = m_storage->getRequest(requestId);
372 }
373 catch (const std::exception& e) {
Zhiyi Zhang693c1272017-05-20 22:58:55 -0700374 _LOG_ERROR(e.what());
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800375 }
376 return certRequest;
377}
378
379void
380CaModule::onRegisterFailed(const std::string& reason)
381{
Zhiyi Zhang693c1272017-05-20 22:58:55 -0700382 _LOG_ERROR("Failed to register prefix in local hub's daemon, REASON: " << reason);
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800383}
384
385Block
386CaModule::dataContentFromJson(const JsonSection& jsonSection)
387{
388 std::stringstream ss;
389 boost::property_tree::write_json(ss, jsonSection);
390 return makeStringBlock(ndn::tlv::Content, ss.str());
391}
392
393JsonSection
394CaModule::jsonFromNameComponent(const Name& name, int pos)
395{
396 std::string jsonString;
397 try {
398 jsonString = encoding::readString(name.at(pos));
399 }
400 catch (const std::exception& e) {
Zhiyi Zhang693c1272017-05-20 22:58:55 -0700401 _LOG_ERROR(e.what());
Zhiyi Zhangf5246c42017-01-26 09:39:20 -0800402 return JsonSection();
403 }
404 std::istringstream ss(jsonString);
405 JsonSection json;
406 boost::property_tree::json_parser::read_json(ss, json);
407 return json;
408}
409
410} // namespace ndncert
411} // namespace ndn