[monitoring-plugins] Fix build failures with OpenSSL 4.0 (#2326)

GitHub git at monitoring-plugins.org
Tue Sep 1 09:30:13 CEST 2026


    Module: monitoring-plugins
    Branch: master
    Commit: 6115aad6f2bf39800186e1d61be884a2ca3c21c9
    Author: loqs <loqs at users.noreply.github.com>
 Committer: GitHub <noreply at github.com>
      Date: Tue Sep  1 08:24:11 2026 +0100
       URL: https://www.monitoring-plugins.org/repositories/monitoring-plugins/commit/?id=6115aad6

Fix build failures with OpenSSL 4.0 (#2326)

* sslutils: Add converter utils to fix OpenSSL 4.0 compatibility
* Add tests for sslutils

---

 .gitignore                    |   1 +
 configure.ac                  |   2 +-
 plugins/Makefile.am           |   8 +-
 plugins/sslutils.c            | 207 ++++++-------------------
 plugins/tests/test_sslutils.c | 341 ++++++++++++++++++++++++++++++++++++++++++
 plugins/tests/test_sslutils.t |   6 +
 6 files changed, 403 insertions(+), 162 deletions(-)

diff --git a/.gitignore b/.gitignore
index da5b6970..6927d8ae 100644
--- a/.gitignore
+++ b/.gitignore
@@ -231,6 +231,7 @@ plugins/check_disk.d/.dirstamp
 # /plugins/tests/
 /plugins/tests/Makefile
 /plugins/tests/Makefile.in
+/plugins/tests/test_sslutils
 /plugins/tests/test_utils
 /plugins/tests/test_check_disk
 /plugins/tests/test_check_swap
diff --git a/configure.ac b/configure.ac
index 0523f791..2bda91ab 100644
--- a/configure.ac
+++ b/configure.ac
@@ -175,7 +175,7 @@ if test "$enable_libtap" = "yes" ; then
 	EXTRA_TEST="test_utils test_tcp test_cmd test_base64 test_generic_output"
 	AC_SUBST(EXTRA_TEST)
 
-	EXTRA_PLUGIN_TESTS="tests/test_check_swap tests/test_check_disk"
+	EXTRA_PLUGIN_TESTS="tests/test_check_swap tests/test_check_disk tests/test_sslutils"
 	AC_SUBST(EXTRA_PLUGIN_TESTS)
 fi
 
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
index 326e4b3a..5455d6fe 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -49,13 +49,15 @@ EXTRA_PROGRAMS = check_mysql check_radius check_pgsql check_hpjd \
 	\
 	tests/test_check_swap \
 	tests/test_check_snmp \
-	tests/test_check_disk
+	tests/test_check_disk \
+	tests/test_sslutils
 
 SUBDIRS = picohttpparser
 
 np_test_scripts = tests/test_check_swap.t \
 				  tests/test_check_snmp.t \
-				  tests/test_check_disk.t
+				  tests/test_check_disk.t \
+				  tests/test_sslutils.t
 
 EXTRA_DIST = t \
 			 tests \
@@ -192,6 +194,8 @@ tests_test_check_snmp_LDADD = $(BASEOBJS) $(tap_ldflags) -ltap
 tests_test_check_snmp_SOURCES = tests/test_check_snmp.c check_snmp.d/check_snmp_helpers.c
 tests_test_check_disk_LDADD = $(BASEOBJS) $(tap_ldflags) check_disk.d/utils_disk.c -ltap
 tests_test_check_disk_SOURCES = tests/test_check_disk.c
+tests_test_sslutils_LDADD = $(SSLOBJS) $(tap_ldflags) -ltap
+tests_test_sslutils_SOURCES = tests/test_sslutils.c
 
 ##############################################################################
 # secondary dependencies
diff --git a/plugins/sslutils.c b/plugins/sslutils.c
index 9151f722..baf7c65a 100644
--- a/plugins/sslutils.c
+++ b/plugins/sslutils.c
@@ -47,6 +47,33 @@ int np_net_ssl_init_with_hostname_and_version(int sd, char *host_name, int versi
 	return np_net_ssl_init_with_hostname_version_and_cert(sd, host_name, version, NULL, NULL);
 }
 
+#	ifdef MOPL_USE_OPENSSL
+int np_net_asn1_time_to_time_t(const ASN1_TIME *asn1_time, time_t *out) {
+	struct tm tm = {};
+	if (!ASN1_TIME_to_tm(asn1_time, &tm)) {
+		return 0;
+	}
+	*out = timegm(&tm);
+	if (*out == (time_t)-1) {
+		return 0;
+	}
+	return 1;
+}
+
+void np_net_format_timestamp(time_t t, char *buf, size_t buflen) {
+	char *tz = getenv("TZ");
+	setenv("TZ", "GMT", 1);
+	tzset();
+	strftime(buf, buflen, "%c %z", localtime(&t));
+	if (tz) {
+		setenv("TZ", tz, 1);
+	} else {
+		unsetenv("TZ");
+	}
+	tzset();
+}
+#endif /* MOPL_USE_OPENSSL */
+
 int np_net_ssl_init_with_hostname_version_and_cert(int sd, char *host_name, int version, char *cert,
 												   char *privkey) {
 	long options = 0;
@@ -213,55 +240,16 @@ mp_state_enum np_net_ssl_check_certificate(X509 *certificate, int days_till_exp_
 	}
 
 	/* Retrieve timestamp of certificate */
-	ASN1_STRING *tm = X509_get_notAfter(certificate);
-
-	int offset = 0;
-	struct tm stamp = {};
-	/* Generate tm structure to process timestamp */
-	if (tm->type == V_ASN1_UTCTIME) {
-		if (tm->length < 10) {
-			printf("%s\n", _("CRITICAL - Wrong time format in certificate."));
-			return STATE_CRITICAL;
-		}
-		stamp.tm_year = (tm->data[0] - '0') * 10 + (tm->data[1] - '0');
-		if (stamp.tm_year < 50) {
-			stamp.tm_year += 100;
-		}
-		offset = 0;
-
-	} else {
-		if (tm->length < 12) {
-			printf("%s\n", _("CRITICAL - Wrong time format in certificate."));
-			return STATE_CRITICAL;
-		}
-		stamp.tm_year = (tm->data[0] - '0') * 1000 + (tm->data[1] - '0') * 100 +
-						(tm->data[2] - '0') * 10 + (tm->data[3] - '0');
-		stamp.tm_year -= 1900;
-		offset = 2;
+	const ASN1_TIME *asn1_not_after = X509_get_notAfter(certificate);
+	time_t expiry_time;
+	if (!np_net_asn1_time_to_time_t(asn1_not_after, &expiry_time)) {
+		printf("%s\n", _("CRITICAL - Wrong time format in certificate."));
+		return STATE_CRITICAL;
 	}
-	stamp.tm_mon = (tm->data[2 + offset] - '0') * 10 + (tm->data[3 + offset] - '0') - 1;
-	stamp.tm_mday = (tm->data[4 + offset] - '0') * 10 + (tm->data[5 + offset] - '0');
-	stamp.tm_hour = (tm->data[6 + offset] - '0') * 10 + (tm->data[7 + offset] - '0');
-	stamp.tm_min = (tm->data[8 + offset] - '0') * 10 + (tm->data[9 + offset] - '0');
-	stamp.tm_sec = (tm->data[10 + offset] - '0') * 10 + (tm->data[11 + offset] - '0');
-	stamp.tm_isdst = -1;
-
-	time_t tm_t = timegm(&stamp);
-	float time_left = difftime(tm_t, time(NULL));
+	float time_left = difftime(expiry_time, time(NULL));
 	int days_left = time_left / 86400;
-	char *tz = getenv("TZ");
-	setenv("TZ", "GMT", 1);
-	tzset();
-
 	char timestamp[50] = "";
-	strftime(timestamp, 50, "%c %z", localtime(&tm_t));
-	if (tz) {
-		setenv("TZ", tz, 1);
-	} else {
-		unsetenv("TZ");
-	}
-
-	tzset();
+	np_net_format_timestamp(expiry_time, timestamp, sizeof(timestamp));
 
 	int time_remaining;
 	mp_state_enum status = STATE_UNKNOWN;
@@ -341,66 +329,15 @@ retrieve_expiration_time_result np_net_ssl_get_cert_expiration(X509 *certificate
 	}
 
 	/* Retrieve timestamp of certificate */
-	ASN1_STRING *expiration_timestamp = X509_get_notAfter(certificate);
-
-	int offset = 0;
-	struct tm stamp = {};
-	/* Generate tm structure to process timestamp */
-	if (expiration_timestamp->type == V_ASN1_UTCTIME) {
-		if (expiration_timestamp->length < 10) {
-			result.errors = WRONG_TIME_FORMAT_IN_CERTIFICATE;
-			return result;
-		}
-
-		stamp.tm_year =
-			(expiration_timestamp->data[0] - '0') * 10 + (expiration_timestamp->data[1] - '0');
-		if (stamp.tm_year < 50) {
-			stamp.tm_year += 100;
-		}
-		offset = 0;
-	} else {
-		if (expiration_timestamp->length < 12) {
-			result.errors = WRONG_TIME_FORMAT_IN_CERTIFICATE;
-			return result;
-		}
-
-		stamp.tm_year = (expiration_timestamp->data[0] - '0') * 1000 +
-						(expiration_timestamp->data[1] - '0') * 100 +
-						(expiration_timestamp->data[2] - '0') * 10 +
-						(expiration_timestamp->data[3] - '0');
-		stamp.tm_year -= 1900;
-		offset = 2;
+	const ASN1_TIME *asn1_not_after = X509_get_notAfter(certificate);
+	time_t expiry_time;
+	if (!np_net_asn1_time_to_time_t(asn1_not_after, &expiry_time)) {
+		result.errors = WRONG_TIME_FORMAT_IN_CERTIFICATE;
+		return result;
 	}
-	stamp.tm_mon = (expiration_timestamp->data[2 + offset] - '0') * 10 +
-				   (expiration_timestamp->data[3 + offset] - '0') - 1;
-	stamp.tm_mday = (expiration_timestamp->data[4 + offset] - '0') * 10 +
-					(expiration_timestamp->data[5 + offset] - '0');
-	stamp.tm_hour = (expiration_timestamp->data[6 + offset] - '0') * 10 +
-					(expiration_timestamp->data[7 + offset] - '0');
-	stamp.tm_min = (expiration_timestamp->data[8 + offset] - '0') * 10 +
-				   (expiration_timestamp->data[9 + offset] - '0');
-	stamp.tm_sec = (expiration_timestamp->data[10 + offset] - '0') * 10 +
-				   (expiration_timestamp->data[11 + offset] - '0');
-	stamp.tm_isdst = -1;
-
-	time_t tm_t = timegm(&stamp);
-	double time_left = difftime(tm_t, time(NULL));
+	double time_left = difftime(expiry_time, time(NULL));
 	result.remaining_seconds = time_left;
 
-	char *timezone = getenv("TZ");
-	setenv("TZ", "GMT", 1);
-	tzset();
-
-	char timestamp[50] = "";
-	strftime(timestamp, 50, "%c %z", localtime(&tm_t));
-	if (timezone) {
-		setenv("TZ", timezone, 1);
-	} else {
-		unsetenv("TZ");
-	}
-
-	tzset();
-
 	X509_free(certificate);
 
 	return result;
@@ -482,65 +419,17 @@ mp_subcheck mp_net_ssl_check_certificate(X509 *certificate, int days_till_exp_wa
 	}
 
 	/* Retrieve timestamp of certificate */
-	ASN1_STRING *expiry_timestamp = X509_get_notAfter(certificate);
-
-	int offset = 0;
-	struct tm stamp = {};
-	/* Generate tm structure to process timestamp */
-	if (expiry_timestamp->type == V_ASN1_UTCTIME) {
-		if (expiry_timestamp->length < 10) {
-			xasprintf(&sc_cert.output, _("Wrong time format in certificate"));
-			sc_cert = mp_set_subcheck_state(sc_cert, STATE_CRITICAL);
-			return sc_cert;
-		}
-
-		stamp.tm_year = (expiry_timestamp->data[0] - '0') * 10 + (expiry_timestamp->data[1] - '0');
-		if (stamp.tm_year < 50) {
-			stamp.tm_year += 100;
-		}
-
-		offset = 0;
-	} else {
-		if (expiry_timestamp->length < 12) {
-			xasprintf(&sc_cert.output, _("Wrong time format in certificate"));
-			sc_cert = mp_set_subcheck_state(sc_cert, STATE_CRITICAL);
-			return sc_cert;
-		}
-		stamp.tm_year = (expiry_timestamp->data[0] - '0') * 1000 +
-						(expiry_timestamp->data[1] - '0') * 100 +
-						(expiry_timestamp->data[2] - '0') * 10 + (expiry_timestamp->data[3] - '0');
-		stamp.tm_year -= 1900;
-		offset = 2;
+	const ASN1_TIME *asn1_not_after = X509_get_notAfter(certificate);
+	time_t expiry_time;
+	if (!np_net_asn1_time_to_time_t(asn1_not_after, &expiry_time)) {
+		xasprintf(&sc_cert.output, _("Wrong time format in certificate"));
+		sc_cert = mp_set_subcheck_state(sc_cert, STATE_CRITICAL);
+		return sc_cert;
 	}
-
-	stamp.tm_mon = (expiry_timestamp->data[2 + offset] - '0') * 10 +
-				   (expiry_timestamp->data[3 + offset] - '0') - 1;
-	stamp.tm_mday = (expiry_timestamp->data[4 + offset] - '0') * 10 +
-					(expiry_timestamp->data[5 + offset] - '0');
-	stamp.tm_hour = (expiry_timestamp->data[6 + offset] - '0') * 10 +
-					(expiry_timestamp->data[7 + offset] - '0');
-	stamp.tm_min = (expiry_timestamp->data[8 + offset] - '0') * 10 +
-				   (expiry_timestamp->data[9 + offset] - '0');
-	stamp.tm_sec = (expiry_timestamp->data[10 + offset] - '0') * 10 +
-				   (expiry_timestamp->data[11 + offset] - '0');
-	stamp.tm_isdst = -1;
-
-	time_t tm_t = timegm(&stamp);
-	double time_left = difftime(tm_t, time(NULL));
+	double time_left = difftime(expiry_time, time(NULL));
 	int days_left = (int)(time_left / 86400);
-	char *timeZone = getenv("TZ");
-	setenv("TZ", "GMT", 1);
-	tzset();
-
 	char timestamp[50] = "";
-	strftime(timestamp, 50, "%c %z", localtime(&tm_t));
-	if (timeZone) {
-		setenv("TZ", timeZone, 1);
-	} else {
-		unsetenv("TZ");
-	}
-
-	tzset();
+	np_net_format_timestamp(expiry_time, timestamp, sizeof(timestamp));
 
 	int time_remaining;
 	if (days_left > 0 && days_left <= days_till_exp_warn) {
diff --git a/plugins/tests/test_sslutils.c b/plugins/tests/test_sslutils.c
new file mode 100644
index 00000000..3bb0c3bd
--- /dev/null
+++ b/plugins/tests/test_sslutils.c
@@ -0,0 +1,341 @@
+/*****************************************************************************
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ *****************************************************************************/
+
+#include "common.h"
+#include "netutils.h"
+#include "../tap/tap.h"
+#include "../../lib/output.h"
+
+#ifdef HAVE_SSL
+#	include <openssl/x509.h>
+#	include <openssl/pem.h>
+#	include <openssl/err.h>
+
+/* prototypes for internal functions*/
+int np_net_asn1_time_to_time_t(const ASN1_TIME *asn1_time, time_t *out);
+void np_net_format_timestamp(time_t t, char *buf, size_t buflen);
+mp_state_enum np_net_ssl_check_certificate(X509 *certificate, int days_till_exp_warn,
+										   int days_till_exp_crit);
+retrieve_expiration_time_result np_net_ssl_get_cert_expiration(X509 *certificate);
+mp_subcheck mp_net_ssl_check_certificate(X509 *certificate, int days_till_exp_warn,
+										 int days_till_exp_crit);
+#endif
+
+const char *progname = "test_sslutils";
+void print_usage(void) {}
+
+#ifdef HAVE_SSL
+static X509 *create_test_cert(long seconds) {
+	X509 *cert = X509_new();
+	if (!cert) {
+		return NULL;
+	}
+
+	ASN1_INTEGER_set(X509_get_serialNumber(cert), 1);
+	X509_set_version(cert, 2);
+
+	X509_gmtime_adj(X509_get_notBefore(cert), 0);
+	X509_gmtime_adj(X509_get_notAfter(cert), seconds);
+
+	X509_NAME *name = X509_get_subject_name(cert);
+	X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char *)"test.example.com", -1,
+							   -1, 0);
+	X509_set_issuer_name(cert, name);
+
+	EVP_PKEY_CTX *kctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
+	if (!kctx) {
+		X509_free(cert);
+		return NULL;
+	}
+	EVP_PKEY *pkey = NULL;
+	if (EVP_PKEY_keygen_init(kctx) <= 0 || EVP_PKEY_CTX_set_rsa_keygen_bits(kctx, 2048) <= 0 ||
+		EVP_PKEY_keygen(kctx, &pkey) <= 0) {
+		EVP_PKEY_CTX_free(kctx);
+		X509_free(cert);
+		return NULL;
+	}
+	EVP_PKEY_CTX_free(kctx);
+
+	X509_set_pubkey(cert, pkey);
+	X509_sign(cert, pkey, EVP_sha256());
+	EVP_PKEY_free(pkey);
+
+	return cert;
+}
+#endif
+
+int main(int argc, char **argv) {
+#ifdef HAVE_SSL
+	plan_tests(44);
+#	define TIME_DELTA 5
+
+	/* Valid cert expiring in 30 days */
+	X509 *cert = create_test_cert(60 * 60 * 24 * 30);
+	ok(cert != NULL, "Created test cert expiring in 30 days");
+	mp_state_enum mp_result;
+	if (cert) {
+		mp_result = np_net_ssl_check_certificate(cert, 14, 7);
+		ok(mp_result == STATE_OK, "Cert expiring in 30 days returns STATE_OK (threshold 14/7)");
+	} else {
+		skip(1, "Cert creation failed");
+	}
+
+	/* Valid cert expiring in 10 days */
+	cert = create_test_cert(60 * 60 * 24 * 10);
+	ok(cert != NULL, "Created test cert expiring in 10 days");
+	if (cert) {
+		mp_result = np_net_ssl_check_certificate(cert, 14, 7);
+		ok(mp_result == STATE_WARNING,
+		   "Cert expiring in 10 days returns STATE_WARNING (threshold 14/7)");
+	} else {
+		skip(1, "Cert creation failed");
+	}
+
+	/* Valid cert expiring in 5 days */
+	cert = create_test_cert(60 * 60 * 24 * 5);
+	ok(cert != NULL, "Created test cert expiring in 5 days");
+	if (cert) {
+		mp_result = np_net_ssl_check_certificate(cert, 14, 7);
+		ok(mp_result == STATE_CRITICAL,
+		   "Cert expiring in 5 days returns STATE_CRITICAL (threshold 14/7)");
+	} else {
+		skip(1, "Cert creation failed");
+	}
+
+	/* Valid cert expiring in 1 hour */
+	cert = create_test_cert(60 * 60 * 1);
+	ok(cert != NULL, "Created test cert expiring in 1 hour");
+	if (cert) {
+		mp_result = np_net_ssl_check_certificate(cert, 14, 7);
+		ok(mp_result == STATE_CRITICAL,
+		   "Cert expiring in 1 hour returns CRITICAL (threshold 14/7)");
+	} else {
+		skip(1, "Cert creation failed");
+	}
+
+	/* Cert expiring in 30 minutes */
+	cert = create_test_cert(60 * 30);
+	ok(cert != NULL, "Created test cert expiring in 30 minutes");
+	if (cert) {
+		mp_result = np_net_ssl_check_certificate(cert, 14, 7);
+		ok(mp_result == STATE_CRITICAL,
+		   "Cert expiring in 30 minutes returns CRITICAL (threshold 14/7)");
+	} else {
+		skip(1, "Cert creation failed");
+	}
+
+	/* Expired cert - should be CRITICAL */
+	cert = create_test_cert(60 * 60 * 24 * -10);
+	ok(cert != NULL, "Created test cert that expired 10 days ago");
+	if (cert) {
+		mp_result = np_net_ssl_check_certificate(cert, 14, 7);
+		ok(mp_result == STATE_CRITICAL, "Expired cert returns STATE_CRITICAL");
+	} else {
+		skip(1, "Cert creation failed");
+	}
+
+	/* np_net_ssl_get_cert_expiration - NULL certificate */
+	retrieve_expiration_time_result expiration_time_result = np_net_ssl_get_cert_expiration(NULL);
+	ok(expiration_time_result.errors == NO_SERVER_CERTIFICATE_PRESENT,
+	   "NULL certificate returns NO_SERVER_CERTIFICATE_PRESENT error");
+
+	/* np_net_ssl_get_cert_expiration - valid cert */
+	cert = create_test_cert(60 * 60 * 24 * 30);
+	if (cert) {
+		expiration_time_result = np_net_ssl_get_cert_expiration(cert);
+		ok(expiration_time_result.errors == ALL_OK, "Valid cert returns ALL_OK error code");
+		ok(expiration_time_result.remaining_seconds > (60 * 60 * 24 * 30) - TIME_DELTA &&
+			   expiration_time_result.remaining_seconds < (60 * 60 * 24 * 30) + TIME_DELTA,
+		   "remaining_seconds is 30 days +- %i", TIME_DELTA);
+	} else {
+		skip(2, "Cert creation failed");
+	}
+
+	/* np_net_ssl_get_cert_expiration - expired cert */
+	cert = create_test_cert(60 * 60 * 24 * -10);
+	if (cert) {
+		expiration_time_result = np_net_ssl_get_cert_expiration(cert);
+		ok(expiration_time_result.errors == ALL_OK, "Expired cert returns ALL_OK error code");
+		ok(expiration_time_result.remaining_seconds < 0,
+		   "remaining_seconds is negative for expired cert");
+	} else {
+		skip(2, "Cert creation failed");
+	}
+
+	struct tm target_date = {0};
+	target_date.tm_year = 2051 - 1900;
+	target_date.tm_mon = 0;
+	target_date.tm_mday = 1;
+	target_date.tm_hour = 0;
+	target_date.tm_min = 0;
+	target_date.tm_sec = 0;
+	target_date.tm_isdst = -1;
+	time_t time_2051 = mktime(&target_date);
+	time_t time_now = time(NULL);
+	long seconds_remaining = time_2051 - time_now;
+	cert = create_test_cert(seconds_remaining);
+
+	/* GENERALIZEDTIME cert (year > 2049) */
+	ok(cert != NULL, "Created GENERALIZEDTIME cert (2051)");
+	if (cert) {
+		mp_result = np_net_ssl_check_certificate(cert, 14, 7);
+		ok(mp_result == STATE_OK, "GENERALIZEDTIME cert expiring in 2051 returns STATE_OK");
+	} else {
+		skip(1, "Cert creation failed");
+	}
+
+	/* np_net_ssl_get_cert_expiration - GENERALIZEDTIME cert */
+	cert = create_test_cert(seconds_remaining);
+	if (cert) {
+		expiration_time_result = np_net_ssl_get_cert_expiration(cert);
+		ok(expiration_time_result.errors == ALL_OK,
+		   "GENERALIZEDTIME cert returns ALL_OK error code");
+		ok(expiration_time_result.remaining_seconds > seconds_remaining - TIME_DELTA &&
+			   expiration_time_result.remaining_seconds < seconds_remaining + TIME_DELTA,
+		   "remaining_seconds is correct for Midnight on January 1st 2051 +- %i", TIME_DELTA);
+	} else {
+		skip(2, "Cert creation failed");
+	}
+
+	/* mp_net_ssl_check_certificate - NULL certificate */
+	mp_subcheck sc_result = mp_net_ssl_check_certificate(NULL, 14, 7);
+	ok(sc_result.state == STATE_CRITICAL, "mp: NULL certificate returns STATE_CRITICAL");
+	ok(sc_result.output != NULL, "mp: NULL certificate sets output");
+
+	/* mp_net_ssl_check_certificate - valid cert expiring in 30 days */
+	cert = create_test_cert(60 * 60 * 24 * 30);
+	if (cert) {
+		sc_result = mp_net_ssl_check_certificate(cert, 14, 7);
+		ok(sc_result.state == STATE_OK,
+		   "mp: Cert expiring in 30 days returns STATE_OK (threshold 14/7)");
+		ok(sc_result.output != NULL, "mp: Cert expiring in 30 days sets output");
+	} else {
+		skip(2, "Cert creation failed");
+	}
+
+	/* mp_net_ssl_check_certificate - valid cert expiring in 10 days */
+	cert = create_test_cert(60 * 60 * 24 * 10);
+	if (cert) {
+		sc_result = mp_net_ssl_check_certificate(cert, 14, 7);
+		ok(sc_result.state == STATE_WARNING,
+		   "mp: Cert expiring in 10 days returns STATE_WARNING (threshold 14/7)");
+		ok(sc_result.output != NULL, "mp: Cert expiring in 10 days sets output");
+	} else {
+		skip(2, "Cert creation failed");
+	}
+
+	/*np_net_asn1_time_to_time_t with valid UTCTIME */
+	ASN1_TIME *asn1_time = ASN1_TIME_new();
+	ASN1_TIME_set_string(asn1_time, "301128210211Z"); /* Nov 28, 2030 21:02:11 UTC */
+	time_t result;
+	ok(np_net_asn1_time_to_time_t(asn1_time, &result) == 1,
+	   "np_net_asn1_time_to_time_t succeeds with valid UTCTIME");
+	struct tm *tm = gmtime(&result);
+	ok(tm->tm_year == 130 && tm->tm_mon == 10 && tm->tm_mday == 28 && tm->tm_hour == 21 &&
+		   tm->tm_min == 2 && tm->tm_sec == 11,
+	   "UTCTIME correctly converts to Nov 28, 2030 21:02:11 UTC");
+	ASN1_TIME_free(asn1_time);
+
+	/* np_net_asn1_time_to_time_t with valid GENERALIZEDTIME */
+	asn1_time = ASN1_TIME_new();
+	ASN1_TIME_set_string(asn1_time, "20510701120000Z"); /* Jul 1, 2051 12:00:00 UTC */
+	ok(np_net_asn1_time_to_time_t(asn1_time, &result) == 1,
+	   "np_net_asn1_time_to_time_t succeeds with valid GENERALIZEDTIME");
+	tm = gmtime(&result);
+	ok(tm->tm_year == 151 && tm->tm_mon == 6 && tm->tm_mday == 1 && tm->tm_hour == 12 &&
+		   tm->tm_min == 0 && tm->tm_sec == 0,
+	   "GENERALIZEDTIME correctly converts to Jul 1, 2051 12:00:00 UTC");
+	ASN1_TIME_free(asn1_time);
+
+	/* np_net_asn1_time_to_time_t fails with empty ASN1_TIME */
+	asn1_time = ASN1_TIME_new();
+	ok(np_net_asn1_time_to_time_t(asn1_time, &result) == 0,
+	   "np_net_asn1_time_to_time_t fails with empty ASN1_TIME");
+	ASN1_TIME_free(asn1_time);
+
+	/* np_net_format_timestamp produces correct GMT output */
+	asn1_time = ASN1_TIME_new();
+	ASN1_TIME_set_string(asn1_time, "301128210211Z"); /* Nov 28, 2030 21:02:11 UTC */
+	time_t t;
+	np_net_asn1_time_to_time_t(asn1_time, &t);
+	char buf[100] = "";
+	np_net_format_timestamp(t, buf, sizeof(buf));
+	ok(strlen(buf) > 0, "np_net_format_timestamp produces non-empty output");
+	ok(strstr(buf, "+0000") != NULL,
+	   "np_net_format_timestamp output contains +0000 for GMT timezone");
+	ASN1_TIME_free(asn1_time);
+
+	/* mp_net_ssl_check_certificate - valid cert expiring in 5 days */
+	cert = create_test_cert(60 * 60 * 24 * 5);
+	if (cert) {
+		sc_result = mp_net_ssl_check_certificate(cert, 14, 7);
+		ok(sc_result.state == STATE_CRITICAL,
+		   "mp: Cert expiring in 5 days returns STATE_CRITICAL (threshold 14/7)");
+		ok(sc_result.output != NULL, "mp: Cert expiring in 5 days sets output");
+	} else {
+		skip(2, "Cert creation failed");
+	}
+
+	/* mp_net_ssl_check_certificate - valid cert expiring in 1 hour */
+	cert = create_test_cert(60 * 60 * 1);
+	if (cert) {
+		sc_result = mp_net_ssl_check_certificate(cert, 14, 7);
+		ok(sc_result.state == STATE_CRITICAL,
+		   "mp: Cert expiring in 1 hour returns CRITICAL (threshold 14/7)");
+		ok(sc_result.output != NULL, "mp: Cert expiring in 1 hour sets output");
+	} else {
+		skip(2, "Cert creation failed");
+	}
+
+	/* mp_net_ssl_check_certificate - cert expiring in 30 minutes */
+	cert = create_test_cert(60 * 30);
+	if (cert) {
+		sc_result = mp_net_ssl_check_certificate(cert, 14, 7);
+		ok(sc_result.state == STATE_CRITICAL,
+		   "mp: Cert expiring in 30 minutes returns CRITICAL (threshold 14/7)");
+		ok(sc_result.output != NULL, "mp: Cert expiring in 30 minutes sets output");
+	} else {
+		skip(2, "Cert creation failed");
+	}
+
+	/* mp_net_ssl_check_certificate - expired cert */
+	cert = create_test_cert(60 * 60 * 24 * -10);
+	if (cert) {
+		sc_result = mp_net_ssl_check_certificate(cert, 14, 7);
+		ok(sc_result.state == STATE_CRITICAL, "mp: Expired cert returns STATE_CRITICAL");
+		ok(sc_result.output != NULL, "mp: Expired cert sets output");
+	} else {
+		skip(2, "Cert creation failed");
+	}
+
+	/* mp_net_ssl_check_certificate - GENERALIZEDTIME cert (year > 2049) */
+	cert = create_test_cert(seconds_remaining);
+	if (cert) {
+		sc_result = mp_net_ssl_check_certificate(cert, 14, 7);
+		ok(sc_result.state == STATE_OK,
+		   "mp: GENERALIZEDTIME cert expiring in 2051 returns STATE_OK");
+		ok(sc_result.output != NULL, "mp: GENERALIZEDTIME cert sets output");
+	} else {
+		skip(2, "Cert creation failed");
+	}
+#else
+	plan_skip_all("SSL support not compiled in");
+#endif
+
+	return exit_status();
+}
diff --git a/plugins/tests/test_sslutils.t b/plugins/tests/test_sslutils.t
new file mode 100644
index 00000000..0b60a65f
--- /dev/null
+++ b/plugins/tests/test_sslutils.t
@@ -0,0 +1,6 @@
+#!/usr/bin/perl
+use Test::More;
+if (! -e "./test_sslutils") {
+	plan skip_all => "./test_sslutils not compiled - please enable libtap library to test";
+}
+exec "./test_sslutils";



More information about the Commits mailing list