Verify peers by default in client socket operations

This commit is contained in:
Daniel Lowrey 2014-01-28 10:05:56 -07:00
parent 79ab514f0c
commit b4b4d9697f
9 changed files with 193 additions and 56 deletions

View file

@ -27,7 +27,9 @@
#endif #endif
#include "php.h" #include "php.h"
#include "php_ini.h"
#include "php_openssl.h" #include "php_openssl.h"
#include "php_openssl_structs.h"
/* PHP Includes */ /* PHP Includes */
#include "ext/standard/file.h" #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
*/ */
PHP_MINIT_FUNCTION(openssl) PHP_MINIT_FUNCTION(openssl)
@ -1204,6 +1213,8 @@ PHP_MINIT_FUNCTION(openssl)
php_register_url_stream_wrapper("https", &php_stream_http_wrapper TSRMLS_CC); php_register_url_stream_wrapper("https", &php_stream_http_wrapper TSRMLS_CC);
php_register_url_stream_wrapper("ftps", &php_stream_ftp_wrapper TSRMLS_CC); php_register_url_stream_wrapper("ftps", &php_stream_ftp_wrapper TSRMLS_CC);
REGISTER_INI_ENTRIES();
return SUCCESS; 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 Library Version", SSLeay_version(SSLEAY_VERSION));
php_info_print_table_row(2, "OpenSSL Header Version", OPENSSL_VERSION_TEXT); php_info_print_table_row(2, "OpenSSL Header Version", OPENSSL_VERSION_TEXT);
php_info_print_table_end(); php_info_print_table_end();
DISPLAY_INI_ENTRIES();
} }
/* }}} */ /* }}} */
@ -1243,6 +1255,8 @@ PHP_MSHUTDOWN_FUNCTION(openssl)
/* reinstate the default tcp handler */ /* reinstate the default tcp handler */
php_stream_xport_register("tcp", php_stream_generic_socket_factory TSRMLS_CC); php_stream_xport_register("tcp", php_stream_generic_socket_factory TSRMLS_CC);
UNREGISTER_INI_ENTRIES();
return SUCCESS; return SUCCESS;
} }
/* }}} */ /* }}} */
@ -5063,9 +5077,13 @@ int php_openssl_apply_verification_policy(SSL *ssl, X509 *peer, php_stream *stre
zval **val = NULL; zval **val = NULL;
char *cnmatch = NULL; char *cnmatch = NULL;
int err; int err;
php_openssl_netstream_data_t *sslsock;
/* verification is turned off */ sslsock = (php_openssl_netstream_data_t*)stream->abstract;
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; 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); 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 (cnmatch) {
if (matches_san_list(peer, cnmatch TSRMLS_CC)) { if (matches_san_list(peer, cnmatch TSRMLS_CC)) {
return SUCCESS; return SUCCESS;
@ -5150,7 +5173,9 @@ SSL *php_SSL_new_from_context(SSL_CTX *ctx, php_stream *stream TSRMLS_DC) /* {{{
ERR_clear_error(); ERR_clear_error();
/* look at context options in the stream and set appropriate verification flags */ /* 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 */ /* turn on verification callback */
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, verify_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("cafile", cafile);
GET_VER_OPT_STRING("capath", capath); 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 (cafile || capath) {
if (!SSL_CTX_load_verify_locations(ctx, 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); php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to set verify locations `%s' `%s'", cafile, capath);
return NULL; 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")) { if (GET_VER_OPT("verify_depth")) {
convert_to_long_ex(val); convert_to_long_ex(val);
SSL_CTX_set_verify_depth(ctx, Z_LVAL_PP(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) */ /* 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) { if (ok) {
SSL *ssl = SSL_new(ctx); SSL *ssl = SSL_new(ctx);

View file

@ -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 <wez@thebrainroom.com> |
| Daniel Lowrey <rdlowrey@gmail.com> |
+----------------------------------------------------------------------+
*/
/* $Id$ */
#include "php_network.h"
#include <openssl/ssl.h>
/* 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;

View file

@ -45,7 +45,10 @@ if ($pid == 0) { // child
// client or failed // client or failed
sleep(1); 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; if (!$sock) exit;
echo fgets($sock); echo fgets($sock);

View file

@ -13,8 +13,7 @@ function ssl_server($port) {
$host = 'ssl://127.0.0.1'.':'.$port; $host = 'ssl://127.0.0.1'.':'.$port;
$flags = STREAM_SERVER_BIND | STREAM_SERVER_LISTEN; $flags = STREAM_SERVER_BIND | STREAM_SERVER_LISTEN;
$data = "Sending bug48182\n"; $data = "Sending bug48182\n";
$pem = dirname(__FILE__) . '/bug54992.pem';
$pem = dirname(__FILE__) . '/bug46127.pem';
$ssl_params = array( 'verify_peer' => false, 'allow_self_signed' => true, 'local_cert' => $pem); $ssl_params = array( 'verify_peer' => false, 'allow_self_signed' => true, 'local_cert' => $pem);
$ssl = array('ssl' => $ssl_params); $ssl = array('ssl' => $ssl_params);
@ -47,8 +46,11 @@ function ssl_async_client($port) {
$host = 'ssl://127.0.0.1'.':'.$port; $host = 'ssl://127.0.0.1'.':'.$port;
$flags = STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT; $flags = STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT;
$data = "Sending data over to SSL server in async mode with contents like Hello World\n"; $data = "Sending data over to SSL server in async mode with contents like Hello World\n";
$context = stream_context_create(array('ssl' => array(
$socket = stream_socket_client($host, $errno, $errstr, 10, $flags); '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); stream_set_blocking($socket, 0);
while ($socket && $data) { while ($socket && $data) {

View file

@ -24,6 +24,7 @@ if ($pid == -1) {
'verify_peer' => true, 'verify_peer' => true,
'cafile' => __DIR__ . '/bug54992-ca.pem', 'cafile' => __DIR__ . '/bug54992-ca.pem',
'capture_peer_cert' => true, 'capture_peer_cert' => true,
'CN_match' => 'bug54992.local',
'peer_fingerprint' => '81cafc260aa8d82956ebc6212a362ece', 'peer_fingerprint' => '81cafc260aa8d82956ebc6212a362ece',
) )
) )
@ -38,6 +39,7 @@ if ($pid == -1) {
'verify_peer' => true, 'verify_peer' => true,
'cafile' => __DIR__ . '/bug54992-ca.pem', 'cafile' => __DIR__ . '/bug54992-ca.pem',
'capture_peer_cert' => true, 'capture_peer_cert' => true,
'CN_match' => 'bug54992.local',
'peer_fingerprint' => array( 'peer_fingerprint' => array(
'sha256' => '78ea579f2c3b439359dec5dac9d445108772927427c4780037e87df3799a0aa0', '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 Warning: stream_socket_client(): unable to connect to ssl://127.0.0.1:64321 (Unknown error) in %s on line %d
bool(false) bool(false)
resource(9) of type (stream) resource(%d) of type (stream)

View file

@ -0,0 +1,56 @@
--TEST--
Peer verification enabled for client streams
--SKIPIF--
<?php
if (!extension_loaded("openssl")) die("skip");
if (!function_exists('pcntl_fork')) die("skip no fork");
--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);
}
--EXPECTF--
bool(false)
bool(false)
resource(%d) of type (stream)
resource(%d) of type (stream)
resource(%d) of type (stream)

View file

@ -24,6 +24,7 @@ function context() {
return stream_context_create(array( return stream_context_create(array(
'ssl' => array( 'ssl' => array(
'capture_peer_cert' => true, 'capture_peer_cert' => true,
'verify_peer' => false
), ),
)); ));
} }

View file

@ -10,6 +10,7 @@ if (!extension_loaded('pcntl')) die('skip, pcntl required');
function client($port, $method) { function client($port, $method) {
$ctx = stream_context_create(); $ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'crypto_method', $method); 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); $fp = @fopen('https://127.0.0.1:' . $port . '/', 'r', false, $ctx);
if ($fp) { if ($fp) {

View file

@ -23,8 +23,8 @@
#include "ext/standard/url.h" #include "ext/standard/url.h"
#include "streams/php_streams_int.h" #include "streams/php_streams_int.h"
#include "ext/standard/php_smart_str.h" #include "ext/standard/php_smart_str.h"
#include "php_network.h"
#include "php_openssl.h" #include "php_openssl.h"
#include "php_openssl_structs.h"
#include <openssl/ssl.h> #include <openssl/ssl.h>
#include <openssl/x509.h> #include <openssl/x509.h>
#include <openssl/err.h> #include <openssl/err.h>
@ -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); SSL *php_SSL_new_from_context(SSL_CTX *ctx, php_stream *stream TSRMLS_DC);
int php_openssl_get_x509_list_id(void); 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; php_stream_ops php_openssl_socket_ops;
/* it doesn't matter that we do some hash traversal here, since it is done only /* it doesn't matter that we do some hash traversal here, since it is done only
@ -285,9 +266,10 @@ static int php_openssl_sockop_close(php_stream *stream, int close_handle TSRMLS_
} }
} }
if (sslsock->sni) { if (sslsock->url_name) {
pefree(sslsock->sni, php_stream_is_persistent(stream)); pefree(sslsock->url_name, php_stream_is_persistent(stream));
} }
pefree(sslsock, php_stream_is_persistent(stream)); pefree(sslsock, php_stream_is_persistent(stream));
return 0; return 0;
@ -467,12 +449,25 @@ static inline int php_openssl_setup_crypto(php_stream *stream,
return 0; 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, static inline int php_openssl_enable_crypto(php_stream *stream,
php_openssl_netstream_data_t *sslsock, php_openssl_netstream_data_t *sslsock,
php_stream_xport_crypto_param *cparam php_stream_xport_crypto_param *cparam
TSRMLS_DC) TSRMLS_DC)
{ {
int n, retry = 1; int n, retry = 1;
zval **val = NULL;
if (cparam->inputs.activate && !sslsock->ssl_active) { if (cparam->inputs.activate && !sslsock->ssl_active) {
struct timeval start_time, struct timeval start_time,
@ -481,9 +476,14 @@ static inline int php_openssl_enable_crypto(php_stream *stream,
has_timeout = 0; has_timeout = 0;
#if OPENSSL_VERSION_NUMBER >= 0x0090806fL && !defined(OPENSSL_NO_TLSEXT) #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 #endif
if (!sslsock->state_set) { if (!sslsock->state_set) {
@ -920,21 +920,9 @@ static int get_crypto_method(php_stream_context *ctx) {
return STREAM_CRYPTO_METHOD_SSLv23_CLIENT; 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; 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) { if (!resourcename) {
return NULL; return NULL;
} }
@ -946,7 +934,7 @@ static char * get_sni(php_stream_context *ctx, const char *resourcename, size_t
if (url->host) { if (url->host) {
const char * host = url->host; const char * host = url->host;
char * sni = NULL; char * url_name = NULL;
size_t len = strlen(host); size_t len = strlen(host);
/* skip trailing dots */ /* skip trailing dots */
@ -955,11 +943,11 @@ static char * get_sni(php_stream_context *ctx, const char *resourcename, size_t
} }
if (len) { if (len) {
sni = pestrndup(host, len, is_persistent); url_name = pestrndup(host, len, is_persistent);
} }
php_url_free(url); php_url_free(url);
return sni; return url_name;
} }
php_url_free(url); php_url_free(url);
@ -1001,8 +989,6 @@ php_stream *php_openssl_ssl_socket_factory(const char *proto, size_t protolen,
return NULL; return NULL;
} }
sslsock->sni = get_sni(context, resourcename, resourcenamelen, !!persistent_id TSRMLS_CC);
if (strncmp(proto, "ssl", protolen) == 0) { if (strncmp(proto, "ssl", protolen) == 0) {
sslsock->enable_on_connect = 1; sslsock->enable_on_connect = 1;
@ -1043,6 +1029,8 @@ php_stream *php_openssl_ssl_socket_factory(const char *proto, size_t protolen,
#endif #endif
} }
sslsock->url_name = get_url_name(resourcename, resourcenamelen, !!persistent_id TSRMLS_CC);
return stream; return stream;
} }