diff options
| author | Ferenc Kovacs <tyrael@php.net> | 2014-02-27 02:35:58 +0100 |
|---|---|---|
| committer | Ferenc Kovacs <tyrael@php.net> | 2014-02-27 02:35:58 +0100 |
| commit | 79bfef5ba316c6a8632b7e0e8ffdca771feb1334 (patch) | |
| tree | c45add10f718e89d7dff688d12ab355a08d50158 /ext/openssl | |
| parent | 326ab8b147485eeb1ca023807272bc1994bcd7de (diff) | |
| parent | 42a43d45317ac93c2ba934486879a0bc418bc34d (diff) | |
| download | php-git-79bfef5ba316c6a8632b7e0e8ffdca771feb1334.tar.gz | |
Merge branch 'PHP-5.6' into PHP-5.6.0
* PHP-5.6: (136 commits)
fix tests broken by 633f898f1520253d3530fe91fc82f68bca7c4627
add missing NEWS entry
add missing NEWS entry
add missing NEWS entry
add missing NEWS entry
add missing NEWS entry
add missing NEWS entry
Deprecate CN_match in favor of peer_name in SSL contexts
Remove cURL close policy related constants
Update stack size in tests/func/010.phpt
Don't add num_additional_args in SEND opcodes
Disallow use of positional args after unpacking
Store arg_num in fcall entry
Introduce zend_function_call_entry structure
move the default encoding NEWS entry to alpha3, as it was pushed after beta2 was tagged
rearrange the NEWS blocks a bit
add NEWS block for 5.6.0 beta1
restored that test part in ext/openssl to enable notify/wait
remove echo
fix stdin reading in new openssl tests
...
Diffstat (limited to 'ext/openssl')
35 files changed, 2205 insertions, 753 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 d57b3eafde..88ae9a1000 100644..100755 --- a/ext/openssl/openssl.c +++ b/ext/openssl/openssl.c @@ -53,6 +53,15 @@ #include <openssl/ssl.h> #include <openssl/pkcs12.h> +/* Windows platform includes */ +#ifdef PHP_WIN32 +# include <Wincrypt.h> +/* These are from Wincrypt.h, they conflict with OpenSSL */ +# undef X509_NAME +# undef X509_CERT_PAIR +# undef X509_EXTENSIONS +#endif + /* Common */ #include <time.h> @@ -79,6 +88,19 @@ #endif #define DEBUG_SMIME 0 +#if !defined(OPENSSL_NO_EC) && defined(EVP_PKEY_EC) +#define HAVE_EVP_PKEY_EC 1 +#endif + +#define PHP_OPENSSL_DEFAULT_STREAM_VERIFY_DEPTH 9 +#define PHP_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" + /* FIXME: Use the openssl constants instead of * enum. It is now impossible to match real values * against php constants. Also sorry to break the @@ -89,7 +111,7 @@ enum php_openssl_key_type { OPENSSL_KEYTYPE_DSA, OPENSSL_KEYTYPE_DH, OPENSSL_KEYTYPE_DEFAULT = OPENSSL_KEYTYPE_RSA, -#ifdef EVP_PKEY_EC +#ifdef HAVE_EVP_PKEY_EC OPENSSL_KEYTYPE_EC = OPENSSL_KEYTYPE_DH +1 #endif }; @@ -420,11 +442,16 @@ 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) @@ -567,6 +594,12 @@ 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); +} +/* }}} */ + /* openssl -> PHP "bridging" */ /* true global; readonly after module startup */ static char default_ssl_conf_filename[MAXPATHLEN]; @@ -605,6 +638,8 @@ 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); +#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))) + static void add_assoc_name_entry(zval * val, char * key, X509_NAME * name, int shortname TSRMLS_DC) /* {{{ */ { zval **data; @@ -695,7 +730,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; } @@ -1075,8 +1110,8 @@ 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_ALL, NULL) - PHP_INI_ENTRY("openssl.capath", NULL, PHP_INI_ALL, NULL) + PHP_INI_ENTRY("openssl.cafile", NULL, PHP_INI_PERDIR, NULL) + PHP_INI_ENTRY("openssl.capath", NULL, PHP_INI_PERDIR, NULL) PHP_INI_END() /* }}} */ @@ -1147,6 +1182,10 @@ 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", PHP_OPENSSL_DEFAULT_STREAM_CIPHERS, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("OPENSSL_DEFAULT_STREAM_VERIFY_DEPTH", PHP_OPENSSL_DEFAULT_STREAM_VERIFY_DEPTH, 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); @@ -1169,7 +1208,7 @@ PHP_MINIT_FUNCTION(openssl) REGISTER_LONG_CONSTANT("OPENSSL_KEYTYPE_DSA", OPENSSL_KEYTYPE_DSA, CONST_CS|CONST_PERSISTENT); #endif REGISTER_LONG_CONSTANT("OPENSSL_KEYTYPE_DH", OPENSSL_KEYTYPE_DH, CONST_CS|CONST_PERSISTENT); -#ifdef EVP_PKEY_EC +#ifdef HAVE_EVP_PKEY_EC REGISTER_LONG_CONSTANT("OPENSSL_KEYTYPE_EC", OPENSSL_KEYTYPE_EC, CONST_CS|CONST_PERSISTENT); #endif @@ -1202,6 +1241,7 @@ 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); @@ -1247,6 +1287,7 @@ 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); @@ -1263,6 +1304,26 @@ PHP_MSHUTDOWN_FUNCTION(openssl) /* {{{ 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: @@ -3468,6 +3529,15 @@ static int php_openssl_is_private_key(EVP_PKEY* pkey TSRMLS_DC) } break; #endif +#ifdef HAVE_EVP_PKEY_EC + case EVP_PKEY_EC: + assert(pkey->pkey.ec != NULL); + + if ( NULL == EC_KEY_get0_private_key(pkey->pkey.ec)) { + return 0; + } + break; +#endif default: php_error_docref(NULL TSRMLS_CC, E_WARNING, "key type not supported in this PHP build!"); break; @@ -3868,7 +3938,7 @@ PHP_FUNCTION(openssl_pkey_get_details) } break; -#ifdef EVP_PKEY_EC +#ifdef HAVE_EVP_PKEY_EC case EVP_PKEY_EC: ktype = OPENSSL_KEYTYPE_EC; break; @@ -5033,10 +5103,10 @@ static zend_bool matches_san_list(X509 *peer, const char *subject_name TSRMLS_DC ASN1_STRING_to_UTF8(&cert_name, san->d.dNSName); /* prevent null byte poisoning */ - if (san_name_len != strlen(cert_name)) { + 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, cert_name) == 0; + is_match = strcasecmp(subject_name, (const char*)cert_name) == 0; } OPENSSL_free(cert_name); @@ -5075,64 +5145,92 @@ static zend_bool matches_common_name(X509 *peer, const char *subject_name TSRMLS int php_openssl_apply_verification_policy(SSL *ssl, X509 *peer, php_stream *stream TSRMLS_DC) /* {{{ */ { zval **val = NULL; - char *cnmatch = NULL; + char *peer_name = NULL; int err; - php_openssl_netstream_data_t *sslsock; - - sslsock = (php_openssl_netstream_data_t*)stream->abstract; + zend_bool must_verify_peer; + zend_bool must_verify_peer_name; + zend_bool must_verify_fingerprint; + zend_bool has_cnmatch_ctx_opt; + php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract; - if (!(GET_VER_OPT("verify_peer") || sslsock->is_client) - || (GET_VER_OPT("verify_peer") && !zval_is_true(*val)) - ) { - return SUCCESS; - } + must_verify_peer = GET_VER_OPT("verify_peer") + ? zend_is_true(*val) + : sslsock->is_client; - if (peer == NULL) { + 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; } - 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 */ + /* 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; - } - /* 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; + 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 */ - - if (GET_VER_OPT("peer_fingerprint")) { + /* 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"); + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "Peer fingerprint doesn't match" + ); return FAILURE; } } else { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "Expected peer fingerprint must be a string or an array"); + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "Expected peer fingerprint must be a string or an array" + ); } } - GET_VER_OPT_STRING("CN_match", cnmatch); + /* verify the host name presented in the peer certificate */ + if (must_verify_peer_name) { + GET_VER_OPT_STRING("peer_name", peer_name); - /* If no CN_match was specified assign the autodetected name when connecting as a client */ - if (cnmatch == NULL && sslsock->is_client) { - cnmatch = sslsock->url_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 (cnmatch) { - if (matches_san_list(peer, cnmatch TSRMLS_CC)) { - return SUCCESS; - } else if (matches_common_name(peer, cnmatch TSRMLS_CC)) { - return SUCCESS; + 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; } @@ -5161,58 +5259,337 @@ static int passwd_callback(char *buf, int num, int verify, void *data) /* {{{ */ } /* }}} */ -SSL *php_SSL_new_from_context(SSL_CTX *ctx, php_stream *stream 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) /* {{{ */ { - zval **val = NULL; - char *cafile = NULL; - char *capath = NULL; - char *certfile = NULL; - char *cipherlist = NULL; - int ok = 1; + PCCERT_CONTEXT cert_ctx = NULL; + PCCERT_CHAIN_CONTEXT cert_chain_ctx = NULL; - ERR_clear_error(); + php_stream *stream; + php_openssl_netstream_data_t *sslsock; + zval **val; + zend_bool is_self_signed = 0; - /* look at context options in the stream and set appropriate verification flags */ - if (GET_VER_OPT("verify_peer") && !zval_is_true(*val)) { - SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL); - } else { + TSRMLS_FETCH(); - /* turn on verification callback */ - SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, verify_callback); + 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; - /* CA stuff */ - GET_VER_OPT_STRING("cafile", cafile); - GET_VER_OPT_STRING("capath", capath); + der_len = i2d_X509(x509_store_ctx->cert, &der_buf); + if (der_len < 0) { + unsigned long err_code, e; + char err_buf[512]; - if (!cafile) { - zend_bool exists = 1; - cafile = zend_ini_string_ex("openssl.cafile", sizeof("openssl.cafile"), 0, &exists); + 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); } + } - if (!capath) { - zend_bool exists = 1; - capath = zend_ini_string_ex("openssl.capath", sizeof("openssl.capath"), 0, &exists); + { /* 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)}; + DWORD chain_flags = 0; + unsigned long verify_depth = PHP_OPENSSL_DEFAULT_STREAM_VERIFY_DEPTH; + unsigned int i; + + enhkey_usage.cUsageIdentifier = 0; + enhkey_usage.rgpszUsageIdentifier = NULL; + cert_usage.dwType = USAGE_MATCH_TYPE_AND; + cert_usage.Usage = enhkey_usage; + chain_params.RequestedUsage = cert_usage; + chain_flags = CERT_CHAIN_CACHE_END_CERT | CERT_CHAIN_REVOCATION_CHECK_CHAIN; + + if (!CertGetCertificateChain(NULL, cert_ctx, NULL, NULL, &chain_params, chain_flags, NULL, &cert_chain_ctx)) { + CertFreeCertificateContext(cert_ctx); + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error getting certificate chain: %s", php_win_err()); + RETURN_CERT_VERIFY_FAILURE(SSL_R_CERTIFICATE_VERIFY_FAILED); } - 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; + /* 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 */ + if (GET_VER_OPT("verify_depth")) { + convert_to_long_ex(val); + verify_depth = (unsigned long)Z_LVAL_PP(val); + } + + for (i = 0; i < cert_chain_ctx->cChain; i++) { + if (cert_chain_ctx->rgpChain[i]->cElement > verify_depth) { + 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"); + 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) { + OPENSSL_free(cert_name_utf8); + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to convert %s to wide character string", cert_name_utf8); + 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) { + OPENSSL_free(cert_name_utf8); + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to convert %s to wide character string", cert_name_utf8); + 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); + + CertFreeCertificateChain(cert_chain_ctx); + CertFreeCertificateContext(cert_ctx); + efree(server_name); + + 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") && zval_is_true(*val)) { + /* allow self-signed certs */ + X509_STORE_CTX_set_error(x509_store_ctx, X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT); + } else { + RETURN_CERT_VERIFY_FAILURE(SSL_R_CERTIFICATE_VERIFY_FAILED); } + } + } + + 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; + 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 { - 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 NULL; + efree(line); + goto cert_start; + } + } + + 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; + } + } + + 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; + } + + 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 void enable_peer_verify_callback(SSL_CTX *ctx, php_stream *stream) /* {{{ */ +{ + zval **val = NULL; + + /* turn on verification callback */ + SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, verify_callback); + + 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_depth(ctx, PHP_OPENSSL_DEFAULT_STREAM_VERIFY_DEPTH); + } +} +/* }}} */ + +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); + + 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 0; } } - if (GET_VER_OPT("verify_depth")) { - convert_to_long_ex(val); - SSL_CTX_set_verify_depth(ctx, Z_LVAL_PP(val)); + enable_peer_verify_callback(ctx, stream); + } 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 0; } + + enable_peer_verify_callback(ctx, stream); +#endif + } + + return 1; +} +/* }}} */ + +static int disable_peer_verification(SSL_CTX *ctx, php_stream *stream TSRMLS_DC) /* {{{ */ +{ + SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL); + + return 1; +} +/* }}} */ + +SSL *php_SSL_new_from_context(SSL_CTX *ctx, php_stream *stream TSRMLS_DC) /* {{{ */ +{ + zval **val = NULL; + char *certfile = NULL; + char *cipherlist = NULL; + int ok = 1; + SSL *ssl; + + 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)) { + ok = disable_peer_verification(ctx, stream TSRMLS_CC); + } else { + ok = enable_peer_verification(ctx, stream TSRMLS_CC); + } + + if (!ok) { + return NULL; } /* callback for the passphrase (for localcert) */ @@ -5223,7 +5600,7 @@ SSL *php_SSL_new_from_context(SSL_CTX *ctx, php_stream *stream TSRMLS_DC) /* {{{ GET_VER_OPT_STRING("ciphers", cipherlist); if (!cipherlist) { - cipherlist = "DEFAULT"; + cipherlist = PHP_OPENSSL_DEFAULT_STREAM_CIPHERS; } if (SSL_CTX_set_cipher_list(ctx, cipherlist) != 1) { return NULL; @@ -5279,17 +5656,14 @@ SSL *php_SSL_new_from_context(SSL_CTX *ctx, php_stream *stream TSRMLS_DC) /* {{{ } } - if (ok) { - SSL *ssl = SSL_new(ctx); + ssl = SSL_new(ctx); - if (ssl) { - /* map SSL => stream */ - SSL_set_ex_data(ssl, ssl_stream_data_index, stream); - } - return ssl; + if (ssl) { + /* map SSL => stream */ + SSL_set_ex_data(ssl, ssl_stream_data_index, stream); } - return NULL; + return ssl; } /* }}} */ diff --git a/ext/openssl/php_openssl.h b/ext/openssl/php_openssl.h index 01e976f1f4..a823d30bd8 100644 --- a/ext/openssl/php_openssl.h +++ b/ext/openssl/php_openssl.h @@ -29,6 +29,10 @@ 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 DEFAULT_RENEG_LIMIT 2 +#define DEFAULT_RENEG_WINDOW 300 + php_stream_transport_factory_func php_openssl_ssl_socket_factory; PHP_MINIT_FUNCTION(openssl); @@ -85,6 +89,8 @@ 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/php_openssl_structs.h b/ext/openssl/php_openssl_structs.h index 13f8f320f8..562c756cd3 100644 --- a/ext/openssl/php_openssl_structs.h +++ b/ext/openssl/php_openssl_structs.h @@ -22,6 +22,14 @@ #include "php_network.h" #include <openssl/ssl.h> +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! @@ -36,6 +44,7 @@ typedef struct _php_openssl_netstream_data_t { int is_client; int ssl_active; php_stream_xport_crypt_method_t method; + php_openssl_handshake_bucket_t *reneg; char *url_name; unsigned state_set:1; unsigned _spare:31; 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 1de4eacd01..80bd61c287 100644 --- a/ext/openssl/tests/bug46127.phpt +++ b/ext/openssl/tests/bug46127.phpt @@ -2,60 +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); -$ctx = stream_context_create(['ssl' => [ - 'verify_peer' => false -]]); -$sock = stream_socket_client("ssl://127.0.0.1:{$port}", $errno, $errstr, 30, STREAM_CLIENT_CONNECT, $ctx); -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, 1, $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 b78ce57074..5211c23d20 100644 --- a/ext/openssl/tests/bug48182.phpt +++ b/ext/openssl/tests/bug48182.phpt @@ -1,93 +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"; - $pem = dirname(__FILE__) . '/bug54992.pem'; - $ssl_params = array( 'verify_peer' => false, 'allow_self_signed' => true, 'local_cert' => $pem); - $ssl = array('ssl' => $ssl_params); + $server = stream_socket_server($serverUri, $errno, $errstr, $serverFlags, $serverCtx); + phpt_notify(); - $context = stream_context_create($ssl); - $sock = stream_socket_server($host, $errno, $errstr, $flags, $context); - if (!$sock) return false; + $client = @stream_socket_accept($server, 1); - $link = stream_socket_accept($sock); - if (!$link) return false; // bad link? + $data = "Sending bug48182\n" . fread($client, 8192); + fwrite($client, $data); +CODE; - $r = array($link); - $w = array(); - $e = array(); - if (stream_select($r, $w, $e, 1, 0) != 0) - $data .= fread($link, 8192); +$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(); - $w = array($link); - if (stream_select($r, $w, $e, 1, 0) != 0) - $wrote = fwrite($link, $data, strlen($data)); + phpt_wait(); + $client = stream_socket_client($serverUri, $errno, $errstr, 10, $clientFlags, $clientCtx); - // close stuff - fclose($link); - fclose($sock); + $data = "Sending data over to SSL server in async mode with contents like Hello World\n"; - 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"; - $context = stream_context_create(array('ssl' => array( - 'cafile' => dirname(__FILE__) . '/bug54992-ca.pem', - 'CN_match' => 'bug54992.local' - ))); - $socket = stream_socket_client($host, $errno, $errstr, 10, $flags, $context); - 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..bcb33fdd8a 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, 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=`buga_buga' in %s on line %d diff --git a/ext/openssl/tests/bug64802.phpt b/ext/openssl/tests/bug64802.phpt index 9a59701494..be0b5f9b5b 100644 --- a/ext/openssl/tests/bug64802.phpt +++ b/ext/openssl/tests/bug64802.phpt @@ -3,6 +3,7 @@ Bug #64802: openssl_x509_parse fails to parse subject properly in some cases --SKIPIF-- <?php if (!extension_loaded("openssl")) die("skip"); +if (!defined(OPENSSL_KEYTYPE_EC)) die("skip no EC available); ?> --FILE-- <?php diff --git a/ext/openssl/tests/bug65538.phar b/ext/openssl/tests/bug65538.phar Binary files differnew file mode 100644 index 0000000000..ae0bd29c6e --- /dev/null +++ b/ext/openssl/tests/bug65538.phar 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..760b720e73 --- /dev/null +++ b/ext/openssl/tests/bug65538_002.phpt @@ -0,0 +1,19 @@ +--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 create an SSL handle 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.phpt b/ext/openssl/tests/bug65729.phpt index c0ee4443eb..b405b7213d 100644 --- a/ext/openssl/tests/bug65729.phpt +++ b/ext/openssl/tests/bug65729.phpt @@ -2,40 +2,46 @@ Bug #65729: CN_match gives false positive when wildcard is used --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__ . "/bug65729.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); - -$expected_names = array('foo.test.com.sg', 'foo.test.com', 'FOO.TEST.COM', 'foo.bar.test.com'); - -$pid = pcntl_fork(); -if ($pid == -1) { - die('could not fork'); -} else if ($pid) { - foreach ($expected_names as $expected_name) { - $contextC = stream_context_create(array( - 'ssl' => array( - 'verify_peer' => true, - 'allow_self_signed' => true, - 'CN_match' => $expected_name, - ) - )); - var_dump(stream_socket_client("ssl://127.0.0.1:64321", $errno, $errstr, 1, - STREAM_CLIENT_CONNECT, $contextC)); - } -} else { - @pcntl_wait($status); - foreach ($expected_names as $name) { - @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__ . '/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, 1, $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 diff --git a/ext/openssl/tests/bug66501.phpt b/ext/openssl/tests/bug66501.phpt new file mode 100644 index 0000000000..7ad5e21749 --- /dev/null +++ b/ext/openssl/tests/bug66501.phpt @@ -0,0 +1,22 @@ +--TEST--
+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");
+--FILE--
+<?php
+$pkey = 'ASN1 OID: prime256v1
+-----BEGIN EC PARAMETERS-----
+BggqhkjOPQMBBw==
+-----END EC PARAMETERS-----
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEILPkqoeyM7XgwYkuSj3077lrsrfWJK5LqMolv+m2oOjZoAoGCCqGSM49
+AwEHoUQDQgAEPq4hbIWHvB51rdWr8ejrjWo4qVNWVugYFtPg/xLQw0mHkIPZ4DvK
+sqOTOnMoezkbSmVVMuwz9flvnqHGmQvmug==
+-----END EC PRIVATE KEY-----';
+$key = openssl_pkey_get_private($pkey);
+$res = openssl_sign($data ='alpha', $sign, $key, 'ecdsa-with-SHA1');
+var_dump($res);
+--EXPECTF--
+bool(true)
diff --git a/ext/openssl/tests/openssl_peer_fingerprint.phpt b/ext/openssl/tests/openssl_peer_fingerprint.phpt index 2e4c192c03..0bd91d543a 100644 --- a/ext/openssl/tests/openssl_peer_fingerprint.phpt +++ b/ext/openssl/tests/openssl_peer_fingerprint.phpt @@ -2,58 +2,48 @@ Testing peer fingerprint on connection --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(); +$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' + ]]); -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); + $server = stream_socket_server($serverUri, $errno, $errstr, $serverFlags, $serverCtx); + phpt_notify(); + @stream_socket_accept($server, 1); + @stream_socket_accept($server, 1); +CODE; -$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', - 'capture_peer_cert' => true, - 'CN_match' => 'bug54992.local', - 'peer_fingerprint' => '81cafc260aa8d82956ebc6212a362ece', - ) - ) - ); - // should be: 81cafc260aa8d82956ebc6212a362ecc - var_dump(stream_socket_client("ssl://127.0.0.1:64321", $errno, $errstr, 1, - STREAM_CLIENT_CONNECT, $contextC)); +$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', + ]]); - $contextC = stream_context_create( - array( - 'ssl' => array( - 'verify_peer' => true, - 'cafile' => __DIR__ . '/bug54992-ca.pem', - 'capture_peer_cert' => true, - 'CN_match' => 'bug54992.local', - 'peer_fingerprint' => array( - 'sha256' => '78ea579f2c3b439359dec5dac9d445108772927427c4780037e87df3799a0aa0', - ), - ) - ) - ); + phpt_wait(); - 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); - @stream_socket_accept($server, 1); -} + // should be: 81cafc260aa8d82956ebc6212a362ecc + stream_context_set_option($clientCtx, 'ssl', 'peer_fingerprint', '81cafc260aa8d82956ebc6212a362ece'); + var_dump(stream_socket_client($serverUri, $errno, $errstr, 1, $clientFlags, $clientCtx)); + + stream_context_set_option($clientCtx, 'ssl', 'peer_fingerprint', [ + 'sha256' => '78ea579f2c3b439359dec5dac9d445108772927427c4780037e87df3799a0aa0', + ]); + var_dump(stream_socket_client($serverUri, $errno, $errstr, 1, $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 diff --git a/ext/openssl/tests/peer_verification.phpt b/ext/openssl/tests/peer_verification.phpt index 7c3347fd65..6aff34ddd8 100644 --- a/ext/openssl/tests/peer_verification.phpt +++ b/ext/openssl/tests/peer_verification.phpt @@ -2,55 +2,60 @@ Peer verification enabled for client streams --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 -$flags = STREAM_SERVER_BIND | STREAM_SERVER_LISTEN; -$ctx = stream_context_create(['ssl' => [ - 'local_cert' => __DIR__ . '/bug54992.pem', - 'allow_self_signed' => true -]]); -$server = stream_socket_server('ssl://127.0.0.1:64321', $errno, $errstr, $flags, $ctx); - -$pid = pcntl_fork(); -if ($pid == -1) { - die('could not fork'); -} else if ($pid) { - // Expected to fail -- no CA File present - var_dump(@stream_socket_client("ssl://127.0.0.1:64321", $errno, $errstr, 1, STREAM_CLIENT_CONNECT)); - - // Expected to fail -- no CA File present - $ctx = stream_context_create(['ssl' => ['verify_peer' => true]]); - var_dump(@stream_socket_client("ssl://127.0.0.1:64321", $errno, $errstr, 1, STREAM_CLIENT_CONNECT, $ctx)); - - // Should succeed with peer verification disabled in context - $ctx = stream_context_create(['ssl' => ['verify_peer' => false]]); - var_dump(stream_socket_client("ssl://127.0.0.1:64321", $errno, $errstr, 1, STREAM_CLIENT_CONNECT, $ctx)); - - // Should succeed with CA file specified in context - $ctx = stream_context_create(['ssl' => [ - 'cafile' => __DIR__ . '/bug54992-ca.pem', - 'CN_match' => 'bug54992.local', - ]]); - var_dump(stream_socket_client("ssl://127.0.0.1:64321", $errno, $errstr, 1, STREAM_CLIENT_CONNECT, $ctx)); - - // Should succeed with globally available CA file specified via php.ini - $cafile = __DIR__ . '/bug54992-ca.pem'; - ini_set('openssl.cafile', $cafile); - var_dump(stream_socket_client("ssl://127.0.0.1:64321", $errno, $errstr, 1, STREAM_CLIENT_CONNECT, $ctx)); - -} else { - @pcntl_wait($status); - @stream_socket_accept($server, 3); - @stream_socket_accept($server, 3); - @stream_socket_accept($server, 3); - @stream_socket_accept($server, 3); - @stream_socket_accept($server, 3); -} +$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) -resource(%d) of type (stream) diff --git a/ext/openssl/tests/san_peer_matching.phpt b/ext/openssl/tests/san_peer_matching.phpt index 4e6531d6cc..0e1f30cb64 100644 --- a/ext/openssl/tests/san_peer_matching.phpt +++ b/ext/openssl/tests/san_peer_matching.phpt @@ -2,53 +2,43 @@ Peer verification matches SAN names --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(array( - 'ssl' => array( - 'local_cert' => __DIR__ . '/san-cert.pem', - '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__ . '/san-ca.pem', - 'CN_match' => 'example.org', - ) - ) - ); - var_dump(stream_socket_client("ssl://127.0.0.1:64321", $errno, $errstr, 1, - STREAM_CLIENT_CONNECT, $contextC)); - - $contextC = stream_context_create(array( - 'ssl' => array( - 'verify_peer' => true, - 'cafile' => __DIR__ . '/san-ca.pem', - 'CN_match' => 'moar.example.org', - ) - )); - - 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); - @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__ . '/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) diff --git a/ext/openssl/tests/session_meta_capture.phpt b/ext/openssl/tests/session_meta_capture.phpt new file mode 100644 index 0000000000..62cdffe686 --- /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, 1, $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 2f76a9f918..e6c05f7ec2 100644 --- a/ext/openssl/tests/sni_001.phpt +++ b/ext/openssl/tests/sni_001.phpt @@ -20,13 +20,18 @@ SNI 001 * the server returned. */ -function context() { - return stream_context_create(array( - 'ssl' => array( - 'capture_peer_cert' => true, - 'verify_peer' => false - ), - )); +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) { @@ -73,13 +78,13 @@ 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"; @@ -97,14 +102,14 @@ 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(); +$context = context('bob.sni.velox.ch'); stream_context_set_option($context, 'ssl', 'SNI_server_name', 'bob.sni.velox.ch'); do_ssl_test('ssl://mallory.sni.velox.ch:443', $context); 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..b72b4d62f2 --- /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, 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)); + + 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_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) +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..b2f2ae3ad0 --- /dev/null +++ b/ext/openssl/tests/stream_server_reneg_limit.phpt @@ -0,0 +1,86 @@ +--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); + pcntl_wait($status); +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..ae97ea1269 --- /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, 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_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.phpt b/ext/openssl/tests/streams_crypto_method.phpt index 981f56b399..84f7934308 100644 --- a/ext/openssl/tests/streams_crypto_method.phpt +++ b/ext/openssl/tests/streams_crypto_method.phpt @@ -1,78 +1,52 @@ --TEST-- Specific crypto method for ssl:// transports. --SKIPIF-- -<?php -if (!extension_loaded('openssl')) die('skip, openssl required'); -if (!extension_loaded('pcntl')) die('skip, pcntl required'); -?> +<?php +if (!extension_loaded("openssl")) die("skip openssl not loaded"); +if (!function_exists("proc_open")) die("skip no proc_open"); --FILE-- <?php -function client($port, $method) { - $ctx = stream_context_create(); - stream_context_set_option($ctx, 'ssl', 'crypto_method', $method); - stream_context_set_option($ctx, 'ssl', 'verify_peer', false); - - $fp = @fopen('https://127.0.0.1:' . $port . '/', 'r', false, $ctx); - if ($fp) { - fpassthru($fp); - fclose($fp); - } -} - -function server($port, $transport) { - $context = stream_context_create(); - - stream_context_set_option($context, 'ssl', 'local_cert', dirname(__FILE__) . '/streams_crypto_method.pem'); - stream_context_set_option($context, 'ssl', 'allow_self_signed', true); - stream_context_set_option($context, 'ssl', 'verify_peer', false); - - $server = stream_socket_server($transport . '127.0.0.1:' . $port, $errno, $errstr, STREAM_SERVER_BIND|STREAM_SERVER_LISTEN, $context); - - $client = @stream_socket_accept($server); - - if ($client) { - $in = ''; - while (!preg_match('/\r?\n\r?\n/', $in)) { - $in .= fread($client, 2048); - } - - $response = <<<EOS -HTTP/1.1 200 OK -Content-Type: text/plain -Content-Length: 13 -Connection: close - -Hello World! - -EOS; - - fwrite($client, $response); - fclose($client); - exit(); - } -} - -$port1 = rand(15000, 16000); -$port2 = rand(16001, 17000); - -$pid1 = pcntl_fork(); -$pid2 = pcntl_fork(); - -if ($pid1 == 0 && $pid2 != 0) { - server($port1, 'sslv3://'); - exit; -} - -if ($pid1 != 0 && $pid2 == 0) { - server($port2, 'sslv3://'); - exit; -} - -client($port1, STREAM_CRYPTO_METHOD_SSLv3_CLIENT); -client($port2, STREAM_CRYPTO_METHOD_SSLv2_CLIENT); - -pcntl_waitpid($pid1, $status); -pcntl_waitpid($pid2, $status); -?> +$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..7479259426 --- /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, 1); + } +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, 1, $flags, $ctx); + var_dump($client); + + $client = @stream_socket_client("sslv3://127.0.0.1:64321", $errno, $errstr, 1, $flags, $ctx); + var_dump($client); + + $client = @stream_socket_client("tlsv1.2://127.0.0.1:64321", $errno, $errstr, 1, $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..3e067a14b7 --- /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, 1); + } +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, 1, $flags, $ctx); + var_dump($client); + + $client = @stream_socket_client("sslv3://127.0.0.1:64321", $errno, $errstr, 1, $flags, $ctx); + var_dump($client); + + $client = @stream_socket_client("tlsv1.2://127.0.0.1:64321", $errno, $errstr, 1, $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_001.phpt b/ext/openssl/tests/tlsv1.1_wrapper_001.phpt deleted file mode 100644 index 56211f0b96..0000000000 --- a/ext/openssl/tests/tlsv1.1_wrapper_001.phpt +++ /dev/null @@ -1,46 +0,0 @@ ---TEST-- -tlsv1.1 stream wrapper ---SKIPIF-- -<?php -if (!extension_loaded("openssl")) die("skip"); -if (OPENSSL_VERSION_NUMBER < 0x10001001) die("skip OpenSSL 1.0.1 required"); -if (!function_exists('pcntl_fork')) die("skip no fork"); ---FILE-- -<?php -$flags = STREAM_SERVER_BIND|STREAM_SERVER_LISTEN; -$ctx = stream_context_create(array('ssl' => array( - 'local_cert' => __DIR__ . '/streams_crypto_method.pem', -))); - -$server = stream_socket_server('tlsv1.1://127.0.0.1:64321', $errno, $errstr, $flags, $ctx); -var_dump($server); - -$pid = pcntl_fork(); -if ($pid == -1) { - die('could not fork'); -} elseif ($pid) { - $flags = STREAM_CLIENT_CONNECT; - $ctx = stream_context_create(array('ssl' => array( - 'verify_peer' => false - ))); - - $client = stream_socket_client("tlsv1.1://127.0.0.1:64321", $errno, $errstr, 1, $flags, $ctx); - var_dump($client); - - $client = @stream_socket_client("sslv3://127.0.0.1:64321", $errno, $errstr, 1, $flags, $ctx); - var_dump($client); - - $client = @stream_socket_client("tlsv1.2://127.0.0.1:64321", $errno, $errstr, 1, $flags, $ctx); - var_dump($client); - -} else { - @pcntl_wait($status); - for ($i=0; $i < 3; $i++) { - @stream_socket_accept($server, 1); - } -} ---EXPECTF-- -resource(%d) of type (stream) -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..ca967d18b4 --- /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, 1); + } +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, 1, $flags, $ctx); + var_dump($client); + + $client = @stream_socket_client("sslv3://127.0.0.1:64321", $errno, $errstr, 1, $flags, $ctx); + var_dump($client); + + $client = @stream_socket_client("tlsv1.1://127.0.0.1:64321", $errno, $errstr, 1, $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_002.phpt b/ext/openssl/tests/tlsv1.2_wrapper_002.phpt deleted file mode 100644 index cb3f4106c7..0000000000 --- a/ext/openssl/tests/tlsv1.2_wrapper_002.phpt +++ /dev/null @@ -1,46 +0,0 @@ ---TEST-- -tlsv1.2 stream wrapper ---SKIPIF-- -<?php -if (!extension_loaded("openssl")) die("skip"); -if (OPENSSL_VERSION_NUMBER < 0x10001001) die("skip OpenSSL 1.0.1 required"); -if (!function_exists('pcntl_fork')) die("skip no fork"); ---FILE-- -<?php -$flags = STREAM_SERVER_BIND|STREAM_SERVER_LISTEN; -$ctx = stream_context_create(array('ssl' => array( - 'local_cert' => __DIR__ . '/streams_crypto_method.pem', -))); - -$server = stream_socket_server('tlsv1.2://127.0.0.1:64321', $errno, $errstr, $flags, $ctx); -var_dump($server); - -$pid = pcntl_fork(); -if ($pid == -1) { - die('could not fork'); -} elseif ($pid) { - $flags = STREAM_CLIENT_CONNECT; - $ctx = stream_context_create(array('ssl' => array( - 'verify_peer' => false - ))); - - $client = stream_socket_client("tlsv1.2://127.0.0.1:64321", $errno, $errstr, 1, $flags, $ctx); - var_dump($client); - - $client = @stream_socket_client("sslv3://127.0.0.1:64321", $errno, $errstr, 1, $flags, $ctx); - var_dump($client); - - $client = @stream_socket_client("tlsv1.1://127.0.0.1:64321", $errno, $errstr, 1, $flags, $ctx); - var_dump($client); - -} else { - @pcntl_wait($status); - for ($i=0; $i < 3; $i++) { - @stream_socket_accept($server, 1); - } -} ---EXPECTF-- -resource(%d) of type (stream) -resource(%d) of type (stream) -bool(false) -bool(false) diff --git a/ext/openssl/xp_ssl.c b/ext/openssl/xp_ssl.c index 523062e043..7424cd8de2 100644 --- a/ext/openssl/xp_ssl.c +++ b/ext/openssl/xp_ssl.c @@ -37,8 +37,21 @@ #include <sys/select.h> #endif +#if !defined(OPENSSL_NO_ECDH) && OPENSSL_VERSION_NUMBER >= 0x0090800fL +#define HAVE_ECDH +#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) + 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); +extern php_stream* php_openssl_get_stream_from_ssl_handle(const SSL *ssl); int php_openssl_get_x509_list_id(void); php_stream_ops php_openssl_socket_ops; @@ -196,7 +209,13 @@ static size_t php_openssl_sockop_read(php_stream *stream, char *buf, size_t coun do { nr_bytes = SSL_read(sslsock->ssl_handle, buf, count); - if (nr_bytes <= 0) { + 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)); @@ -222,13 +241,13 @@ static size_t php_openssl_sockop_read(php_stream *stream, char *buf, size_t coun 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 + if (close_handle) { if (sslsock->ssl_active) { SSL_shutdown(sslsock->ssl_handle); @@ -270,6 +289,10 @@ static int php_openssl_sockop_close(php_stream *stream, int close_handle TSRMLS_ 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; @@ -285,112 +308,239 @@ static int php_openssl_sockop_stat(php_stream *stream, php_stream_statbuf *ssb T return php_stream_socket_ops.stat(stream, ssb TSRMLS_CC); } - -static inline int php_openssl_setup_crypto(php_stream *stream, - php_openssl_netstream_data_t *sslsock, - php_stream_xport_crypto_param *cparam - TSRMLS_DC) +static inline void limit_handshake_reneg(const SSL *ssl) /* {{{ */ { - const SSL_METHOD *method; - long ssl_ctx_options = SSL_OP_ALL; - - 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; + 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] = ¶m; + + /* 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 { - return 0; + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "SSL: client-initiated handshake rate limit exceeded by peer"); } } +} +/* }}} */ - /* need to do slightly different things, based on client/server method, - * so lets remember which method was selected */ +static void php_openssl_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); + } +} +/* }}} */ - 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; +static inline void init_handshake_limiting(php_stream *stream, php_openssl_netstream_data_t *sslsock) /* {{{ */ +{ + zval **val; + long limit = DEFAULT_RENEG_LIMIT; + long window = 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_CTX_set_info_callback(sslsock->ctx, php_openssl_info_callback); +} +/* }}} */ + +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 - sslsock->is_client = 1; - method = SSLv2_client_method(); - break; + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "SSLv2 support is not compiled into the OpenSSL library PHP is linked against"); + return NULL; #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_TLSv1_1_CLIENT: + } 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 - sslsock->is_client = 1; - method = TLSv1_1_client_method(); - break; + 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 -1; + 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 - case STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT: + } else if (method_value == STREAM_CRYPTO_METHOD_TLSv1_2) { #if OPENSSL_VERSION_NUMBER >= 0x10001001L - sslsock->is_client = 1; - method = TLSv1_2_client_method(); - break; + 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 -1; + 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 - 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; -#else - sslsock->is_client = 0; - method = SSLv2_server_method(); - break; + } 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 - case STREAM_CRYPTO_METHOD_TLS_SERVER: - sslsock->is_client = 0; - method = TLSv1_server_method(); - break; - case STREAM_CRYPTO_METHOD_TLSv1_1_SERVER: -#if OPENSSL_VERSION_NUMBER >= 0x10001001L - sslsock->is_client = 0; - method = TLSv1_1_server_method(); - break; -#else - php_error_docref(NULL TSRMLS_CC, E_WARNING, "TLSv1.1 support is not compiled into the OpenSSL library PHP is linked against"); - return -1; +#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 - case STREAM_CRYPTO_METHOD_TLSv1_2_SERVER: #if OPENSSL_VERSION_NUMBER >= 0x10001001L - sslsock->is_client = 0; - method = TLSv1_2_server_method(); - break; -#else - php_error_docref(NULL TSRMLS_CC, E_WARNING, "TLSv1.2 support is not compiled into the OpenSSL library PHP is linked against"); - return -1; + 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 - default: + + return ssl_ctx_options; +} + +static inline int php_openssl_setup_crypto(php_stream *stream, + php_openssl_netstream_data_t *sslsock, + php_stream_xport_crypto_param *cparam + TSRMLS_DC) +{ + const SSL_METHOD *method; + long ssl_ctx_options; + long method_flags; + zval **val; +#ifdef SSL_MODE_RELEASE_BUFFERS + long mode; +#endif + + 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; + } else { + return 0; + } + } + /* 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 -1; + } + } 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 -1; + } } +#if OPENSSL_VERSION_NUMBER >= 0x10001001L sslsock->ctx = SSL_CTX_new(method); +#else + sslsock->ctx = SSL_CTX_new((SSL_METHOD*)method); +#endif if (sslsock->ctx == NULL) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "failed to create an SSL context"); return -1; @@ -403,8 +553,6 @@ static inline int php_openssl_setup_crypto(php_stream *stream, #if OPENSSL_VERSION_NUMBER >= 0x0090806fL { - zval **val; - if (stream->context && SUCCESS == php_stream_context_get_option( stream->context, "ssl", "no_ticket", &val) && zend_is_true(*val) @@ -416,17 +564,36 @@ static inline int php_openssl_setup_crypto(php_stream *stream, #if OPENSSL_VERSION_NUMBER >= 0x10000000L { - zval **val; - - if (stream->context && SUCCESS == php_stream_context_get_option( - stream->context, "ssl", "disable_compression", &val) && - zend_is_true(*val) + if (stream->context && (FAILURE == php_stream_context_get_option( + stream->context, "ssl", "disable_compression", &val) || + zend_is_true(*val)) ) { SSL_CTX_set_options(sslsock->ctx, SSL_OP_NO_COMPRESSION); } } #endif + if (!sslsock->is_client && stream->context && SUCCESS == php_stream_context_get_option( + stream->context, "ssl", "honor_cipher_order", &val) && + zend_is_true(*val) + ) { + SSL_CTX_set_options(sslsock->ctx, SSL_OP_CIPHER_SERVER_PREFERENCE); + } + + if (!sslsock->is_client && stream->context && SUCCESS == php_stream_context_get_option( + stream->context, "ssl", "single_dh_use", &val) && + zend_is_true(*val) + ) { + SSL_CTX_set_options(sslsock->ctx, SSL_OP_SINGLE_DH_USE); + } + + if (!sslsock->is_client && stream->context && SUCCESS == php_stream_context_get_option( + stream->context, "ssl", "single_ecdh_use", &val) && + zend_is_true(*val) + ) { + SSL_CTX_set_options(sslsock->ctx, SSL_OP_SINGLE_ECDH_USE); + } + sslsock->ssl_handle = php_SSL_new_from_context(sslsock->ctx, stream TSRMLS_CC); if (sslsock->ssl_handle == NULL) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "failed to create an SSL handle"); @@ -435,6 +602,22 @@ static inline int php_openssl_setup_crypto(php_stream *stream, return -1; } + if (!sslsock->is_client && stream->context && SUCCESS == php_stream_context_get_option( + stream->context, "ssl", "honor_cipher_order", &val) && + zend_is_true(*val) + ) { + SSL_CTX_set_options(sslsock->ctx, SSL_OP_CIPHER_SERVER_PREFERENCE); + } + +#ifdef SSL_MODE_RELEASE_BUFFERS + mode = SSL_get_mode(sslsock->ssl_handle); + SSL_set_mode(sslsock->ssl_handle, mode | SSL_MODE_RELEASE_BUFFERS); +#endif + + if (!sslsock->is_client) { + init_handshake_limiting(stream, sslsock); + } + if (!SSL_set_fd(sslsock->ssl_handle, sslsock->s.socket)) { handle_ssl_error(stream, 0, 1 TSRMLS_CC); } @@ -451,6 +634,193 @@ static inline int php_openssl_setup_crypto(php_stream *stream, return 0; } +static zval *php_capture_ssl_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 php_set_server_rsa_key(php_stream *stream, + php_openssl_netstream_data_t *sslsock + TSRMLS_DC) +{ + zval ** val; + int rsa_key_size; + RSA* rsa; + int retval = 1; + + 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; + } + } else { + rsa_key_size = 2048; + } + + rsa = RSA_generate_key(rsa_key_size, RSA_F4, NULL, NULL); + + if (!SSL_set_tmp_rsa(sslsock->ssl_handle, rsa)) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "Failed setting RSA key"); + retval = 0; + } + + RSA_free(rsa); + + return retval; +} + + +static int php_set_server_dh_param(php_openssl_netstream_data_t *sslsock, + 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 0; + } + + 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 0; + } + + if (SSL_set_tmp_dh(sslsock->ssl_handle, dh) < 0) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "DH param assignment failed"); + DH_free(dh); + return 0; + } + + DH_free(dh); + + return 1; +} + +static int php_enable_server_crypto_opts(php_stream *stream, + php_openssl_netstream_data_t *sslsock + TSRMLS_DC) +{ + zval **val; +#ifdef HAVE_ECDH + int curve_nid; + EC_KEY *ecdh; +#endif + + if (php_stream_context_get_option(stream->context, "ssl", "dh_param", &val) == SUCCESS) { + convert_to_string_ex(val); + if (!php_set_server_dh_param(sslsock, Z_STRVAL_PP(val) TSRMLS_CC)) { + return 0; + } + } + +#ifdef HAVE_ECDH + + if (php_stream_context_get_option(stream->context, "ssl", "ecdh_curve", &val) == SUCCESS) { + char *curve_str; + 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 0; + } + } 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 0; + } + + SSL_set_tmp_ecdh(sslsock->ssl_handle, ecdh); + EC_KEY_free(ecdh); + +#else + + if (php_stream_context_get_option(stream->context, "ssl", "ecdh_curve", &val) == SUCCESS) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "ECDH curve support not compiled into the OpenSSL lib against which PHP is linked"); + } + +#endif + + if (!php_set_server_rsa_key(stream, sslsock TSRMLS_CC)) { + return 0; + } + + return 1; +} + +#if OPENSSL_VERSION_NUMBER >= 0x00908070L && !defined(OPENSSL_NO_TLSEXT) +static inline void enable_client_sni(php_stream *stream, php_openssl_netstream_data_t *sslsock) /* {{{ */ +{ + zval **val; + + if (stream->context && + (php_stream_context_get_option(stream->context, "ssl", "SNI_enabled", &val) == FAILURE + || zend_is_true(*val)) + ) { + if (php_stream_context_get_option(stream->context, "ssl", "SNI_server_name", &val) == SUCCESS) { + convert_to_string_ex(val); + SSL_set_tlsext_host_name(sslsock->ssl_handle, Z_STRVAL_PP(val)); + } else if (sslsock->url_name) { + SSL_set_tlsext_host_name(sslsock->ssl_handle, sslsock->url_name); + } + } else if (!stream->context && sslsock->url_name) { + SSL_set_tlsext_host_name(sslsock->ssl_handle, sslsock->url_name); + } +} +/* }}} */ +#endif static inline int php_openssl_enable_crypto(php_stream *stream, php_openssl_netstream_data_t *sslsock, @@ -466,23 +836,17 @@ static inline int php_openssl_enable_crypto(php_stream *stream, has_timeout = 0; #if OPENSSL_VERSION_NUMBER >= 0x00908070L && !defined(OPENSSL_NO_TLSEXT) - - zval **val; - - if (sslsock->is_client - && (php_stream_context_get_option(stream->context, "ssl", "SNI_enabled", &val) == FAILURE - || zend_is_true(*val)) - ) { - if (php_stream_context_get_option(stream->context, "ssl", "SNI_server_name", &val) == SUCCESS) { - convert_to_string_ex(val); - SSL_set_tlsext_host_name(sslsock->ssl_handle, Z_STRVAL_PP(val)); - } else if (sslsock->url_name) { - SSL_set_tlsext_host_name(sslsock->ssl_handle, sslsock->url_name); - } +{ + if (sslsock->is_client) { + enable_client_sni(stream, sslsock); } - +} #endif + if (!sslsock->is_client && !php_enable_server_crypto_opts(stream, sslsock TSRMLS_CC)) { + return -1; + } + if (!sslsock->state_set) { if (sslsock->is_client) { SSL_set_connect_state(sslsock->ssl_handle); @@ -577,6 +941,18 @@ static inline int php_openssl_enable_crypto(php_stream *stream, if (SUCCESS == php_stream_context_get_option( stream->context, "ssl", + "capture_session_meta", &val) && + zend_is_true(*val)) { + zval *meta_arr = php_capture_ssl_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 (SUCCESS == php_stream_context_get_option( + stream->context, "ssl", "capture_peer_cert", &val) && zend_is_true(*val)) { MAKE_STD_ZVAL(zcert); @@ -686,28 +1062,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; - case STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT: - sock->method = STREAM_CRYPTO_METHOD_TLSv1_1_SERVER; - break; - case STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT: - sock->method = STREAM_CRYPTO_METHOD_TLSv1_2_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; @@ -892,32 +1249,21 @@ php_stream_ops php_openssl_socket_ops = { php_openssl_sockop_set_option, }; -static int get_crypto_method(php_stream_context *ctx) { - if (ctx) { - zval **val = NULL; - long crypto_method; - - if (php_stream_context_get_option(ctx, "ssl", "crypto_method", &val) == SUCCESS) { - convert_to_long_ex(val); - crypto_method = (long)Z_LVAL_PP(val); - - switch (crypto_method) { - case STREAM_CRYPTO_METHOD_SSLv2_CLIENT: - case STREAM_CRYPTO_METHOD_SSLv3_CLIENT: - case STREAM_CRYPTO_METHOD_SSLv23_CLIENT: - case STREAM_CRYPTO_METHOD_TLS_CLIENT: - case STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT: - case STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT: - return crypto_method; - } - - } - } +static long get_crypto_method(php_stream_context *ctx, long crypto_method) +{ + zval **val; - return STREAM_CRYPTO_METHOD_SSLv23_CLIENT; + 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; + } + + return crypto_method; } -static char * get_url_name(const char *resourcename, size_t resourcenamelen, int is_persistent TSRMLS_DC) { +static char *get_url_name(const char *resourcename, size_t resourcenamelen, int is_persistent TSRMLS_DC) +{ php_url *url; if (!resourcename) { @@ -959,7 +1305,7 @@ php_stream *php_openssl_ssl_socket_factory(const char *proto, size_t protolen, { 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)); @@ -975,10 +1321,10 @@ php_stream *php_openssl_ssl_socket_factory(const char *proto, size_t 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) { @@ -988,12 +1334,7 @@ php_stream *php_openssl_ssl_socket_factory(const char *proto, size_t protolen, if (strncmp(proto, "ssl", protolen) == 0) { sslsock->enable_on_connect = 1; - - /* General ssl:// transports can use a number - * of crypto methods. The actual methhod can be - * provided in the streams context options. - */ - sslsock->method = get_crypto_method(context); + 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"); @@ -1007,7 +1348,10 @@ php_stream *php_openssl_ssl_socket_factory(const char *proto, size_t 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; |
