Commit 3678f391d8f for php

commit 3678f391d8f64738cdb37d3e2a95aeba615413c4
Author: Weilin Du <weilindu@php.net>
Date:   Tue Oct 27 01:37:55 2026 +0800

    ext/openssl: Fix build with OpenSSL 4.1 (#24686)

    OpenSSL 4.1 deprecates ASN1_STRING_set() and ASN1_STRING_length().
    use ASN1_STRING_set1_data() and ASN1_STRING_get_length() instead as
    recommended in official doc
    https://docs.openssl.org/4.1/man7/ossl-guide-migration/#asn1_string-values-have-never-been-nul-byte-terminated

diff --git a/NEWS b/NEWS
index 1995c82d1e8..355ea8880a6 100644
--- a/NEWS
+++ b/NEWS
@@ -33,6 +33,9 @@ PHP                                                                        NEWS
   . Fixed bug GH-24745 (File cache loses attributes on constants on 32-bit
     builds). (Yannick Decat, Lazizbek Ergashev)

+- OpenSSL:
+  . Fixed build with OpenSSL 4.1. (Weilin Du)
+
 - PDO_PGSQL:
   . Fixed bug GH-24461 (DISCARD ALL on persistent connection cleanup drops
     PgBouncer's prepared statements). (David Carlier, Artem Ukrainskiy)
diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c
index 0f36f7b42c5..0466bbf06c6 100644
--- a/ext/openssl/openssl.c
+++ b/ext/openssl/openssl.c
@@ -1019,7 +1019,7 @@ PHP_FUNCTION(openssl_spki_new)
 	}

 	if (challenge) {
-		if (!ASN1_STRING_set(spki->spkac->challenge, challenge, (int)challenge_len)) {
+		if (!ASN1_STRING_set1_data(spki->spkac->challenge, (const unsigned char *) challenge, challenge_len)) {
 			php_openssl_store_errors();
 			php_error_docref(NULL, E_WARNING, "Unable to set challenge data");
 			goto cleanup;
@@ -1206,7 +1206,8 @@ PHP_FUNCTION(openssl_spki_export_challenge)
 		goto cleanup;
 	}

-	RETVAL_STRING((const char *)ASN1_STRING_get0_data(spki->spkac->challenge));
+	RETVAL_STRINGL_FAST((const char *)ASN1_STRING_get0_data(spki->spkac->challenge),
+		ASN1_STRING_get_length(spki->spkac->challenge));
 	goto cleanup;

 cleanup:
diff --git a/ext/openssl/openssl_backend_common.c b/ext/openssl/openssl_backend_common.c
index 8ea132a1ab0..995d836deea 100644
--- a/ext/openssl/openssl_backend_common.c
+++ b/ext/openssl/openssl_backend_common.c
@@ -72,7 +72,7 @@ void php_openssl_add_assoc_name_entry(zval * val, char * key, const X509_NAME *
 		} else {
 			/* ASN1_STRING_get0_data(3): Since this is an internal pointer it should not be freed or modified in any way */
 			to_add = ASN1_STRING_get0_data(str);
-			to_add_len = ASN1_STRING_length(str);
+			to_add_len = ASN1_STRING_get_length(str);
 		}

 		if (to_add_len != -1) {
@@ -107,7 +107,7 @@ void php_openssl_add_assoc_name_entry(zval * val, char * key, const X509_NAME *

 void php_openssl_add_assoc_asn1_string(zval * val, char * key, const ASN1_STRING * str)
 {
-	add_assoc_stringl(val, key, (const char *)ASN1_STRING_get0_data(str), ASN1_STRING_length(str));
+	add_assoc_stringl(val, key, (const char *)ASN1_STRING_get0_data(str), ASN1_STRING_get_length(str));
 }

 time_t php_openssl_asn1_time_to_time_t(ASN1_UTCTIME * timestr)
@@ -123,6 +123,7 @@ time_t php_openssl_asn1_time_to_time_t(ASN1_UTCTIME * timestr)
 	struct tm thetime;
 	char * strbuf;
 	char * thestr;
+	const char *timestr_data;
 	long gmadjust = 0;
 	size_t timestr_len;

@@ -131,24 +132,25 @@ time_t php_openssl_asn1_time_to_time_t(ASN1_UTCTIME * timestr)
 		return (time_t)-1;
 	}

-	timestr_len = (size_t)ASN1_STRING_length(timestr);
+	timestr_len = ASN1_STRING_get_length(timestr);
+	timestr_data = timestr_len ? (const char *)ASN1_STRING_get0_data(timestr) : "";

-	if (timestr_len != strlen((const char *)ASN1_STRING_get0_data(timestr))) {
+	if (memchr(timestr_data, '\0', timestr_len) != NULL) {
 		php_error_docref(NULL, E_WARNING, "Illegal length in timestamp");
 		return (time_t)-1;
 	}

 	if (timestr_len < 13) {
-		php_error_docref(NULL, E_WARNING, "Unable to parse time string %s correctly", ASN1_STRING_get0_data(timestr));
+		php_error_docref(NULL, E_WARNING, "Unable to parse time string %.*s correctly", (int)timestr_len, timestr_data);
 		return (time_t)-1;
 	}

 	if (ASN1_STRING_type(timestr) == V_ASN1_GENERALIZEDTIME && timestr_len < 15) {
-		php_error_docref(NULL, E_WARNING, "Unable to parse time string %s correctly", ASN1_STRING_get0_data(timestr));
+		php_error_docref(NULL, E_WARNING, "Unable to parse time string %.*s correctly", (int)timestr_len, timestr_data);
 		return (time_t)-1;
 	}

-	strbuf = estrdup((const char *)ASN1_STRING_get0_data(timestr));
+	strbuf = estrndup(timestr_data, timestr_len);

 	memset(&thetime, 0, sizeof(thetime));

@@ -636,7 +638,7 @@ int openssl_x509v3_subjectAltName(BIO *bio, PHP_OPENSSL_X509_EXTENSION *extensio

 	extension_data = X509_EXTENSION_get_data(extension);
 	p = ASN1_STRING_get0_data(extension_data);
-	length = ASN1_STRING_length(extension_data);
+	length = ASN1_STRING_get_length(extension_data);
 	if (method->it) {
 		names = (GENERAL_NAMES*) (ASN1_item_d2i(NULL, &p, length,
 			ASN1_ITEM_ptr(method->it)));
@@ -658,19 +660,19 @@ int openssl_x509v3_subjectAltName(BIO *bio, PHP_OPENSSL_X509_EXTENSION *extensio
 				BIO_puts(bio, "email:");
 				as = name->d.rfc822Name;
 				BIO_write(bio, ASN1_STRING_get0_data(as),
-					ASN1_STRING_length(as));
+					ASN1_STRING_get_length(as));
 				break;
 			case GEN_DNS:
 				BIO_puts(bio, "DNS:");
 				as = name->d.dNSName;
 				BIO_write(bio, ASN1_STRING_get0_data(as),
-					ASN1_STRING_length(as));
+					ASN1_STRING_get_length(as));
 				break;
 			case GEN_URI:
 				BIO_puts(bio, "URI:");
 				as = name->d.uniformResourceIdentifier;
 				BIO_write(bio, ASN1_STRING_get0_data(as),
-					ASN1_STRING_length(as));
+					ASN1_STRING_get_length(as));
 				break;
 			default:
 				/* use builtin print for GEN_OTHERNAME, GEN_X400,
diff --git a/ext/openssl/php_openssl_backend.h b/ext/openssl/php_openssl_backend.h
index fcbb8d42542..364f27eec23 100644
--- a/ext/openssl/php_openssl_backend.h
+++ b/ext/openssl/php_openssl_backend.h
@@ -35,6 +35,11 @@
 #include <openssl/pkcs12.h>
 #include <openssl/cms.h>

+#if OPENSSL_VERSION_NUMBER < 0x40100000L
+#define ASN1_STRING_set1_data(str, data, len) ASN1_STRING_set((str), (data), (int) (len))
+#define ASN1_STRING_get_length(str) ((size_t) ASN1_STRING_length(str))
+#endif
+
 #if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 4
 typedef const X509_EXTENSION PHP_OPENSSL_X509_EXTENSION;
 #else
diff --git a/ext/openssl/tests/openssl_spki_export_challenge_binary.phpt b/ext/openssl/tests/openssl_spki_export_challenge_binary.phpt
new file mode 100644
index 00000000000..d5eb8617cb1
--- /dev/null
+++ b/ext/openssl/tests/openssl_spki_export_challenge_binary.phpt
@@ -0,0 +1,19 @@
+--TEST--
+openssl_spki_export_challenge() preserves empty challenges and embedded NUL bytes
+--EXTENSIONS--
+openssl
+--FILE--
+<?php
+$key = openssl_pkey_get_private('file://' . __DIR__ . '/private_rsa_2048.key');
+
+foreach (["", "challenge", "\0challenge", "challenge\0", "left\0right"] as $challenge) {
+    $spkac = openssl_spki_new($key, $challenge, OPENSSL_ALGO_SHA256);
+    var_dump(openssl_spki_export_challenge(substr($spkac, 6)) === $challenge);
+}
+?>
+--EXPECT--
+bool(true)
+bool(true)
+bool(true)
+bool(true)
+bool(true)
diff --git a/ext/openssl/tests/session_resumption_get_cb_num_tickets_positive.phpt b/ext/openssl/tests/session_resumption_get_cb_num_tickets_positive.phpt
index 5563b0c22cf..5c7512d73a3 100644
--- a/ext/openssl/tests/session_resumption_get_cb_num_tickets_positive.phpt
+++ b/ext/openssl/tests/session_resumption_get_cb_num_tickets_positive.phpt
@@ -16,6 +16,7 @@
     // Test with num_tickets = 3 (should issue 3 tickets)
     $ctx = stream_context_create(['ssl' => [
         'local_cert' => '%s',
+        'session_id_context' => 'num-tickets-test',
         'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_3_SERVER,
         'num_tickets' => 3,  // Issue 3 tickets per connection
     ]]);
diff --git a/ext/openssl/tests/session_unserialize_repeat.phpt b/ext/openssl/tests/session_unserialize_repeat.phpt
index b1e33c2d03b..20084df4fb2 100644
--- a/ext/openssl/tests/session_unserialize_repeat.phpt
+++ b/ext/openssl/tests/session_unserialize_repeat.phpt
@@ -13,6 +13,7 @@
 $serverCode = <<<'CODE'
     $serverCtx = stream_context_create(['ssl' => [
         'local_cert' => '%s',
+        'session_id_context' => 'unserialize-repeat-test',
     ]]);

     $server = stream_socket_server('tls://127.0.0.1:0', $errno, $errstr,
diff --git a/ext/openssl/xp_ssl.c b/ext/openssl/xp_ssl.c
index ad06af444ff..5cead037715 100644
--- a/ext/openssl/xp_ssl.c
+++ b/ext/openssl/xp_ssl.c
@@ -541,7 +541,7 @@ static bool php_openssl_matches_san_list(
 				/* TODO: warn ? */
 				continue;
 			}
-			if ((size_t)ASN1_STRING_length(san->d.dNSName) != strlen((const char*)cert_name)) {
+			if (ASN1_STRING_get_length(san->d.dNSName) != strlen((const char*)cert_name)) {
 				OPENSSL_free(cert_name);
 				/* prevent null-byte poisoning*/
 				continue;
@@ -561,7 +561,7 @@ static bool php_openssl_matches_san_list(
 			}
 			OPENSSL_free(cert_name);
 		} else if (san->type == GEN_IPADD) {
-			if (ASN1_STRING_length(san->d.iPAddress) == 4) {
+			if (ASN1_STRING_get_length(san->d.iPAddress) == 4) {
 				snprintf(ipbuffer, sizeof(ipbuffer), "%d.%d.%d.%d",
 					ASN1_STRING_get0_data(san->d.iPAddress)[0],
 					ASN1_STRING_get0_data(san->d.iPAddress)[1],
@@ -575,7 +575,7 @@ static bool php_openssl_matches_san_list(
 				}
 			}
 #ifdef HAVE_IPV6_SAN
-			else if (ASN1_STRING_length(san->d.ip) == 16 && subject_name_is_ipv6) {
+			else if (ASN1_STRING_get_length(san->d.ip) == 16 && subject_name_is_ipv6) {
 				ipbuffer[0] = 0;
 				EXPAND_IPV6_ADDRESS(ipbuffer, ASN1_STRING_get0_data(san->d.iPAddress));
 				if (strcasecmp((const char*)subject_name_ipv6_expanded, (const char*)ipbuffer) == 0) {
@@ -630,7 +630,7 @@ static bool php_openssl_matches_common_name(php_stream *stream, const X509 *peer
 		return false;
 	}
 	name_asn1 = X509_NAME_ENTRY_get_data(name_entry);
-	cert_name_len = ASN1_STRING_length(name_asn1);
+	cert_name_len = ASN1_STRING_get_length(name_asn1);
 	cert_name = (unsigned char *) OPENSSL_strndup((const char *) ASN1_STRING_get0_data(name_asn1), cert_name_len);
 	if (cert_name == NULL) {
 		php_stream_warn(stream, NetworkRecvFailed, "Unable to locate peer certificate CN");