Merge pull request #1744 from pi-hole/new/check_certificate

Add X.509 parsing capabilities
This commit is contained in:
DL6ER
2023-11-10 22:14:01 +01:00
committed by GitHub
9 changed files with 474 additions and 8 deletions
+60
View File
@@ -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 [<input file>] [<domain>]\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");
+63
View File
@@ -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 <strong>%s</strong>!", 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");
}
+1
View File
@@ -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
+10
View File
@@ -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
+4
View File
@@ -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)
+14 -8
View File
@@ -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 <sys/sysinfo.h>
// 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;
+236
View File
@@ -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=<domain>"
if(strlen(subject) > 3 && strncasecmp(subject, "CN=", 3) == 0 && strcasecmp(domain, subject + 3) == 0)
found = true;
// Check subject == "<domain>"
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;
}
+3
View File
@@ -13,6 +13,9 @@
#include <mbedtls/entropy.h>
#include <mbedtls/ctr_drbg.h>
#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
+83
View File
@@ -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"