mgmt: management tools
refs #2034
Change-Id: I43ff11e0aa7e67f6207e75f139b7417779590efc
diff --git a/src/mgmt/management-tool.cpp b/src/mgmt/management-tool.cpp
new file mode 100644
index 0000000..619d1c0
--- /dev/null
+++ b/src/mgmt/management-tool.cpp
@@ -0,0 +1,642 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014, Regents of the University of California.
+ *
+ * This file is part of NDNS (Named Data Networking Domain Name Service).
+ * See AUTHORS.md for complete list of NDNS authors and contributors.
+ *
+ * NDNS is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NDNS is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NDNS, e.g., in COPYING.md file. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "management-tool.hpp"
+#include "logger.hpp"
+#include "ndns-label.hpp"
+#include "ndns-tlv.hpp"
+
+#include <string>
+#include <iomanip>
+
+#include <boost/filesystem/operations.hpp>
+#include <boost/filesystem/path.hpp>
+#include <boost/algorithm/string/replace.hpp>
+
+#include <ndn-cxx/util/io.hpp>
+#include <ndn-cxx/util/regex.hpp>
+#include <ndn-cxx/encoding/oid.hpp>
+#include <ndn-cxx/security/cryptopp.hpp>
+
+namespace ndn {
+namespace ndns {
+
+NDNS_LOG_INIT("ManagementTool");
+
+ManagementTool::ManagementTool(const std::string& dbFile)
+ : m_dbMgr(dbFile)
+{
+}
+
+void
+ManagementTool::createZone(const Name &zoneName,
+ const Name& parentZoneName,
+ const time::seconds& cacheTtl,
+ const time::seconds& certTtl,
+ const Name& kskCertName,
+ const Name& dskCertName)
+{
+ bool isRoot = zoneName == ROOT_ZONE;
+
+ //check preconditions
+ Zone zone(zoneName, cacheTtl);
+ if (m_dbMgr.find(zone)) {
+ throw Error(zoneName.toUri() + " is already presented in the NDNS db");
+ }
+
+ if (!isRoot && parentZoneName.equals(zoneName)) {
+ throw Error("Parent zone name can not be the zone itself");
+ }
+
+ if (!isRoot && !parentZoneName.isPrefixOf(zoneName)) {
+ throw Error(parentZoneName.toUri() + " is not a prefix of " + zoneName.toUri());
+ }
+
+ if (kskCertName != DEFAULT_CERT) {
+ if (!matchCertificate(kskCertName, zoneName)) {
+ throw Error("Cannot verify KSK certificate");
+ }
+ }
+
+ if (dskCertName != DEFAULT_CERT) {
+ if (!matchCertificate(dskCertName, zoneName)) {
+ throw Error("Cannot verify DSK certificate");
+ }
+ }
+
+ if (kskCertName == DEFAULT_CERT && isRoot) {
+ throw Error("Cannot generate KSK for root zone");
+ }
+
+ //first generate KSK and DSK to the keyChain system, and add DSK as default
+ NDNS_LOG_INFO("Start generating KSK and DSK and their corresponding certificates");
+ time::system_clock::TimePoint notBefore = time::system_clock::now();
+ time::system_clock::TimePoint notAfter = notBefore + certTtl;
+ shared_ptr<IdentityCertificate> kskCert;
+
+ if (kskCertName == DEFAULT_CERT) {
+ //create KSK's certificate
+ Name kskName = m_keyChain.generateRsaKeyPair(zoneName, true);
+ std::vector<CertificateSubjectDescription> kskDesc;
+ kskCert = m_keyChain.prepareUnsignedIdentityCertificate(kskName, zoneName, notBefore, notAfter,
+ kskDesc);
+ //prepare the correct name for the ksk certificate
+ Name newScertName = parentZoneName;
+ newScertName.append(label::NDNS_CERT_QUERY);
+ newScertName.append(zoneName.getSubName(parentZoneName.size()));
+ //remove the zone prefix and KEY
+ newScertName.append(kskCert->getName().getSubName(zoneName.size()+1));
+ kskCert->setName(newScertName);
+
+ m_keyChain.selfSign(*kskCert);
+ m_keyChain.addCertificate(*kskCert);
+ NDNS_LOG_INFO("Generated KSK: " << kskCert->getName().toUri());
+ }
+ else {
+ kskCert = m_keyChain.getCertificate(kskCertName);
+ }
+
+ Name dskName;
+ shared_ptr<IdentityCertificate> dskCert;
+ if (dskCertName == DEFAULT_CERT) {
+ dskName = m_keyChain.generateRsaKeyPairAsDefault(zoneName, false);
+ //create DSK's certificate
+ std::vector<CertificateSubjectDescription> dskDesc;
+ dskCert = m_keyChain.prepareUnsignedIdentityCertificate(dskName, zoneName, notBefore, notAfter,
+ dskDesc);
+ m_keyChain.sign(*dskCert, kskCert->getName());
+ m_keyChain.addCertificateAsKeyDefault(*dskCert);
+ NDNS_LOG_INFO("Generated DSK: " << dskCert->getName().toUri());
+ }
+ else {
+ dskCert = m_keyChain.getCertificate(dskCertName);
+ dskName = dskCert->getPublicKeyName();
+ m_keyChain.setDefaultKeyNameForIdentity(dskName);
+ m_keyChain.setDefaultCertificateNameForKey(dskCert->getName());
+ }
+
+ //second add zone to the database
+ NDNS_LOG_INFO("Start adding new zone to data base");
+ addZone(zone);
+
+ //third create ID-cert
+ NDNS_LOG_INFO("Start creating DSK's ID-CERT");
+ addIdCert(zone, dskCert, cacheTtl);
+}
+
+void
+ManagementTool::deleteZone(const Name& zoneName)
+{
+ //check pre-conditions
+ Zone zone(zoneName);
+ if (!m_dbMgr.find(zone)) {
+ throw Error(zoneName.toUri() + " is not presented in the NDNS db");
+ }
+
+ //first remove all rrsets of this zone from local ndns database
+ std::vector<Rrset> rrsets = m_dbMgr.findRrsets(zone);
+ for (Rrset& rrset : rrsets) {
+ m_dbMgr.remove(rrset);
+ }
+
+ //second remove zone from local ndns database
+ removeZone(zone);
+
+ //third remove identity
+ m_keyChain.deleteIdentity(zoneName);
+}
+
+void
+ManagementTool::exportCertificate(const Name& certName, const std::string& outFile)
+{
+ //search for the certificate, start from KeyChain then local NDNS database
+ shared_ptr<IdentityCertificate> cert;
+ if (m_keyChain.doesCertificateExist(certName)) {
+ cert = m_keyChain.getCertificate(certName);
+ }
+ else {
+ shared_ptr<Regex> regex = make_shared<Regex>("(<>*)<KEY>(<>+)<ID-CERT><>");
+ if (regex->match(certName) != true) {
+ throw Error("Certificate name is illegal");
+ }
+ Name zoneName = regex->expand("\\1");
+ Name label = regex->expand("\\2");
+
+ Zone zone(zoneName);
+ Rrset rrset(&zone);
+ rrset.setLabel(label);
+ rrset.setType(label::CERT_RR_TYPE);
+ if (m_dbMgr.find(rrset)) {
+ Data data(rrset.getData());
+ cert = make_shared<IdentityCertificate>(data);
+ }
+ else {
+ throw Error("Cannot find the cert: " + certName.toUri());
+ }
+ }
+
+ if (outFile == DEFAULT_IO) {
+ ndn::io::save(*cert, std::cout);
+ }
+ else {
+ ndn::io::save(*cert, outFile);
+ NDNS_LOG_INFO("save cert to file: " << outFile);
+ }
+}
+
+void
+ManagementTool::addRrSet(const Name& zoneName,
+ const Name& label,
+ const name::Component& type,
+ NdnsType ndnsType,
+ const uint64_t version,
+ const std::vector<std::string>& contents,
+ const Name& inputDskCertName,
+ const time::seconds& ttl)
+{
+ // check pre-condition
+ Zone zone(zoneName);
+ if (!m_dbMgr.find(zone)) {
+ throw Error(zoneName.toUri() + " is not presented in the NDNS db");
+ }
+
+ if (ndnsType == NDNS_UNKNOWN) {
+ throw Error("The ndns type is unknown");
+ }
+
+ if (type == label::CERT_RR_TYPE) {
+ throw Error("It cannot handle ID-CERT rrset type");
+ }
+
+ // check strange rr type and ndns type combination
+ if (type == label::NS_RR_TYPE && ndnsType == NDNS_RAW) {
+ throw Error("NS cannot be of the type NDNS_RAW");
+ }
+
+ if (type == label::TXT_RR_TYPE && ndnsType != NDNS_RESP) {
+ throw Error("TXT cannot be of the type NDNS_RAW or NDNS_AUTH");
+ }
+
+ if (ndnsType == NDNS_RAW && contents.size() != 1) {
+ throw Error("NDNS_RAW must contain a single content element");
+ }
+
+ Name dskName;
+ Name dskCertName = inputDskCertName;
+ if (dskCertName == DEFAULT_CERT) {
+ dskName = m_keyChain.getDefaultKeyNameForIdentity(zoneName);
+ dskCertName = m_keyChain.getDefaultCertificateNameForKey(dskName);
+ }
+ else {
+ if (!matchCertificate(dskCertName, zoneName)) {
+ throw Error("Cannot verify certificate");
+ }
+ }
+
+ // set rrset
+ Rrset rrset(&zone);
+ rrset.setLabel(label);
+ rrset.setType(type);
+ if (ttl == DEFAULT_RR_TTL)
+ rrset.setTtl(zone.getTtl());
+ else
+ rrset.setTtl(ttl);
+
+ // set response
+ Response re;
+ re.setZone(zoneName);
+ re.setQueryType(label::NDNS_ITERATIVE_QUERY);
+ re.setRrLabel(label);
+ re.setRrType(type);
+ re.setNdnsType(ndnsType);
+
+ //set content according to ndns type
+ if (ndnsType == NDNS_RAW) {
+ Block tmp = ndn::dataBlock(ndn::tlv::Content, contents[0].c_str(), contents[0].length());
+ re.setAppContent(tmp);
+ }
+ else if (ndnsType != NDNS_AUTH) {
+ if (contents.empty()) {
+ re.addRr("");
+ }
+ else {
+ for (const auto& item : contents) {
+ re.addRr(item);
+ }
+ }
+ }
+
+ shared_ptr<Data> data = re.toData();
+ if (version != VERSION_USE_UNIX_TIMESTAMP) {
+ name::Component tmp = name::Component::fromVersion(version);
+ re.setVersion(tmp);
+ }
+ m_keyChain.sign(*data, dskCertName);
+
+ rrset.setVersion(re.getVersion());
+ rrset.setData(data->wireEncode());
+
+ if (m_dbMgr.find(rrset)) {
+ throw Error("Rrset with label=" + label.toUri() + " is already in local NDNS databse");
+ }
+ NDNS_LOG_INFO("Add rrset with zone-id: " << zone.getId() << " label: " << label << " type: "
+ << type);
+ m_dbMgr.insert(rrset);
+}
+
+void
+ManagementTool::addRrSet(const Name& zoneName,
+ const std::string& inFile,
+ const time::seconds& ttl,
+ const Name& inputDskCertName)
+{
+ //check precondition
+ Zone zone(zoneName);
+ if (!m_dbMgr.find(zone)) {
+ throw Error(zoneName.toUri() + " is not presented in the NDNS db");
+ }
+
+ Name dskName;
+ Name dskCertName = inputDskCertName;
+ if (dskCertName == DEFAULT_CERT) {
+ dskName = m_keyChain.getDefaultKeyNameForIdentity(zoneName);
+ dskCertName = m_keyChain.getDefaultCertificateNameForKey(dskName);
+ }
+ else {
+ if (!matchCertificate(dskCertName, zoneName)) {
+ throw Error("Cannot verify certificate");
+ }
+ }
+
+ if (inFile != DEFAULT_IO) {
+ boost::filesystem::path dir = boost::filesystem::path(inFile);
+ if (!boost::filesystem::exists(dir) || boost::filesystem::is_directory(dir)) {
+ throw Error("Data: " + inFile + " does not exist");
+ }
+ }
+
+ //first load the data
+ shared_ptr<Data> data;
+ if (inFile == DEFAULT_IO)
+ data = ndn::io::load<ndn::Data>(std::cin);
+ else
+ data = ndn::io::load<ndn::Data>(inFile);
+
+ //determine whether the data is a self-signed certificate
+ shared_ptr<Regex> regex1 = make_shared<Regex>("(<>*)<KEY>(<>+)<ID-CERT><>");
+ if (regex1->match(data->getName())) {
+ IdentityCertificate scert(*data);
+ Name keyName = scert.getPublicKeyName();
+ Name keyLocator = scert.getSignature().getKeyLocator().getName();
+
+ //if it is, extract the content and name from the data, and resign it using the dsk.
+ shared_ptr<Regex> regex2 = make_shared<Regex>("(<>*)<KEY>(<>+)<ID-CERT>");
+ BOOST_VERIFY(regex2->match(keyLocator) == true);
+ if (keyName == regex2->expand("\\1\\2")) {
+ shared_ptr<Data> pre = data;
+ Name name = pre->getName();
+ //check whether the name is legal or not. if not converting it to a legal name
+ if (zoneName != regex1->expand("\\1")) {
+ Name comp1 = regex1->expand("\\1").getSubName(zoneName.size());
+ Name comp2 = regex1->expand("\\2");
+ name = zoneName;
+ name.append("KEY");
+ name.append(comp1);
+ name.append(comp2);
+ name.append("ID-CERT");
+ name.append(pre->getName().get(-1));
+ }
+
+ data = make_shared<Data>();
+ data->setName(name);
+ data->setContent(pre->getContent());
+
+ m_keyChain.sign(*data, dskCertName);
+ }
+ }
+
+ // create response for the input data
+ Response re;
+ Name hint;
+ re.fromData(hint, zoneName, *data);
+ Name label = re.getRrLabel();
+ name::Component type = re.getRrType();
+
+ Rrset rrset(&zone);
+ rrset.setLabel(label);
+ rrset.setType(type);
+ if (ttl == DEFAULT_RR_TTL)
+ rrset.setTtl(zone.getTtl());
+ else
+ rrset.setTtl(ttl);
+ rrset.setVersion(re.getVersion());
+ rrset.setData(data->wireEncode());
+
+ if (m_dbMgr.find(rrset)) {
+ throw Error("Rrset with label=" + label.toUri() + " is already in local NDNS databse");
+ }
+ NDNS_LOG_INFO("Add rrset with zone-id: " << zone.getId() << " label: " << label << " type: "
+ << type);
+ m_dbMgr.insert(rrset);
+}
+
+void
+ManagementTool::listZone(const Name& zoneName, std::ostream& os, const bool printRaw) {
+ Zone zone(zoneName);
+ if (!m_dbMgr.find(zone)) {
+ os << "No record is found" << std::endl;
+ return;
+ }
+
+ //first output the zone name
+ os << "; Zone " << zoneName.toUri() << std::endl << std::endl;
+
+ //second output all rrsets
+ std::vector<Rrset> rrsets = m_dbMgr.findRrsets(zone);
+
+ //set width for different columns
+ size_t labelWidth = 0;
+ size_t ttlWidth = 0;
+ size_t typeWidth = 0;
+ for (Rrset& rrset : rrsets) {
+ Data data(rrset.getData());
+ Response re;
+ Name hint;
+ re.fromData(hint, zoneName, data);
+
+ if (rrset.getLabel().toUri().size() > labelWidth)
+ labelWidth = rrset.getLabel().toUri().size();
+
+ std::stringstream seconds;
+ seconds << rrset.getTtl().count();
+ if (seconds.str().size() > ttlWidth)
+ ttlWidth = seconds.str().size();
+
+ if (rrset.getType().toUri().size() > typeWidth)
+ typeWidth = rrset.getType().toUri().size();
+ }
+
+ //output
+ for (Rrset& rrset : rrsets) {
+ Data data(rrset.getData());
+ Response re;
+ Name hint;
+ re.fromData(hint, zoneName, data);
+ int iteration = re.getNdnsType() == NDNS_RAW || re.getNdnsType() == NDNS_AUTH ?
+ 1 : re.getRrs().size();
+ const std::vector<Block> &rrs = re.getRrs();
+
+ if (re.getNdnsType() != NDNS_RAW && re.getNdnsType() != NDNS_AUTH) {
+ os << "; rrset=" << rrset.getLabel().toUri()
+ << " type=" << rrset.getType().toUri()
+ << " version=" << rrset.getVersion().toUri()
+ << " signed-by=" << data.getSignature().getKeyLocator().getName().toUri()
+ << std::endl;
+ }
+
+ for (int i = 0; i < iteration; i++) {
+ os.setf(os.left);
+ os.width(labelWidth + 2);
+ os << rrset.getLabel().toUri();
+
+ os.width(ttlWidth + 2);
+ os << rrset.getTtl().count();
+
+ os.width(typeWidth + 2);
+ os << rrset.getType().toUri();
+
+ if (re.getNdnsType() != NDNS_RAW && re.getNdnsType() != NDNS_AUTH) {
+ using namespace CryptoPP;
+ if (rrset.getType() == label::TXT_RR_TYPE) {
+ os.write(reinterpret_cast<const char*>(rrs[i].value()), rrs[i].value_size());
+ os << std::endl;
+ }
+ else if (rrset.getType() == label::NS_RR_TYPE) {
+ //TODO output the NS data once we have it
+ os << std::endl;
+ }
+ else {
+ StringSource ss(rrs[i].wire(), rrs[i].size(), true,
+ new Base64Encoder(new FileSink(os), true, 64));
+ }
+ }
+ }
+
+ if (re.getNdnsType() == NDNS_RAW || re.getNdnsType() == NDNS_AUTH) {
+ os.width();
+ os << "; content-type=" << re.getNdnsType()
+ << " version=" << rrset.getVersion().toUri()
+ << " signed-by=" << data.getSignature().getKeyLocator().getName().toUri();
+ os << std::endl;
+
+ if (printRaw && re.getNdnsType() == NDNS_RAW) {
+ using namespace CryptoPP;
+ std::stringstream sstream;
+ StringSource ss(re.getAppContent().wire(), re.getAppContent().size(), true,
+ new Base64Encoder(new FileSink(sstream), true, 64));
+
+ std::string content = sstream.str();
+ std::string delimiter = "\n";
+ size_t pos = 0;
+ std::string token;
+ while ((pos = content.find(delimiter)) != std::string::npos) {
+ token = content.substr(0, pos);
+ os << "; " << token << std::endl;
+ content.erase(0, pos + delimiter.length());
+ }
+
+ os << std::endl;
+ }
+ }
+ else {
+ os << std::endl;
+ }
+ }
+}
+
+void
+ManagementTool::listAllZones(std::ostream& os) {
+ std::vector<Zone> zones = m_dbMgr.listZones();
+
+ size_t nameWidth = 0;
+ for (const Zone& zone : zones) {
+ if (zone.getName().toUri().size() > nameWidth)
+ nameWidth = zone.getName().toUri().size();
+ }
+
+ for (const Zone& zone : zones) {
+ os.setf(os.left);
+ os.width(nameWidth + 2);
+ os << zone.getName().toUri();
+
+ os << "; default-ttl=" << zone.getTtl().count();
+ os << " default-key=" << m_keyChain.getDefaultKeyNameForIdentity(zone.getName());
+ os << " default-certificate="
+ << m_keyChain.getDefaultCertificateNameForIdentity(zone.getName());
+ os << std::endl;
+ }
+}
+
+void
+ManagementTool::removeRrSet(const Name& zoneName, const Name& label, const name::Component& type)
+{
+ Zone zone(zoneName);
+ Rrset rrset(&zone);
+ rrset.setLabel(label);
+ rrset.setType(type);
+
+ if (!m_dbMgr.find(rrset)) {
+ return;
+ }
+ NDNS_LOG_INFO("Remove rrset with zone-id: " << zone.getId() << " label: " << label << " type: "
+ << type);
+ m_dbMgr.remove(rrset);
+}
+
+void
+ManagementTool::getRrSet(const Name& zoneName,
+ const Name& label,
+ const name::Component& type,
+ std::ostream& os)
+{
+ Zone zone(zoneName);
+ Rrset rrset(&zone);
+ rrset.setLabel(label);
+ rrset.setType(type);
+
+ if (!m_dbMgr.find(rrset)) {
+ os << "No record is found" << std::endl;
+ return;
+ }
+
+ using namespace CryptoPP;
+ StringSource ss(rrset.getData().wire(), rrset.getData().size(), true,
+ new Base64Encoder(new FileSink(os), true, 64));
+}
+
+void
+ManagementTool::addIdCert(Zone& zone, shared_ptr<IdentityCertificate> cert,
+ const time::seconds& ttl)
+{
+ Rrset rrset(&zone);
+ size_t size = zone.getName().size();
+ Name label = cert->getName().getSubName(size + 1, cert->getName().size() - size - 3);
+ rrset.setLabel(label);
+ rrset.setType(label::CERT_RR_TYPE);
+ rrset.setTtl(ttl);
+ rrset.setVersion(cert->getName().get(-1));
+ rrset.setData(cert->wireEncode());
+
+ if (m_dbMgr.find(rrset)) {
+ throw Error("ID-CERT with label=" + label.toUri() +
+ " is already presented in local NDNS databse");
+ }
+ NDNS_LOG_INFO("Add rrset with zone-id: " << zone.getId() << " label: " << label << " type: "
+ << label::CERT_RR_TYPE);
+ m_dbMgr.insert(rrset);
+}
+
+void
+ManagementTool::addZone(Zone& zone)
+{
+ if (m_dbMgr.find(zone)) {
+ throw Error("Zone with Name=" + zone.getName().toUri() +
+ " is already presented in local NDNS databse");
+ }
+ NDNS_LOG_INFO("Add zone with Name: " << zone.getName().toUri());
+ m_dbMgr.insert(zone);
+}
+
+void
+ManagementTool::removeZone(Zone& zone)
+{
+ if (!m_dbMgr.find(zone)) {
+ return;
+ }
+ NDNS_LOG_INFO("Remove zone with Name: " << zone.getName().toUri());
+ m_dbMgr.remove(zone);
+}
+
+bool
+ManagementTool::matchCertificate(const Name& certName, const Name& identity)
+{
+ if (!m_keyChain.doesCertificateExist(certName)) {
+ NDNS_LOG_WARN(certName.toUri() << " is not presented in KeyChain");
+ return false;
+ }
+
+ //check its public key information
+ shared_ptr<IdentityCertificate> cert = m_keyChain.getCertificate(certName);
+ Name keyName = cert->getPublicKeyName();
+
+ if (!identity.isPrefixOf(keyName) || identity.size()!=keyName.size()-1) {
+ NDNS_LOG_WARN(keyName.toUri() << " is not a key of " << identity.toUri());
+ return false;
+ }
+
+ if (!m_keyChain.doesKeyExistInTpm(keyName, KEY_CLASS_PRIVATE)) {
+ NDNS_LOG_WARN("Private key: " << keyName.toUri() << " is not presented in KeyChain");
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace ndns
+} // namespace ndn