summaryrefslogtreecommitdiff
path: root/ext/openssl
diff options
context:
space:
mode:
Diffstat (limited to 'ext/openssl')
-rw-r--r--ext/openssl/config.w321
-rwxr-xr-xext/openssl/openssl.c736
-rw-r--r--ext/openssl/php_openssl.h20
-rw-r--r--ext/openssl/tests/ServerClientTestCase.inc109
-rw-r--r--ext/openssl/tests/bug46127.phpt86
-rw-r--r--ext/openssl/tests/bug48182.phpt104
-rw-r--r--ext/openssl/tests/bug54992.phpt61
-rw-r--r--ext/openssl/tests/bug65538.pharbin0 -> 9402 bytes
-rw-r--r--ext/openssl/tests/bug65538_001.phpt52
-rw-r--r--ext/openssl/tests/bug65538_002.phpt17
-rw-r--r--ext/openssl/tests/bug65538_003.phpt53
-rw-r--r--ext/openssl/tests/bug65729.pem28
-rw-r--r--ext/openssl/tests/bug65729.phpt60
-rw-r--r--ext/openssl/tests/bug66501.phpt2
-rw-r--r--ext/openssl/tests/capture_peer_cert_001.phpt39
-rw-r--r--ext/openssl/tests/openssl_peer_fingerprint.phpt54
-rw-r--r--ext/openssl/tests/openssl_spki_export.phpt62
-rw-r--r--ext/openssl/tests/openssl_spki_export_challenge.phpt105
-rw-r--r--ext/openssl/tests/openssl_spki_new.phpt77
-rw-r--r--ext/openssl/tests/openssl_spki_verify.phpt105
-rw-r--r--ext/openssl/tests/openssl_x509_fingerprint.phpt47
-rw-r--r--ext/openssl/tests/peer_verification.phpt61
-rw-r--r--ext/openssl/tests/san-ca.pem15
-rw-r--r--ext/openssl/tests/san-cert.pem31
-rw-r--r--ext/openssl/tests/san_peer_matching.phpt50
-rw-r--r--ext/openssl/tests/session_meta_capture.phpt65
-rw-r--r--ext/openssl/tests/sni_001.phpt38
-rw-r--r--ext/openssl/tests/sni_server.phpt60
-rw-r--r--ext/openssl/tests/sni_server_ca.pem63
-rw-r--r--ext/openssl/tests/sni_server_domain1.pem82
-rw-r--r--ext/openssl/tests/sni_server_domain2.pem82
-rw-r--r--ext/openssl/tests/sni_server_domain3.pem82
-rw-r--r--ext/openssl/tests/stream_crypto_flags_001.phpt50
-rw-r--r--ext/openssl/tests/stream_crypto_flags_002.phpt57
-rw-r--r--ext/openssl/tests/stream_crypto_flags_003.phpt60
-rw-r--r--ext/openssl/tests/stream_crypto_flags_004.phpt60
-rw-r--r--ext/openssl/tests/stream_server_reneg_limit.phpt85
-rw-r--r--ext/openssl/tests/stream_verify_peer_name_001.phpt39
-rw-r--r--ext/openssl/tests/stream_verify_peer_name_002.phpt40
-rw-r--r--ext/openssl/tests/stream_verify_peer_name_003.phpt44
-rw-r--r--ext/openssl/tests/streams_crypto_method.pem33
-rw-r--r--ext/openssl/tests/streams_crypto_method.phpt52
-rw-r--r--ext/openssl/tests/tlsv1.0_wrapper.phpt47
-rw-r--r--ext/openssl/tests/tlsv1.1_wrapper.phpt48
-rw-r--r--ext/openssl/tests/tlsv1.2_wrapper.phpt48
-rw-r--r--ext/openssl/xp_ssl.c1817
46 files changed, 4228 insertions, 699 deletions
diff --git a/ext/openssl/config.w32 b/ext/openssl/config.w32
index 49edb068a8..066d7bc853 100644
--- a/ext/openssl/config.w32
+++ b/ext/openssl/config.w32
@@ -6,6 +6,7 @@ ARG_WITH("openssl", "OpenSSL support", "no");
if (PHP_OPENSSL != "no") {
if (CHECK_LIB("ssleay32.lib", "openssl", PHP_OPENSSL) &&
CHECK_LIB("libeay32.lib", "openssl", PHP_OPENSSL) &&
+ CHECK_LIB("crypt32.lib", "openssl") &&
CHECK_HEADER_ADD_INCLUDE("openssl/ssl.h", "CFLAGS_OPENSSL")) {
EXTENSION("openssl", "openssl.c xp_ssl.c");
diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c
index 90b1cc6c9c..0d9b9564d8 100755
--- a/ext/openssl/openssl.c
+++ b/ext/openssl/openssl.c
@@ -27,6 +27,7 @@
#endif
#include "php.h"
+#include "php_ini.h"
#include "php_openssl.h"
/* PHP Includes */
@@ -133,6 +134,12 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_x509_export, 0, 0, 2)
ZEND_ARG_INFO(0, notext)
ZEND_END_ARG_INFO()
+ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_x509_fingerprint, 0, 0, 1)
+ ZEND_ARG_INFO(0, x509)
+ ZEND_ARG_INFO(0, method)
+ ZEND_ARG_INFO(0, raw_output)
+ZEND_END_ARG_INFO()
+
ZEND_BEGIN_ARG_INFO(arginfo_openssl_x509_check_private_key, 0)
ZEND_ARG_INFO(0, cert)
ZEND_ARG_INFO(0, key)
@@ -398,11 +405,40 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_random_pseudo_bytes, 0, 0, 1)
ZEND_ARG_INFO(0, length)
ZEND_ARG_INFO(1, result_is_strong)
ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_openssl_spki_new, 0, 0, 2)
+ ZEND_ARG_INFO(0, privkey)
+ ZEND_ARG_INFO(0, challenge)
+ ZEND_ARG_INFO(0, algo)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO(arginfo_openssl_spki_verify, 0)
+ ZEND_ARG_INFO(0, spki)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO(arginfo_openssl_spki_export, 0)
+ ZEND_ARG_INFO(0, spki)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO(arginfo_openssl_spki_export_challenge, 0)
+ ZEND_ARG_INFO(0, spki)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO(arginfo_openssl_get_cert_locations, 0)
+ZEND_END_ARG_INFO()
/* }}} */
/* {{{ openssl_functions[]
*/
const zend_function_entry openssl_functions[] = {
+ PHP_FE(openssl_get_cert_locations, arginfo_openssl_get_cert_locations)
+
+/* spki functions */
+ PHP_FE(openssl_spki_new, arginfo_openssl_spki_new)
+ PHP_FE(openssl_spki_verify, arginfo_openssl_spki_verify)
+ PHP_FE(openssl_spki_export, arginfo_openssl_spki_export)
+ PHP_FE(openssl_spki_export_challenge, arginfo_openssl_spki_export_challenge)
+
/* public/private key functions */
PHP_FE(openssl_pkey_free, arginfo_openssl_pkey_free)
PHP_FE(openssl_pkey_new, arginfo_openssl_pkey_new)
@@ -423,6 +459,7 @@ const zend_function_entry openssl_functions[] = {
PHP_FE(openssl_x509_checkpurpose, arginfo_openssl_x509_checkpurpose)
PHP_FE(openssl_x509_check_private_key, arginfo_openssl_x509_check_private_key)
PHP_FE(openssl_x509_export, arginfo_openssl_x509_export)
+ PHP_FE(openssl_x509_fingerprint, arginfo_openssl_x509_fingerprint)
PHP_FE(openssl_x509_export_to_file, arginfo_openssl_x509_export_to_file)
/* PKCS12 funcs */
@@ -538,6 +575,16 @@ inline static int php_openssl_open_base_dir_chk(char *filename TSRMLS_DC)
}
/* }}} */
+php_stream* php_openssl_get_stream_from_ssl_handle(const SSL *ssl)
+{
+ return (php_stream*)SSL_get_ex_data(ssl, ssl_stream_data_index);
+}
+
+int php_openssl_get_ssl_stream_data_index()
+{
+ return ssl_stream_data_index;
+}
+
/* openssl -> PHP "bridging" */
/* true global; readonly after module startup */
static char default_ssl_conf_filename[MAXPATHLEN];
@@ -571,7 +618,7 @@ struct php_x509_request { /* {{{ */
static X509 * php_openssl_x509_from_zval(zval ** val, int makeresource, long * resourceval TSRMLS_DC);
static EVP_PKEY * php_openssl_evp_from_zval(zval ** val, int public_key, char * passphrase, int makeresource, long * resourceval TSRMLS_DC);
static int php_openssl_is_private_key(EVP_PKEY* pkey TSRMLS_DC);
-static X509_STORE * setup_verify(zval * calist TSRMLS_DC);
+static X509_STORE * setup_verify(zval * calist TSRMLS_DC);
static STACK_OF(X509) * load_all_certs_from_file(char *certfile);
static X509_REQ * php_openssl_csr_from_zval(zval ** val, int makeresource, long * resourceval TSRMLS_DC);
static EVP_PKEY * php_openssl_generate_private_key(struct php_x509_request * req TSRMLS_DC);
@@ -666,7 +713,7 @@ static time_t asn1_time_to_time_t(ASN1_UTCTIME * timestr TSRMLS_DC) /* {{{ */
return (time_t)-1;
}
- if (ASN1_STRING_length(timestr) != strlen(ASN1_STRING_data(timestr))) {
+ if (ASN1_STRING_length(timestr) != strlen((const char*)ASN1_STRING_data(timestr))) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "illegal length in timestamp");
return (time_t)-1;
}
@@ -807,6 +854,7 @@ static int add_oid_section(struct php_x509_request * req TSRMLS_DC) /* {{{ */
static const EVP_CIPHER * php_openssl_get_evp_cipher_from_algo(long algo);
+int openssl_spki_cleanup(const char *src, char *dest);
static int php_openssl_parse_config(struct php_x509_request * req, zval * optional_args TSRMLS_DC) /* {{{ */
{
@@ -1055,6 +1103,13 @@ static const EVP_CIPHER * php_openssl_get_evp_cipher_from_algo(long algo) { /* {
}
/* }}} */
+/* {{{ INI Settings */
+PHP_INI_BEGIN()
+ PHP_INI_ENTRY("openssl.cafile", NULL, PHP_INI_PERDIR, NULL)
+ PHP_INI_ENTRY("openssl.capath", NULL, PHP_INI_PERDIR, NULL)
+PHP_INI_END()
+/* }}} */
+
/* {{{ PHP_MINIT_FUNCTION
*/
PHP_MINIT_FUNCTION(openssl)
@@ -1122,6 +1177,9 @@ PHP_MINIT_FUNCTION(openssl)
REGISTER_LONG_CONSTANT("OPENSSL_NO_PADDING", RSA_NO_PADDING, CONST_CS|CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("OPENSSL_PKCS1_OAEP_PADDING", RSA_PKCS1_OAEP_PADDING, CONST_CS|CONST_PERSISTENT);
+ /* Informational stream wrapper constants */
+ REGISTER_STRING_CONSTANT("OPENSSL_DEFAULT_STREAM_CIPHERS", OPENSSL_DEFAULT_STREAM_CIPHERS, CONST_CS|CONST_PERSISTENT);
+
/* Ciphers */
#ifndef OPENSSL_NO_RC2
REGISTER_LONG_CONSTANT("OPENSSL_CIPHER_RC2_40", PHP_OPENSSL_CIPHER_RC2_40, CONST_CS|CONST_PERSISTENT);
@@ -1177,13 +1235,20 @@ PHP_MINIT_FUNCTION(openssl)
php_stream_xport_register("sslv2", php_openssl_ssl_socket_factory TSRMLS_CC);
#endif
php_stream_xport_register("tls", php_openssl_ssl_socket_factory TSRMLS_CC);
+ php_stream_xport_register("tlsv1.0", php_openssl_ssl_socket_factory TSRMLS_CC);
+#if OPENSSL_VERSION_NUMBER >= 0x10001001L
+ php_stream_xport_register("tlsv1.1", php_openssl_ssl_socket_factory TSRMLS_CC);
+ php_stream_xport_register("tlsv1.2", php_openssl_ssl_socket_factory TSRMLS_CC);
+#endif
/* override the default tcp socket provider */
php_stream_xport_register("tcp", php_openssl_ssl_socket_factory TSRMLS_CC);
php_register_url_stream_wrapper("https", &php_stream_http_wrapper TSRMLS_CC);
php_register_url_stream_wrapper("ftps", &php_stream_ftp_wrapper TSRMLS_CC);
-
+
+ REGISTER_INI_ENTRIES();
+
return SUCCESS;
}
/* }}} */
@@ -1197,6 +1262,7 @@ PHP_MINFO_FUNCTION(openssl)
php_info_print_table_row(2, "OpenSSL Library Version", SSLeay_version(SSLEAY_VERSION));
php_info_print_table_row(2, "OpenSSL Header Version", OPENSSL_VERSION_TEXT);
php_info_print_table_end();
+ DISPLAY_INI_ENTRIES();
}
/* }}} */
@@ -1215,16 +1281,43 @@ PHP_MSHUTDOWN_FUNCTION(openssl)
#endif
php_stream_xport_unregister("sslv3" TSRMLS_CC);
php_stream_xport_unregister("tls" TSRMLS_CC);
+ php_stream_xport_unregister("tlsv1.0" TSRMLS_CC);
+#if OPENSSL_VERSION_NUMBER >= 0x10001001L
+ php_stream_xport_unregister("tlsv1.1" TSRMLS_CC);
+ php_stream_xport_unregister("tlsv1.2" TSRMLS_CC);
+#endif
/* reinstate the default tcp handler */
php_stream_xport_register("tcp", php_stream_generic_socket_factory TSRMLS_CC);
+ UNREGISTER_INI_ENTRIES();
+
return SUCCESS;
}
/* }}} */
/* {{{ x509 cert functions */
+/* {{{ proto array openssl_get_cert_locations(void)
+ Retrieve an array mapping available certificate locations */
+PHP_FUNCTION(openssl_get_cert_locations)
+{
+ array_init(return_value);
+
+ add_assoc_string(return_value, "default_cert_file", (char *) X509_get_default_cert_file(), 1);
+ add_assoc_string(return_value, "default_cert_file_env", (char *) X509_get_default_cert_file_env(), 1);
+ add_assoc_string(return_value, "default_cert_dir", (char *) X509_get_default_cert_dir(), 1);
+ add_assoc_string(return_value, "default_cert_dir_env", (char *) X509_get_default_cert_dir_env(), 1);
+ add_assoc_string(return_value, "default_private_dir", (char *) X509_get_default_private_dir(), 1);
+ add_assoc_string(return_value, "default_default_cert_area", (char *) X509_get_default_cert_area(), 1);
+ add_assoc_string(return_value, "ini_cafile",
+ zend_ini_string("openssl.cafile", sizeof("openssl.cafile"), 0), 1);
+ add_assoc_string(return_value, "ini_capath",
+ zend_ini_string("openssl.capath", sizeof("openssl.capath"), 0), 1);
+}
+/* }}} */
+
+
/* {{{ php_openssl_x509_from_zval
Given a zval, coerce it into an X509 object.
The zval can be:
@@ -1351,6 +1444,279 @@ PHP_FUNCTION(openssl_x509_export_to_file)
}
/* }}} */
+/* {{{ proto string openssl_spki_new(mixed zpkey, string challenge [, mixed method])
+ Creates new private key (or uses existing) and creates a new spki cert
+ outputting results to var */
+PHP_FUNCTION(openssl_spki_new)
+{
+ int challenge_len;
+ char * challenge = NULL, * spkstr = NULL, * s = NULL;
+ long keyresource = -1;
+ const char *spkac = "SPKAC=";
+ long algo = OPENSSL_ALGO_MD5;
+
+ zval *method = NULL;
+ zval * zpkey = NULL;
+ EVP_PKEY * pkey = NULL;
+ NETSCAPE_SPKI *spki=NULL;
+ const EVP_MD *mdtype;
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs|z", &zpkey, &challenge, &challenge_len, &method) == FAILURE) {
+ return;
+ }
+ RETVAL_FALSE;
+
+ pkey = php_openssl_evp_from_zval(&zpkey, 0, challenge, 1, &keyresource TSRMLS_CC);
+
+ if (pkey == NULL) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to use supplied private key");
+ goto cleanup;
+ }
+
+ if (method != NULL) {
+ if (Z_TYPE_P(method) == IS_LONG) {
+ algo = Z_LVAL_P(method);
+ } else {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Algorithm must be of supported type");
+ goto cleanup;
+ }
+ }
+ mdtype = php_openssl_get_evp_md_from_algo(algo);
+
+ if (!mdtype) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unknown signature algorithm");
+ goto cleanup;
+ }
+
+ if ((spki = NETSCAPE_SPKI_new()) == NULL) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to create new SPKAC");
+ goto cleanup;
+ }
+
+ if (challenge) {
+ ASN1_STRING_set(spki->spkac->challenge, challenge, challenge_len);
+ }
+
+ if (!NETSCAPE_SPKI_set_pubkey(spki, pkey)) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to embed public key");
+ goto cleanup;
+ }
+
+ if (!NETSCAPE_SPKI_sign(spki, pkey, mdtype)) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to sign with specified algorithm");
+ goto cleanup;
+ }
+
+ spkstr = NETSCAPE_SPKI_b64_encode(spki);
+ if (!spkstr){
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to encode SPKAC");
+ goto cleanup;
+ }
+
+ s = emalloc(strlen(spkac) + strlen(spkstr) + 1);
+ sprintf(s, "%s%s", spkac, spkstr);
+
+ RETVAL_STRINGL(s, strlen(s), 0);
+ goto cleanup;
+
+cleanup:
+
+ if (keyresource == -1 && spki != NULL) {
+ NETSCAPE_SPKI_free(spki);
+ }
+ if (keyresource == -1 && pkey != NULL) {
+ EVP_PKEY_free(pkey);
+ }
+ if (keyresource == -1 && spkstr != NULL) {
+ efree(spkstr);
+ }
+
+ if (strlen(s) <= 0) {
+ RETVAL_FALSE;
+ }
+
+ if (keyresource == -1 && s != NULL) {
+ efree(s);
+ }
+}
+/* }}} */
+
+/* {{{ proto bool openssl_spki_verify(string spki)
+ Verifies spki returns boolean */
+PHP_FUNCTION(openssl_spki_verify)
+{
+ int spkstr_len, i = 0;
+ char *spkstr = NULL, * spkstr_cleaned = NULL;
+
+ EVP_PKEY *pkey = NULL;
+ NETSCAPE_SPKI *spki = NULL;
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &spkstr, &spkstr_len) == FAILURE) {
+ return;
+ }
+ RETVAL_FALSE;
+
+ if (spkstr == NULL) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to use supplied SPKAC");
+ goto cleanup;
+ }
+
+ spkstr_cleaned = emalloc(spkstr_len + 1);
+ openssl_spki_cleanup(spkstr, spkstr_cleaned);
+
+ if (strlen(spkstr_cleaned)<=0) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid SPKAC");
+ goto cleanup;
+ }
+
+ spki = NETSCAPE_SPKI_b64_decode(spkstr_cleaned, strlen(spkstr_cleaned));
+ if (spki == NULL) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to decode supplied SPKAC");
+ goto cleanup;
+ }
+
+ pkey = X509_PUBKEY_get(spki->spkac->pubkey);
+ if (pkey == NULL) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to acquire signed public key");
+ goto cleanup;
+ }
+
+ i = NETSCAPE_SPKI_verify(spki, pkey);
+ goto cleanup;
+
+cleanup:
+ if (spki != NULL) {
+ NETSCAPE_SPKI_free(spki);
+ }
+ if (pkey != NULL) {
+ EVP_PKEY_free(pkey);
+ }
+ if (spkstr_cleaned != NULL) {
+ efree(spkstr_cleaned);
+ }
+
+ if (i > 0) {
+ RETVAL_TRUE;
+ }
+}
+/* }}} */
+
+/* {{{ proto string openssl_spki_export(string spki)
+ Exports public key from existing spki to var */
+PHP_FUNCTION(openssl_spki_export)
+{
+ int spkstr_len;
+ char *spkstr = NULL, * spkstr_cleaned = NULL, * s = NULL;
+
+ EVP_PKEY *pkey = NULL;
+ NETSCAPE_SPKI *spki = NULL;
+ BIO *out = BIO_new(BIO_s_mem());
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &spkstr, &spkstr_len) == FAILURE) {
+ return;
+ }
+ RETVAL_FALSE;
+
+ if (spkstr == NULL) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to use supplied SPKAC");
+ goto cleanup;
+ }
+
+ spkstr_cleaned = emalloc(spkstr_len + 1);
+ openssl_spki_cleanup(spkstr, spkstr_cleaned);
+
+ spki = NETSCAPE_SPKI_b64_decode(spkstr_cleaned, strlen(spkstr_cleaned));
+ if (spki == NULL) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to decode supplied SPKAC");
+ goto cleanup;
+ }
+
+ pkey = X509_PUBKEY_get(spki->spkac->pubkey);
+ if (pkey == NULL) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to acquire signed public key");
+ goto cleanup;
+ }
+
+ out = BIO_new_fp(stdout, BIO_NOCLOSE);
+ PEM_write_bio_PUBKEY(out, pkey);
+ goto cleanup;
+
+cleanup:
+
+ if (spki != NULL) {
+ NETSCAPE_SPKI_free(spki);
+ }
+ if (out != NULL) {
+ BIO_free_all(out);
+ }
+ if (pkey != NULL) {
+ EVP_PKEY_free(pkey);
+ }
+ if (spkstr_cleaned != NULL) {
+ efree(spkstr_cleaned);
+ }
+ if (s != NULL) {
+ efree(s);
+ }
+}
+/* }}} */
+
+/* {{{ proto string openssl_spki_export_challenge(string spki)
+ Exports spkac challenge from existing spki to var */
+PHP_FUNCTION(openssl_spki_export_challenge)
+{
+ int spkstr_len;
+ char *spkstr = NULL, * spkstr_cleaned = NULL;
+
+ NETSCAPE_SPKI *spki = NULL;
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &spkstr, &spkstr_len) == FAILURE) {
+ return;
+ }
+ RETVAL_FALSE;
+
+ if (spkstr == NULL) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to use supplied SPKAC");
+ goto cleanup;
+ }
+
+ spkstr_cleaned = emalloc(spkstr_len + 1);
+ openssl_spki_cleanup(spkstr, spkstr_cleaned);
+
+ spki = NETSCAPE_SPKI_b64_decode(spkstr_cleaned, strlen(spkstr_cleaned));
+ if (spki == NULL) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to decode SPKAC");
+ goto cleanup;
+ }
+
+ RETVAL_STRING((char *) ASN1_STRING_data(spki->spkac->challenge), 1);
+ goto cleanup;
+
+cleanup:
+ if (spkstr_cleaned != NULL) {
+ efree(spkstr_cleaned);
+ }
+}
+/* }}} */
+
+/* {{{ strip line endings from spkac */
+int openssl_spki_cleanup(const char *src, char *dest)
+{
+ int removed=0;
+
+ while (*src) {
+ if (*src!='\n'&&*src!='\r') {
+ *dest++=*src;
+ } else {
+ ++removed;
+ }
+ ++src;
+ }
+ *dest=0;
+ return removed;
+}
+/* }}} */
+
/* {{{ proto bool openssl_x509_export(mixed x509, string &out [, bool notext = true])
Exports a CERT to file or a var */
PHP_FUNCTION(openssl_x509_export)
@@ -1393,6 +1759,121 @@ PHP_FUNCTION(openssl_x509_export)
}
/* }}} */
+static int php_openssl_x509_fingerprint(X509 *peer, const char *method, zend_bool raw, char **out, int *out_len TSRMLS_DC)
+{
+ unsigned char md[EVP_MAX_MD_SIZE];
+ const EVP_MD *mdtype;
+ unsigned int n;
+
+ if (!(mdtype = EVP_get_digestbyname(method))) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unknown signature algorithm");
+ return FAILURE;
+ } else if (!X509_digest(peer, mdtype, md, &n)) {
+ php_error_docref(NULL TSRMLS_CC, E_ERROR, "Could not generate signature");
+ return FAILURE;
+ }
+
+ if (raw) {
+ *out_len = n;
+ *out = estrndup((char *) md, n);
+ } else {
+ *out_len = n * 2;
+ *out = emalloc(*out_len + 1);
+
+ make_digest_ex(*out, md, n);
+ }
+
+ return SUCCESS;
+}
+
+static int php_x509_fingerprint_cmp(X509 *peer, const char *method, const char *expected TSRMLS_DC)
+{
+ char *fingerprint;
+ int fingerprint_len;
+ int result = -1;
+
+ if (php_openssl_x509_fingerprint(peer, method, 0, &fingerprint, &fingerprint_len TSRMLS_CC) == SUCCESS) {
+ result = strcmp(expected, fingerprint);
+ efree(fingerprint);
+ }
+
+ return result;
+}
+
+zend_bool php_x509_fingerprint_match(X509 *peer, zval *val TSRMLS_DC)
+{
+ if (Z_TYPE_P(val) == IS_STRING) {
+ const char *method = NULL;
+
+ switch (Z_STRLEN_P(val)) {
+ case 32:
+ method = "md5";
+ break;
+
+ case 40:
+ method = "sha1";
+ break;
+ }
+
+ return method && php_x509_fingerprint_cmp(peer, method, Z_STRVAL_P(val) TSRMLS_CC) == 0;
+ } else if (Z_TYPE_P(val) == IS_ARRAY) {
+ HashPosition pos;
+ zval **current;
+ char *key;
+ uint key_len;
+ ulong key_index;
+
+ for (zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(val), &pos);
+ zend_hash_get_current_data_ex(Z_ARRVAL_P(val), (void **)&current, &pos) == SUCCESS;
+ zend_hash_move_forward_ex(Z_ARRVAL_P(val), &pos)
+ ) {
+ int key_type = zend_hash_get_current_key_ex(Z_ARRVAL_P(val), &key, &key_len, &key_index, 0, &pos);
+
+ if (key_type == HASH_KEY_IS_STRING
+ && Z_TYPE_PP(current) == IS_STRING
+ && php_x509_fingerprint_cmp(peer, key, Z_STRVAL_PP(current) TSRMLS_CC) != 0
+ ) {
+ return 0;
+ }
+ }
+ return 1;
+ }
+ return 0;
+}
+
+PHP_FUNCTION(openssl_x509_fingerprint)
+{
+ X509 *cert;
+ zval **zcert;
+ long certresource;
+ zend_bool raw_output = 0;
+ char *method = "sha1";
+ int method_len;
+
+ char *fingerprint;
+ int fingerprint_len;
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Z|sb", &zcert, &method, &method_len, &raw_output) == FAILURE) {
+ return;
+ }
+
+ cert = php_openssl_x509_from_zval(zcert, 0, &certresource TSRMLS_CC);
+ if (cert == NULL) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "cannot get cert from parameter 1");
+ RETURN_FALSE;
+ }
+
+ if (php_openssl_x509_fingerprint(cert, method, raw_output, &fingerprint, &fingerprint_len TSRMLS_CC) == SUCCESS) {
+ RETVAL_STRINGL(fingerprint, fingerprint_len, 0);
+ } else {
+ RETVAL_FALSE;
+ }
+
+ if (certresource == -1 && cert) {
+ X509_free(cert);
+ }
+}
+
/* {{{ proto bool openssl_x509_check_private_key(mixed cert, mixed key)
Checks if a private key corresponds to a CERT */
PHP_FUNCTION(openssl_x509_check_private_key)
@@ -4522,255 +5003,7 @@ PHP_FUNCTION(openssl_open)
}
/* }}} */
-/* SSL verification functions */
-
-#define GET_VER_OPT(name) (stream->context && SUCCESS == php_stream_context_get_option(stream->context, "ssl", name, &val))
-#define GET_VER_OPT_STRING(name, str) if (GET_VER_OPT(name)) { convert_to_string_ex(val); str = Z_STRVAL_PP(val); }
-
-static int verify_callback(int preverify_ok, X509_STORE_CTX *ctx) /* {{{ */
-{
- php_stream *stream;
- SSL *ssl;
- X509 *err_cert;
- int err, depth, ret;
- zval **val;
-
- ret = preverify_ok;
-
- /* determine the status for the current cert */
- err_cert = X509_STORE_CTX_get_current_cert(ctx);
- err = X509_STORE_CTX_get_error(ctx);
- depth = X509_STORE_CTX_get_error_depth(ctx);
-
- /* conjure the stream & context to use */
- ssl = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
- stream = (php_stream*)SSL_get_ex_data(ssl, ssl_stream_data_index);
-
- /* if allow_self_signed is set, make sure that verification succeeds */
- if (err == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT && GET_VER_OPT("allow_self_signed") && zval_is_true(*val)) {
- ret = 1;
- }
-
- /* check the depth */
- if (GET_VER_OPT("verify_depth")) {
- convert_to_long_ex(val);
-
- if (depth > Z_LVAL_PP(val)) {
- ret = 0;
- X509_STORE_CTX_set_error(ctx, X509_V_ERR_CERT_CHAIN_TOO_LONG);
- }
- }
-
- return ret;
-
-}
-/* }}} */
-
-int php_openssl_apply_verification_policy(SSL *ssl, X509 *peer, php_stream *stream TSRMLS_DC) /* {{{ */
-{
- zval **val = NULL;
- char *cnmatch = NULL;
- X509_NAME *name;
- char buf[1024];
- int err;
-
- /* verification is turned off */
- if (!(GET_VER_OPT("verify_peer") && zval_is_true(*val))) {
- return SUCCESS;
- }
-
- if (peer == NULL) {
- php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not get peer certificate");
- return FAILURE;
- }
-
- err = SSL_get_verify_result(ssl);
- switch (err) {
- case X509_V_OK:
- /* fine */
- break;
- case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
- if (GET_VER_OPT("allow_self_signed") && zval_is_true(*val)) {
- /* allowed */
- break;
- }
- /* not allowed, so fall through */
- default:
- php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not verify peer: code:%d %s", err, X509_verify_cert_error_string(err));
- return FAILURE;
- }
-
- /* if the cert passed the usual checks, apply our own local policies now */
-
- name = X509_get_subject_name(peer);
-
- /* Does the common name match ? (used primarily for https://) */
- GET_VER_OPT_STRING("CN_match", cnmatch);
- if (cnmatch) {
- int match = 0;
- int name_len = X509_NAME_get_text_by_NID(name, NID_commonName, buf, sizeof(buf));
-
- if (name_len == -1) {
- php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to locate peer certificate CN");
- return FAILURE;
- } else if (name_len != strlen(buf)) {
- php_error_docref(NULL TSRMLS_CC, E_WARNING, "Peer certificate CN=`%.*s' is malformed", name_len, buf);
- return FAILURE;
- }
-
- match = strcmp(cnmatch, buf) == 0;
- if (!match && strlen(buf) > 3 && buf[0] == '*' && buf[1] == '.') {
- /* Try wildcard */
-
- if (strchr(buf+2, '.')) {
- char *tmp = strstr(cnmatch, buf+1);
-
- match = tmp && strcmp(tmp, buf+2) && tmp == strchr(cnmatch, '.');
- }
- }
-
- if (!match) {
- /* didn't match */
- php_error_docref(NULL TSRMLS_CC, E_WARNING, "Peer certificate CN=`%.*s' did not match expected CN=`%s'", name_len, buf, cnmatch);
- return FAILURE;
- }
- }
-
- return SUCCESS;
-}
-/* }}} */
-
-static int passwd_callback(char *buf, int num, int verify, void *data) /* {{{ */
-{
- php_stream *stream = (php_stream *)data;
- zval **val = NULL;
- char *passphrase = NULL;
- /* TODO: could expand this to make a callback into PHP user-space */
-
- GET_VER_OPT_STRING("passphrase", passphrase);
-
- if (passphrase) {
- if (Z_STRLEN_PP(val) < num - 1) {
- memcpy(buf, Z_STRVAL_PP(val), Z_STRLEN_PP(val)+1);
- return Z_STRLEN_PP(val);
- }
- }
- return 0;
-}
-/* }}} */
-
-SSL *php_SSL_new_from_context(SSL_CTX *ctx, php_stream *stream TSRMLS_DC) /* {{{ */
-{
- zval **val = NULL;
- char *cafile = NULL;
- char *capath = NULL;
- char *certfile = NULL;
- char *cipherlist = NULL;
- int ok = 1;
-
- ERR_clear_error();
-
- /* look at context options in the stream and set appropriate verification flags */
- if (GET_VER_OPT("verify_peer") && zval_is_true(*val)) {
-
- /* turn on verification callback */
- SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, verify_callback);
-
- /* CA stuff */
- GET_VER_OPT_STRING("cafile", cafile);
- GET_VER_OPT_STRING("capath", capath);
-
- if (cafile || capath) {
- if (!SSL_CTX_load_verify_locations(ctx, cafile, capath)) {
- php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to set verify locations `%s' `%s'", cafile, capath);
- return NULL;
- }
- }
-
- if (GET_VER_OPT("verify_depth")) {
- convert_to_long_ex(val);
- SSL_CTX_set_verify_depth(ctx, Z_LVAL_PP(val));
- }
- } else {
- SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL);
- }
- /* callback for the passphrase (for localcert) */
- if (GET_VER_OPT("passphrase")) {
- SSL_CTX_set_default_passwd_cb_userdata(ctx, stream);
- SSL_CTX_set_default_passwd_cb(ctx, passwd_callback);
- }
-
- GET_VER_OPT_STRING("ciphers", cipherlist);
- if (!cipherlist) {
- cipherlist = "DEFAULT";
- }
- if (SSL_CTX_set_cipher_list(ctx, cipherlist) != 1) {
- return NULL;
- }
-
- GET_VER_OPT_STRING("local_cert", certfile);
- if (certfile) {
- char resolved_path_buff[MAXPATHLEN];
- const char * private_key = NULL;
-
- if (VCWD_REALPATH(certfile, resolved_path_buff)) {
- /* a certificate to use for authentication */
- if (SSL_CTX_use_certificate_chain_file(ctx, resolved_path_buff) != 1) {
- php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to set local cert chain file `%s'; Check that your cafile/capath settings include details of your certificate and its issuer", certfile);
- return NULL;
- }
- GET_VER_OPT_STRING("local_pk", private_key);
-
- if (private_key) {
- char resolved_path_buff_pk[MAXPATHLEN];
- if (VCWD_REALPATH(private_key, resolved_path_buff_pk)) {
- if (SSL_CTX_use_PrivateKey_file(ctx, resolved_path_buff_pk, SSL_FILETYPE_PEM) != 1) {
- php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to set private key file `%s'", resolved_path_buff_pk);
- return NULL;
- }
- }
- } else {
- if (SSL_CTX_use_PrivateKey_file(ctx, resolved_path_buff, SSL_FILETYPE_PEM) != 1) {
- php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to set private key file `%s'", resolved_path_buff);
- return NULL;
- }
- }
-
-#if OPENSSL_VERSION_NUMBER < 0x10001001L
- do {
- /* Unnecessary as of OpenSSLv1.0.1 (will segfault if used with >= 10001001 ) */
- X509 *cert = NULL;
- EVP_PKEY *key = NULL;
- SSL *tmpssl = SSL_new(ctx);
- cert = SSL_get_certificate(tmpssl);
-
- if (cert) {
- key = X509_get_pubkey(cert);
- EVP_PKEY_copy_parameters(key, SSL_get_privatekey(tmpssl));
- EVP_PKEY_free(key);
- }
- SSL_free(tmpssl);
- } while (0);
-#endif
- if (!SSL_CTX_check_private_key(ctx)) {
- php_error_docref(NULL TSRMLS_CC, E_WARNING, "Private key does not match certificate!");
- }
- }
- }
- if (ok) {
- SSL *ssl = SSL_new(ctx);
-
- if (ssl) {
- /* map SSL => stream */
- SSL_set_ex_data(ssl, ssl_stream_data_index, stream);
- }
- return ssl;
- }
-
- return NULL;
-}
-/* }}} */
static void openssl_add_method_or_alias(const OBJ_NAME *name, void *arg) /* {{{ */
{
@@ -5186,3 +5419,4 @@ PHP_FUNCTION(openssl_random_pseudo_bytes)
* vim600: sw=4 ts=4 fdm=marker
* vim<600: sw=4 ts=4
*/
+
diff --git a/ext/openssl/php_openssl.h b/ext/openssl/php_openssl.h
index e4cb7e890e..968919eb64 100644
--- a/ext/openssl/php_openssl.h
+++ b/ext/openssl/php_openssl.h
@@ -29,6 +29,18 @@ extern zend_module_entry openssl_module_entry;
#define OPENSSL_RAW_DATA 1
#define OPENSSL_ZERO_PADDING 2
+/* Used for client-initiated handshake renegotiation DoS protection*/
+#define OPENSSL_DEFAULT_RENEG_LIMIT 2
+#define OPENSSL_DEFAULT_RENEG_WINDOW 300
+#define OPENSSL_DEFAULT_STREAM_VERIFY_DEPTH 9
+#define OPENSSL_DEFAULT_STREAM_CIPHERS "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:" \
+ "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:" \
+ "DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:" \
+ "ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:" \
+ "ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:" \
+ "DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:" \
+ "AES256-GCM-SHA384:AES128:AES256:HIGH:!SSLv2:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!RC4:!ADH"
+
php_stream_transport_factory_func php_openssl_ssl_socket_factory;
PHP_MINIT_FUNCTION(openssl);
@@ -66,6 +78,7 @@ PHP_FUNCTION(openssl_x509_free);
PHP_FUNCTION(openssl_x509_parse);
PHP_FUNCTION(openssl_x509_checkpurpose);
PHP_FUNCTION(openssl_x509_export);
+PHP_FUNCTION(openssl_x509_fingerprint);
PHP_FUNCTION(openssl_x509_export_to_file);
PHP_FUNCTION(openssl_x509_check_private_key);
@@ -79,6 +92,13 @@ PHP_FUNCTION(openssl_csr_export_to_file);
PHP_FUNCTION(openssl_csr_sign);
PHP_FUNCTION(openssl_csr_get_subject);
PHP_FUNCTION(openssl_csr_get_public_key);
+
+PHP_FUNCTION(openssl_spki_new);
+PHP_FUNCTION(openssl_spki_verify);
+PHP_FUNCTION(openssl_spki_export);
+PHP_FUNCTION(openssl_spki_export_challenge);
+
+PHP_FUNCTION(openssl_get_cert_locations);
#else
#define phpext_openssl_ptr NULL
diff --git a/ext/openssl/tests/ServerClientTestCase.inc b/ext/openssl/tests/ServerClientTestCase.inc
new file mode 100644
index 0000000000..03e0c2de87
--- /dev/null
+++ b/ext/openssl/tests/ServerClientTestCase.inc
@@ -0,0 +1,109 @@
+<?php
+
+const WORKER_ARGV_VALUE = 'RUN_WORKER';
+
+function phpt_notify()
+{
+ ServerClientTestCase::getInstance()->notify();
+}
+
+function phpt_wait()
+{
+ ServerClientTestCase::getInstance()->wait();
+}
+
+/**
+ * This is a singleton to let the wait/notify functions work
+ * I know it's horrible, but it's a means to an end
+ */
+class ServerClientTestCase
+{
+ private $isWorker = false;
+
+ private $workerHandle;
+
+ private $workerStdIn;
+
+ private $workerStdOut;
+
+ private static $instance;
+
+ public static function getInstance($isWorker = false)
+ {
+ if (!isset(self::$instance)) {
+ self::$instance = new self($isWorker);
+ }
+
+ return self::$instance;
+ }
+
+ public function __construct($isWorker = false)
+ {
+ if (!isset(self::$instance)) {
+ self::$instance = $this;
+ }
+
+ $this->isWorker = $isWorker;
+ }
+
+ private function spawnWorkerProcess($code)
+ {
+ $cmd = sprintf('%s "%s" %s', PHP_BINARY, __FILE__, WORKER_ARGV_VALUE);
+
+ $this->workerHandle = proc_open($cmd, [['pipe', 'r'], ['pipe', 'w'], STDERR], $pipes);
+ $this->workerStdIn = $pipes[0];
+ $this->workerStdOut = $pipes[1];
+
+ fwrite($this->workerStdIn, $code . "\n---\n");
+ }
+
+ private function cleanupWorkerProcess()
+ {
+ fclose($this->workerStdIn);
+ fclose($this->workerStdOut);
+ proc_close($this->workerHandle);
+ }
+
+ private function stripPhpTagsFromCode($code)
+ {
+ return preg_replace('/^\s*<\?(?:php)?|\?>\s*$/i', '', $code);
+ }
+
+ public function runWorker()
+ {
+ $code = '';
+
+ while (1) {
+ $line = fgets(STDIN);
+
+ if (trim($line) === "---") {
+ break;
+ }
+
+ $code .= $line;
+ }
+
+ eval($code);
+ }
+
+ public function run($proc1Code, $proc2Code)
+ {
+ $this->spawnWorkerProcess($this->stripPhpTagsFromCode($proc2Code));
+ eval($this->stripPhpTagsFromCode($proc1Code));
+ $this->cleanupWorkerProcess();
+ }
+
+ public function wait()
+ {
+ fgets($this->isWorker ? STDIN : $this->workerStdOut);
+ }
+
+ public function notify()
+ {
+ fwrite($this->isWorker ? STDOUT : $this->workerStdIn, "\n");
+ }
+}
+
+if (isset($argv[1]) && $argv[1] === WORKER_ARGV_VALUE) {
+ ServerClientTestCase::getInstance(true)->runWorker();
+}
diff --git a/ext/openssl/tests/bug46127.phpt b/ext/openssl/tests/bug46127.phpt
index a3bfd3a012..5410d2f87e 100644
--- a/ext/openssl/tests/bug46127.phpt
+++ b/ext/openssl/tests/bug46127.phpt
@@ -2,57 +2,41 @@
#46127, openssl_sign/verify: accept different algos
--SKIPIF--
<?php
-if (!extension_loaded("openssl")) die("skip, openssl required");
-if (!extension_loaded("pcntl")) die("skip, pcntl required");
-if (OPENSSL_VERSION_NUMBER < 0x009070af) die("skip");
-?>
+if (!extension_loaded("openssl")) die("skip openssl not loaded");
+if (!function_exists("proc_open")) die("skip no proc_open");
+if (OPENSSL_VERSION_NUMBER < 0x009070af) die("skip openssl version too low");
--FILE--
<?php
-
-function ssl_server($port) {
- $pem = dirname(__FILE__) . '/bug46127.pem';
- $ssl = array(
- 'verify_peer' => false,
- 'allow_self_signed' => true,
- 'local_cert' => $pem,
- // 'passphrase' => '',
- );
- $context = stream_context_create(array('ssl' => $ssl));
- $sock = stream_socket_server('ssl://127.0.0.1:'.$port, $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $context);
- if (!$sock) return false;
-
- $link = stream_socket_accept($sock);
- if (!$link) return false; // bad link?
-
- fputs($link, "Sending bug 46127\n");
-
- // close stuff
- fclose($link);
- fclose($sock);
-
- exit;
-}
-
-echo "Running bug46127\n";
-
-$port = rand(15000, 32000);
-
-$pid = pcntl_fork();
-if ($pid == 0) { // child
- ssl_server($port);
- exit;
-}
-
-// client or failed
-sleep(1);
-$sock = fsockopen('ssl://127.0.0.1', $port, $errno, $errstr);
-if (!$sock) exit;
-
-echo fgets($sock);
-
-pcntl_waitpid($pid, $status);
-
-?>
---EXPECTF--
-Running bug46127
+$serverCode = <<<'CODE'
+ $serverUri = "ssl://127.0.0.1:64321";
+ $serverFlags = STREAM_SERVER_BIND | STREAM_SERVER_LISTEN;
+ $serverCtx = stream_context_create(['ssl' => [
+ 'local_cert' => __DIR__ . '/bug46127.pem',
+ ]]);
+
+ $sock = stream_socket_server($serverUri, $errno, $errstr, $serverFlags, $serverCtx);
+ phpt_notify();
+
+ $link = stream_socket_accept($sock);
+ fwrite($link, "Sending bug 46127\n");
+CODE;
+
+$clientCode = <<<'CODE'
+ $serverUri = "ssl://127.0.0.1:64321";
+ $clientFlags = STREAM_CLIENT_CONNECT;
+
+ $clientCtx = stream_context_create(['ssl' => [
+ 'verify_peer' => false,
+ 'verify_peer_name' => false
+ ]]);
+
+ phpt_wait();
+ $sock = stream_socket_client($serverUri, $errno, $errstr, 2, $clientFlags, $clientCtx);
+
+ echo fgets($sock);
+CODE;
+
+include 'ServerClientTestCase.inc';
+ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
+--EXPECT--
Sending bug 46127
diff --git a/ext/openssl/tests/bug48182.phpt b/ext/openssl/tests/bug48182.phpt
index 146c4c9226..5211c23d20 100644
--- a/ext/openssl/tests/bug48182.phpt
+++ b/ext/openssl/tests/bug48182.phpt
@@ -1,91 +1,49 @@
--TEST--
-#48182,ssl handshake fails during asynchronous socket connection
+Bug #48182: ssl handshake fails during asynchronous socket connection
--SKIPIF--
<?php
-if (!extension_loaded("openssl")) die("skip, openssl required");
-if (!extension_loaded("pcntl")) die("skip, pcntl required");
-if (OPENSSL_VERSION_NUMBER < 0x009070af) die("skip");
-?>
+if (!extension_loaded("openssl")) die("skip openssl not loaded");
+if (!function_exists("proc_open")) die("skip no proc_open");
+if (OPENSSL_VERSION_NUMBER < 0x009070af) die("skip openssl version too low");
--FILE--
<?php
+$serverCode = <<<'CODE'
+ $serverUri = "ssl://127.0.0.1:64321";
+ $serverFlags = STREAM_SERVER_BIND | STREAM_SERVER_LISTEN;
+ $serverCtx = stream_context_create(['ssl' => [
+ 'local_cert' => __DIR__ . '/bug54992.pem'
+ ]]);
-function ssl_server($port) {
- $host = 'ssl://127.0.0.1'.':'.$port;
- $flags = STREAM_SERVER_BIND | STREAM_SERVER_LISTEN;
- $data = "Sending bug48182\n";
+ $server = stream_socket_server($serverUri, $errno, $errstr, $serverFlags, $serverCtx);
+ phpt_notify();
- $pem = dirname(__FILE__) . '/bug46127.pem';
- $ssl_params = array( 'verify_peer' => false, 'allow_self_signed' => true, 'local_cert' => $pem);
- $ssl = array('ssl' => $ssl_params);
+ $client = @stream_socket_accept($server, 1);
- $context = stream_context_create($ssl);
- $sock = stream_socket_server($host, $errno, $errstr, $flags, $context);
- if (!$sock) return false;
+ $data = "Sending bug48182\n" . fread($client, 8192);
+ fwrite($client, $data);
+CODE;
- $link = stream_socket_accept($sock);
- if (!$link) return false; // bad link?
+$clientCode = <<<'CODE'
+ $serverUri = "ssl://127.0.0.1:64321";
+ $clientFlags = STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT;
+ $clientCtx = stream_context_create(['ssl' => [
+ 'cafile' => __DIR__ . '/bug54992-ca.pem',
+ 'peer_name' => 'bug54992.local'
+ ]]);
- $r = array($link);
- $w = array();
- $e = array();
- if (stream_select($r, $w, $e, 1, 0) != 0)
- $data .= fread($link, 8192);
+ phpt_wait();
+ $client = stream_socket_client($serverUri, $errno, $errstr, 10, $clientFlags, $clientCtx);
- $r = array();
- $w = array($link);
- if (stream_select($r, $w, $e, 1, 0) != 0)
- $wrote = fwrite($link, $data, strlen($data));
+ $data = "Sending data over to SSL server in async mode with contents like Hello World\n";
- // close stuff
- fclose($link);
- fclose($sock);
-
- exit;
-}
-
-function ssl_async_client($port) {
- $host = 'ssl://127.0.0.1'.':'.$port;
- $flags = STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT;
- $data = "Sending data over to SSL server in async mode with contents like Hello World\n";
-
- $socket = stream_socket_client($host, $errno, $errstr, 10, $flags);
- stream_set_blocking($socket, 0);
-
- while ($socket && $data) {
- $wrote = fwrite($socket, $data, strlen($data));
- $data = substr($data, $wrote);
- }
-
- $r = array($socket);
- $w = array();
- $e = array();
- if (stream_select($r, $w, $e, 1, 0) != 0)
- {
- $data .= fread($socket, 1024);
- }
-
- echo "$data";
-
- fclose($socket);
-}
+ fwrite($client, $data);
+ echo fread($client, 1024);
+CODE;
echo "Running bug48182\n";
-$port = rand(15000, 32000);
-
-$pid = pcntl_fork();
-if ($pid == 0) { // child
- ssl_server($port);
- exit;
-}
-
-// client or failed
-sleep(1);
-ssl_async_client($port);
-
-pcntl_waitpid($pid, $status);
-
-?>
+include 'ServerClientTestCase.inc';
+ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
--EXPECTF--
Running bug48182
Sending bug48182
diff --git a/ext/openssl/tests/bug54992.phpt b/ext/openssl/tests/bug54992.phpt
index 768b07378e..878cb4a872 100644
--- a/ext/openssl/tests/bug54992.phpt
+++ b/ext/openssl/tests/bug54992.phpt
@@ -2,37 +2,40 @@
Bug #54992: Stream not closed and error not returned when SSL CN_match fails
--SKIPIF--
<?php
-if (!extension_loaded("openssl")) die("skip");
-if (!function_exists('pcntl_fork')) die("skip no fork");
+if (!extension_loaded("openssl")) die("skip openssl not loaded");
+if (!function_exists("proc_open")) die("skip no proc_open");
--FILE--
<?php
-$context = stream_context_create();
-
-stream_context_set_option($context, 'ssl', 'local_cert', __DIR__ . "/bug54992.pem");
-stream_context_set_option($context, 'ssl', 'allow_self_signed', true);
-$server = stream_socket_server('ssl://127.0.0.1:64321', $errno, $errstr,
- STREAM_SERVER_BIND|STREAM_SERVER_LISTEN, $context);
-
-
-$pid = pcntl_fork();
-if ($pid == -1) {
- die('could not fork');
-} else if ($pid) {
- $contextC = stream_context_create(
- array(
- 'ssl' => array(
- 'verify_peer' => true,
- 'cafile' => __DIR__ . '/bug54992-ca.pem',
- 'CN_match' => 'buga_buga',
- )
- )
- );
- var_dump(stream_socket_client("ssl://127.0.0.1:64321", $errno, $errstr, 1,
- STREAM_CLIENT_CONNECT, $contextC));
-} else {
- @pcntl_wait($status);
- @stream_socket_accept($server, 1);
-}
+$serverCode = <<<'CODE'
+ $serverUri = "ssl://127.0.0.1:64321";
+ $serverFlags = STREAM_SERVER_BIND | STREAM_SERVER_LISTEN;
+ $serverCtx = stream_context_create(['ssl' => [
+ 'local_cert' => __DIR__ . '/bug54992.pem',
+ ]]);
+
+ $server = stream_socket_server($serverUri, $errno, $errstr, $serverFlags, $serverCtx);
+ phpt_notify();
+
+ @stream_socket_accept($server, 1);
+CODE;
+
+$clientCode = <<<'CODE'
+ $serverUri = "ssl://127.0.0.1:64321";
+ $clientFlags = STREAM_CLIENT_CONNECT;
+ $clientCtx = stream_context_create(['ssl' => [
+ 'verify_peer' => true,
+ 'cafile' => __DIR__ . '/bug54992-ca.pem',
+ 'peer_name' => 'buga_buga',
+ ]]);
+
+ phpt_wait();
+ $client = stream_socket_client($serverUri, $errno, $errstr, 2, $clientFlags, $clientCtx);
+
+ var_dump($client);
+CODE;
+
+include 'ServerClientTestCase.inc';
+ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
--EXPECTF--
Warning: stream_socket_client(): Peer certificate CN=`bug54992.local' did not match expected CN=`buga_buga' in %s on line %d
diff --git a/ext/openssl/tests/bug65538.phar b/ext/openssl/tests/bug65538.phar
new file mode 100644
index 0000000000..ae0bd29c6e
--- /dev/null
+++ b/ext/openssl/tests/bug65538.phar
Binary files differ
diff --git a/ext/openssl/tests/bug65538_001.phpt b/ext/openssl/tests/bug65538_001.phpt
new file mode 100644
index 0000000000..e666859d0d
--- /dev/null
+++ b/ext/openssl/tests/bug65538_001.phpt
@@ -0,0 +1,52 @@
+--TEST--
+Bug #65538: SSL context "cafile" supports stream wrappers
+--SKIPIF--
+<?php
+if (!extension_loaded("openssl")) die("skip openssl not loaded");
+if (!function_exists("proc_open")) die("skip no proc_open");
+--FILE--
+<?php
+$serverCode = <<<'CODE'
+ $serverUri = "ssl://127.0.0.1:64321";
+ $serverFlags = STREAM_SERVER_BIND | STREAM_SERVER_LISTEN;
+ $serverCtx = stream_context_create(['ssl' => [
+ 'local_cert' => __DIR__ . '/bug54992.pem',
+ ]]);
+
+ $server = stream_socket_server($serverUri, $errno, $errstr, $serverFlags, $serverCtx);
+ phpt_notify();
+
+ $client = @stream_socket_accept($server);
+ if ($client) {
+ $in = '';
+ while (!preg_match('/\r?\n\r?\n/', $in)) {
+ $in .= fread($client, 2048);
+ }
+ $response = "HTTP/1.0 200 OK\r\n"
+ . "Content-Type: text/plain\r\n"
+ . "Content-Length: 12\r\n"
+ . "Connection: close\r\n"
+ . "\r\n"
+ . "Hello World!";
+ fwrite($client, $response);
+ fclose($client);
+ }
+CODE;
+
+$clientCode = <<<'CODE'
+ $serverUri = "https://127.0.0.1:64321/";
+ $clientCtx = stream_context_create(['ssl' => [
+ 'cafile' => 'file://' . __DIR__ . '/bug54992-ca.pem',
+ 'peer_name' => 'bug54992.local',
+ ]]);
+
+ phpt_wait();
+ $html = file_get_contents($serverUri, false, $clientCtx);
+
+ var_dump($html);
+CODE;
+
+include 'ServerClientTestCase.inc';
+ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
+--EXPECT--
+string(12) "Hello World!"
diff --git a/ext/openssl/tests/bug65538_002.phpt b/ext/openssl/tests/bug65538_002.phpt
new file mode 100644
index 0000000000..dfc6f94ff7
--- /dev/null
+++ b/ext/openssl/tests/bug65538_002.phpt
@@ -0,0 +1,17 @@
+--TEST--
+Bug #65538: SSL context "cafile" disallows URL stream wrappers
+--SKIPIF--
+<?php
+if (!extension_loaded('openssl')) die('skip, openssl required');
+--FILE--
+<?php
+$clientCtx = stream_context_create(['ssl' => [
+ 'cafile' => 'http://curl.haxx.se/ca/cacert.pem'
+]]);
+file_get_contents('https://github.com', false, $clientCtx);
+--EXPECTF--
+Warning: remote cafile streams are disabled for security purposes in %s on line %d
+
+Warning: file_get_contents(): Failed to enable crypto in %s on line %d
+
+Warning: file_get_contents(%s): failed to open stream: operation failed in %s on line %d
diff --git a/ext/openssl/tests/bug65538_003.phpt b/ext/openssl/tests/bug65538_003.phpt
new file mode 100644
index 0000000000..da99779143
--- /dev/null
+++ b/ext/openssl/tests/bug65538_003.phpt
@@ -0,0 +1,53 @@
+--TEST--
+Bug #65538: SSL context "cafile" supports phar wrapper
+--SKIPIF--
+<?php
+if (!extension_loaded("openssl")) die("skip openssl not loaded");
+if (!extension_loaded("phar")) die("skip phar not loaded");
+if (!function_exists("proc_open")) die("skip no proc_open");
+--FILE--
+<?php
+$serverCode = <<<'CODE'
+ $serverUri = "ssl://127.0.0.1:64321";
+ $serverFlags = STREAM_SERVER_BIND | STREAM_SERVER_LISTEN;
+ $serverCtx = stream_context_create(['ssl' => [
+ 'local_cert' => __DIR__ . '/bug54992.pem',
+ ]]);
+
+ $server = stream_socket_server($serverUri, $errno, $errstr, $serverFlags, $serverCtx);
+ phpt_notify();
+
+ $client = @stream_socket_accept($server);
+ if ($client) {
+ $in = '';
+ while (!preg_match('/\r?\n\r?\n/', $in)) {
+ $in .= fread($client, 2048);
+ }
+ $response = "HTTP/1.0 200 OK\r\n"
+ . "Content-Type: text/plain\r\n"
+ . "Content-Length: 12\r\n"
+ . "Connection: close\r\n"
+ . "\r\n"
+ . "Hello World!";
+ fwrite($client, $response);
+ fclose($client);
+ }
+CODE;
+
+$clientCode = <<<'CODE'
+ $serverUri = "https://127.0.0.1:64321/";
+ $clientCtx = stream_context_create(['ssl' => [
+ 'cafile' => 'phar://' . __DIR__ . '/bug65538.phar/bug54992-ca.pem',
+ 'peer_name' => 'bug54992.local',
+ ]]);
+
+ phpt_wait();
+ $html = file_get_contents($serverUri, false, $clientCtx);
+
+ var_dump($html);
+CODE;
+
+include 'ServerClientTestCase.inc';
+ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
+--EXPECTF--
+string(12) "Hello World!"
diff --git a/ext/openssl/tests/bug65729.pem b/ext/openssl/tests/bug65729.pem
new file mode 100644
index 0000000000..dbeed6efd3
--- /dev/null
+++ b/ext/openssl/tests/bug65729.pem
@@ -0,0 +1,28 @@
+-----BEGIN CERTIFICATE-----
+MIICCTCCAXICCQDNMI29sowT7TANBgkqhkiG9w0BAQUFADBJMQswCQYDVQQGEwJT
+RzESMBAGA1UECBMJVGVzdHZpbGxlMREwDwYDVQQKEwhkYXRpYmJhdzETMBEGA1UE
+AxQKKi50ZXN0LmNvbTAeFw0xMzA5MjEwNzUyMjRaFw0xNDA5MjEwNzUyMjRaMEkx
+CzAJBgNVBAYTAlNHMRIwEAYDVQQIEwlUZXN0dmlsbGUxETAPBgNVBAoTCGRhdGli
+YmF3MRMwEQYDVQQDFAoqLnRlc3QuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB
+iQKBgQCdzVnic8K5W4SVbwVuqezcTjeqVLoQ91vVNZB0Jnsuz6q3DoK03oAd1jTe
+Vd0k+MQDbXpHoc37lA4+8z/g5Bs0UXxNx+nkbFTE7Ba2/G24caI9/cOXZPG3UViD
+rtqXKL6h5/umqRG9Dt5liF2MVP9XFAesVC7B8+Ca+PbPlQoYzwIDAQABMA0GCSqG
+SIb3DQEBBQUAA4GBAAS07u/Ke+EhEHidz6CG3Qcr+zg483JKRgZFyGz+YUKyyKKy
+fmLs7JieGJxYQjOmIpj/6X9Gnb2HjIPDnI6A+MV1emXDTnnmsgf2/lZGcthhpZn2
+rMbj9bI0iH6HwOVGtp4ZJA5fB7nj3J+gWNTCQzDDOxwX36d2LL9ua+UMnk/g
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+MIICXQIBAAKBgQCdzVnic8K5W4SVbwVuqezcTjeqVLoQ91vVNZB0Jnsuz6q3DoK0
+3oAd1jTeVd0k+MQDbXpHoc37lA4+8z/g5Bs0UXxNx+nkbFTE7Ba2/G24caI9/cOX
+ZPG3UViDrtqXKL6h5/umqRG9Dt5liF2MVP9XFAesVC7B8+Ca+PbPlQoYzwIDAQAB
+AoGAeyzTwKPDl5QMRejHQL57GOwlH1vLcXrjv+VzwHZZKQ0IoKM++5fCQYf29KXp
+XPahaluGW2u9sWa8R/7wGcd0Q4RtquGzsgT3+AQsIc5KfIamyOyDaRVM/ymX3fWg
+gHIU7OOzB+ihOU8sHyRIwfbk01/kmrBXLRj8E31sy3i3PIECQQDQQYE+aN7Acrdt
+yN5CaqvbkiCGjRvASlemiTzPosgOtndyp21w1gakJwKYhYDk1N6A6Qb8REMZqM/U
+wFypldV/AkEAwfq6NFuhpGL6hDA7MvlyY1KiZ0cHetPUX+PgdNqy2DA+1Sv4i7gm
+Wd/uA651K7aPXuUaf9dKtPCmZwI4M6SEsQJBALW89HTqP7niYoDEEnITdPaghxHk
+gptERUln6lGo1L1CLus3gSI/JHyMLo+7scgAnEwTD62GRKhX0Ubwt+ymfTECQAY5
+fHYnppU20+EgBxZIqOIFCc8UmWnYmE0Ha/Fz/x8u1SVUBuK84wYpSGL32yyu7ATY
+hzQo/W229zABAzqtAdECQQCUdB7IBFpPnsfv/EUBFX7X/7zAc9JpACmu9It5ju8C
+KIsMuz/02D+TQoJNjdAngBM+4AJDIaGFgTMIfaDMh5L7
+-----END RSA PRIVATE KEY-----
diff --git a/ext/openssl/tests/bug65729.phpt b/ext/openssl/tests/bug65729.phpt
new file mode 100644
index 0000000000..347dc55e75
--- /dev/null
+++ b/ext/openssl/tests/bug65729.phpt
@@ -0,0 +1,60 @@
+--TEST--
+Bug #65729: CN_match gives false positive when wildcard is used
+--SKIPIF--
+<?php
+if (!extension_loaded("openssl")) die("skip openssl not loaded");
+if (!function_exists("proc_open")) die("skip no proc_open");
+--FILE--
+<?php
+$serverCode = <<<'CODE'
+ $serverUri = "ssl://127.0.0.1:64321";
+ $serverFlags = STREAM_SERVER_BIND | STREAM_SERVER_LISTEN;
+ $serverCtx = stream_context_create(['ssl' => [
+ 'local_cert' => __DIR__ . '/bug65729.pem'
+ ]]);
+
+ $server = stream_socket_server($serverUri, $errno, $errstr, $serverFlags, $serverCtx);
+ phpt_notify();
+
+ $expected_names = ['foo.test.com.sg', 'foo.test.com', 'FOO.TEST.COM', 'foo.bar.test.com'];
+ foreach ($expected_names as $name) {
+ @stream_socket_accept($server, 1);
+ }
+CODE;
+
+$clientCode = <<<'CODE'
+ $serverUri = "ssl://127.0.0.1:64321";
+ $clientFlags = STREAM_CLIENT_CONNECT;
+
+ phpt_wait();
+
+ $expected_names = ['foo.test.com.sg', 'foo.test.com', 'FOO.TEST.COM', 'foo.bar.test.com'];
+ foreach ($expected_names as $expected_name) {
+ $clientCtx = stream_context_create(['ssl' => [
+ 'verify_peer' => true,
+ 'allow_self_signed' => true,
+ 'peer_name' => $expected_name,
+ ]]);
+
+ var_dump(stream_socket_client($serverUri, $errno, $errstr, 2, $clientFlags, $clientCtx));
+ }
+CODE;
+
+include 'ServerClientTestCase.inc';
+ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
+--EXPECTF--
+Warning: stream_socket_client(): Peer certificate CN=`*.test.com' did not match expected CN=`foo.test.com.sg' in %s on line %d
+
+Warning: stream_socket_client(): Failed to enable crypto in %s on line %d
+
+Warning: stream_socket_client(): unable to connect to ssl://127.0.0.1:64321 (Unknown error) in %s on line %d
+bool(false)
+resource(%d) of type (stream)
+resource(%d) of type (stream)
+
+Warning: stream_socket_client(): Peer certificate CN=`*.test.com' did not match expected CN=`foo.bar.test.com' in %s on line %d
+
+Warning: stream_socket_client(): Failed to enable crypto in %s on line %d
+
+Warning: stream_socket_client(): unable to connect to ssl://127.0.0.1:64321 (Unknown error) in %s on line %d
+bool(false)
diff --git a/ext/openssl/tests/bug66501.phpt b/ext/openssl/tests/bug66501.phpt
index cd0da1f289..7ad5e21749 100644
--- a/ext/openssl/tests/bug66501.phpt
+++ b/ext/openssl/tests/bug66501.phpt
@@ -3,7 +3,7 @@ Bug #66501: EC private key support in openssl_sign
--SKIPIF--
<?php
if (!extension_loaded("openssl")) die("skip");
-if (!defined(OPENSSL_KEYTYPE_EC)) die("skip no EC available);
+if (!defined('OPENSSL_KEYTYPE_EC')) die("skip no EC available");
--FILE--
<?php
$pkey = 'ASN1 OID: prime256v1
diff --git a/ext/openssl/tests/capture_peer_cert_001.phpt b/ext/openssl/tests/capture_peer_cert_001.phpt
new file mode 100644
index 0000000000..0396cace43
--- /dev/null
+++ b/ext/openssl/tests/capture_peer_cert_001.phpt
@@ -0,0 +1,39 @@
+--TEST--
+capture_peer_cert context captures on verify failure
+--SKIPIF--
+<?php
+if (!extension_loaded("openssl")) die("skip openssl not loaded");
+if (!function_exists("proc_open")) die("skip no proc_open");
+--FILE--
+<?php
+$serverCode = <<<'CODE'
+ $serverUri = "ssl://127.0.0.1:64321";
+ $serverFlags = STREAM_SERVER_BIND | STREAM_SERVER_LISTEN;
+ $serverCtx = stream_context_create(['ssl' => [
+ 'local_cert' => __DIR__ . '/bug54992.pem'
+ ]]);
+
+ $server = stream_socket_server($serverUri, $errno, $errstr, $serverFlags, $serverCtx);
+ phpt_notify();
+
+ @stream_socket_accept($server, 1);
+CODE;
+
+$clientCode = <<<'CODE'
+ $serverUri = "ssl://127.0.0.1:64321";
+ $clientFlags = STREAM_CLIENT_CONNECT;
+ $clientCtx = stream_context_create(['ssl' => [
+ 'capture_peer_cert' => true,
+ 'cafile' => __DIR__ . '/bug54992-ca.pem'
+ ]]);
+
+ phpt_wait();
+ $client = @stream_socket_client($serverUri, $errno, $errstr, 1, $clientFlags, $clientCtx);
+ $cert = stream_context_get_options($clientCtx)['ssl']['peer_certificate'];
+ var_dump(openssl_x509_parse($cert)['subject']['CN']);
+CODE;
+
+include 'ServerClientTestCase.inc';
+ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
+--EXPECTF--
+string(%d) "bug54992.local"
diff --git a/ext/openssl/tests/openssl_peer_fingerprint.phpt b/ext/openssl/tests/openssl_peer_fingerprint.phpt
new file mode 100644
index 0000000000..7f48cb4546
--- /dev/null
+++ b/ext/openssl/tests/openssl_peer_fingerprint.phpt
@@ -0,0 +1,54 @@
+--TEST--
+Testing peer fingerprint on connection
+--SKIPIF--
+<?php
+if (!extension_loaded("openssl")) die("skip openssl not loaded");
+if (!function_exists("proc_open")) die("skip no proc_open");
+--FILE--
+<?php
+$serverCode = <<<'CODE'
+ $serverUri = "ssl://127.0.0.1:64321";
+ $serverFlags = STREAM_SERVER_BIND | STREAM_SERVER_LISTEN;
+ $serverCtx = stream_context_create(['ssl' => [
+ 'local_cert' => __DIR__ . '/bug54992.pem'
+ ]]);
+
+ $server = stream_socket_server($serverUri, $errno, $errstr, $serverFlags, $serverCtx);
+ phpt_notify();
+
+ @stream_socket_accept($server, 1);
+ @stream_socket_accept($server, 1);
+CODE;
+
+$clientCode = <<<'CODE'
+ $serverUri = "ssl://127.0.0.1:64321";
+ $clientFlags = STREAM_CLIENT_CONNECT;
+ $clientCtx = stream_context_create(['ssl' => [
+ 'verify_peer' => true,
+ 'cafile' => __DIR__ . '/bug54992-ca.pem',
+ 'capture_peer_cert' => true,
+ 'peer_name' => 'bug54992.local',
+ ]]);
+
+ phpt_wait();
+
+ // should be: 81cafc260aa8d82956ebc6212a362ecc
+ stream_context_set_option($clientCtx, 'ssl', 'peer_fingerprint', '81cafc260aa8d82956ebc6212a362ece');
+ var_dump(stream_socket_client($serverUri, $errno, $errstr, 2, $clientFlags, $clientCtx));
+
+ stream_context_set_option($clientCtx, 'ssl', 'peer_fingerprint', [
+ 'sha256' => '78ea579f2c3b439359dec5dac9d445108772927427c4780037e87df3799a0aa0',
+ ]);
+ var_dump(stream_socket_client($serverUri, $errno, $errstr, 2, $clientFlags, $clientCtx));
+CODE;
+
+include 'ServerClientTestCase.inc';
+ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
+--EXPECTF--
+Warning: stream_socket_client(): Peer fingerprint doesn't match in %s on line %d
+
+Warning: stream_socket_client(): Failed to enable crypto in %s on line %d
+
+Warning: stream_socket_client(): unable to connect to ssl://127.0.0.1:64321 (Unknown error) in %s on line %d
+bool(false)
+resource(%d) of type (stream)
diff --git a/ext/openssl/tests/openssl_spki_export.phpt b/ext/openssl/tests/openssl_spki_export.phpt
new file mode 100644
index 0000000000..59332f70a5
--- /dev/null
+++ b/ext/openssl/tests/openssl_spki_export.phpt
@@ -0,0 +1,62 @@
+--TEST--
+Testing openssl_spki_export()
+Creates SPKAC for all available key sizes & signature algorithms and exports public key
+--SKIPIF--
+<?php
+if (!extension_loaded("openssl")) die("skip");
+if (!@openssl_pkey_new()) die("skip cannot create private key");
+?>
+--FILE--
+<?php
+
+/* array of private key sizes to test */
+$ksize = array('1024'=>1024,
+ '2048'=>2048,
+ '4096'=>4096);
+
+/* array of available hashings to test */
+$algo = array('md4'=>OPENSSL_ALGO_MD4,
+ 'md5'=>OPENSSL_ALGO_MD5,
+ 'sha1'=>OPENSSL_ALGO_SHA1,
+ 'sha224'=>OPENSSL_ALGO_SHA224,
+ 'sha256'=>OPENSSL_ALGO_SHA256,
+ 'sha384'=>OPENSSL_ALGO_SHA384,
+ 'sha512'=>OPENSSL_ALGO_SHA512,
+ 'rmd160'=>OPENSSL_ALGO_RMD160);
+
+/* loop over key sizes for test */
+foreach($ksize as $k => $v) {
+
+ /* generate new private key of specified size to use for tests */
+ $pkey = openssl_pkey_new(array('digest_alg' => 'sha512',
+ 'private_key_type' => OPENSSL_KEYTYPE_RSA,
+ 'private_key_bits' => $v));
+ openssl_pkey_export($pkey, $pass);
+
+ /* loop to create and verify results */
+ foreach($algo as $key => $value) {
+ $spkac = openssl_spki_new($pkey, _uuid(), $value);
+ echo openssl_spki_export(preg_replace('/SPKAC=/', '', $spkac));
+ }
+ openssl_free_key($pkey);
+}
+
+/* generate a random challenge */
+function _uuid()
+{
+ return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x', mt_rand(0, 0xffff),
+ mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0x0fff) | 0x4000,
+ mt_rand(0, 0x3fff) | 0x8000, mt_rand(0, 0xffff),
+ mt_rand(0, 0xffff), mt_rand(0, 0xffff));
+}
+
+?>
+--EXPECTREGEX--
+\-\-\-\-\-BEGIN PUBLIC KEY\-\-\-\-\-.*\-\-\-\-\-END PUBLIC KEY\-\-\-\-\-
+\-\-\-\-\-BEGIN PUBLIC KEY\-\-\-\-\-.*\-\-\-\-\-END PUBLIC KEY\-\-\-\-\-
+\-\-\-\-\-BEGIN PUBLIC KEY\-\-\-\-\-.*\-\-\-\-\-END PUBLIC KEY\-\-\-\-\-
+\-\-\-\-\-BEGIN PUBLIC KEY\-\-\-\-\-.*\-\-\-\-\-END PUBLIC KEY\-\-\-\-\-
+\-\-\-\-\-BEGIN PUBLIC KEY\-\-\-\-\-.*\-\-\-\-\-END PUBLIC KEY\-\-\-\-\-
+\-\-\-\-\-BEGIN PUBLIC KEY\-\-\-\-\-.*\-\-\-\-\-END PUBLIC KEY\-\-\-\-\-
+\-\-\-\-\-BEGIN PUBLIC KEY\-\-\-\-\-.*\-\-\-\-\-END PUBLIC KEY\-\-\-\-\-
+\-\-\-\-\-BEGIN PUBLIC KEY\-\-\-\-\-.*\-\-\-\-\-END PUBLIC KEY\-\-\-\-\-
diff --git a/ext/openssl/tests/openssl_spki_export_challenge.phpt b/ext/openssl/tests/openssl_spki_export_challenge.phpt
new file mode 100644
index 0000000000..71ef62edd5
--- /dev/null
+++ b/ext/openssl/tests/openssl_spki_export_challenge.phpt
@@ -0,0 +1,105 @@
+--TEST--
+Testing openssl_spki_export_challenge()
+Creates SPKAC for all available key sizes & signature algorithms and exports challenge
+--INI--
+error_reporting=0
+--SKIPIF--
+<?php
+if (!extension_loaded("openssl")) die("skip");
+if (!@openssl_pkey_new()) die("skip cannot create private key");
+?>
+--FILE--
+<?php
+
+/* array of private key sizes to test */
+$ksize = array('1024'=>1024,
+ '2048'=>2048,
+ '4096'=>4096);
+
+/* array of available hashings to test */
+$algo = array('md4'=>OPENSSL_ALGO_MD4,
+ 'md5'=>OPENSSL_ALGO_MD5,
+ 'sha1'=>OPENSSL_ALGO_SHA1,
+ 'sha224'=>OPENSSL_ALGO_SHA224,
+ 'sha256'=>OPENSSL_ALGO_SHA256,
+ 'sha384'=>OPENSSL_ALGO_SHA384,
+ 'sha512'=>OPENSSL_ALGO_SHA512,
+ 'rmd160'=>OPENSSL_ALGO_RMD160);
+
+/* loop over key sizes for test */
+foreach($ksize as $k => $v) {
+
+ /* generate new private key of specified size to use for tests */
+ $pkey = openssl_pkey_new(array('digest_alg' => 'sha512',
+ 'private_key_type' => OPENSSL_KEYTYPE_RSA,
+ 'private_key_bits' => $v));
+ openssl_pkey_export($pkey, $pass);
+
+ /* loop to create and verify results */
+ foreach($algo as $key => $value) {
+ $spkac = openssl_spki_new($pkey, _uuid(), $value);
+ var_dump(openssl_spki_export_challenge(preg_replace('/SPKAC=/', '', $spkac)));
+ var_dump(openssl_spki_export_challenge($spkac.'Make it fail'));
+ }
+ openssl_free_key($pkey);
+}
+
+/* generate a random challenge */
+function _uuid()
+{
+ return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x', mt_rand(0, 0xffff),
+ mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0x0fff) | 0x4000,
+ mt_rand(0, 0x3fff) | 0x8000, mt_rand(0, 0xffff),
+ mt_rand(0, 0xffff), mt_rand(0, 0xffff));
+}
+
+?>
+--EXPECTREGEX--
+string\(36\) \"[0-9a-f]{8}\-([0-9a-f]{4}\-){3}[0-9a-f]{12}\"
+bool\(false\)
+string\(36\) \"[0-9a-f]{8}\-([0-9a-f]{4}\-){3}[0-9a-f]{12}\"
+bool\(false\)
+string\(36\) \"[0-9a-f]{8}\-([0-9a-f]{4}\-){3}[0-9a-f]{12}\"
+bool\(false\)
+string\(36\) \"[0-9a-f]{8}\-([0-9a-f]{4}\-){3}[0-9a-f]{12}\"
+bool\(false\)
+string\(36\) \"[0-9a-f]{8}\-([0-9a-f]{4}\-){3}[0-9a-f]{12}\"
+bool\(false\)
+string\(36\) \"[0-9a-f]{8}\-([0-9a-f]{4}\-){3}[0-9a-f]{12}\"
+bool\(false\)
+string\(36\) \"[0-9a-f]{8}\-([0-9a-f]{4}\-){3}[0-9a-f]{12}\"
+bool\(false\)
+string\(36\) \"[0-9a-f]{8}\-([0-9a-f]{4}\-){3}[0-9a-f]{12}\"
+bool\(false\)
+string\(36\) \"[0-9a-f]{8}\-([0-9a-f]{4}\-){3}[0-9a-f]{12}\"
+bool\(false\)
+string\(36\) \"[0-9a-f]{8}\-([0-9a-f]{4}\-){3}[0-9a-f]{12}\"
+bool\(false\)
+string\(36\) \"[0-9a-f]{8}\-([0-9a-f]{4}\-){3}[0-9a-f]{12}\"
+bool\(false\)
+string\(36\) \"[0-9a-f]{8}\-([0-9a-f]{4}\-){3}[0-9a-f]{12}\"
+bool\(false\)
+string\(36\) \"[0-9a-f]{8}\-([0-9a-f]{4}\-){3}[0-9a-f]{12}\"
+bool\(false\)
+string\(36\) \"[0-9a-f]{8}\-([0-9a-f]{4}\-){3}[0-9a-f]{12}\"
+bool\(false\)
+string\(36\) \"[0-9a-f]{8}\-([0-9a-f]{4}\-){3}[0-9a-f]{12}\"
+bool\(false\)
+string\(36\) \"[0-9a-f]{8}\-([0-9a-f]{4}\-){3}[0-9a-f]{12}\"
+bool\(false\)
+string\(36\) \"[0-9a-f]{8}\-([0-9a-f]{4}\-){3}[0-9a-f]{12}\"
+bool\(false\)
+string\(36\) \"[0-9a-f]{8}\-([0-9a-f]{4}\-){3}[0-9a-f]{12}\"
+bool\(false\)
+string\(36\) \"[0-9a-f]{8}\-([0-9a-f]{4}\-){3}[0-9a-f]{12}\"
+bool\(false\)
+string\(36\) \"[0-9a-f]{8}\-([0-9a-f]{4}\-){3}[0-9a-f]{12}\"
+bool\(false\)
+string\(36\) \"[0-9a-f]{8}\-([0-9a-f]{4}\-){3}[0-9a-f]{12}\"
+bool\(false\)
+string\(36\) \"[0-9a-f]{8}\-([0-9a-f]{4}\-){3}[0-9a-f]{12}\"
+bool\(false\)
+string\(36\) \"[0-9a-f]{8}\-([0-9a-f]{4}\-){3}[0-9a-f]{12}\"
+bool\(false\)
+string\(36\) \"[0-9a-f]{8}\-([0-9a-f]{4}\-){3}[0-9a-f]{12}\"
+bool\(false\)
diff --git a/ext/openssl/tests/openssl_spki_new.phpt b/ext/openssl/tests/openssl_spki_new.phpt
new file mode 100644
index 0000000000..e40f9bf28e
--- /dev/null
+++ b/ext/openssl/tests/openssl_spki_new.phpt
@@ -0,0 +1,77 @@
+--TEST--
+Testing openssl_spki_new()
+Tests SPKAC for all available private key sizes & hashing algorithms
+--SKIPIF--
+<?php
+if (!extension_loaded("openssl")) die("skip");
+if (!@openssl_pkey_new()) die("skip cannot create private key");
+?>
+--FILE--
+<?php
+
+/* array of private key sizes to test */
+$ksize = array('1024'=>1024,
+ '2048'=>2048,
+ '4096'=>4096);
+
+/* array of available hashings to test */
+$algo = array('md4'=>OPENSSL_ALGO_MD4,
+ 'md5'=>OPENSSL_ALGO_MD5,
+ 'sha1'=>OPENSSL_ALGO_SHA1,
+ 'sha224'=>OPENSSL_ALGO_SHA224,
+ 'sha256'=>OPENSSL_ALGO_SHA256,
+ 'sha384'=>OPENSSL_ALGO_SHA384,
+ 'sha512'=>OPENSSL_ALGO_SHA512,
+ 'rmd160'=>OPENSSL_ALGO_RMD160);
+
+/* loop over key sizes for test */
+foreach($ksize as $k => $v) {
+
+ /* generate new private key of specified size to use for tests */
+ $pkey = openssl_pkey_new(array('digest_alg' => 'sha512',
+ 'private_key_type' => OPENSSL_KEYTYPE_RSA,
+ 'private_key_bits' => $v));
+ openssl_pkey_export($pkey, $pass);
+
+ /* loop to create and verify results */
+ foreach($algo as $key => $value) {
+ var_dump(openssl_spki_new($pkey, _uuid(), $value));
+ }
+ openssl_free_key($pkey);
+}
+
+/* generate a random challenge */
+function _uuid()
+{
+ return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x', mt_rand(0, 0xffff),
+ mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0x0fff) | 0x4000,
+ mt_rand(0, 0x3fff) | 0x8000, mt_rand(0, 0xffff),
+ mt_rand(0, 0xffff), mt_rand(0, 0xffff));
+}
+
+?>
+--EXPECTF--
+string(478) "%s"
+string(478) "%s"
+string(478) "%s"
+string(478) "%s"
+string(478) "%s"
+string(478) "%s"
+string(478) "%s"
+string(474) "%s"
+string(830) "%s"
+string(830) "%s"
+string(830) "%s"
+string(830) "%s"
+string(830) "%s"
+string(830) "%s"
+string(830) "%s"
+string(826) "%s"
+string(1510) "%s"
+string(1510) "%s"
+string(1510) "%s"
+string(1510) "%s"
+string(1510) "%s"
+string(1510) "%s"
+string(1510) "%s"
+string(1506) "%s"
diff --git a/ext/openssl/tests/openssl_spki_verify.phpt b/ext/openssl/tests/openssl_spki_verify.phpt
new file mode 100644
index 0000000000..1ee573fd3f
--- /dev/null
+++ b/ext/openssl/tests/openssl_spki_verify.phpt
@@ -0,0 +1,105 @@
+--TEST--
+Testing openssl_spki_verify()
+Creates SPKAC for all available key sizes & signature algorithms and tests for valid signature
+--INI--
+error_reporting=0
+--SKIPIF--
+<?php
+if (!extension_loaded("openssl")) die("skip");
+if (!@openssl_pkey_new()) die("skip cannot create private key");
+?>
+--FILE--
+<?php
+
+/* array of private key sizes to test */
+$ksize = array('1024'=>1024,
+ '2048'=>2048,
+ '4096'=>4096);
+
+/* array of available hashings to test */
+$algo = array('md4'=>OPENSSL_ALGO_MD4,
+ 'md5'=>OPENSSL_ALGO_MD5,
+ 'sha1'=>OPENSSL_ALGO_SHA1,
+ 'sha224'=>OPENSSL_ALGO_SHA224,
+ 'sha256'=>OPENSSL_ALGO_SHA256,
+ 'sha384'=>OPENSSL_ALGO_SHA384,
+ 'sha512'=>OPENSSL_ALGO_SHA512,
+ 'rmd160'=>OPENSSL_ALGO_RMD160);
+
+/* loop over key sizes for test */
+foreach($ksize as $k => $v) {
+
+ /* generate new private key of specified size to use for tests */
+ $pkey = openssl_pkey_new(array('digest_alg' => 'sha512',
+ 'private_key_type' => OPENSSL_KEYTYPE_RSA,
+ 'private_key_bits' => $v));
+ openssl_pkey_export($pkey, $pass);
+
+ /* loop to create and verify results */
+ foreach($algo as $key => $value) {
+ $spkac = openssl_spki_new($pkey, _uuid(), $value);
+ var_dump(openssl_spki_verify(preg_replace('/SPKAC=/', '', $spkac)));
+ var_dump(openssl_spki_verify($spkac.'Make it fail'));
+ }
+ openssl_free_key($pkey);
+}
+
+/* generate a random challenge */
+function _uuid()
+{
+ return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x', mt_rand(0, 0xffff),
+ mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0x0fff) | 0x4000,
+ mt_rand(0, 0x3fff) | 0x8000, mt_rand(0, 0xffff),
+ mt_rand(0, 0xffff), mt_rand(0, 0xffff));
+}
+
+?>
+--EXPECT--
+bool(true)
+bool(false)
+bool(true)
+bool(false)
+bool(true)
+bool(false)
+bool(true)
+bool(false)
+bool(true)
+bool(false)
+bool(true)
+bool(false)
+bool(true)
+bool(false)
+bool(true)
+bool(false)
+bool(true)
+bool(false)
+bool(true)
+bool(false)
+bool(true)
+bool(false)
+bool(true)
+bool(false)
+bool(true)
+bool(false)
+bool(true)
+bool(false)
+bool(true)
+bool(false)
+bool(true)
+bool(false)
+bool(true)
+bool(false)
+bool(true)
+bool(false)
+bool(true)
+bool(false)
+bool(true)
+bool(false)
+bool(true)
+bool(false)
+bool(true)
+bool(false)
+bool(true)
+bool(false)
+bool(true)
+bool(false) \ No newline at end of file
diff --git a/ext/openssl/tests/openssl_x509_fingerprint.phpt b/ext/openssl/tests/openssl_x509_fingerprint.phpt
new file mode 100644
index 0000000000..6cd464a894
--- /dev/null
+++ b/ext/openssl/tests/openssl_x509_fingerprint.phpt
@@ -0,0 +1,47 @@
+--TEST--
+Testing openssl_x509_fingerprint()
+--SKIPIF--
+<?php
+if (!extension_loaded("openssl")) die("skip");
+?>
+--FILE--
+<?php
+
+$cert = "file://" . dirname(__FILE__) . "/cert.crt";
+
+echo "** Testing with no parameters **\n";
+var_dump(openssl_x509_fingerprint());
+
+echo "** Testing default functionality **\n";
+var_dump(openssl_x509_fingerprint($cert));
+
+echo "** Testing hash method md5 **\n";
+var_dump(openssl_x509_fingerprint($cert, 'md5'));
+
+echo "**Testing raw output md5 **\n";
+var_dump(bin2hex(openssl_x509_fingerprint($cert, 'md5', true)));
+
+echo "** Testing bad certification **\n";
+var_dump(openssl_x509_fingerprint('123'));
+echo "** Testing bad hash method **\n";
+var_dump(openssl_x509_fingerprint($cert, 'xx45'));
+--EXPECTF--
+** Testing with no parameters **
+
+Warning: openssl_x509_fingerprint() expects at least 1 parameter, 0 given in %s on line %d
+NULL
+** Testing default functionality **
+string(40) "6e6fd1ea10a5a23071d61c728ee9b40df6dbc33c"
+** Testing hash method md5 **
+string(32) "ac77008e172897e06c0b065294487a67"
+**Testing raw output md5 **
+string(32) "ac77008e172897e06c0b065294487a67"
+** Testing bad certification **
+
+Warning: openssl_x509_fingerprint(): cannot get cert from parameter 1 in %s on line %d
+bool(false)
+** Testing bad hash method **
+
+Warning: openssl_x509_fingerprint(): Unknown signature algorithm in %s on line %d
+bool(false)
+
diff --git a/ext/openssl/tests/peer_verification.phpt b/ext/openssl/tests/peer_verification.phpt
new file mode 100644
index 0000000000..6aff34ddd8
--- /dev/null
+++ b/ext/openssl/tests/peer_verification.phpt
@@ -0,0 +1,61 @@
+--TEST--
+Peer verification enabled for client streams
+--SKIPIF--
+<?php
+if (!extension_loaded("openssl")) die("skip openssl not loaded");
+if (!function_exists("proc_open")) die("skip no proc_open");
+--FILE--
+<?php
+$serverCode = <<<'CODE'
+ $serverUri = "ssl://127.0.0.1:64321";
+ $serverFlags = STREAM_SERVER_BIND | STREAM_SERVER_LISTEN;
+ $serverCtx = stream_context_create(['ssl' => [
+ 'local_cert' => __DIR__ . '/bug54992.pem'
+ ]]);
+
+ $server = stream_socket_server($serverUri, $errno, $errstr, $serverFlags, $serverCtx);
+ phpt_notify();
+
+ for ($i = 0; $i < 5; $i++) {
+ @stream_socket_accept($server, 1);
+ }
+CODE;
+
+$clientCode = <<<'CODE'
+ $serverUri = "ssl://127.0.0.1:64321";
+ $clientFlags = STREAM_CLIENT_CONNECT;
+ $caFile = __DIR__ . '/bug54992-ca.pem';
+
+ phpt_wait();
+
+ // Expected to fail -- untrusted server cert and no CA File present
+ var_dump(@stream_socket_client($serverUri, $errno, $errstr, 1, $clientFlags));
+
+ // Expected to fail -- untrusted server cert and no CA File present
+ $clientCtx = stream_context_create(['ssl' => [
+ 'verify_peer' => true,
+ ]]);
+ var_dump(@stream_socket_client($serverUri, $errno, $errstr, 1, $clientFlags, $clientCtx));
+
+ // Should succeed with peer verification disabled in context
+ $clientCtx = stream_context_create(['ssl' => [
+ 'verify_peer' => false,
+ 'verify_peer_name' => false,
+ ]]);
+ var_dump(stream_socket_client($serverUri, $errno, $errstr, 1, $clientFlags, $clientCtx));
+
+ // Should succeed with CA file specified in context
+ $clientCtx = stream_context_create(['ssl' => [
+ 'cafile' => $caFile,
+ 'peer_name' => 'bug54992.local',
+ ]]);
+ var_dump(stream_socket_client($serverUri, $errno, $errstr, 1, $clientFlags, $clientCtx));
+CODE;
+
+include 'ServerClientTestCase.inc';
+ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
+--EXPECTF--
+bool(false)
+bool(false)
+resource(%d) of type (stream)
+resource(%d) of type (stream)
diff --git a/ext/openssl/tests/san-ca.pem b/ext/openssl/tests/san-ca.pem
new file mode 100644
index 0000000000..88682ba2dc
--- /dev/null
+++ b/ext/openssl/tests/san-ca.pem
@@ -0,0 +1,15 @@
+-----BEGIN CERTIFICATE-----
+MIICYTCCAcqgAwIBAgIJAIaqxtY5dwjtMA0GCSqGSIb3DQEBBQUAMFMxCzAJBgNV
+BAYTAlVTMQswCQYDVQQIEwJNTjEUMBIGA1UEBxMLTWlubmVhcG9saXMxITAfBgNV
+BAsTGERvbWFpbiBDb250cm9sIFZhbGlkYXRlZDAeFw0xMzA5MjQwODA1NTFaFw0y
+MTEyMTEwODA1NTFaMFMxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJNTjEUMBIGA1UE
+BxMLTWlubmVhcG9saXMxITAfBgNVBAsTGERvbWFpbiBDb250cm9sIFZhbGlkYXRl
+ZDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAsFGqfbU/8D+KjroQl4XMyt9m
+dcSP7iZtqphOu9nVZxYAAqfaqj8FnC/pwYV3TU6ZHndLTQAllwYT3sQBQPPGmZQ9
+clSIMEL003t3pi4ZVXkttG6Vvr+Z9PBcHhlKLQ7WMHnn4qctllWXTSoyTQpkETF3
+Fc3mrG5G37BhoUno7NECAwEAAaM9MDswOQYDVR0RBDIwMIILZXhhbXBsZS5vcmeC
+D3d3dy5leGFtcGxlLm9yZ4IQdGVzdC5leGFtcGxlLm9yZzANBgkqhkiG9w0BAQUF
+AAOBgQBf/FZhzheIcQJ+dyTk8xQ/nJLvpmBhbd1LNtfwk/MsC9UHsz4QXs9sBw1k
+rH0FjoqgM6avj7zKHJFTj6q7Rd+OX5V4HynYPhX67sWbN3KWEHffL98nGGd/bo3X
+pSjNk5vnyKYiwdUUe11Ac9csh0HcSBbhOYjy0T/i9AlQcKbuCg==
+-----END CERTIFICATE-----
diff --git a/ext/openssl/tests/san-cert.pem b/ext/openssl/tests/san-cert.pem
new file mode 100644
index 0000000000..923d490e72
--- /dev/null
+++ b/ext/openssl/tests/san-cert.pem
@@ -0,0 +1,31 @@
+-----BEGIN CERTIFICATE-----
+MIICYTCCAcqgAwIBAgIJAIaqxtY5dwjtMA0GCSqGSIb3DQEBBQUAMFMxCzAJBgNV
+BAYTAlVTMQswCQYDVQQIEwJNTjEUMBIGA1UEBxMLTWlubmVhcG9saXMxITAfBgNV
+BAsTGERvbWFpbiBDb250cm9sIFZhbGlkYXRlZDAeFw0xMzA5MjQwODA1NTFaFw0y
+MTEyMTEwODA1NTFaMFMxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJNTjEUMBIGA1UE
+BxMLTWlubmVhcG9saXMxITAfBgNVBAsTGERvbWFpbiBDb250cm9sIFZhbGlkYXRl
+ZDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAsFGqfbU/8D+KjroQl4XMyt9m
+dcSP7iZtqphOu9nVZxYAAqfaqj8FnC/pwYV3TU6ZHndLTQAllwYT3sQBQPPGmZQ9
+clSIMEL003t3pi4ZVXkttG6Vvr+Z9PBcHhlKLQ7WMHnn4qctllWXTSoyTQpkETF3
+Fc3mrG5G37BhoUno7NECAwEAAaM9MDswOQYDVR0RBDIwMIILZXhhbXBsZS5vcmeC
+D3d3dy5leGFtcGxlLm9yZ4IQdGVzdC5leGFtcGxlLm9yZzANBgkqhkiG9w0BAQUF
+AAOBgQBf/FZhzheIcQJ+dyTk8xQ/nJLvpmBhbd1LNtfwk/MsC9UHsz4QXs9sBw1k
+rH0FjoqgM6avj7zKHJFTj6q7Rd+OX5V4HynYPhX67sWbN3KWEHffL98nGGd/bo3X
+pSjNk5vnyKYiwdUUe11Ac9csh0HcSBbhOYjy0T/i9AlQcKbuCg==
+-----END CERTIFICATE-----
+-----BEGIN PRIVATE KEY-----
+MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALBRqn21P/A/io66
+EJeFzMrfZnXEj+4mbaqYTrvZ1WcWAAKn2qo/BZwv6cGFd01OmR53S00AJZcGE97E
+AUDzxpmUPXJUiDBC9NN7d6YuGVV5LbRulb6/mfTwXB4ZSi0O1jB55+KnLZZVl00q
+Mk0KZBExdxXN5qxuRt+wYaFJ6OzRAgMBAAECgYB11e5iWvqjPmQEZRdnnJU0VD8u
+n7ItT+Nk6qtb4gY8Abj6DWIW+01th5vqqJ8FvGyartFVYa69kuM+srG/zevAZWeu
+fGZtwiwZR4DRSyRcPp4rnNiksK3dkAZA6UewmRDPv8uyHJlXc5i+Ft1ILJ5Q5jgn
+UkC4z3EJP5Se9KZywQJBAOO4lRq42wLsYr2SDrQDSs4leie3FKc2bgvjF7Djosh1
+ZYbf55F5b9w1zgnccmni2HkqOnyFu4SKarmXyCsYxrkCQQDGNvnUh7/zZswrdWZ/
+PMp9zVDTh/5Oc2B4ByNLw1ERDwYhjchKgPRlQvn4cp3Pwf3UYPQ/8XGXzzEJey3A
+r0rZAkBf/tDEOgcBPXsGZQrTscuYCU5sbY5ESvqrAilbhSp7DJom+D5bIfEYyIm5
+uHd20Yzlzvpmwc1huyPwZt6X5FLpAkATDReoGMAXSesXxjnqwtIHk2NQYYLM0YQV
+JUJ8NrKk/Bevw+vbVVeoH+7ctU97t36JGiR/vNoZKD3jVmaIXZDJAkEA4wJbwzIo
+L32mu9VmZa7wjmfkraQEmXTPaA5D9lNC0AwRTgkj+x2Qe1vawNblNK9PPLBDdplQ
+L//53ADq/wv5rA==
+-----END PRIVATE KEY-----
diff --git a/ext/openssl/tests/san_peer_matching.phpt b/ext/openssl/tests/san_peer_matching.phpt
new file mode 100644
index 0000000000..0e1f30cb64
--- /dev/null
+++ b/ext/openssl/tests/san_peer_matching.phpt
@@ -0,0 +1,50 @@
+--TEST--
+Peer verification matches SAN names
+--SKIPIF--
+<?php
+if (!extension_loaded("openssl")) die("skip openssl not loaded");
+if (!function_exists("proc_open")) die("skip no proc_open");
+--FILE--
+<?php
+$serverCode = <<<'CODE'
+ $serverUri = "ssl://127.0.0.1:64321";
+ $serverFlags = STREAM_SERVER_BIND | STREAM_SERVER_LISTEN;
+ $serverCtx = stream_context_create(['ssl' => [
+ 'local_cert' => __DIR__ . '/san-cert.pem',
+ ]]);
+
+ $server = stream_socket_server($serverUri, $errno, $errstr, $serverFlags, $serverCtx);
+ phpt_notify();
+
+ @stream_socket_accept($server, 1);
+ @stream_socket_accept($server, 1);
+CODE;
+
+$clientCode = <<<'CODE'
+ $serverUri = "ssl://127.0.0.1:64321";
+ $clientFlags = STREAM_CLIENT_CONNECT;
+ $clientCtx = stream_context_create(['ssl' => [
+ 'verify_peer' => false,
+ 'cafile' => __DIR__ . '/san-ca.pem',
+ ]]);
+
+ phpt_wait();
+
+ stream_context_set_option($clientCtx, 'ssl', 'peer_name', 'example.org');
+ var_dump(stream_socket_client($serverUri, $errno, $errstr, 1, $clientFlags, $clientCtx));
+
+ stream_context_set_option($clientCtx, 'ssl', 'peer_name', 'moar.example.org');
+ var_dump(stream_socket_client($serverUri, $errno, $errstr, 1, $clientFlags, $clientCtx));
+CODE;
+
+include 'ServerClientTestCase.inc';
+ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
+--EXPECTF--
+resource(%d) of type (stream)
+
+Warning: stream_socket_client(): Unable to locate peer certificate CN in %s on line %d
+
+Warning: stream_socket_client(): Failed to enable crypto in %s on line %d
+
+Warning: stream_socket_client(): unable to connect to ssl://127.0.0.1:64321 (Unknown error) in %s on line %d
+bool(false)
diff --git a/ext/openssl/tests/session_meta_capture.phpt b/ext/openssl/tests/session_meta_capture.phpt
new file mode 100644
index 0000000000..1e5e1e67ee
--- /dev/null
+++ b/ext/openssl/tests/session_meta_capture.phpt
@@ -0,0 +1,65 @@
+--TEST--
+Capture SSL session meta array in stream context
+--SKIPIF--
+<?php
+if (!extension_loaded("openssl")) die("skip openssl not loaded");
+if (!function_exists("proc_open")) die("skip no proc_open");
+if (OPENSSL_VERSION_NUMBER < 0x10001001) die("skip OpenSSLv1.0.1 required");
+--FILE--
+<?php
+$serverCode = <<<'CODE'
+ $serverUri = "ssl://127.0.0.1:64321";
+ $serverFlags = STREAM_SERVER_BIND | STREAM_SERVER_LISTEN;
+ $serverCtx = stream_context_create(['ssl' => [
+ 'local_cert' => __DIR__ . '/bug54992.pem'
+ ]]);
+
+ $server = stream_socket_server($serverUri, $errno, $errstr, $serverFlags, $serverCtx);
+ phpt_notify();
+
+ @stream_socket_accept($server, 1);
+ @stream_socket_accept($server, 1);
+ @stream_socket_accept($server, 1);
+ @stream_socket_accept($server, 1);
+CODE;
+
+$clientCode = <<<'CODE'
+ $serverUri = "ssl://127.0.0.1:64321";
+ $clientFlags = STREAM_CLIENT_CONNECT;
+ $clientCtx = stream_context_create(['ssl' => [
+ 'verify_peer' => true,
+ 'cafile' => __DIR__ . '/bug54992-ca.pem',
+ 'peer_name' => 'bug54992.local',
+ 'capture_session_meta' => true,
+ ]]);
+
+ phpt_wait();
+
+ stream_context_set_option($clientCtx, 'ssl', 'crypto_method', STREAM_CRYPTO_METHOD_SSLv3_CLIENT);
+ stream_socket_client($serverUri, $errno, $errstr, 1, $clientFlags, $clientCtx);
+ $meta = stream_context_get_options($clientCtx)['ssl']['session_meta'];
+ var_dump($meta['protocol']);
+
+ stream_context_set_option($clientCtx, 'ssl', 'crypto_method', STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT);
+ stream_socket_client($serverUri, $errno, $errstr, 1, $clientFlags, $clientCtx);
+ $meta = stream_context_get_options($clientCtx)['ssl']['session_meta'];
+ var_dump($meta['protocol']);
+
+ stream_context_set_option($clientCtx, 'ssl', 'crypto_method', STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT);
+ stream_socket_client($serverUri, $errno, $errstr, 1, $clientFlags, $clientCtx);
+ $meta = stream_context_get_options($clientCtx)['ssl']['session_meta'];
+ var_dump($meta['protocol']);
+
+ stream_context_set_option($clientCtx, 'ssl', 'crypto_method', STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT);
+ stream_socket_client($serverUri, $errno, $errstr, 2, $clientFlags, $clientCtx);
+ $meta = stream_context_get_options($clientCtx)['ssl']['session_meta'];
+ var_dump($meta['protocol']);
+CODE;
+
+include 'ServerClientTestCase.inc';
+ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
+--EXPECTF--
+string(5) "SSLv3"
+string(5) "TLSv1"
+string(7) "TLSv1.1"
+string(7) "TLSv1.2"
diff --git a/ext/openssl/tests/sni_001.phpt b/ext/openssl/tests/sni_001.phpt
index 3d7798cf85..e7dbf3f19e 100644
--- a/ext/openssl/tests/sni_001.phpt
+++ b/ext/openssl/tests/sni_001.phpt
@@ -20,12 +20,18 @@ SNI 001
* the server returned.
*/
-function context() {
- return stream_context_create(array(
- 'ssl' => array(
- 'capture_peer_cert' => true,
- ),
- ));
+function context($host = NULL) {
+
+ $ctx = stream_context_create();
+ stream_context_set_option($ctx, 'ssl', 'capture_peer_cert', true);
+ stream_context_set_option($ctx, 'ssl', 'verify_peer', false);
+ if ($host) {
+ stream_context_set_option($ctx, 'ssl', 'peer_name', $host);
+ } else {
+ stream_context_set_option($ctx, 'ssl', 'verify_peer_name', false);
+ }
+
+ return $ctx;
}
function get_CN($context) {
@@ -72,18 +78,18 @@ function do_enable_crypto_test($url, $context) {
/* Test https:// streams */
echo "-- auto host name (1) --\n";
-do_http_test('https://alice.sni.velox.ch/', context());
+do_http_test('https://alice.sni.velox.ch/', context('alice.sni.velox.ch'));
echo "-- auto host name (2) --\n";
-do_http_test('https://bob.sni.velox.ch/', context());
+do_http_test('https://bob.sni.velox.ch/', context('bob.sni.velox.ch'));
echo "-- auto host name (3) --\n";
-do_http_test('https://bob.sni.velox.ch./', context());
+do_http_test('https://bob.sni.velox.ch./', context('bob.sni.velox.ch'));
echo "-- user supplied server name --\n";
$context = context();
-stream_context_set_option($context, 'ssl', 'SNI_server_name', 'bob.sni.velox.ch');
+stream_context_set_option($context, 'ssl', 'peer_name', 'bob.sni.velox.ch');
stream_context_set_option($context, 'http', 'header', b'Host: bob.sni.velox.ch');
do_http_test('https://alice.sni.velox.ch/', $context);
@@ -96,15 +102,15 @@ do_http_test('https://bob.sni.velox.ch/', $context);
/* Test ssl:// socket streams */
echo "-- raw SSL stream (1) --\n";
-do_ssl_test('ssl://bob.sni.velox.ch:443', context());
+do_ssl_test('ssl://bob.sni.velox.ch:443', context('bob.sni.velox.ch'));
echo "-- raw SSL stream (2) --\n";
-do_ssl_test('ssl://mallory.sni.velox.ch:443', context());
+do_ssl_test('ssl://mallory.sni.velox.ch:443', context('mallory.sni.velox.ch'));
echo "-- raw SSL stream with user supplied sni --\n";
-$context = context();
-stream_context_set_option($context, 'ssl', 'SNI_server_name', 'bob.sni.velox.ch');
+$context = context('bob.sni.velox.ch');
+stream_context_set_option($context, 'ssl', 'peer_name', 'bob.sni.velox.ch');
do_ssl_test('ssl://mallory.sni.velox.ch:443', $context);
@@ -128,7 +134,7 @@ do_enable_crypto_test('tcp://mallory.sni.velox.ch:443', context());
echo "-- stream_socket_enable_crypto with user supplied sni --\n";
$context = context();
-stream_context_set_option($context, 'ssl', 'SNI_server_name', 'bob.sni.velox.ch');
+stream_context_set_option($context, 'ssl', 'peer_name', 'bob.sni.velox.ch');
do_enable_crypto_test('tcp://mallory.sni.velox.ch:443', $context);
@@ -142,7 +148,7 @@ do_enable_crypto_test('tcp://mallory.sni.velox.ch:443', $context);
echo "-- stream_socket_enable_crypto with long name --\n";
$context = context();
-stream_context_set_option($context, 'ssl', 'SNI_server_name', str_repeat('a.', 500) . '.sni.velox.ch');
+stream_context_set_option($context, 'ssl', 'peer_name', str_repeat('a.', 500) . '.sni.velox.ch');
do_enable_crypto_test('tcp://mallory.sni.velox.ch:443', $context);
diff --git a/ext/openssl/tests/sni_server.phpt b/ext/openssl/tests/sni_server.phpt
new file mode 100644
index 0000000000..d44a69f549
--- /dev/null
+++ b/ext/openssl/tests/sni_server.phpt
@@ -0,0 +1,60 @@
+--TEST--
+sni_server
+--SKIPIF--
+<?php
+if (!extension_loaded("openssl")) die("skip openssl not loaded");
+--FILE--
+<?php
+$serverCode = <<<'CODE'
+ $flags = STREAM_SERVER_BIND|STREAM_SERVER_LISTEN;
+ $ctx = stream_context_create(['ssl' => [
+ 'local_cert' => __DIR__ . '/domain1.pem',
+ 'SNI_server_certs' => [
+ "domain1.com" => __DIR__ . "/sni_server_domain1.pem",
+ "domain2.com" => __DIR__ . "/sni_server_domain2.pem",
+ "domain3.com" => __DIR__ . "/sni_server_domain3.pem"
+ ]
+ ]]);
+
+ $server = stream_socket_server('tls://127.0.0.1:64321', $errno, $errstr, $flags, $ctx);
+ phpt_notify();
+
+ for ($i=0; $i < 3; $i++) {
+ @stream_socket_accept($server, 3);
+ }
+CODE;
+
+$clientCode = <<<'CODE'
+ $flags = STREAM_CLIENT_CONNECT;
+ $ctxArr = [
+ 'cafile' => __DIR__ . '/sni_server_ca.pem',
+ 'capture_peer_cert' => true
+ ];
+
+ phpt_wait();
+
+ $ctxArr['peer_name'] = 'domain1.com';
+ $ctx = stream_context_create(['ssl' => $ctxArr]);
+ $client = stream_socket_client("tls://127.0.0.1:64321", $errno, $errstr, 1, $flags, $ctx);
+ $cert = stream_context_get_options($ctx)['ssl']['peer_certificate'];
+ var_dump(openssl_x509_parse($cert)['subject']['CN']);
+
+ $ctxArr['peer_name'] = 'domain2.com';
+ $ctx = stream_context_create(['ssl' => $ctxArr]);
+ $client = @stream_socket_client("tls://127.0.0.1:64321", $errno, $errstr, 1, $flags, $ctx);
+ $cert = stream_context_get_options($ctx)['ssl']['peer_certificate'];
+ var_dump(openssl_x509_parse($cert)['subject']['CN']);
+
+ $ctxArr['peer_name'] = 'domain3.com';
+ $ctx = stream_context_create(['ssl' => $ctxArr]);
+ $client = @stream_socket_client("tls://127.0.0.1:64321", $errno, $errstr, 1, $flags, $ctx);
+ $cert = stream_context_get_options($ctx)['ssl']['peer_certificate'];
+ var_dump(openssl_x509_parse($cert)['subject']['CN']);
+CODE;
+
+include 'ServerClientTestCase.inc';
+ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
+--EXPECTF--
+string(%d) "domain1.com"
+string(%d) "domain2.com"
+string(%d) "domain3.com"
diff --git a/ext/openssl/tests/sni_server_ca.pem b/ext/openssl/tests/sni_server_ca.pem
new file mode 100644
index 0000000000..f840802c29
--- /dev/null
+++ b/ext/openssl/tests/sni_server_ca.pem
@@ -0,0 +1,63 @@
+-----BEGIN CERTIFICATE-----
+MIIFPjCCAyYCAQEwDQYJKoZIhvcNAQEFBQAwWTELMAkGA1UEBhMCVVMxCzAJBgNV
+BAgMAlNDMRUwEwYDVQQHDAxNeXJ0bGUgQmVhY2gxEjAQBgNVBAoMCXBocC50ZXN0
+czESMBAGA1UEAwwJcGhwLnRlc3RzMB4XDTE0MDMwNTE0MTg1M1oXDTI0MDMwMjE0
+MTg1M1owcTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAlNDMRUwEwYDVQQHDAxNeXJ0
+bGUgQmVhY2gxHjAcBgNVBAoMFXBocC50ZXN0cyBzdWJvcmRpbmF0ZTEeMBwGA1UE
+AwwVcGhwLnRlc3RzLnN1Ym9yZGluYXRlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
+MIICCgKCAgEA4rmcz5M0B2Td+0g8+pmGT0IBCxZTVL14xKA1yLeY+6eshdNiRAIR
+8m5sI21AirtAh7mJziA2Q4C/1bRvY9VI/0yxXRP20aicNmhEvFVAvQCxmAkg8hIm
+WwDiNLw+4HpAkQInpd8exjExhvanUgAz6tq8w6CL2BqquiwQPVIf5HHMHjomz1gz
+iZEs6CRE5YMdUd7ZSx+MHl6ww3WYwW/XHDPzAUIe8uhOT+yA8lVLFAac3+lyjA6g
+zMPYG5SrqE6/+yZYDVx2WhVMjJq8d+N6hnIkhZeJyZo2T0G7/pSGIwaVsceIJ/8/
+6km8p4Dn2Pq75F8RZW5iz0MnSqKqSH/ANP71UuinpcVfnyg0ajy7J+cNMDC3gNPC
+L0b13qRhRsjNYdyOR7Aj5uQgCegDVipMGLIa+5Vxnzinc059/81QttbsrF2Ll96y
+lQk46zhyMOOTnVuX/6k2iFnNUNouXiFlEYPdxRAOJtcOL650F7wTtWolNAyEsRyH
+sv3wDXAFXp8b+B/Be1i1yfomP6VnXQMls6RR00qBtc8qEVAMddYihAv5MWa6vXSc
++PA3dBJJBmBJeesqR2PC8kgs+CeGJfUS3VRcUmUKBWz+dMvHPSSiQebwg+Za3xz8
+l3WWPMVWwFUviyv8pw+AB30d5m/akB/r7a1f2FJjwg1P7atkZzz0/SUCAwEAATAN
+BgkqhkiG9w0BAQUFAAOCAgEATP38i4XRo2if8SZLqg/kdZQ/B7ER5PpOS0YkMgp+
+g1dLpUrjthIFJ96s9akxO4Bq1NUnt/Ms/8RgTHlM8xF3HuXBt6C6MjeIL9keg0rz
+D3dObstOGhqYrUpEhcCaG9mtWQ4G7n86e1/zKTIORAPBD1LPvp6nIzJGNU5DwPeR
+u+RK1/DCUBNk1YHrhxj6AevagVozTEeZ2F5AQK9B78MG1fXezN5q5ubLyJZG673q
+G1+rhigUrK2xoJzE+DDWlhrVt2nkdzK+rlP63Y9I/XUH76OT4kuLXqttnbWfuvCr
+mVt5st+nx24hGmd9yaiKPsUFPlbSfhwzstGreGlsdWIYKzCwnXX6niVMBdKwrRfa
+PBnmZbG3YYcMflWLcMUscCqvlPDn9fQS6uwKT6xcEDIou6a8KpQpEgo9k+KUpX2Z
+DpyFkDGEeYR2qENlblEoPpkBITWq68tY4jjkHETh1spgDisIzZFkFcupSIrcIaXb
+T05gv3POWqb8soejrxMBQHQwkHkZNHcaOBPTJMRG3UTkRbI/UN77oOjCk7/tWa0B
+GDEF2eMQ8bpTChQsr4pfIqaHjx4NVdLlZ4OfVoGlKISmMYjU+bVIYZibRNZg/GYP
+fUs9sB8ZSRTFvsOHtL5hoD/BYhPIQ5g2fkDsYdfQZIMPQidO+R9lOvMQUgPy/gGd
+JzA=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFhTCCA22gAwIBAgIJAMJT3RVinFnaMA0GCSqGSIb3DQEBBQUAMFkxCzAJBgNV
+BAYTAlVTMQswCQYDVQQIDAJTQzEVMBMGA1UEBwwMTXlydGxlIEJlYWNoMRIwEAYD
+VQQKDAlwaHAudGVzdHMxEjAQBgNVBAMMCXBocC50ZXN0czAeFw0xNDAzMDUxNDE1
+NDBaFw0yNDAzMDIxNDE1NDBaMFkxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJTQzEV
+MBMGA1UEBwwMTXlydGxlIEJlYWNoMRIwEAYDVQQKDAlwaHAudGVzdHMxEjAQBgNV
+BAMMCXBocC50ZXN0czCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAM1G
+jKhUktyuMjqqUSHW5pq+sR53S2E79fCqQq+Gom0NV/KdL5VAkwa3HBFzy+t0z6gE
+9wVP6h+AdVU0b9BEb9bEuaDVpW7kwclNLXtRVPw5BN3amLs0ukPoZqoBQVhjfg2p
+4qqYBpHBi6U7ltdEHApsBVxYsxGJz+g0NkF4oSlZt+M07jscNexXqpd1m08b9dp/
+nbIR6GLyQ6fSqIXJR7ImvBew2LE8WhFt9ldzzA0cEmr3NQcUXuS4fO8mRNIF+9dJ
+f/M9myn6NlCl/eO+YBVSLOp+J72wKLo2LT/C4zbSj2bhc24ui66olzVOCe/97V76
+xusTDKXwagcMxNJD6lZIjZsl5VS9/SYpECj63cGTu5OE6UP6ZyBKZsaX4ZpmklhH
+PTMfCTGOtK9MazStM7YtDLSTO3O0yGZXRd0uHcPXM+H2lHqpM90GfGNNdIzS8h7p
+hFmocpnHQLVN9SKbrgv9Rt+QbGpBwYH6NFtcwJRiNU9cabez2dcaXWs8+Okvxb7/
+Azvs0jv6d4Y05iIQ4uyJeBOfAuc0UyoG/y+XrGko/8omrTUnAUQtKD/1ymTs+yjb
+YNZ6dw5Q5w/FbgL6pMNNOcDgl37mGKFzpRqkUHbOhDZTEUlzYyvtn84t7rbC5g2u
+y+KFo3/S4qJheyRfl5FANFTUlxv4vMvMnuaHcJxvAgMBAAGjUDBOMB0GA1UdDgQW
+BBRQ95jPSVj20YO2Eq2+wnknOpCjqjAfBgNVHSMEGDAWgBRQ95jPSVj20YO2Eq2+
+wnknOpCjqjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4ICAQCaYxaA9H8O
+J55kLRzzgLV56yc+MhowAqOAOjD4J20aWbkRQIwUMIi6xnCwRRnwjuscPFOSF6P+
+Bn3kdVX+KhzFLNRhAk1qjEGjwSVLPKJSKJSdwOmrvCkSsIcbASxSXQkz714LkFAL
+hZ9cs4QD+/JVEPazgqjBwtlu3hwwuGAxwHNAKWeFaXpOJu9UerwfBmF090L0uBND
+QJAqDyCZRaQp4Re6qH3iQhxyERlAwCNIRSV2cWHJP0HmWye76Z2aehp96fKTo5Cd
+NXW3GKGSpmZeZgY8MZVyzUau1c83+nHJCYO0jhFyZjO4XMJ+cce0a2H8iIhox5fY
+ZBEuxYN6cPkdvDbFRQU+e+KO1jjyumNWentW57DDPihpgZ3f3Gf5xtUjnHTC5VKT
+chW9ujq18Hk4JWL5uyF+5am5Qm4YdjhhZ1TtGHNjoZGpHV0Tf4h5AcDG4Zr6TCmM
+3Rw952ytcMtZKZzBq2ZWC3lKpBPPzihAgwzgflVvi0BLP9Ek98oMxq22pymHEHY8
+ivfm+t4rRY6JMl7DxDcARvWoKujrJy2JUUm50vT5D2GG8xYwYr9XKgD8rP36qCjC
+B+1l+upz+37r9U12uWfjPsRjyphGbN8ZZAkjSSZQU2snogxaKOvWZb+2M/rajdek
+BSQ9sUZ6sNXnTkyYon6iH6WULvmSTgBGnQ==
+-----END CERTIFICATE-----
diff --git a/ext/openssl/tests/sni_server_domain1.pem b/ext/openssl/tests/sni_server_domain1.pem
new file mode 100644
index 0000000000..f00857bda8
--- /dev/null
+++ b/ext/openssl/tests/sni_server_domain1.pem
@@ -0,0 +1,82 @@
+-----BEGIN RSA PRIVATE KEY-----
+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
+-----END RSA PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIIFSDCCAzACAQEwDQYJKoZIhvcNAQEFBQAwcTELMAkGA1UEBhMCVVMxCzAJBgNV
+BAgMAlNDMRUwEwYDVQQHDAxNeXJ0bGUgQmVhY2gxHjAcBgNVBAoMFXBocC50ZXN0
+cyBzdWJvcmRpbmF0ZTEeMBwGA1UEAwwVcGhwLnRlc3RzLnN1Ym9yZGluYXRlMB4X
+DTE0MDMwNTE0MjgwOFoXDTI0MDMwMjE0MjgwOFowYzELMAkGA1UEBhMCVVMxFTAT
+BgNVBAgMDE15cnRsZSBCZWFjaDELMAkGA1UEBwwCU0MxGjAYBgNVBAoMEWRvbWFp
+bjEgdGVzdCBjZXJ0MRQwEgYDVQQDDAtkb21haW4xLmNvbTCCAiIwDQYJKoZIhvcN
+AQEBBQADggIPADCCAgoCggIBAJwmiDb308e7/g1DA+cyS4PvJRCbMRrezkIxJhZB
+FJsK189SORNva3JAUv5QjRujbxBurjbcYF6M+nC5TsC/XHRxkijwzmBxmuyTfhBo
+jJ0nGSzXPWqNaLry3QWuZb+s3KArumD8PeQJ2ejxxB4/JhXJFrZhY1dev3YgdUkf
+cA6SMMnu5pyZVUVADM6wAVTQ2j1Kqo/Bvsa9NjKOqU19cBh4EMk0f6TTh2RogwwV
+3ljQ7hATb02LQVl11zKxgt+h00kfijJQ4xX5gk2/stLPCC0+z2oMeixaLp/fUm4V
+0La8Av9LFEebcqjq4FY9XW7iklrkmolmDCWV2hSpJ3ZAk0C4jwg5bRzne6aWJkbf
+OPFqik24zlYaY8Y4buK3x8N8p4UDQudDf44lYigXKabUIV+HDDZbV7CVyNIa+nVi
+Nv+q+6KfY9rsTh7Yc5y3o7B3BpluthfMKVGcQE/Mrr03BDM8fh43S8gbvjf7ewRy
+pOPpyADfBtuupk+AY0ix3HZ8uCfFK1aAbw3lu6EJHb4ZbiKJmmxiYN8JJ542Pf0G
+7IggHEdOR2diBK3plPVZt7qIAMAQVpNmExLiLjW5vEwB6n049T5MV0ROmEy/dO3x
+WTHMP/t5zmBEakuBVeTrzaqDSC1sZdHQWGga7UcITBGGRQyuYw3NTraJaRdO7OOS
+cG2pAgMBAAEwDQYJKoZIhvcNAQEFBQADggIBAJ3pMaMcWaW5TJ1QV58npS/F7Ao7
+mciFMQ+KiQ6E5TJ/S37EfR/r5+mqQodM6Zvs07x4k2HdwcuI0cdpddLKfIH2znDW
+pJY1nEKI9W/pz9pEj3TBu9M3QAPzWVwYyBaJzVJGIll1yYVQVNGneHF8YpWQo+Ow
+yg97x1220oUSAlJ9zUwwls6QjJCo95PcClV9ZK6A+H17v+kwzBco7DwGhoxy3s+n
+D9+LjDa5Z6yk7Y1h+I4kIExenjuXT1wgQatruR8MBl8MX+KKeAi4neRdCBiDceHs
+zzO0f+IBNiF1J8THokV81i7DMbsHRGv28r3rcQpalDK9/rcBRVXUl0Kw8ZDQJLaS
+C1EuAcqw8TgvZOAhw431EpZW7Beek4xvrwTSzrF1XhlbD/1Jm8YAjGQyO138qzq2
+p40NRXgGL3rT6LEDcUVjL9D2z/7Tu7G9g17vleEzBC13ILnAOg/UFsZ4BuGNCnxa
+3PiJ7QaDBL0UA4uUuXjVgGTFzeDaTJbNCybcTKI7rD4zAa/4d05364gA3S1XJU66
+tH24Z3ncMBrh1GrsX4z6vLkNyb7m2wBqWBuyuVidm7rPcMGoggbrAsIP5vPjIMdg
+GR+ghPgzhJsbEg/WoSxv8+oljAJT2I1A53ulJaL4w6S/Ku1yEQ01Xk4vZEBJHAAw
+TJxrq625wwS0cKQd
+-----END CERTIFICATE-----
diff --git a/ext/openssl/tests/sni_server_domain2.pem b/ext/openssl/tests/sni_server_domain2.pem
new file mode 100644
index 0000000000..4acc355352
--- /dev/null
+++ b/ext/openssl/tests/sni_server_domain2.pem
@@ -0,0 +1,82 @@
+-----BEGIN RSA PRIVATE KEY-----
+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
+-----END RSA PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIIFSDCCAzACAQIwDQYJKoZIhvcNAQEFBQAwcTELMAkGA1UEBhMCVVMxCzAJBgNV
+BAgMAlNDMRUwEwYDVQQHDAxNeXJ0bGUgQmVhY2gxHjAcBgNVBAoMFXBocC50ZXN0
+cyBzdWJvcmRpbmF0ZTEeMBwGA1UEAwwVcGhwLnRlc3RzLnN1Ym9yZGluYXRlMB4X
+DTE0MDMwNTE0MzAzMVoXDTI0MDMwMjE0MzAzMVowYzELMAkGA1UEBhMCVVMxCzAJ
+BgNVBAgMAlNDMRUwEwYDVQQHDAxNeXJ0bGUgQmVhY2gxGjAYBgNVBAoMEWRvbWFp
+bjIgdGVzdCBjZXJ0MRQwEgYDVQQDDAtkb21haW4yLmNvbTCCAiIwDQYJKoZIhvcN
+AQEBBQADggIPADCCAgoCggIBAOdrgSdbb+KLIdxlKnYgUtNRY5zp83ZDQf4wJPpt
+AR+g4iiU9I0er7Kc7rbjyJ9Ba1RO1dddqOFzoWIsYxnwJeqgP7Of5hNCYMNgYyJ4
+L3sxXqnbJdYNmdAeQpwimTMvw7gcrLNxF4wPbrjqxAWpwMBPi61BH5k0AbT2Z4Jf
+RZ9UwKpGAOFAa/3TrUjEpmpv3c+ppK5RFjVGasLhQoEEdhjK019Mw/W715Q+TL8o
+utQ1HOWEURmM4jCtCskg6MIGuYyKq1LebLy2hyHTlx3zCY43LQ/ptxTa34gublzf
+Vmw2fSyJygpmv52Y4UR5MFBEx5LC/C/4cwjWN5ZPWOKTCAoTeynTDGL2pwA21Tnk
+ae0RgPftFpyst7thAsoAgyfuMPiGWBnhqdN+RzeFpw+lm22gquxr3UPOBG51rrN0
+//UJQNbhqxG5sqxS7EDYolUFG4ViQwJ0Ney3GPa59E60Iw+IU14Lif6NVzYYlT3d
+czZpt/uR97cKeCZDHO28o0XCspdm8z4dT1WTWNVuCLArb7zm9+IOu47bUJa/y9N8
+ApqPjrwWPXWwdQq8ViUCSewTSQa9GeB2MnIVKQCjNuLmPCDDkmmJYcVpyiwljkwb
+29Itigeo7TMgVBeadN3biZGYSgIeYvf/MjOjxMKt4bCcoKNsieIsCNHj9+9lHbIr
+UrpDAgMBAAEwDQYJKoZIhvcNAQEFBQADggIBAAzy1BUPLBK3+230sILn9iJV+7DJ
+1pFOTBdChTn8UrCzP8NrhQ7TNyNuwETcjxOMZ0IYc9SBUZgQV2RZrGM4Ek2dV2so
+Z+HzQ9UsAl31t8bL1uSBH4lspAeSAIq8HyLK52JxZ4yBK8ID2e7oHzbqY58Xcfzn
+4WlA54XYNI1+gj6bBTP7lXLz40H9lcPTHDsed3usYYWtfH/ncIW0rw9/fK1P6aTO
+680lOaLFB26Z3ygGiJXbfmcnIjEmbpgWLfcPOoIBFrDmkiHqo31UT1WBxQ16c4yw
+DwO+DY5KK6adI1j5PWUmT/8vwlsO2CEtq4DOKfB51ggG9NyciCw+vJXP4Ec30IiX
+5TNWNwpg+ex44ICScSF6ew4hS2tO59VFBvDaa/2nFhEOG0iECN0537v10vOxgaVr
+kHlCgJtgCwUbKrfVTA2FogXRDIBJ1aOZiA7kIPPSqdTH49HwieH55Q5kfi7xak5y
+tSijrFw70vZOf3ORErmXEAZFqQgPuK4KlvTMom7z9QnNq4L579DeGUdBhLYGXJzP
+8WcMXRlQ7XUT6TQaSTDwDYlWcxcZ584seK4rVCLT2jyChEWSLR2K29hZ0eNVQt43
+3Xxz9hExGS/oELv53zPqp10w/0ZKlFQc5Wa4kEkjBz5R8VzzWuelxarqNAH6m9Du
+i3jyxtHS869PHMU3
+-----END CERTIFICATE-----
diff --git a/ext/openssl/tests/sni_server_domain3.pem b/ext/openssl/tests/sni_server_domain3.pem
new file mode 100644
index 0000000000..9f80b717a2
--- /dev/null
+++ b/ext/openssl/tests/sni_server_domain3.pem
@@ -0,0 +1,82 @@
+-----BEGIN RSA PRIVATE KEY-----
+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
+-----END RSA PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIIFTzCCAzcCAQIwDQYJKoZIhvcNAQEFBQAwcTELMAkGA1UEBhMCVVMxCzAJBgNV
+BAgMAlNDMRUwEwYDVQQHDAxNeXJ0bGUgQmVhY2gxHjAcBgNVBAoMFXBocC50ZXN0
+cyBzdWJvcmRpbmF0ZTEeMBwGA1UEAwwVcGhwLnRlc3RzLnN1Ym9yZGluYXRlMB4X
+DTE0MDMwNTE0MzA0M1oXDTI0MDMwMjE0MzA0M1owajELMAkGA1UEBhMCVVMxCzAJ
+BgNVBAgMAlNDMRUwEwYDVQQHDAxNeXJ0bGUgQmVhY2gxITAfBgNVBAoMGEludGVy
+bmV0IFdpZGdpdHMgUHR5IEx0ZDEUMBIGA1UEAwwLZG9tYWluMy5jb20wggIiMA0G
+CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCu5H20GpgEO8HUN8wSFwVJxFsoQtH4
+AsXhrQQb996s58XJzgeBZLulxO7Oq2moY0hnFCRH7JyiwI3dlpfaC65uENq0sIYQ
+iCeEV7LOO0sXA7JdkNvWyiTP4tt6p1t5xmX6b72PLIPff+OK35a1N3IVqbo2D3WP
+m4ALTMIHwryVrZO5nfeBW2IznVHSLMofYpKeg/pMgQ1QI20FbqnU/FN8KZvGZTn2
+wpKRfgz/tCOZbdW3NYD0S/f5zo4W92FrbgKEqYVuzqRm31O05uilWuIw57c6cp8Y
+4Dx14jJUugaBs50LJVtkhYiYCH51UQKfGnmcYwctFfNsbBxPLYLzLXfmph3l3lFB
+5zZXf68uTQhj6Nqvykuj08KyqDKPS95qMDkIvxw68vVZpFEtRUkLGSOci+f70Vtl
+5l8w7TJ+zPPtKPe5DuQSRbDwU8e9NdhqbbliYu6H1pNKG6tHdZF5QNrZCxom8jfn
+jTKtM369VhUEUiZ1Wf19DbY7K6LVpkzu8B0hsD8oDU2oEPsS5iLAHzv8kbbPkYWc
+GfKsn+VBWm7aX9zv/JT2FEAQOiVzSe6Bi8walXJ9qEI5p53Wvfny0bmgrhMDvbbN
+5QRyBfTWs2SjsEgdUJFi7QhbZn1JVAwScMnAX/Ne2uS0uMdcEqU59FYfhW8jHI1v
+EY5BD8etxaT34QIDAQABMA0GCSqGSIb3DQEBBQUAA4ICAQDUel0pS4vCng3sQ2ab
+U4bSTDiiyR09TUlX54eaHrQFnnJAXxnHdu9S2xlizaGgkUHqQ7P6HXiMn5rl2GTw
+7uLcV2CoapceuX3HK+Iiy5r4phXtylmkUT737jR32Rib/jH7swkdPLDcnbI2J2Cs
+I1LIlCiuaJagu0q2liRnJOkdZQd1Rz3w9I/WHECxS9SAnaQsF4LbXabXObVeRrtq
+qiNoDbC0Q9c4RquVtbdjm4vP6eCjnqck/0Tq8ceq7Hg5hu3Q0scg3mEK+7cyp19X
+Y+/nCg8SZe+7LxewJ7GgqRgnDiHgfO5Nu4jhuZh72LA1mtIS+dU/cDVf8kxLiusG
+UlJKDwJ14Jh7LAKdRorqGFqNi8R5zEaCIcuSWvUfNl9TzOXlRj6c4lP8eixWG4FD
+CiHkv7jXsz8AYZcKy7IqsXVdnem+8PoKmBzhtngNX86W9JgQ65vLwsT4kdWHMfU0
+zEZrHZ3+qcmL5GUXI7r3uQQmp0RcNQV57X6bLRG4PaCMxuTv6JJ3Bi+qqX4vVWPL
+K1Au3W5UiJ3XPVllxSynA6lcUvVw/PoNLaV21sskxwJDtLwbSAH3DS0JQQSZzesj
+hGayf3SOVU1n84lTuc84KEgM+zaxGzqfShUadlLMKBS+w+8j8N+qBjUi08UOUKa7
+OAES8tCv7DK0Mi21rLXMadYwlw==
+-----END CERTIFICATE-----
diff --git a/ext/openssl/tests/stream_crypto_flags_001.phpt b/ext/openssl/tests/stream_crypto_flags_001.phpt
new file mode 100644
index 0000000000..f988886db2
--- /dev/null
+++ b/ext/openssl/tests/stream_crypto_flags_001.phpt
@@ -0,0 +1,50 @@
+--TEST--
+Basic bitwise stream crypto context flag assignment
+--SKIPIF--
+<?php
+if (!extension_loaded("openssl")) die("skip openssl not loaded");
+if (!function_exists("proc_open")) die("skip no proc_open");
+--FILE--
+<?php
+$serverCode = <<<'CODE'
+ $serverUri = "ssl://127.0.0.1:64321";
+ $serverFlags = STREAM_SERVER_BIND | STREAM_SERVER_LISTEN;
+ $serverCtx = stream_context_create(['ssl' => [
+ 'local_cert' => __DIR__ . '/bug54992.pem'
+ ]]);
+
+ $server = stream_socket_server($serverUri, $errno, $errstr, $serverFlags, $serverCtx);
+ phpt_notify();
+
+ @stream_socket_accept($server, 1);
+ @stream_socket_accept($server, 1);
+ @stream_socket_accept($server, 1);
+CODE;
+
+$clientCode = <<<'CODE'
+ $serverUri = "ssl://127.0.0.1:64321";
+ $clientFlags = STREAM_CLIENT_CONNECT;
+ $clientCtx = stream_context_create(['ssl' => [
+ 'verify_peer' => true,
+ 'cafile' => __DIR__ . '/bug54992-ca.pem',
+ 'peer_name' => 'bug54992.local',
+ ]]);
+
+ phpt_wait();
+
+ stream_context_set_option($clientCtx, 'ssl', 'crypto_method', STREAM_CRYPTO_METHOD_SSLv3_CLIENT);
+ var_dump(stream_socket_client($serverUri, $errno, $errstr, 1, $clientFlags, $clientCtx));
+
+ stream_context_set_option($clientCtx, 'ssl', 'crypto_method', STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT);
+ var_dump(stream_socket_client($serverUri, $errno, $errstr, 1, $clientFlags, $clientCtx));
+
+ stream_context_set_option($clientCtx, 'ssl', 'crypto_method', STREAM_CRYPTO_METHOD_TLS_CLIENT);
+ var_dump(stream_socket_client($serverUri, $errno, $errstr, 1, $clientFlags, $clientCtx));
+CODE;
+
+include 'ServerClientTestCase.inc';
+ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
+--EXPECTF--
+resource(%d) of type (stream)
+resource(%d) of type (stream)
+resource(%d) of type (stream)
diff --git a/ext/openssl/tests/stream_crypto_flags_002.phpt b/ext/openssl/tests/stream_crypto_flags_002.phpt
new file mode 100644
index 0000000000..e57f2b7069
--- /dev/null
+++ b/ext/openssl/tests/stream_crypto_flags_002.phpt
@@ -0,0 +1,57 @@
+--TEST--
+TLSv1.1 and TLSv1.2 bitwise stream crypto flag assignment
+--SKIPIF--
+<?php
+if (!extension_loaded("openssl")) die("skip openssl not loaded");
+if (!function_exists("proc_open")) die("skip no proc_open");
+if (OPENSSL_VERSION_NUMBER < 0x10001001) die("skip OpenSSLv1.0.1 required");
+--FILE--
+<?php
+$serverCode = <<<'CODE'
+ $serverUri = "ssl://127.0.0.1:64321";
+ $serverFlags = STREAM_SERVER_BIND | STREAM_SERVER_LISTEN;
+ $serverCtx = stream_context_create(['ssl' => [
+ 'local_cert' => __DIR__ . '/bug54992.pem'
+ ]]);
+
+ $server = stream_socket_server($serverUri, $errno, $errstr, $serverFlags, $serverCtx);
+ phpt_notify();
+
+ @stream_socket_accept($server, 1);
+ @stream_socket_accept($server, 1);
+ @stream_socket_accept($server, 1);
+ @stream_socket_accept($server, 1);
+CODE;
+
+$clientCode = <<<'CODE'
+ $serverUri = "ssl://127.0.0.1:64321";
+ $clientFlags = STREAM_CLIENT_CONNECT;
+ $clientCtx = stream_context_create(['ssl' => [
+ 'verify_peer' => true,
+ 'cafile' => __DIR__ . '/bug54992-ca.pem',
+ 'peer_name' => 'bug54992.local',
+ ]]);
+
+ phpt_wait();
+
+ stream_context_set_option($clientCtx, 'ssl', 'crypto_method', STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT);
+ var_dump(stream_socket_client($serverUri, $errno, $errstr, 2, $clientFlags, $clientCtx));
+
+ stream_context_set_option($clientCtx, 'ssl', 'crypto_method', STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT);
+ var_dump(stream_socket_client($serverUri, $errno, $errstr, 2, $clientFlags, $clientCtx));
+
+ stream_context_set_option($clientCtx, 'ssl', 'crypto_method', STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT);
+ var_dump(stream_socket_client($serverUri, $errno, $errstr, 2, $clientFlags, $clientCtx));
+
+ stream_context_set_option($clientCtx, 'ssl', 'crypto_method', STREAM_CRYPTO_METHOD_TLS_CLIENT);
+ var_dump(stream_socket_client($serverUri, $errno, $errstr, 2, $clientFlags, $clientCtx));
+CODE;
+
+include 'ServerClientTestCase.inc';
+ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
+--EXPECTF--
+resource(%d) of type (stream)
+resource(%d) of type (stream)
+resource(%d) of type (stream)
+resource(%d) of type (stream)
+
diff --git a/ext/openssl/tests/stream_crypto_flags_003.phpt b/ext/openssl/tests/stream_crypto_flags_003.phpt
new file mode 100644
index 0000000000..30ca7a76e9
--- /dev/null
+++ b/ext/openssl/tests/stream_crypto_flags_003.phpt
@@ -0,0 +1,60 @@
+--TEST--
+Server bitwise stream crypto flag assignment
+--SKIPIF--
+<?php
+if (!extension_loaded("openssl")) die("skip openssl not loaded");
+if (!function_exists("proc_open")) die("skip no proc_open");
+if (OPENSSL_VERSION_NUMBER < 0x10001001) die("skip OpenSSLv1.0.1 required");
+--FILE--
+<?php
+$serverCode = <<<'CODE'
+ $serverUri = "ssl://127.0.0.1:64321";
+ $serverFlags = STREAM_SERVER_BIND | STREAM_SERVER_LISTEN;
+ $serverCtx = stream_context_create(['ssl' => [
+ 'local_cert' => __DIR__ . '/bug54992.pem',
+
+ // Only accept SSLv3 and TLSv1.2 connections
+ 'crypto_method' => STREAM_CRYPTO_METHOD_SSLv3_SERVER | STREAM_CRYPTO_METHOD_TLSv1_2_SERVER,
+ ]]);
+
+ $server = stream_socket_server($serverUri, $errno, $errstr, $serverFlags, $serverCtx);
+ phpt_notify();
+
+ @stream_socket_accept($server, 1);
+ @stream_socket_accept($server, 1);
+ @stream_socket_accept($server, 1);
+ @stream_socket_accept($server, 1);
+CODE;
+
+$clientCode = <<<'CODE'
+ $serverUri = "ssl://127.0.0.1:64321";
+ $clientFlags = STREAM_CLIENT_CONNECT;
+ $clientCtx = stream_context_create(['ssl' => [
+ 'verify_peer' => true,
+ 'cafile' => __DIR__ . '/bug54992-ca.pem',
+ 'peer_name' => 'bug54992.local',
+ ]]);
+
+ phpt_wait();
+
+ stream_context_set_option($clientCtx, 'ssl', 'crypto_method', STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT);
+ var_dump(stream_socket_client($serverUri, $errno, $errstr, 1, $clientFlags, $clientCtx));
+
+ stream_context_set_option($clientCtx, 'ssl', 'crypto_method', STREAM_CRYPTO_METHOD_SSLv3_CLIENT);
+ var_dump(stream_socket_client($serverUri, $errno, $errstr, 1, $clientFlags, $clientCtx));
+
+ stream_context_set_option($clientCtx, 'ssl', 'crypto_method', STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT);
+ var_dump(@stream_socket_client($serverUri, $errno, $errstr, 1, $clientFlags, $clientCtx));
+
+ stream_context_set_option($clientCtx, 'ssl', 'crypto_method', STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT);
+ var_dump(@stream_socket_client($serverUri, $errno, $errstr, 1, $clientFlags, $clientCtx));
+CODE;
+
+include 'ServerClientTestCase.inc';
+ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
+--EXPECTF--
+resource(%d) of type (stream)
+resource(%d) of type (stream)
+bool(false)
+bool(false)
+
diff --git a/ext/openssl/tests/stream_crypto_flags_004.phpt b/ext/openssl/tests/stream_crypto_flags_004.phpt
new file mode 100644
index 0000000000..e51a2bab3e
--- /dev/null
+++ b/ext/openssl/tests/stream_crypto_flags_004.phpt
@@ -0,0 +1,60 @@
+--TEST--
+Specific protocol method specification
+--SKIPIF--
+<?php
+if (!extension_loaded("openssl")) die("skip openssl not loaded");
+if (!function_exists("proc_open")) die("skip no proc_open");
+--FILE--
+<?php
+$serverCode = <<<'CODE'
+ $serverUri = "ssl://127.0.0.1:64321";
+ $serverFlags = STREAM_SERVER_BIND | STREAM_SERVER_LISTEN;
+ $serverCtx = stream_context_create(['ssl' => [
+ 'local_cert' => __DIR__ . '/bug54992.pem',
+ 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_0_SERVER,
+ ]]);
+
+ $server = stream_socket_server($serverUri, $errno, $errstr, $serverFlags, $serverCtx);
+ phpt_notify();
+
+ @stream_socket_accept($server, 1);
+ @stream_socket_accept($server, 1);
+ @stream_socket_accept($server, 1);
+ @stream_socket_accept($server, 1);
+CODE;
+
+$clientCode = <<<'CODE'
+ $serverUri = "ssl://127.0.0.1:64321";
+ $clientFlags = STREAM_CLIENT_CONNECT;
+ $clientCtx = stream_context_create(['ssl' => [
+ 'verify_peer' => true,
+ 'cafile' => __DIR__ . '/bug54992-ca.pem',
+ 'peer_name' => 'bug54992.local',
+ ]]);
+
+ phpt_wait();
+
+ // Should succeed because the SSLv23 handshake here is compatible with the
+ // TLSv1 hello method employed in the server
+ var_dump(@stream_socket_client($serverUri, $errno, $errstr, 1, $clientFlags, $clientCtx));
+
+ // Should fail because the TLSv1.1 hello method is not supported
+ stream_context_set_option($clientCtx, 'ssl', 'crypto_method', STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT);
+ var_dump(@stream_socket_client($serverUri, $errno, $errstr, 1, $clientFlags, $clientCtx));
+
+ // Should fail because the TLSv1.2 hello method is not supported
+ stream_context_set_option($clientCtx, 'ssl', 'crypto_method', STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT);
+ var_dump(@stream_socket_client($serverUri, $errno, $errstr, 1, $clientFlags, $clientCtx));
+
+ // Should succeed because we use the same TLSv1 hello
+ stream_context_set_option($clientCtx, 'ssl', 'crypto_method', STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT);
+ var_dump(stream_socket_client($serverUri, $errno, $errstr, 1, $clientFlags, $clientCtx));
+CODE;
+
+include 'ServerClientTestCase.inc';
+ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
+--EXPECTF--
+resource(%d) of type (stream)
+bool(false)
+bool(false)
+resource(%d) of type (stream)
diff --git a/ext/openssl/tests/stream_server_reneg_limit.phpt b/ext/openssl/tests/stream_server_reneg_limit.phpt
new file mode 100644
index 0000000000..3abaa48e41
--- /dev/null
+++ b/ext/openssl/tests/stream_server_reneg_limit.phpt
@@ -0,0 +1,85 @@
+--TEST--
+TLS server rate-limits client-initiated renegotiation
+--SKIPIF--
+<?php
+if (!extension_loaded("openssl")) die("skip openssl not loaded");
+if (!function_exists("proc_open")) die("skip no proc_open");
+exec('openssl help', $out, $code);
+if ($code > 0) die("skip couldn't locate openssl binary");
+--FILE--
+<?php
+
+/**
+ * This test uses the openssl binary directly to initiate renegotiation. At this time it's not
+ * possible renegotiate the TLS handshake in PHP userland, so using the openssl s_client binary
+ * command is the only feasible way to test renegotiation limiting functionality. It's not an ideal
+ * solution, but it's really the only way to get test coverage on the rate-limiting functionality
+ * given current limitations.
+ */
+
+$serverCode = <<<'CODE'
+ $serverUri = "ssl://127.0.0.1:64321";
+ $serverFlags = STREAM_SERVER_BIND | STREAM_SERVER_LISTEN;
+ $serverCtx = stream_context_create(['ssl' => [
+ 'local_cert' => __DIR__ . '/bug54992.pem',
+ 'reneg_limit' => 0,
+ 'reneg_window' => 30,
+ 'reneg_limit_callback' => function($stream) {
+ var_dump($stream);
+ }
+ ]]);
+
+ $server = stream_socket_server($serverUri, $errno, $errstr, $serverFlags, $serverCtx);
+ phpt_notify();
+
+ $clients = [];
+ while (1) {
+ $r = array_merge([$server], $clients);
+ $w = $e = [];
+
+ stream_select($r, $w, $e, $timeout=42);
+
+ foreach ($r as $sock) {
+ if ($sock === $server && ($client = stream_socket_accept($server, $timeout = 42))) {
+ $clientId = (int) $client;
+ $clients[$clientId] = $client;
+ } elseif ($sock !== $server) {
+ $clientId = (int) $sock;
+ $buffer = fread($sock, 1024);
+ if (strlen($buffer)) {
+ continue;
+ } elseif (!is_resource($sock) || feof($sock)) {
+ unset($clients[$clientId]);
+ break 2;
+ }
+ }
+ }
+ }
+CODE;
+
+$clientCode = <<<'CODE'
+ $cmd = 'openssl s_client -connect 127.0.0.1:64321';
+ $descriptorSpec = [["pipe", "r"], ["pipe", "w"], ["pipe", "w"]];
+ $process = proc_open($cmd, $descriptorSpec, $pipes);
+
+ list($stdin, $stdout, $stderr) = $pipes;
+
+ // Trigger renegotiation twice
+ // Server settings only allow one per second (should result in disconnection)
+ fwrite($stdin, "R\nR\nR\nR\n");
+
+ $lines = [];
+ while(!feof($stderr)) {
+ fgets($stderr);
+ }
+
+ fclose($stdin);
+ fclose($stdout);
+ fclose($stderr);
+ proc_terminate($process);
+CODE;
+
+include 'ServerClientTestCase.inc';
+ServerClientTestCase::getInstance()->run($serverCode, $clientCode);
+--EXPECTF--
+resource(%d) of type (stream)
diff --git a/ext/openssl/tests/stream_verify_peer_name_001.phpt b/ext/openssl/tests/stream_verify_peer_name_001.phpt
new file mode 100644
index 0000000000..4aecf8c744
--- /dev/null
+++ b/ext/openssl/tests/stream_verify_peer_name_001.phpt
@@ -0,0 +1,39 @@
+--TEST--
+Verify host name by default in client transfers
+--SKIPIF--
+<?php
+if (!extension_loaded("openssl")) die("skip openssl not loaded");
+if (!function_exists("proc_open")) die("skip no proc_open");
+--FILE--
+<?php
+$serverCode = <<<'CODE'
+ $serverUri = "ssl://127.0.0.1:64321";
+ $serverFlags = STREAM_SERVER_BIND | STREAM_SERVER_LISTEN;
+ $serverCtx = stream_context_create(['ssl' => [
+ 'local_cert' => __DIR__ . '/bug54992.pem'
+ ]]);
+
+ $server = stream_socket_server($serverUri, $errno, $errstr, $serverFlags, $serverCtx);
+ phpt_notify();
+
+ @stream_socket_accept($server, 1);
+CODE;
+
+$clientCode = <<<'CODE'
+ $serverUri = "ssl://127.0.0.1:64321";
+ $clientFlags = STREAM_CLIENT_CONNECT;
+ $clientCtx = stream_context_create(['ssl' => [
+ 'verify_peer' => false,
+ 'peer_name' => 'bug54992.local'
+ ]]);
+
+ phpt_wait();
+ $client = stream_socket_client($serverUri, $errno, $errstr, 1, $clientFlags, $clientCtx);
+
+ var_dump($client);
+CODE;
+
+include 'ServerClientTestCase.inc';
+ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
+--EXPECTF--
+resource(%d) of type (stream)
diff --git a/ext/openssl/tests/stream_verify_peer_name_002.phpt b/ext/openssl/tests/stream_verify_peer_name_002.phpt
new file mode 100644
index 0000000000..cfee8b1c07
--- /dev/null
+++ b/ext/openssl/tests/stream_verify_peer_name_002.phpt
@@ -0,0 +1,40 @@
+--TEST--
+Allow host name mismatch when "verify_host" disabled
+--SKIPIF--
+<?php
+if (!extension_loaded("openssl")) die("skip openssl not loaded");
+if (!function_exists("proc_open")) die("skip no proc_open");
+--FILE--
+<?php
+$serverCode = <<<'CODE'
+ $serverUri = "ssl://127.0.0.1:64321";
+ $serverFlags = STREAM_SERVER_BIND | STREAM_SERVER_LISTEN;
+ $serverCtx = stream_context_create(['ssl' => [
+ 'local_cert' => __DIR__ . '/bug54992.pem'
+ ]]);
+
+ $server = stream_socket_server($serverUri, $errno, $errstr, $serverFlags, $serverCtx);
+ phpt_notify();
+
+ @stream_socket_accept($server, 1);
+CODE;
+
+$clientCode = <<<'CODE'
+ $serverUri = "ssl://127.0.0.1:64321";
+ $clientFlags = STREAM_CLIENT_CONNECT;
+ $clientCtx = stream_context_create(['ssl' => [
+ 'verify_peer' => true,
+ 'cafile' => __DIR__ . '/bug54992-ca.pem',
+ 'verify_peer_name' => false
+ ]]);
+
+ phpt_wait();
+ $client = stream_socket_client($serverUri, $errno, $errstr, 2, $clientFlags, $clientCtx);
+
+ var_dump($client);
+CODE;
+
+include 'ServerClientTestCase.inc';
+ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
+--EXPECTF--
+resource(%d) of type (stream)
diff --git a/ext/openssl/tests/stream_verify_peer_name_003.phpt b/ext/openssl/tests/stream_verify_peer_name_003.phpt
new file mode 100644
index 0000000000..e4e083f7f6
--- /dev/null
+++ b/ext/openssl/tests/stream_verify_peer_name_003.phpt
@@ -0,0 +1,44 @@
+--TEST--
+Host name mismatch triggers error
+--SKIPIF--
+<?php
+if (!extension_loaded("openssl")) die("skip openssl not loaded");
+if (!function_exists("proc_open")) die("skip no proc_open");
+--FILE--
+<?php
+$serverCode = <<<'CODE'
+ $serverUri = "ssl://127.0.0.1:64321";
+ $serverFlags = STREAM_SERVER_BIND | STREAM_SERVER_LISTEN;
+ $serverCtx = stream_context_create(['ssl' => [
+ 'local_cert' => __DIR__ . '/bug54992.pem'
+ ]]);
+
+ $server = stream_socket_server($serverUri, $errno, $errstr, $serverFlags, $serverCtx);
+ phpt_notify();
+
+ @stream_socket_accept($server, 1);
+CODE;
+
+$clientCode = <<<'CODE'
+ $serverUri = "ssl://127.0.0.1:64321";
+ $clientFlags = STREAM_CLIENT_CONNECT;
+ $clientCtx = stream_context_create(['ssl' => [
+ 'verify_peer' => true,
+ 'cafile' => __DIR__ . '/bug54992-ca.pem'
+ ]]);
+
+ phpt_wait();
+ $client = stream_socket_client($serverUri, $errno, $errstr, 1, $clientFlags, $clientCtx);
+
+ var_dump($client);
+CODE;
+
+include 'ServerClientTestCase.inc';
+ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
+--EXPECTF--
+Warning: stream_socket_client(): Peer certificate CN=`bug54992.local' did not match expected CN=`127.0.0.1' in %s on line %d
+
+Warning: stream_socket_client(): Failed to enable crypto in %s on line %d
+
+Warning: stream_socket_client(): unable to connect to ssl://127.0.0.1:64321 (Unknown error) in %s on line %d
+bool(false)
diff --git a/ext/openssl/tests/streams_crypto_method.pem b/ext/openssl/tests/streams_crypto_method.pem
new file mode 100644
index 0000000000..9d754d460d
--- /dev/null
+++ b/ext/openssl/tests/streams_crypto_method.pem
@@ -0,0 +1,33 @@
+-----BEGIN CERTIFICATE-----
+MIIC5jCCAk+gAwIBAgIBADANBgkqhkiG9w0BAQQFADBcMQswCQYDVQQGEwJBVTET
+MBEGA1UECBMKUXVlZW5zbGFuZDEaMBgGA1UEChMRQ3J5cHRTb2Z0IFB0eSBMdGQx
+HDAaBgNVBAMTE1Rlc3QgUENBICgxMDI0IGJpdCkwHhcNOTkxMjAyMjEzNTQ4WhcN
+MDUwNzExMjEzNTQ4WjBcMQswCQYDVQQGEwJBVTETMBEGA1UECBMKUXVlZW5zbGFu
+ZDEaMBgGA1UEChMRQ3J5cHRTb2Z0IFB0eSBMdGQxHDAaBgNVBAMTE1Rlc3QgUENB
+ICgxMDI0IGJpdCkwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAJ2haT/f5Zwy
+V+MiuSDjSR62adBoSiBB7Usty44lXqsp9RICw+DCCxpsn/CfxPEDXLLd4olsWXc6
+JRcxGynbYmnzk+Z6aIPPJQhK3CTvaqGnWKZsA1m+WaUIUqJCuNTK4N+7hMAGaf6S
+S3e9HVgEQ4a34gXJ7VQFVIBNV1EnZRWHAgMBAAGjgbcwgbQwHQYDVR0OBBYEFE0R
+aEcrj18q1dw+G6nJbsTWR213MIGEBgNVHSMEfTB7gBRNEWhHK49fKtXcPhupyW7E
+1kdtd6FgpF4wXDELMAkGA1UEBhMCQVUxEzARBgNVBAgTClF1ZWVuc2xhbmQxGjAY
+BgNVBAoTEUNyeXB0U29mdCBQdHkgTHRkMRwwGgYDVQQDExNUZXN0IFBDQSAoMTAy
+NCBiaXQpggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAUa8B3pho
++Mvxeq9HsEzJxHIFQla05S5J/e/V+DQTYoKiRFchKPrDAdrzYSEvP3h4QJEtsNqQ
+JfOxg5M42uLFq7aPGWkF6ZZqZsYS+zA9IVT14g7gNA6Ne+5QtJqQtH9HA24st0T0
+Tga/lZ9M2ovImovaxSL/kRHbpCWcqWVxpOw=
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQCdoWk/3+WcMlfjIrkg40ketmnQaEogQe1LLcuOJV6rKfUSAsPg
+wgsabJ/wn8TxA1yy3eKJbFl3OiUXMRsp22Jp85PmemiDzyUIStwk72qhp1imbANZ
+vlmlCFKiQrjUyuDfu4TABmn+kkt3vR1YBEOGt+IFye1UBVSATVdRJ2UVhwIDAQAB
+AoGAba4fTtuap5l7/8ZsbE7Z1O32KJY4ZcOZukLOLUUhXxXduT+FTgGWujc0/rgc
+z9qYCLlNZHOouMYTgtSfYvuMuLZ11VIt0GYH+nRioLShE59Yy+zCRyC+gPigS1kz
+xvo14AsOIPYV14Tk/SsHyq6E0eTk7VzaIE197giiINUERPECQQDSKmtPTh/lRKw7
+HSZSM0I1mFWn/1zqrAbontRQY5w98QWIOe5qmzYyFbPXYT3d9BzlsMyhgiRNoBbD
+yvohSHXJAkEAwAHx6ezAZeWWzD5yXD36nyjpkVCw7Tk7TSmOceLJMWt1QcrCfqlS
+xA5jjpQ6Z8suU5DdtWAryM2sAir1WisYzwJAd6Zcx56jvAQ3xcPXsE6scBTVFzrj
+7FqZ6E+cclPzfLQ+QQsyOBE7bpI6e/FJppY26XGZXo3YGzV8IGXrt40oOQJALETG
+h86EFXo3qGOFbmsDy4pdP5nBERCu8X1xUCSfintiD4c2DInxgS5oGclnJeMcjTvL
+QjQoJCX3UJCi/OUO1QJBAKgcDHWjMvt+l1pjJBsSEZ0HX9AAIIVx0RQmbFGS+F2Q
+hhu5l77WnnZOQ9vvhV5u7NPCUF9nhU3jh60qWWO8mkc=
+-----END RSA PRIVATE KEY-----
diff --git a/ext/openssl/tests/streams_crypto_method.phpt b/ext/openssl/tests/streams_crypto_method.phpt
new file mode 100644
index 0000000000..84f7934308
--- /dev/null
+++ b/ext/openssl/tests/streams_crypto_method.phpt
@@ -0,0 +1,52 @@
+--TEST--
+Specific crypto method for ssl:// transports.
+--SKIPIF--
+<?php
+if (!extension_loaded("openssl")) die("skip openssl not loaded");
+if (!function_exists("proc_open")) die("skip no proc_open");
+--FILE--
+<?php
+$serverCode = <<<'CODE'
+ $serverUri = "ssl://127.0.0.1:64321";
+ $serverFlags = STREAM_SERVER_BIND | STREAM_SERVER_LISTEN;
+ $serverCtx = stream_context_create(['ssl' => [
+ 'local_cert' => __DIR__ . '/streams_crypto_method.pem',
+ ]]);
+
+ $server = stream_socket_server($serverUri, $errno, $errstr, $serverFlags, $serverCtx);
+ phpt_notify();
+
+ $client = @stream_socket_accept($server);
+ if ($client) {
+ $in = '';
+ while (!preg_match('/\r?\n\r?\n/', $in)) {
+ $in .= fread($client, 2048);
+ }
+ $response = "HTTP/1.0 200 OK\r\n"
+ . "Content-Type: text/plain\r\n"
+ . "Content-Length: 12\r\n"
+ . "Connection: close\r\n"
+ . "\r\n"
+ . "Hello World!";
+ fwrite($client, $response);
+ fclose($client);
+ }
+CODE;
+
+$clientCode = <<<'CODE'
+ $serverUri = "https://127.0.0.1:64321/";
+ $clientFlags = STREAM_CLIENT_CONNECT;
+ $clientCtx = stream_context_create(['ssl' => [
+ 'crypto_method' => STREAM_CRYPTO_METHOD_SSLv3_CLIENT,
+ 'verify_peer' => false,
+ 'verify_peer_name' => false
+ ]]);
+
+ phpt_wait();
+ echo file_get_contents($serverUri, false, $clientCtx);
+CODE;
+
+include 'ServerClientTestCase.inc';
+ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
+--EXPECTF--
+Hello World!
diff --git a/ext/openssl/tests/tlsv1.0_wrapper.phpt b/ext/openssl/tests/tlsv1.0_wrapper.phpt
new file mode 100644
index 0000000000..c0477dfe32
--- /dev/null
+++ b/ext/openssl/tests/tlsv1.0_wrapper.phpt
@@ -0,0 +1,47 @@
+--TEST--
+tlsv1.0 stream wrapper
+--SKIPIF--
+<?php
+if (!extension_loaded("openssl")) die("skip openssl not loaded");
+if (!function_exists("proc_open")) die("skip no proc_open");
+--FILE--
+<?php
+$serverCode = <<<'CODE'
+ $flags = STREAM_SERVER_BIND|STREAM_SERVER_LISTEN;
+ $ctx = stream_context_create(['ssl' => [
+ 'local_cert' => __DIR__ . '/streams_crypto_method.pem',
+ ]]);
+
+ $server = stream_socket_server('tlsv1.0://127.0.0.1:64321', $errno, $errstr, $flags, $ctx);
+ phpt_notify();
+
+ for ($i=0; $i < 3; $i++) {
+ @stream_socket_accept($server, 3);
+ }
+CODE;
+
+$clientCode = <<<'CODE'
+ $flags = STREAM_CLIENT_CONNECT;
+ $ctx = stream_context_create(['ssl' => [
+ 'verify_peer' => false,
+ 'verify_peer_name' => false,
+ ]]);
+
+ phpt_wait();
+
+ $client = stream_socket_client("tlsv1.0://127.0.0.1:64321", $errno, $errstr, 3, $flags, $ctx);
+ var_dump($client);
+
+ $client = @stream_socket_client("sslv3://127.0.0.1:64321", $errno, $errstr, 3, $flags, $ctx);
+ var_dump($client);
+
+ $client = @stream_socket_client("tlsv1.2://127.0.0.1:64321", $errno, $errstr, 3, $flags, $ctx);
+ var_dump($client);
+CODE;
+
+include 'ServerClientTestCase.inc';
+ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
+--EXPECTF--
+resource(%d) of type (stream)
+bool(false)
+bool(false)
diff --git a/ext/openssl/tests/tlsv1.1_wrapper.phpt b/ext/openssl/tests/tlsv1.1_wrapper.phpt
new file mode 100644
index 0000000000..a5dba299ec
--- /dev/null
+++ b/ext/openssl/tests/tlsv1.1_wrapper.phpt
@@ -0,0 +1,48 @@
+--TEST--
+tlsv1.1 stream wrapper
+--SKIPIF--
+<?php
+if (!extension_loaded("openssl")) die("skip openssl not loaded");
+if (!function_exists("proc_open")) die("skip no proc_open");
+if (OPENSSL_VERSION_NUMBER < 0x10001001) die("skip OpenSSL 1.0.1 required");
+--FILE--
+<?php
+$serverCode = <<<'CODE'
+ $flags = STREAM_SERVER_BIND|STREAM_SERVER_LISTEN;
+ $ctx = stream_context_create(['ssl' => [
+ 'local_cert' => __DIR__ . '/streams_crypto_method.pem',
+ ]]);
+
+ $server = stream_socket_server('tlsv1.1://127.0.0.1:64321', $errno, $errstr, $flags, $ctx);
+ phpt_notify();
+
+ for ($i=0; $i < 3; $i++) {
+ @stream_socket_accept($server, 3);
+ }
+CODE;
+
+$clientCode = <<<'CODE'
+ $flags = STREAM_CLIENT_CONNECT;
+ $ctx = stream_context_create(['ssl' => [
+ 'verify_peer' => false,
+ 'verify_peer_name' => false,
+ ]]);
+
+ phpt_wait();
+
+ $client = stream_socket_client("tlsv1.1://127.0.0.1:64321", $errno, $errstr, 3, $flags, $ctx);
+ var_dump($client);
+
+ $client = @stream_socket_client("sslv3://127.0.0.1:64321", $errno, $errstr, 3, $flags, $ctx);
+ var_dump($client);
+
+ $client = @stream_socket_client("tlsv1.2://127.0.0.1:64321", $errno, $errstr, 3, $flags, $ctx);
+ var_dump($client);
+CODE;
+
+include 'ServerClientTestCase.inc';
+ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
+--EXPECTF--
+resource(%d) of type (stream)
+bool(false)
+bool(false)
diff --git a/ext/openssl/tests/tlsv1.2_wrapper.phpt b/ext/openssl/tests/tlsv1.2_wrapper.phpt
new file mode 100644
index 0000000000..42980cf661
--- /dev/null
+++ b/ext/openssl/tests/tlsv1.2_wrapper.phpt
@@ -0,0 +1,48 @@
+--TEST--
+tlsv1.2 stream wrapper
+--SKIPIF--
+<?php
+if (!extension_loaded("openssl")) die("skip openssl not loaded");
+if (!function_exists("proc_open")) die("skip no proc_open");
+if (OPENSSL_VERSION_NUMBER < 0x10001001) die("skip OpenSSL 1.0.1 required");
+--FILE--
+<?php
+$serverCode = <<<'CODE'
+ $flags = STREAM_SERVER_BIND|STREAM_SERVER_LISTEN;
+ $ctx = stream_context_create(['ssl' => [
+ 'local_cert' => __DIR__ . '/streams_crypto_method.pem',
+ ]]);
+
+ $server = stream_socket_server('tlsv1.2://127.0.0.1:64321', $errno, $errstr, $flags, $ctx);
+ phpt_notify();
+
+ for ($i=0; $i < 3; $i++) {
+ @stream_socket_accept($server, 3);
+ }
+CODE;
+
+$clientCode = <<<'CODE'
+ $flags = STREAM_CLIENT_CONNECT;
+ $ctx = stream_context_create(['ssl' => [
+ 'verify_peer' => false,
+ 'verify_peer_name' => false,
+ ]]);
+
+ phpt_wait();
+
+ $client = stream_socket_client("tlsv1.2://127.0.0.1:64321", $errno, $errstr, 3, $flags, $ctx);
+ var_dump($client);
+
+ $client = @stream_socket_client("sslv3://127.0.0.1:64321", $errno, $errstr, 3, $flags, $ctx);
+ var_dump($client);
+
+ $client = @stream_socket_client("tlsv1.1://127.0.0.1:64321", $errno, $errstr, 3, $flags, $ctx);
+ var_dump($client);
+CODE;
+
+include 'ServerClientTestCase.inc';
+ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
+--EXPECTF--
+resource(%d) of type (stream)
+bool(false)
+bool(false)
diff --git a/ext/openssl/xp_ssl.c b/ext/openssl/xp_ssl.c
index 6f41c217d6..c6a91570cf 100644
--- a/ext/openssl/xp_ssl.c
+++ b/ext/openssl/xp_ssl.c
@@ -12,40 +12,94 @@
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
- | Author: Wez Furlong <wez@thebrainroom.com> |
+ | Authors: Wez Furlong <wez@thebrainroom.com> |
+ | Daniel Lowrey <rdlowrey@php.net> |
+ | Chris Wright <daverandom@php.net> |
+----------------------------------------------------------------------+
*/
/* $Id$ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
#include "php.h"
#include "ext/standard/file.h"
#include "ext/standard/url.h"
#include "streams/php_streams_int.h"
#include "ext/standard/php_smart_str.h"
-#include "php_network.h"
#include "php_openssl.h"
+#include "php_network.h"
#include <openssl/ssl.h>
#include <openssl/x509.h>
+#include <openssl/x509v3.h>
#include <openssl/err.h>
#ifdef PHP_WIN32
+#include "win32/winutil.h"
#include "win32/time.h"
+#include <Wincrypt.h>
+/* These are from Wincrypt.h, they conflict with OpenSSL */
+#undef X509_NAME
+#undef X509_CERT_PAIR
+#undef X509_EXTENSIONS
#endif
#ifdef NETWARE
#include <sys/select.h>
#endif
-int php_openssl_apply_verification_policy(SSL *ssl, X509 *peer, php_stream *stream TSRMLS_DC);
-SSL *php_SSL_new_from_context(SSL_CTX *ctx, php_stream *stream TSRMLS_DC);
-int php_openssl_get_x509_list_id(void);
+#if !defined(OPENSSL_NO_ECDH) && OPENSSL_VERSION_NUMBER >= 0x0090800fL
+#define HAVE_ECDH 1
+#endif
+
+#if OPENSSL_VERSION_NUMBER >= 0x00908070L && !defined(OPENSSL_NO_TLSEXT)
+#define HAVE_SNI 1
+#endif
+
+/* Flags for determining allowed stream crypto methods */
+#define STREAM_CRYPTO_IS_CLIENT (1<<0)
+#define STREAM_CRYPTO_METHOD_SSLv2 (1<<1)
+#define STREAM_CRYPTO_METHOD_SSLv3 (1<<2)
+#define STREAM_CRYPTO_METHOD_TLSv1_0 (1<<3)
+#define STREAM_CRYPTO_METHOD_TLSv1_1 (1<<4)
+#define STREAM_CRYPTO_METHOD_TLSv1_2 (1<<5)
+
+/* Simplify ssl context option retrieval */
+#define GET_VER_OPT(name) (stream->context && SUCCESS == php_stream_context_get_option(stream->context, "ssl", name, &val))
+#define GET_VER_OPT_STRING(name, str) if (GET_VER_OPT(name)) { convert_to_string_ex(val); str = Z_STRVAL_PP(val); }
+#define GET_VER_OPT_LONG(name, num) if (GET_VER_OPT(name)) { convert_to_long_ex(val); num = Z_LVAL_PP(val); }
+
+/* Used for peer verification in windows */
+#define PHP_X509_NAME_ENTRY_TO_UTF8(ne, i, out) ASN1_STRING_to_UTF8(&out, X509_NAME_ENTRY_get_data(X509_NAME_get_entry(ne, i)))
+
+extern php_stream* php_openssl_get_stream_from_ssl_handle(const SSL *ssl);
+extern zend_bool php_x509_fingerprint_match(X509 *peer, zval *val TSRMLS_DC);
+extern int php_openssl_get_ssl_stream_data_index();
+extern int php_openssl_get_x509_list_id(void);
+
+php_stream_ops php_openssl_socket_ops;
+
+/* Certificate contexts used for server-side SNI selection */
+typedef struct _php_openssl_sni_cert_t {
+ char *name;
+ SSL_CTX *ctx;
+} php_openssl_sni_cert_t;
+
+/* Provides leaky bucket handhsake renegotiation rate-limiting */
+typedef struct _php_openssl_handshake_bucket_t {
+ long prev_handshake;
+ long limit;
+ long window;
+ float tokens;
+ unsigned should_close;
+} php_openssl_handshake_bucket_t;
/* This implementation is very closely tied to the that of the native
* sockets implemented in the core.
* Don't try this technique in other extensions!
* */
-
typedef struct _php_openssl_netstream_data_t {
php_netstream_data_t s;
SSL *ssl_handle;
@@ -55,16 +109,17 @@ typedef struct _php_openssl_netstream_data_t {
int is_client;
int ssl_active;
php_stream_xport_crypt_method_t method;
- char *sni;
+ php_openssl_handshake_bucket_t *reneg;
+ php_openssl_sni_cert_t *sni_certs;
+ unsigned sni_cert_count;
+ char *url_name;
unsigned state_set:1;
unsigned _spare:31;
} php_openssl_netstream_data_t;
-php_stream_ops php_openssl_socket_ops;
-
/* it doesn't matter that we do some hash traversal here, since it is done only
* in an error condition arising from a network connection problem */
-static int is_http_stream_talking_to_iis(php_stream *stream TSRMLS_DC)
+static int is_http_stream_talking_to_iis(php_stream *stream TSRMLS_DC) /* {{{ */
{
if (stream->wrapperdata && stream->wrapper && strcasecmp(stream->wrapper->wops->label, "HTTP") == 0) {
/* the wrapperdata is an array zval containing the headers */
@@ -87,8 +142,9 @@ static int is_http_stream_talking_to_iis(php_stream *stream TSRMLS_DC)
}
return 0;
}
+/* }}} */
-static int handle_ssl_error(php_stream *stream, int nr_bytes, zend_bool is_init TSRMLS_DC)
+static int handle_ssl_error(php_stream *stream, int nr_bytes, zend_bool is_init TSRMLS_DC) /* {{{ */
{
php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract;
int err = SSL_get_error(sslsock->ssl_handle, nr_bytes);
@@ -170,255 +226,1245 @@ static int handle_ssl_error(php_stream *stream, int nr_bytes, zend_bool is_init
}
return retry;
}
+/* }}} */
+
+static int verify_callback(int preverify_ok, X509_STORE_CTX *ctx) /* {{{ */
+{
+ php_stream *stream;
+ SSL *ssl;
+ int err, depth, ret;
+ zval **val;
+ unsigned long allowed_depth = OPENSSL_DEFAULT_STREAM_VERIFY_DEPTH;
+
+ ret = preverify_ok;
+
+ /* determine the status for the current cert */
+ err = X509_STORE_CTX_get_error(ctx);
+ depth = X509_STORE_CTX_get_error_depth(ctx);
+
+ /* conjure the stream & context to use */
+ ssl = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
+ stream = (php_stream*)SSL_get_ex_data(ssl, php_openssl_get_ssl_stream_data_index());
+
+ /* if allow_self_signed is set, make sure that verification succeeds */
+ if (err == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT &&
+ GET_VER_OPT("allow_self_signed") &&
+ zend_is_true(*val)
+ ) {
+ ret = 1;
+ }
+
+ /* check the depth */
+ GET_VER_OPT_LONG("verify_depth", allowed_depth);
+ if ((unsigned long)depth > allowed_depth) {
+ ret = 0;
+ X509_STORE_CTX_set_error(ctx, X509_V_ERR_CERT_CHAIN_TOO_LONG);
+ }
+
+ return ret;
+}
+/* }}} */
+
+static zend_bool matches_wildcard_name(const char *subjectname, const char *certname) /* {{{ */
+{
+ char *wildcard = NULL;
+ int prefix_len, suffix_len, subject_len;
+
+ if (strcasecmp(subjectname, certname) == 0) {
+ return 1;
+ }
+
+ if (!(wildcard = strchr(certname, '*'))) {
+ return 0;
+ }
+
+ // 1) prefix, if not empty, must match subject
+ prefix_len = wildcard - certname;
+ if (prefix_len && strncasecmp(subjectname, certname, prefix_len) != 0) {
+ return 0;
+ }
+
+ suffix_len = strlen(wildcard + 1);
+ subject_len = strlen(subjectname);
+ if (suffix_len <= subject_len) {
+ /* 2) suffix must match
+ * 3) no . between prefix and suffix
+ **/
+ return strcasecmp(wildcard + 1, subjectname + subject_len - suffix_len) == 0 &&
+ memchr(subjectname + prefix_len, '.', subject_len - suffix_len - prefix_len) == NULL;
+ }
+
+ return 0;
+}
+/* }}} */
+
+static zend_bool matches_san_list(X509 *peer, const char *subject_name TSRMLS_DC) /* {{{ */
+{
+ int i, san_name_len;
+ zend_bool is_match = 0;
+ unsigned char *cert_name = NULL;
+
+ GENERAL_NAMES *alt_names = X509_get_ext_d2i(peer, NID_subject_alt_name, 0, 0);
+ int alt_name_count = sk_GENERAL_NAME_num(alt_names);
+
+ for (i = 0; i < alt_name_count; i++) {
+ GENERAL_NAME *san = sk_GENERAL_NAME_value(alt_names, i);
+ if (san->type != GEN_DNS) {
+ /* we only care about DNS names */
+ continue;
+ }
+
+ san_name_len = ASN1_STRING_length(san->d.dNSName);
+ ASN1_STRING_to_UTF8(&cert_name, san->d.dNSName);
+
+ /* prevent null byte poisoning */
+ if (san_name_len != strlen((const char*)cert_name)) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Peer SAN entry is malformed");
+ } else {
+ is_match = strcasecmp(subject_name, (const char*)cert_name) == 0;
+ }
+
+ OPENSSL_free(cert_name);
+
+ if (is_match) {
+ break;
+ }
+ }
+
+ return is_match;
+}
+/* }}} */
+
+static zend_bool matches_common_name(X509 *peer, const char *subject_name TSRMLS_DC) /* {{{ */
+{
+ char buf[1024];
+ X509_NAME *cert_name;
+ zend_bool is_match = 0;
+ 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));
+
+ if (cert_name_len == -1) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to locate peer certificate CN");
+ } else if (cert_name_len != strlen(buf)) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Peer certificate CN=`%.*s' is malformed", cert_name_len, buf);
+ } else if (matches_wildcard_name(subject_name, buf)) {
+ is_match = 1;
+ } else {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Peer certificate CN=`%.*s' did not match expected CN=`%s'", cert_name_len, buf, subject_name);
+ }
+ return is_match;
+}
+/* }}} */
-static size_t php_openssl_sockop_write(php_stream *stream, const char *buf, size_t count TSRMLS_DC)
+static int apply_peer_verification_policy(SSL *ssl, X509 *peer, php_stream *stream TSRMLS_DC) /* {{{ */
{
+ zval **val = NULL;
+ char *peer_name = NULL;
+ int err,
+ must_verify_peer,
+ must_verify_peer_name,
+ must_verify_fingerprint,
+ has_cnmatch_ctx_opt;
+
php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract;
- int didwrite;
-
- if (sslsock->ssl_active) {
- int retry = 1;
- do {
- didwrite = SSL_write(sslsock->ssl_handle, buf, count);
+ must_verify_peer = GET_VER_OPT("verify_peer")
+ ? zend_is_true(*val)
+ : sslsock->is_client;
- if (didwrite <= 0) {
- retry = handle_ssl_error(stream, didwrite, 0 TSRMLS_CC);
- } else {
+ has_cnmatch_ctx_opt = GET_VER_OPT("CN_match");
+ must_verify_peer_name = (has_cnmatch_ctx_opt || GET_VER_OPT("verify_peer_name"))
+ ? zend_is_true(*val)
+ : sslsock->is_client;
+
+ must_verify_fingerprint = (GET_VER_OPT("peer_fingerprint") && zend_is_true(*val));
+
+ if ((must_verify_peer || must_verify_peer_name || must_verify_fingerprint) && peer == NULL) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not get peer certificate");
+ return FAILURE;
+ }
+
+ /* Verify the peer against using CA file/path settings */
+ if (must_verify_peer) {
+ err = SSL_get_verify_result(ssl);
+ switch (err) {
+ case X509_V_OK:
+ /* fine */
break;
+ case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
+ if (GET_VER_OPT("allow_self_signed") && zend_is_true(*val)) {
+ /* allowed */
+ break;
+ }
+ /* not allowed, so fall through */
+ default:
+ php_error_docref(NULL TSRMLS_CC, E_WARNING,
+ "Could not verify peer: code:%d %s",
+ err,
+ X509_verify_cert_error_string(err)
+ );
+ return FAILURE;
+ }
+ }
+
+ /* If a peer_fingerprint match is required this trumps peer and peer_name verification */
+ if (must_verify_fingerprint) {
+ if (Z_TYPE_PP(val) == IS_STRING || Z_TYPE_PP(val) == IS_ARRAY) {
+ if (!php_x509_fingerprint_match(peer, *val TSRMLS_CC)) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING,
+ "Peer fingerprint doesn't match"
+ );
+ return FAILURE;
}
- } while(retry);
+ } else {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING,
+ "Expected peer fingerprint must be a string or an array"
+ );
+ }
+ }
- if (didwrite > 0) {
- php_stream_notify_progress_increment(stream->context, didwrite, 0);
+ /* verify the host name presented in the peer certificate */
+ if (must_verify_peer_name) {
+ GET_VER_OPT_STRING("peer_name", peer_name);
+
+ if (has_cnmatch_ctx_opt) {
+ GET_VER_OPT_STRING("CN_match", peer_name);
+ php_error(E_DEPRECATED,
+ "the 'CN_match' SSL context option is deprecated in favor of 'peer_name'"
+ );
+ }
+ /* If no peer name was specified we use the autodetected url name in client environments */
+ if (peer_name == NULL && sslsock->is_client) {
+ peer_name = sslsock->url_name;
+ }
+
+ if (peer_name) {
+ if (matches_san_list(peer, peer_name TSRMLS_CC)) {
+ return SUCCESS;
+ } else if (matches_common_name(peer, peer_name TSRMLS_CC)) {
+ return SUCCESS;
+ } else {
+ return FAILURE;
+ }
+ } else {
+ return FAILURE;
}
- } else {
- didwrite = php_stream_socket_ops.write(stream, buf, count TSRMLS_CC);
}
- if (didwrite < 0) {
- didwrite = 0;
+ return SUCCESS;
+}
+/* }}} */
+
+static int passwd_callback(char *buf, int num, int verify, void *data) /* {{{ */
+{
+ php_stream *stream = (php_stream *)data;
+ zval **val = NULL;
+ char *passphrase = NULL;
+ /* TODO: could expand this to make a callback into PHP user-space */
+
+ GET_VER_OPT_STRING("passphrase", passphrase);
+
+ if (passphrase) {
+ if (Z_STRLEN_PP(val) < num - 1) {
+ memcpy(buf, Z_STRVAL_PP(val), Z_STRLEN_PP(val)+1);
+ return Z_STRLEN_PP(val);
+ }
}
-
- return didwrite;
+ return 0;
}
+/* }}} */
-static size_t php_openssl_sockop_read(php_stream *stream, char *buf, size_t count TSRMLS_DC)
+#if defined(PHP_WIN32) && OPENSSL_VERSION_NUMBER >= 0x00907000L
+#define RETURN_CERT_VERIFY_FAILURE(code) X509_STORE_CTX_set_error(x509_store_ctx, code); return 0;
+static int win_cert_verify_callback(X509_STORE_CTX *x509_store_ctx, void *arg) /* {{{ */
{
- php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract;
- int nr_bytes = 0;
+ PCCERT_CONTEXT cert_ctx = NULL;
+ PCCERT_CHAIN_CONTEXT cert_chain_ctx = NULL;
- if (sslsock->ssl_active) {
- int retry = 1;
+ php_stream *stream;
+ php_openssl_netstream_data_t *sslsock;
+ zval **val;
+ zend_bool is_self_signed = 0;
- do {
- nr_bytes = SSL_read(sslsock->ssl_handle, buf, count);
+ TSRMLS_FETCH();
- if (nr_bytes <= 0) {
- retry = handle_ssl_error(stream, nr_bytes, 0 TSRMLS_CC);
- stream->eof = (retry == 0 && errno != EAGAIN && !SSL_pending(sslsock->ssl_handle));
-
+ stream = (php_stream*)arg;
+ sslsock = (php_openssl_netstream_data_t*)stream->abstract;
+
+ { /* First convert the x509 struct back to a DER encoded buffer and let Windows decode it into a form it can work with */
+ unsigned char *der_buf = NULL;
+ int der_len;
+
+ der_len = i2d_X509(x509_store_ctx->cert, &der_buf);
+ if (der_len < 0) {
+ unsigned long err_code, e;
+ char err_buf[512];
+
+ while ((e = ERR_get_error()) != 0) {
+ err_code = e;
+ }
+
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error encoding X509 certificate: %d: %s", err_code, ERR_error_string(err_code, err_buf));
+ RETURN_CERT_VERIFY_FAILURE(SSL_R_CERTIFICATE_VERIFY_FAILED);
+ }
+
+ cert_ctx = CertCreateCertificateContext(X509_ASN_ENCODING, der_buf, der_len);
+ OPENSSL_free(der_buf);
+
+ if (cert_ctx == NULL) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error creating certificate context: %s", php_win_err());
+ RETURN_CERT_VERIFY_FAILURE(SSL_R_CERTIFICATE_VERIFY_FAILED);
+ }
+ }
+
+ { /* Next fetch the relevant cert chain from the store */
+ CERT_ENHKEY_USAGE enhkey_usage = {0};
+ CERT_USAGE_MATCH cert_usage = {0};
+ CERT_CHAIN_PARA chain_params = {sizeof(CERT_CHAIN_PARA)};
+ LPSTR usages[] = {szOID_PKIX_KP_SERVER_AUTH, szOID_SERVER_GATED_CRYPTO, szOID_SGC_NETSCAPE};
+ DWORD chain_flags = 0;
+ unsigned long allowed_depth = OPENSSL_DEFAULT_STREAM_VERIFY_DEPTH;
+ unsigned int i;
+
+ enhkey_usage.cUsageIdentifier = 3;
+ enhkey_usage.rgpszUsageIdentifier = usages;
+ cert_usage.dwType = USAGE_MATCH_TYPE_OR;
+ cert_usage.Usage = enhkey_usage;
+ chain_params.RequestedUsage = cert_usage;
+ chain_flags = CERT_CHAIN_CACHE_END_CERT | CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT;
+
+ if (!CertGetCertificateChain(NULL, cert_ctx, NULL, NULL, &chain_params, chain_flags, NULL, &cert_chain_ctx)) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error getting certificate chain: %s", php_win_err());
+ CertFreeCertificateContext(cert_ctx);
+ RETURN_CERT_VERIFY_FAILURE(SSL_R_CERTIFICATE_VERIFY_FAILED);
+ }
+
+ /* check if the cert is self-signed */
+ if (cert_chain_ctx->cChain > 0 && cert_chain_ctx->rgpChain[0]->cElement > 0
+ && (cert_chain_ctx->rgpChain[0]->rgpElement[0]->TrustStatus.dwInfoStatus & CERT_TRUST_IS_SELF_SIGNED) != 0) {
+ is_self_signed = 1;
+ }
+
+ /* check the depth */
+ GET_VER_OPT_LONG("verify_depth", allowed_depth);
+
+ for (i = 0; i < cert_chain_ctx->cChain; i++) {
+ if (cert_chain_ctx->rgpChain[i]->cElement > allowed_depth) {
+ CertFreeCertificateChain(cert_chain_ctx);
+ CertFreeCertificateContext(cert_ctx);
+ RETURN_CERT_VERIFY_FAILURE(X509_V_ERR_CERT_CHAIN_TOO_LONG);
+ }
+ }
+ }
+
+ { /* Then verify it against a policy */
+ SSL_EXTRA_CERT_CHAIN_POLICY_PARA ssl_policy_params = {sizeof(SSL_EXTRA_CERT_CHAIN_POLICY_PARA)};
+ CERT_CHAIN_POLICY_PARA chain_policy_params = {sizeof(CERT_CHAIN_POLICY_PARA)};
+ CERT_CHAIN_POLICY_STATUS chain_policy_status = {sizeof(CERT_CHAIN_POLICY_STATUS)};
+ LPWSTR server_name = NULL;
+ BOOL verify_result;
+
+ { /* This looks ridiculous and it is - but we validate the name ourselves using the peer_name
+ ctx option, so just use the CN from the cert here */
+
+ X509_NAME *cert_name;
+ unsigned char *cert_name_utf8;
+ int index, cert_name_utf8_len;
+ DWORD num_wchars;
+
+ cert_name = X509_get_subject_name(x509_store_ctx->cert);
+ index = X509_NAME_get_index_by_NID(cert_name, NID_commonName, -1);
+ if (index < 0) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to locate certificate CN");
+ CertFreeCertificateChain(cert_chain_ctx);
+ CertFreeCertificateContext(cert_ctx);
+ RETURN_CERT_VERIFY_FAILURE(SSL_R_CERTIFICATE_VERIFY_FAILED);
+ }
+
+ cert_name_utf8_len = PHP_X509_NAME_ENTRY_TO_UTF8(cert_name, index, cert_name_utf8);
+
+ num_wchars = MultiByteToWideChar(CP_UTF8, 0, (char*)cert_name_utf8, -1, NULL, 0);
+ if (num_wchars == 0) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to convert %s to wide character string", cert_name_utf8);
+ OPENSSL_free(cert_name_utf8);
+ CertFreeCertificateChain(cert_chain_ctx);
+ CertFreeCertificateContext(cert_ctx);
+ RETURN_CERT_VERIFY_FAILURE(SSL_R_CERTIFICATE_VERIFY_FAILED);
+ }
+
+ server_name = emalloc((num_wchars * sizeof(WCHAR)) + sizeof(WCHAR));
+
+ num_wchars = MultiByteToWideChar(CP_UTF8, 0, (char*)cert_name_utf8, -1, server_name, num_wchars);
+ if (num_wchars == 0) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to convert %s to wide character string", cert_name_utf8);
+ efree(server_name);
+ OPENSSL_free(cert_name_utf8);
+ CertFreeCertificateChain(cert_chain_ctx);
+ CertFreeCertificateContext(cert_ctx);
+ RETURN_CERT_VERIFY_FAILURE(SSL_R_CERTIFICATE_VERIFY_FAILED);
+ }
+
+ OPENSSL_free(cert_name_utf8);
+ }
+
+ ssl_policy_params.dwAuthType = (sslsock->is_client) ? AUTHTYPE_SERVER : AUTHTYPE_CLIENT;
+ ssl_policy_params.pwszServerName = server_name;
+ chain_policy_params.pvExtraPolicyPara = &ssl_policy_params;
+
+ verify_result = CertVerifyCertificateChainPolicy(CERT_CHAIN_POLICY_SSL, cert_chain_ctx, &chain_policy_params, &chain_policy_status);
+
+ efree(server_name);
+ CertFreeCertificateChain(cert_chain_ctx);
+ CertFreeCertificateContext(cert_ctx);
+
+ if (!verify_result) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error verifying certificate chain policy: %s", php_win_err());
+ RETURN_CERT_VERIFY_FAILURE(SSL_R_CERTIFICATE_VERIFY_FAILED);
+ }
+
+ if (chain_policy_status.dwError != 0) {
+ /* The chain does not match the policy */
+ if (is_self_signed && chain_policy_status.dwError == CERT_E_UNTRUSTEDROOT
+ && GET_VER_OPT("allow_self_signed") && zend_is_true(*val)) {
+ /* allow self-signed certs */
+ X509_STORE_CTX_set_error(x509_store_ctx, X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT);
} else {
- /* we got the data */
- break;
+ RETURN_CERT_VERIFY_FAILURE(SSL_R_CERTIFICATE_VERIFY_FAILED);
}
- } while (retry);
+ }
+ }
- if (nr_bytes > 0) {
- php_stream_notify_progress_increment(stream->context, nr_bytes, 0);
+ return 1;
+}
+/* }}} */
+#endif
+
+static long load_stream_cafile(X509_STORE *cert_store, const char *cafile TSRMLS_DC) /* {{{ */
+{
+ php_stream *stream;
+ X509 *cert;
+ BIO *buffer;
+ int buffer_active = 0;
+ char *line = NULL;
+ size_t line_len;
+ long certs_added = 0;
+
+ stream = php_stream_open_wrapper(cafile, "rb", 0, NULL);
+
+ if (stream == NULL) {
+ php_error(E_WARNING, "failed loading cafile stream: `%s'", cafile);
+ return 0;
+ } else if (stream->wrapper->is_url) {
+ php_stream_close(stream);
+ php_error(E_WARNING, "remote cafile streams are disabled for security purposes");
+ return 0;
+ }
+
+ cert_start: {
+ line = php_stream_get_line(stream, NULL, 0, &line_len);
+ if (line == NULL) {
+ goto stream_complete;
+ } else if (!strcmp(line, "-----BEGIN CERTIFICATE-----\n") ||
+ !strcmp(line, "-----BEGIN CERTIFICATE-----\r\n")
+ ) {
+ buffer = BIO_new(BIO_s_mem());
+ buffer_active = 1;
+ goto cert_line;
+ } else {
+ efree(line);
+ goto cert_start;
}
}
- else
- {
- nr_bytes = php_stream_socket_ops.read(stream, buf, count TSRMLS_CC);
+
+ cert_line: {
+ BIO_puts(buffer, line);
+ efree(line);
+ line = php_stream_get_line(stream, NULL, 0, &line_len);
+ if (line == NULL) {
+ goto stream_complete;
+ } else if (!strcmp(line, "-----END CERTIFICATE-----") ||
+ !strcmp(line, "-----END CERTIFICATE-----\n") ||
+ !strcmp(line, "-----END CERTIFICATE-----\r\n")
+ ) {
+ goto add_cert;
+ } else {
+ goto cert_line;
+ }
}
- if (nr_bytes < 0) {
- nr_bytes = 0;
+ add_cert: {
+ BIO_puts(buffer, line);
+ efree(line);
+ cert = PEM_read_bio_X509(buffer, NULL, 0, NULL);
+ BIO_free(buffer);
+ buffer_active = 0;
+ if (cert && X509_STORE_add_cert(cert_store, cert)) {
+ ++certs_added;
+ }
+ goto cert_start;
}
- return nr_bytes;
+ stream_complete: {
+ php_stream_close(stream);
+ if (buffer_active == 1) {
+ BIO_free(buffer);
+ }
+ }
+
+ if (certs_added == 0) {
+ php_error(E_WARNING, "no valid certs found cafile stream: `%s'", cafile);
+ }
+
+ return certs_added;
}
+/* }}} */
+
+static int enable_peer_verification(SSL_CTX *ctx, php_stream *stream TSRMLS_DC) /* {{{ */
+{
+ zval **val = NULL;
+ char *cafile = NULL;
+ char *capath = NULL;
+ GET_VER_OPT_STRING("cafile", cafile);
+ GET_VER_OPT_STRING("capath", capath);
-static int php_openssl_sockop_close(php_stream *stream, int close_handle TSRMLS_DC)
+ if (!cafile) {
+ cafile = zend_ini_string("openssl.cafile", sizeof("openssl.cafile"), 0);
+ cafile = strlen(cafile) ? cafile : NULL;
+ }
+
+ if (!capath) {
+ capath = zend_ini_string("openssl.capath", sizeof("openssl.capath"), 0);
+ capath = strlen(capath) ? capath : NULL;
+ }
+
+ if (cafile || capath) {
+ if (!SSL_CTX_load_verify_locations(ctx, cafile, capath)) {
+ if (cafile && !load_stream_cafile(SSL_CTX_get_cert_store(ctx), cafile TSRMLS_CC)) {
+ return FAILURE;
+ }
+ }
+ } else {
+#if defined(PHP_WIN32) && OPENSSL_VERSION_NUMBER >= 0x00907000L
+ SSL_CTX_set_cert_verify_callback(ctx, win_cert_verify_callback, (void *)stream);
+ SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
+#else
+ php_openssl_netstream_data_t *sslsock;
+ sslsock = (php_openssl_netstream_data_t*)stream->abstract;
+
+ if (sslsock->is_client && !SSL_CTX_set_default_verify_paths(ctx)) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING,
+ "Unable to set default verify locations and no CA settings specified");
+ return FAILURE;
+ }
+#endif
+ }
+
+ SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, verify_callback);
+
+ return SUCCESS;
+}
+/* }}} */
+
+static void disable_peer_verification(SSL_CTX *ctx, php_stream *stream TSRMLS_DC) /* {{{ */
{
- php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract;
-#ifdef PHP_WIN32
- int n;
+ SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL);
+}
+/* }}} */
+
+static int set_local_cert(SSL_CTX *ctx, php_stream *stream TSRMLS_DC) /* {{{ */
+{
+ zval **val = NULL;
+ char *certfile = NULL;
+
+ GET_VER_OPT_STRING("local_cert", certfile);
+
+ if (certfile) {
+ char resolved_path_buff[MAXPATHLEN];
+ const char * private_key = NULL;
+
+ if (VCWD_REALPATH(certfile, resolved_path_buff)) {
+ /* a certificate to use for authentication */
+ if (SSL_CTX_use_certificate_chain_file(ctx, resolved_path_buff) != 1) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to set local cert chain file `%s'; Check that your cafile/capath settings include details of your certificate and its issuer", certfile);
+ return FAILURE;
+ }
+ GET_VER_OPT_STRING("local_pk", private_key);
+
+ if (private_key) {
+ char resolved_path_buff_pk[MAXPATHLEN];
+ if (VCWD_REALPATH(private_key, resolved_path_buff_pk)) {
+ if (SSL_CTX_use_PrivateKey_file(ctx, resolved_path_buff_pk, SSL_FILETYPE_PEM) != 1) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to set private key file `%s'", resolved_path_buff_pk);
+ return FAILURE;
+ }
+ }
+ } else {
+ if (SSL_CTX_use_PrivateKey_file(ctx, resolved_path_buff, SSL_FILETYPE_PEM) != 1) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to set private key file `%s'", resolved_path_buff);
+ return FAILURE;
+ }
+ }
+
+#if OPENSSL_VERSION_NUMBER < 0x10001001L
+ do {
+ /* Unnecessary as of OpenSSLv1.0.1 (will segfault if used with >= 10001001 ) */
+ X509 *cert = NULL;
+ EVP_PKEY *key = NULL;
+ SSL *tmpssl = SSL_new(ctx);
+ cert = SSL_get_certificate(tmpssl);
+
+ if (cert) {
+ key = X509_get_pubkey(cert);
+ EVP_PKEY_copy_parameters(key, SSL_get_privatekey(tmpssl));
+ EVP_PKEY_free(key);
+ }
+ SSL_free(tmpssl);
+ } while (0);
#endif
- if (close_handle) {
- if (sslsock->ssl_active) {
- SSL_shutdown(sslsock->ssl_handle);
- sslsock->ssl_active = 0;
+ if (!SSL_CTX_check_private_key(ctx)) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Private key does not match certificate!");
+ }
}
- if (sslsock->ssl_handle) {
- SSL_free(sslsock->ssl_handle);
- sslsock->ssl_handle = NULL;
+ }
+
+ return SUCCESS;
+}
+/* }}} */
+
+static const SSL_METHOD *php_select_crypto_method(long method_value, int is_client TSRMLS_DC) /* {{{ */
+{
+ if (method_value == STREAM_CRYPTO_METHOD_SSLv2) {
+#ifndef OPENSSL_NO_SSL2
+ return is_client ? SSLv2_client_method() : SSLv2_server_method();
+#else
+ php_error_docref(NULL TSRMLS_CC, E_WARNING,
+ "SSLv2 support is not compiled into the OpenSSL library PHP is linked against");
+ return NULL;
+#endif
+ } else if (method_value == STREAM_CRYPTO_METHOD_SSLv3) {
+ return is_client ? SSLv3_client_method() : SSLv3_server_method();
+ } else if (method_value == STREAM_CRYPTO_METHOD_TLSv1_0) {
+ return is_client ? TLSv1_client_method() : TLSv1_server_method();
+ } else if (method_value == STREAM_CRYPTO_METHOD_TLSv1_1) {
+#if OPENSSL_VERSION_NUMBER >= 0x10001001L
+ return is_client ? TLSv1_1_client_method() : TLSv1_1_server_method();
+#else
+ php_error_docref(NULL TSRMLS_CC, E_WARNING,
+ "TLSv1.1 support is not compiled into the OpenSSL library PHP is linked against");
+ return NULL;
+#endif
+ } else if (method_value == STREAM_CRYPTO_METHOD_TLSv1_2) {
+#if OPENSSL_VERSION_NUMBER >= 0x10001001L
+ return is_client ? TLSv1_2_client_method() : TLSv1_2_server_method();
+#else
+ php_error_docref(NULL TSRMLS_CC, E_WARNING,
+ "TLSv1.2 support is not compiled into the OpenSSL library PHP is linked against");
+ return NULL;
+#endif
+ } else {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING,
+ "Invalid crypto method");
+ return NULL;
+ }
+}
+/* }}} */
+
+static long php_get_crypto_method_ctx_flags(long method_flags TSRMLS_DC) /* {{{ */
+{
+ long ssl_ctx_options = SSL_OP_ALL;
+
+#ifndef OPENSSL_NO_SSL2
+ if (!(method_flags & STREAM_CRYPTO_METHOD_SSLv2)) {
+ ssl_ctx_options |= SSL_OP_NO_SSLv2;
+ }
+#endif
+#ifndef OPENSSL_NO_SSL3
+ if (!(method_flags & STREAM_CRYPTO_METHOD_SSLv3)) {
+ ssl_ctx_options |= SSL_OP_NO_SSLv3;
+ }
+#endif
+#ifndef OPENSSL_NO_TLS1
+ if (!(method_flags & STREAM_CRYPTO_METHOD_TLSv1_0)) {
+ ssl_ctx_options |= SSL_OP_NO_TLSv1;
+ }
+#endif
+#if OPENSSL_VERSION_NUMBER >= 0x10001001L
+ if (!(method_flags & STREAM_CRYPTO_METHOD_TLSv1_1)) {
+ ssl_ctx_options |= SSL_OP_NO_TLSv1_1;
+ }
+
+ if (!(method_flags & STREAM_CRYPTO_METHOD_TLSv1_2)) {
+ ssl_ctx_options |= SSL_OP_NO_TLSv1_2;
+ }
+#endif
+
+ return ssl_ctx_options;
+}
+/* }}} */
+
+static void limit_handshake_reneg(const SSL *ssl) /* {{{ */
+{
+ php_stream *stream;
+ php_openssl_netstream_data_t *sslsock;
+ struct timeval now;
+ long elapsed_time;
+
+ stream = php_openssl_get_stream_from_ssl_handle(ssl);
+ sslsock = (php_openssl_netstream_data_t*)stream->abstract;
+ gettimeofday(&now, NULL);
+
+ /* The initial handshake is never rate-limited */
+ if (sslsock->reneg->prev_handshake == 0) {
+ sslsock->reneg->prev_handshake = now.tv_sec;
+ return;
+ }
+
+ elapsed_time = (now.tv_sec - sslsock->reneg->prev_handshake);
+ sslsock->reneg->prev_handshake = now.tv_sec;
+ sslsock->reneg->tokens -= (elapsed_time * (sslsock->reneg->limit / sslsock->reneg->window));
+
+ if (sslsock->reneg->tokens < 0) {
+ sslsock->reneg->tokens = 0;
+ }
+ ++sslsock->reneg->tokens;
+
+ /* The token level exceeds our allowed limit */
+ if (sslsock->reneg->tokens > sslsock->reneg->limit) {
+ zval **val;
+
+ TSRMLS_FETCH();
+
+ sslsock->reneg->should_close = 1;
+
+ if (stream->context && SUCCESS == php_stream_context_get_option(stream->context,
+ "ssl", "reneg_limit_callback", &val)
+ ) {
+ zval *param, **params[1], *retval;
+
+ MAKE_STD_ZVAL(param);
+ php_stream_to_zval(stream, param);
+ params[0] = &param;
+
+ /* Closing the stream inside this callback would segfault! */
+ stream->flags |= PHP_STREAM_FLAG_NO_FCLOSE;
+ if (FAILURE == call_user_function_ex(EG(function_table), NULL, *val, &retval, 1, params, 0, NULL TSRMLS_CC)) {
+ php_error(E_WARNING, "SSL: failed invoking reneg limit notification callback");
+ }
+ stream->flags ^= PHP_STREAM_FLAG_NO_FCLOSE;
+
+ /* If the reneg_limit_callback returned true don't auto-close */
+ if (retval != NULL && Z_TYPE_P(retval) == IS_BOOL && Z_BVAL_P(retval) == 1) {
+ sslsock->reneg->should_close = 0;
+ }
+
+ FREE_ZVAL(param);
+ if (retval != NULL) {
+ zval_ptr_dtor(&retval);
+ }
+ } else {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING,
+ "SSL: client-initiated handshake rate limit exceeded by peer");
}
- if (sslsock->ctx) {
- SSL_CTX_free(sslsock->ctx);
- sslsock->ctx = NULL;
+ }
+}
+/* }}} */
+
+static void info_callback(const SSL *ssl, int where, int ret) /* {{{ */
+{
+ /* Rate-limit client-initiated handshake renegotiation to prevent DoS */
+ if (where & SSL_CB_HANDSHAKE_START) {
+ limit_handshake_reneg(ssl);
+ }
+}
+/* }}} */
+
+static void init_server_reneg_limit(php_stream *stream, php_openssl_netstream_data_t *sslsock) /* {{{ */
+{
+ zval **val;
+ long limit = OPENSSL_DEFAULT_RENEG_LIMIT;
+ long window = OPENSSL_DEFAULT_RENEG_WINDOW;
+
+ if (stream->context &&
+ SUCCESS == php_stream_context_get_option(stream->context,
+ "ssl", "reneg_limit", &val)
+ ) {
+ convert_to_long(*val);
+ limit = Z_LVAL_PP(val);
+ }
+
+ /* No renegotiation rate-limiting */
+ if (limit < 0) {
+ return;
+ }
+
+ if (stream->context &&
+ SUCCESS == php_stream_context_get_option(stream->context,
+ "ssl", "reneg_window", &val)
+ ) {
+ convert_to_long(*val);
+ window = Z_LVAL_PP(val);
+ }
+
+ sslsock->reneg = (void*)pemalloc(sizeof(php_openssl_handshake_bucket_t),
+ php_stream_is_persistent(stream)
+ );
+
+ sslsock->reneg->limit = limit;
+ sslsock->reneg->window = window;
+ sslsock->reneg->prev_handshake = 0;
+ sslsock->reneg->tokens = 0;
+ sslsock->reneg->should_close = 0;
+
+ SSL_set_info_callback(sslsock->ssl_handle, info_callback);
+}
+/* }}} */
+
+static int set_server_rsa_key(php_stream *stream, SSL_CTX *ctx TSRMLS_DC) /* {{{ */
+{
+ zval ** val;
+ int rsa_key_size;
+ RSA* rsa;
+
+ if (php_stream_context_get_option(stream->context, "ssl", "rsa_key_size", &val) == SUCCESS) {
+ rsa_key_size = (int) Z_LVAL_PP(val);
+ if ((rsa_key_size != 1) && (rsa_key_size & (rsa_key_size - 1))) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "RSA key size requires a power of 2: %d", rsa_key_size);
+ rsa_key_size = 2048;
}
-#ifdef PHP_WIN32
- if (sslsock->s.socket == -1)
- sslsock->s.socket = SOCK_ERR;
+ } else {
+ rsa_key_size = 2048;
+ }
+
+ rsa = RSA_generate_key(rsa_key_size, RSA_F4, NULL, NULL);
+
+ if (!SSL_CTX_set_tmp_rsa(ctx, rsa)) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed setting RSA key");
+ RSA_free(rsa);
+ return FAILURE;
+ }
+
+ RSA_free(rsa);
+
+ return SUCCESS;
+}
+/* }}} */
+
+static int set_server_dh_param(SSL_CTX *ctx, char *dh_path TSRMLS_DC) /* {{{ */
+{
+ DH *dh;
+ BIO* bio;
+
+ bio = BIO_new_file(dh_path, "r");
+
+ if (bio == NULL) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid dh_param file: %s", dh_path);
+ return FAILURE;
+ }
+
+ dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
+ BIO_free(bio);
+
+ if (dh == NULL) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed reading DH params from file: %s", dh_path);
+ return FAILURE;
+ }
+
+ if (SSL_CTX_set_tmp_dh(ctx, dh) < 0) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "DH param assignment failed");
+ DH_free(dh);
+ return FAILURE;
+ }
+
+ DH_free(dh);
+
+ return SUCCESS;
+}
+/* }}} */
+
+#ifdef HAVE_ECDH
+static int set_server_ecdh_curve(php_stream *stream, SSL_CTX *ctx TSRMLS_DC) /* {{{ */
+{
+ zval **val;
+ int curve_nid;
+ char *curve_str;
+ EC_KEY *ecdh;
+
+ if (php_stream_context_get_option(stream->context, "ssl", "ecdh_curve", &val) == SUCCESS) {
+ convert_to_string_ex(val);
+ curve_str = Z_STRVAL_PP(val);
+ curve_nid = OBJ_sn2nid(curve_str);
+ if (curve_nid == NID_undef) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid ECDH curve: %s", curve_str);
+ return FAILURE;
+ }
+ } else {
+ curve_nid = NID_X9_62_prime256v1;
+ }
+
+ ecdh = EC_KEY_new_by_curve_name(curve_nid);
+ if (ecdh == NULL) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING,
+ "Failed generating ECDH curve");
+
+ return FAILURE;
+ }
+
+ SSL_CTX_set_tmp_ecdh(ctx, ecdh);
+ EC_KEY_free(ecdh);
+
+ return SUCCESS;
+}
+/* }}} */
#endif
- if (sslsock->s.socket != SOCK_ERR) {
-#ifdef PHP_WIN32
- /* prevent more data from coming in */
- shutdown(sslsock->s.socket, SHUT_RD);
- /* try to make sure that the OS sends all data before we close the connection.
- * Essentially, we are waiting for the socket to become writeable, which means
- * that all pending data has been sent.
- * We use a small timeout which should encourage the OS to send the data,
- * but at the same time avoid hanging indefinitely.
- * */
- do {
- n = php_pollfd_for_ms(sslsock->s.socket, POLLOUT, 500);
- } while (n == -1 && php_socket_errno() == EINTR);
+static int set_server_specific_opts(php_stream *stream, SSL_CTX *ctx TSRMLS_DC) /* {{{ */
+{
+ zval **val;
+ long ssl_ctx_options = SSL_CTX_get_options(ctx);
+
+#ifdef HAVE_ECDH
+ if (FAILURE == set_server_ecdh_curve(stream, ctx TSRMLS_CC)) {
+ return FAILURE;
+ }
+#else
+ if (SUCCESS == php_stream_context_get_option(stream->context, "ssl", "ecdh_curve", &val)) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING,
+ "ECDH curve support not compiled into the OpenSSL lib against which PHP is linked");
+
+ return FAILURE;
+ }
#endif
- closesocket(sslsock->s.socket);
- sslsock->s.socket = SOCK_ERR;
+
+ if (php_stream_context_get_option(stream->context, "ssl", "dh_param", &val) == SUCCESS) {
+ convert_to_string_ex(val);
+ if (FAILURE == set_server_dh_param(ctx, Z_STRVAL_PP(val) TSRMLS_CC)) {
+ return FAILURE;
}
}
- if (sslsock->sni) {
- pefree(sslsock->sni, php_stream_is_persistent(stream));
+ if (FAILURE == set_server_rsa_key(stream, ctx TSRMLS_CC)) {
+ return FAILURE;
}
- pefree(sslsock, php_stream_is_persistent(stream));
-
- return 0;
+
+ if (SUCCESS == php_stream_context_get_option(
+ stream->context, "ssl", "honor_cipher_order", &val) &&
+ zend_is_true(*val)
+ ) {
+ ssl_ctx_options |= SSL_OP_CIPHER_SERVER_PREFERENCE;
+ }
+
+ if (SUCCESS == php_stream_context_get_option(
+ stream->context, "ssl", "single_dh_use", &val) &&
+ zend_is_true(*val)
+ ) {
+ ssl_ctx_options |= SSL_OP_SINGLE_DH_USE;
+ }
+
+ if (SUCCESS == php_stream_context_get_option(
+ stream->context, "ssl", "single_ecdh_use", &val) &&
+ zend_is_true(*val)
+ ) {
+ ssl_ctx_options |= SSL_OP_SINGLE_ECDH_USE;
+ }
+
+ SSL_CTX_set_options(ctx, ssl_ctx_options);
+
+ return SUCCESS;
}
+/* }}} */
-static int php_openssl_sockop_flush(php_stream *stream TSRMLS_DC)
+#ifdef HAVE_SNI
+static int server_sni_callback(SSL *ssl_handle, int *al, void *arg) /* {{{ */
{
- return php_stream_socket_ops.flush(stream TSRMLS_CC);
+ php_stream *stream;
+ php_openssl_netstream_data_t *sslsock;
+ unsigned i;
+ const char *server_name;
+
+ server_name = SSL_get_servername(ssl_handle, TLSEXT_NAMETYPE_host_name);
+
+ if (!server_name) {
+ return SSL_TLSEXT_ERR_NOACK;
+ }
+
+ stream = (php_stream*)SSL_get_ex_data(ssl_handle, php_openssl_get_ssl_stream_data_index());
+ sslsock = (php_openssl_netstream_data_t*)stream->abstract;
+
+ if (!(sslsock->sni_cert_count && sslsock->sni_certs)) {
+ return SSL_TLSEXT_ERR_NOACK;
+ }
+
+ for (i=0; i < sslsock->sni_cert_count; i++) {
+ if (matches_wildcard_name(server_name, sslsock->sni_certs[i].name)) {
+ SSL_set_SSL_CTX(ssl_handle, sslsock->sni_certs[i].ctx);
+ return SSL_TLSEXT_ERR_OK;
+ }
+ }
+
+ return SSL_TLSEXT_ERR_NOACK;
}
+/* }}} */
-static int php_openssl_sockop_stat(php_stream *stream, php_stream_statbuf *ssb TSRMLS_DC)
+static int enable_server_sni(php_stream *stream, php_openssl_netstream_data_t *sslsock TSRMLS_DC)
{
- return php_stream_socket_ops.stat(stream, ssb TSRMLS_CC);
+ zval **val;
+ zval **current;
+ char *key;
+ uint key_len;
+ ulong key_index;
+ int key_type;
+ HashPosition pos;
+ int i = 0;
+ char resolved_path_buff[MAXPATHLEN];
+ SSL_CTX *ctx;
+
+ /* If the stream ctx disables SNI we're finished here */
+ if (GET_VER_OPT("SNI_enabled") && !zend_is_true(*val)) {
+ return SUCCESS;
+ }
+
+ /* If no SNI cert array is specified we're finished here */
+ if (!GET_VER_OPT("SNI_server_certs")) {
+ return SUCCESS;
+ }
+
+ if (Z_TYPE_PP(val) != IS_ARRAY) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING,
+ "SNI_server_certs requires an array mapping host names to cert paths"
+ );
+ return FAILURE;
+ }
+
+ sslsock->sni_cert_count = zend_hash_num_elements(Z_ARRVAL_PP(val));
+ if (sslsock->sni_cert_count == 0) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING,
+ "SNI_server_certs host cert array must not be empty"
+ );
+ return FAILURE;
+ }
+
+ sslsock->sni_certs = (php_openssl_sni_cert_t*)safe_pemalloc(sslsock->sni_cert_count,
+ sizeof(php_openssl_sni_cert_t), 0, php_stream_is_persistent(stream)
+ );
+
+ for (zend_hash_internal_pointer_reset_ex(Z_ARRVAL_PP(val), &pos);
+ zend_hash_get_current_data_ex(Z_ARRVAL_PP(val), (void **)&current, &pos) == SUCCESS;
+ zend_hash_move_forward_ex(Z_ARRVAL_PP(val), &pos)
+ ) {
+ key_type = zend_hash_get_current_key_ex(Z_ARRVAL_PP(val), &key, &key_len, &key_index, 0, &pos);
+ if (key_type != HASH_KEY_IS_STRING) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING,
+ "SNI_server_certs array requires string host name keys"
+ );
+ return FAILURE;
+ }
+
+ if (VCWD_REALPATH(Z_STRVAL_PP(current), resolved_path_buff)) {
+ /* The hello method is not inherited by SSL structs when assigning a new context
+ * inside the SNI callback, so the just use SSLv23 */
+ ctx = SSL_CTX_new(SSLv23_server_method());
+
+ if (SSL_CTX_use_certificate_chain_file(ctx, resolved_path_buff) != 1) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING,
+ "failed setting local cert chain file `%s'; " \
+ "check that your cafile/capath settings include " \
+ "details of your certificate and its issuer",
+ resolved_path_buff
+ );
+ SSL_CTX_free(ctx);
+ return FAILURE;
+ } else if (SSL_CTX_use_PrivateKey_file(ctx, resolved_path_buff, SSL_FILETYPE_PEM) != 1) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING,
+ "failed setting private key from file `%s'",
+ resolved_path_buff
+ );
+ SSL_CTX_free(ctx);
+ return FAILURE;
+ } else {
+ sslsock->sni_certs[i].name = pestrdup(key, php_stream_is_persistent(stream));
+ sslsock->sni_certs[i].ctx = ctx;
+ ++i;
+ }
+ } else {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING,
+ "failed setting local cert chain file `%s'; file not found",
+ Z_STRVAL_PP(current)
+ );
+ return FAILURE;
+ }
+ }
+
+ SSL_CTX_set_tlsext_servername_callback(sslsock->ctx, server_sni_callback);
+
+ return SUCCESS;
}
+static void enable_client_sni(php_stream *stream, php_openssl_netstream_data_t *sslsock) /* {{{ */
+{
+ zval **val;
+ char *sni_server_name;
+
+ /* If SNI is explicitly disabled we're finished here */
+ if (GET_VER_OPT("SNI_enabled") && !zend_is_true(*val)) {
+ return;
+ }
+
+ sni_server_name = sslsock->url_name;
+
+ GET_VER_OPT_STRING("peer_name", sni_server_name);
-static inline int php_openssl_setup_crypto(php_stream *stream,
+ if (GET_VER_OPT("SNI_server_name")) {
+ GET_VER_OPT_STRING("SNI_server_name", sni_server_name);
+ php_error(E_DEPRECATED, "SNI_server_name is deprecated in favor of peer_name");
+ }
+
+ if (sni_server_name) {
+ SSL_set_tlsext_host_name(sslsock->ssl_handle, sni_server_name);
+ }
+}
+/* }}} */
+#endif
+
+int php_openssl_setup_crypto(php_stream *stream,
php_openssl_netstream_data_t *sslsock,
php_stream_xport_crypto_param *cparam
- TSRMLS_DC)
+ TSRMLS_DC) /* {{{ */
{
- SSL_METHOD *method;
- long ssl_ctx_options = SSL_OP_ALL;
-
+ const SSL_METHOD *method;
+ long ssl_ctx_options;
+ long method_flags;
+ char *cipherlist = NULL;
+ zval **val;
+
if (sslsock->ssl_handle) {
if (sslsock->s.is_blocked) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "SSL/TLS already set-up for this stream");
- return -1;
+ return FAILURE;
} else {
- return 0;
+ return SUCCESS;
}
}
- /* need to do slightly different things, based on client/server method,
+ ERR_clear_error();
+
+ /* We need to do slightly different things based on client/server method
* so lets remember which method was selected */
+ sslsock->is_client = cparam->inputs.method & STREAM_CRYPTO_IS_CLIENT;
+ method_flags = ((cparam->inputs.method >> 1) << 1);
+
+ /* Should we use a specific crypto method or is generic SSLv23 okay? */
+ if ((method_flags & (method_flags-1)) == 0) {
+ ssl_ctx_options = SSL_OP_ALL;
+ method = php_select_crypto_method(method_flags, sslsock->is_client TSRMLS_CC);
+ if (method == NULL) {
+ return FAILURE;
+ }
+ } else {
+ method = sslsock->is_client ? SSLv23_client_method() : SSLv23_server_method();
+ ssl_ctx_options = php_get_crypto_method_ctx_flags(method_flags TSRMLS_CC);
+ if (ssl_ctx_options == -1) {
+ return FAILURE;
+ }
+ }
- switch (cparam->inputs.method) {
- case STREAM_CRYPTO_METHOD_SSLv23_CLIENT:
- sslsock->is_client = 1;
- method = SSLv23_client_method();
- break;
- case STREAM_CRYPTO_METHOD_SSLv2_CLIENT:
-#ifdef OPENSSL_NO_SSL2
- php_error_docref(NULL TSRMLS_CC, E_WARNING, "SSLv2 support is not compiled into the OpenSSL library PHP is linked against");
- return -1;
-#else
- sslsock->is_client = 1;
- method = SSLv2_client_method();
- break;
-#endif
- case STREAM_CRYPTO_METHOD_SSLv3_CLIENT:
- sslsock->is_client = 1;
- method = SSLv3_client_method();
- break;
- case STREAM_CRYPTO_METHOD_TLS_CLIENT:
- sslsock->is_client = 1;
- method = TLSv1_client_method();
- break;
- case STREAM_CRYPTO_METHOD_SSLv23_SERVER:
- sslsock->is_client = 0;
- method = SSLv23_server_method();
- break;
- case STREAM_CRYPTO_METHOD_SSLv3_SERVER:
- sslsock->is_client = 0;
- method = SSLv3_server_method();
- break;
- case STREAM_CRYPTO_METHOD_SSLv2_SERVER:
-#ifdef OPENSSL_NO_SSL2
- php_error_docref(NULL TSRMLS_CC, E_WARNING, "SSLv2 support is not compiled into the OpenSSL library PHP is linked against");
- return -1;
+#if OPENSSL_VERSION_NUMBER >= 0x10001001L
+ sslsock->ctx = SSL_CTX_new(method);
#else
- sslsock->is_client = 0;
- method = SSLv2_server_method();
- break;
+ /* Avoid const warning with old versions */
+ sslsock->ctx = SSL_CTX_new((SSL_METHOD*)method);
#endif
- case STREAM_CRYPTO_METHOD_TLS_SERVER:
- sslsock->is_client = 0;
- method = TLSv1_server_method();
- break;
- default:
- return -1;
+ if (sslsock->ctx == NULL) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "SSL context creation failure");
+ return FAILURE;
}
- sslsock->ctx = SSL_CTX_new(method);
- if (sslsock->ctx == NULL) {
- php_error_docref(NULL TSRMLS_CC, E_WARNING, "failed to create an SSL context");
- return -1;
+#if OPENSSL_VERSION_NUMBER >= 0x0090806fL
+ if (GET_VER_OPT("no_ticket") && zend_is_true(*val)) {
+ ssl_ctx_options |= SSL_OP_NO_TICKET;
}
+#endif
#if OPENSSL_VERSION_NUMBER >= 0x0090605fL
ssl_ctx_options &= ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS;
#endif
- SSL_CTX_set_options(sslsock->ctx, ssl_ctx_options);
-
-#if OPENSSL_VERSION_NUMBER >= 0x0090806fL
- {
- zval **val;
- if (stream->context && SUCCESS == php_stream_context_get_option(
- stream->context, "ssl", "no_ticket", &val) &&
- zval_is_true(*val)) {
- SSL_CTX_set_options(sslsock->ctx, SSL_OP_NO_TICKET);
- }
+#if OPENSSL_VERSION_NUMBER >= 0x10000000L
+ if (!GET_VER_OPT("disable_compression") || zend_is_true(*val)) {
+ ssl_ctx_options |= SSL_OP_NO_COMPRESSION;
}
#endif
-#if OPENSSL_VERSION_NUMBER >= 0x10000000L
- {
- zval **val;
+ if (GET_VER_OPT("verify_peer") && !zend_is_true(*val)) {
+ disable_peer_verification(sslsock->ctx, stream TSRMLS_CC);
+ } else if (FAILURE == enable_peer_verification(sslsock->ctx, stream TSRMLS_CC)) {
+ return FAILURE;
+ }
- if (stream->context && SUCCESS == php_stream_context_get_option(
- stream->context, "ssl", "disable_compression", &val) &&
- zval_is_true(*val)) {
- SSL_CTX_set_options(sslsock->ctx, SSL_OP_NO_COMPRESSION);
- }
+ /* callback for the passphrase (for localcert) */
+ if (GET_VER_OPT("passphrase")) {
+ SSL_CTX_set_default_passwd_cb_userdata(sslsock->ctx, stream);
+ SSL_CTX_set_default_passwd_cb(sslsock->ctx, passwd_callback);
}
-#endif
- sslsock->ssl_handle = php_SSL_new_from_context(sslsock->ctx, stream TSRMLS_CC);
+ GET_VER_OPT_STRING("ciphers", cipherlist);
+ if (!cipherlist) {
+ cipherlist = OPENSSL_DEFAULT_STREAM_CIPHERS;
+ }
+ if (SSL_CTX_set_cipher_list(sslsock->ctx, cipherlist) != 1) {
+ return FAILURE;
+ }
+
+ if (FAILURE == set_local_cert(sslsock->ctx, stream TSRMLS_CC)) {
+ return FAILURE;
+ }
+
+ SSL_CTX_set_options(sslsock->ctx, ssl_ctx_options);
+
+ if (sslsock->is_client == 0 &&
+ stream->context &&
+ FAILURE == set_server_specific_opts(stream, sslsock->ctx TSRMLS_CC)
+ ) {
+ return FAILURE;
+ }
+
+ sslsock->ssl_handle = SSL_new(sslsock->ctx);
if (sslsock->ssl_handle == NULL) {
- php_error_docref(NULL TSRMLS_CC, E_WARNING, "failed to create an SSL handle");
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "SSL handle creation failure");
SSL_CTX_free(sslsock->ctx);
sslsock->ctx = NULL;
- return -1;
+ return FAILURE;
+ } else {
+ SSL_set_ex_data(sslsock->ssl_handle, php_openssl_get_ssl_stream_data_index(), stream);
}
if (!SSL_set_fd(sslsock->ssl_handle, sslsock->s.socket)) {
handle_ssl_error(stream, 0, 1 TSRMLS_CC);
}
+#ifdef HAVE_SNI
+ /* Enable server-side SNI */
+ if (sslsock->is_client == 0 && enable_server_sni(stream, sslsock TSRMLS_CC) == FAILURE) {
+ return FAILURE;
+ }
+#endif
+
+ /* Enable server-side handshake renegotiation rate-limiting */
+ if (sslsock->is_client == 0) {
+ init_server_reneg_limit(stream, sslsock);
+ }
+
+#ifdef SSL_MODE_RELEASE_BUFFERS
+ do {
+ long mode = SSL_get_mode(sslsock->ssl_handle);
+ SSL_set_mode(sslsock->ssl_handle, mode | SSL_MODE_RELEASE_BUFFERS);
+ } while (0);
+#endif
+
if (cparam->inputs.session) {
if (cparam->inputs.session->ops != &php_openssl_socket_ops) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "supplied session stream must be an SSL enabled stream");
@@ -428,15 +1474,99 @@ static inline int php_openssl_setup_crypto(php_stream *stream,
SSL_copy_session_id(sslsock->ssl_handle, ((php_openssl_netstream_data_t*)cparam->inputs.session->abstract)->ssl_handle);
}
}
- return 0;
+
+ return SUCCESS;
+}
+/* }}} */
+
+static zval *capture_session_meta(SSL *ssl_handle) /* {{{ */
+{
+ zval *meta_arr;
+ char *proto_str;
+ long proto = SSL_version(ssl_handle);
+ const SSL_CIPHER *cipher = SSL_get_current_cipher(ssl_handle);
+
+ switch (proto) {
+#if OPENSSL_VERSION_NUMBER >= 0x10001001L
+ case TLS1_2_VERSION: proto_str = "TLSv1.2"; break;
+ case TLS1_1_VERSION: proto_str = "TLSv1.1"; break;
+#endif
+ case TLS1_VERSION: proto_str = "TLSv1"; break;
+ case SSL3_VERSION: proto_str = "SSLv3"; break;
+ case SSL2_VERSION: proto_str = "SSLv2"; break;
+ default: proto_str = "UNKNOWN";
+ }
+
+ MAKE_STD_ZVAL(meta_arr);
+ array_init(meta_arr);
+ add_assoc_string(meta_arr, "protocol", proto_str, 1);
+ add_assoc_string(meta_arr, "cipher_name", (char *) SSL_CIPHER_get_name(cipher), 1);
+ add_assoc_long(meta_arr, "cipher_bits", SSL_CIPHER_get_bits(cipher, NULL));
+ add_assoc_string(meta_arr, "cipher_version", SSL_CIPHER_get_version(cipher), 1);
+
+ return meta_arr;
+}
+/* }}} */
+
+static int capture_peer_certs(php_stream *stream, php_openssl_netstream_data_t *sslsock, X509 *peer_cert TSRMLS_DC) /* {{{ */
+{
+ zval **val, *zcert;
+ int cert_captured = 0;
+
+ if (SUCCESS == php_stream_context_get_option(stream->context,
+ "ssl", "capture_peer_cert", &val) &&
+ zend_is_true(*val)
+ ) {
+ MAKE_STD_ZVAL(zcert);
+ ZVAL_RESOURCE(zcert, zend_list_insert(peer_cert, php_openssl_get_x509_list_id() TSRMLS_CC));
+ php_stream_context_set_option(stream->context, "ssl", "peer_certificate", zcert);
+ cert_captured = 1;
+ FREE_ZVAL(zcert);
+ }
+
+ if (SUCCESS == php_stream_context_get_option(stream->context,
+ "ssl", "capture_peer_cert_chain", &val) &&
+ zend_is_true(*val)
+ ) {
+ zval *arr;
+ STACK_OF(X509) *chain;
+
+ MAKE_STD_ZVAL(arr);
+ chain = SSL_get_peer_cert_chain(sslsock->ssl_handle);
+
+ if (chain && sk_X509_num(chain) > 0) {
+ int i;
+ array_init(arr);
+
+ for (i = 0; i < sk_X509_num(chain); i++) {
+ X509 *mycert = X509_dup(sk_X509_value(chain, i));
+ MAKE_STD_ZVAL(zcert);
+ ZVAL_RESOURCE(zcert, zend_list_insert(mycert, php_openssl_get_x509_list_id() TSRMLS_CC));
+ add_next_index_zval(arr, zcert);
+ }
+
+ } else {
+ ZVAL_NULL(arr);
+ }
+
+ php_stream_context_set_option(stream->context, "ssl", "peer_certificate_chain", arr);
+ zval_dtor(arr);
+ efree(arr);
+ }
+
+ return cert_captured;
}
+/* }}} */
-static inline int php_openssl_enable_crypto(php_stream *stream,
+static int php_openssl_enable_crypto(php_stream *stream,
php_openssl_netstream_data_t *sslsock,
php_stream_xport_crypto_param *cparam
TSRMLS_DC)
{
- int n, retry = 1;
+ int n;
+ int retry = 1;
+ int cert_captured;
+ X509 *peer_cert;
if (cparam->inputs.activate && !sslsock->ssl_active) {
struct timeval start_time,
@@ -444,9 +1574,9 @@ static inline int php_openssl_enable_crypto(php_stream *stream,
int blocked = sslsock->s.is_blocked,
has_timeout = 0;
-#if OPENSSL_VERSION_NUMBER >= 0x0090806fL && !defined(OPENSSL_NO_TLSEXT)
- if (sslsock->is_client && sslsock->sni) {
- SSL_set_tlsext_host_name(sslsock->ssl_handle, sslsock->sni);
+#ifdef HAVE_SNI
+ if (sslsock->is_client) {
+ enable_client_sni(stream, sslsock);
}
#endif
@@ -458,7 +1588,7 @@ static inline int php_openssl_enable_crypto(php_stream *stream,
}
sslsock->state_set = 1;
}
-
+
if (SUCCESS == php_set_sock_blocking(sslsock->s.socket, 0 TSRMLS_CC)) {
sslsock->s.is_blocked = 0;
}
@@ -492,7 +1622,7 @@ static inline int php_openssl_enable_crypto(php_stream *stream,
if (elapsed_time.tv_sec > timeout->tv_sec ||
(elapsed_time.tv_sec == timeout->tv_sec &&
elapsed_time.tv_usec > timeout->tv_usec)) {
- php_error_docref(NULL TSRMLS_CC, E_WARNING, "SSL: crypto enabling timeout");
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "SSL: Handshake timed out");
return -1;
}
}
@@ -527,78 +1657,43 @@ static inline int php_openssl_enable_crypto(php_stream *stream,
}
if (n == 1) {
- X509 *peer_cert;
-
peer_cert = SSL_get_peer_certificate(sslsock->ssl_handle);
+ if (peer_cert && stream->context) {
+ cert_captured = capture_peer_certs(stream, sslsock, peer_cert TSRMLS_CC);
+ }
- if (FAILURE == php_openssl_apply_verification_policy(sslsock->ssl_handle, peer_cert, stream TSRMLS_CC)) {
+ if (FAILURE == apply_peer_verification_policy(sslsock->ssl_handle, peer_cert, stream TSRMLS_CC)) {
SSL_shutdown(sslsock->ssl_handle);
n = -1;
} else {
sslsock->ssl_active = 1;
- /* allow the script to capture the peer cert
- * and/or the certificate chain */
if (stream->context) {
- zval **val, *zcert;
-
- if (SUCCESS == php_stream_context_get_option(
- stream->context, "ssl",
- "capture_peer_cert", &val) &&
- zval_is_true(*val)) {
- MAKE_STD_ZVAL(zcert);
- ZVAL_RESOURCE(zcert, zend_list_insert(peer_cert,
- php_openssl_get_x509_list_id() TSRMLS_CC));
- php_stream_context_set_option(stream->context,
- "ssl", "peer_certificate",
- zcert);
- peer_cert = NULL;
- FREE_ZVAL(zcert);
- }
-
- if (SUCCESS == php_stream_context_get_option(
- stream->context, "ssl",
- "capture_peer_cert_chain", &val) &&
- zval_is_true(*val)) {
- zval *arr;
- STACK_OF(X509) *chain;
-
- MAKE_STD_ZVAL(arr);
- chain = SSL_get_peer_cert_chain(
- sslsock->ssl_handle);
-
- if (chain && sk_X509_num(chain) > 0) {
- int i;
- array_init(arr);
-
- for (i = 0; i < sk_X509_num(chain); i++) {
- X509 *mycert = X509_dup(
- sk_X509_value(chain, i));
- MAKE_STD_ZVAL(zcert);
- ZVAL_RESOURCE(zcert,
- zend_list_insert(mycert,
- php_openssl_get_x509_list_id() TSRMLS_CC));
- add_next_index_zval(arr, zcert);
- }
-
- } else {
- ZVAL_NULL(arr);
- }
-
- php_stream_context_set_option(stream->context,
- "ssl", "peer_certificate_chain",
- arr);
- zval_dtor(arr);
- efree(arr);
+ zval **val;
+
+ if (SUCCESS == php_stream_context_get_option(stream->context,
+ "ssl", "capture_session_meta", &val) &&
+ zend_is_true(*val)
+ ) {
+ zval *meta_arr = capture_session_meta(sslsock->ssl_handle);
+ php_stream_context_set_option(stream->context, "ssl", "session_meta", meta_arr);
+ zval_dtor(meta_arr);
+ efree(meta_arr);
}
}
}
-
- if (peer_cert) {
- X509_free(peer_cert);
+ } else if (errno == EAGAIN) {
+ n = 0;
+ } else {
+ n = -1;
+ peer_cert = SSL_get_peer_certificate(sslsock->ssl_handle);
+ if (peer_cert && stream->context) {
+ cert_captured = capture_peer_certs(stream, sslsock, peer_cert TSRMLS_CC);
}
- } else {
- n = errno == EAGAIN ? 0 : -1;
+ }
+
+ if (n && peer_cert && cert_captured == 0) {
+ X509_free(peer_cert);
}
return n;
@@ -608,9 +1703,167 @@ static inline int php_openssl_enable_crypto(php_stream *stream,
SSL_shutdown(sslsock->ssl_handle);
sslsock->ssl_active = 0;
}
+
return -1;
}
+static size_t php_openssl_sockop_write(php_stream *stream, const char *buf, size_t count TSRMLS_DC) /* {{{ */
+{
+ php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract;
+ int didwrite;
+
+ if (sslsock->ssl_active) {
+ int retry = 1;
+
+ do {
+ didwrite = SSL_write(sslsock->ssl_handle, buf, count);
+
+ if (didwrite <= 0) {
+ retry = handle_ssl_error(stream, didwrite, 0 TSRMLS_CC);
+ } else {
+ break;
+ }
+ } while(retry);
+
+ if (didwrite > 0) {
+ php_stream_notify_progress_increment(stream->context, didwrite, 0);
+ }
+ } else {
+ didwrite = php_stream_socket_ops.write(stream, buf, count TSRMLS_CC);
+ }
+
+ if (didwrite < 0) {
+ didwrite = 0;
+ }
+
+ return didwrite;
+}
+/* }}} */
+
+static size_t php_openssl_sockop_read(php_stream *stream, char *buf, size_t count TSRMLS_DC) /* {{{ */
+{
+ php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract;
+ int nr_bytes = 0;
+
+ if (sslsock->ssl_active) {
+ int retry = 1;
+
+ do {
+ nr_bytes = SSL_read(sslsock->ssl_handle, buf, count);
+
+ if (sslsock->reneg && sslsock->reneg->should_close) {
+ /* renegotiation rate limiting triggered */
+ php_stream_xport_shutdown(stream, (stream_shutdown_t)SHUT_RDWR TSRMLS_CC);
+ nr_bytes = 0;
+ stream->eof = 1;
+ break;
+ } else if (nr_bytes <= 0) {
+ retry = handle_ssl_error(stream, nr_bytes, 0 TSRMLS_CC);
+ stream->eof = (retry == 0 && errno != EAGAIN && !SSL_pending(sslsock->ssl_handle));
+
+ } else {
+ /* we got the data */
+ break;
+ }
+ } while (retry);
+
+ if (nr_bytes > 0) {
+ php_stream_notify_progress_increment(stream->context, nr_bytes, 0);
+ }
+ }
+ else
+ {
+ nr_bytes = php_stream_socket_ops.read(stream, buf, count TSRMLS_CC);
+ }
+
+ if (nr_bytes < 0) {
+ nr_bytes = 0;
+ }
+
+ return nr_bytes;
+}
+/* }}} */
+
+static int php_openssl_sockop_close(php_stream *stream, int close_handle TSRMLS_DC) /* {{{ */
+{
+ php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract;
+#ifdef PHP_WIN32
+ int n;
+#endif
+ unsigned i;
+
+ if (close_handle) {
+ if (sslsock->ssl_active) {
+ SSL_shutdown(sslsock->ssl_handle);
+ sslsock->ssl_active = 0;
+ }
+ if (sslsock->ssl_handle) {
+ SSL_free(sslsock->ssl_handle);
+ sslsock->ssl_handle = NULL;
+ }
+ if (sslsock->ctx) {
+ SSL_CTX_free(sslsock->ctx);
+ sslsock->ctx = NULL;
+ }
+#ifdef PHP_WIN32
+ if (sslsock->s.socket == -1)
+ sslsock->s.socket = SOCK_ERR;
+#endif
+ if (sslsock->s.socket != SOCK_ERR) {
+#ifdef PHP_WIN32
+ /* prevent more data from coming in */
+ shutdown(sslsock->s.socket, SHUT_RD);
+
+ /* try to make sure that the OS sends all data before we close the connection.
+ * Essentially, we are waiting for the socket to become writeable, which means
+ * that all pending data has been sent.
+ * We use a small timeout which should encourage the OS to send the data,
+ * but at the same time avoid hanging indefinitely.
+ * */
+ do {
+ n = php_pollfd_for_ms(sslsock->s.socket, POLLOUT, 500);
+ } while (n == -1 && php_socket_errno() == EINTR);
+#endif
+ closesocket(sslsock->s.socket);
+ sslsock->s.socket = SOCK_ERR;
+ }
+ }
+
+ if (sslsock->sni_certs) {
+ for (i=0; i<sslsock->sni_cert_count; i++) {
+ SSL_CTX_free(sslsock->sni_certs[i].ctx);
+ pefree(sslsock->sni_certs[i].name, php_stream_is_persistent(stream));
+ }
+ pefree(sslsock->sni_certs, php_stream_is_persistent(stream));
+ sslsock->sni_certs = NULL;
+ }
+
+ if (sslsock->url_name) {
+ pefree(sslsock->url_name, php_stream_is_persistent(stream));
+ }
+
+ if (sslsock->reneg) {
+ pefree(sslsock->reneg, php_stream_is_persistent(stream));
+ }
+
+ pefree(sslsock, php_stream_is_persistent(stream));
+
+ return 0;
+}
+/* }}} */
+
+static int php_openssl_sockop_flush(php_stream *stream TSRMLS_DC) /* {{{ */
+{
+ return php_stream_socket_ops.flush(stream TSRMLS_CC);
+}
+/* }}} */
+
+static int php_openssl_sockop_stat(php_stream *stream, php_stream_statbuf *ssb TSRMLS_DC) /* {{{ */
+{
+ return php_stream_socket_ops.stat(stream, ssb TSRMLS_CC);
+}
+/* }}} */
+
static inline int php_openssl_tcp_sockop_accept(php_stream *stream, php_openssl_netstream_data_t *sock,
php_stream_xport_param *xparam STREAMS_DC TSRMLS_DC)
{
@@ -653,22 +1906,9 @@ static inline int php_openssl_tcp_sockop_accept(php_stream *stream, php_openssl_
}
if (xparam->outputs.client && sock->enable_on_connect) {
- /* apply crypto */
- switch (sock->method) {
- case STREAM_CRYPTO_METHOD_SSLv23_CLIENT:
- sock->method = STREAM_CRYPTO_METHOD_SSLv23_SERVER;
- break;
- case STREAM_CRYPTO_METHOD_SSLv2_CLIENT:
- sock->method = STREAM_CRYPTO_METHOD_SSLv2_SERVER;
- break;
- case STREAM_CRYPTO_METHOD_SSLv3_CLIENT:
- sock->method = STREAM_CRYPTO_METHOD_SSLv3_SERVER;
- break;
- case STREAM_CRYPTO_METHOD_TLS_CLIENT:
- sock->method = STREAM_CRYPTO_METHOD_TLS_SERVER;
- break;
- default:
- break;
+ /* remove the client bit */
+ if (sock->method & STREAM_CRYPTO_IS_CLIENT) {
+ sock->method = ((sock->method >> 1) << 1);
}
clisockdata->method = sock->method;
@@ -687,6 +1927,7 @@ static inline int php_openssl_tcp_sockop_accept(php_stream *stream, php_openssl_
return xparam->outputs.client == NULL ? -1 : 0;
}
+
static int php_openssl_sockop_set_option(php_stream *stream, int option, int value, void *ptrparam TSRMLS_DC)
{
php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract;
@@ -853,21 +2094,22 @@ php_stream_ops php_openssl_socket_ops = {
php_openssl_sockop_set_option,
};
-static char * get_sni(php_stream_context *ctx, char *resourcename, long resourcenamelen, int is_persistent TSRMLS_DC) {
+static long get_crypto_method(php_stream_context *ctx, long crypto_method)
+{
+ zval **val;
- php_url *url;
+ if (ctx && php_stream_context_get_option(ctx, "ssl", "crypto_method", &val) == SUCCESS) {
+ convert_to_long_ex(val);
+ crypto_method = (long)Z_LVAL_PP(val);
+ crypto_method |= STREAM_CRYPTO_IS_CLIENT;
+ }
- if (ctx) {
- zval **val = NULL;
+ return crypto_method;
+}
- if (php_stream_context_get_option(ctx, "ssl", "SNI_enabled", &val) == SUCCESS && !zend_is_true(*val)) {
- return NULL;
- }
- if (php_stream_context_get_option(ctx, "ssl", "SNI_server_name", &val) == SUCCESS) {
- convert_to_string_ex(val);
- return pestrdup(Z_STRVAL_PP(val), is_persistent);
- }
- }
+static char *get_url_name(const char *resourcename, size_t resourcenamelen, int is_persistent TSRMLS_DC)
+{
+ php_url *url;
if (!resourcename) {
return NULL;
@@ -880,7 +2122,7 @@ static char * get_sni(php_stream_context *ctx, char *resourcename, long resource
if (url->host) {
const char * host = url->host;
- char * sni = NULL;
+ char * url_name = NULL;
size_t len = strlen(host);
/* skip trailing dots */
@@ -889,26 +2131,26 @@ static char * get_sni(php_stream_context *ctx, char *resourcename, long resource
}
if (len) {
- sni = pestrndup(host, len, is_persistent);
+ url_name = pestrndup(host, len, is_persistent);
}
php_url_free(url);
- return sni;
+ return url_name;
}
php_url_free(url);
return NULL;
}
-php_stream *php_openssl_ssl_socket_factory(const char *proto, long protolen,
- char *resourcename, long resourcenamelen,
+php_stream *php_openssl_ssl_socket_factory(const char *proto, size_t protolen,
+ const char *resourcename, size_t resourcenamelen,
const char *persistent_id, int options, int flags,
struct timeval *timeout,
php_stream_context *context STREAMS_DC TSRMLS_DC)
{
php_stream *stream = NULL;
php_openssl_netstream_data_t *sslsock = NULL;
-
+
sslsock = pemalloc(sizeof(php_openssl_netstream_data_t), persistent_id ? 1 : 0);
memset(sslsock, 0, sizeof(*sslsock));
@@ -924,10 +2166,10 @@ php_stream *php_openssl_ssl_socket_factory(const char *proto, long protolen,
/* we don't know the socket until we have determined if we are binding or
* connecting */
sslsock->s.socket = -1;
-
+
/* Initialize context as NULL */
sslsock->ctx = NULL;
-
+
stream = php_stream_alloc_rel(&php_openssl_socket_ops, sslsock, persistent_id, "r+");
if (stream == NULL) {
@@ -935,11 +2177,9 @@ php_stream *php_openssl_ssl_socket_factory(const char *proto, long protolen,
return NULL;
}
- sslsock->sni = get_sni(context, resourcename, resourcenamelen, !!persistent_id TSRMLS_CC);
-
if (strncmp(proto, "ssl", protolen) == 0) {
sslsock->enable_on_connect = 1;
- sslsock->method = STREAM_CRYPTO_METHOD_SSLv23_CLIENT;
+ sslsock->method = get_crypto_method(context, STREAM_CRYPTO_METHOD_ANY_CLIENT);
} else if (strncmp(proto, "sslv2", protolen) == 0) {
#ifdef OPENSSL_NO_SSL2
php_error_docref(NULL TSRMLS_CC, E_WARNING, "SSLv2 support is not compiled into the OpenSSL library PHP is linked against");
@@ -953,9 +2193,30 @@ php_stream *php_openssl_ssl_socket_factory(const char *proto, long protolen,
sslsock->method = STREAM_CRYPTO_METHOD_SSLv3_CLIENT;
} else if (strncmp(proto, "tls", protolen) == 0) {
sslsock->enable_on_connect = 1;
- sslsock->method = STREAM_CRYPTO_METHOD_TLS_CLIENT;
+ sslsock->method = get_crypto_method(context, STREAM_CRYPTO_METHOD_TLS_CLIENT);
+ } else if (strncmp(proto, "tlsv1.0", protolen) == 0) {
+ sslsock->enable_on_connect = 1;
+ sslsock->method = STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT;
+ } else if (strncmp(proto, "tlsv1.1", protolen) == 0) {
+#if OPENSSL_VERSION_NUMBER >= 0x10001001L
+ sslsock->enable_on_connect = 1;
+ sslsock->method = STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
+#else
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "TLSv1.1 support is not compiled into the OpenSSL library PHP is linked against");
+ return NULL;
+#endif
+ } else if (strncmp(proto, "tlsv1.2", protolen) == 0) {
+#if OPENSSL_VERSION_NUMBER >= 0x10001001L
+ sslsock->enable_on_connect = 1;
+ sslsock->method = STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
+#else
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "TLSv1.2 support is not compiled into the OpenSSL library PHP is linked against");
+ return NULL;
+#endif
}
+ sslsock->url_name = get_url_name(resourcename, resourcenamelen, !!persistent_id TSRMLS_CC);
+
return stream;
}