blob: 4705e94492be65111a9ea51adf6effcfb14ec6ad [file] [log] [blame]
Amna423b25b2024-02-06 13:21:19 -05001/*
2 * Copyright (C) 2023 Savoir-faire Linux Inc.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 */
17#include "dhtnet_crtmgr.h"
Amnac03c4b52024-07-24 17:57:25 -040018#include"common.h"
Amna423b25b2024-02-06 13:21:19 -050019
20#include <iostream>
Louis Maillard9737f532024-07-23 14:26:33 -040021#include <fstream>
Amna423b25b2024-02-06 13:21:19 -050022#include <unistd.h>
23#include <getopt.h>
24#if __has_include(<fmt/std.h>)
25#include <fmt/std.h>
26#else
27#include <fmt/ostream.h>
28#endif
29
30
31struct dhtnet_crtmgr_params
32{
33 bool help {false};
34 bool version {false};
35 std::filesystem::path ca {};
Amna77e27dc2024-07-24 13:09:40 -040036 std::filesystem::path output {};
Amna423b25b2024-02-06 13:21:19 -050037 std::filesystem::path privatekey {};
Amna77e27dc2024-07-24 13:09:40 -040038 bool identifier {false};
Amna423b25b2024-02-06 13:21:19 -050039 std::string name {};
40 bool setup {false};
Louis Maillard9737f532024-07-23 14:26:33 -040041 bool interactive {false};
Amna423b25b2024-02-06 13:21:19 -050042};
43static const constexpr struct option long_options[]
44 = {{"help", no_argument, nullptr, 'h'},
45 {"version", no_argument, nullptr, 'v'},
Amna77e27dc2024-07-24 13:09:40 -040046 {"certificate", required_argument, nullptr, 'c'},
47 {"output", required_argument, nullptr, 'o'},
Amna423b25b2024-02-06 13:21:19 -050048 {"privatekey", required_argument, nullptr, 'p'},
49 {"name", required_argument, nullptr, 'n'},
Amna77e27dc2024-07-24 13:09:40 -040050 {"identifier", no_argument, nullptr, 'a'},
Amna423b25b2024-02-06 13:21:19 -050051 {"setup", no_argument, nullptr, 's'},
Louis Maillard9737f532024-07-23 14:26:33 -040052 {"interactive", no_argument, nullptr, 'i'},
Amna423b25b2024-02-06 13:21:19 -050053 {nullptr, 0, nullptr, 0}};
54
55dhtnet_crtmgr_params
56parse_args(int argc, char** argv)
57{
58 dhtnet_crtmgr_params params;
59 int opt;
Amna77e27dc2024-07-24 13:09:40 -040060 while ((opt = getopt_long(argc, argv, "hvasic:o:p:n:", long_options, nullptr)) != -1) {
Amna423b25b2024-02-06 13:21:19 -050061 switch (opt) {
62 case 'h':
63 params.help = true;
64 break;
65 case 'v':
66 params.version = true;
67 break;
68 case 'c':
69 params.ca = optarg;
70 break;
Amnab5f0a682024-02-08 16:21:12 -050071 case 'o':
Amna77e27dc2024-07-24 13:09:40 -040072 params.output = optarg;
Amna423b25b2024-02-06 13:21:19 -050073 break;
74 case 'p':
75 params.privatekey = optarg;
76 break;
Louis Maillardd887d3b2024-07-23 14:29:31 -040077 case 'a':
Amna77e27dc2024-07-24 13:09:40 -040078 params.identifier = true;
Amna423b25b2024-02-06 13:21:19 -050079 break;
80 case 'n':
81 params.name = optarg;
82 break;
83 case 's':
84 params.setup = true;
85 break;
Louis Maillard9737f532024-07-23 14:26:33 -040086 case 'i':
87 params.interactive = true;
88 break;
Amna423b25b2024-02-06 13:21:19 -050089 default:
90 std::cerr << "Invalid option" << std::endl;
91 exit(EXIT_FAILURE);
92 }
93 }
94
Amna77e27dc2024-07-24 13:09:40 -040095 if (params.output.empty() && !params.identifier && !params.help && !params.version && !params.interactive) {
Louis Maillard2da67252024-07-23 14:17:16 -040096 std::cerr << "Error: The path to save the generated certificate is not provided.\n Please specify the path using the -o option.\n";
Amnab5f0a682024-02-08 16:21:12 -050097 exit(EXIT_FAILURE);
Amna423b25b2024-02-06 13:21:19 -050098 }
99 return params;
100}
101
102
Louis Maillard9737f532024-07-23 14:26:33 -0400103int create_yaml_config(std::filesystem::path file, std::filesystem::path certificate, std::filesystem::path privateKey, bool is_client)
104{
105 std::ofstream yaml_file (file);
106 if (yaml_file.is_open()) {
107 yaml_file << "# The bootstrap node serves as the entry point to the DHT network.\n";
108 yaml_file << "# By default, bootstrap.jami.net is configured for the public DHT network and should be used for personal use only.\n";
109 yaml_file << "# For production environments, it is recommended to set up your own bootstrap node to establish your own DHT network.\n";
110 yaml_file << "# Documentation: https://docs.jami.net/en_US/user/lan-only.html#boostraping\n";
111 yaml_file << "bootstrap: \"bootstrap.jami.net\"\n";
112
113 yaml_file << "\n# TURN server is used as a fallback for connections if the NAT block all possible connections.\n";
114 yaml_file << "# By default is turn.jami.net (which uses coturn) but can be any TURN.\n";
115 yaml_file << "# Developer must set up their own TURN server.\n";
116 yaml_file << "# Documentation: https://docs.jami.net/en_US/developer/going-further/setting-up-your-own-turn-server.html\n";
117 yaml_file << "turn_host: \"turn.jami.net\"\n";
118 yaml_file << "turn_user: \"ring\"\n";
119 yaml_file << "turn_pass: \"ring\"\n";
120 yaml_file << "turn_realm: \"ring\"\n";
Louis Maillardf81d36b2024-07-23 14:38:28 -0400121
122 yaml_file << "\n# When verbose is set to true, the server logs all incoming connections\n";
123 yaml_file << "verbose: false\n";
124
Amna45db7762024-07-24 18:33:48 -0400125 yaml_file << "\n# If true, will send request to use UPNP if available\n";
126 yaml_file << "enable_upnp: true\n";
127
Louis Maillard9737f532024-07-23 14:26:33 -0400128 yaml_file << "\n# On server, identities are saved in /etc/dhtnet/id/\n";
129 yaml_file << "# On client, they are generaly saved in ~/.dnc/\n";
130 yaml_file << "certificate: " << certificate << "\n";
131 yaml_file << "privateKey: " << privateKey << "\n";
Louis Maillardf81d36b2024-07-23 14:38:28 -0400132 if (is_client) {
133 yaml_file << "\n# When dnc server receives connexions, it forwards them to service at specified IP:port requested by CLIENT\n";
134 yaml_file << "# By default, it forwards them to SSH server running on localhost at port 22\n";
135 yaml_file << "ip: \"127.0.0.1\"\n";
136 yaml_file << "port: 22\n";
137 } else {
Louis Maillard9737f532024-07-23 14:26:33 -0400138 yaml_file << "\n# When anonymous is set to true, the server accepts any connection without checking CA\n";
139 yaml_file << "# When anonymous is set to false, the server allows only connection which are issued by the same CA as the server\n";
140 yaml_file << "anonymous: false\n";
Amna2ee14f02024-07-24 15:15:55 -0400141
142 yaml_file << "\n# List of authorized services\n";
143 yaml_file << "# Each service is defined by an IP and a port\n";
144 yaml_file << "# If no authorized services are defined, the server will accept any connection.\n";
145 yaml_file << "authorized_services:\n";
146 yaml_file << " - ip: \"127.0.0.1\"\n";
147 yaml_file << " port: 22\n";
148 yaml_file << " # - ip: \"127.0.0.1\"\n";
149 yaml_file << " # port: 80\n";
150 yaml_file << " # - ip: \"127.0.0.1\"\n";
151 yaml_file << " # port: 443\n";
Louis Maillard9737f532024-07-23 14:26:33 -0400152 }
Louis Maillard9737f532024-07-23 14:26:33 -0400153 yaml_file.close();
Amnac03c4b52024-07-24 17:57:25 -0400154 Log("Configuration file created in {}\n", file);
Louis Maillard9737f532024-07-23 14:26:33 -0400155 } else {
156 fmt::print(stderr, "Error: Could not create configuration file {}.\n", file);
157 return 1;
158 }
159 return 0;
160}
161
162int configure_ssh_config(std::filesystem::path yaml_config)
163{
164 std::filesystem::path home_dir = getenv("HOME");
165 if (home_dir.empty()) {
166 fmt::print(stderr, "Error: HOME environment variable is not set. Cannot configure SSH.\n");
167 return 1;
168 }
169 std::filesystem::path ssh_dir = home_dir / ".ssh";
170 if (!std::filesystem::exists(ssh_dir)) {
171 fmt::print(stderr, "Error: {} folder doesn't exist. Install and configure ssh client first.\n", ssh_dir);
172 return 1;
173 }
174 std::filesystem::path ssh_config = ssh_dir / "config";
175 if (std::filesystem::exists(ssh_config)) {
176 std::ifstream ssh_file(ssh_config);
177 std::string line;
178 while (std::getline(ssh_file, line)) {
179 if (line.find("Host dnc") != std::string::npos) {
Amnac03c4b52024-07-24 17:57:25 -0400180 Log("Info: dnc configuration already exists in ssh config. File is left untouched\n");
Louis Maillard9737f532024-07-23 14:26:33 -0400181 return 0;
182 }
183 }
184 }
185 std::ofstream ssh_file(ssh_config, std::ios::app);
186 if (ssh_file.is_open()) {
187 ssh_file << "\nHost dnc/*\n";
188 ssh_file << " ProxyCommand dnc -d " << yaml_config << " $(basename %h)\n";
189 ssh_file.close();
Amnac03c4b52024-07-24 17:57:25 -0400190 Log("SSH configuration added to {}\n", ssh_config);
Louis Maillard9737f532024-07-23 14:26:33 -0400191 } else {
192 fmt::print(stderr, "Error: Could not open ssh config file.\n");
193 return 1;
194 }
195 return 0;
196}
197
198// https://en.cppreference.com/w/cpp/string/byte/tolower
199std::string str_tolower(std::string s)
200{
201 std::transform(s.begin(), s.end(), s.begin(),
202 [](unsigned char c){ return std::tolower(c); } // correct
203 );
204 return s;
205}
206
Amna423b25b2024-02-06 13:21:19 -0500207int
208main(int argc, char** argv)
209{
210 auto params = parse_args(argc, argv);
211
212 if (params.help) {
Amnac03c4b52024-07-24 17:57:25 -0400213 Log("Usage: dhtnet-crtmgr [options]\n"
Amna423b25b2024-02-06 13:21:19 -0500214 "\nOptions:\n"
Louis Maillardd887d3b2024-07-23 14:29:31 -0400215 " -h, --help Display this help message and then exit.\n"
216 " -v, --version Show the version of the program.\n"
Amna77e27dc2024-07-24 13:09:40 -0400217 " -p, --privatekey [FILE] Provide the path to the private key as an argument.\n"
218 " -c, --certificate [FILE] Provide the path to the certificate as an argument.\n"
Louis Maillardd887d3b2024-07-23 14:29:31 -0400219 " -o, --output [FOLDER] Provide the path where the generated certificate should be saved as an argument.\n"
220 " -a, --identifier Display the user identifier.\n"
221 " -n, --name [NAME] Provide the name of the certificate to be generated.\n"
222 " -s, --setup Create an CA and a certificate.\n"
223 " -i, --interactive Interactively create and setup identities.\n");
Amna423b25b2024-02-06 13:21:19 -0500224 return EXIT_SUCCESS;
225 }
226
227 if (params.version) {
Amnac03c4b52024-07-24 17:57:25 -0400228 Log("dhtnet-crtmgr v1.0\n");
Amna423b25b2024-02-06 13:21:19 -0500229 return EXIT_SUCCESS;
230 }
231 // check if the public key id is requested
Amna77e27dc2024-07-24 13:09:40 -0400232 if (params.identifier) {
Amna423b25b2024-02-06 13:21:19 -0500233 if (params.ca.empty() || params.privatekey.empty()) {
Louis Maillardd887d3b2024-07-23 14:29:31 -0400234 fmt::print(stderr, "Error: The path to the private key and the certificate is not provided.\n Please specify the path for the private key and the certificate using the -p and -c options.\n");
Amna423b25b2024-02-06 13:21:19 -0500235 exit(EXIT_FAILURE);
236 }
237 auto identity = dhtnet::loadIdentity(params.privatekey, params.ca);
Amnac03c4b52024-07-24 17:57:25 -0400238 Log("Public key id: {}\n", identity.second->getId());
Amna423b25b2024-02-06 13:21:19 -0500239 return EXIT_SUCCESS;
240 }
241
Louis Maillard9737f532024-07-23 14:26:33 -0400242 // check if the interactive mode is requested
243 if (params.interactive) {
244 // Ask user if he want to setup client or server config
245 std::string usage = "";
246 do {
Amnac03c4b52024-07-24 17:57:25 -0400247 Log("Generate identity for server or client? [(C)lient/(s)erver] (default: client): ");
Louis Maillard9737f532024-07-23 14:26:33 -0400248 std::getline(std::cin, usage);
249 usage = str_tolower(usage);
250 if (usage == "s") usage = "server";
251 if (usage == "c") usage = "client";
252 if (usage.empty()) usage = "client";
253 } while (usage != "server" && usage != "client");
254
255 // In case user select client mode, Ask if we should sign using server CA (required for anonymous: false)
256 std::string use_server_ca = "";
257 if (usage == "client") {
258 do {
Amnac03c4b52024-07-24 17:57:25 -0400259 Log("Sign client certificate using server CA? [Y/n] (default: yes): ");
Louis Maillard9737f532024-07-23 14:26:33 -0400260 std::getline(std::cin, use_server_ca);
261 use_server_ca = str_tolower(use_server_ca);
262 if (use_server_ca == "y") use_server_ca = "yes";
263 if (use_server_ca == "n") use_server_ca = "no";
264 if (use_server_ca.empty()) use_server_ca = "yes";
265 } while (use_server_ca != "yes" && use_server_ca != "no");
266 }
267
268 // Before asking for save folder, pre-compute default locations
269 std::filesystem::path home_dir = getenv("HOME");
270 if (home_dir.empty()) home_dir = "/tmp/.dnc";
271 else if (usage == "server") home_dir = "/etc/dhtnet";
272 else home_dir = home_dir / ".dnc";
273
274 std::string input_folder;
275
276 // Ask where to store identity files
277 std::filesystem::path folder;
Amnac03c4b52024-07-24 17:57:25 -0400278 Log("Enter the path to save identities and config [{}]: ", home_dir);
Louis Maillard9737f532024-07-23 14:26:33 -0400279 std::getline(std::cin, input_folder);
280 if (input_folder.empty()) {
281 folder = home_dir;
282 } else {
283 folder = input_folder;
284 }
285 folder = std::filesystem::absolute(folder);
Amnac8354042024-07-24 15:31:32 -0400286
287 std::error_code e;
288 std::filesystem::create_directories(folder, e);
289 if (e) {
290 fmt::print(stderr, "Error: Could not create directory {}. {}\n", folder, e.message());
291 return EXIT_FAILURE;
292 }
Louis Maillard9737f532024-07-23 14:26:33 -0400293
294 if (usage == "client") {
295 // Use existing CA or generate new CA
296 dht::crypto::Identity ca;
297 if (use_server_ca == "yes") {
298 try {
299 std::filesystem::path server_ca = "/etc/dhtnet/CA";
300 ca = dhtnet::loadIdentity(server_ca / "ca-server.pem", server_ca / "ca-server.crt");
301 if (!ca.first || !ca.second) {
302 throw std::runtime_error("Failed to load server CA");
303 }
304 }
305 catch (const std::exception& e) {
306 fmt::print(stderr, "Error: Could not load server CA. Please generate server CA first.\n");
307 return EXIT_FAILURE;
308 }
309 } else {
310 ca = dhtnet::generateIdentity(folder, "ca");
Amnac8354042024-07-24 15:31:32 -0400311 if (!ca.first || !ca.second) {
312 fmt::print(stderr, "Error: Could not generate CA.\n");
313 return EXIT_FAILURE;
314 }
Amnac03c4b52024-07-24 17:57:25 -0400315 Log("Generated CA in {}: {} {}\n", folder, "ca", ca.second->getId());
Louis Maillard9737f532024-07-23 14:26:33 -0400316 }
317
318 // Generate client certificate
319 auto id = dhtnet::generateIdentity(folder, "certificate", ca);
Amnac8354042024-07-24 15:31:32 -0400320 if (!id.first || !id.second) {
321 fmt::print(stderr, "Error: Could not generate certificate.\n");
322 return EXIT_FAILURE;
323 }
Amnac03c4b52024-07-24 17:57:25 -0400324 Log("Generated certificate in {}: {} {}\n", folder, "certificate", id.second->getId());
Louis Maillard9737f532024-07-23 14:26:33 -0400325
326 // Create configuration file with generated keys
327 std::filesystem::path yaml_config{folder / "config.yml"};
328 if (create_yaml_config(yaml_config, folder / "certificate.crt", folder / "certificate.pem", true) != 0) {
329 return EXIT_FAILURE;
330 }
331
332 // Ask user if he want to configure SSH
333 std::string ssh_setup = "";
334 do {
Amnac03c4b52024-07-24 17:57:25 -0400335 Log("Configure SSH to support dnc protocol? [Y/n] (default: yes): ");
Louis Maillard9737f532024-07-23 14:26:33 -0400336 std::getline(std::cin, ssh_setup);
337 ssh_setup = str_tolower(ssh_setup);
338 if (ssh_setup == "y") ssh_setup = "yes";
339 if (ssh_setup == "n") ssh_setup = "no";
340 if (ssh_setup.empty()) ssh_setup = "yes";
341 } while (ssh_setup != "yes" && ssh_setup != "no");
342
343 if (ssh_setup == "yes") {
344 if (configure_ssh_config(yaml_config) != 0) {
345 return EXIT_FAILURE;
346 }
347 }
348
349 return EXIT_SUCCESS;
350 } else {
351 // Create configuration file with generated keys
Amnac8354042024-07-24 15:31:32 -0400352 std::filesystem::path yaml_config{folder / "dnc.yaml"};
Louis Maillard9737f532024-07-23 14:26:33 -0400353 std::string overwrite = "";
354 if (std::filesystem::exists(yaml_config)) {
355 do {
Amnac03c4b52024-07-24 17:57:25 -0400356 Log("Configuration file already exists in {}. Overwrite it? [y/N] (default: no): ", yaml_config);
Louis Maillard9737f532024-07-23 14:26:33 -0400357 std::getline(std::cin, overwrite);
358 overwrite = str_tolower(overwrite);
359 if (overwrite == "y") overwrite = "yes";
360 if (overwrite == "n") overwrite = "no";
361 if (overwrite.empty()) overwrite = "no";
362 } while (overwrite != "yes" && overwrite != "no");
363 } else {
364 overwrite = "yes"; // File doesn't exist, create it
365 }
366 if (overwrite == "yes") {
Amnac8354042024-07-24 15:31:32 -0400367 if (create_yaml_config(yaml_config, folder / "id" / "id-server.crt", folder / "id" / "id-server.pem", false) != 0) {
Louis Maillard9737f532024-07-23 14:26:33 -0400368 return EXIT_FAILURE;
369 }
370 }
371 params.setup = true;
Amna77e27dc2024-07-24 13:09:40 -0400372 params.output = folder;
Louis Maillard9737f532024-07-23 14:26:33 -0400373 }
374 }
375
Amna423b25b2024-02-06 13:21:19 -0500376 // check if the setup is requested
377 if (params.setup) {
378 // create CA with name ca-server
Amna77e27dc2024-07-24 13:09:40 -0400379 std::filesystem::path path_ca = params.output / "CA";
Amna423b25b2024-02-06 13:21:19 -0500380 auto ca = dhtnet::generateIdentity(path_ca, "ca-server");
Amnac8354042024-07-24 15:31:32 -0400381 if (!ca.first || !ca.second) {
382 fmt::print(stderr, "Error: Could not generate CA.\n");
383 return EXIT_FAILURE;
384 }
Amnac03c4b52024-07-24 17:57:25 -0400385 Log("Generated CA in {}: {} {}\n", path_ca, "ca-server", ca.second->getId());
Amna423b25b2024-02-06 13:21:19 -0500386 // create identity with name id-server
Amna77e27dc2024-07-24 13:09:40 -0400387 std::filesystem::path path_id = params.output / "id";
Amna423b25b2024-02-06 13:21:19 -0500388 auto identity = dhtnet::generateIdentity(path_id, "id-server", ca);
Amnac8354042024-07-24 15:31:32 -0400389 if (!identity.first || !identity.second) {
390 fmt::print(stderr, "Error: Could not generate certificate.\n");
391 return EXIT_FAILURE;
392 }
Amnac03c4b52024-07-24 17:57:25 -0400393 Log("Generated certificate in {}: {} {}\n", path_id,"id-server", identity.second->getId());
Amna423b25b2024-02-06 13:21:19 -0500394 return EXIT_SUCCESS;
395 }
396
397 if (params.ca.empty() || params.privatekey.empty()) {
398 if (params.name.empty()) {
Amna77e27dc2024-07-24 13:09:40 -0400399 auto ca = dhtnet::generateIdentity(params.output, "ca");
Amnac8354042024-07-24 15:31:32 -0400400 if (!ca.first || !ca.second) {
401 fmt::print(stderr, "Error: Could not generate CA.\n");
402 return EXIT_FAILURE;
403 }
Amnac03c4b52024-07-24 17:57:25 -0400404 Log("Generated certificate in {}: {} {}\n", params.output, "ca", ca.second->getId());
Amna423b25b2024-02-06 13:21:19 -0500405 }else{
Amna77e27dc2024-07-24 13:09:40 -0400406 auto ca = dhtnet::generateIdentity(params.output, params.name);
Amnac8354042024-07-24 15:31:32 -0400407 if (!ca.first || !ca.second) {
408 fmt::print(stderr, "Error: Could not generate CA.\n");
409 return EXIT_FAILURE;
410 }
Amnac03c4b52024-07-24 17:57:25 -0400411 Log("Generated certificate in {}: {} {}\n", params.output, params.name, ca.second->getId());
Amna423b25b2024-02-06 13:21:19 -0500412 }
413 }else{
414 auto ca = dhtnet::loadIdentity(params.privatekey, params.ca);
415 if (params.name.empty()) {
Amna77e27dc2024-07-24 13:09:40 -0400416 auto id = dhtnet::generateIdentity(params.output, "certificate", ca);
Amnac8354042024-07-24 15:31:32 -0400417 if (!id.first || !id.second) {
418 fmt::print(stderr, "Error: Could not generate certificate.\n");
419 return EXIT_FAILURE;
420 }
Amnac03c4b52024-07-24 17:57:25 -0400421 Log("Generated certificate in {}: {} {}\n", params.output, "certificate", id.second->getId());
Amna423b25b2024-02-06 13:21:19 -0500422 }else{
Amna77e27dc2024-07-24 13:09:40 -0400423 auto id = dhtnet::generateIdentity(params.output, params.name, ca);
Amnac8354042024-07-24 15:31:32 -0400424 if (!id.first || !id.second) {
425 fmt::print(stderr, "Error: Could not generate certificate.\n");
426 return EXIT_FAILURE;
427 }
Amnac03c4b52024-07-24 17:57:25 -0400428 Log("Generated certificate in {}: {} {}\n", params.output, params.name, id.second->getId());
Amna423b25b2024-02-06 13:21:19 -0500429 }
430 }
431 return EXIT_SUCCESS;
432}