Commit 4036f4b0e3 for openssl.org

commit 4036f4b0e324ead33424ba83ececc2d3c649acc1
Author: Bob Beck <beck@openssl.org>
Date:   Fri Nov 14 13:36:56 2025 -0700

    Add new public API for checking certificate times.

    Fixes: #1631

    This changes the previously internal ossl_x509_check_certificate_times()
    to be the public X509_check_certificate_times(). It adds documentation
    for the new function and marks X509_cmp_time, X509_cmp_timeframe,
    and X509_cmp_current_time as deprecated in 4.0, as discussed in #1631.

    Since the function is now public, we can replace the remaining
    uses of deprecated stuff with this function.

    Reviewed-by: Neil Horman <nhorman@openssl.org>
    Reviewed-by: Saša NedvÄ›dický <sashan@openssl.org>
    (Merged from https://github.com/openssl/openssl/pull/29152)

diff --git a/apps/lib/apps.c b/apps/lib/apps.c
index eea7bd79b3..ae9ecfff39 100644
--- a/apps/lib/apps.c
+++ b/apps/lib/apps.c
@@ -675,18 +675,16 @@ static void warn_cert_msg(const char *uri, X509 *cert, const char *msg)
 static void warn_cert(const char *uri, X509 *cert, int warn_EE,
     X509_VERIFY_PARAM *vpm)
 {
+    int error;
     uint32_t ex_flags = X509_get_extension_flags(cert);
-    /*
-     * This should not be used as as example for how to verify
-     * certificates. This treats an invalid not before or an invalid
-     * not after time in the certificate as infinitely valid, which
-     * you don't want outside of a toy testing function like this.
-     */
-    int res = X509_cmp_timeframe(vpm, X509_get0_notBefore(cert),
-        X509_get0_notAfter(cert));

-    if (res != 0)
-        warn_cert_msg(uri, cert, res > 0 ? "has expired" : "not yet valid");
+    if (!X509_check_certificate_times(vpm, cert, &error)) {
+        char msg[128];
+
+        ERR_error_string_n(error, msg, sizeof(msg));
+        warn_cert_msg(uri, cert, msg);
+    }
+
     if (warn_EE && (ex_flags & EXFLAG_V1) == 0 && (ex_flags & EXFLAG_CA) == 0)
         warn_cert_msg(uri, cert, "is not a CA cert");
 }
diff --git a/apps/x509.c b/apps/x509.c
index 665d5b85db..85c87518e4 100644
--- a/apps/x509.c
+++ b/apps/x509.c
@@ -1146,19 +1146,37 @@ cert_loop:
     }

     if (checkend) {
+        X509_VERIFY_PARAM *vpm;
         time_t tcheck = time(NULL) + checkoffset;
-        int expired = X509_cmp_time(X509_get0_notAfter(x), &tcheck) < 0;
+        int expired = 0;
+        int error, valid;

-        if (expired)
+        if ((vpm = X509_VERIFY_PARAM_new()) == NULL) {
+            BIO_printf(out, "Malloc failed\n");
+            goto end_cert_loop;
+        }
+        X509_VERIFY_PARAM_set_flags(vpm, X509_V_FLAG_USE_CHECK_TIME);
+        X509_VERIFY_PARAM_set_time(vpm, tcheck);
+
+        valid = X509_check_certificate_times(vpm, x, &error);
+        if (!valid) {
+            char msg[128];
+
+            ERR_error_string_n(error, msg, sizeof(msg));
+            BIO_printf(out, "%s\n", msg);
+        }
+        if (error == X509_V_ERR_CERT_HAS_EXPIRED) {
             BIO_printf(out, "Certificate will expire\n");
-        else
+            expired = 1;
+        } else {
             BIO_printf(out, "Certificate will not expire\n");
-
+        }
         if (multi && k > 0)
             ret |= expired;
         else
             ret = expired;

+        X509_VERIFY_PARAM_free(vpm);
         if (multi && k < sk_X509_num(certs) - 1)
             goto end_cert_loop;
         else
diff --git a/crypto/cmp/cmp_genm.c b/crypto/cmp/cmp_genm.c
index 1966be60ec..67b0531675 100644
--- a/crypto/cmp/cmp_genm.c
+++ b/crypto/cmp/cmp_genm.c
@@ -39,7 +39,7 @@ static int ossl_X509_check(OSSL_CMP_CTX *ctx, const char *source, X509 *cert,
     int ret, err;
     OSSL_CMP_severity level = vpm == NULL ? OSSL_CMP_LOG_WARNING : OSSL_CMP_LOG_ERR;

-    ret = ossl_x509_check_certificate_times(vpm, cert, &err);
+    ret = X509_check_certificate_times(vpm, cert, &err);
     if (!ret) {
         const char *msg;

diff --git a/crypto/cmp/cmp_vfy.c b/crypto/cmp/cmp_vfy.c
index 6d11c7ec0c..01c7422c38 100644
--- a/crypto/cmp/cmp_vfy.c
+++ b/crypto/cmp/cmp_vfy.c
@@ -265,7 +265,7 @@ static int cert_acceptable(const OSSL_CMP_CTX *ctx,
         return 0;
     }

-    if (!ossl_x509_check_certificate_times(vpm, cert, &err)) {
+    if (!X509_check_certificate_times(vpm, cert, &err)) {
         const char *message;

         switch (err) {
diff --git a/crypto/ocsp/ocsp_cl.c b/crypto/ocsp/ocsp_cl.c
index 22d6692a91..a4f954f9a4 100644
--- a/crypto/ocsp/ocsp_cl.c
+++ b/crypto/ocsp/ocsp_cl.c
@@ -16,6 +16,7 @@
 #include <openssl/pem.h>
 #include <openssl/x509v3.h>
 #include <openssl/ocsp.h>
+#include <openssl/posix_time.h>
 #include "ocsp_local.h"

 /*
@@ -288,6 +289,20 @@ int OCSP_resp_find_status(OCSP_BASICRESP *bs, OCSP_CERTID *id, int *status,
     return 1;
 }

+static int gentime_to_posix(ASN1_GENERALIZEDTIME *time, int64_t *out_time)
+{
+    struct tm ctm;
+
+    if (!ASN1_GENERALIZEDTIME_check(time))
+        return 0;
+    if (!ASN1_TIME_to_tm(time, &ctm))
+        return 0;
+    if (!OPENSSL_tm_to_posix(&ctm, out_time))
+        return 0;
+
+    return 1;
+}
+
 /*
  * Check validity of thisUpdate and nextUpdate fields. It is possible that
  * the request will take a few seconds to process and/or the time won't be
@@ -299,55 +314,52 @@ int OCSP_resp_find_status(OCSP_BASICRESP *bs, OCSP_CERTID *id, int *status,
 int OCSP_check_validity(ASN1_GENERALIZEDTIME *thisupd,
     ASN1_GENERALIZEDTIME *nextupd, long nsec, long maxsec)
 {
-    int ret = 1;
-    time_t t_now, t_tmp;
+    int64_t t_now, this_time, next_time;
+    int ret = 0;
+
+    if (nsec < 0)
+        nsec = 0;

-    time(&t_now);
-    /* Check thisUpdate is valid and not more than nsec in the future */
-    if (!ASN1_GENERALIZEDTIME_check(thisupd)) {
+    t_now = (int64_t)time(NULL);
+    /* Check thisUpdate is valid */
+    if (!gentime_to_posix(thisupd, &this_time)) {
         ERR_raise(ERR_LIB_OCSP, OCSP_R_ERROR_IN_THISUPDATE_FIELD);
-        ret = 0;
-    } else {
-        t_tmp = t_now + nsec;
-        if (X509_cmp_time(thisupd, &t_tmp) > 0) {
-            ERR_raise(ERR_LIB_OCSP, OCSP_R_STATUS_NOT_YET_VALID);
-            ret = 0;
+        goto err;
+    }
+    /* Check if thisUpdate is more than nsec in the future */
+    if (this_time > t_now + nsec) {
+        ERR_raise(ERR_LIB_OCSP, OCSP_R_STATUS_NOT_YET_VALID);
+        goto err;
+    }
+    /*
+     * If maxsec specified check thisUpdate is not more than maxsec in
+     * the past
+     */
+    if (maxsec >= 0 && this_time < t_now - maxsec) {
+        ERR_raise(ERR_LIB_OCSP, OCSP_R_STATUS_TOO_OLD);
+        goto err;
+    }
+    if (nextupd != NULL) {
+        /* Check nextUpdate is valid */
+        if (!gentime_to_posix(nextupd, &next_time)) {
+            ERR_raise(ERR_LIB_OCSP, OCSP_R_ERROR_IN_NEXTUPDATE_FIELD);
+            goto err;
         }
-
-        /*
-         * If maxsec specified check thisUpdate is not more than maxsec in
-         * the past
-         */
-        if (maxsec >= 0) {
-            t_tmp = t_now - maxsec;
-            if (X509_cmp_time(thisupd, &t_tmp) < 0) {
-                ERR_raise(ERR_LIB_OCSP, OCSP_R_STATUS_TOO_OLD);
-                ret = 0;
-            }
+        /* Check nextUpdate is not more than nsec in the past */
+        if (next_time < t_now - nsec) {
+            ERR_raise(ERR_LIB_OCSP, OCSP_R_STATUS_NOT_YET_VALID);
+            goto err;
         }
-    }
-
-    if (nextupd == NULL)
-        return ret;
-
-    /* Check nextUpdate is valid and not more than nsec in the past */
-    if (!ASN1_GENERALIZEDTIME_check(nextupd)) {
-        ERR_raise(ERR_LIB_OCSP, OCSP_R_ERROR_IN_NEXTUPDATE_FIELD);
-        ret = 0;
-    } else {
-        t_tmp = t_now - nsec;
-        if (X509_cmp_time(nextupd, &t_tmp) < 0) {
-            ERR_raise(ERR_LIB_OCSP, OCSP_R_STATUS_EXPIRED);
-            ret = 0;
+        /* Also don't allow nextUpdate to precede thisUpdate */
+        if (next_time < this_time) {
+            ERR_raise(ERR_LIB_OCSP, OCSP_R_NEXTUPDATE_BEFORE_THISUPDATE);
+            goto err;
         }
     }

-    /* Also don't allow nextUpdate to precede thisUpdate */
-    if (ASN1_STRING_cmp(nextupd, thisupd) < 0) {
-        ERR_raise(ERR_LIB_OCSP, OCSP_R_NEXTUPDATE_BEFORE_THISUPDATE);
-        ret = 0;
-    }
+    ret = 1;

+err:
     return ret;
 }

diff --git a/crypto/x509/t_x509.c b/crypto/x509/t_x509.c
index c4927d357b..0c43c50fe6 100644
--- a/crypto/x509/t_x509.c
+++ b/crypto/x509/t_x509.c
@@ -381,31 +381,45 @@ int X509_aux_print(BIO *out, const X509 *x, int indent)
 int ossl_x509_print_ex_brief(BIO *bio, X509 *cert, unsigned long neg_cflags)
 {
     unsigned long flags = ASN1_STRFLGS_RFC2253 | ASN1_STRFLGS_ESC_QUOTE | XN_FLAG_SEP_CPLUS_SPC | XN_FLAG_FN_SN;
+    X509_VERIFY_PARAM *vpm = X509_VERIFY_PARAM_new();
+    int error, ret = 0;

-    if (cert == NULL)
-        return BIO_printf(bio, "    (no certificate)\n") > 0;
+    if (vpm == NULL) {
+        ret = BIO_printf(bio, "    (malloc failed)\n") > 0;
+        goto err;
+    }
+    if (cert == NULL) {
+        ret = BIO_printf(bio, "    (no certificate)\n") > 0;
+        goto err;
+    }
     if (BIO_printf(bio, "    certificate\n") <= 0
         || !X509_print_ex(bio, cert, flags, ~X509_FLAG_NO_SUBJECT))
-        return 0;
+        goto err;
     if (X509_check_issued((X509 *)cert, cert) == X509_V_OK) {
         if (BIO_printf(bio, "        self-issued\n") <= 0)
-            return 0;
+            goto err;
     } else {
         if (BIO_printf(bio, " ") <= 0
             || !X509_print_ex(bio, cert, flags, ~X509_FLAG_NO_ISSUER))
-            return 0;
+            goto err;
     }
     if (!X509_print_ex(bio, cert, flags,
             ~(X509_FLAG_NO_SERIAL | X509_FLAG_NO_VALIDITY)))
-        return 0;
-    if (X509_cmp_current_time(X509_get0_notBefore(cert)) > 0)
-        if (BIO_printf(bio, "        not yet valid\n") <= 0)
-            return 0;
-    if (X509_cmp_current_time(X509_get0_notAfter(cert)) < 0)
-        if (BIO_printf(bio, "        no more valid\n") <= 0)
-            return 0;
-    return X509_print_ex(bio, cert, flags,
+        goto err;
+
+    if (!X509_check_certificate_times(vpm, cert, &error)) {
+        char msg[128];
+
+        ERR_error_string_n(error, msg, sizeof(msg));
+        if (BIO_printf(bio, "        %s\n", msg) <= 0)
+            goto err;
+    }
+    ret = X509_print_ex(bio, cert, flags,
         ~neg_cflags & ~X509_FLAG_EXTENSIONS_ONLY_KID);
+
+err:
+    X509_VERIFY_PARAM_free(vpm);
+    return ret;
 }

 static int print_certs(BIO *bio, const STACK_OF(X509) *certs)
diff --git a/crypto/x509/x509_vfy.c b/crypto/x509/x509_vfy.c
index c535334f24..ef15131e82 100644
--- a/crypto/x509/x509_vfy.c
+++ b/crypto/x509/x509_vfy.c
@@ -7,6 +7,7 @@
  * https://www.openssl.org/source/license.html
  */

+#define OPENSSL_SUPPRESS_DEPRECATED
 #include "internal/deprecated.h"

 #include <stdio.h>
@@ -2126,9 +2127,13 @@ memerr:
 /*-
  * Check certificate validity times.
  *
- * Return 1 on success, 0 otherwise.
+ * Returns 1 if the certificate |x| is temporally valid at the
+ * verification time requested by |vpm|, or 0 otherwise. if |error| is
+ * non-NULL, |*error| will be set to 0 when the certificate is
+ * temporally valid, otherwise it will be set to a non-zero error
+ * code.
  */
-int ossl_x509_check_certificate_times(const X509_VERIFY_PARAM *vpm, X509 *x,
+int X509_check_certificate_times(const X509_VERIFY_PARAM *vpm, const X509 *x,
     int *error)
 {
     int ret = 0, err = 0;
@@ -2352,6 +2357,7 @@ static int internal_verify(X509_STORE_CTX *ctx)
     return 1;
 }

+#if !defined(OPENSSL_NO_DEPRECATED_4_0)
 int X509_cmp_current_time(const ASN1_TIME *ctm)
 {
     return X509_cmp_time(ctm, NULL);
@@ -2421,6 +2427,7 @@ int X509_cmp_timeframe(const X509_VERIFY_PARAM *vpm,
         return -1;
     return 0;
 }
+#endif /* !defined(OPENSSL_NO_DEPRECATED_4_0) */

 ASN1_TIME *X509_gmtime_adj(ASN1_TIME *s, long adj)
 {
diff --git a/doc/build.info b/doc/build.info
index 7b03b5b97b..fdaa35df76 100644
--- a/doc/build.info
+++ b/doc/build.info
@@ -3023,6 +3023,10 @@ DEPEND[html/man3/X509_check_ca.html]=man3/X509_check_ca.pod
 GENERATE[html/man3/X509_check_ca.html]=man3/X509_check_ca.pod
 DEPEND[man/man3/X509_check_ca.3]=man3/X509_check_ca.pod
 GENERATE[man/man3/X509_check_ca.3]=man3/X509_check_ca.pod
+DEPEND[html/man3/X509_check_certificate_times.html]=man3/X509_check_certificate_times.pod
+GENERATE[html/man3/X509_check_certificate_times.html]=man3/X509_check_certificate_times.pod
+DEPEND[man/man3/X509_check_certificate_times.3]=man3/X509_check_certificate_times.pod
+GENERATE[man/man3/X509_check_certificate_times.3]=man3/X509_check_certificate_times.pod
 DEPEND[html/man3/X509_check_host.html]=man3/X509_check_host.pod
 GENERATE[html/man3/X509_check_host.html]=man3/X509_check_host.pod
 DEPEND[man/man3/X509_check_host.3]=man3/X509_check_host.pod
@@ -3043,10 +3047,6 @@ DEPEND[html/man3/X509_cmp.html]=man3/X509_cmp.pod
 GENERATE[html/man3/X509_cmp.html]=man3/X509_cmp.pod
 DEPEND[man/man3/X509_cmp.3]=man3/X509_cmp.pod
 GENERATE[man/man3/X509_cmp.3]=man3/X509_cmp.pod
-DEPEND[html/man3/X509_cmp_time.html]=man3/X509_cmp_time.pod
-GENERATE[html/man3/X509_cmp_time.html]=man3/X509_cmp_time.pod
-DEPEND[man/man3/X509_cmp_time.3]=man3/X509_cmp_time.pod
-GENERATE[man/man3/X509_cmp_time.3]=man3/X509_cmp_time.pod
 DEPEND[html/man3/X509_digest.html]=man3/X509_digest.pod
 GENERATE[html/man3/X509_digest.html]=man3/X509_digest.pod
 DEPEND[man/man3/X509_digest.3]=man3/X509_digest.pod
@@ -3801,12 +3801,12 @@ html/man3/X509_STORE_set_verify_cb_func.html \
 html/man3/X509_VERIFY_PARAM_set_flags.html \
 html/man3/X509_add_cert.html \
 html/man3/X509_check_ca.html \
+html/man3/X509_check_certificate_times.html \
 html/man3/X509_check_host.html \
 html/man3/X509_check_issued.html \
 html/man3/X509_check_private_key.html \
 html/man3/X509_check_purpose.html \
 html/man3/X509_cmp.html \
-html/man3/X509_cmp_time.html \
 html/man3/X509_digest.html \
 html/man3/X509_dup.html \
 html/man3/X509_get0_distinguishing_id.html \
@@ -4473,12 +4473,12 @@ man/man3/X509_STORE_set_verify_cb_func.3 \
 man/man3/X509_VERIFY_PARAM_set_flags.3 \
 man/man3/X509_add_cert.3 \
 man/man3/X509_check_ca.3 \
+man/man3/X509_check_certificate_times.3 \
 man/man3/X509_check_host.3 \
 man/man3/X509_check_issued.3 \
 man/man3/X509_check_private_key.3 \
 man/man3/X509_check_purpose.3 \
 man/man3/X509_cmp.3 \
-man/man3/X509_cmp_time.3 \
 man/man3/X509_digest.3 \
 man/man3/X509_dup.3 \
 man/man3/X509_get0_distinguishing_id.3 \
diff --git a/doc/man3/X509_cmp_time.pod b/doc/man3/X509_check_certificate_times.pod
similarity index 54%
rename from doc/man3/X509_cmp_time.pod
rename to doc/man3/X509_check_certificate_times.pod
index 52b1722c9f..fa991784f6 100644
--- a/doc/man3/X509_cmp_time.pod
+++ b/doc/man3/X509_check_certificate_times.pod
@@ -2,23 +2,43 @@

 =head1 NAME

-X509_cmp_time, X509_cmp_current_time, X509_cmp_timeframe,
-X509_time_adj, X509_time_adj_ex, X509_gmtime_adj
-- X509 time functions
+X509_check_certificate_times, X509_time_adj, X509_time_adj_ex, X509_gmtime_adj,
+X509_cmp_time, X509_cmp_current_time, X509_cmp_timeframe - X509 time functions

 =head1 SYNOPSIS

- int X509_cmp_time(const ASN1_TIME *asn1_time, time_t *in_tm);
- int X509_cmp_current_time(const ASN1_TIME *asn1_time);
- int X509_cmp_timeframe(const X509_VERIFY_PARAM *vpm,
-                        const ASN1_TIME *start, const ASN1_TIME *end);
+ int X509_check_certificate_times(const X509_VERIFY_PARAM *vpm, const X509 *x,
+                                  int *error);
  ASN1_TIME *X509_time_adj(ASN1_TIME *asn1_time, long offset_sec, time_t *in_tm);
  ASN1_TIME *X509_time_adj_ex(ASN1_TIME *asn1_time, int offset_day, long
                              offset_sec, time_t *in_tm);
  ASN1_TIME *X509_gmtime_adj(ASN1_TIME *asn1_time, long offset_sec);

+The following functions have been deprecated since OpenSSL 4.0, and can be
+hidden entirely by defining B<OPENSSL_API_COMPAT> with a suitable version value,
+see L<openssl_user_macros(7)>:
+
+ int X509_cmp_time(const ASN1_TIME *asn1_time, time_t *in_tm);
+ int X509_cmp_current_time(const ASN1_TIME *asn1_time);
+ int X509_cmp_timeframe(const X509_VERIFY_PARAM *vpm,
+                        const ASN1_TIME *start, const ASN1_TIME *end);
+
 =head1 DESCRIPTION

+X509_check_certificate_times() compares the notBefore and notAfter
+times in certificate I<x> to check the certificate for temporal validity.
+The time used for the check will be the current system time, unless
+The the reference time included in the verification parameter I<vpm>
+is non NULL and I<vpm> has the flag B<X509_V_FLAG_USE_CHECK_TIME> set.
+
+The notBefore and notAfter times in the certificate will be accepted
+only if they are either the format of a GeneralizedTime
+(YYYYMMDDHHMMSSZ), or a UTCTime (YYMMDDHHMMSSZ) as per RFC5280, with
+the exception that the requirement: "CAs conforming to this profile
+MUST always encode certificate validity dates through the year 2049 as
+UTCTime; certificate validity dates in 2050 or later MUST be encoded
+as GeneralizedTime." is not enforced.
+
 X509_cmp_time() compares the ASN1_TIME in I<asn1_time> with the time
 in <in_tm>.

@@ -52,11 +72,35 @@ X509_time_adj() with the last parameter as NULL.

 =head1 BUGS

-Unlike many standard comparison functions, X509_cmp_time() and
-X509_cmp_current_time() return 0 on error.
+Unlike many standard comparison functions, The deprecated functions
+X509_cmp_time() and X509_cmp_current_time() return 0 on error, and
+return -1 when the values are equal.
+
+The deprecated function X509_cmp_timeframe() may accept invalid
+certificate times as infinitely valid.

 =head1 RETURN VALUES

+X509_check_certiticate_times() returns 1 if the certificate is
+temporally valid at the verification time as per the rules from RFC
+5280. It returns 0 otherwise. if I<error> is non NULL, the integer
+value it points to will be set to an error code when the certificate
+is not temporally valid, or 0 when the certificate is temporally valid.
+
+The integer pointed to by I<error> will be set to
+X509_V_ERROR_ERROR_IN_CERT_NOT_BEFORE_FIELD
+or
+X509_V_ERROR_ERROR_IN_CERT_NOT_AFTER_FIELD
+if the certificate has an invalid notBefore or notAfter field, respectively.
+
+The integer pointed to by I<error> will be set to
+X509_V_ERR_CERT_NOT_YET_VALID
+or
+X509_V_ERR_CERT_HAS_EXPIRED
+if the
+verification time is outside of the certificate's correctly encoded
+validity window as per RFC5280.
+
 X509_cmp_time() and X509_cmp_current_time() return -1 if I<asn1_time>
 is earlier than, or equal to, I<in_tm> (resp. current time), and 1
 otherwise. These methods return 0 on error.
@@ -85,13 +129,31 @@ as valid forever.
 X509_time_adj(), X509_time_adj_ex() and X509_gmtime_adj() return a pointer to
 the updated ASN1_TIME structure, and NULL on error.

+=head1 SEE ALSO
+
+L<ASN1_GENERALIZEDTIME_check(3)>
+L<ASN1_UTCTIME_check(3)>
+L<ASN1_TIME_to_tm(3)>
+L<OPENSSL_tm_to_posix(3)> L<OPENSSL_posix_to_tm(3)>
+L<ERR_error_string_n(3)>
+
 =head1 HISTORY

-X509_cmp_timeframe() was added in OpenSSL 3.0.
+X509_cmp_timeframe(), X509_cmp_current_time(), and
+X509_cmp_timeframe() were deprecated in OpenSSL 4.0
+
+For replacement, consider using X509_check_certificate_times() for use
+with X509 certificates. For applications checking individual ASN1_TIME
+values, consider using ASN1_TIME_to_tm(3) with appropriate validity
+checking of the I<ASN1_TIME> value for your application, and subsequent
+comparison of either the resulting I<tm> structure, or conversion to
+posix seconds via OPENSSL_tm_to_posix(3)
+
+X509_check_certificate_times() was added in OpenSSL 4.0.

 =head1 COPYRIGHT

-Copyright 2017-2018 The OpenSSL Project Authors. All Rights Reserved.
+Copyright 2017-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
diff --git a/include/crypto/x509.h b/include/crypto/x509.h
index 608b78dc15..95367b8ff2 100644
--- a/include/crypto/x509.h
+++ b/include/crypto/x509.h
@@ -399,8 +399,6 @@ int ossl_serial_number_print(BIO *out, const ASN1_INTEGER *bs, int indent);
 int ossl_bio_print_hex(BIO *out, unsigned char *buf, int len);
 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);
 int ossl_x509_check_crl_time(X509_STORE_CTX *ctx, X509_CRL *crl, int notify);
diff --git a/include/openssl/macros.h b/include/openssl/macros.h
index e149a27f6e..653b1926b2 100644
--- a/include/openssl/macros.h
+++ b/include/openssl/macros.h
@@ -49,6 +49,9 @@
 #define OSSL_DEPRECATED_FOR(since, message) __declspec(deprecated)
 #define OSSL_DEPRECATED_MESSAGE(message) __declspec(deprecated)
 #endif
+#define OSSL_BEGIN_ALLOW_DEPRECATED \
+    __pragma(warning(push)) __pragma(warning(disable : 4996))
+#define OSSL_END_ALLOW_DEPRECATED __pragma(warning(pop))
 #elif defined(__GNUC__)
 /*
  * According to GCC documentation, deprecations with message appeared in
@@ -65,12 +68,20 @@
 #define OSSL_DEPRECATED_FOR(since, message) __attribute__((deprecated))
 #define OSSL_DEPRECATED_MESSAGE(message) __attribute__((deprecated))
 #endif
+#define OSSL_BEGIN_ALLOW_DEPRECATED \
+    _Pragma("GCC diagnostic push")  \
+        _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"")
+#define OSSL_END_ALLOW_DEPRECATED _Pragma("GCC diagnostic pop")
 #elif defined(__SUNPRO_C)
 #if (__SUNPRO_C >= 0x5130)
 #define OSSL_DEPRECATED(since) __attribute__((deprecated))
 #define OSSL_DEPRECATED_FOR(since, message) __attribute__((deprecated))
 #define OSSL_DEPRECATED_MESSAGE(message) __attribute__((deprecated))
 #endif
+#define OSSL_BEGIN_ALLOW_DEPRECATED \
+    #pragma error_messages(off, E_DEPRECATED_ATT, E_DEPRECATED_ATT_MESS)
+#define OSSL_END_ALLOW_DEPRECATED \
+    #pragma error_messages(on, E_DEPRECATED_ATT, E_DEPRECATED_ATT_MESS)
 #endif
 #endif
 #endif
diff --git a/include/openssl/x509.h.in b/include/openssl/x509.h.in
index a0e17b862b..9c99332fee 100644
--- a/include/openssl/x509.h.in
+++ b/include/openssl/x509.h.in
@@ -495,10 +495,15 @@ int X509_ALGOR_copy(X509_ALGOR *dest, const X509_ALGOR *src);
 DECLARE_ASN1_DUP_FUNCTION(X509_NAME)
 DECLARE_ASN1_DUP_FUNCTION(X509_NAME_ENTRY)

-int X509_cmp_time(const ASN1_TIME *s, time_t *t);
-int X509_cmp_current_time(const ASN1_TIME *s);
-int X509_cmp_timeframe(const X509_VERIFY_PARAM *vpm,
-    const ASN1_TIME *start, const ASN1_TIME *end);
+#ifndef OPENSSL_NO_DEPRECATED_4_0
+OSSL_DEPRECATEDIN_4_0 int X509_cmp_time(const ASN1_TIME *s, time_t *t);
+OSSL_DEPRECATEDIN_4_0 int X509_cmp_current_time(const ASN1_TIME *s);
+OSSL_DEPRECATEDIN_4_0 int X509_cmp_timeframe(const X509_VERIFY_PARAM *vpm,
+    const ASN1_TIME *start,
+    const ASN1_TIME *end);
+#endif
+int X509_check_certificate_times(const X509_VERIFY_PARAM *vpm, const X509 *x,
+    int *error);
 ASN1_TIME *X509_time_adj(ASN1_TIME *s, long adj, time_t *t);
 ASN1_TIME *X509_time_adj_ex(ASN1_TIME *s,
     int offset_day, long offset_sec, time_t *t);
diff --git a/test/recipes/25-test_x509.t b/test/recipes/25-test_x509.t
index 665ea164c6..34a6d17be1 100644
--- a/test/recipes/25-test_x509.t
+++ b/test/recipes/25-test_x509.t
@@ -670,8 +670,9 @@ ok(!run(app(["openssl", "x509", "-checkend", $delta_early + 3600,
 # Single + expiring at boundary
 # Test may fail erroneously due to sequential now() calls
 # See https://github.com/openssl/openssl/pull/29155
+# Certificate should be valid at exact NotAfter time.
 my $delta_exact = Time::Piece->strptime( get_field($c_early, "Not After "),
-                    "%b %d %T %Y %Z")->epoch - Time::Piece->gmtime->epoch;
+                    "%b %d %T %Y %Z")->epoch - Time::Piece->gmtime->epoch + 1;
 ok(!run(app(["openssl", "x509", "-checkend", $delta_exact, "-in", $c_early])),
     "Single cert + expiring at -checkend boundary");
 # Multi + none expiring
diff --git a/test/x509_internal_test.c b/test/x509_internal_test.c
index 8482e6b47d..ecd6d7651e 100644
--- a/test/x509_internal_test.c
+++ b/test/x509_internal_test.c
@@ -308,8 +308,8 @@ static int test_a_time(X509_STORE_CTX *ctx, X509 *x509,
         return 1;
     }
     error = 0;
-    if (ossl_x509_check_certificate_times(vpm, x509, &error) != expected_value) {
-        TEST_info("%s:%d - ossl_X509_check_certificate_times %s unexpectedly "
+    if (X509_check_certificate_times(vpm, x509, &error) != expected_value) {
+        TEST_info("%s:%d - X509_check_certificate_times %s unexpectedly "
                   "when verifying notBefore %lld, notAfter %lld at time %lld\n",
             file, line,
             expected_value ? "failed" : "succeeded",
diff --git a/test/x509_time_test.c b/test/x509_time_test.c
index b96a627bb5..4737876de4 100644
--- a/test/x509_time_test.c
+++ b/test/x509_time_test.c
@@ -214,6 +214,7 @@ static TESTDATA_FORMAT x509_format_tests[] = {
     },
 };

+#if !defined(OPENSSL_NO_DEPRECATED_4_0)
 static TESTDATA x509_cmp_tests[] = {
     {
         "20170217180154Z",
@@ -413,7 +414,9 @@ static int test_x509_cmp_time(int idx)
     t.length = (int)strlen(x509_cmp_tests[idx].data);
     t.flags = 0;

+    OSSL_BEGIN_ALLOW_DEPRECATED
     result = X509_cmp_time(&t, &x509_cmp_tests[idx].cmp_time);
+    OSSL_END_ALLOW_DEPRECATED
     if (!TEST_int_eq(result, x509_cmp_tests[idx].expected)) {
         TEST_info("test_x509_cmp_time(%d) failed: expected %d, got %d\n",
             idx, x509_cmp_tests[idx].expected, result);
@@ -434,6 +437,7 @@ static int test_x509_cmp_time_current(void)
     asn1_now = ASN1_TIME_adj(NULL, now, 0, 0);

     /* X509_cmp_time is expected to return -1 for equal */
+    OSSL_BEGIN_ALLOW_DEPRECATED
     cmp_result = X509_cmp_time(asn1_now, &now);
     if (!TEST_int_eq(cmp_result, -1))
         failed = 1;
@@ -445,6 +449,7 @@ static int test_x509_cmp_time_current(void)
     cmp_result = X509_cmp_time(asn1_after, &now);
     if (!TEST_int_eq(cmp_result, 1))
         failed = 1;
+    OSSL_END_ALLOW_DEPRECATED

     ASN1_TIME_free(asn1_before);
     ASN1_TIME_free(asn1_after);
@@ -462,6 +467,7 @@ static int test_X509_cmp_timeframe_vpm(const X509_VERIFY_PARAM *vpm,
         && (X509_VERIFY_PARAM_get_flags(vpm) & X509_V_FLAG_USE_CHECK_TIME) == 0
         && (X509_VERIFY_PARAM_get_flags(vpm) & X509_V_FLAG_NO_CHECK_TIME) != 0;

+    OSSL_BEGIN_ALLOW_DEPRECATED
     return asn1_before != NULL && asn1_mid != NULL && asn1_after != NULL
         && TEST_int_eq(X509_cmp_timeframe(vpm, asn1_before, asn1_after), 0)
         && TEST_int_eq(X509_cmp_timeframe(vpm, asn1_before, NULL), 0)
@@ -473,6 +479,7 @@ static int test_X509_cmp_timeframe_vpm(const X509_VERIFY_PARAM *vpm,
             always_0 ? 0 : 1)
         && TEST_int_eq(X509_cmp_timeframe(vpm, asn1_after, asn1_before),
             always_0 ? 0 : 1);
+    OSSL_END_ALLOW_DEPRECATED
 }

 static int test_X509_cmp_timeframe(void)
@@ -504,6 +511,7 @@ finish:

     return res;
 }
+#endif /* !defined(OPENSSL_NO_DEPRECATED_4_0) */

 static int test_x509_time(int idx)
 {
@@ -749,9 +757,11 @@ err:

 int setup_tests(void)
 {
+#if !defined(OPENSSL_NO_DEPRECATED_4_0)
     ADD_TEST(test_x509_cmp_time_current);
     ADD_TEST(test_X509_cmp_timeframe);
     ADD_ALL_TESTS(test_x509_cmp_time, OSSL_NELEM(x509_cmp_tests));
+#endif /* !defined(OPENSSL_NO_DEPRECATED_4_0) */
     ADD_ALL_TESTS(test_x509_time, OSSL_NELEM(x509_format_tests));
     ADD_ALL_TESTS(test_days, OSSL_NELEM(day_of_week_tests));
     ADD_ALL_TESTS(test_x509_time_print_rfc_822, OSSL_NELEM(x509_print_tests_rfc_822));
diff --git a/util/libcrypto.num b/util/libcrypto.num
index 5e4e18b0fe..16a60ba98c 100644
--- a/util/libcrypto.num
+++ b/util/libcrypto.num
@@ -4427,9 +4427,10 @@ X509_ALGOR_cmp                          ?	4_0_0	EXIST::FUNCTION:
 X509_ALGOR_copy                         ?	4_0_0	EXIST::FUNCTION:
 X509_NAME_dup                           ?	4_0_0	EXIST::FUNCTION:
 X509_NAME_ENTRY_dup                     ?	4_0_0	EXIST::FUNCTION:
-X509_cmp_time                           ?	4_0_0	EXIST::FUNCTION:
-X509_cmp_current_time                   ?	4_0_0	EXIST::FUNCTION:
-X509_cmp_timeframe                      ?	4_0_0	EXIST::FUNCTION:
+X509_cmp_time                           ?	4_0_0	EXIST::FUNCTION:DEPRECATEDIN_4_0
+X509_cmp_current_time                   ?	4_0_0	EXIST::FUNCTION:DEPRECATEDIN_4_0
+X509_cmp_timeframe                      ?	4_0_0	EXIST::FUNCTION:DEPRECATEDIN_4_0
+X509_check_certificate_times            ?	4_0_0	EXIST::FUNCTION:
 X509_time_adj                           ?	4_0_0	EXIST::FUNCTION:
 X509_time_adj_ex                        ?	4_0_0	EXIST::FUNCTION:
 X509_gmtime_adj                         ?	4_0_0	EXIST::FUNCTION: