mirror of
https://github.com/pi-hole/FTL.git
synced 2026-07-07 23:45:06 +01:00
Implement automatic TLS/SSL certificate renewals. The validity can now be configured through webserver.tls.validity and defaults to 2 years right now.
Signed-off-by: DL6ER <dl6er@dl6er.de>
This commit is contained in:
@@ -429,6 +429,8 @@ components:
|
||||
properties:
|
||||
cert:
|
||||
type: string
|
||||
validity:
|
||||
type: integer
|
||||
paths:
|
||||
type: object
|
||||
properties:
|
||||
@@ -766,6 +768,7 @@ components:
|
||||
restore: true
|
||||
tls:
|
||||
cert: "/etc/pihole/tls.pem"
|
||||
validity: 730
|
||||
paths:
|
||||
webroot: "/var/www/html"
|
||||
webhome: "/admin/"
|
||||
|
||||
+6
-1
@@ -383,12 +383,17 @@ void parse_args(int argc, char *argv[])
|
||||
printf(" RSA with domain: %s --gen-x509 /etc/pihole/tls.pem nanopi.lan rsa\n", argv[0]);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
// Read config
|
||||
readFTLconf(&config, false);
|
||||
|
||||
// Enable stdout printing
|
||||
cli_mode = true;
|
||||
log_ctrl(false, true);
|
||||
|
||||
const char *domain = argc > 3 ? argv[3] : "pi.hole";
|
||||
const bool rsa = argc > 4 && strcasecmp(argv[4], "rsa") == 0;
|
||||
exit(generate_certificate(argv[2], rsa, domain) ? EXIT_SUCCESS : EXIT_FAILURE);
|
||||
|
||||
exit(generate_certificate(argv[2], rsa, domain, config.webserver.tls.validity.v.ui) ? EXIT_SUCCESS : EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// Parse X.509 certificate
|
||||
|
||||
@@ -1048,6 +1048,12 @@ static void initConfig(struct config *conf)
|
||||
conf->webserver.tls.cert.d.s = (char*)"/etc/pihole/tls.pem";
|
||||
conf->webserver.tls.cert.c = validate_filepath;
|
||||
|
||||
conf->webserver.tls.validity.k = "webserver.tls.validity";
|
||||
conf->webserver.tls.validity.h = "Number of days the automatically generated self-signed TLS/SSL certificate will be valid for. Defaults to 730 days (= 2 years). A minimum of 7 days is enforced.\n Some devices may enforce shorter validity ranges. Note that defining a lower validity range may require you to accept the self-signed certificate more often in your browser.\n Pi-hole will regenerate certificates it created itself two days prior to expiration. If you are using your own certificate, you need to regenerate it yourself. In this case, it is advised to set the validity range to 0 days, so that Pi-hole does not try to regenerate your certificate.";
|
||||
conf->webserver.tls.validity.t = CONF_UINT;
|
||||
conf->webserver.tls.validity.d.ui = 730; // 2 years
|
||||
conf->webserver.tls.validity.c = validate_ui_min_7_or_0;
|
||||
|
||||
conf->webserver.session.timeout.k = "webserver.session.timeout";
|
||||
conf->webserver.session.timeout.h = "Session timeout in seconds. If a session is inactive for more than this time, it will be terminated. Sessions are continuously refreshed by the web interface, preventing sessions from timing out while the web interface is open.\n This option may also be used to make logins persistent for long times, e.g. 86400 seconds (24 hours), 604800 seconds (7 days) or 2592000 seconds (30 days). Note that the total number of concurrent sessions is limited so setting this value too high may result in users being rejected and unable to log in if there are already too many sessions active.";
|
||||
conf->webserver.session.timeout.t = CONF_UINT;
|
||||
|
||||
@@ -255,6 +255,7 @@ struct config {
|
||||
} session;
|
||||
struct {
|
||||
struct conf_item cert;
|
||||
struct conf_item validity;
|
||||
} tls;
|
||||
struct {
|
||||
struct conf_item webroot;
|
||||
|
||||
@@ -535,3 +535,14 @@ bool validate_dns_revServers(union conf_value *val, const char *key, char err[VA
|
||||
// Return success
|
||||
return true;
|
||||
}
|
||||
|
||||
bool validate_ui_min_7_or_0(union conf_value *val, const char *key, char err[VALIDATOR_ERRBUF_LEN])
|
||||
{
|
||||
if(val->ui < 7 && val->ui != 0)
|
||||
{
|
||||
snprintf(err, VALIDATOR_ERRBUF_LEN, "%s: cannot be lower than 7", key);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -26,5 +26,6 @@ bool validate_filepath_empty(union conf_value *val, const char *key, char err[VA
|
||||
bool validate_filepath_dash(union conf_value *val, const char *key, char err[VALIDATOR_ERRBUF_LEN]);
|
||||
bool validate_regex_array(union conf_value *val, const char *key, char err[VALIDATOR_ERRBUF_LEN]);
|
||||
bool validate_dns_revServers(union conf_value *val, const char *key, char err[VALIDATOR_ERRBUF_LEN]);
|
||||
bool validate_ui_min_7_or_0(union conf_value *val, const char *key, char err[VALIDATOR_ERRBUF_LEN]);
|
||||
|
||||
#endif // CONFIG_VALIDATOR_H
|
||||
|
||||
@@ -3275,6 +3275,13 @@ void FTL_fork_and_bind_sockets(struct passwd *ent_pw, bool dnsmasq_start)
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// Start webserver thread
|
||||
if(pthread_create( &threads[WEBSERVER], &attr, webserver_thread, NULL ) != 0)
|
||||
{
|
||||
log_crit("Unable to create webserver thread. Exiting...");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// Chown files if FTL started as user root but a dnsmasq config
|
||||
// option states to run as a different user/group (e.g. "nobody")
|
||||
if(getuid() == 0)
|
||||
|
||||
+4
-1
@@ -245,6 +245,7 @@ enum thread_types {
|
||||
NTP_CLIENT,
|
||||
NTP_SERVER4,
|
||||
NTP_SERVER6,
|
||||
WEBSERVER,
|
||||
THREADS_MAX
|
||||
} __attribute__ ((packed));
|
||||
|
||||
@@ -325,7 +326,9 @@ enum cert_check {
|
||||
CERT_CANNOT_PARSE_KEY,
|
||||
CERT_DOMAIN_MISMATCH,
|
||||
CERT_DOMAIN_MATCH,
|
||||
CERT_OKAY
|
||||
CERT_NOT_YET_VALID,
|
||||
CERT_EXPIRES_SOON,
|
||||
CERT_OKAY,
|
||||
} __attribute__ ((packed));
|
||||
|
||||
enum http_method {
|
||||
|
||||
@@ -42,6 +42,7 @@ const char * const thread_names[THREADS_MAX] = {
|
||||
"ntp-client",
|
||||
"ntp-server4",
|
||||
"ntp-server6",
|
||||
"webserver",
|
||||
};
|
||||
|
||||
// Return the (null-terminated) name of the calling thread
|
||||
|
||||
@@ -30,6 +30,8 @@
|
||||
#include "database/message-table.h"
|
||||
// create_cli_password()
|
||||
#include "config/password.h"
|
||||
// thread_names
|
||||
#include "signals.h"
|
||||
|
||||
// Server context handle
|
||||
static struct mg_context *ctx = NULL;
|
||||
@@ -639,7 +641,7 @@ void http_init(void)
|
||||
// Try to generate certificate if not present
|
||||
if(!file_readable(config.webserver.tls.cert.v.s))
|
||||
{
|
||||
if(generate_certificate(config.webserver.tls.cert.v.s, false, config.webserver.domain.v.s))
|
||||
if(generate_certificate(config.webserver.tls.cert.v.s, false, config.webserver.domain.v.s, config.webserver.tls.validity.v.ui))
|
||||
{
|
||||
log_info("Created SSL/TLS certificate for %s at %s",
|
||||
config.webserver.domain.v.s, config.webserver.tls.cert.v.s);
|
||||
@@ -722,7 +724,7 @@ void http_init(void)
|
||||
// Replace trailing slash with end-of-string marker for matcher
|
||||
char *prefix_webhome_matcher = strdup(prefix_webhome);
|
||||
prefix_webhome_matcher[strlen(prefix_webhome_matcher)-1] = '$';
|
||||
|
||||
|
||||
log_debug(DEBUG_API, "Redirecting %s --308--> %s",
|
||||
prefix_webhome, config.webserver.paths.webhome.v.s);
|
||||
mg_set_request_handler(ctx, prefix_webhome_matcher, redirect_admin_handler, NULL);
|
||||
@@ -854,8 +856,56 @@ void http_terminate(void)
|
||||
// Free admin_api_uri path
|
||||
if(admin_api_uri != NULL)
|
||||
free(admin_api_uri);
|
||||
|
||||
|
||||
// Free login_uri path
|
||||
if(login_uri != NULL)
|
||||
free(login_uri);
|
||||
}
|
||||
|
||||
static void restart_http(void)
|
||||
{
|
||||
// Stop the server
|
||||
http_terminate();
|
||||
|
||||
// Reinitialize the webserver
|
||||
http_init();
|
||||
}
|
||||
|
||||
void *webserver_thread(void *val)
|
||||
{
|
||||
(void)val;
|
||||
// Set thread name
|
||||
prctl(PR_SET_NAME, thread_names[WEBSERVER], 0, 0, 0);
|
||||
|
||||
// Initial delay until we check the certificate for the first time
|
||||
thread_sleepms(WEBSERVER, 2000);
|
||||
|
||||
while(!killed)
|
||||
{
|
||||
// Check if the certificate is about to expire soon
|
||||
const enum cert_check status = cert_currently_valid(config.webserver.tls.cert.v.s, 2);
|
||||
|
||||
if(status == CERT_EXPIRES_SOON &&
|
||||
config.webserver.tls.validity.v.ui > 0 &&
|
||||
is_pihole_certificate(config.webserver.tls.cert.v.s))
|
||||
{
|
||||
log_info("TLS certificate at %s is about to expire soon, generating new one",
|
||||
config.webserver.tls.cert.v.s);
|
||||
generate_certificate(config.webserver.tls.cert.v.s, false,
|
||||
config.webserver.domain.v.s,
|
||||
config.webserver.tls.validity.v.ui);
|
||||
|
||||
log_info("Restarting HTTP server");
|
||||
restart_http();
|
||||
|
||||
log_info("Done. The new certificate is valid for %u days",
|
||||
config.webserver.tls.validity.v.ui);
|
||||
}
|
||||
|
||||
// Idle for 1 hour
|
||||
thread_sleepms(WEBSERVER, 3600000);
|
||||
}
|
||||
|
||||
log_info("Terminating webserver thread");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
|
||||
void http_init(void);
|
||||
void http_terminate(void);
|
||||
void *webserver_thread(void *val);
|
||||
|
||||
int ftl_http_redirect(struct mg_connection *conn, const int code, const char *format, ...) __attribute__((format(printf, 3, 4), nonnull(1, 3)));
|
||||
in_port_t get_https_port(void) __attribute__((pure));
|
||||
|
||||
+179
-79
@@ -12,6 +12,10 @@
|
||||
#include "log.h"
|
||||
#include "x509.h"
|
||||
|
||||
#ifndef HAVE_MBEDTLS
|
||||
#define HAVE_MBEDTLS
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_MBEDTLS
|
||||
# include <mbedtls/rsa.h>
|
||||
# include <mbedtls/x509.h>
|
||||
@@ -24,6 +28,7 @@
|
||||
|
||||
#define RSA_KEY_SIZE 4096
|
||||
#define BUFFER_SIZE 16000
|
||||
#define PIHOLE_ISSUER "CN=pi.hole,O=Pi-hole,C=DE"
|
||||
|
||||
static bool read_id_file(const char *filename, char *buffer, size_t buffer_size)
|
||||
{
|
||||
@@ -260,7 +265,7 @@ static bool write_to_file(const char *filename, const char *type, const char *su
|
||||
return true;
|
||||
}
|
||||
|
||||
bool generate_certificate(const char* certfile, bool rsa, const char *domain)
|
||||
bool generate_certificate(const char* certfile, bool rsa, const char *domain, const unsigned int validity_days)
|
||||
{
|
||||
int ret;
|
||||
mbedtls_x509write_cert ca_cert, server_cert;
|
||||
@@ -339,7 +344,8 @@ bool generate_certificate(const char* certfile, bool rsa, const char *domain)
|
||||
char not_before[16] = { 0 };
|
||||
char not_after[16] = { 0 };
|
||||
strftime(not_before, sizeof(not_before), "%Y%m%d%H%M%S", tm);
|
||||
tm->tm_year += 30; // 30 years from now
|
||||
tm->tm_mday += validity_days;
|
||||
mktime(tm); // normalize time
|
||||
// Check for leap year, and adjust the date accordingly
|
||||
const bool isLeapYear = tm->tm_year % 4 == 0 && (tm->tm_year % 100 != 0 || tm->tm_year % 400 == 0);
|
||||
tm->tm_mday = tm->tm_mon == 1 && tm->tm_mday == 29 && !isLeapYear ? 28 : tm->tm_mday;
|
||||
@@ -355,8 +361,8 @@ bool generate_certificate(const char* certfile, bool rsa, const char *domain)
|
||||
mbedtls_x509write_crt_set_subject_key_identifier(&ca_cert);
|
||||
mbedtls_x509write_crt_set_issuer_key(&ca_cert, &ca_key);
|
||||
mbedtls_x509write_crt_set_authority_key_identifier(&ca_cert);
|
||||
mbedtls_x509write_crt_set_issuer_name(&ca_cert, "CN=pi.hole,O=Pi-hole,C=DE");
|
||||
mbedtls_x509write_crt_set_subject_name(&ca_cert, "CN=pi.hole,O=Pi-hole,C=DE");
|
||||
mbedtls_x509write_crt_set_issuer_name(&ca_cert, PIHOLE_ISSUER);
|
||||
mbedtls_x509write_crt_set_subject_name(&ca_cert, PIHOLE_ISSUER);
|
||||
mbedtls_x509write_crt_set_validity(&ca_cert, not_before, not_after);
|
||||
mbedtls_x509write_crt_set_basic_constraints(&ca_cert, 1, -1);
|
||||
|
||||
@@ -378,7 +384,7 @@ bool generate_certificate(const char* certfile, bool rsa, const char *domain)
|
||||
mbedtls_x509write_crt_set_issuer_key(&server_cert, &ca_key);
|
||||
mbedtls_x509write_crt_set_authority_key_identifier(&server_cert);
|
||||
// subject name set below
|
||||
mbedtls_x509write_crt_set_issuer_name(&server_cert, "CN=pi.hole,O=Pi-hole,C=DE");
|
||||
mbedtls_x509write_crt_set_issuer_name(&server_cert, PIHOLE_ISSUER);
|
||||
mbedtls_x509write_crt_set_validity(&server_cert, not_before, not_after);
|
||||
mbedtls_x509write_crt_set_basic_constraints(&server_cert, 0, -1);
|
||||
|
||||
@@ -469,12 +475,87 @@ static bool check_wildcard_domain(const char *domain, char *san, const size_t sa
|
||||
return strncasecmp(wild_domain, san + 1, san_len - 1) == 0;
|
||||
}
|
||||
|
||||
static bool search_domain(mbedtls_x509_crt *crt, mbedtls_x509_sequence *sans, const char *domain)
|
||||
{
|
||||
bool found = false;
|
||||
// Loop over all SANs
|
||||
while(sans != NULL)
|
||||
{
|
||||
// Parse the SAN
|
||||
mbedtls_x509_subject_alternative_name san = { 0 };
|
||||
const int ret = mbedtls_x509_parse_subject_alt_name(&sans->buf, &san);
|
||||
|
||||
// Check if SAN is used (otherwise ret < 0, e.g.,
|
||||
// MBEDTLS_ERR_X509_FEATURE_UNAVAILABLE) and if it is a
|
||||
// DNS name, skip otherwise
|
||||
if(ret < 0 || san.type != MBEDTLS_X509_SAN_DNS_NAME)
|
||||
goto next_san;
|
||||
|
||||
// Check if the SAN matches the domain
|
||||
// Attention: The SAN is not NUL-terminated, so we need to
|
||||
// use the length field
|
||||
if(strncasecmp(domain, (char*)san.san.unstructured_name.p, san.san.unstructured_name.len) == 0)
|
||||
{
|
||||
found = true;
|
||||
// Free resources
|
||||
mbedtls_x509_free_subject_alt_name(&san);
|
||||
break;
|
||||
}
|
||||
|
||||
// Also check if the SAN is a wildcard domain and if the domain
|
||||
// matches the wildcard
|
||||
if(check_wildcard_domain(domain, (char*)san.san.unstructured_name.p, san.san.unstructured_name.len))
|
||||
{
|
||||
found = true;
|
||||
// Free resources
|
||||
mbedtls_x509_free_subject_alt_name(&san);
|
||||
break;
|
||||
}
|
||||
next_san:
|
||||
// Free resources
|
||||
mbedtls_x509_free_subject_alt_name(&san);
|
||||
|
||||
// Go to next SAN
|
||||
sans = sans->next;
|
||||
}
|
||||
|
||||
if(found)
|
||||
return true;
|
||||
|
||||
// Also check against the common name (CN) field
|
||||
char subject[MBEDTLS_X509_MAX_DN_NAME_SIZE];
|
||||
const size_t subject_len = mbedtls_x509_dn_gets(subject, sizeof(subject), &(crt->subject));
|
||||
if(subject_len > 0)
|
||||
{
|
||||
// Check subjects prefixed with "CN="
|
||||
if(subject_len > 3 && strncasecmp(subject, "CN=", 3) == 0)
|
||||
{
|
||||
// Check subject + 3 to skip the prefix
|
||||
if(strncasecmp(domain, subject + 3, subject_len - 3) == 0)
|
||||
found = true;
|
||||
// Also check if the subject is a wildcard domain
|
||||
else if(check_wildcard_domain(domain, subject + 3, subject_len - 3))
|
||||
found = true;
|
||||
}
|
||||
// Check subject == "<domain>"
|
||||
else if(strcasecmp(domain, subject) == 0)
|
||||
found = true;
|
||||
// Also check if the subject is a wildcard domain and if the domain
|
||||
// matches the wildcard
|
||||
else if(check_wildcard_domain(domain, subject, subject_len))
|
||||
found = true;
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
|
||||
// This function reads a X.509 certificate from a file and prints a
|
||||
// human-readable representation of the certificate to stdout. If a domain is
|
||||
// specified, we only check if this domain is present in the certificate.
|
||||
// Otherwise, we print verbose human-readable information about the certificate
|
||||
// and about the private key (if requested).
|
||||
enum cert_check read_certificate(const char* certfile, const char *domain, const bool private_key)
|
||||
enum cert_check read_certificate(const char *certfile, const char *domain, const bool private_key)
|
||||
{
|
||||
if(certfile == NULL && domain == NULL)
|
||||
{
|
||||
@@ -514,80 +595,10 @@ enum cert_check read_certificate(const char* certfile, const char *domain, const
|
||||
|
||||
// Parse mbedtls_x509_parse_subject_alt_names()
|
||||
mbedtls_x509_sequence *sans = &crt.subject_alt_names;
|
||||
bool found = false;
|
||||
|
||||
// When a domain is specified, possibly return early
|
||||
if(domain != NULL)
|
||||
{
|
||||
// Loop over all SANs
|
||||
while(sans != NULL)
|
||||
{
|
||||
// Parse the SAN
|
||||
mbedtls_x509_subject_alternative_name san = { 0 };
|
||||
const int ret = mbedtls_x509_parse_subject_alt_name(&sans->buf, &san);
|
||||
|
||||
// Check if SAN is used (otherwise ret < 0, e.g.,
|
||||
// MBEDTLS_ERR_X509_FEATURE_UNAVAILABLE) and if it is a
|
||||
// DNS name, skip otherwise
|
||||
if(ret < 0 || san.type != MBEDTLS_X509_SAN_DNS_NAME)
|
||||
goto next_san;
|
||||
|
||||
// Check if the SAN matches the domain
|
||||
// Attention: The SAN is not NUL-terminated, so we need to
|
||||
// use the length field
|
||||
if(strncasecmp(domain, (char*)san.san.unstructured_name.p, san.san.unstructured_name.len) == 0)
|
||||
{
|
||||
found = true;
|
||||
// Free resources
|
||||
mbedtls_x509_free_subject_alt_name(&san);
|
||||
break;
|
||||
}
|
||||
|
||||
// Also check if the SAN is a wildcard domain and if the domain
|
||||
// matches the wildcard
|
||||
if(check_wildcard_domain(domain, (char*)san.san.unstructured_name.p, san.san.unstructured_name.len))
|
||||
{
|
||||
found = true;
|
||||
// Free resources
|
||||
mbedtls_x509_free_subject_alt_name(&san);
|
||||
break;
|
||||
}
|
||||
next_san:
|
||||
// Free resources
|
||||
mbedtls_x509_free_subject_alt_name(&san);
|
||||
|
||||
// Go to next SAN
|
||||
sans = sans->next;
|
||||
}
|
||||
|
||||
// Also check against the common name (CN) field
|
||||
char subject[MBEDTLS_X509_MAX_DN_NAME_SIZE];
|
||||
const size_t subject_len = mbedtls_x509_dn_gets(subject, sizeof(subject), &crt.subject);
|
||||
if(subject_len > 0)
|
||||
{
|
||||
// Check subjects prefixed with "CN="
|
||||
if(subject_len > 3 && strncasecmp(subject, "CN=", 3) == 0)
|
||||
{
|
||||
// Check subject + 3 to skip the prefix
|
||||
if(strncasecmp(domain, subject + 3, subject_len - 3) == 0)
|
||||
found = true;
|
||||
// Also check if the subject is a wildcard domain
|
||||
else if(check_wildcard_domain(domain, subject + 3, subject_len - 3))
|
||||
found = true;
|
||||
}
|
||||
// Check subject == "<domain>"
|
||||
else if(strcasecmp(domain, subject) == 0)
|
||||
found = true;
|
||||
// Also check if the subject is a wildcard domain and if the domain
|
||||
// matches the wildcard
|
||||
else if(check_wildcard_domain(domain, subject, subject_len))
|
||||
found = true;
|
||||
}
|
||||
|
||||
|
||||
// Free resources
|
||||
mbedtls_x509_crt_free(&crt);
|
||||
mbedtls_pk_free(&key);
|
||||
return found ? CERT_DOMAIN_MATCH : CERT_DOMAIN_MISMATCH;
|
||||
}
|
||||
return search_domain(&crt, sans, domain) ? CERT_DOMAIN_MATCH : CERT_DOMAIN_MISMATCH;
|
||||
|
||||
// else: Print verbose information about the certificate
|
||||
char certinfo[BUFFER_SIZE] = { 0 };
|
||||
@@ -728,6 +739,95 @@ end:
|
||||
return CERT_OKAY;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if the certificate at the given file path is currently valid and will remain valid for at least the specified number of days.
|
||||
*
|
||||
* This function loads an X.509 certificate from the specified file, verifies that it is readable and parsable,
|
||||
* and checks its validity period. It ensures that the certificate is already valid (not before date is in the past)
|
||||
* and that it will not expire within the next `valid_for_at_least_days` days.
|
||||
*
|
||||
* @param certfile Path to the certificate file to check. If NULL, the function returns CERT_FILE_NOT_FOUND.
|
||||
* @param valid_for_at_least_days The minimum number of days the certificate should remain valid from now.
|
||||
*
|
||||
* @return enum cert_check
|
||||
* - CERT_OKAY: Certificate is valid and will remain valid for at least the specified number of days.
|
||||
* - CERT_FILE_NOT_FOUND: Certificate file is not specified, does not exist, or is not readable.
|
||||
* - CERT_CANNOT_PARSE_CERT: Certificate file could not be parsed.
|
||||
* - CERT_NOT_YET_VALID: Certificate is not yet valid (valid_from is in the future).
|
||||
* - CERT_EXPIRES_SOON: Certificate will expire within the specified number of days.
|
||||
*/
|
||||
enum cert_check cert_currently_valid(const char *certfile, const time_t valid_for_at_least_days)
|
||||
{
|
||||
// If no file was specified, we do not want to recreate it
|
||||
if(certfile == NULL)
|
||||
return CERT_FILE_NOT_FOUND;
|
||||
|
||||
mbedtls_x509_crt crt;
|
||||
mbedtls_x509_crt_init(&crt);
|
||||
|
||||
// Check if the file exists and is readable
|
||||
if(access(certfile, R_OK) != 0)
|
||||
{
|
||||
log_err("Could not read certificate file: %s", strerror(errno));
|
||||
return CERT_FILE_NOT_FOUND;
|
||||
}
|
||||
|
||||
int rc = mbedtls_x509_crt_parse_file(&crt, certfile);
|
||||
if (rc != 0)
|
||||
{
|
||||
log_err("Cannot parse certificate: Error code %d", rc);
|
||||
return CERT_CANNOT_PARSE_CERT;
|
||||
}
|
||||
|
||||
// Compare validity of certificate
|
||||
// - crt.valid_from needs to be in the past
|
||||
// - crt.valid_to should be further away than at least two days
|
||||
mbedtls_x509_time now;
|
||||
mbedtls_x509_time_gmtime(mbedtls_time(NULL) + valid_for_at_least_days * (24 * 3600), &now);
|
||||
const bool is_valid_to = mbedtls_x509_time_cmp(&(crt.valid_to), &now) > 0;
|
||||
const bool is_valid_from = mbedtls_x509_time_is_past(&(crt.valid_from));
|
||||
|
||||
// Free resources
|
||||
mbedtls_x509_crt_free(&crt);
|
||||
|
||||
// Return result
|
||||
if(!is_valid_from)
|
||||
return CERT_NOT_YET_VALID;
|
||||
if(!is_valid_to)
|
||||
return CERT_EXPIRES_SOON;
|
||||
return CERT_OKAY;
|
||||
}
|
||||
|
||||
bool is_pihole_certificate(const char *certfile)
|
||||
{
|
||||
// Check if the file exists and is readable
|
||||
if(access(certfile, R_OK) != 0)
|
||||
{
|
||||
log_err("Could not read certificate file: %s", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
mbedtls_x509_crt crt;
|
||||
mbedtls_x509_crt_init(&crt);
|
||||
|
||||
int rc = mbedtls_x509_crt_parse_file(&crt, certfile);
|
||||
if (rc != 0)
|
||||
{
|
||||
log_err("Cannot parse certificate: Error code %d", rc);
|
||||
return false;
|
||||
}
|
||||
// Check if the issuer is "pi.hole"
|
||||
const bool is_pihole_issuer = strncasecmp((char*)crt.issuer.val.p, "pi.hole", crt.issuer.val.len) == 0;
|
||||
// Check if the subject is "pi.hole"
|
||||
const bool is_pihole_subject = strncasecmp((char*)crt.subject.val.p, "pi.hole", crt.subject.val.len) == 0;
|
||||
|
||||
|
||||
// Free resources
|
||||
mbedtls_x509_crt_free(&crt);
|
||||
|
||||
return is_pihole_issuer && is_pihole_subject;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
enum cert_check read_certificate(const char* certfile, const char *domain, const bool private_key)
|
||||
|
||||
@@ -19,9 +19,13 @@
|
||||
#include <stdbool.h>
|
||||
// ssize_t
|
||||
#include <unistd.h>
|
||||
// time_t
|
||||
#include <time.h>
|
||||
|
||||
bool generate_certificate(const char* certfile, bool rsa, const char *domain);
|
||||
enum cert_check read_certificate(const char* certfile, const char *domain, const bool private_key);
|
||||
bool generate_certificate(const char *certfile, bool rsa, const char *domain, const unsigned int validity_days);
|
||||
enum cert_check read_certificate(const char *certfile, const char *domain, const bool private_key);
|
||||
enum cert_check cert_currently_valid(const char *certfile, const time_t valid_for_at_least_days);
|
||||
bool is_pihole_certificate(const char *certfile);
|
||||
|
||||
bool init_entropy(void);
|
||||
void destroy_entropy(void);
|
||||
|
||||
+14
-3
@@ -1,7 +1,7 @@
|
||||
# Pi-hole configuration file (v6.0.4-75-gc53cf00b)
|
||||
# Pi-hole configuration file (v6.1-110-g8bec7efc-dirty)
|
||||
# Encoding: UTF-8
|
||||
# This file is managed by pihole-FTL
|
||||
# Last updated on 2025-03-27 11:04:20 UTC
|
||||
# Last updated on 2025-05-22 12:43:05 UTC
|
||||
|
||||
[dns]
|
||||
# Array of upstream DNS servers used by Pi-hole
|
||||
@@ -750,6 +750,17 @@
|
||||
# <valid TLS certificate file (*.pem)>
|
||||
cert = "/etc/pihole/test.pem" ### CHANGED, default = "/etc/pihole/tls.pem"
|
||||
|
||||
# Number of days the automatically generated self-signed TLS/SSL certificate will be
|
||||
# valid for. Defaults to 730 days (= 2 years). A minimum of 7 days is enforced.
|
||||
# Some devices may enforce shorter validity ranges. Note that defining a lower
|
||||
# validity range may require you to accept the self-signed certificate more often in
|
||||
# your browser.
|
||||
# Pi-hole will regenerate certificates it created itself two days prior to expiration.
|
||||
# If you are using your own certificate, you need to regenerate it yourself. In this
|
||||
# case, it is advised to set the validity range to 0 days, so that Pi-hole does not
|
||||
# try to regenerate your certificate.
|
||||
validity = 730
|
||||
|
||||
[webserver.paths]
|
||||
# Server root on the host
|
||||
#
|
||||
@@ -1202,7 +1213,7 @@
|
||||
all = true ### CHANGED, default = false
|
||||
|
||||
# Configuration statistics:
|
||||
# 155 total entries out of which 98 entries are default
|
||||
# 156 total entries out of which 99 entries are default
|
||||
# --> 57 entries are modified
|
||||
# 3 entries are forced through environment:
|
||||
# - misc.nice
|
||||
|
||||
Reference in New Issue
Block a user