From dd861c9a7d6e3bdcaf6bc9b81f239ca94a4ec0ec Mon Sep 17 00:00:00 2001 From: Shivam Mathur Date: Fri, 3 Jul 2026 19:15:40 +0530 Subject: [PATCH 1/2] ext/openssl: fix OpenSSL 4 build and test compatibility (#22267) * Fix OpenSSL 4 test compatibility * Fix OpenSSL 4 build compatibility * Use OpenSSL 4 in macOS CI * Fix OpenSSL 1.1 build compatibility * Preserve raw CN matching with OpenSSL 4 (cherry picked from commit 6aca6e30bb4ecf062d3d863de254b8b1d0914a8f) --- ext/openssl/openssl.c | 45 +++++++++++------ ext/openssl/php_openssl.h | 8 +++ ext/openssl/tests/bug28382.phpt | 8 ++- ext/openssl/tests/ecc_custom_params.phpt | 24 +++++++++ .../openssl_x509_parse_basic_openssl32.phpt | 20 +++++++- ext/openssl/xp_ssl.c | 49 +++++++++++++++---- 6 files changed, 127 insertions(+), 27 deletions(-) diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c index ce0a1656cabb..cc3badd8bf47 100644 --- a/ext/openssl/openssl.c +++ b/ext/openssl/openssl.c @@ -663,16 +663,16 @@ static STACK_OF(X509) * php_openssl_load_all_certs_from_file( char *cert_file, size_t cert_file_len, uint32_t arg_num); static EVP_PKEY * php_openssl_generate_private_key(struct php_x509_request * req); -static void php_openssl_add_assoc_name_entry(zval * val, char * key, X509_NAME * name, int shortname) /* {{{ */ +static void php_openssl_add_assoc_name_entry(zval * val, char * key, const X509_NAME * name, int shortname) /* {{{ */ { zval *data; zval subitem, tmp; int i; char *sname; int nid; - X509_NAME_ENTRY * ne; - ASN1_STRING * str = NULL; - ASN1_OBJECT * obj; + const X509_NAME_ENTRY * ne; + const ASN1_STRING * str = NULL; + const ASN1_OBJECT * obj; if (key != NULL) { array_init(&subitem); @@ -737,7 +737,7 @@ static void php_openssl_add_assoc_name_entry(zval * val, char * key, X509_NAME * } /* }}} */ -static void php_openssl_add_assoc_asn1_string(zval * val, char * key, ASN1_STRING * str) /* {{{ */ +static 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)); } @@ -1324,7 +1324,7 @@ PHP_MINIT_FUNCTION(openssl) } php_stream_xport_register("ssl", php_openssl_ssl_socket_factory); -#ifndef OPENSSL_NO_SSL3 +#if OPENSSL_VERSION_NUMBER < 0x40000000L && !defined(OPENSSL_NO_SSL3) php_stream_xport_register("sslv3", php_openssl_ssl_socket_factory); #endif php_stream_xport_register("tls", php_openssl_ssl_socket_factory); @@ -1410,7 +1410,7 @@ PHP_MSHUTDOWN_FUNCTION(openssl) php_unregister_url_stream_wrapper("ftps"); php_stream_xport_unregister("ssl"); -#ifndef OPENSSL_NO_SSL3 +#if OPENSSL_VERSION_NUMBER < 0x40000000L && !defined(OPENSSL_NO_SSL3) php_stream_xport_unregister("sslv3"); #endif php_stream_xport_unregister("tls"); @@ -2026,11 +2026,11 @@ PHP_FUNCTION(openssl_x509_verify) * Christian Heimes */ -static int openssl_x509v3_subjectAltName(BIO *bio, X509_EXTENSION *extension) +static int openssl_x509v3_subjectAltName(BIO *bio, PHP_OPENSSL_X509_EXTENSION *extension) { GENERAL_NAMES *names; const X509V3_EXT_METHOD *method = NULL; - ASN1_OCTET_STRING *extension_data; + const ASN1_OCTET_STRING *extension_data; long i, length, num; const unsigned char *p; @@ -2103,8 +2103,8 @@ PHP_FUNCTION(openssl_x509_parse) bool useshortnames = 1; char * tmpstr; zval subitem; - X509_EXTENSION *extension; - X509_NAME *subject_name; + PHP_OPENSSL_X509_EXTENSION *extension; + const X509_NAME *subject_name; char *cert_name; char *extname; BIO *bio_out; @@ -2989,7 +2989,12 @@ static zend_result php_openssl_csr_make(struct php_x509_request * req, X509_REQ zval *item, *subitem; zend_string *strindex = NULL; - subj = X509_REQ_get_subject_name(csr); + subj = X509_NAME_new(); + if (subj == NULL) { + php_openssl_store_errors(); + return FAILURE; + } + /* apply values from the dn hash */ ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(dn), strindex, item) { if (strindex) { @@ -2998,10 +3003,12 @@ static zend_result php_openssl_csr_make(struct php_x509_request * req, X509_REQ if (Z_TYPE_P(item) == IS_ARRAY) { ZEND_HASH_FOREACH_NUM_KEY_VAL(Z_ARRVAL_P(item), i, subitem) { if (php_openssl_csr_add_subj_entry(subitem, subj, nid) == FAILURE) { + X509_NAME_free(subj); return FAILURE; } } ZEND_HASH_FOREACH_END(); } else if (php_openssl_csr_add_subj_entry(item, subj, nid) == FAILURE) { + X509_NAME_free(subj); return FAILURE; } } else { @@ -3052,13 +3059,23 @@ static zend_result php_openssl_csr_make(struct php_x509_request * req, X509_REQ if (!X509_NAME_add_entry_by_txt(subj, type, MBSTRING_UTF8, (unsigned char*)v->value, -1, -1, 0)) { php_openssl_store_errors(); php_error_docref(NULL, E_WARNING, "add_entry_by_txt %s -> %s (failed)", type, v->value); + X509_NAME_free(subj); return FAILURE; } if (!X509_NAME_entry_count(subj)) { php_error_docref(NULL, E_WARNING, "No objects specified in config file"); + X509_NAME_free(subj); return FAILURE; } } + + if (!X509_REQ_set_subject_name(csr, subj)) { + php_openssl_store_errors(); + X509_NAME_free(subj); + return FAILURE; + } + X509_NAME_free(subj); + if (attribs) { ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(attribs), strindex, item) { int nid; @@ -3547,7 +3564,7 @@ PHP_FUNCTION(openssl_csr_get_subject) zend_object *csr_obj; zend_string *csr_str; bool use_shortnames = 1; - X509_NAME *subject; + const X509_NAME *subject; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_OBJ_OF_CLASS_OR_STR(csr_obj, php_openssl_request_ce, csr_str) @@ -4697,7 +4714,7 @@ static EVP_PKEY *php_openssl_pkey_init_ec(zval *data, bool *is_private) { } OPENSSL_PKEY_SET_BN(data, cofactor); - if (!OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_EC_COFACTOR, cofactor) || + if ((cofactor && !OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_EC_COFACTOR, cofactor)) || !EC_GROUP_set_generator(group, point_g, order, cofactor)) { goto cleanup; } diff --git a/ext/openssl/php_openssl.h b/ext/openssl/php_openssl.h index ece57efa7c07..0b1dd35795cd 100644 --- a/ext/openssl/php_openssl.h +++ b/ext/openssl/php_openssl.h @@ -25,7 +25,9 @@ extern zend_module_entry openssl_module_entry; #include "php_version.h" #define PHP_OPENSSL_VERSION PHP_VERSION +#include #include +#include #ifdef LIBRESSL_VERSION_NUMBER /* LibreSSL version check */ #if LIBRESSL_VERSION_NUMBER < 0x20700000L @@ -44,6 +46,12 @@ extern zend_module_entry openssl_module_entry; #endif #endif +#if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 4 +typedef const X509_EXTENSION PHP_OPENSSL_X509_EXTENSION; +#else +typedef X509_EXTENSION PHP_OPENSSL_X509_EXTENSION; +#endif + #define PHP_OPENSSL_ERR_BUFFER_SIZE 16 #define OPENSSL_RAW_DATA 1 diff --git a/ext/openssl/tests/bug28382.phpt b/ext/openssl/tests/bug28382.phpt index e2842023135c..7627da2931b5 100644 --- a/ext/openssl/tests/bug28382.phpt +++ b/ext/openssl/tests/bug28382.phpt @@ -6,6 +6,12 @@ openssl = 0x40000000) { + $extensions = &$ext['extensions']; + $extensions['crlDistributionPoints'] = preg_replace('/^Full Name:\R\s*/', '', trim($extensions['crlDistributionPoints'])); +} + var_dump($ext['extensions']); /* * The reason for %A at the end of crlDistributionPoints and authorityKeyIdentifier is that @@ -30,7 +36,7 @@ array(11) { string(59) "B0:A7:FF:F9:41:15:DE:23:39:BD:DD:31:0F:97:A0:B2:A2:74:E0:FC" ["authorityKeyIdentifier"]=> string(%d) "DirName:/C=RO/ST=Romania/L=Craiova/O=Sergiu/OU=Sergiu SRL/CN=Sergiu CA/emailAddress=n_sergiu@hotmail.com -serial:00%A" +serial:%A" ["keyUsage"]=> string(71) "Digital Signature, Non Repudiation, Key Encipherment, Data Encipherment" ["nsBaseUrl"]=> diff --git a/ext/openssl/tests/ecc_custom_params.phpt b/ext/openssl/tests/ecc_custom_params.phpt index 0c63af1450ba..e3854eefa4d8 100644 --- a/ext/openssl/tests/ecc_custom_params.phpt +++ b/ext/openssl/tests/ecc_custom_params.phpt @@ -5,6 +5,30 @@ openssl --SKIPIF-- = 0x40000000) { + $d = hex2bin('8D0AC65AAEA0D6B96254C65817D4A143A9E7A03876F1A37D'); + $p = hex2bin('BDB6F4FE3E8B1D9E0DA8C0D46F4C318CEFE4AFE3B6B8551F'); + $a = hex2bin('BB8E5E8FBC115E139FE6A814FE48AAA6F0ADA1AA5DF91985'); + $b = hex2bin('1854BEBDC31B21B7AEFC80AB0ECD10D5B1B3308E6DBF11C1'); + $g_x = hex2bin('4AD5F7048DE709AD51236DE65E4D4B482C836DC6E4106640'); + $g_y = hex2bin('02BB3A02D4AAADACAE24817A4CA3A1B014B5270432DB27D2'); + $order = hex2bin('BDB6F4FE3E8B1D9E0DA8C0D40FC962195DFAE76F56564677'); + + if (@openssl_pkey_new(array( + 'ec' => array( + 'p' => $p, + 'a' => $a, + 'b' => $b, + 'order' => $order, + 'g_x' => $g_x, + 'g_y' => $g_y, + 'd' => $d, + ), + )) === false) { + die("skip EC custom params unsupported with OpenSSL 4"); + } +} ?> --FILE-- = 3.2'); ?> --FILE-- --EXPECTF-- bool(true) diff --git a/ext/openssl/xp_ssl.c b/ext/openssl/xp_ssl.c index 125b22423806..d98275aea9b3 100644 --- a/ext/openssl/xp_ssl.c +++ b/ext/openssl/xp_ssl.c @@ -93,7 +93,7 @@ #define HAVE_SEC_LEVEL 1 #endif -#ifndef OPENSSL_NO_SSL3 +#if OPENSSL_VERSION_NUMBER < 0x40000000L && !defined(OPENSSL_NO_SSL3) #define HAVE_SSL3 1 #define PHP_OPENSSL_MIN_PROTO_VERSION STREAM_CRYPTO_METHOD_SSLv3 #else @@ -544,26 +544,55 @@ static bool php_openssl_matches_san_list( static bool php_openssl_matches_common_name(X509 *peer, const char *subject_name) /* {{{ */ { - char buf[1024]; - X509_NAME *cert_name; + unsigned char *cert_name = NULL; +#if PHP_OPENSSL_API_VERSION < 0x30000 + X509_NAME *name; +#else + const X509_NAME *name; +#endif + const X509_NAME_ENTRY *name_entry; + const ASN1_STRING *name_asn1; bool is_match = 0; + int name_index; int cert_name_len; - cert_name = X509_get_subject_name(peer); - cert_name_len = X509_NAME_get_text_by_NID(cert_name, NID_commonName, buf, sizeof(buf)); + name = X509_get_subject_name(peer); + name_index = X509_NAME_get_index_by_NID(name, NID_commonName, -1); + if (name_index == -1) { + php_error_docref(NULL, E_WARNING, "Unable to locate peer certificate CN"); + return false; + } - if (cert_name_len == -1) { + name_entry = X509_NAME_get_entry(name, name_index); + if (name_entry == NULL) { php_error_docref(NULL, E_WARNING, "Unable to locate peer certificate CN"); - } else if ((size_t)cert_name_len != strlen(buf)) { - php_error_docref(NULL, E_WARNING, "Peer certificate CN=`%.*s' is malformed", cert_name_len, buf); - } else if (php_openssl_matches_wildcard_name(subject_name, buf)) { + return false; + } + + name_asn1 = X509_NAME_ENTRY_get_data(name_entry); + cert_name_len = ASN1_STRING_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_error_docref(NULL, E_WARNING, "Unable to locate peer certificate CN"); + return false; + } + + if ((size_t)cert_name_len != strlen((const char *)cert_name)) { + php_error_docref(NULL, E_WARNING, "Peer certificate CN=`%.*s' is malformed", cert_name_len, (const char *)cert_name); + OPENSSL_free(cert_name); + return false; + } else if (php_openssl_matches_wildcard_name(subject_name, (const char *)cert_name)) { is_match = 1; } else { php_error_docref(NULL, E_WARNING, "Peer certificate CN=`%.*s' did not match expected CN=`%s'", - cert_name_len, buf, subject_name); + cert_name_len, (const char *)cert_name, subject_name); + OPENSSL_free(cert_name); + return false; } + OPENSSL_free(cert_name); + return is_match; } /* }}} */ From 6a4443126f60bdc3cc41e1b7e169536ac204b070 Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Tue, 27 Oct 2026 01:37:55 +0800 Subject: [PATCH 2/2] 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 (cherry picked from commit 3678f391d8f64738cdb37d3e2a95aeba615413c4) --- ext/openssl/openssl.c | 29 ++++++++++++++++------------- ext/openssl/php_openssl.h | 5 +++++ ext/openssl/xp_ssl.c | 8 ++++---- 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c index cc3badd8bf47..48b68fd80108 100644 --- a/ext/openssl/openssl.c +++ b/ext/openssl/openssl.c @@ -703,7 +703,7 @@ static void php_openssl_add_assoc_name_entry(zval * val, char * key, const X509_ } 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) { @@ -739,7 +739,7 @@ static void php_openssl_add_assoc_name_entry(zval * val, char * key, const X509_ static 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)); } /* }}} */ @@ -756,6 +756,7 @@ static 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; @@ -764,24 +765,25 @@ static 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)); @@ -1632,7 +1634,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; @@ -1824,7 +1826,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: @@ -2041,7 +2044,7 @@ static int openssl_x509v3_subjectAltName(BIO *bio, PHP_OPENSSL_X509_EXTENSION *e 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))); @@ -2063,19 +2066,19 @@ static int openssl_x509v3_subjectAltName(BIO *bio, PHP_OPENSSL_X509_EXTENSION *e 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.h b/ext/openssl/php_openssl.h index 0b1dd35795cd..4683672f9d0c 100644 --- a/ext/openssl/php_openssl.h +++ b/ext/openssl/php_openssl.h @@ -46,6 +46,11 @@ extern zend_module_entry openssl_module_entry; #endif #endif +#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/xp_ssl.c b/ext/openssl/xp_ssl.c index d98275aea9b3..9ae5105fea10 100644 --- a/ext/openssl/xp_ssl.c +++ b/ext/openssl/xp_ssl.c @@ -480,7 +480,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; @@ -500,7 +500,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], @@ -514,7 +514,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) { @@ -570,7 +570,7 @@ static bool php_openssl_matches_common_name(X509 *peer, const char *subject_name } 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_error_docref(NULL, E_WARNING, "Unable to locate peer certificate CN");