From cc0577a80367e740470455b0e85b8c02347dc5a8 Mon Sep 17 00:00:00 2001 From: Bob Beck Date: Mon, 6 Oct 2025 11:01:39 -0600 Subject: [PATCH] Add unit test for X509 temporal validity functions. --- include/crypto/x509.h | 2 + test/x509_internal_test.c | 295 +++++++++++++++++++++++++++++++++++++- 2 files changed, 295 insertions(+), 2 deletions(-) diff --git a/include/crypto/x509.h b/include/crypto/x509.h index bb847bb8d0..0ed3316fd2 100644 --- a/include/crypto/x509.h +++ b/include/crypto/x509.h @@ -401,5 +401,7 @@ int ossl_x509_compare_asn1_time(const X509_VERIFY_PARAM *vpm, const ASN1_TIME *time, int *comparison); int ossl_x509_check_certificate_times(const X509_VERIFY_PARAM *vpm, X509 *x, int *error); +/* No error callback if depth < 0 */ +int ossl_x509_check_cert_time(X509_STORE_CTX *ctx, X509 *x, int depth); #endif /* OSSL_CRYPTO_X509_H */ diff --git a/test/x509_internal_test.c b/test/x509_internal_test.c index 7cd04d84f6..e2ee729a67 100644 --- a/test/x509_internal_test.c +++ b/test/x509_internal_test.c @@ -1,5 +1,4 @@ -/* - * Copyright 2016-2025 The OpenSSL Project Authors. All Rights Reserved. +/* Copyright 2016-2025 The OpenSSL Project Authors. All Rights Reserved. * * Licensed under the Apache License 2.0 (the "License"). You may not use * this file except in compliance with the License. You can obtain a copy @@ -14,8 +13,10 @@ #include #include +#include #include "testutil.h" #include "internal/nelem.h" +#include "crypto/x509.h" /********************************************************************** * @@ -221,10 +222,300 @@ static int tests_X509_PURPOSE(void) && TEST_int_eq(X509_PURPOSE_get_id(xp), X509_PURPOSE_DEFAULT_ANY); } +/* 0000-01-01 00:00:00 UTC */ +#define MIN_CERT_TIME INT64_C(-62167219200) +/* 9999-12-31 23:59:59 UTC */ +#define MAX_CERT_TIME INT64_C(253402300799) +/* 1950-01-01 00:00:00 UTC */ +#define MIN_UTC_TIME INT64_C(-631152000) +/* 2049-12-31 23:59:59 UTC */ +#define MAX_UTC_TIME INT64_C(2524607999) + +typedef struct { + int64_t NotBefore; + int64_t NotAfter; +} CERT_TEST_DATA; + +/* clang-format off */ +static CERT_TEST_DATA cert_test_data[] = { + { 0, 0 }, + { 0, 1 }, + { 1, 1 }, + { -1, 0 }, + { -1, 1 }, + { 1442939232, 1443004020 }, + { 0, INT32_MAX }, + { INT32_MIN, 0 }, + { INT32_MIN, INT32_MAX }, + { 0, UINT32_MAX }, + { MIN_UTC_TIME, 0 }, + { MIN_UTC_TIME - 1, 0 }, + { 0, MAX_UTC_TIME }, + { 0, MAX_UTC_TIME + 1 }, + { MIN_UTC_TIME, MAX_UTC_TIME}, + { MIN_UTC_TIME - 1, MAX_UTC_TIME + 1 }, + { MIN_CERT_TIME, MAX_CERT_TIME }, + { MIN_CERT_TIME, MAX_CERT_TIME - 1 }, + { MIN_CERT_TIME + 1, MAX_CERT_TIME }, + { MIN_CERT_TIME + 1, MAX_CERT_TIME - 1 }, + { 0, MAX_CERT_TIME }, + { 0, MAX_CERT_TIME - 1 } +}; +/* clang-format on */ + +/* Returns 0 for success, 1 if failed */ +static int test_a_time(X509_STORE_CTX *ctx, X509 *x509, + const int64_t test_time, + int64_t notBefore, int64_t notAfter, + int64_t lower_limit, int64_t upper_limit) { + int expected_value, error, expected_error;; + X509_VERIFY_PARAM *vpm; + + /* Skip tests out of time_t range */ + if (test_time < lower_limit || test_time > upper_limit) + return 0; + + /* + * XXX beck This block below is a hack. The current comparison + * routines needlessly convert the time_t value to a struct + * tm to compare it to the asn1_string converted to a struct tm. + * OPENSSL_gmtime() does this, but fails on large time_t values. + * Once we remove this conversion we should be able to compare + * against the full range of time_t. but for the moment we need + * to skip this test if OPENSSL_gmtime() fails. + */ + { + const time_t t = (const time_t) test_time; + struct tm tm; + + if (OPENSSL_gmtime(&t, &tm) == NULL) { + TEST_info("OPENSSL_gmtime can't handle time of %lld, skipping test", + (long long) test_time); + return 0; + } + } + + expected_value = notBefore <= test_time; + if (expected_value) { + expected_value = notAfter == MAX_CERT_TIME || notAfter >= test_time; + } + + if (notBefore > test_time) + expected_error = X509_V_ERR_CERT_NOT_YET_VALID; + else if (notAfter < test_time && notAfter != MAX_CERT_TIME) + expected_error = X509_V_ERR_CERT_HAS_EXPIRED; + else + expected_error = 0; + + vpm = X509_STORE_CTX_get0_param(ctx); + X509_VERIFY_PARAM_set_time(vpm, test_time); + if (ossl_x509_check_cert_time(ctx, x509, 0) != expected_value) { + TEST_info("ossl_X509_check_cert_time %s unexpectedly when verifying " + "notBefore %lld, notAfter %lld at time %lld\n", + expected_value ? "failed" : "succeeded", + (long long)notBefore, (long long)notAfter, + (long long)test_time); + return 1; + } + error = 0; + if (ossl_x509_check_certificate_times(vpm, x509, &error) != expected_value) { + TEST_info("ossl_X509_check_certificate_times %s unexpectedly when " + "verifying notBefore %lld, notAfter %lld at time %lld\n", + expected_value ? "failed" : "succeeded", + (long long)notBefore, (long long)notAfter, + (long long)test_time); + return 1; + } + if (error != expected_error) { + TEST_info("ossl_X509_check_certificate_times error return was %d, " + "expected %d when verifying notBefore %lld, notAfter %lld " + "at time %lld\n", + error, expected_error, + (long long)notBefore, (long long )notAfter, + (long long)test_time); + return 1; + } + return 0; +} + +static int do_x509_time_tests(CERT_TEST_DATA *tests, size_t ntests, int64_t lower_limit, int64_t upper_limit) +{ + int ret = 0; + int failures = 0; + X509 *x509 = NULL; + X509_STORE_CTX *ctx = NULL; + X509_VERIFY_PARAM *vpm = NULL; + ASN1_TIME *nb = NULL, *na = NULL; + size_t i; + + if ((x509 = X509_new()) == NULL) { + TEST_info("Malloc posral se do postele."); + goto err; + } + if ((ctx = X509_STORE_CTX_new()) == NULL) { + TEST_info("Malloc posral se do postele."); + goto err; + } + X509_STORE_CTX_init(ctx, NULL, NULL, NULL); + if ((vpm = X509_VERIFY_PARAM_new()) == NULL) { + TEST_info("Malloc posral se do postele."); + goto err; + } + X509_STORE_CTX_set0_param(ctx, vpm); + if ((nb = ASN1_TIME_new()) == NULL) { + TEST_info("Malloc posral se do postele."); + goto err; + } + if ((na = ASN1_TIME_new()) == NULL) { + TEST_info("Malloc posral se do postele."); + goto err; + } + + for (i = 0; i < ntests; i++) { + int64_t test_time; + + /* Skip this test if any cert values are out of time_t range */ + if (tests[i].NotBefore < lower_limit || tests[i].NotBefore > upper_limit) + continue; + if (tests[i].NotAfter < lower_limit || tests[i].NotAfter > upper_limit) + continue; + + if (ASN1_TIME_adj(nb, (time_t)tests[i].NotBefore, 0, 0) == NULL) { + TEST_info("Could not create NotBefore"); + goto err; + } + if (ASN1_TIME_adj(na, (time_t)tests[i].NotAfter, 0, 0) == NULL) { + TEST_info("Could not create NotAfter"); + goto err; + } + + /* Forcibly jam the times into the X509 */ + if (!X509_set1_notBefore(x509, nb)) { + TEST_info("X509_set1_notBefore failed"); + goto err; + } + if (!X509_set1_notAfter(x509, na)) { + TEST_info("X509_set1_notBefore failed"); + goto err; + } + + /* Test boundaries of NotBefore */ + test_time = tests[i].NotBefore - 1; + failures += test_a_time(ctx, x509, test_time, tests[i].NotBefore, + tests[i].NotAfter, lower_limit, upper_limit); + test_time = tests[i].NotBefore; + failures += test_a_time(ctx, x509, test_time, tests[i].NotBefore, + tests[i].NotAfter, lower_limit, upper_limit); + test_time = tests[i].NotBefore + 1; + failures += test_a_time(ctx, x509, test_time, tests[i].NotBefore, + tests[i].NotAfter, lower_limit, upper_limit); + + /* Test boundaries of NotAfter */ + test_time = tests[i].NotAfter - 1; + failures += test_a_time(ctx, x509, test_time, tests[i].NotBefore, + tests[i].NotAfter, lower_limit, upper_limit); + test_time = tests[i].NotAfter; + failures += test_a_time(ctx, x509, test_time, tests[i].NotBefore, + tests[i].NotAfter, lower_limit, upper_limit); + test_time = tests[i].NotAfter + 1; + failures += test_a_time(ctx, x509, test_time, tests[i].NotBefore, + tests[i].NotAfter, lower_limit, upper_limit); + + test_time = 1442939232; + failures += test_a_time(ctx, x509, test_time, tests[i].NotBefore, + tests[i].NotAfter, lower_limit, upper_limit); + test_time = 1443004020; + failures += test_a_time(ctx, x509, test_time, tests[i].NotBefore, + tests[i].NotAfter, lower_limit, upper_limit); + + test_time = MIN_UTC_TIME; + failures += test_a_time(ctx, x509, test_time, tests[i].NotBefore, + tests[i].NotAfter, lower_limit, upper_limit); + test_time = MIN_UTC_TIME - 1; + failures += test_a_time(ctx, x509, test_time, tests[i].NotBefore, + tests[i].NotAfter, lower_limit, upper_limit); + + test_time = MAX_UTC_TIME; + failures += test_a_time(ctx, x509, test_time, tests[i].NotBefore, + tests[i].NotAfter, lower_limit, upper_limit); + + test_time = MAX_UTC_TIME + 1; + failures += test_a_time(ctx, x509, test_time, tests[i].NotBefore, + tests[i].NotAfter, lower_limit, upper_limit); + + /* Test integer value boundaries */ + test_time = INT64_MIN; + failures += test_a_time(ctx, x509, test_time, tests[i].NotBefore, + tests[i].NotAfter, lower_limit, upper_limit); + test_time = INT32_MIN; + failures += test_a_time(ctx, x509, test_time, tests[i].NotBefore, + tests[i].NotAfter, lower_limit, upper_limit); + test_time = -1; + failures += test_a_time(ctx, x509, test_time, tests[i].NotBefore, + tests[i].NotAfter, lower_limit, upper_limit); + test_time = 0; + failures += test_a_time(ctx, x509, test_time, tests[i].NotBefore, + tests[i].NotAfter, lower_limit, upper_limit); + test_time = 1; + failures += test_a_time(ctx, x509, test_time, tests[i].NotBefore, + tests[i].NotAfter, lower_limit, upper_limit); + test_time = INT32_MAX; + failures += test_a_time(ctx, x509, test_time, tests[i].NotBefore, + tests[i].NotAfter, lower_limit, upper_limit); + test_time = UINT32_MAX; + failures += test_a_time(ctx, x509, test_time, tests[i].NotBefore, + tests[i].NotAfter, lower_limit, upper_limit); + test_time = INT64_MAX; + failures += test_a_time(ctx, x509, test_time, tests[i].NotBefore, + tests[i].NotAfter, lower_limit, + upper_limit); + } + + ret = (failures == 0); + +err: + X509_STORE_CTX_free(ctx); + X509_free(x509); + ASN1_STRING_free(nb); + ASN1_STRING_free(na); + return ret; +} + +static int tests_X509_check_time(void) +{ + /* + * time_t sanity checks. We need these until we can evaluate cert + * time without depending on platform time_t. Then all this + * unpleasantness to decide to not run unit tests that exceed the + * range of a platform time_t can go away. + */ + time_t test_time_t = -1; + int time_t_is_unsigned = (test_time_t > 0); + int time_t_is_64_bit = (sizeof(time_t) == sizeof(int64_t)); + int time_t_is_32_bit = (sizeof(time_t) == sizeof(int32_t)); + + OPENSSL_assert(time_t_is_32_bit || time_t_is_64_bit); + OPENSSL_assert(!time_t_is_unsigned || time_t_is_32_bit); + + if (time_t_is_32_bit) { + if (time_t_is_unsigned) { + return do_x509_time_tests(cert_test_data, sizeof(cert_test_data) / sizeof(CERT_TEST_DATA), + INT32_MIN, INT32_MAX); + } + else { + return do_x509_time_tests(cert_test_data, sizeof(cert_test_data) / sizeof(CERT_TEST_DATA), + 0, UINT32_MAX); + } + } + return do_x509_time_tests(cert_test_data, sizeof(cert_test_data) / sizeof(CERT_TEST_DATA), + INT64_MIN, INT64_MAX); +} + int setup_tests(void) { ADD_TEST(test_standard_exts); ADD_ALL_TESTS(test_a2i_ipaddress, OSSL_NELEM(a2i_ipaddress_tests)); ADD_TEST(tests_X509_PURPOSE); + ADD_TEST(tests_X509_check_time); return 1; }