tools: add dhtnet-certmgr

Certificate manager: generate and load certificate/identity

Change-Id: I920834133b5f78985833ee4043b5aa4562211197
diff --git a/CMakeLists.txt b/CMakeLists.txt
index e6efdb5..7b1b1e3 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -247,7 +247,8 @@
     add_executable(dnc
         tools/dnc/main.cpp
         tools/dnc/dnc.cpp
-        tools/common.cpp)
+        tools/common.cpp
+        tools/dhtnet_crtmgr/dhtnet_crtmgr.cpp)
     target_link_libraries(dnc PRIVATE dhtnet fmt::fmt yaml-cpp)
     target_include_directories(dnc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/tools)
     install(TARGETS dnc RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
@@ -255,7 +256,8 @@
     add_executable(dsh
         tools/dsh/main.cpp
         tools/dsh/dsh.cpp
-        tools/common.cpp)
+        tools/common.cpp
+        tools/dhtnet_crtmgr/dhtnet_crtmgr.cpp)
     target_link_libraries(dsh PRIVATE dhtnet fmt::fmt yaml-cpp)
     target_include_directories(dsh PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/tools)
     install(TARGETS dsh RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
@@ -263,7 +265,8 @@
     add_executable(dvpn
         tools/dvpn/main.cpp
         tools/dvpn/dvpn.cpp
-        tools/common.cpp)
+        tools/common.cpp
+        tools/dhtnet_crtmgr/dhtnet_crtmgr.cpp)
     target_link_libraries(dvpn PRIVATE dhtnet fmt::fmt yaml-cpp)
     target_include_directories(dvpn PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/tools)
     install(TARGETS dvpn RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
@@ -280,6 +283,13 @@
     target_include_directories(upnpctrl PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/tools)
     install(TARGETS upnpctrl RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
 
+    add_executable(dhtnet-crtmgr
+        tools/dhtnet_crtmgr/main.cpp
+        tools/dhtnet_crtmgr/dhtnet_crtmgr.cpp)
+    target_link_libraries(dhtnet-crtmgr PRIVATE dhtnet fmt::fmt)
+    target_include_directories(dhtnet-crtmgr PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/tools)
+    install(TARGETS dhtnet-crtmgr RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
+
     install(FILES
         tools/dnc/dnc.1
         tools/dsh/dsh.1
diff --git a/tools/common.cpp b/tools/common.cpp
index 7d338e5..76597a8 100644
--- a/tools/common.cpp
+++ b/tools/common.cpp
@@ -29,41 +29,6 @@
 
 namespace dhtnet {
 
-dht::crypto::Identity
-loadIdentity(const std::filesystem::path& path_id){
-    try {
-        for (const auto& path_id : std::filesystem::directory_iterator(path_id)) {
-            auto p = path_id.path();
-            if (p.extension() == ".pem") {
-                auto privateKey = std::make_unique<dht::crypto::PrivateKey>(fileutils::loadFile(p));
-                auto certificate = std::make_unique<dht::crypto::Certificate>(
-                    fileutils::loadFile(p.replace_extension(".crt")));
-                return dht::crypto::Identity(std::move(privateKey), std::move(certificate));
-            }
-        }
-    } catch (const std::exception& e) {}
-    return {};
-}
-dht::crypto::Identity
-loadIdentity(const std::filesystem::path& path_id, const std::filesystem::path& path_ca)
-{
-    if (!std::filesystem::exists(path_id)) {
-        std::filesystem::create_directory(path_id);
-    }
-    // Load identity
-    auto id = loadIdentity(path_id);
-    if (!id.first or !id.second) {
-        // Load CA
-        auto ca_id = loadIdentity(path_ca);
-        if (!ca_id.first or !ca_id.second)
-            ca_id = dht::crypto::generateIdentity("dhtnet");
-    id = dht::crypto::generateIdentity("dhtnet", ca_id);
-    fmt::print("Generated new identity: {}\n", id.first->getPublicKey().getId());
-    dht::crypto::saveIdentity(id, path_id / "id");
-}
-    return id;
-}
-
 std::unique_ptr<ConnectionManager::Config>
 connectionManagerConfig(const std::filesystem::path& path,
                         dht::crypto::Identity identity,
diff --git a/tools/common.h b/tools/common.h
index ea5a278..67672d0 100644
--- a/tools/common.h
+++ b/tools/common.h
@@ -25,13 +25,6 @@
 using Buffer = std::shared_ptr<std::vector<uint8_t>>;
 constexpr size_t BUFFER_SIZE = 64 * 1024;
 
-/**
- * Attempt to retrieve the identity from the .ssh directory, and if none is found, generate a new
- * certification.
- * @return dht::crypto::Identity
- */
-dht::crypto::Identity loadIdentity(const std::filesystem::path& path_id, const std::filesystem::path& path_ca);
-// add certstore to the config
 std::unique_ptr<ConnectionManager::Config> connectionManagerConfig(
     const std::filesystem::path& path,
     dht::crypto::Identity identity,
diff --git a/tools/dhtnet_crtmgr/dhtnet_crtmgr.cpp b/tools/dhtnet_crtmgr/dhtnet_crtmgr.cpp
new file mode 100644
index 0000000..3d3d94a
--- /dev/null
+++ b/tools/dhtnet_crtmgr/dhtnet_crtmgr.cpp
@@ -0,0 +1,51 @@
+/*
+ *  Copyright (C) 2023 Savoir-faire Linux Inc.
+ *
+ *  This program 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.
+ *
+ *  This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "dhtnet_crtmgr.h"
+#include "fileutils.h"
+
+#include <opendht/crypto.h>
+
+
+namespace dhtnet {
+
+dht::crypto::Identity
+loadIdentity(const std::filesystem::path& privatekey, const std::filesystem::path& cert)
+{
+    // check files exists
+    if (!std::filesystem::exists(privatekey) or !std::filesystem::exists(cert))
+    {
+        fmt::print(stderr, "Error: missing identity files\n");
+        return {};
+    }
+
+    // Load identity
+    auto privateKey = std::make_unique<dht::crypto::PrivateKey>(fileutils::loadFile(privatekey));
+    auto certificate = std::make_unique<dht::crypto::Certificate>(fileutils::loadFile(cert));
+    return dht::crypto::Identity(std::move(privateKey), std::move(certificate));
+}
+
+// generate a new identity
+dht::crypto::Identity generateIdentity(const std::filesystem::path& path_id, const std::string& name, const dht::crypto::Identity& ca)
+{
+    auto identity = dht::crypto::generateIdentity(name, ca);
+    if (!std::filesystem::exists(path_id))
+        std::filesystem::create_directories(path_id);
+    dht::crypto::saveIdentity(identity, path_id / name);
+    return identity;
+}
+} // namespace dhtnet
diff --git a/tools/dhtnet_crtmgr/dhtnet_crtmgr.h b/tools/dhtnet_crtmgr/dhtnet_crtmgr.h
new file mode 100644
index 0000000..16fe9e9
--- /dev/null
+++ b/tools/dhtnet_crtmgr/dhtnet_crtmgr.h
@@ -0,0 +1,33 @@
+/*
+ *  Copyright (C) 2023 Savoir-faire Linux Inc.
+ *
+ *  This program 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.
+ *
+ *  This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+#include <opendht/crypto.h>
+#include "fileutils.h"
+
+namespace dhtnet {
+
+/**
+ * Get the private key and certificate from the given paths.
+ * @return dht::crypto::Identity
+ */
+dht::crypto::Identity loadIdentity(const std::filesystem::path& path_pkey, const std::filesystem::path& path_cert);
+
+/**
+ * Generate a new identity.
+ */
+dht::crypto::Identity generateIdentity(const std::filesystem::path& path_id, const std::string& name, const dht::crypto::Identity& ca = {});
+
+}
\ No newline at end of file
diff --git a/tools/dhtnet_crtmgr/main.cpp b/tools/dhtnet_crtmgr/main.cpp
new file mode 100644
index 0000000..f27d5bb
--- /dev/null
+++ b/tools/dhtnet_crtmgr/main.cpp
@@ -0,0 +1,162 @@
+/*
+ *  Copyright (C) 2023 Savoir-faire Linux Inc.
+ *
+ *  This program 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.
+ *
+ *  This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+#include "dhtnet_crtmgr.h"
+
+
+#include <iostream>
+#include <unistd.h>
+#include <getopt.h>
+#if __has_include(<fmt/std.h>)
+#include <fmt/std.h>
+#else
+#include <fmt/ostream.h>
+#endif
+
+
+struct dhtnet_crtmgr_params
+{
+    bool help {false};
+    bool version {false};
+    std::filesystem::path ca {};
+    std::filesystem::path id {};
+    std::filesystem::path privatekey {};
+    bool pkid {false};
+    std::string name {};
+    bool setup {false};
+};
+static const constexpr struct option long_options[]
+    = {{"help", no_argument, nullptr, 'h'},
+       {"version", no_argument, nullptr, 'v'},
+       {"CA", required_argument, nullptr, 'c'},
+       {"id", required_argument, nullptr, 'i'},
+       {"privatekey", required_argument, nullptr, 'p'},
+       {"name", required_argument, nullptr, 'n'},
+       {"pkid", no_argument, nullptr, 'g'},
+       {"setup", no_argument, nullptr, 's'},
+       {nullptr, 0, nullptr, 0}};
+
+dhtnet_crtmgr_params
+parse_args(int argc, char** argv)
+{
+    dhtnet_crtmgr_params params;
+    int opt;
+    while ((opt = getopt_long(argc, argv, "hgsv:c:i:p:n:", long_options, nullptr)) != -1) {
+        switch (opt) {
+        case 'h':
+            params.help = true;
+            break;
+        case 'v':
+            params.version = true;
+            break;
+        case 'c':
+            params.ca = optarg;
+            break;
+        case 'i':
+            params.id = optarg;
+            break;
+        case 'p':
+            params.privatekey = optarg;
+            break;
+        case 'g':
+            params.pkid = true;
+            break;
+        case 'n':
+            params.name = optarg;
+            break;
+        case 's':
+            params.setup = true;
+            break;
+        default:
+            std::cerr << "Invalid option" << std::endl;
+            exit(EXIT_FAILURE);
+        }
+    }
+
+    if (params.id.empty() && !params.pkid) {
+        std::cerr << "Error: The path to save the generated identity is not provided.\n Please specify the path for saving the generated identity using the -i option.\n";        exit(EXIT_FAILURE);
+    }
+    return params;
+}
+
+
+int
+main(int argc, char** argv)
+{
+    auto params = parse_args(argc, argv);
+
+    if (params.help) {
+        fmt::print("Usage: dhtnet-crtmgr [options]\n"
+                "\nOptions:\n"
+                "  -h, --help            Display this help message and then exit.\n"
+                "  -v, --version         Show the version of the program.\n"
+                "  -p, --privatekey      Provide the path to the private key as an argument.\n"
+                "  -c, --CA              Provide the path to the Certificate Authority as an argument.\n"
+                "  -i, --id              Provide the path where the generated identity should be saved as an argument.\n"
+                "  -g, --pkid            Display the publickey id used by the server dnc.\n"
+                "  -n, --name            Provide the name of the identity to be generated.\n"
+                "  -s, --setup           Create an CA and an id.\n");
+        return EXIT_SUCCESS;
+    }
+
+    if (params.version) {
+        fmt::print("dhtnet-crtmgr v1.0\n");
+        return EXIT_SUCCESS;
+    }
+    // check if the public key id is requested
+    if (params.pkid) {
+        if (params.ca.empty() || params.privatekey.empty()) {
+            fmt::print(stderr, "Error: The path to the private key and the Certificate Authority is not provided.\n Please specify the path for the private key and the Certificate Authority using the -p and -c options.\n");
+            exit(EXIT_FAILURE);
+        }
+        auto identity = dhtnet::loadIdentity(params.privatekey, params.ca);
+        fmt::print("Public key id: {}\n", identity.second->getId());
+        return EXIT_SUCCESS;
+    }
+
+    // check if the setup is requested
+    if (params.setup) {
+        // create CA  with name ca-server
+        std::filesystem::path path_ca = params.id / "CA";
+        auto ca = dhtnet::generateIdentity(path_ca, "ca-server");
+        fmt::print("Generated CA in {}: {} {}\n", path_ca, "ca-server", ca.second->getId());
+        // create identity with name id-server
+        std::filesystem::path path_id = params.id / "id";
+        auto identity = dhtnet::generateIdentity(path_id, "id-server", ca);
+        fmt::print("Generated identity in {}: {} {}\n", path_id,"id-server", identity.second->getId());
+        return EXIT_SUCCESS;
+    }
+
+    if (params.ca.empty() || params.privatekey.empty()) {
+        if (params.name.empty()) {
+            auto ca = dhtnet::generateIdentity(params.id, "ca");
+            fmt::print("Generated CA in {}: {} {}\n", params.id, "ca", ca.second->getId());
+        }else{
+        auto ca = dhtnet::generateIdentity(params.id, params.name);
+        fmt::print("Generated CA in {}: {} {}\n", params.id, params.name, ca.second->getId());
+        }
+    }else{
+        auto ca = dhtnet::loadIdentity(params.privatekey, params.ca);
+        if (params.name.empty()) {
+            auto id = dhtnet::generateIdentity(params.id, "id", ca);
+            fmt::print("Generated identity in {}: {} {}\n", params.id, "id", id.second->getId());
+        }else{
+            auto id = dhtnet::generateIdentity(params.id, params.name, ca);
+            fmt::print("Generated identity in {}: {} {}\n", params.id, params.name, id.second->getId());
+        }
+    }
+    return EXIT_SUCCESS;
+}
diff --git a/tools/dnc/main.cpp b/tools/dnc/main.cpp
index 363e05e..02debd5 100644
--- a/tools/dnc/main.cpp
+++ b/tools/dnc/main.cpp
@@ -16,6 +16,7 @@
  */
 #include "dnc.h"
 #include "common.h"
+#include "dhtnet_crtmgr/dhtnet_crtmgr.h"
 
 #include <string>
 #include <vector>
diff --git a/tools/dsh/main.cpp b/tools/dsh/main.cpp
index 19ff4e5..0e91e91 100644
--- a/tools/dsh/main.cpp
+++ b/tools/dsh/main.cpp
@@ -16,6 +16,8 @@
  */
 #include "dsh.h"
 #include "../common.h"
+#include "dhtnet_crtmgr/dhtnet_crtmgr.h"
+
 #include <string>
 #include <vector>
 #include <iostream>
diff --git a/tools/dvpn/main.cpp b/tools/dvpn/main.cpp
index 5b19459..8cbeebc 100644
--- a/tools/dvpn/main.cpp
+++ b/tools/dvpn/main.cpp
@@ -16,6 +16,7 @@
  */
 #include "dvpn.h"
 #include "common.h"
+#include "dhtnet_crtmgr/dhtnet_crtmgr.h"
 
 #include <string>
 #include <vector>