diff --git a/src/args.c b/src/args.c index f80a046e..4696b537 100644 --- a/src/args.c +++ b/src/args.c @@ -309,6 +309,7 @@ void parse_args(int argc, char* argv[]) exit(read_teleporter_zip_from_disk(argv[2]) ? EXIT_SUCCESS : EXIT_FAILURE); } + // Generate X.509 certificate if(argc > 1 && strcmp(argv[1], "--gen-x509") == 0) { if(argc < 3 || argc > 5) @@ -327,6 +328,55 @@ void parse_args(int argc, char* argv[]) exit(generate_certificate(argv[2], rsa, domain) ? EXIT_SUCCESS : EXIT_FAILURE); } + // Parse X.509 certificate + if(argc > 1 && + (strcmp(argv[1], "--read-x509") == 0 || + strcmp(argv[1], "--read-x509-key") == 0)) + { + if(argc < 2 || argc > 4) + { + printf("Usage: %s %s [] []\n", argv[0], argv[1]); + printf("Example: %s %s /etc/pihole/tls.pem\n", argv[0], argv[1]); + printf(" with domain: %s %s /etc/pihole/tls.pem pi.hole\n", argv[0], argv[1]); + exit(EXIT_FAILURE); + } + + // Option parsing + // Should we report on the private key? + const bool private_key = strcmp(argv[1], "--read-x509-key") == 0; + // If no certificate file is given, we use the one from the config + const char *certfile = NULL; + if(argc == 2) + { + readFTLconf(&config, false); + certfile = config.webserver.tls.cert.v.s; + } + else + certfile = argv[2]; + + // If no domain is given, we only check the certificate + const char *domain = argc > 3 ? argv[3] : NULL; + + // Enable stdout printing + cli_mode = true; + log_ctrl(false, true); + + enum cert_check result = read_certificate(certfile, domain, private_key); + + if(argc < 4) + exit(result == CERT_OKAY ? EXIT_SUCCESS : EXIT_FAILURE); + else if(result == CERT_DOMAIN_MATCH) + { + printf("Certificate matches domain %s\n", argv[3]); + exit(EXIT_SUCCESS); + } + else + { + printf("Certificate does not match domain %s\n", argv[3]); + exit(EXIT_FAILURE); + } + } + // If the first argument is "gravity" (e.g., /usr/bin/pihole-FTL gravity), // we offer some specialized gravity tools if(argc > 1 && (strcmp(argv[1], "gravity") == 0 || strcmp(argv[1], "antigravity") == 0)) @@ -812,6 +862,16 @@ void parse_args(int argc, char* argv[]) printf(" an RSA (4096 bit) key will be generated instead.\n\n"); printf(" Usage: %spihole-FTL --gen-x509 %soutfile %s[rsa]%s\n\n", green, cyan, purple, normal); + printf("%sTLS X.509 certificate parser:%s\n", yellow, normal); + printf(" Parse the given X.509 certificate and optionally check if\n"); + printf(" it matches a given domain. If no domain is given, only a\n"); + printf(" human-readable output string is printed.\n\n"); + printf(" If no certificate file is given, the one from the config\n"); + printf(" is used (if applicable). If --read-x509-key is used, details\n"); + printf(" about the private key are printed as well.\n\n"); + printf(" Usage: %spihole-FTL --read-x509 %s[certfile] %s[domain]%s\n", green, cyan, purple, normal); + printf(" Usage: %spihole-FTL --read-x509-key %s[certfile] %s[domain]%s\n\n", green, cyan, purple, normal); + printf("%sGravity tools:%s\n", yellow, normal); printf(" Check domains in a given file for validity using Pi-hole's\n"); printf(" gravity filters. The expected input format is one domain\n"); diff --git a/src/database/message-table.c b/src/database/message-table.c index 72ac7b2f..ba1b7591 100644 --- a/src/database/message-table.c +++ b/src/database/message-table.c @@ -54,6 +54,8 @@ static const char *get_message_type_str(const enum message_type type) return "LIST"; case DISK_MESSAGE_EXTENDED: return "DISK_EXTENDED"; + case CERTIFICATE_DOMAIN_MISMATCH_MESSAGE: + return "CERTIFICATE_DOMAIN_MISMATCH"; case MAX_MESSAGE: default: return "UNKNOWN"; @@ -84,6 +86,8 @@ static enum message_type get_message_type_from_string(const char *typestr) return INACCESSIBLE_ADLIST_MESSAGE; else if (strcmp(typestr, "DISK_EXTENDED") == 0) return DISK_MESSAGE_EXTENDED; + else if (strcmp(typestr, "CERTIFICATE_DOMAIN_MISMATCH") == 0) + return CERTIFICATE_DOMAIN_MISMATCH_MESSAGE; else return MAX_MESSAGE; } @@ -167,6 +171,14 @@ static unsigned char message_blob_types[MAX_MESSAGE][5] = SQLITE_TEXT, // File system type SQLITE_TEXT, // Directory mounted on SQLITE_NULL // not used + }, + { + // CERTIFICATE_DOMAIN_MISMATCH_MESSAGE: The message column contains the certificate file + SQLITE_TEXT, // domain + SQLITE_NULL, // not used + SQLITE_NULL, // not used + SQLITE_NULL, // not used + SQLITE_NULL // not used } }; // Create message table in the database @@ -333,6 +345,8 @@ static int add_message(const enum message_type type, case SQLITE_NULL: /* Fall through */ default: + log_warn("add_message(type=%s, message=%s) - Excess property, binding NULL", + get_message_type_str(type), message); rc = sqlite3_bind_null(stmt, 3 + j); break; } @@ -653,6 +667,28 @@ static void format_inaccessible_adlist_message(char *plain, const int sizeof_pla free(escaped_address); } +static void format_certificate_domain_mismatch(char *plain, const int sizeof_plain, char *html, const int sizeof_html, + const char *certfile, const char*domain) +{ + if(snprintf(plain, sizeof_plain, "SSL/TLS certificate %s does not match domain %s!", certfile, domain) > sizeof_plain) + log_warn("format_certificate_domain_mismatch(): Buffer too small to hold plain message, warning truncated"); + + // Return early if HTML text is not required + if(sizeof_html < 1 || html == NULL) + return; + + char *escaped_certfile = escape_html(certfile); + char *escaped_domain = escape_html(domain); + + if(snprintf(html, sizeof_html, "SSL/TLS certificate %s does not match domain %s!", escaped_certfile, escaped_domain) > sizeof_html) + log_warn("format_certificate_domain_mismatch(): Buffer too small to hold HTML message, warning truncated"); + + if(escaped_certfile != NULL) + free(escaped_certfile); + if(escaped_domain != NULL) + free(escaped_domain); +} + int count_messages(const bool filter_dnsmasq_warnings) { int count = 0; @@ -876,6 +912,17 @@ bool format_messages(cJSON *array) break; } + + case CERTIFICATE_DOMAIN_MISMATCH_MESSAGE: + { + const char *certfile = (const char*)sqlite3_column_text(stmt, 3); + const char *domain = (const char*)sqlite3_column_text(stmt, 4); + + format_certificate_domain_mismatch(plain, sizeof(plain), html, sizeof(html), + certfile, domain); + + break; + } } // Add the plain message @@ -1095,3 +1142,19 @@ void logg_inaccessible_adlist(const int dbindex, const char *address) if(rowid == -1) log_err("logg_inaccessible_adlist(): Failed to add message to database"); } + +void log_certificate_domain_mismatch(const char *certfile, const char *domain) +{ + // Create message + char buf[2048]; + format_certificate_domain_mismatch(buf, sizeof(buf), NULL, 0, certfile, domain); + + // Log to FTL.log + log_warn("%s", buf); + + // Log to database + const int rowid = add_message(CERTIFICATE_DOMAIN_MISMATCH_MESSAGE, certfile, 1, domain); + + if(rowid == -1) + log_err("log_certificate_domain_mismatch(): Failed to add message to database"); +} diff --git a/src/database/message-table.h b/src/database/message-table.h index 52aff56c..196b406a 100644 --- a/src/database/message-table.h +++ b/src/database/message-table.h @@ -28,5 +28,6 @@ void logg_rate_limit_message(const char *clientIP, const unsigned int rate_limit void logg_warn_dnsmasq_message(char *message); void log_resource_shortage(const double load, const int nprocs, const int shmem, const int disk, const char *path, const char *msg); void logg_inaccessible_adlist(const int dbindex, const char *address); +void log_certificate_domain_mismatch(const char *certfile, const char *domain); #endif //MESSAGETABLE_H diff --git a/src/enums.h b/src/enums.h index bfbcff33..65e48182 100644 --- a/src/enums.h +++ b/src/enums.h @@ -270,6 +270,7 @@ enum message_type { DISK_MESSAGE, INACCESSIBLE_ADLIST_MESSAGE, DISK_MESSAGE_EXTENDED, + CERTIFICATE_DOMAIN_MISMATCH_MESSAGE, MAX_MESSAGE, } __attribute__ ((packed)); @@ -311,4 +312,13 @@ enum adlist_type { ADLIST_ALLOW } __attribute__ ((packed)); +enum cert_check { + CERT_FILE_NOT_FOUND, + CERT_CANNOT_PARSE_CERT, + CERT_CANNOT_PARSE_KEY, + CERT_DOMAIN_MISMATCH, + CERT_DOMAIN_MATCH, + CERT_OKAY +} __attribute__ ((packed)); + #endif // ENUMS_H diff --git a/src/webserver/http-common.c b/src/webserver/http-common.c index f7521dac..c1fdc275 100644 --- a/src/webserver/http-common.c +++ b/src/webserver/http-common.c @@ -524,6 +524,10 @@ void read_and_parse_payload(struct ftl_conn *api) // See https://www.w3.org/International/questions/qa-escapes#use char *__attribute__((malloc)) escape_html(const char *string) { + // If the string is NULL, return NULL + if(string == NULL) + return NULL; + // Allocate memory for escaped string char *escaped = calloc(strlen(string) * 6 + 1, sizeof(char)); if(!escaped) diff --git a/src/webserver/webserver.c b/src/webserver/webserver.c index 59f6d93a..95940e49 100644 --- a/src/webserver/webserver.c +++ b/src/webserver/webserver.c @@ -8,24 +8,26 @@ * 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 "webserver.h" +#include "FTL.h" +#include "webserver/webserver.h" // api_handler() -#include "../api/api.h" +#include "api/api.h" // send_http() #include "http-common.h" // struct config -#include "../config/config.h" +#include "config/config.h" // log_web() -#include "../log.h" +#include "log.h" // get_nprocs() #include // file_readable() -#include "../files.h" +#include "files.h" // generate_certificate() -#include "x509.h" +#include "webserver/x509.h" // allocate_lua(), free_lua(), init_lua(), request_handler() -#include "lua_web.h" +#include "webserver/lua_web.h" +// log_certificate_domain_mismatch() +#include "database/message-table.h" // Server context handle static struct mg_context *ctx = NULL; @@ -341,6 +343,10 @@ void http_init(void) if(file_readable(config.webserver.tls.cert.v.s)) { + if(read_certificate(config.webserver.tls.cert.v.s, config.webserver.domain.v.s, false) != CERT_DOMAIN_MATCH) + { + log_certificate_domain_mismatch(config.webserver.tls.cert.v.s, config.webserver.domain.v.s); + } options[++next_option] = "ssl_certificate"; options[++next_option] = config.webserver.tls.cert.v.s; diff --git a/src/webserver/x509.c b/src/webserver/x509.c index eff3ed59..ca0b2140 100644 --- a/src/webserver/x509.c +++ b/src/webserver/x509.c @@ -282,3 +282,239 @@ bool generate_certificate(const char* certfile, bool rsa, const char *domain) return true; } + +// 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) +{ + if(certfile == NULL && domain == NULL) + { + log_err("No certificate file specified\n"); + return CERT_FILE_NOT_FOUND; + } + + mbedtls_x509_crt crt; + mbedtls_pk_context key; + mbedtls_entropy_context entropy; + mbedtls_ctr_drbg_context ctr_drbg; + mbedtls_x509_crt_init(&crt); + mbedtls_pk_init(&key); + mbedtls_entropy_init(&entropy); + mbedtls_ctr_drbg_init(&ctr_drbg); + + printf("Reading certificate from %s ...\n\n", certfile); + + // Check if the file exists and is readable + if(access(certfile, R_OK) != 0) + { + log_err("Could not read certificate file: %s\n", strerror(errno)); + return CERT_FILE_NOT_FOUND; + } + + int rc = mbedtls_pk_parse_keyfile(&key, certfile, NULL, mbedtls_ctr_drbg_random, &ctr_drbg); + if (rc != 0) + { + log_err("Cannot parse key: Error code %d\n", rc); + return CERT_CANNOT_PARSE_KEY; + } + + rc = mbedtls_x509_crt_parse_file(&crt, certfile); + if (rc != 0) + { + log_err("Cannot parse certificate: Error code %d\n", rc); + return CERT_CANNOT_PARSE_CERT; + } + + // Parse mbedtls_x509_parse_subject_alt_names() + mbedtls_x509_sequence *sans = &crt.subject_alt_names; + bool found = false; + 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 + if(strncasecmp(domain, (char*)san.san.unstructured_name.p, san.san.unstructured_name.len) == 0) + { + found = true; + break; + } +next_san: + // Go to next SAN + sans = sans->next; + } + + // Also check against the common name (CN) field + char subject[MBEDTLS_X509_MAX_DN_NAME_SIZE]; + if(mbedtls_x509_dn_gets(subject, sizeof(subject), &crt.subject) > 0) + { + // Check subject == "CN=" + if(strlen(subject) > 3 && strncasecmp(subject, "CN=", 3) == 0 && strcasecmp(domain, subject + 3) == 0) + found = true; + // Check subject == "" + else if(strcasecmp(domain, subject) == 0) + found = true; + } + + + // Free resources + mbedtls_x509_crt_free(&crt); + mbedtls_pk_free(&key); + mbedtls_entropy_free(&entropy); + mbedtls_ctr_drbg_free(&ctr_drbg); + return found ? CERT_DOMAIN_MATCH : CERT_DOMAIN_MISMATCH; + } + + // else: Print verbose information about the certificate + char certinfo[BUFFER_SIZE] = { 0 }; + mbedtls_x509_crt_info(certinfo, BUFFER_SIZE, " ", &crt); + puts("Certificate (X.509):\n"); + puts(certinfo); + + if(!private_key) + goto end; + + puts("Private key:"); + const char *keytype = mbedtls_pk_get_name(&key); + printf(" Type: %s\n", keytype); + mbedtls_pk_type_t pk_type = mbedtls_pk_get_type(&key); + if(pk_type == MBEDTLS_PK_RSA) + { + mbedtls_rsa_context *rsa = mbedtls_pk_rsa(key); + printf(" RSA modulus: %zu bit\n", 8*mbedtls_rsa_get_len(rsa)); + mbedtls_mpi E, N, P, Q, D; + mbedtls_mpi_init(&E); // E = public exponent (public) + mbedtls_mpi_init(&N); // N = P * Q (public) + mbedtls_mpi_init(&P); // P = prime factor 1 (private) + mbedtls_mpi_init(&Q); // Q = prime factor 2 (private) + mbedtls_mpi_init(&D); // D = private exponent (private) + mbedtls_mpi DP, DQ, QP; + mbedtls_mpi_init(&DP); + mbedtls_mpi_init(&DQ); + mbedtls_mpi_init(&QP); + if(mbedtls_rsa_export(rsa, &N, &P, &Q, &D, &E) != 0 || + mbedtls_rsa_export_crt(rsa, &DP, &DQ, &QP) != 0) + { + puts(" could not export RSA parameters\n"); + return EXIT_FAILURE; + } + puts(" Core parameters:"); + if(mbedtls_mpi_write_file(" Exponent:\n E = 0x", &E, 16, NULL) != 0) + { + puts(" could not write MPI\n"); + return EXIT_FAILURE; + } + + if(mbedtls_mpi_write_file(" Modulus:\n N = 0x", &N, 16, NULL) != 0) + { + puts(" could not write MPI\n"); + return EXIT_FAILURE; + } + + if(mbedtls_mpi_cmp_mpi(&P, &Q) >= 0) + { + if(mbedtls_mpi_write_file(" Prime factors:\n P = 0x", &P, 16, NULL) != 0 || + mbedtls_mpi_write_file(" Q = 0x", &Q, 16, NULL) != 0) + { + puts(" could not write MPIs\n"); + return EXIT_FAILURE; + } + } + else + { + if(mbedtls_mpi_write_file(" Prime factors:\n Q = 0x", &Q, 16, NULL) != 0 || + mbedtls_mpi_write_file("\n P = 0x", &P, 16, NULL) != 0) + { + puts(" could not write MPIs\n"); + return EXIT_FAILURE; + } + } + + if(mbedtls_mpi_write_file(" Private exponent:\n D = 0x", &D, 16, NULL) != 0) + { + puts(" could not write MPI\n"); + return EXIT_FAILURE; + } + + mbedtls_mpi_free(&N); + mbedtls_mpi_free(&P); + mbedtls_mpi_free(&Q); + mbedtls_mpi_free(&D); + mbedtls_mpi_free(&E); + + puts(" CRT parameters:"); + if(mbedtls_mpi_write_file(" D mod (P-1):\n DP = 0x", &DP, 16, NULL) != 0 || + mbedtls_mpi_write_file(" D mod (Q-1):\n DQ = 0x", &DQ, 16, NULL) != 0 || + mbedtls_mpi_write_file(" Q^-1 mod P:\n QP = 0x", &QP, 16, NULL) != 0) + { + puts(" could not write MPIs\n"); + return EXIT_FAILURE; + } + + mbedtls_mpi_free(&DP); + mbedtls_mpi_free(&DQ); + mbedtls_mpi_free(&QP); + + } + else if(pk_type == MBEDTLS_PK_ECKEY) + { + mbedtls_ecp_keypair *ec = mbedtls_pk_ec(key); + mbedtls_ecp_curve_type ec_type = mbedtls_ecp_get_type(&ec->private_grp); + switch (ec_type) + { + case MBEDTLS_ECP_TYPE_NONE: + puts(" Curve type: Unknown"); + break; + case MBEDTLS_ECP_TYPE_SHORT_WEIERSTRASS: + puts(" Curve type: Short Weierstrass (y^2 = x^3 + a x + b)"); + break; + case MBEDTLS_ECP_TYPE_MONTGOMERY: + puts(" Curve type: Montgomery (y^2 = x^3 + a x^2 + x)"); + break; + } + const size_t bitlen = mbedtls_mpi_bitlen(&ec->private_d); + printf(" Bitlen: %zu bit\n", bitlen); + + mbedtls_mpi_write_file(" Private key:\n D = 0x", &ec->private_d, 16, NULL); + mbedtls_mpi_write_file(" Public key:\n X = 0x", &ec->MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(X), 16, NULL); + mbedtls_mpi_write_file(" Y = 0x", &ec->MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(Y), 16, NULL); + mbedtls_mpi_write_file(" Z = 0x", &ec->MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(Z), 16, NULL); + } + else + { + puts("Sorry, but FTL does not know how to print key information for this type\n"); + goto end; + } + + // Print private key in PEM format + mbedtls_pk_write_key_pem(&key, (unsigned char*)certinfo, BUFFER_SIZE); + puts("Private key (PEM):"); + puts(certinfo); + +end: + // Print public key in PEM format + mbedtls_pk_write_pubkey_pem(&key, (unsigned char*)certinfo, BUFFER_SIZE); + puts("Public key (PEM):"); + puts(certinfo); + + // Free resources + mbedtls_x509_crt_free(&crt); + mbedtls_pk_free(&key); + mbedtls_entropy_free(&entropy); + mbedtls_ctr_drbg_free(&ctr_drbg); + + return CERT_OKAY; +} diff --git a/src/webserver/x509.h b/src/webserver/x509.h index ad97537a..e59ee1a7 100644 --- a/src/webserver/x509.h +++ b/src/webserver/x509.h @@ -13,6 +13,9 @@ #include #include +#include "enums.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); #endif // X509_H diff --git a/test/test_suite.bats b/test/test_suite.bats index b5de84d8..fd264cf4 100644 --- a/test/test_suite.bats +++ b/test/test_suite.bats @@ -1368,6 +1368,89 @@ run bash -c 'curl -I --cacert /etc/pihole/test.crt --resolve pi.hole:443:127.0.0.1 https://pi.hole/' } +@test "X.509 certificate parser returns expected result" { + # We are getting the certificate from the config + run bash -c './pihole-FTL --read-x509' + printf "%s\n" "${lines[@]}" + [[ "${lines[0]}" == "Reading certificate from /etc/pihole/test.pem ..." ]] + [[ "${lines[1]}" == "Certificate (X.509):" ]] + [[ "${lines[2]}" == " cert. version : 3" ]] + [[ "${lines[3]}" == " serial number : 30:36:35:35:38:30:34:30:38:32:39:39:39:31:36" ]] + [[ "${lines[4]}" == " issuer name : CN=pi.hole" ]] + [[ "${lines[5]}" == " subject name : CN=pi.hole" ]] + [[ "${lines[6]}" == " issued on : 2001-01-01 00:00:00" ]] + [[ "${lines[7]}" == " expires on : 2030-12-31 23:59:59" ]] + [[ "${lines[8]}" == " signed using : ECDSA with SHA256" ]] + [[ "${lines[9]}" == " EC key size : 521 bits" ]] + [[ "${lines[10]}" == " basic constraints : CA=false" ]] + [[ "${lines[11]}" == "Public key (PEM):" ]] + [[ "${lines[12]}" == "-----BEGIN PUBLIC KEY-----" ]] + [[ "${lines[13]}" == "MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBQ51HeOLjSap1Xr+pnFQJqvBZc92T" ]] + [[ "${lines[14]}" == "XyL4KwIZdpsHl95Pc0Xcn8Xzyox0cWhMyycQgcGbIw3nuefCZaXfc3CuU30BPDdb" ]] + [[ "${lines[15]}" == "91h+rDhV4+VkEkANPBbgKQ6kCiHNtMAdugyaeHxzFpqegGGvgQ2l4Vp98l4M7zBC" ]] + [[ "${lines[16]}" == "G6K/RbZDlDvNUCgwElE=" ]] + [[ "${lines[17]}" == "-----END PUBLIC KEY-----" ]] + [[ "${lines[18]}" == "" ]] +} + +@test "X.509 certificate parser returns expected result (with private key)" { + # We are explicitly specifying the certificate file here + run bash -c './pihole-FTL --read-x509-key /etc/pihole/test.pem' + printf "%s\n" "${lines[@]}" + [[ "${lines[0]}" == "Reading certificate from /etc/pihole/test.pem ..." ]] + [[ "${lines[1]}" == "Certificate (X.509):" ]] + [[ "${lines[2]}" == " cert. version : 3" ]] + [[ "${lines[3]}" == " serial number : 30:36:35:35:38:30:34:30:38:32:39:39:39:31:36" ]] + [[ "${lines[4]}" == " issuer name : CN=pi.hole" ]] + [[ "${lines[5]}" == " subject name : CN=pi.hole" ]] + [[ "${lines[6]}" == " issued on : 2001-01-01 00:00:00" ]] + [[ "${lines[7]}" == " expires on : 2030-12-31 23:59:59" ]] + [[ "${lines[8]}" == " signed using : ECDSA with SHA256" ]] + [[ "${lines[9]}" == " EC key size : 521 bits" ]] + [[ "${lines[10]}" == " basic constraints : CA=false" ]] + [[ "${lines[11]}" == "Private key:" ]] + [[ "${lines[12]}" == " Type: EC" ]] + [[ "${lines[13]}" == " Curve type: Short Weierstrass (y^2 = x^3 + a x + b)" ]] + [[ "${lines[14]}" == " Bitlen: 518 bit" ]] + [[ "${lines[15]}" == " Private key:" ]] + [[ "${lines[16]}" == " D = 0x2CBE6CF8A913B445F211165B0473B7037B5B06187C8685AEF4A58354C7061C388173E0B00374A55CEAC7BB5886159C9D54B3C020564355A0FA71A55559304156D8"* ]] + [[ "${lines[17]}" == " Public key:" ]] + [[ "${lines[18]}" == " X = 0x01439D4778E2E349AA755EBFA99C5409AAF05973DD935F22F82B0219769B0797DE4F7345DC9FC5F3CA8C7471684CCB271081C19B230DE7B9E7C265A5DF7370AE537D"* ]] + [[ "${lines[19]}" == " Y = 0x013C375BF7587EAC3855E3E56412400D3C16E0290EA40A21CDB4C01DBA0C9A787C73169A9E8061AF810DA5E15A7DF25E0CEF30421BA2BF45B643943BCD5028301251"* ]] + [[ "${lines[20]}" == " Z = 0x01"* ]] + [[ "${lines[21]}" == "Private key (PEM):" ]] + [[ "${lines[22]}" == "-----BEGIN EC PRIVATE KEY-----" ]] + [[ "${lines[23]}" == "MIHcAgEBBEIALL5s+KkTtEXyERZbBHO3A3tbBhh8hoWu9KWDVMcGHDiBc+CwA3Sl" ]] + [[ "${lines[24]}" == "XOrHu1iGFZydVLPAIFZDVaD6caVVWTBBVtigBwYFK4EEACOhgYkDgYYABAFDnUd4" ]] + [[ "${lines[25]}" == "4uNJqnVev6mcVAmq8Flz3ZNfIvgrAhl2mweX3k9zRdyfxfPKjHRxaEzLJxCBwZsj" ]] + [[ "${lines[26]}" == "Dee558Jlpd9zcK5TfQE8N1v3WH6sOFXj5WQSQA08FuApDqQKIc20wB26DJp4fHMW" ]] + [[ "${lines[27]}" == "mp6AYa+BDaXhWn3yXgzvMEIbor9FtkOUO81QKDASUQ==" ]] + [[ "${lines[28]}" == "-----END EC PRIVATE KEY-----" ]] + [[ "${lines[29]}" == "Public key (PEM):" ]] + [[ "${lines[30]}" == "-----BEGIN PUBLIC KEY-----" ]] + [[ "${lines[31]}" == "MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBQ51HeOLjSap1Xr+pnFQJqvBZc92T" ]] + [[ "${lines[32]}" == "XyL4KwIZdpsHl95Pc0Xcn8Xzyox0cWhMyycQgcGbIw3nuefCZaXfc3CuU30BPDdb" ]] + [[ "${lines[33]}" == "91h+rDhV4+VkEkANPBbgKQ6kCiHNtMAdugyaeHxzFpqegGGvgQ2l4Vp98l4M7zBC" ]] + [[ "${lines[34]}" == "G6K/RbZDlDvNUCgwElE=" ]] + [[ "${lines[35]}" == "-----END PUBLIC KEY-----" ]] + [[ "${lines[36]}" == "" ]] +} + +@test "X.509 certificate parser can check if domain is included" { + run bash -c './pihole-FTL --read-x509-key /etc/pihole/test.pem pi.hole' + printf "%s\n" "${lines[@]}" + [[ "${lines[0]}" == "Reading certificate from /etc/pihole/test.pem ..." ]] + [[ "${lines[1]}" == "Certificate matches domain pi.hole" ]] + [[ "${lines[2]}" == "" ]] + [[ $status == 0 ]] + run bash -c './pihole-FTL --read-x509-key /etc/pihole/test.pem pi-hole.net' + printf "%s\n" "${lines[@]}" + [[ "${lines[0]}" == "Reading certificate from /etc/pihole/test.pem ..." ]] + [[ "${lines[1]}" == "Certificate does not match domain pi-hole.net" ]] + [[ "${lines[2]}" == "" ]] + [[ $status == 1 ]] +} + @test "Test embedded GZIP compressor" { run bash -c './pihole-FTL gzip test/pihole-FTL.db.sql' printf "Compression output:\n"