Submitted By:            Douglas R. Reno <renodr at linuxfromscratch dot org>
Date:                    2026-05-12
Initial Package Version: 2.4.67
Upstream Status:         Pending
Origin:                  Upstream PR#642
Description:             Fixes building httpd-2.4.67 with OpenSSL-4 by making
                         several adaptations to ASN1_*/X509_* usage.

diff -Naurp httpd-2.4.67.orig/.github/workflows/linux.yml httpd-2.4.67/.github/workflows/linux.yml
--- httpd-2.4.67.orig/.github/workflows/linux.yml	2026-04-28 13:10:32.000000000 -0500
+++ httpd-2.4.67/.github/workflows/linux.yml	2026-05-12 15:05:26.353213272 -0500
@@ -260,6 +260,58 @@ jobs:
           #     APR_VERSION=1.7.3
           #     APU_VERSION=1.6.3
           # APU_CONFIG="--with-crypto --with-ldap"
+          # -------------------------------------------------------------------------
+          - name: OpenSSL 3.0 LTS
+            config: --enable-mods-shared=most --enable-maintainer-mode --disable-md --disable-http2 --disable-ldap --disable-crypto
+            env: |
+              TEST_OPENSSL3=3.0.18
+              APR_VERSION=1.7.6
+              APU_VERSION=1.6.3
+              APU_CONFIG="--without-crypto"
+            pkgs: subversion
+          # -------------------------------------------------------------------------
+          - name: OpenSSL 3.4 -Werror
+            config: --enable-mods-shared=most --enable-maintainer-mode --disable-md --disable-http2 --disable-ldap --disable-crypto
+            notest-cflags: -Werror -O2
+            env: |
+              TEST_OPENSSL3=3.4.4
+              APR_VERSION=1.7.6
+              APU_VERSION=1.6.3
+              APU_CONFIG="--without-crypto"
+            pkgs: subversion
+          # -------------------------------------------------------------------------
+          - name: OpenSSL 3.4 no-engine
+            config: --enable-mods-shared=most --enable-maintainer-mode --disable-md --disable-http2 --disable-ldap --disable-crypto
+            env: |
+              TEST_OPENSSL3=3.4.4
+              OPENSSL_CONFIG=no-engine
+              APR_VERSION=1.7.6
+              APU_VERSION=1.6.3
+              APU_CONFIG="--without-crypto"
+            pkgs: subversion
+          # -------------------------------------------------------------------------
+          - name: OpenSSL 3.5 no-engine -Werror
+            config: --enable-mods-shared=most --enable-maintainer-mode --disable-md --disable-http2 --disable-ldap --disable-crypto
+            notest-cflags: -Werror -O2
+            env: |
+              TEST_OPENSSL3=3.5.5
+              OPENSSL_CONFIG=no-engine
+              APR_VERSION=1.7.6
+              APU_VERSION=1.6.3
+              APU_CONFIG="--without-crypto"
+            pkgs: subversion
+          # -------------------------------------------------------------------------
+          - name: OpenSSL 4.0
+            config: --enable-mods-shared=most --enable-maintainer-mode --disable-md --disable-http2 --disable-ldap --disable-crypto
+            notest-cflags: -Werror -O2
+            env: |
+              TEST_OPENSSL3=4.0.0
+              OPENSSL_CONFIG=
+              APR_VERSION=1.7.6
+              APU_VERSION=1.6.3
+              APU_CONFIG="--without-crypto"
+            pkgs: subversion
+          # -------------------------------------------------------------------------
     runs-on: ${{ matrix.os == '' && 'ubuntu-latest' || matrix.os }}
     timeout-minutes: 30
     env:
diff -Naurp httpd-2.4.67.orig/modules/ssl/ssl_engine_kernel.c httpd-2.4.67/modules/ssl/ssl_engine_kernel.c
--- httpd-2.4.67.orig/modules/ssl/ssl_engine_kernel.c	2025-11-24 03:06:41.000000000 -0600
+++ httpd-2.4.67/modules/ssl/ssl_engine_kernel.c	2026-05-12 15:05:26.346876649 -0500
@@ -1254,7 +1254,7 @@ int ssl_hook_UserCheck(request_rec *r)
     }
 
     if (!sslconn->client_dn) {
-        X509_NAME *name = X509_get_subject_name(sslconn->client_cert);
+        const X509_NAME *name = X509_get_subject_name(sslconn->client_cert);
         char *cp = X509_NAME_oneline(name, NULL, 0);
         sslconn->client_dn = apr_pstrdup(r->connection->pool, cp);
         OPENSSL_free(cp);
@@ -1778,7 +1778,7 @@ int ssl_callback_proxy_cert(SSL *ssl, X5
     server_rec *s = mySrvFromConn(c);
     SSLSrvConfigRec *sc = mySrvConfig(s);
     SSLDirConfigRec *dc = myDirConfigFromConn(c);
-    X509_NAME *ca_name, *issuer, *ca_issuer;
+    const X509_NAME *ca_name, *issuer, *ca_issuer;
     X509_INFO *info;
     X509 *ca_cert;
     STACK_OF(X509_NAME) *ca_list;
diff -Naurp httpd-2.4.67.orig/modules/ssl/ssl_engine_log.c httpd-2.4.67/modules/ssl/ssl_engine_log.c
--- httpd-2.4.67.orig/modules/ssl/ssl_engine_log.c	2021-12-13 04:27:03.000000000 -0600
+++ httpd-2.4.67/modules/ssl/ssl_engine_log.c	2026-05-12 15:05:26.346996647 -0500
@@ -126,7 +126,7 @@ void ssl_log_ssl_error(const char *file,
 static void ssl_log_cert_error(const char *file, int line, int level,
                                apr_status_t rv, const server_rec *s,
                                const conn_rec *c, const request_rec *r,
-                               apr_pool_t *p, X509 *cert, const char *format,
+                               apr_pool_t *p, const X509 *cert, const char *format,
                                va_list ap)
 {
     char buf[HUGE_STRING_LEN];
@@ -167,14 +167,14 @@ static void ssl_log_cert_error(const cha
             }
 
             BIO_puts(bio, " / serial: ");
-            if (i2a_ASN1_INTEGER(bio, X509_get_serialNumber(cert)) == -1)
+            if (i2a_ASN1_INTEGER(bio, X509_get0_serialNumber(cert)) == -1)
                 BIO_puts(bio, "(ERROR)");
 
             BIO_puts(bio, " / notbefore: ");
-            ASN1_TIME_print(bio, X509_get_notBefore(cert));
+            ASN1_TIME_print(bio, X509_get0_notBefore(cert));
 
             BIO_puts(bio, " / notafter: ");
-            ASN1_TIME_print(bio, X509_get_notAfter(cert));
+            ASN1_TIME_print(bio, X509_get0_notAfter(cert));
 
             BIO_puts(bio, "]");
 
@@ -209,7 +209,7 @@ static void ssl_log_cert_error(const cha
  * in the other cases we use the connection and request pool, respectively).
  */
 void ssl_log_xerror(const char *file, int line, int level, apr_status_t rv,
-                    apr_pool_t *ptemp, server_rec *s, X509 *cert,
+                    apr_pool_t *ptemp, server_rec *s, const X509 *cert,
                     const char *fmt, ...)
 {
     if (APLOG_IS_LEVEL(s,level)) {
@@ -222,7 +222,7 @@ void ssl_log_xerror(const char *file, in
 }
 
 void ssl_log_cxerror(const char *file, int line, int level, apr_status_t rv,
-                     conn_rec *c, X509 *cert, const char *fmt, ...)
+                     conn_rec *c, const X509 *cert, const char *fmt, ...)
 {
     if (APLOG_IS_LEVEL(mySrvFromConn(c),level)) {
        va_list ap;
@@ -234,7 +234,7 @@ void ssl_log_cxerror(const char *file, i
 }
 
 void ssl_log_rxerror(const char *file, int line, int level, apr_status_t rv,
-                     request_rec *r, X509 *cert, const char *fmt, ...)
+                     request_rec *r, const X509 *cert, const char *fmt, ...)
 {
     if (APLOG_R_IS_LEVEL(r,level)) {
        va_list ap;
diff -Naurp httpd-2.4.67.orig/modules/ssl/ssl_engine_ocsp.c httpd-2.4.67/modules/ssl/ssl_engine_ocsp.c
--- httpd-2.4.67.orig/modules/ssl/ssl_engine_ocsp.c	2021-04-18 14:11:48.000000000 -0500
+++ httpd-2.4.67/modules/ssl/ssl_engine_ocsp.c	2026-05-12 15:05:26.347043405 -0500
@@ -38,8 +38,8 @@ static const char *extract_responder_uri
         /* Name found in extension, and is a URI: */
         if (OBJ_obj2nid(value->method) == NID_ad_OCSP
             && value->location->type == GEN_URI) {
-            result = apr_pstrdup(pool,
-                                 (char *)value->location->d.uniformResourceIdentifier->data);
+            const ASN1_STRING *uri = value->location->d.uniformResourceIdentifier;
+            result = modssl_ASN1_STRING_convert(pool, uri, 0);
         }
     }
 
diff -Naurp httpd-2.4.67.orig/modules/ssl/ssl_engine_vars.c httpd-2.4.67/modules/ssl/ssl_engine_vars.c
--- httpd-2.4.67.orig/modules/ssl/ssl_engine_vars.c	2025-07-07 07:03:42.000000000 -0500
+++ httpd-2.4.67/modules/ssl/ssl_engine_vars.c	2026-05-12 15:05:26.347135771 -0500
@@ -41,10 +41,10 @@
 
 static char *ssl_var_lookup_ssl(apr_pool_t *p, SSLConnRec *sslconn, request_rec *r, char *var);
 static char *ssl_var_lookup_ssl_cert(apr_pool_t *p, request_rec *r, X509 *xs, char *var);
-static char *ssl_var_lookup_ssl_cert_dn(apr_pool_t *p, X509_NAME *xsname, const char *var);
+static char *ssl_var_lookup_ssl_cert_dn(apr_pool_t *p, const X509_NAME *xsname, const char *var);
 static char *ssl_var_lookup_ssl_cert_san(apr_pool_t *p, X509 *xs, char *var);
-static char *ssl_var_lookup_ssl_cert_valid(apr_pool_t *p, ASN1_TIME *tm);
-static char *ssl_var_lookup_ssl_cert_remain(apr_pool_t *p, ASN1_TIME *tm);
+static char *ssl_var_lookup_ssl_cert_valid(apr_pool_t *p, const ASN1_TIME *tm);
+static char *ssl_var_lookup_ssl_cert_remain(apr_pool_t *p, const ASN1_TIME *tm);
 static char *ssl_var_lookup_ssl_cert_serial(apr_pool_t *p, X509 *xs);
 static char *ssl_var_lookup_ssl_cert_chain(apr_pool_t *p, STACK_OF(X509) *sk, char *var);
 static char *ssl_var_lookup_ssl_cert_rfc4523_cea(apr_pool_t *p, SSL *ssl);
@@ -444,7 +444,7 @@ static char *ssl_var_lookup_ssl(apr_pool
 }
 
 static char *ssl_var_lookup_ssl_cert_dn_oneline(apr_pool_t *p, request_rec *r,
-                                                X509_NAME *xsname)
+                                                const X509_NAME *xsname)
 {
     char *result = NULL;
     SSLDirConfigRec *dc;
@@ -476,7 +476,7 @@ static char *ssl_var_lookup_ssl_cert(apr
 {
     char *result;
     BOOL resdup;
-    X509_NAME *xsname;
+    const X509_NAME *xsname;
     int nid;
 
     result = NULL;
@@ -490,13 +490,13 @@ static char *ssl_var_lookup_ssl_cert(apr
         result = ssl_var_lookup_ssl_cert_serial(p, xs);
     }
     else if (strcEQ(var, "V_START")) {
-        result = ssl_var_lookup_ssl_cert_valid(p, X509_get_notBefore(xs));
+        result = ssl_var_lookup_ssl_cert_valid(p, X509_get0_notBefore(xs));
     }
     else if (strcEQ(var, "V_END")) {
-        result = ssl_var_lookup_ssl_cert_valid(p, X509_get_notAfter(xs));
+        result = ssl_var_lookup_ssl_cert_valid(p, X509_get0_notAfter(xs));
     }
     else if (strcEQ(var, "V_REMAIN")) {
-        result = ssl_var_lookup_ssl_cert_remain(p, X509_get_notAfter(xs));
+        result = ssl_var_lookup_ssl_cert_remain(p, X509_get0_notAfter(xs));
         resdup = FALSE;
     }
     else if (*var && strcEQ(var+1, "_DN")) {
@@ -583,12 +583,12 @@ static const struct {
     { NULL,    0,                          0 }
 };
 
-static char *ssl_var_lookup_ssl_cert_dn(apr_pool_t *p, X509_NAME *xsname,
+static char *ssl_var_lookup_ssl_cert_dn(apr_pool_t *p, const X509_NAME *xsname,
                                         const char *var)
 {
     const char *ptr;
     char *result;
-    X509_NAME_ENTRY *xsne;
+    const X509_NAME_ENTRY *xsne;
     int i, j, n, idx = 0, raw = 0;
     apr_size_t varlen;
 
@@ -615,7 +615,7 @@ static char *ssl_var_lookup_ssl_cert_dn(
             for (j = 0; j < X509_NAME_entry_count(xsname); j++) {
                 xsne = X509_NAME_get_entry(xsname, j);
 
-                n =OBJ_obj2nid((ASN1_OBJECT *)X509_NAME_ENTRY_get_object(xsne));
+                n = OBJ_obj2nid(X509_NAME_ENTRY_get_object(xsne));
 
                 if (n == ssl_var_lookup_ssl_cert_dn_rec[i].nid && idx-- == 0) {
                     result = modssl_X509_NAME_ENTRY_to_string(p, xsne, raw);
@@ -672,7 +672,7 @@ static char *ssl_var_lookup_ssl_cert_san
         return NULL;
 }
 
-static char *ssl_var_lookup_ssl_cert_valid(apr_pool_t *p, ASN1_TIME *tm)
+static char *ssl_var_lookup_ssl_cert_valid(apr_pool_t *p, const ASN1_TIME *tm)
 {
     BIO* bio;
 
@@ -687,8 +687,15 @@ static char *ssl_var_lookup_ssl_cert_val
 
 /* Return a string giving the number of days remaining until 'tm', or
  * "0" if this can't be determined. */
-static char *ssl_var_lookup_ssl_cert_remain(apr_pool_t *p, ASN1_TIME *tm)
+static char *ssl_var_lookup_ssl_cert_remain(apr_pool_t *p, const ASN1_TIME *tm)
 {
+#if OPENSSL_VERSION_NUMBER >= 0x10101000L && !defined(LIBRESSL_VERSION_NUMBER)
+    int diff;
+
+    if (ASN1_TIME_check(tm) != 1 || ASN1_TIME_diff(&diff, NULL, NULL, tm) != 1) {
+        return "0";
+    }
+#else
     apr_time_t then, now = apr_time_now();
     apr_time_exp_t exp = {0};
     long diff;
@@ -723,6 +730,7 @@ static char *ssl_var_lookup_ssl_cert_rem
     }
 
     diff = (long)((apr_time_sec(then) - apr_time_sec(now)) / (60*60*24));
+#endif
 
     return diff > 0 ? apr_ltoa(p, diff) : apr_pstrdup(p, "0");
 }
@@ -772,7 +780,7 @@ static char *ssl_var_lookup_ssl_cert_rfc
 
     serialNumber = X509_get_serialNumber(xs);
     if (serialNumber) {
-        X509_NAME *issuer = X509_get_issuer_name(xs);
+        const X509_NAME *issuer = X509_get_issuer_name(xs);
         if (issuer) {
             BIGNUM *bn = ASN1_INTEGER_to_BN(serialNumber, NULL);
             char *decimal = BN_bn2dec(bn);
@@ -896,9 +904,9 @@ static char *ssl_var_lookup_ssl_version(
 /* Add each RDN in 'xn' to the table 't' where the NID is present in
  * 'nids', using key prefix 'pfx'.  */
 static void extract_dn(apr_table_t *t, apr_hash_t *nids, const char *pfx,
-                       X509_NAME *xn, apr_pool_t *p)
+                       const X509_NAME *xn, apr_pool_t *p)
 {
-    X509_NAME_ENTRY *xsne;
+    const X509_NAME_ENTRY *xsne;
     apr_hash_t *count;
     int i, nid;
 
@@ -913,7 +921,7 @@ static void extract_dn(apr_table_t *t, a
 
          /* Retrieve the nid, and check whether this is one of the nids
           * which are to be extracted. */
-         nid = OBJ_obj2nid((ASN1_OBJECT *)X509_NAME_ENTRY_get_object(xsne));
+         nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(xsne));
 
          tag = apr_hash_get(nids, &nid, sizeof nid);
          if (tag) {
@@ -1026,15 +1034,19 @@ void modssl_var_extract_san_entries(apr_
  * parse the extension type as a primitive string.  This will fail for
  * any structured extension type per the docs.  Returns non-zero on
  * success and writes the string to the given bio. */
-static int dump_extn_value(BIO *bio, ASN1_OCTET_STRING *str)
+static int dump_extn_value(BIO *bio, const ASN1_OCTET_STRING *str)
 {
-    const unsigned char *pp = str->data;
+    const unsigned char *pp = ASN1_STRING_get0_data(str);
     ASN1_STRING *ret = ASN1_STRING_new();
     int rv = 0;
 
+    if (!ret) {
+        return rv;
+    }
+
     /* This allows UTF8String, IA5String, VisibleString, or BMPString;
      * conversion to UTF-8 is forced. */
-    if (d2i_DISPLAYTEXT(&ret, &pp, str->length)) {
+    if (d2i_DISPLAYTEXT(&ret, &pp, ASN1_STRING_length(str))) {
         ASN1_STRING_print_ex(bio, ret, ASN1_STRFLGS_UTF8_CONVERT);
         rv = 1;
     }
@@ -1081,7 +1093,7 @@ apr_array_header_t *ssl_ext_list(apr_poo
      */
     array = apr_array_make(p, count, sizeof(char *));
     for (j = 0; j < count; j++) {
-        X509_EXTENSION *ext = X509_get_ext(xs, j);
+        MODSSL_X509_EXT_CONST X509_EXTENSION *ext = X509_get_ext(xs, j);
 
         if (OBJ_cmp(X509_EXTENSION_get_object(ext), oid) == 0) {
             BIO *bio = BIO_new(BIO_s_mem());
diff -Naurp httpd-2.4.67.orig/modules/ssl/ssl_private.h httpd-2.4.67/modules/ssl/ssl_private.h
--- httpd-2.4.67.orig/modules/ssl/ssl_private.h	2025-11-06 03:09:18.000000000 -0600
+++ httpd-2.4.67/modules/ssl/ssl_private.h	2026-05-12 15:05:26.347249167 -0500
@@ -145,6 +145,12 @@
 #define MODSSL_SSL_METHOD_CONST
 #endif
 
+#if OPENSSL_VERSION_NUMBER >= 0x40000000L
+#define MODSSL_X509_EXT_CONST const
+#else
+#define MODSSL_X509_EXT_CONST
+#endif
+
 #if defined(LIBRESSL_VERSION_NUMBER)
 /* Missing from LibreSSL */
 #if LIBRESSL_VERSION_NUMBER < 0x2060000f
@@ -266,6 +272,12 @@
 #define BIO_get_shutdown(x)        (x->shutdown)
 #define BIO_set_shutdown(x,v)      (x->shutdown=v)
 #define DH_bits(x)                 (BN_num_bits(x->p))
+#define X509_up_ref(x)             (CRYPTO_add(&(x)->references, +1, CRYPTO_LOCK_X509))
+#define EVP_PKEY_up_ref(pk)        (CRYPTO_add(&(pk)->references, +1, CRYPTO_LOCK_EVP_PKEY))
+#define ASN1_STRING_get0_data(x)   ((x)->data)
+#define ASN1_STRING_length(x)      ((int)(x)->length)
+#define X509_get0_before(x)        X509_get_before(x)
+#define X509_get0_after(x)         X509_get_after(x)
 #else
 void init_bio_methods(void);
 void free_bio_methods(void);
@@ -1164,16 +1176,16 @@ void         ssl_log_ssl_error(const cha
  * counterparts. */
 void ssl_log_xerror(const char *file, int line, int level,
                     apr_status_t rv, apr_pool_t *p, server_rec *s,
-                    X509 *cert, const char *format, ...)
+                    const X509 *cert, const char *format, ...)
     __attribute__((format(printf,8,9)));
 
 void ssl_log_cxerror(const char *file, int line, int level,
-                     apr_status_t rv, conn_rec *c, X509 *cert,
+                     apr_status_t rv, conn_rec *c, const X509 *cert,
                      const char *format, ...)
     __attribute__((format(printf,7,8)));
 
 void ssl_log_rxerror(const char *file, int line, int level,
-                     apr_status_t rv, request_rec *r, X509 *cert,
+                     apr_status_t rv, request_rec *r, const X509 *cert,
                      const char *format, ...)
     __attribute__((format(printf,7,8)));
 
diff -Naurp httpd-2.4.67.orig/modules/ssl/ssl_util_ssl.c httpd-2.4.67/modules/ssl/ssl_util_ssl.c
--- httpd-2.4.67.orig/modules/ssl/ssl_util_ssl.c	2024-06-21 09:28:25.000000000 -0500
+++ httpd-2.4.67/modules/ssl/ssl_util_ssl.c	2026-05-12 15:05:26.347328638 -0500
@@ -202,7 +202,7 @@ char *modssl_bio_free_read(apr_pool_t *p
 /* Convert ASN.1 string to a pool-allocated char * string, escaping
  * control characters.  If raw is zero, convert to UTF-8, otherwise
  * unchanged from the character set. */
-static char *asn1_string_convert(apr_pool_t *p, ASN1_STRING *asn1str, int raw)
+char *modssl_ASN1_STRING_convert(apr_pool_t *p, const ASN1_STRING *asn1str, int raw)
 {
     BIO *bio;
     int flags = ASN1_STRFLGS_ESC_CTRL;
@@ -217,13 +217,13 @@ static char *asn1_string_convert(apr_poo
     return modssl_bio_free_read(p, bio);
 }
 
-#define asn1_string_to_utf8(p, a) asn1_string_convert(p, a, 0)
+#define asn1_string_to_utf8(p, a) modssl_ASN1_STRING_convert(p, a, 0)
 
 /* convert a NAME_ENTRY to UTF8 string */
-char *modssl_X509_NAME_ENTRY_to_string(apr_pool_t *p, X509_NAME_ENTRY *xsne,
+char *modssl_X509_NAME_ENTRY_to_string(apr_pool_t *p, const X509_NAME_ENTRY *xsne,
                                        int raw)
 {
-    char *result = asn1_string_convert(p, X509_NAME_ENTRY_get_data(xsne), raw);
+    char *result = modssl_ASN1_STRING_convert(p, X509_NAME_ENTRY_get_data(xsne), raw);
     ap_xlate_proto_from_ascii(result, len);
     return result;
 }
@@ -232,7 +232,7 @@ char *modssl_X509_NAME_ENTRY_to_string(a
  * convert an X509_NAME to an RFC 2253 formatted string, optionally truncated
  * to maxlen characters (specify a maxlen of 0 for no length limit)
  */
-char *modssl_X509_NAME_to_string(apr_pool_t *p, X509_NAME *dn, int maxlen)
+char *modssl_X509_NAME_to_string(apr_pool_t *p, const X509_NAME *dn, int maxlen)
 {
     char *result = NULL;
     BIO *bio;
@@ -362,7 +362,7 @@ BOOL modssl_X509_getSAN(apr_pool_t *p, X
 /* return an array of (RFC 6125 coined) DNS-IDs and CN-IDs in a certificate */
 static BOOL getIDs(apr_pool_t *p, X509 *x509, apr_array_header_t **ids)
 {
-    X509_NAME *subj;
+    const X509_NAME *subj;
     int i = -1;
 
     /* First, the DNS-IDs (dNSName entries in the subjectAltName extension) */
diff -Naurp httpd-2.4.67.orig/modules/ssl/ssl_util_ssl.h httpd-2.4.67/modules/ssl/ssl_util_ssl.h
--- httpd-2.4.67.orig/modules/ssl/ssl_util_ssl.h	2021-05-12 05:14:42.000000000 -0500
+++ httpd-2.4.67/modules/ssl/ssl_util_ssl.h	2026-05-12 15:05:26.347378001 -0500
@@ -71,13 +71,19 @@ EVP_PKEY   *modssl_read_privatekey(const
 
 int         modssl_smart_shutdown(SSL *ssl);
 BOOL        modssl_X509_getBC(X509 *, int *, int *);
-char       *modssl_X509_NAME_ENTRY_to_string(apr_pool_t *p, X509_NAME_ENTRY *xsne,
+char       *modssl_X509_NAME_ENTRY_to_string(apr_pool_t *p, const X509_NAME_ENTRY *xsne,
                                              int raw);
-char       *modssl_X509_NAME_to_string(apr_pool_t *, X509_NAME *, int);
+char       *modssl_X509_NAME_to_string(apr_pool_t *, const X509_NAME *, int);
 BOOL        modssl_X509_getSAN(apr_pool_t *, X509 *, int, const char *, int, apr_array_header_t **);
 BOOL        modssl_X509_match_name(apr_pool_t *, X509 *, const char *, BOOL, server_rec *);
 char       *modssl_SSL_SESSION_id2sz(IDCONST unsigned char *, int, char *, int);
 
+/* Convert ASN.1 string to a pool-allocated char * string, escaping
+ * control characters.  If raw is zero, convert to UTF-8, otherwise
+ * unchanged from the character set. */
+char *modssl_ASN1_STRING_convert(apr_pool_t *p, const ASN1_STRING *asn1str,
+                                 int raw);
+
 /* Reads the remaining data in BIO, if not empty, and copies it into a
  * pool-allocated string.  If empty, returns NULL.  BIO_free(bio) is
  * called for both cases. */
diff -Naurp httpd-2.4.67.orig/support/ab.c httpd-2.4.67/support/ab.c
--- httpd-2.4.67.orig/support/ab.c	2025-01-15 05:51:28.000000000 -0600
+++ httpd-2.4.67/support/ab.c	2026-05-12 15:05:26.347467812 -0500
@@ -675,7 +675,7 @@ static int ssl_print_connection_info(BIO
 
 static void ssl_print_cert_info(BIO *bio, X509 *cert)
 {
-    X509_NAME *dn;
+    const X509_NAME *dn;
     EVP_PKEY *pk;
     char buf[1024];
 
diff -Naurp httpd-2.4.67.orig/test/travis_before_linux.sh httpd-2.4.67/test/travis_before_linux.sh
--- httpd-2.4.67.orig/test/travis_before_linux.sh	2026-02-23 05:53:02.000000000 -0600
+++ httpd-2.4.67/test/travis_before_linux.sh	2026-05-12 15:05:26.353390228 -0500
@@ -120,6 +120,13 @@ if ! test -v SKIP_TESTING -o -v NO_TEST_
 
     # Make a shallow clone of httpd-tests git repo.
     git clone -q --depth=1 https://github.com/apache/httpd-tests.git test/perl-framework
+
+    # For OpenSSL 3.2+ testing, Apache::Test r1916067 is required, so
+    # use a checkout of trunk until there is an updated CPAN release
+    # with that revision.
+    if test -v TEST_OPENSSL3; then
+       svn co -q https://svn.apache.org/repos/asf/perl/Apache-Test/trunk test/perl-framework/Apache-Test
+    fi
 fi
 
 # For LDAP testing, run slapd listening on port 8389 and populate the
@@ -138,22 +145,46 @@ if test -v TEST_SSL; then
     popd
 fi
 
+# Build the requested version of OpenSSL if it's not already installed
+# in the cached ~/root
 if test -v TEST_OPENSSL3; then
-    # Build the requested version of OpenSSL if it's not already
-    # installed in the cached ~/root
+    # For a branch, rebuild if the remote branch has updated.
+    if test -v TEST_OPENSSL3_BRANCH -a -f $HOME/root/openssl-is-${TEST_OPENSSL3}; then
+        latest=`git ls-remote https://github.com/openssl/openssl refs/heads/${TEST_OPENSSL3_BRANCH} | cut -f1`
+        : Got branch latest commit ${latest}
+        if grep -q ^${latest} $HOME/root/openssl-is-${TEST_OPENSSL3}; then
+            : Cached repos already at ${latest}
+        else
+            : Forcing rebuild
+            rm -f $HOME/root/openssl-is-${TEST_OPENSSL3}
+        fi
+    fi
+
     if ! test -f $HOME/root/openssl-is-${TEST_OPENSSL3}; then
         # Remove any previous install.
         rm -rf $HOME/root/openssl3
 
         mkdir -p build/openssl
         pushd build/openssl
-           curl "https://www.openssl.org/source/openssl-${TEST_OPENSSL3}.tar.gz" |
-              tar -xzf -
+           if test -v TEST_OPENSSL3_BRANCH; then
+               git clone --depth=1 -b $TEST_OPENSSL3_BRANCH -q https://github.com/openssl/openssl openssl-${TEST_OPENSSL3}
+           else
+               curl -L "https://github.com/openssl/openssl/releases/download/openssl-${TEST_OPENSSL3}/openssl-${TEST_OPENSSL3}.tar.gz" |
+                   tar -xzf -
+           fi
            cd openssl-${TEST_OPENSSL3}
-           ./Configure --prefix=$HOME/root/openssl3 shared no-tests
+           # Build with RPATH so ./bin/openssl doesn't require $LD_LIBRARY_PATH
+           ./Configure --prefix=$HOME/root/openssl3 \
+                       shared no-tests ${OPENSSL_CONFIG} \
+                       '-Wl,-rpath=$(LIBRPATH)'
            make $MFLAGS
            make install_sw
-           touch $HOME/root/openssl-is-${TEST_OPENSSL3}
+           if test -d .git; then
+               : Caching git commit hash:
+               git rev-parse HEAD | tee $HOME/root/openssl-is-${TEST_OPENSSL3}
+           else
+               touch $HOME/root/openssl-is-${TEST_OPENSSL3}
+           fi
        popd
     fi
 
diff -Naurp httpd-2.4.67.orig/test/travis_run_linux.sh httpd-2.4.67/test/travis_run_linux.sh
--- httpd-2.4.67.orig/test/travis_run_linux.sh	2024-09-17 06:17:23.000000000 -0500
+++ httpd-2.4.67/test/travis_run_linux.sh	2026-05-12 15:05:26.353462405 -0500
@@ -61,7 +61,10 @@ fi
 
 if test -v TEST_OPENSSL3; then
     CONFIG="$CONFIG --with-ssl=$HOME/root/openssl3"
-    export LD_LIBRARY_PATH=$HOME/root/openssl3/lib:$HOME/root/openssl3/lib64
+    export PATH=$HOME/root/openssl3/bin:$PATH
+    # Force everything built to hard-code an RPATH
+    export LDFLAGS="-Wl,-rpath,$HOME/root/openssl3/lib -Wl,-rpath,$HOME/root/openssl3/lib64"
+    openssl version
 fi
 
 srcdir=$PWD
@@ -74,9 +77,19 @@ fi
 $srcdir/configure --prefix=$PREFIX $CONFIG
 make $MFLAGS
 
+if test -v TEST_OPENSSL3; then
+   # Clear the library/run paths so that anything else run during
+   # testing is not forced to use the custom OpenSSL build; e.g. perl,
+   # php-fpm, ...
+   unset LD_LIBRARY_PATH
+   unset LD_RUN_PATH
+fi
+
 if test -v TEST_INSTALL; then
    make install
    pushd $PREFIX
+     # Basic sanity tests of the installed server.
+     ./bin/apachectl -V
      test `./bin/apxs -q PREFIX` = $PREFIX
      test `$PWD/bin/apxs -q PREFIX` = $PREFIX
      ./bin/apxs -g -n foobar
@@ -84,174 +97,196 @@ if test -v TEST_INSTALL; then
    popd
 fi
 
-if ! test -v SKIP_TESTING; then
-    set +e
-    RV=0
-
-    if test -v TEST_MALLOC; then
-        # Enable enhanced glibc malloc debugging, see mallopt(3)
-        export MALLOC_PERTURB_=65 MALLOC_CHECK_=3
-        export LIBC_FATAL_STDERR_=1
-    fi
+if test -v SKIP_TESTING; then
+    # Check that httpd was built successfully, nothing more.
+    ./httpd -V
+    exit 0
+fi
 
-    if test -v TEST_UBSAN; then
-        export UBSAN_OPTIONS="log_path=$PWD/ubsan.log"
-    fi
+###############################################################
+### Everything below is only run if SKIP_TESTING was not set ##
+###############################################################
+
+: Running tests...
+
+set +e
+RV=0
+
+if test -v TEST_MALLOC; then
+    # Enable enhanced glibc malloc debugging, see mallopt(3)
+    export MALLOC_PERTURB_=65 MALLOC_CHECK_=3
+    export LIBC_FATAL_STDERR_=1
+fi
 
-    if test -v TEST_ASAN; then
-        export ASAN_OPTIONS="log_path=$PWD/asan.log:detect_leaks=0"
-    fi
+if test -v TEST_UBSAN; then
+    export UBSAN_OPTIONS="log_path=$PWD/ubsan.log"
+fi
 
-    # Try to keep all potential coredumps from all processes
-    sudo sysctl -w kernel.core_uses_pid=1 2>/dev/null || true
-    # Systemd based systems might process core dumps via systemd-coredump.
-    # But we want to have local unprocessed files.
-    sudo sysctl -w kernel.core_pattern=core || true
-    ulimit -c unlimited 2>/dev/null || true
+if test -v TEST_ASAN; then
+    export ASAN_OPTIONS="log_path=$PWD/asan.log:detect_leaks=0"
+fi
 
-    if test -v WITH_TEST_SUITE; then
-        make check TESTS="${TESTS}" TEST_CONFIG="${TEST_ARGS}"
-        RV=$?
-    else
-        test -v TEST_INSTALL || make install
-        pushd test/perl-framework
-            perl Makefile.PL -apxs $PREFIX/bin/apxs
-            make test APACHE_TEST_EXTRA_ARGS="${TEST_ARGS} ${TESTS}" | tee test.log
-            RV=${PIPESTATUS[0]}
-            # re-run failing tests with -v, avoiding set -e
-            if [ $RV -ne 0 ]; then
-                #mv t/logs/error_log t/logs/error_log_save
-                FAILERS=""
-                while read FAILER; do
-                    FAILERS="$FAILERS $FAILER"
-                done < <(awk '/Failed:/{print $1}' test.log)
-                if [ -n "$FAILERS" ]; then
-                    t/TEST -v $FAILERS || true
-                fi
-                # set -e would have killed us after the original t/TEST
-                rm -f test.log
-                #mv t/logs/error_log_save t/logs/error_log
-                false
+if test -v PHP_FPM; then
+    # Sanity test the executable exists.
+    $PHP_FPM --version
+fi
+
+# Try to keep all potential coredumps from all processes
+sudo sysctl -w kernel.core_uses_pid=1 2>/dev/null || true
+# Systemd based systems might process core dumps via systemd-coredump.
+# But we want to have local unprocessed files.
+sudo sysctl -w kernel.core_pattern=core || true
+ulimit -c unlimited 2>/dev/null || true
+
+if test -v WITH_TEST_SUITE; then
+    make check TESTS="${TESTS}" TEST_CONFIG="${TEST_ARGS}"
+    RV=$?
+else
+    test -v TEST_INSTALL || make install
+    pushd test/perl-framework
+        perl Makefile.PL -apxs $PREFIX/bin/apxs
+        make test APACHE_TEST_EXTRA_ARGS="${TEST_ARGS} ${TESTS}" | tee test.log
+        RV=${PIPESTATUS[0]}
+        # re-run failing tests with -v, avoiding set -e
+        if [ $RV -ne 0 ]; then
+            #mv t/logs/error_log t/logs/error_log_save
+            FAILERS=""
+            while read FAILER; do
+                FAILERS="$FAILERS $FAILER"
+            done < <(awk '/Failed:/{print $1}' test.log)
+            if [ -n "$FAILERS" ]; then
+                t/TEST -v $FAILERS || true
             fi
-        popd
-    fi
+            # set -e would have killed us after the original t/TEST
+            rm -f test.log
+            #mv t/logs/error_log_save t/logs/error_log
+            false
+        fi
+    popd
+fi
 
-    # Skip further testing if a core dump was created during the test
-    # suite run above.
-    if test $RV -eq 0 && test -n "`ls test/perl-framework/t/core{,.*} 2>/dev/null`"; then
-        RV=4
-    fi
+# Skip further testing if a core dump was created during the test
+# suite run above.
+if test $RV -eq 0 && test -n "`ls test/perl-framework/t/core{,.*} 2>/dev/null`"; then
+    RV=4
+fi
 
-    if test -v TEST_SSL -a $RV -eq 0; then
-        pushd test/perl-framework
-            # Test loading encrypted private keys
-            ./t/TEST -defines "TEST_SSL_DES3_KEY TEST_SSL_PASSPHRASE_EXEC" t/ssl
-            RV=$?
+if test \( -v TEST_SSL -o -v TEST_OPENSSL3 \) \
+        -a -f test/perl-framework/t/logs/error_log; then
+    : -- Check OpenSSL version used by mod_ssl at compile- and run-time --
+    grep 'mod_ssl.*compiled against' test/perl-framework/t/logs/error_log | tail -n1 | grep --color=always 'OpenSSL/[^ ]*'
+    grep 'resuming normal operations' test/perl-framework/t/logs/error_log | tail -n1 | grep --color=always 'OpenSSL/[^ ]*'
+fi
 
-            # Log the OpenSSL version.
-            grep 'mod_ssl.*compiled against' t/logs/error_log | tail -n 1
-            
-            # Test various session cache backends
-            for cache in shmcb redis:localhost:6379 memcache:localhost:11211; do
-                test $RV -eq 0 || break
-
-                SSL_SESSCACHE=$cache ./t/TEST -sslproto TLSv1.2 -defines TEST_SSL_SESSCACHE -start
-                ./t/TEST t/ssl
-                RV=$?
-                ./t/TEST -stop
-                SRV=$?
-                if test $RV -eq 0 -a $SRV -ne 0; then
-                    RV=$SRV
-                fi
-            done
-        popd
-    fi
+if test -v TEST_SSL -a $RV -eq 0; then
+    pushd test/perl-framework
+        # Test loading encrypted private keys
+        ./t/TEST -defines "TEST_SSL_DES3_KEY TEST_SSL_PASSPHRASE_EXEC" t/ssl
+        RV=$?
 
-    if test -v LITMUS -a $RV -eq 0; then
-        pushd test/perl-framework
-           mkdir -p t/htdocs/modules/dav
-           ./t/TEST -start
-           # litmus uses $TESTS, so unset it.
-           unset TESTS
-           litmus http://localhost:8529/modules/dav/
-           RV=$?
-           ./t/TEST -stop
-        popd
-    fi
+        # Log the OpenSSL version.
+        grep 'mod_ssl.*compiled against' t/logs/error_log | tail -n 1
+        
+        # Test various session cache backends
+        for cache in shmcb redis:localhost:6379 memcache:localhost:11211; do
+            test $RV -eq 0 || break
 
-    if test $RV -ne 0 && test -f test/perl-framework/t/logs/error_log; then
-        grep -v ':\(debug\|trace[12345678]\)\]' test/perl-framework/t/logs/error_log
-    fi
+            SSL_SESSCACHE=$cache ./t/TEST -sslproto TLSv1.2 -defines TEST_SSL_SESSCACHE -start
+            ./t/TEST t/ssl
+            RV=$?
+            ./t/TEST -stop
+            SRV=$?
+            if test $RV -eq 0 -a $SRV -ne 0; then
+                RV=$SRV
+            fi
+        done
+    popd
+fi
 
-    if test -v TEST_CORE -a $RV -eq 0; then
-        # Run HTTP/2 tests.
-        MPM=event py.test-3 test/modules/core
-        RV=$?
-    fi
+if test -v LITMUS -a $RV -eq 0; then
+    pushd test/perl-framework
+       mkdir -p t/htdocs/modules/dav
+       ./t/TEST -start
+       # litmus uses $TESTS, so unset it.
+       unset TESTS
+       litmus http://localhost:8529/modules/dav/
+       RV=$?
+       ./t/TEST -stop
+    popd
+fi
 
-    if test -v TEST_H2 -a $RV -eq 0; then
-        # Build the test clients
-        (cd test/clients && make)
-        # Run HTTP/2 tests.
-        MPM=event py.test-3 test/modules/http2
-        RV=$?
-        if test $RV -eq 0; then
-          MPM=worker py.test-3 test/modules/http2
-          RV=$?
-        fi
-    fi
+if test $RV -ne 0 && test -f test/perl-framework/t/logs/error_log; then
+    grep -v ':\(debug\|trace[12345678]\)\]' test/perl-framework/t/logs/error_log
+fi
 
-    if test -v TEST_MD -a $RV -eq 0; then
-        # Run ACME tests.
-        # need the go based pebble as ACME test server
-        # which is a package on debian sid, but not on focal
-        export GOPATH=${PREFIX}/gocode
-        mkdir -p "${GOPATH}"
-        export PATH="${GOROOT}/bin:${GOPATH}/bin:${PATH}"
-        go get -u github.com/letsencrypt/pebble/...
-        (cd $GOPATH/src/github.com/letsencrypt/pebble && go install ./...)
+if test -v TEST_CORE -a $RV -eq 0; then
+    # Run HTTP/2 tests.
+    MPM=event py.test-3 test/modules/core
+    RV=$?
+fi
 
-        py.test-3 test/modules/md
-        RV=$?
+if test -v TEST_H2 -a $RV -eq 0; then
+    # Build the test clients
+    (cd test/clients && make)
+    # Run HTTP/2 tests.
+    MPM=event py.test-3 test/modules/http2
+    RV=$?
+    if test $RV -eq 0; then
+      MPM=worker py.test-3 test/modules/http2
+      RV=$?
     fi
+fi
 
-    # Catch cases where abort()s get logged to stderr by libraries but
-    # only cause child processes to terminate e.g. during shutdown,
-    # which may not otherwise trigger test failures.
-
-    # "glibc detected": printed with LIBC_FATAL_STDERR_/MALLOC_CHECK_
-    # glibc will abort when malloc errors are detected.  This will get
-    # caught by the segfault grep as well.
-
-    # "pool concurrency check": printed by APR built with
-    # --enable-thread-debug when an APR pool concurrency check aborts
-
-    for phrase in 'Segmentation fault' 'glibc detected' 'pool concurrency check:' 'Assertion.*failed'; do
-        # Ignore IO/debug logs
-        if grep -v ':\(debug\|trace[12345678]\)\]' test/perl-framework/t/logs/error_log | grep -q "$phrase"; then
-            grep --color=always -C5 "$phrase" test/perl-framework/t/logs/error_log
-            RV=2
-        fi
-    done
+if test -v TEST_MD -a $RV -eq 0; then
+    # Run ACME tests.
+    # need the go based pebble as ACME test server
+    # which is a package on debian sid, but not on focal
+    export GOPATH=${PREFIX}/gocode
+    mkdir -p "${GOPATH}"
+    export PATH="${GOROOT}/bin:${GOPATH}/bin:${PATH}"
+    go get -u github.com/letsencrypt/pebble/...
+    (cd $GOPATH/src/github.com/letsencrypt/pebble && go install ./...)
 
-    if test -v TEST_UBSAN && test -n "`ls ubsan.log.* 2>/dev/null`"; then
-        cat ubsan.log.*
-        RV=3
-    fi
+    py.test-3 test/modules/md
+    RV=$?
+fi
 
-    if test -v TEST_ASAN && test -n "`ls asan.log.* 2>/dev/null`"; then
-        cat asan.log.*
+# Catch cases where abort()s get logged to stderr by libraries but
+# only cause child processes to terminate e.g. during shutdown,
+# which may not otherwise trigger test failures.
+
+# "glibc detected": printed with LIBC_FATAL_STDERR_/MALLOC_CHECK_
+# glibc will abort when malloc errors are detected.  This will get
+# caught by the segfault grep as well.
+
+# "pool concurrency check": printed by APR built with
+# --enable-thread-debug when an APR pool concurrency check aborts
+
+for phrase in 'Segmentation fault' 'glibc detected' 'pool concurrency check:' 'Assertion.*failed'; do
+    # Ignore IO/debug logs
+    if grep -v ':\(debug\|trace[12345678]\)\]' test/perl-framework/t/logs/error_log | grep -q "$phrase"; then
+        grep --color=always -C5 "$phrase" test/perl-framework/t/logs/error_log
+        RV=2
+    fi
+done
+
+if test -v TEST_UBSAN && test -n "`ls ubsan.log.* 2>/dev/null`"; then
+    cat ubsan.log.*
+    RV=3
+fi
 
-        # ASan can report memory leaks, fail on errors only
-        if grep -q "ERROR: AddressSanitizer:" `ls asan.log.*`; then
-            RV=4
-        fi
+if test -v TEST_ASAN && test -n "`ls asan.log.* 2>/dev/null`"; then
+    cat asan.log.*
+
+    # ASan can report memory leaks, fail on errors only
+    if grep -q "ERROR: AddressSanitizer:" `ls asan.log.*`; then
+        RV=4
     fi
+fi
 
-    for core in `ls test/perl-framework/t/core{,.*} test/gen/apache/core{,.*} 2>/dev/null`; do
-        gdb -ex 'thread apply all backtrace full' -batch ./httpd "$core"
-        RV=5
-    done
+for core in `ls test/perl-framework/t/core{,.*} test/gen/apache/core{,.*} 2>/dev/null`; do
+    gdb -ex 'thread apply all backtrace full' -batch ./httpd "$core"
+    RV=5
+done
 
-    exit $RV
-fi
+exit $RV
