blob: 619d1c06cbec3ca51da7f37771b0c95ca2faf935 [file] [log] [blame]
Jiewen Tan870b29b2014-11-17 19:09:49 -08001/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
2/**
3 * Copyright (c) 2014, Regents of the University of California.
4 *
5 * This file is part of NDNS (Named Data Networking Domain Name Service).
6 * See AUTHORS.md for complete list of NDNS authors and contributors.
7 *
8 * NDNS is free software: you can redistribute it and/or modify it under the terms
9 * of the GNU General Public License as published by the Free Software Foundation,
10 * either version 3 of the License, or (at your option) any later version.
11 *
12 * NDNS is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
13 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
14 * PURPOSE. See the GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along with
17 * NDNS, e.g., in COPYING.md file. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20#include "management-tool.hpp"
21#include "logger.hpp"
22#include "ndns-label.hpp"
23#include "ndns-tlv.hpp"
24
25#include <string>
26#include <iomanip>
27
28#include <boost/filesystem/operations.hpp>
29#include <boost/filesystem/path.hpp>
30#include <boost/algorithm/string/replace.hpp>
31
32#include <ndn-cxx/util/io.hpp>
33#include <ndn-cxx/util/regex.hpp>
34#include <ndn-cxx/encoding/oid.hpp>
35#include <ndn-cxx/security/cryptopp.hpp>
36
37namespace ndn {
38namespace ndns {
39
40NDNS_LOG_INIT("ManagementTool");
41
42ManagementTool::ManagementTool(const std::string& dbFile)
43 : m_dbMgr(dbFile)
44{
45}
46
47void
48ManagementTool::createZone(const Name &zoneName,
49 const Name& parentZoneName,
50 const time::seconds& cacheTtl,
51 const time::seconds& certTtl,
52 const Name& kskCertName,
53 const Name& dskCertName)
54{
55 bool isRoot = zoneName == ROOT_ZONE;
56
57 //check preconditions
58 Zone zone(zoneName, cacheTtl);
59 if (m_dbMgr.find(zone)) {
60 throw Error(zoneName.toUri() + " is already presented in the NDNS db");
61 }
62
63 if (!isRoot && parentZoneName.equals(zoneName)) {
64 throw Error("Parent zone name can not be the zone itself");
65 }
66
67 if (!isRoot && !parentZoneName.isPrefixOf(zoneName)) {
68 throw Error(parentZoneName.toUri() + " is not a prefix of " + zoneName.toUri());
69 }
70
71 if (kskCertName != DEFAULT_CERT) {
72 if (!matchCertificate(kskCertName, zoneName)) {
73 throw Error("Cannot verify KSK certificate");
74 }
75 }
76
77 if (dskCertName != DEFAULT_CERT) {
78 if (!matchCertificate(dskCertName, zoneName)) {
79 throw Error("Cannot verify DSK certificate");
80 }
81 }
82
83 if (kskCertName == DEFAULT_CERT && isRoot) {
84 throw Error("Cannot generate KSK for root zone");
85 }
86
87 //first generate KSK and DSK to the keyChain system, and add DSK as default
88 NDNS_LOG_INFO("Start generating KSK and DSK and their corresponding certificates");
89 time::system_clock::TimePoint notBefore = time::system_clock::now();
90 time::system_clock::TimePoint notAfter = notBefore + certTtl;
91 shared_ptr<IdentityCertificate> kskCert;
92
93 if (kskCertName == DEFAULT_CERT) {
94 //create KSK's certificate
95 Name kskName = m_keyChain.generateRsaKeyPair(zoneName, true);
96 std::vector<CertificateSubjectDescription> kskDesc;
97 kskCert = m_keyChain.prepareUnsignedIdentityCertificate(kskName, zoneName, notBefore, notAfter,
98 kskDesc);
99 //prepare the correct name for the ksk certificate
100 Name newScertName = parentZoneName;
101 newScertName.append(label::NDNS_CERT_QUERY);
102 newScertName.append(zoneName.getSubName(parentZoneName.size()));
103 //remove the zone prefix and KEY
104 newScertName.append(kskCert->getName().getSubName(zoneName.size()+1));
105 kskCert->setName(newScertName);
106
107 m_keyChain.selfSign(*kskCert);
108 m_keyChain.addCertificate(*kskCert);
109 NDNS_LOG_INFO("Generated KSK: " << kskCert->getName().toUri());
110 }
111 else {
112 kskCert = m_keyChain.getCertificate(kskCertName);
113 }
114
115 Name dskName;
116 shared_ptr<IdentityCertificate> dskCert;
117 if (dskCertName == DEFAULT_CERT) {
118 dskName = m_keyChain.generateRsaKeyPairAsDefault(zoneName, false);
119 //create DSK's certificate
120 std::vector<CertificateSubjectDescription> dskDesc;
121 dskCert = m_keyChain.prepareUnsignedIdentityCertificate(dskName, zoneName, notBefore, notAfter,
122 dskDesc);
123 m_keyChain.sign(*dskCert, kskCert->getName());
124 m_keyChain.addCertificateAsKeyDefault(*dskCert);
125 NDNS_LOG_INFO("Generated DSK: " << dskCert->getName().toUri());
126 }
127 else {
128 dskCert = m_keyChain.getCertificate(dskCertName);
129 dskName = dskCert->getPublicKeyName();
130 m_keyChain.setDefaultKeyNameForIdentity(dskName);
131 m_keyChain.setDefaultCertificateNameForKey(dskCert->getName());
132 }
133
134 //second add zone to the database
135 NDNS_LOG_INFO("Start adding new zone to data base");
136 addZone(zone);
137
138 //third create ID-cert
139 NDNS_LOG_INFO("Start creating DSK's ID-CERT");
140 addIdCert(zone, dskCert, cacheTtl);
141}
142
143void
144ManagementTool::deleteZone(const Name& zoneName)
145{
146 //check pre-conditions
147 Zone zone(zoneName);
148 if (!m_dbMgr.find(zone)) {
149 throw Error(zoneName.toUri() + " is not presented in the NDNS db");
150 }
151
152 //first remove all rrsets of this zone from local ndns database
153 std::vector<Rrset> rrsets = m_dbMgr.findRrsets(zone);
154 for (Rrset& rrset : rrsets) {
155 m_dbMgr.remove(rrset);
156 }
157
158 //second remove zone from local ndns database
159 removeZone(zone);
160
161 //third remove identity
162 m_keyChain.deleteIdentity(zoneName);
163}
164
165void
166ManagementTool::exportCertificate(const Name& certName, const std::string& outFile)
167{
168 //search for the certificate, start from KeyChain then local NDNS database
169 shared_ptr<IdentityCertificate> cert;
170 if (m_keyChain.doesCertificateExist(certName)) {
171 cert = m_keyChain.getCertificate(certName);
172 }
173 else {
174 shared_ptr<Regex> regex = make_shared<Regex>("(<>*)<KEY>(<>+)<ID-CERT><>");
175 if (regex->match(certName) != true) {
176 throw Error("Certificate name is illegal");
177 }
178 Name zoneName = regex->expand("\\1");
179 Name label = regex->expand("\\2");
180
181 Zone zone(zoneName);
182 Rrset rrset(&zone);
183 rrset.setLabel(label);
184 rrset.setType(label::CERT_RR_TYPE);
185 if (m_dbMgr.find(rrset)) {
186 Data data(rrset.getData());
187 cert = make_shared<IdentityCertificate>(data);
188 }
189 else {
190 throw Error("Cannot find the cert: " + certName.toUri());
191 }
192 }
193
194 if (outFile == DEFAULT_IO) {
195 ndn::io::save(*cert, std::cout);
196 }
197 else {
198 ndn::io::save(*cert, outFile);
199 NDNS_LOG_INFO("save cert to file: " << outFile);
200 }
201}
202
203void
204ManagementTool::addRrSet(const Name& zoneName,
205 const Name& label,
206 const name::Component& type,
207 NdnsType ndnsType,
208 const uint64_t version,
209 const std::vector<std::string>& contents,
210 const Name& inputDskCertName,
211 const time::seconds& ttl)
212{
213 // check pre-condition
214 Zone zone(zoneName);
215 if (!m_dbMgr.find(zone)) {
216 throw Error(zoneName.toUri() + " is not presented in the NDNS db");
217 }
218
219 if (ndnsType == NDNS_UNKNOWN) {
220 throw Error("The ndns type is unknown");
221 }
222
223 if (type == label::CERT_RR_TYPE) {
224 throw Error("It cannot handle ID-CERT rrset type");
225 }
226
227 // check strange rr type and ndns type combination
228 if (type == label::NS_RR_TYPE && ndnsType == NDNS_RAW) {
229 throw Error("NS cannot be of the type NDNS_RAW");
230 }
231
232 if (type == label::TXT_RR_TYPE && ndnsType != NDNS_RESP) {
233 throw Error("TXT cannot be of the type NDNS_RAW or NDNS_AUTH");
234 }
235
236 if (ndnsType == NDNS_RAW && contents.size() != 1) {
237 throw Error("NDNS_RAW must contain a single content element");
238 }
239
240 Name dskName;
241 Name dskCertName = inputDskCertName;
242 if (dskCertName == DEFAULT_CERT) {
243 dskName = m_keyChain.getDefaultKeyNameForIdentity(zoneName);
244 dskCertName = m_keyChain.getDefaultCertificateNameForKey(dskName);
245 }
246 else {
247 if (!matchCertificate(dskCertName, zoneName)) {
248 throw Error("Cannot verify certificate");
249 }
250 }
251
252 // set rrset
253 Rrset rrset(&zone);
254 rrset.setLabel(label);
255 rrset.setType(type);
256 if (ttl == DEFAULT_RR_TTL)
257 rrset.setTtl(zone.getTtl());
258 else
259 rrset.setTtl(ttl);
260
261 // set response
262 Response re;
263 re.setZone(zoneName);
264 re.setQueryType(label::NDNS_ITERATIVE_QUERY);
265 re.setRrLabel(label);
266 re.setRrType(type);
267 re.setNdnsType(ndnsType);
268
269 //set content according to ndns type
270 if (ndnsType == NDNS_RAW) {
271 Block tmp = ndn::dataBlock(ndn::tlv::Content, contents[0].c_str(), contents[0].length());
272 re.setAppContent(tmp);
273 }
274 else if (ndnsType != NDNS_AUTH) {
275 if (contents.empty()) {
276 re.addRr("");
277 }
278 else {
279 for (const auto& item : contents) {
280 re.addRr(item);
281 }
282 }
283 }
284
285 shared_ptr<Data> data = re.toData();
286 if (version != VERSION_USE_UNIX_TIMESTAMP) {
287 name::Component tmp = name::Component::fromVersion(version);
288 re.setVersion(tmp);
289 }
290 m_keyChain.sign(*data, dskCertName);
291
292 rrset.setVersion(re.getVersion());
293 rrset.setData(data->wireEncode());
294
295 if (m_dbMgr.find(rrset)) {
296 throw Error("Rrset with label=" + label.toUri() + " is already in local NDNS databse");
297 }
298 NDNS_LOG_INFO("Add rrset with zone-id: " << zone.getId() << " label: " << label << " type: "
299 << type);
300 m_dbMgr.insert(rrset);
301}
302
303void
304ManagementTool::addRrSet(const Name& zoneName,
305 const std::string& inFile,
306 const time::seconds& ttl,
307 const Name& inputDskCertName)
308{
309 //check precondition
310 Zone zone(zoneName);
311 if (!m_dbMgr.find(zone)) {
312 throw Error(zoneName.toUri() + " is not presented in the NDNS db");
313 }
314
315 Name dskName;
316 Name dskCertName = inputDskCertName;
317 if (dskCertName == DEFAULT_CERT) {
318 dskName = m_keyChain.getDefaultKeyNameForIdentity(zoneName);
319 dskCertName = m_keyChain.getDefaultCertificateNameForKey(dskName);
320 }
321 else {
322 if (!matchCertificate(dskCertName, zoneName)) {
323 throw Error("Cannot verify certificate");
324 }
325 }
326
327 if (inFile != DEFAULT_IO) {
328 boost::filesystem::path dir = boost::filesystem::path(inFile);
329 if (!boost::filesystem::exists(dir) || boost::filesystem::is_directory(dir)) {
330 throw Error("Data: " + inFile + " does not exist");
331 }
332 }
333
334 //first load the data
335 shared_ptr<Data> data;
336 if (inFile == DEFAULT_IO)
337 data = ndn::io::load<ndn::Data>(std::cin);
338 else
339 data = ndn::io::load<ndn::Data>(inFile);
340
341 //determine whether the data is a self-signed certificate
342 shared_ptr<Regex> regex1 = make_shared<Regex>("(<>*)<KEY>(<>+)<ID-CERT><>");
343 if (regex1->match(data->getName())) {
344 IdentityCertificate scert(*data);
345 Name keyName = scert.getPublicKeyName();
346 Name keyLocator = scert.getSignature().getKeyLocator().getName();
347
348 //if it is, extract the content and name from the data, and resign it using the dsk.
349 shared_ptr<Regex> regex2 = make_shared<Regex>("(<>*)<KEY>(<>+)<ID-CERT>");
350 BOOST_VERIFY(regex2->match(keyLocator) == true);
351 if (keyName == regex2->expand("\\1\\2")) {
352 shared_ptr<Data> pre = data;
353 Name name = pre->getName();
354 //check whether the name is legal or not. if not converting it to a legal name
355 if (zoneName != regex1->expand("\\1")) {
356 Name comp1 = regex1->expand("\\1").getSubName(zoneName.size());
357 Name comp2 = regex1->expand("\\2");
358 name = zoneName;
359 name.append("KEY");
360 name.append(comp1);
361 name.append(comp2);
362 name.append("ID-CERT");
363 name.append(pre->getName().get(-1));
364 }
365
366 data = make_shared<Data>();
367 data->setName(name);
368 data->setContent(pre->getContent());
369
370 m_keyChain.sign(*data, dskCertName);
371 }
372 }
373
374 // create response for the input data
375 Response re;
376 Name hint;
377 re.fromData(hint, zoneName, *data);
378 Name label = re.getRrLabel();
379 name::Component type = re.getRrType();
380
381 Rrset rrset(&zone);
382 rrset.setLabel(label);
383 rrset.setType(type);
384 if (ttl == DEFAULT_RR_TTL)
385 rrset.setTtl(zone.getTtl());
386 else
387 rrset.setTtl(ttl);
388 rrset.setVersion(re.getVersion());
389 rrset.setData(data->wireEncode());
390
391 if (m_dbMgr.find(rrset)) {
392 throw Error("Rrset with label=" + label.toUri() + " is already in local NDNS databse");
393 }
394 NDNS_LOG_INFO("Add rrset with zone-id: " << zone.getId() << " label: " << label << " type: "
395 << type);
396 m_dbMgr.insert(rrset);
397}
398
399void
400ManagementTool::listZone(const Name& zoneName, std::ostream& os, const bool printRaw) {
401 Zone zone(zoneName);
402 if (!m_dbMgr.find(zone)) {
403 os << "No record is found" << std::endl;
404 return;
405 }
406
407 //first output the zone name
408 os << "; Zone " << zoneName.toUri() << std::endl << std::endl;
409
410 //second output all rrsets
411 std::vector<Rrset> rrsets = m_dbMgr.findRrsets(zone);
412
413 //set width for different columns
414 size_t labelWidth = 0;
415 size_t ttlWidth = 0;
416 size_t typeWidth = 0;
417 for (Rrset& rrset : rrsets) {
418 Data data(rrset.getData());
419 Response re;
420 Name hint;
421 re.fromData(hint, zoneName, data);
422
423 if (rrset.getLabel().toUri().size() > labelWidth)
424 labelWidth = rrset.getLabel().toUri().size();
425
426 std::stringstream seconds;
427 seconds << rrset.getTtl().count();
428 if (seconds.str().size() > ttlWidth)
429 ttlWidth = seconds.str().size();
430
431 if (rrset.getType().toUri().size() > typeWidth)
432 typeWidth = rrset.getType().toUri().size();
433 }
434
435 //output
436 for (Rrset& rrset : rrsets) {
437 Data data(rrset.getData());
438 Response re;
439 Name hint;
440 re.fromData(hint, zoneName, data);
441 int iteration = re.getNdnsType() == NDNS_RAW || re.getNdnsType() == NDNS_AUTH ?
442 1 : re.getRrs().size();
443 const std::vector<Block> &rrs = re.getRrs();
444
445 if (re.getNdnsType() != NDNS_RAW && re.getNdnsType() != NDNS_AUTH) {
446 os << "; rrset=" << rrset.getLabel().toUri()
447 << " type=" << rrset.getType().toUri()
448 << " version=" << rrset.getVersion().toUri()
449 << " signed-by=" << data.getSignature().getKeyLocator().getName().toUri()
450 << std::endl;
451 }
452
453 for (int i = 0; i < iteration; i++) {
454 os.setf(os.left);
455 os.width(labelWidth + 2);
456 os << rrset.getLabel().toUri();
457
458 os.width(ttlWidth + 2);
459 os << rrset.getTtl().count();
460
461 os.width(typeWidth + 2);
462 os << rrset.getType().toUri();
463
464 if (re.getNdnsType() != NDNS_RAW && re.getNdnsType() != NDNS_AUTH) {
465 using namespace CryptoPP;
466 if (rrset.getType() == label::TXT_RR_TYPE) {
467 os.write(reinterpret_cast<const char*>(rrs[i].value()), rrs[i].value_size());
468 os << std::endl;
469 }
470 else if (rrset.getType() == label::NS_RR_TYPE) {
471 //TODO output the NS data once we have it
472 os << std::endl;
473 }
474 else {
475 StringSource ss(rrs[i].wire(), rrs[i].size(), true,
476 new Base64Encoder(new FileSink(os), true, 64));
477 }
478 }
479 }
480
481 if (re.getNdnsType() == NDNS_RAW || re.getNdnsType() == NDNS_AUTH) {
482 os.width();
483 os << "; content-type=" << re.getNdnsType()
484 << " version=" << rrset.getVersion().toUri()
485 << " signed-by=" << data.getSignature().getKeyLocator().getName().toUri();
486 os << std::endl;
487
488 if (printRaw && re.getNdnsType() == NDNS_RAW) {
489 using namespace CryptoPP;
490 std::stringstream sstream;
491 StringSource ss(re.getAppContent().wire(), re.getAppContent().size(), true,
492 new Base64Encoder(new FileSink(sstream), true, 64));
493
494 std::string content = sstream.str();
495 std::string delimiter = "\n";
496 size_t pos = 0;
497 std::string token;
498 while ((pos = content.find(delimiter)) != std::string::npos) {
499 token = content.substr(0, pos);
500 os << "; " << token << std::endl;
501 content.erase(0, pos + delimiter.length());
502 }
503
504 os << std::endl;
505 }
506 }
507 else {
508 os << std::endl;
509 }
510 }
511}
512
513void
514ManagementTool::listAllZones(std::ostream& os) {
515 std::vector<Zone> zones = m_dbMgr.listZones();
516
517 size_t nameWidth = 0;
518 for (const Zone& zone : zones) {
519 if (zone.getName().toUri().size() > nameWidth)
520 nameWidth = zone.getName().toUri().size();
521 }
522
523 for (const Zone& zone : zones) {
524 os.setf(os.left);
525 os.width(nameWidth + 2);
526 os << zone.getName().toUri();
527
528 os << "; default-ttl=" << zone.getTtl().count();
529 os << " default-key=" << m_keyChain.getDefaultKeyNameForIdentity(zone.getName());
530 os << " default-certificate="
531 << m_keyChain.getDefaultCertificateNameForIdentity(zone.getName());
532 os << std::endl;
533 }
534}
535
536void
537ManagementTool::removeRrSet(const Name& zoneName, const Name& label, const name::Component& type)
538{
539 Zone zone(zoneName);
540 Rrset rrset(&zone);
541 rrset.setLabel(label);
542 rrset.setType(type);
543
544 if (!m_dbMgr.find(rrset)) {
545 return;
546 }
547 NDNS_LOG_INFO("Remove rrset with zone-id: " << zone.getId() << " label: " << label << " type: "
548 << type);
549 m_dbMgr.remove(rrset);
550}
551
552void
553ManagementTool::getRrSet(const Name& zoneName,
554 const Name& label,
555 const name::Component& type,
556 std::ostream& os)
557{
558 Zone zone(zoneName);
559 Rrset rrset(&zone);
560 rrset.setLabel(label);
561 rrset.setType(type);
562
563 if (!m_dbMgr.find(rrset)) {
564 os << "No record is found" << std::endl;
565 return;
566 }
567
568 using namespace CryptoPP;
569 StringSource ss(rrset.getData().wire(), rrset.getData().size(), true,
570 new Base64Encoder(new FileSink(os), true, 64));
571}
572
573void
574ManagementTool::addIdCert(Zone& zone, shared_ptr<IdentityCertificate> cert,
575 const time::seconds& ttl)
576{
577 Rrset rrset(&zone);
578 size_t size = zone.getName().size();
579 Name label = cert->getName().getSubName(size + 1, cert->getName().size() - size - 3);
580 rrset.setLabel(label);
581 rrset.setType(label::CERT_RR_TYPE);
582 rrset.setTtl(ttl);
583 rrset.setVersion(cert->getName().get(-1));
584 rrset.setData(cert->wireEncode());
585
586 if (m_dbMgr.find(rrset)) {
587 throw Error("ID-CERT with label=" + label.toUri() +
588 " is already presented in local NDNS databse");
589 }
590 NDNS_LOG_INFO("Add rrset with zone-id: " << zone.getId() << " label: " << label << " type: "
591 << label::CERT_RR_TYPE);
592 m_dbMgr.insert(rrset);
593}
594
595void
596ManagementTool::addZone(Zone& zone)
597{
598 if (m_dbMgr.find(zone)) {
599 throw Error("Zone with Name=" + zone.getName().toUri() +
600 " is already presented in local NDNS databse");
601 }
602 NDNS_LOG_INFO("Add zone with Name: " << zone.getName().toUri());
603 m_dbMgr.insert(zone);
604}
605
606void
607ManagementTool::removeZone(Zone& zone)
608{
609 if (!m_dbMgr.find(zone)) {
610 return;
611 }
612 NDNS_LOG_INFO("Remove zone with Name: " << zone.getName().toUri());
613 m_dbMgr.remove(zone);
614}
615
616bool
617ManagementTool::matchCertificate(const Name& certName, const Name& identity)
618{
619 if (!m_keyChain.doesCertificateExist(certName)) {
620 NDNS_LOG_WARN(certName.toUri() << " is not presented in KeyChain");
621 return false;
622 }
623
624 //check its public key information
625 shared_ptr<IdentityCertificate> cert = m_keyChain.getCertificate(certName);
626 Name keyName = cert->getPublicKeyName();
627
628 if (!identity.isPrefixOf(keyName) || identity.size()!=keyName.size()-1) {
629 NDNS_LOG_WARN(keyName.toUri() << " is not a key of " << identity.toUri());
630 return false;
631 }
632
633 if (!m_keyChain.doesKeyExistInTpm(keyName, KEY_CLASS_PRIVATE)) {
634 NDNS_LOG_WARN("Private key: " << keyName.toUri() << " is not presented in KeyChain");
635 return false;
636 }
637
638 return true;
639}
640
641} // namespace ndns
642} // namespace ndn