Add unit test for X509 temporal validity functions.

This commit is contained in:
Bob Beck 2025-10-06 11:01:39 -06:00
parent 06ed41a5c8
commit cc0577a803
2 changed files with 295 additions and 2 deletions

View File

@ -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 */

View File

@ -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 <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/x509_vfy.h>
#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;
}