From b4b4d9697fa43a526466165a3ca89d058296cb04 Mon Sep 17 00:00:00 2001 From: Daniel Lowrey Date: Tue, 28 Jan 2014 10:05:56 -0700 Subject: [PATCH] Verify peers by default in client socket operations --- ext/openssl/openssl.c | 54 +++++++++++-- ext/openssl/php_openssl_structs.h | 42 ++++++++++ ext/openssl/tests/bug46127.phpt | 5 +- ext/openssl/tests/bug48182.phpt | 10 ++- .../tests/openssl_peer_fingerprint.phpt | 4 +- ext/openssl/tests/peer_verification.phpt | 56 ++++++++++++++ ext/openssl/tests/sni_001.phpt | 1 + ext/openssl/tests/streams_crypto_method.phpt | 1 + ext/openssl/xp_ssl.c | 76 ++++++++----------- 9 files changed, 193 insertions(+), 56 deletions(-) create mode 100644 ext/openssl/php_openssl_structs.h create mode 100644 ext/openssl/tests/peer_verification.phpt diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c index b38dd6d206..d57b3eafde 100644 --- a/ext/openssl/openssl.c +++ b/ext/openssl/openssl.c @@ -27,7 +27,9 @@ #endif #include "php.h" +#include "php_ini.h" #include "php_openssl.h" +#include "php_openssl_structs.h" /* PHP Includes */ #include "ext/standard/file.h" @@ -1071,6 +1073,13 @@ static const EVP_CIPHER * php_openssl_get_evp_cipher_from_algo(long algo) { /* { } /* }}} */ +/* {{{ INI Settings */ +PHP_INI_BEGIN() + PHP_INI_ENTRY("openssl.cafile", NULL, PHP_INI_ALL, NULL) + PHP_INI_ENTRY("openssl.capath", NULL, PHP_INI_ALL, NULL) +PHP_INI_END() +/* }}} */ + /* {{{ PHP_MINIT_FUNCTION */ PHP_MINIT_FUNCTION(openssl) @@ -1203,7 +1212,9 @@ PHP_MINIT_FUNCTION(openssl) php_register_url_stream_wrapper("https", &php_stream_http_wrapper TSRMLS_CC); php_register_url_stream_wrapper("ftps", &php_stream_ftp_wrapper TSRMLS_CC); - + + REGISTER_INI_ENTRIES(); + return SUCCESS; } /* }}} */ @@ -1217,6 +1228,7 @@ PHP_MINFO_FUNCTION(openssl) php_info_print_table_row(2, "OpenSSL Library Version", SSLeay_version(SSLEAY_VERSION)); php_info_print_table_row(2, "OpenSSL Header Version", OPENSSL_VERSION_TEXT); php_info_print_table_end(); + DISPLAY_INI_ENTRIES(); } /* }}} */ @@ -1243,6 +1255,8 @@ PHP_MSHUTDOWN_FUNCTION(openssl) /* reinstate the default tcp handler */ php_stream_xport_register("tcp", php_stream_generic_socket_factory TSRMLS_CC); + UNREGISTER_INI_ENTRIES(); + return SUCCESS; } /* }}} */ @@ -5063,9 +5077,13 @@ int php_openssl_apply_verification_policy(SSL *ssl, X509 *peer, php_stream *stre zval **val = NULL; char *cnmatch = NULL; int err; + php_openssl_netstream_data_t *sslsock; + + sslsock = (php_openssl_netstream_data_t*)stream->abstract; - /* verification is turned off */ - if (!(GET_VER_OPT("verify_peer") && zval_is_true(*val))) { + if (!(GET_VER_OPT("verify_peer") || sslsock->is_client) + || (GET_VER_OPT("verify_peer") && !zval_is_true(*val)) + ) { return SUCCESS; } @@ -5105,6 +5123,11 @@ int php_openssl_apply_verification_policy(SSL *ssl, X509 *peer, php_stream *stre GET_VER_OPT_STRING("CN_match", cnmatch); + /* 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 (cnmatch) { if (matches_san_list(peer, cnmatch TSRMLS_CC)) { return SUCCESS; @@ -5150,7 +5173,9 @@ SSL *php_SSL_new_from_context(SSL_CTX *ctx, php_stream *stream TSRMLS_DC) /* {{{ 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)) { + if (GET_VER_OPT("verify_peer") && !zval_is_true(*val)) { + SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL); + } else { /* turn on verification callback */ SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, verify_callback); @@ -5159,19 +5184,35 @@ SSL *php_SSL_new_from_context(SSL_CTX *ctx, php_stream *stream TSRMLS_DC) /* {{{ GET_VER_OPT_STRING("cafile", cafile); GET_VER_OPT_STRING("capath", capath); + if (!cafile) { + zend_bool exists = 1; + cafile = zend_ini_string_ex("openssl.cafile", sizeof("openssl.cafile"), 0, &exists); + } + + if (!capath) { + zend_bool exists = 1; + capath = zend_ini_string_ex("openssl.capath", sizeof("openssl.capath"), 0, &exists); + } + 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; } + } 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; + } } if (GET_VER_OPT("verify_depth")) { convert_to_long_ex(val); SSL_CTX_set_verify_depth(ctx, Z_LVAL_PP(val)); } - } else { - SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL); } /* callback for the passphrase (for localcert) */ @@ -5237,6 +5278,7 @@ SSL *php_SSL_new_from_context(SSL_CTX *ctx, php_stream *stream TSRMLS_DC) /* {{{ } } } + if (ok) { SSL *ssl = SSL_new(ctx); diff --git a/ext/openssl/php_openssl_structs.h b/ext/openssl/php_openssl_structs.h new file mode 100644 index 0000000000..13f8f320f8 --- /dev/null +++ b/ext/openssl/php_openssl_structs.h @@ -0,0 +1,42 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2013 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong | + | Daniel Lowrey | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#include "php_network.h" +#include + +/* This implementation is very closely tied to the that of the native + * sockets implemented in the core. + * Don't try this technique in other extensions! + * */ + +typedef struct _php_openssl_netstream_data_t { + php_netstream_data_t s; + SSL *ssl_handle; + SSL_CTX *ctx; + struct timeval connect_timeout; + int enable_on_connect; + int is_client; + int ssl_active; + php_stream_xport_crypt_method_t method; + char *url_name; + unsigned state_set:1; + unsigned _spare:31; +} php_openssl_netstream_data_t; diff --git a/ext/openssl/tests/bug46127.phpt b/ext/openssl/tests/bug46127.phpt index a3bfd3a012..1de4eacd01 100644 --- a/ext/openssl/tests/bug46127.phpt +++ b/ext/openssl/tests/bug46127.phpt @@ -45,7 +45,10 @@ if ($pid == 0) { // child // client or failed sleep(1); -$sock = fsockopen('ssl://127.0.0.1', $port, $errno, $errstr); +$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); diff --git a/ext/openssl/tests/bug48182.phpt b/ext/openssl/tests/bug48182.phpt index 146c4c9226..b78ce57074 100644 --- a/ext/openssl/tests/bug48182.phpt +++ b/ext/openssl/tests/bug48182.phpt @@ -13,8 +13,7 @@ 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__) . '/bug46127.pem'; + $pem = dirname(__FILE__) . '/bug54992.pem'; $ssl_params = array( 'verify_peer' => false, 'allow_self_signed' => true, 'local_cert' => $pem); $ssl = array('ssl' => $ssl_params); @@ -47,8 +46,11 @@ function ssl_async_client($port) { $host = 'ssl://127.0.0.1'.':'.$port; $flags = STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT; $data = "Sending data over to SSL server in async mode with contents like Hello World\n"; - - $socket = stream_socket_client($host, $errno, $errstr, 10, $flags); + $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) { diff --git a/ext/openssl/tests/openssl_peer_fingerprint.phpt b/ext/openssl/tests/openssl_peer_fingerprint.phpt index 2960dffae5..2e4c192c03 100644 --- a/ext/openssl/tests/openssl_peer_fingerprint.phpt +++ b/ext/openssl/tests/openssl_peer_fingerprint.phpt @@ -24,6 +24,7 @@ if ($pid == -1) { 'verify_peer' => true, 'cafile' => __DIR__ . '/bug54992-ca.pem', 'capture_peer_cert' => true, + 'CN_match' => 'bug54992.local', 'peer_fingerprint' => '81cafc260aa8d82956ebc6212a362ece', ) ) @@ -38,6 +39,7 @@ if ($pid == -1) { 'verify_peer' => true, 'cafile' => __DIR__ . '/bug54992-ca.pem', 'capture_peer_cert' => true, + 'CN_match' => 'bug54992.local', 'peer_fingerprint' => array( 'sha256' => '78ea579f2c3b439359dec5dac9d445108772927427c4780037e87df3799a0aa0', ), @@ -59,4 +61,4 @@ Warning: stream_socket_client(): Failed to enable crypto in %s on line %d Warning: stream_socket_client(): unable to connect to ssl://127.0.0.1:64321 (Unknown error) in %s on line %d bool(false) -resource(9) of type (stream) +resource(%d) of type (stream) diff --git a/ext/openssl/tests/peer_verification.phpt b/ext/openssl/tests/peer_verification.phpt new file mode 100644 index 0000000000..7c3347fd65 --- /dev/null +++ b/ext/openssl/tests/peer_verification.phpt @@ -0,0 +1,56 @@ +--TEST-- +Peer verification enabled for client streams +--SKIPIF-- + [ + '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); +} +--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/sni_001.phpt b/ext/openssl/tests/sni_001.phpt index 3d7798cf85..2f76a9f918 100644 --- a/ext/openssl/tests/sni_001.phpt +++ b/ext/openssl/tests/sni_001.phpt @@ -24,6 +24,7 @@ function context() { return stream_context_create(array( 'ssl' => array( 'capture_peer_cert' => true, + 'verify_peer' => false ), )); } diff --git a/ext/openssl/tests/streams_crypto_method.phpt b/ext/openssl/tests/streams_crypto_method.phpt index 97a6e9ee8b..981f56b399 100644 --- a/ext/openssl/tests/streams_crypto_method.phpt +++ b/ext/openssl/tests/streams_crypto_method.phpt @@ -10,6 +10,7 @@ if (!extension_loaded('pcntl')) die('skip, pcntl required'); 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) { diff --git a/ext/openssl/xp_ssl.c b/ext/openssl/xp_ssl.c index 631d4756e4..cc8ea8a033 100644 --- a/ext/openssl/xp_ssl.c +++ b/ext/openssl/xp_ssl.c @@ -23,8 +23,8 @@ #include "ext/standard/url.h" #include "streams/php_streams_int.h" #include "ext/standard/php_smart_str.h" -#include "php_network.h" #include "php_openssl.h" +#include "php_openssl_structs.h" #include #include #include @@ -41,25 +41,6 @@ int php_openssl_apply_verification_policy(SSL *ssl, X509 *peer, php_stream *stre SSL *php_SSL_new_from_context(SSL_CTX *ctx, php_stream *stream TSRMLS_DC); int php_openssl_get_x509_list_id(void); -/* This implementation is very closely tied to the that of the native - * sockets implemented in the core. - * Don't try this technique in other extensions! - * */ - -typedef struct _php_openssl_netstream_data_t { - php_netstream_data_t s; - SSL *ssl_handle; - SSL_CTX *ctx; - struct timeval connect_timeout; - int enable_on_connect; - int is_client; - int ssl_active; - php_stream_xport_crypt_method_t method; - char *sni; - unsigned state_set:1; - unsigned _spare:31; -} php_openssl_netstream_data_t; - php_stream_ops php_openssl_socket_ops; /* it doesn't matter that we do some hash traversal here, since it is done only @@ -285,11 +266,12 @@ static int php_openssl_sockop_close(php_stream *stream, int close_handle TSRMLS_ } } - if (sslsock->sni) { - pefree(sslsock->sni, php_stream_is_persistent(stream)); + if (sslsock->url_name) { + pefree(sslsock->url_name, php_stream_is_persistent(stream)); } + pefree(sslsock, php_stream_is_persistent(stream)); - + return 0; } @@ -467,12 +449,25 @@ static inline int php_openssl_setup_crypto(php_stream *stream, return 0; } +static void enable_server_name_indication(php_stream_context *ctx, php_openssl_netstream_data_t *sslsock) +{ + zval **val = NULL; + + if (php_stream_context_get_option(ctx, "ssl", "SNI_server_name", &val) == SUCCESS) { + convert_to_string_ex(val); + SSL_set_tlsext_host_name(sslsock->ssl_handle, &val); + } else if (sslsock->url_name) { + SSL_set_tlsext_host_name(sslsock->ssl_handle, sslsock->url_name); + } +} + static inline int php_openssl_enable_crypto(php_stream *stream, php_openssl_netstream_data_t *sslsock, php_stream_xport_crypto_param *cparam TSRMLS_DC) { int n, retry = 1; + zval **val = NULL; if (cparam->inputs.activate && !sslsock->ssl_active) { struct timeval start_time, @@ -481,9 +476,14 @@ static inline int php_openssl_enable_crypto(php_stream *stream, has_timeout = 0; #if OPENSSL_VERSION_NUMBER >= 0x0090806fL && !defined(OPENSSL_NO_TLSEXT) - if (sslsock->is_client && sslsock->sni) { - SSL_set_tlsext_host_name(sslsock->ssl_handle, sslsock->sni); + + if (sslsock->is_client + && (php_stream_context_get_option(stream->context, "ssl", "SNI_enabled", &val) == FAILURE + || zend_is_true(*val TSRMLS_CC)) + ) { + enable_server_name_indication(stream->context, sslsock); } + #endif if (!sslsock->state_set) { @@ -920,21 +920,9 @@ static int get_crypto_method(php_stream_context *ctx) { return STREAM_CRYPTO_METHOD_SSLv23_CLIENT; } -static char * get_sni(php_stream_context *ctx, 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 (ctx) { - zval **val = NULL; - - if (php_stream_context_get_option(ctx, "ssl", "SNI_enabled", &val) == SUCCESS && !zend_is_true(*val)) { - return NULL; - } - if (php_stream_context_get_option(ctx, "ssl", "SNI_server_name", &val) == SUCCESS) { - convert_to_string_ex(val); - return pestrdup(Z_STRVAL_PP(val), is_persistent); - } - } - if (!resourcename) { return NULL; } @@ -946,7 +934,7 @@ static char * get_sni(php_stream_context *ctx, const char *resourcename, size_t if (url->host) { const char * host = url->host; - char * sni = NULL; + char * url_name = NULL; size_t len = strlen(host); /* skip trailing dots */ @@ -955,11 +943,11 @@ static char * get_sni(php_stream_context *ctx, const char *resourcename, size_t } if (len) { - sni = pestrndup(host, len, is_persistent); + url_name = pestrndup(host, len, is_persistent); } php_url_free(url); - return sni; + return url_name; } php_url_free(url); @@ -1001,8 +989,6 @@ php_stream *php_openssl_ssl_socket_factory(const char *proto, size_t protolen, return NULL; } - sslsock->sni = get_sni(context, resourcename, resourcenamelen, !!persistent_id TSRMLS_CC); - if (strncmp(proto, "ssl", protolen) == 0) { sslsock->enable_on_connect = 1; @@ -1042,7 +1028,9 @@ php_stream *php_openssl_ssl_socket_factory(const char *proto, size_t protolen, return NULL; #endif } - + + sslsock->url_name = get_url_name(resourcename, resourcenamelen, !!persistent_id TSRMLS_CC); + return stream; } -- 2.40.0