Author: brane
Date: Fri Jun  5 12:11:35 2026
New Revision: 1935023

Log:
Add support for OpenSSL 4.0.

* CMakeLists.txt, SConstruct: Check for X509_check_certificate_times().

* buckets/ssl_buckets.c
  (const_X509_NAME_p): Define as non-/const depending on OpenSSL version.
   Replace all uses of 'X509_NAME *' with this typedef.
  (get_subject_alt_names): Don't access ASN1_STRING fields directly.
  (get_first_name_entry_data): New helper function. Retreives X509_NAME
   entries with X509_NAME_get_index_by_NID() instead of the deprecated
   X509_NAME_get_text_by_NID().
  (validate_cert_hostname): Use get_first_name_entry_data() and remove
   now-unused variables.
  (validate_server_certificate): Use X509_check_certificate_times() if
   available,
  (set_X500_name_entry): New helper function for ...
  (convert_X509_NAME_to_table): ... this implementation.

Modified:
   serf/trunk/CMakeLists.txt
   serf/trunk/SConstruct
   serf/trunk/buckets/ssl_buckets.c

Modified: serf/trunk/CMakeLists.txt
==============================================================================
--- serf/trunk/CMakeLists.txt   Fri Jun  5 12:10:04 2026        (r1935022)
+++ serf/trunk/CMakeLists.txt   Fri Jun  5 12:11:35 2026        (r1935023)
@@ -375,6 +375,10 @@ CheckNotFunction("X509_STORE_CTX_get0_ch
 CheckNotFunction("ASN1_STRING_get0_data" "NULL" 
"SERF_NO_SSL_ASN1_STRING_GET0_DATA"
                  "openssl/asn1.h" "${OPENSSL_INCLUDE_DIR}"
                  ${OPENSSL_LIBRARIES} ${SERF_STANDARD_LIBRARIES})
+CheckFunction("X509_check_certificate_times" "NULL, NULL, NULL"
+              "SERF_HAVE_SSL_X509_CHECK_CERTIFICATE_TIMES"
+              "openssl/x509.h" "${OPENSSL_INCLUDE_DIR}"
+              ${OPENSSL_LIBRARIES} ${SERF_STANDARD_LIBRARIES})
 CheckFunction("OSSL_STORE_open_ex"
               "NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL"
               "SERF_HAVE_OSSL_STORE_OPEN_EX" "openssl/store.h"

Modified: serf/trunk/SConstruct
==============================================================================
--- serf/trunk/SConstruct       Fri Jun  5 12:10:04 2026        (r1935022)
+++ serf/trunk/SConstruct       Fri Jun  5 12:11:35 2026        (r1935023)
@@ -651,6 +651,9 @@ if not conf.CheckFunc('X509_STORE_CTX_ge
   env.Append(CPPDEFINES=['SERF_NO_SSL_X509_GET0_CHAIN'])
 if not conf.CheckFunc('ASN1_STRING_get0_data', ssl_includes, 'C', 'NULL'):
   env.Append(CPPDEFINES=['SERF_NO_SSL_ASN1_STRING_GET0_DATA'])
+if conf.CheckFunc('X509_check_certificate_times', ssl_includes, 'C',
+                  'NULL, NULL, NULL'):
+  env.Append(CPPDEFINES=['SERF_HAVE_SSL_X509_CHECK_CERTIFICATE_TIMES'])
 if conf.CheckFunc('CRYPTO_set_locking_callback', ssl_includes, 'C', 'NULL'):
   env.Append(CPPDEFINES=['SERF_HAVE_SSL_LOCKING_CALLBACKS'])
 if conf.CheckFunc('OPENSSL_malloc_init', ssl_includes):

Modified: serf/trunk/buckets/ssl_buckets.c
==============================================================================
--- serf/trunk/buckets/ssl_buckets.c    Fri Jun  5 12:10:04 2026        
(r1935022)
+++ serf/trunk/buckets/ssl_buckets.c    Fri Jun  5 12:11:35 2026        
(r1935023)
@@ -78,6 +78,18 @@ DEFINE_STACK_OF(EVP_PKEY)
 #define ASN1_STRING_get0_data(asn1string) (ASN1_STRING_data(asn1string))
 #endif
 
+/* OpenSSL 4.0 adds 'const' qualifiers to pointer arguments and return values 
*/
+#ifndef OPENSSL_VERSION_PREREQ
+#define OPENSSL_VERSION_PREREQ(m, n) 0
+#endif
+
+#if OPENSSL_VERSION_PREREQ(4, 0)
+typedef const X509_NAME *const_X509_NAME_p;
+#else
+typedef X509_NAME *const_X509_NAME_p;
+#endif
+
+
 /*
  * Here's an overview of the SSL bucket's relationship to OpenSSL and serf.
  *
@@ -827,14 +839,18 @@ get_subject_alt_names(apr_array_header_t
 
             switch (nm->type) {
                 case GEN_DNS:
-                    if (copy_action == ErrorOnNul &&
-                        strlen((const char *)nm->d.ia5->data) != 
nm->d.ia5->length)
+                {
+                    const char *const data =
+                        (const char*)ASN1_STRING_get0_data(nm->d.ia5);
+                    const int length = ASN1_STRING_length(nm->d.ia5);
+
+                    if (copy_action == ErrorOnNul && strlen(data) != length)
                         return SERF_ERROR_SSL_CERT_FAILED;
                     if (san_arr && *san_arr)
-                        p = pstrdup_escape_nul_bytes((const char 
*)nm->d.ia5->data,
-                                                     nm->d.ia5->length,
-                                                     pool);
+                        p = pstrdup_escape_nul_bytes(data, length, pool);
+
                     break;
+                }
                 default:
                     /* Don't know what to do - skip. */
                     break;
@@ -885,11 +901,22 @@ get_ocsp_responders(apr_array_header_t *
     return APR_SUCCESS;
 }
 
+/* Get the first instance of NID from NAME. */
+static const ASN1_STRING *
+get_first_name_entry_data(const_X509_NAME_p name, int nid)
+{
+    const int loc = X509_NAME_get_index_by_NID(name, nid, -1);
+    if (loc >= 0) {
+        const X509_NAME_ENTRY *const entry = X509_NAME_get_entry(name, loc);
+        if (entry) {
+            return X509_NAME_ENTRY_get_data(entry);
+        }
+    }
+    return NULL;
+}
 
 static apr_status_t validate_cert_hostname(X509 *server_cert, apr_pool_t *pool)
 {
-    char buf[1024];
-    int length;
     apr_status_t ret;
 
     ret = get_subject_alt_names(NULL, server_cert, ErrorOnNul, NULL);
@@ -897,14 +924,18 @@ static apr_status_t validate_cert_hostna
       return ret;
     } else {
         /* Fail if the subject's CN field contains \0 characters. */
-        X509_NAME *subject = X509_get_subject_name(server_cert);
+        const ASN1_STRING *asn1;
+        const_X509_NAME_p subject = X509_get_subject_name(server_cert);
         if (!subject)
             return SERF_ERROR_SSL_CERT_FAILED;
 
-        length = X509_NAME_get_text_by_NID(subject, NID_commonName, buf, 1024);
-        if (length != -1)
-            if (strlen(buf) != length)
+        asn1 = get_first_name_entry_data(subject, NID_commonName);
+        if (asn1) {
+            const char *const data = (const char*)ASN1_STRING_get0_data(asn1);
+            const int length = ASN1_STRING_length(asn1);
+            if (data && length >= 0 && length != strlen(data))
                 return SERF_ERROR_SSL_CERT_FAILED;
+        }
     }
 
     return APR_SUCCESS;
@@ -976,12 +1007,28 @@ validate_server_certificate(int cert_val
         failures |= SERF_SSL_CERT_INVALID_HOST;
 
     /* Check certificate expiry dates. */
+#ifdef SERF_HAVE_SSL_X509_CHECK_CERTIFICATE_TIMES /* OpenSSL >= 4.0 */
+    {
+        int error;
+        if (!X509_check_certificate_times(NULL, server_cert, &error)) {
+            if (error == X509_V_ERR_CERT_NOT_YET_VALID)
+                failures |= SERF_SSL_CERT_NOTYETVALID;
+            else if (error == X509_V_ERR_CERT_HAS_EXPIRED)
+                failures |= SERF_SSL_CERT_EXPIRED;
+            else {
+                /* There was an error in the certificate time fields. */
+                failures |= SERF_SSL_CERT_NOTYETVALID | SERF_SSL_CERT_EXPIRED;
+            }
+        }
+    }
+#else
     if (X509_cmp_current_time(X509_get0_notBefore(server_cert)) >= 0) {
         failures |= SERF_SSL_CERT_NOTYETVALID;
     }
     else if (X509_cmp_current_time(X509_get0_notAfter(server_cert)) <= 0) {
         failures |= SERF_SSL_CERT_EXPIRED;
     }
+#endif
 
     if (ctx->server_cert_callback &&
         (depth == 0 || failures)) {
@@ -2646,58 +2693,41 @@ pstrdup_escape_nul_bytes(const char *buf
     return ret;
 }
 
+/* Helper for convert_X509_NAME_to_table() */
+static void
+set_X500_name_entry(const_X509_NAME_p org, int nid,
+                    const char *key, apr_hash_t *tgt,
+                    apr_pool_t *pool)
+{
+    const ASN1_STRING *const asn1 = get_first_name_entry_data(org, nid);
+    if (asn1) {
+        const char *const data = (const char*)ASN1_STRING_get0_data(asn1);
+        const int length = ASN1_STRING_length(asn1);
+        apr_hash_set(tgt, key, APR_HASH_KEY_STRING,
+                     pstrdup_escape_nul_bytes(data, length, pool));
+    }
+}
+
 /* Creates a hash_table with keys (E, CN, OU, O, L, ST and C). Any NUL bytes in
-   these fields in the certificate will be escaped as \00. */
+   these fields in the certificate will be escaped as \00.
+
+   FIXME: This function will only store the FIRST instance of a NID into the
+          hash table. Other instances will be ignored. In other words,
+          serf_ssl_cert_issuer() and serf_ssl_cert_subject() return incomplete
+          data. We need a better public API for this.
+*/
 static apr_hash_t *
-convert_X509_NAME_to_table(X509_NAME *org, apr_pool_t *pool)
+convert_X509_NAME_to_table(const_X509_NAME_p org, apr_pool_t *pool)
 {
-    char buf[1024];
-    int ret;
-
     apr_hash_t *tgt = apr_hash_make(pool);
 
-    ret = X509_NAME_get_text_by_NID(org,
-                                    NID_commonName,
-                                    buf, 1024);
-    if (ret != -1)
-        apr_hash_set(tgt, "CN", APR_HASH_KEY_STRING,
-                     pstrdup_escape_nul_bytes(buf, ret, pool));
-    ret = X509_NAME_get_text_by_NID(org,
-                                    NID_pkcs9_emailAddress,
-                                    buf, 1024);
-    if (ret != -1)
-        apr_hash_set(tgt, "E", APR_HASH_KEY_STRING,
-                     pstrdup_escape_nul_bytes(buf, ret, pool));
-    ret = X509_NAME_get_text_by_NID(org,
-                                    NID_organizationalUnitName,
-                                    buf, 1024);
-    if (ret != -1)
-        apr_hash_set(tgt, "OU", APR_HASH_KEY_STRING,
-                     pstrdup_escape_nul_bytes(buf, ret, pool));
-    ret = X509_NAME_get_text_by_NID(org,
-                                    NID_organizationName,
-                                    buf, 1024);
-    if (ret != -1)
-        apr_hash_set(tgt, "O", APR_HASH_KEY_STRING,
-                     pstrdup_escape_nul_bytes(buf, ret, pool));
-    ret = X509_NAME_get_text_by_NID(org,
-                                    NID_localityName,
-                                    buf, 1024);
-    if (ret != -1)
-        apr_hash_set(tgt, "L", APR_HASH_KEY_STRING,
-                     pstrdup_escape_nul_bytes(buf, ret, pool));
-    ret = X509_NAME_get_text_by_NID(org,
-                                    NID_stateOrProvinceName,
-                                    buf, 1024);
-    if (ret != -1)
-        apr_hash_set(tgt, "ST", APR_HASH_KEY_STRING,
-                     pstrdup_escape_nul_bytes(buf, ret, pool));
-    ret = X509_NAME_get_text_by_NID(org,
-                                    NID_countryName,
-                                    buf, 1024);
-    if (ret != -1)
-        apr_hash_set(tgt, "C", APR_HASH_KEY_STRING,
-                     pstrdup_escape_nul_bytes(buf, ret, pool));
+    set_X500_name_entry(org, NID_commonName, "CN", tgt, pool);
+    set_X500_name_entry(org, NID_pkcs9_emailAddress, "E", tgt, pool);
+    set_X500_name_entry(org, NID_organizationalUnitName, "OU", tgt, pool);
+    set_X500_name_entry(org, NID_organizationName, "O", tgt, pool);
+    set_X500_name_entry(org, NID_localityName, "L", tgt, pool);
+    set_X500_name_entry(org, NID_stateOrProvinceName, "ST", tgt, pool);
+    set_X500_name_entry(org, NID_countryName, "C", tgt, pool);
 
     return tgt;
 }
@@ -2713,7 +2743,7 @@ apr_hash_t *serf_ssl_cert_issuer(
     const serf_ssl_certificate_t *cert,
     apr_pool_t *pool)
 {
-    X509_NAME *issuer = X509_get_issuer_name(cert->ssl_cert);
+    const_X509_NAME_p issuer = X509_get_issuer_name(cert->ssl_cert);
 
     if (!issuer)
         return NULL;
@@ -2726,7 +2756,7 @@ apr_hash_t *serf_ssl_cert_subject(
     const serf_ssl_certificate_t *cert,
     apr_pool_t *pool)
 {
-    X509_NAME *subject = X509_get_subject_name(cert->ssl_cert);
+    const_X509_NAME_p subject = X509_get_subject_name(cert->ssl_cert);
 
     if (!subject)
         return NULL;

Reply via email to