]> git.ipfire.org Git - thirdparty/openssl.git/commitdiff
Add unit test for X509 temporal validity functions.
authorBob Beck <beck@openssl.org>
Mon, 6 Oct 2025 17:01:39 +0000 (11:01 -0600)
committerNeil Horman <nhorman@openssl.org>
Thu, 16 Oct 2025 13:23:46 +0000 (09:23 -0400)
Reviewed-by: Neil Horman <nhorman@openssl.org>
Reviewed-by: Saša Nedvědický <sashan@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/28623)

crypto/x509/x509_vfy.c
include/crypto/x509.h
test/x509_internal_test.c

index 08af5e0db321125c546720ed8afec20cea725193..7560172d9011d73785c1ef41c8c54b0f6af3b6d2 100644 (file)
@@ -2131,13 +2131,13 @@ static int check_policy(X509_STORE_CTX *ctx)
  * Return 1 on success, 0 otherwise.
  */
 int ossl_x509_compare_asn1_time(const X509_VERIFY_PARAM *vpm,
-                                       const ASN1_TIME *time, int *comparison)
+                                const ASN1_TIME *time, int *comparison)
 {
     const time_t *check_time = NULL;
 
-    if ((vpm->flags & X509_V_FLAG_USE_CHECK_TIME) != 0)
+    if ((vpm->flags & X509_V_FLAG_USE_CHECK_TIME) != 0) {
         check_time = &vpm->check_time;
-    else if ((vpm->flags & X509_V_FLAG_NO_CHECK_TIME) != 0) {
+    else if ((vpm->flags & X509_V_FLAG_NO_CHECK_TIME) != 0) {
         *comparison = 0;
         return 1;
     }
@@ -2389,11 +2389,10 @@ int X509_cmp_time(const ASN1_TIME *ctm, time_t *cmp_time)
 int X509_cmp_timeframe(const X509_VERIFY_PARAM *vpm,
                        const ASN1_TIME *start, const ASN1_TIME *end)
 {
+    unsigned long flags = vpm == NULL ? 0 : X509_VERIFY_PARAM_get_flags(vpm);
     time_t ref_time;
     time_t *time = NULL;
 
-    unsigned long flags = vpm == NULL ? 0 : X509_VERIFY_PARAM_get_flags(vpm);
-
     if ((flags & X509_V_FLAG_USE_CHECK_TIME) != 0) {
         ref_time = X509_VERIFY_PARAM_get_time(vpm);
         time = &ref_time;
index bb847bb8d0c428f1c72243e87205f86fdfd483bd..0ed3316fd265e55a9ee1dc9ee6f0a1faf94ac5e7 100644 (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 */
index 7cd04d84f6a4d52b33fdc51b8f2f77bb6327e88b..53c704d32d7857fda1e47357f4fdeb02d67e6fc4 100644 (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
 
 #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;
 }