From 515fe8b440ee9819078a2fee493a349db8fcbe53 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 5 Apr 2023 21:21:14 +0200 Subject: [PATCH] Add certificate generation command (pihole-FTL --gen-x509 ...) Signed-off-by: DL6ER --- src/api/docs/content/specs/info.yaml | 2 +- src/args.c | 25 ++++ src/webserver/CMakeLists.txt | 2 + src/webserver/x509.c | 194 +++++++++++++++++++++++++++ src/webserver/x509.h | 15 +++ 5 files changed, 237 insertions(+), 1 deletion(-) create mode 100644 src/webserver/x509.c create mode 100644 src/webserver/x509.h diff --git a/src/api/docs/content/specs/info.yaml b/src/api/docs/content/specs/info.yaml index 79c77181..fe7d78d8 100644 --- a/src/api/docs/content/specs/info.yaml +++ b/src/api/docs/content/specs/info.yaml @@ -926,7 +926,7 @@ components: type: string description: RR name of cache entry count: - type: string + type: integer description: Number of cache entries for this RR type replies: type: object diff --git a/src/args.c b/src/args.c index 49d5baa4..82909acd 100644 --- a/src/args.c +++ b/src/args.c @@ -52,6 +52,8 @@ #include "zip/teleporter.h" // printTOTP() #include "api/api.h" +// generate_certificate() +#include "webserver/x509.h" // defined in dnsmasq.c extern void print_dnsmasq_version(const char *yellow, const char *green, const char *bold, const char *normal); @@ -291,6 +293,22 @@ void parse_args(int argc, char* argv[]) exit(read_teleporter_zip_from_disk(argv[2]) ? EXIT_SUCCESS : EXIT_FAILURE); } + if(argc > 1 && strcmp(argv[1], "--gen-x509") == 0) + { + if(argc != 3 && argc != 4) + { + printf("Usage: %s --gen-x509 [rsa]\n", argv[0]); + printf("Example: %s --gen-x509 /etc/pihole/tls.pem\n", argv[0]); + printf(" or: %s --gen-x509 /etc/pihole/tls.pem rsa\n", argv[0]); + exit(EXIT_FAILURE); + } + // Enable stdout printing + cli_mode = true; + log_ctrl(false, true); + const bool rsa = argc == 4 && strcasecmp(argv[3], "rsa") == 0; + exit(generate_certificate(argv[2], rsa) ? EXIT_SUCCESS : EXIT_FAILURE); + } + // start from 1, as argv[0] is the executable name for(int i = 1; i < argc; i++) { @@ -728,6 +746,13 @@ void parse_args(int argc, char* argv[]) printf("\t current directory and print its name\n"); printf("\t%s--teleporter%s file%s Import the Teleporter archive %sfile%s\n\n", green, cyan, normal, cyan, normal); + printf("%sTLS X.509 certificate generator:%s\n", yellow, normal); + printf(" Generate a self-signed certificate suitable for SSL/TLS\n\n"); + printf(" By default, this new certificate is based on the elliptic\n"); + printf(" curve secp521r1. If the optional flag %s[rsa]%s is specified,\n", purple, normal); + printf(" an RSA (4096 bit) key will be generated instead.\n\n"); + printf(" Usage: %spihole-FTL --gen-x509 %s %s[rsa]%s\n\n", green, yellow, purple, normal); + printf("%sOther:%s\n", yellow, normal); printf("\t%sdhcp-discover%s Discover DHCP servers in the local\n", green, normal); printf("\t network\n"); diff --git a/src/webserver/CMakeLists.txt b/src/webserver/CMakeLists.txt index a4c3ae2c..ff0ad9d6 100644 --- a/src/webserver/CMakeLists.txt +++ b/src/webserver/CMakeLists.txt @@ -16,6 +16,8 @@ set(sources ph7.c ph7.h json_macros.h + x509.c + x509.h ) add_library(webserver OBJECT ${sources}) diff --git a/src/webserver/x509.c b/src/webserver/x509.c new file mode 100644 index 00000000..a0a978c6 --- /dev/null +++ b/src/webserver/x509.c @@ -0,0 +1,194 @@ +/* Pi-hole: A black hole for Internet advertisements +* (c) 2023 Pi-hole, LLC (https://pi-hole.net) +* Network-wide ad blocking via your own hardware. +* +* FTL Engine +* X.509 certificate routines +* +* This file is copyright under the latest version of the EUPL. +* Please see LICENSE file for your rights under this license. */ + +#include "../FTL.h" +#include "../log.h" +#include "x509.h" +#include +#include +#include +#include +#include + +#define RSA_KEY_SIZE 4096 +#define BUFFER_SIZE 16000 + +// Generate private RSA key +static int generate_private_key_rsa(mbedtls_pk_context *key, + mbedtls_ctr_drbg_context *ctr_drbg, + unsigned char key_buffer[]) +{ + int ret; + if((ret = mbedtls_pk_setup(key, mbedtls_pk_info_from_type(MBEDTLS_PK_RSA))) != 0) + { + log_err("mbedtls_pk_setup returned %d", ret); + return ret; + } + + if((ret = mbedtls_rsa_gen_key(mbedtls_pk_rsa(*key), mbedtls_ctr_drbg_random, + ctr_drbg, RSA_KEY_SIZE, 65537)) != 0) + { + log_err("mbedtls_rsa_gen_key returned %d", ret); + return ret; + } + + // Export key in PEM format + if ((ret = mbedtls_pk_write_key_pem(key, key_buffer, BUFFER_SIZE)) != 0) { + log_err("mbedtls_pk_write_key_pem returned %d", ret); + return ret; + } + + return 0; +} + +// Generate private EC key (secp521r1) +static int generate_private_key_ec(mbedtls_pk_context *key, + mbedtls_ctr_drbg_context *ctr_drbg, + unsigned char key_buffer[]) +{ + int ret; + // Setup key + if((ret = mbedtls_pk_setup(key, mbedtls_pk_info_from_type(MBEDTLS_PK_ECKEY))) != 0) + { + log_err("mbedtls_pk_setup returned %d", ret); + return ret; + } + + // Generate key + if((ret = mbedtls_ecp_gen_key(MBEDTLS_ECP_DP_SECP521R1, mbedtls_pk_ec(*key), + mbedtls_ctr_drbg_random, ctr_drbg)) != 0) + { + log_err("mbedtls_ecp_gen_key returned %d", ret); + return ret; + } + + // Export key in PEM format + if ((ret = mbedtls_pk_write_key_pem(key, key_buffer, BUFFER_SIZE)) != 0) { + log_err("mbedtls_pk_write_key_pem returned %d", ret); + return ret; + } + + return 0; +} + +bool generate_certificate(const char* certfile, bool rsa) +{ + int ret; + mbedtls_x509write_cert crt; + mbedtls_pk_context key; + mbedtls_entropy_context entropy; + mbedtls_ctr_drbg_context ctr_drbg; + const char *pers = "pihole-FTL"; + unsigned char cert_buffer[BUFFER_SIZE]; + unsigned char key_buffer[BUFFER_SIZE]; + size_t olen = 0; + + // Initialize structures + mbedtls_x509write_crt_init(&crt); + mbedtls_pk_init(&key); + mbedtls_ctr_drbg_init(&ctr_drbg); + mbedtls_entropy_init(&entropy); + + // Initialize random number generator + log_info("Initializing random number generator..."); + if((ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, + (const unsigned char *) pers, strlen(pers))) != 0) + { + log_err("mbedtls_ctr_drbg_seed returned %d", ret); + return false; + } + + if(rsa) + { + // Generate RSA key + log_info("Generating RSA key..."); + if((ret = generate_private_key_rsa(&key, &ctr_drbg, key_buffer)) != 0) + { + log_err("generate_private_key returned %d", ret); + return false; + } + } + else + { + // Generate EC key + log_info("Generating EC key..."); + if((ret = generate_private_key_ec(&key, &ctr_drbg, key_buffer)) != 0) + { + log_err("generate_private_key_ec returned %d", ret); + return false; + } + } + + // Create string with random digits for unique serial number + unsigned char serial[16] = { 0 }; + for(int i = 0; i < 15; i++) + serial[i] = '0' + (rand() % 10); + + // Generate certificate + log_info("Generating new certificate with serial number %s...", serial); + mbedtls_x509write_crt_set_version(&crt, MBEDTLS_X509_CRT_VERSION_3); + + mbedtls_x509write_crt_set_serial_raw(&crt, serial, sizeof(serial)-1); + mbedtls_x509write_crt_set_md_alg(&crt, MBEDTLS_MD_SHA256); + mbedtls_x509write_crt_set_subject_key(&crt, &key); + mbedtls_x509write_crt_set_issuer_key(&crt, &key); + mbedtls_x509write_crt_set_subject_name(&crt, "CN=pi.hole"); + mbedtls_x509write_crt_set_issuer_name(&crt, "CN=pi.hole"); + mbedtls_x509write_crt_set_validity(&crt, "20010101000000", "20301231235959"); + mbedtls_x509write_crt_set_basic_constraints(&crt, 0, -1); + mbedtls_x509write_crt_set_subject_key_identifier(&crt); + mbedtls_x509write_crt_set_authority_key_identifier(&crt); + + // Export certificate in PEM format + if((ret = mbedtls_x509write_crt_pem(&crt, cert_buffer, sizeof(cert_buffer), + mbedtls_ctr_drbg_random, &ctr_drbg)) != 0) + { + log_err("mbedtls_x509write_crt_pem returned %d", ret); + return false; + } + + // Write private key and certificate to file + FILE *f = NULL; + log_info("Storing key + certificate in %s ...", certfile); + if ((f = fopen(certfile, "wb")) == NULL) + { + log_err("Could not open %s for writing", certfile); + return false; + } + + // Write private key + olen = strlen((char *) key_buffer); + if (fwrite(key_buffer, 1, olen, f) != olen) + { + log_err("Could not write key to %s", certfile); + fclose(f); + return false; + } + + // Write certificate + olen = strlen((char *) cert_buffer); + if (fwrite(cert_buffer, 1, olen, f) != olen) + { + log_err("Could not write certificate to %s", certfile); + fclose(f); + return false; + } + + // Close file + fclose(f); + + // Free ressources + mbedtls_x509write_crt_free(&crt); + mbedtls_pk_free(&key); + mbedtls_ctr_drbg_free(&ctr_drbg); + mbedtls_entropy_free(&entropy); + + return true; +} diff --git a/src/webserver/x509.h b/src/webserver/x509.h new file mode 100644 index 00000000..42b1cfc7 --- /dev/null +++ b/src/webserver/x509.h @@ -0,0 +1,15 @@ +/* Pi-hole: A black hole for Internet advertisements +* (c) 2023 Pi-hole, LLC (https://pi-hole.net) +* Network-wide ad blocking via your own hardware. +* +* FTL Engine +* X.509 certificate routines +* +* This file is copyright under the latest version of the EUPL. +* Please see LICENSE file for your rights under this license. */ +#ifndef X509_H +#define X509_H + +bool generate_certificate(const char* certfile, bool rsa); + +#endif // X509_H