Commit c7c8dea229 for openssl.org

commit c7c8dea22984bab394bc227fabfe1b9416df952f
Author: Bob Beck <beck@openssl.org>
Date:   Thu Jan 29 11:31:40 2026 -0700

    Deprecate the X509_check_{email,host,ip,ip_asc} family of functions

    Our own documentation for quite some time has indicated
    that you should call X509_verify_cert() instead of using these.
    Actually deprecate them and make apps not use the now deprecated
    functions.

    Resolves: https://github.com/openssl/project/issues/1899
    References: https://github.com/openssl/project/issues/1897

    Reviewed-by: Neil Horman <nhorman@openssl.org>
    Reviewed-by: Eugene Syromiatnikov <esyr@openssl.org>
    Reviewed-by: Norbert Pocs <norbertp@openssl.org>
    MergeDate: Mon May 11 00:08:33 2026
    (Merged from https://github.com/openssl/openssl/pull/30403)

diff --git a/CHANGES.md b/CHANGES.md
index 94953f18bd..1dd27df4ba 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -80,6 +80,13 @@ OpenSSL Releases

    *John Claus*

+* 'X509_check_host()', 'X509_check_email()', 'X509_check_ip()', and 'X509_check_ip_asc()'
+   have been deprecated. Applications should migrate to setting a reference identifier
+   to check using 'X509_VERIFY_PARAM_set1_host()', 'X509_VERIFY_PARAM_set1_email()', or
+   X509_VERIFY_PARAM_set1_ip_asc()', and using 'X509_verify_cert()'.
+
+   *Bob Beck*
+
  * Added AVX2 optimized ML-DSA NTT operations on `x86_64`.

    *Marcel Cornu and Tomasz Kantecki*
diff --git a/apps/include/apps.h b/apps/include/apps.h
index 43097c013e..97c6669e3a 100644
--- a/apps/include/apps.h
+++ b/apps/include/apps.h
@@ -317,9 +317,9 @@ extern char *psk_key;

 unsigned char *next_protos_parse(size_t *outlen, const char *in);

-int check_cert_attributes(BIO *bio, X509 *x,
+int check_cert_might_be_valid(BIO *bio, BIO *bio_err, X509 *x,
     const char *checkhost, const char *checkemail,
-    const char *checkip, int print);
+    const char *checkip);

 void store_setup_crl_download(X509_STORE *st);

diff --git a/apps/lib/apps.c b/apps/lib/apps.c
index fe1bc7e812..467f36ae1f 100644
--- a/apps/lib/apps.c
+++ b/apps/lib/apps.c
@@ -86,6 +86,22 @@ int app_init(long mesgwin)
 }
 #endif

+static int maybe_printf(BIO *bio, const char *format, ...)
+{
+    va_list args;
+    int ret = 0;
+
+    if (bio != NULL) {
+        va_start(args, format);
+
+        ret = BIO_vprintf(bio, format, args);
+
+        va_end(args);
+    }
+
+    return ret;
+}
+
 int ctx_set_verify_locations(SSL_CTX *ctx,
     const char *CAfile, int noCAfile,
     const char *CApath, int noCApath,
@@ -2447,42 +2463,132 @@ unsigned char *next_protos_parse(size_t *outlen, const char *in)
     return out;
 }

-int check_cert_attributes(BIO *bio, X509 *x, const char *checkhost,
-    const char *checkemail, const char *checkip,
-    int print)
+int check_cert_might_be_valid(BIO *bio, BIO *b_err, X509 *x, const char *checkhost,
+    const char *checkemail, const char *checkip)
 {
-    int valid_host = 0;
-    int valid_mail = 0;
-    int valid_ip = 0;
-    int ret = 1;
+    int ret = 0;
+    int error;
+    X509_STORE_CTX *ctx = NULL;
+    X509_STORE *store = NULL;
+    X509_VERIFY_PARAM *vpm = NULL;

-    if (x == NULL)
-        return 0;
+    if (x == NULL) {
+        maybe_printf(b_err, "Internal error, NULL certificate\n");
+        goto err;
+    }

-    if (checkhost != NULL) {
-        valid_host = X509_check_host(x, checkhost, 0, 0, NULL);
-        if (print)
-            BIO_printf(bio, "Hostname %s does%s match certificate\n",
-                checkhost, valid_host == 1 ? "" : " NOT");
-        ret = ret && valid_host > 0;
+    if ((store = X509_STORE_new()) == NULL) {
+        maybe_printf(b_err, "Malloc failed or internal error\n");
+        goto err;
     }

-    if (checkemail != NULL) {
-        valid_mail = X509_check_email(x, checkemail, 0, 0);
-        if (print)
-            BIO_printf(bio, "Email %s does%s match certificate\n",
-                checkemail, valid_mail ? "" : " NOT");
-        ret = ret && valid_mail > 0;
+    if (!X509_STORE_add_cert(store, x)) {
+        maybe_printf(b_err, "Malloc failed or internal error\n");
+        goto err;
     }

-    if (checkip != NULL) {
-        valid_ip = X509_check_ip_asc(x, checkip, 0);
-        if (print)
-            BIO_printf(bio, "IP %s does%s match certificate\n",
-                checkip, valid_ip ? "" : " NOT");
-        ret = ret && valid_ip > 0;
+    if ((vpm = X509_STORE_get0_param(store)) == NULL) {
+        maybe_printf(b_err, "Malloc failed or internal error\n");
+        goto err;
+    }
+
+    if ((ctx = X509_STORE_CTX_new()) == NULL) {
+        maybe_printf(b_err, "Malloc failed or internal error\n");
+        goto err;
     }

+    /*
+     * As this is "might verify":
+     *
+     * We don't care about the verification time.
+     * We are trusting ourselves.
+     * We are very liberal in what we allow.
+     *
+     * Needless to say these flags should normally not be used in a
+     * for real verification.
+     */
+    X509_VERIFY_PARAM_set_flags(vpm, X509_V_FLAG_NO_CHECK_TIME);
+    X509_VERIFY_PARAM_set_flags(vpm, X509_V_FLAG_PARTIAL_CHAIN);
+    X509_VERIFY_PARAM_set_flags(vpm, X509_V_FLAG_IGNORE_CRITICAL);
+    X509_VERIFY_PARAM_set_flags(vpm, X509_V_FLAG_ALLOW_PROXY_CERTS);
+    X509_VERIFY_PARAM_set_trust(vpm, X509_TRUST_OK_ANY_EKU);
+
+    if (!X509_VERIFY_PARAM_set1_ip_asc(vpm, checkip)) {
+        maybe_printf(b_err, "Invalid IP address: %s\n", checkip);
+        goto err;
+    }
+
+    if (!X509_VERIFY_PARAM_set1_host(vpm, checkhost, 0)) {
+        maybe_printf(b_err, "Invalid host name: %s\n", checkhost);
+        goto err;
+    }
+
+    if (!X509_VERIFY_PARAM_set1_email(vpm, checkemail, 0)) {
+        maybe_printf(b_err, "Invalid email address: %s\n", checkemail);
+        goto err;
+    }
+
+    if (!X509_VERIFY_PARAM_set1_ip_asc(vpm, checkip)) {
+        maybe_printf(b_err, "Invalid IP address: %s\n", checkip);
+        goto err;
+    }
+
+    if (!X509_STORE_CTX_init(ctx, store, x, NULL)) {
+        maybe_printf(b_err, "Malloc failed or internal error\n");
+        goto err;
+    }
+
+    /* We might be verifying for ANY purpose ... */
+    if (!X509_STORE_CTX_set_purpose(ctx, X509_PURPOSE_ANY)) {
+        maybe_printf(b_err, "Malloc failed or internal error\n");
+        goto err;
+    }
+
+    ret = X509_verify_cert(ctx);
+    error = X509_STORE_CTX_get_error(ctx);
+    if (!ret) {
+
+        maybe_printf(bio, "Certificate may not verify: error %s\n",
+            X509_verify_cert_error_string(error));
+
+        if (checkhost != NULL && error == X509_V_ERR_HOSTNAME_MISMATCH)
+            maybe_printf(bio, "Hostname %s does NOT match certificate\n",
+                checkhost);
+        else if (checkemail != NULL && error == X509_V_ERR_EMAIL_MISMATCH)
+            maybe_printf(bio, "Email %s does NOT match certificate\n",
+                checkemail);
+        else if (checkip != NULL && error == X509_V_ERR_IP_ADDRESS_MISMATCH)
+            maybe_printf(bio, "IP %s does NOT match certificate\n",
+                checkip);
+        else {
+            /* Originally, we only cared about the above failures */
+            /*
+             * Suppress trust rejection errors, we don't care, because
+             * we don't really know what this might be used for if
+             * this was for real.
+             */
+            if (error == X509_V_ERR_CERT_REJECTED) {
+                maybe_printf(bio, "Ignoring certificate rejection error\n");
+                ret = 1;
+            }
+        }
+    } else {
+        if (checkhost != NULL)
+            maybe_printf(bio, "Hostname %s does match certificate\n",
+                checkhost);
+        if (checkemail != NULL)
+            maybe_printf(bio, "Email %s does match certificate\n",
+                checkemail);
+        if (checkip != NULL)
+            maybe_printf(bio, "IP %s does match certificate\n",
+                checkip);
+    }
+    X509_STORE_CTX_cleanup(ctx);
+
+err:
+    ERR_print_errors(b_err);
+    X509_STORE_free(store);
+    X509_STORE_CTX_free(ctx);
     return ret;
 }

diff --git a/apps/s_server.c b/apps/s_server.c
index bcb26cb285..e8f431cd0a 100644
--- a/apps/s_server.c
+++ b/apps/s_server.c
@@ -617,7 +617,8 @@ static int ssl_ech_servername_cb(SSL *s, int *ad, void *arg)
         return SSL_TLSEXT_ERR_NOACK;
     if (echrv == SSL_ECH_STATUS_SUCCESS && servername != NULL) {
         if (ctx2 != NULL) {
-            int check_host = X509_check_host(p->scert, servername, 0, 0, NULL);
+            int check_host = check_cert_might_be_valid(p->biodebug,
+                p->biodebug, p->scert, servername, NULL, NULL);

             if (check_host == 1) {
                 if (p->biodebug != NULL)
diff --git a/apps/x509.c b/apps/x509.c
index 824daa1ef5..867961e61d 100644
--- a/apps/x509.c
+++ b/apps/x509.c
@@ -1233,7 +1233,8 @@ cert_loop:
             goto end;
     }

-    if (!check_cert_attributes(out, x, checkhost, checkemail, checkip, 1))
+    if (!check_cert_might_be_valid(out, bio_err, x, checkhost, checkemail,
+            checkip))
         goto err;

     if (noout || nocert) {
diff --git a/crypto/x509/v3_utl.c b/crypto/x509/v3_utl.c
index 774d3790e2..4ccfcacadb 100644
--- a/crypto/x509/v3_utl.c
+++ b/crypto/x509/v3_utl.c
@@ -1001,7 +1001,7 @@ static int do_x509_check(const X509 *x, const char *chk, size_t chklen,
     return 0;
 }

-int X509_check_host(const X509 *x, const char *chk, size_t chklen,
+int ossl_x509_check_host(const X509 *x, const char *chk, size_t chklen,
     unsigned int flags, char **peername)
 {
     if (chk == NULL)
@@ -1020,6 +1020,14 @@ int X509_check_host(const X509 *x, const char *chk, size_t chklen,
     return do_x509_check(x, chk, chklen, flags, GEN_DNS, 0, peername);
 }

+#if !defined(OPENSSL_NO_DEPRECATED_4_1)
+int X509_check_host(const X509 *x, const char *chk, size_t chklen,
+    unsigned int flags, char **peername)
+{
+    return ossl_x509_check_host(x, chk, chklen, flags, peername);
+}
+#endif /* !defined(OPENSSL_NO_DEPRECATED_4_1) */
+
 int ossl_x509_check_rfc822(X509 *x, const char *chk, size_t chklen,
     unsigned int flags)
 {
@@ -1034,6 +1042,15 @@ int ossl_x509_check_smtputf8(X509 *x, const char *chk, size_t chklen,
         == 1;
 }

+int ossl_x509_check_ip(const X509 *x, const unsigned char *chk, size_t chklen,
+    unsigned int flags)
+{
+    if (chk == NULL)
+        return -2;
+    return do_x509_check(x, (char *)chk, chklen, flags, GEN_IPADD, 0, NULL);
+}
+
+#if !defined(OPENSSL_NO_DEPRECATED_4_1)
 int X509_check_email(const X509 *x, const char *chk, size_t chklen,
     unsigned int flags)
 {
@@ -1063,9 +1080,7 @@ int X509_check_email(const X509 *x, const char *chk, size_t chklen,
 int X509_check_ip(const X509 *x, const unsigned char *chk, size_t chklen,
     unsigned int flags)
 {
-    if (chk == NULL)
-        return -2;
-    return do_x509_check(x, (char *)chk, chklen, flags, GEN_IPADD, 0, NULL);
+    return ossl_x509_check_ip(x, chk, chklen, flags);
 }

 int X509_check_ip_asc(const X509 *x, const char *ipasc, unsigned int flags)
@@ -1080,6 +1095,7 @@ int X509_check_ip_asc(const X509 *x, const char *ipasc, unsigned int flags)
         return -2;
     return do_x509_check(x, (char *)ipout, iplen, flags, GEN_IPADD, 0, NULL);
 }
+#endif /* !defined(OPENSSL_NO_DEPRECATED_4_1) */

 char *ossl_ipaddr_to_asc(const unsigned char *p, int len)
 {
diff --git a/crypto/x509/x509_vfy.c b/crypto/x509/x509_vfy.c
index 3ec7f69104..27ea9921cd 100644
--- a/crypto/x509/x509_vfy.c
+++ b/crypto/x509/x509_vfy.c
@@ -939,7 +939,9 @@ static int check_hosts(X509 *x, X509_VERIFY_PARAM *vpm)
     for (int i = 0; i < n; ++i) {
         size_t len = sk_X509_BUFFER_value(vpm->hosts, i)->len;
         name = sk_X509_BUFFER_value(vpm->hosts, i)->data;
-        if (X509_check_host(x, (const char *)name, len, vpm->hostflags, &vpm->peername) > 0)
+        if (ossl_x509_check_host(x, (const char *)name, len, vpm->hostflags,
+                &vpm->peername)
+            > 0)
             return 1;
     }
     return n <= 0;
@@ -974,7 +976,7 @@ static int check_ips(X509 *x, X509_VERIFY_PARAM *vpm)
     for (int i = 0; i < n; ++i) {
         size_t len = sk_X509_BUFFER_value(vpm->ips, i)->len;
         name = sk_X509_BUFFER_value(vpm->ips, i)->data;
-        if (X509_check_ip(x, name, len, vpm->hostflags) > 0)
+        if (ossl_x509_check_ip(x, name, len, vpm->hostflags) > 0)
             return 1;
     }
     return n <= 0;
diff --git a/doc/man1/openssl-x509.pod.in b/doc/man1/openssl-x509.pod.in
index 4309c763fa..cbb2ab29e2 100644
--- a/doc/man1/openssl-x509.pod.in
+++ b/doc/man1/openssl-x509.pod.in
@@ -379,6 +379,15 @@ Check that the certificate matches the specified email address.

 Check that the certificate matches the specified IP address.

+Certificate checking is done with X.509 certificate verification,
+which will fail when encountering an error. As such, when combining
+the B<-checkhost>, B<-checkemail>, and B<-checkip> flags, verify will
+indicate success if all checks pass, and will indicate failure if they
+do not. A diagnostic message of only the first encountered failed
+check that stops certificate verification will be printed in the
+failing case. If a specific diagnostic message is needed for
+individual checks they should be tried individually.
+
 =back

 =head2 Certificate Output Options
diff --git a/doc/man3/X509_check_host.pod b/doc/man3/X509_check_host.pod
index 42d0baa777..6b095f9582 100644
--- a/doc/man3/X509_check_host.pod
+++ b/doc/man3/X509_check_host.pod
@@ -134,11 +134,11 @@ NULs.

 =head1 NOTES

-Applications are encouraged to use X509_VERIFY_PARAM_set1_host()
-rather than explicitly calling L<X509_check_host(3)>. Hostname
-checks may be out of scope with the DANE-EE(3) certificate usage,
-and the internal checks will be suppressed as appropriate when
-DANE support is enabled.
+Applications should use X509_VERIFY_PARAM_set1_host() and
+X509_verify_cert() rather than explicitly calling
+L<X509_check_host(3)>. Hostname checks may be out of scope with the
+DANE-EE(3) certificate usage, and the internal checks will be
+suppressed as appropriate when DANE support is enabled.

 =head1 SEE ALSO

@@ -146,12 +146,15 @@ L<SSL_get_verify_result(3)>,
 L<X509_VERIFY_PARAM_set1_host(3)>,
 L<X509_VERIFY_PARAM_add1_host(3)>,
 L<X509_VERIFY_PARAM_set1_email(3)>,
-L<X509_VERIFY_PARAM_set1_ip(3)>
+L<X509_VERIFY_PARAM_set1_ip(3)>,
+L<X509_verify_cert(3)>

 =head1 HISTORY

 These functions were added in OpenSSL 1.0.2.

+These functions were deprecated in OpenSSL 4.1.0.
+
 =head1 COPYRIGHT

 Copyright 2012-2026 The OpenSSL Project Authors. All Rights Reserved.
diff --git a/include/crypto/x509.h b/include/crypto/x509.h
index 06ec5ecf45..4925131180 100644
--- a/include/crypto/x509.h
+++ b/include/crypto/x509.h
@@ -408,5 +408,9 @@ 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);
 int ossl_posix_to_asn1_time(int64_t posix_time, ASN1_TIME **out_time);
 void ossl_x509_verify_param_set_time_posix(X509_VERIFY_PARAM *param, int64_t t);
+int ossl_x509_check_host(const X509 *x, const char *chk, size_t chklen,
+    unsigned int flags, char **peername);
+int ossl_x509_check_ip(const X509 *x, const unsigned char *chk, size_t chklen,
+    unsigned int flags);

 #endif /* OSSL_CRYPTO_X509_H */
diff --git a/include/openssl/x509v3.h.in b/include/openssl/x509v3.h.in
index 16e40f5379..8e50d31144 100644
--- a/include/openssl/x509v3.h.in
+++ b/include/openssl/x509v3.h.in
@@ -814,13 +814,15 @@ STACK_OF(OPENSSL_STRING) *X509_get1_ocsp(const X509 *x);
  */
 #define _X509_CHECK_FLAG_DOT_SUBDOMAINS 0x8000

-int X509_check_host(const X509 *x, const char *chk, size_t chklen,
+#if !defined(OPENSSL_NO_DEPRECATED_4_1)
+OSSL_DEPRECATEDIN_4_1 int X509_check_host(const X509 *x, const char *chk, size_t chklen,
     unsigned int flags, char **peername);
-int X509_check_email(const X509 *x, const char *chk, size_t chklen,
+OSSL_DEPRECATEDIN_4_1 int X509_check_email(const X509 *x, const char *chk, size_t chklen,
     unsigned int flags);
-int X509_check_ip(const X509 *x, const unsigned char *chk, size_t chklen,
+OSSL_DEPRECATEDIN_4_1 int X509_check_ip(const X509 *x, const unsigned char *chk, size_t chklen,
     unsigned int flags);
-int X509_check_ip_asc(const X509 *x, const char *ipasc, unsigned int flags);
+OSSL_DEPRECATEDIN_4_1 int X509_check_ip_asc(const X509 *x, const char *ipasc, unsigned int flags);
+#endif /* !defined(OPENSSL_NO_DEPRECATED_4_1) */

 ASN1_OCTET_STRING *a2i_IPADDRESS(const char *ipasc);
 ASN1_OCTET_STRING *a2i_IPADDRESS_NC(const char *ipasc);
diff --git a/test/v3nametest.c b/test/v3nametest.c
index c73067d947..8ae7a9e54f 100644
--- a/test/v3nametest.c
+++ b/test/v3nametest.c
@@ -10,11 +10,14 @@
 #include <string.h>

 #include <openssl/e_os2.h>
+#include <openssl/macros.h>
 #include <openssl/x509.h>
 #include <openssl/x509v3.h>
 #include "internal/nelem.h"
 #include "testutil.h"

+#if !defined(OPENSSL_NO_DEPRECATED_4_1)
+OSSL_BEGIN_ALLOW_DEPRECATED
 static const char *const names[] = {
     "a", "b", ".", "*", "@",
     ".a", "a.", ".b", "b.", ".*", "*.", "*@", "@*", "a@", "@a", "b@", "..",
@@ -227,6 +230,8 @@ static int set_altname_email(X509 *crt, const char *name)
 {
     return set_altname(crt, GEN_EMAIL, name, 0);
 }
+OSSL_END_ALLOW_DEPRECATED
+#endif /* !defined(OPENSSL_NO_DEPRECATED_4_1) */

 struct set_name_fn {
     int (*fn)(X509 *, const char *);
@@ -235,6 +240,8 @@ struct set_name_fn {
     int email;
 };

+#if !defined(OPENSSL_NO_DEPRECATED_4_1)
+OSSL_BEGIN_ALLOW_DEPRECATED
 static const struct set_name_fn name_fns[] = {
     { set_cn1, "set CN", 1, 0 },
     { set_cn2, "set CN", 1, 0 },
@@ -358,6 +365,8 @@ static int call_run_cert(int i)
     }
     return failed == 0;
 }
+OSSL_END_ALLOW_DEPRECATED
+#endif /* !defined(OPENSSL_NO_DEPRECATED_4_1) */

 static struct gennamedata {
     const unsigned char der[22];
@@ -657,7 +666,9 @@ end:

 int setup_tests(void)
 {
+#if !defined(OPENSSL_NO_DEPRECATED_4_1)
     ADD_ALL_TESTS(call_run_cert, OSSL_NELEM(name_fns));
+#endif /* !defined(OPENSSL_NO_DEPRECATED_4_1) */
     ADD_TEST(test_GENERAL_NAME_cmp);
     return 1;
 }
diff --git a/util/libcrypto.num b/util/libcrypto.num
index 4e6ac0563c..b0d0e7266c 100644
--- a/util/libcrypto.num
+++ b/util/libcrypto.num
@@ -5439,10 +5439,10 @@ X509_get1_email                         5436	4_0_0	EXIST::FUNCTION:
 X509_REQ_get1_email                     5437	4_0_0	EXIST::FUNCTION:
 X509_email_free                         5438	4_0_0	EXIST::FUNCTION:
 X509_get1_ocsp                          5439	4_0_0	EXIST::FUNCTION:
-X509_check_host                         5440	4_0_0	EXIST::FUNCTION:
-X509_check_email                        5441	4_0_0	EXIST::FUNCTION:
-X509_check_ip                           5442	4_0_0	EXIST::FUNCTION:
-X509_check_ip_asc                       5443	4_0_0	EXIST::FUNCTION:
+X509_check_host                         5440	4_0_0	EXIST::FUNCTION:DEPRECATEDIN_4_1
+X509_check_email                        5441	4_0_0	EXIST::FUNCTION:DEPRECATEDIN_4_1
+X509_check_ip                           5442	4_0_0	EXIST::FUNCTION:DEPRECATEDIN_4_1
+X509_check_ip_asc                       5443	4_0_0	EXIST::FUNCTION:DEPRECATEDIN_4_1
 a2i_IPADDRESS                           5444	4_0_0	EXIST::FUNCTION:
 a2i_IPADDRESS_NC                        5445	4_0_0	EXIST::FUNCTION:
 X509V3_NAME_from_section                5446	4_0_0	EXIST::FUNCTION: