Commit 6aca6e30bb4 for php.net
commit 6aca6e30bb4ecf062d3d863de254b8b1d0914a8f
Author: Shivam Mathur <shivam_jpr@hotmail.com>
Date: Fri Jul 3 19:15:40 2026 +0530
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
diff --git a/.github/actions/brew/action.yml b/.github/actions/brew/action.yml
index f90a66f239d..db43010f396 100644
--- a/.github/actions/brew/action.yml
+++ b/.github/actions/brew/action.yml
@@ -33,4 +33,6 @@ runs:
t1lib \
libxml2 \
libjpeg \
- libxslt
+ libxslt \
+ openssl@4
+ brew link --force --overwrite openssl@4
diff --git a/.github/actions/configure-macos/action.yml b/.github/actions/configure-macos/action.yml
index f0af959d210..b9739a79053 100644
--- a/.github/actions/configure-macos/action.yml
+++ b/.github/actions/configure-macos/action.yml
@@ -11,7 +11,7 @@ runs:
set -x
BREW_OPT="$(brew --prefix)"/opt
export PATH="$BREW_OPT/bison/bin:$PATH"
- export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:$BREW_OPT/openssl/lib/pkgconfig"
+ export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:$BREW_OPT/openssl@4/lib/pkgconfig"
export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:$BREW_OPT/curl/lib/pkgconfig"
export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:$BREW_OPT/libffi/lib/pkgconfig"
export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:$BREW_OPT/libxml2/lib/pkgconfig"
diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c
index 1e63eb1381f..e11ad5c3428 100644
--- a/ext/openssl/openssl.c
+++ b/ext/openssl/openssl.c
@@ -821,7 +821,7 @@ PHP_MINIT_FUNCTION(openssl)
ssl_stream_data_index = SSL_get_ex_new_index(0, "PHP stream index", NULL, NULL, NULL);
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);
@@ -895,7 +895,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");
@@ -1395,8 +1395,8 @@ PHP_FUNCTION(openssl_x509_parse)
zval subitem;
zval critext;
int critcount = 0;
- 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;
@@ -2383,7 +2383,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)
diff --git a/ext/openssl/openssl_backend_common.c b/ext/openssl/openssl_backend_common.c
index 8115a7c91b3..7261a617af3 100644
--- a/ext/openssl/openssl_backend_common.c
+++ b/ext/openssl/openssl_backend_common.c
@@ -33,16 +33,16 @@
/* true global; readonly after module startup */
static char default_ssl_conf_filename[MAXPATHLEN];
-void php_openssl_add_assoc_name_entry(zval * val, char * key, X509_NAME * name, int shortname)
+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);
@@ -106,7 +106,7 @@ void php_openssl_add_assoc_name_entry(zval * val, char * key, X509_NAME * name,
}
}
-void php_openssl_add_assoc_asn1_string(zval * val, char * key, ASN1_STRING * str)
+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));
}
@@ -622,11 +622,11 @@ zend_string* php_openssl_x509_fingerprint(X509 *peer, const char *method, bool r
/* Special handling of subjectAltName, see CVE-2013-4073
* Christian Heimes
*/
-int openssl_x509v3_subjectAltName(BIO *bio, X509_EXTENSION *extension)
+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;
@@ -982,7 +982,12 @@ zend_result php_openssl_csr_make(struct php_x509_request * req, X509_REQ * csr,
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) {
@@ -991,10 +996,12 @@ zend_result php_openssl_csr_make(struct php_x509_request * req, X509_REQ * csr,
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 {
@@ -1045,13 +1052,23 @@ zend_result php_openssl_csr_make(struct php_x509_request * req, X509_REQ * csr,
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;
diff --git a/ext/openssl/openssl_backend_v3.c b/ext/openssl/openssl_backend_v3.c
index 339ff53f7b6..375c0104fac 100644
--- a/ext/openssl/openssl_backend_v3.c
+++ b/ext/openssl/openssl_backend_v3.c
@@ -402,7 +402,7 @@ 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 b88a6c59e4e..588cca0e93d 100644
--- a/ext/openssl/php_openssl.h
+++ b/ext/openssl/php_openssl.h
@@ -23,6 +23,7 @@ extern zend_module_entry openssl_module_entry;
#include "php_version.h"
#define PHP_OPENSSL_VERSION PHP_VERSION
+#include <openssl/opensslconf.h>
#include <openssl/opensslv.h>
/* OpenSSL version check */
#if OPENSSL_VERSION_NUMBER < 0x30000000L
diff --git a/ext/openssl/php_openssl_backend.h b/ext/openssl/php_openssl_backend.h
index bd12a5fe312..fcbb8d42542 100644
--- a/ext/openssl/php_openssl_backend.h
+++ b/ext/openssl/php_openssl_backend.h
@@ -35,6 +35,12 @@
#include <openssl/pkcs12.h>
#include <openssl/cms.h>
+#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
+
/* number conversion flags checks */
#define PHP_OPENSSL_CHECK_NUMBER_CONVERSION(_cond, _name, _arg_num) \
do { \
@@ -166,8 +172,8 @@ struct php_x509_request {
const EVP_CIPHER * priv_key_encrypt_cipher;
};
-void php_openssl_add_assoc_name_entry(zval * val, char * key, X509_NAME * name, int shortname);
-void php_openssl_add_assoc_asn1_string(zval * val, char * key, ASN1_STRING * str);
+void php_openssl_add_assoc_name_entry(zval * val, char * key, const X509_NAME * name, int shortname);
+void php_openssl_add_assoc_asn1_string(zval * val, char * key, const ASN1_STRING * str);
time_t php_openssl_asn1_time_to_time_t(ASN1_UTCTIME * timestr);
int php_openssl_config_check_syntax(const char * section_label, const char * config_filename, const char * section, CONF *config);
char *php_openssl_conf_get_string(CONF *conf, const char *group, const char *name);
@@ -265,7 +271,7 @@ X509 *php_openssl_x509_from_zval(
zend_string* php_openssl_x509_fingerprint(
X509 *peer, const char *method, bool raw, struct _php_stream *stream);
-int openssl_x509v3_subjectAltName(BIO *bio, X509_EXTENSION *extension);
+int openssl_x509v3_subjectAltName(BIO *bio, PHP_OPENSSL_X509_EXTENSION *extension);
STACK_OF(X509) *php_openssl_load_all_certs_from_file(
char *cert_file, size_t cert_file_len, uint32_t arg_num);
diff --git a/ext/openssl/tests/bug28382.phpt b/ext/openssl/tests/bug28382.phpt
index e2842023135..7627da2931b 100644
--- a/ext/openssl/tests/bug28382.phpt
+++ b/ext/openssl/tests/bug28382.phpt
@@ -6,6 +6,12 @@
<?php
$cert = file_get_contents(__DIR__ . "/bug28382cert.txt");
$ext = openssl_x509_parse($cert);
+
+if (OPENSSL_VERSION_NUMBER >= 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 @@
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 0c63af1450b..e3854eefa4d 100644
--- a/ext/openssl/tests/ecc_custom_params.phpt
+++ b/ext/openssl/tests/ecc_custom_params.phpt
@@ -5,6 +5,30 @@
--SKIPIF--
<?php
if (!defined("OPENSSL_KEYTYPE_EC")) die("skip EC disabled");
+
+if (OPENSSL_VERSION_NUMBER >= 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--
<?php
diff --git a/ext/openssl/tests/openssl_x509_parse_basic_openssl32.phpt b/ext/openssl/tests/openssl_x509_parse_basic_openssl32.phpt
index a0b125fb4ea..84415d133f5 100644
--- a/ext/openssl/tests/openssl_x509_parse_basic_openssl32.phpt
+++ b/ext/openssl/tests/openssl_x509_parse_basic_openssl32.phpt
@@ -8,12 +8,28 @@
?>
--FILE--
<?php
+function normalize_openssl4_x509_parse_output(array $cert): array {
+ if (OPENSSL_VERSION_NUMBER < 0x40000000 || !isset($cert['serialNumberHex'], $cert['extensions']['authorityKeyIdentifier'])) {
+ return $cert;
+ }
+
+ $serial = strtoupper($cert['serialNumberHex']);
+ if (strlen($serial) % 2) {
+ $serial = '0' . $serial;
+ }
+ $serial = implode(':', str_split($serial, 2));
+ $cert['extensions']['authorityKeyIdentifier'] = preg_replace('/^serial:\d+$/m', 'serial:' . $serial, $cert['extensions']['authorityKeyIdentifier']);
+
+ return $cert;
+}
+
$cert = "file://" . __DIR__ . "/cert.crt";
$parsedCert = openssl_x509_parse($cert);
-var_dump($parsedCert === openssl_x509_parse(openssl_x509_read($cert)));
+$parsedCert = normalize_openssl4_x509_parse_output($parsedCert);
+var_dump($parsedCert === normalize_openssl4_x509_parse_output(openssl_x509_parse(openssl_x509_read($cert))));
var_dump($parsedCert);
-var_dump(openssl_x509_parse($cert, false));
+var_dump(normalize_openssl4_x509_parse_output(openssl_x509_parse($cert, false)));
?>
--EXPECTF--
bool(true)
diff --git a/ext/openssl/xp_ssl.c b/ext/openssl/xp_ssl.c
index 93a9971b4a7..d4158634e5b 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
@@ -274,7 +274,7 @@ static int php_openssl_handle_ssl_error(php_stream *stream, int nr_bytes, bool i
errno = EAGAIN;
retry = is_init ? true : sslsock->s.is_blocked;
if (!retry) {
- sslsock->last_status = err == SSL_ERROR_WANT_READ ?
+ sslsock->last_status = err == SSL_ERROR_WANT_READ ?
STREAM_CRYPTO_STATUS_WANT_READ : STREAM_CRYPTO_STATUS_WANT_WRITE;
}
break;
@@ -566,26 +566,50 @@ static bool php_openssl_matches_san_list(X509 *peer, const char *subject_name) /
static bool php_openssl_matches_common_name(php_stream *stream, const 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 = false;
+ 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_stream_warn(stream, NetworkRecvFailed, "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_stream_warn(stream, NetworkRecvFailed, "Unable to locate peer certificate CN");
- } else if ((size_t)cert_name_len != strlen(buf)) {
- php_stream_warn(stream, AuthFailed, "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_stream_warn(stream, NetworkRecvFailed, "Unable to locate peer certificate CN");
+ return false;
+ }
+
+ if ((size_t)cert_name_len != strlen((const char *)cert_name)) {
+ php_stream_warn(stream, AuthFailed, "Peer certificate CN=`%.*s' is malformed", cert_name_len, (const char *)cert_name);
+ } else if (php_openssl_matches_wildcard_name(subject_name, (const char *)cert_name)) {
is_match = true;
} else {
php_stream_warn(stream, AuthFailed,
"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 is_match;
}
/* }}} */