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