From ce8dc0ede2e8084beef1e7b03c8960e938c8399f Mon Sep 17 00:00:00 2001 From: Daniel Lowrey Date: Fri, 14 Feb 2014 15:17:30 -0700 Subject: [PATCH] Bug #47030 (separate host and peer verification) --- ext/openssl/openssl.c | 106 +++++++++++------- ext/openssl/tests/bug46127.phpt | 4 +- ext/openssl/tests/peer_verification.phpt | 5 +- ext/openssl/tests/sni_001.phpt | 31 ++--- ext/openssl/tests/stream_verify_host_001.phpt | 35 ++++++ ext/openssl/tests/stream_verify_host_002.phpt | 36 ++++++ ext/openssl/tests/stream_verify_host_003.phpt | 40 +++++++ ext/openssl/tests/streams_crypto_method.phpt | 52 ++++----- ext/openssl/tests/tlsv1.1_wrapper_001.phpt | 3 +- ext/openssl/tests/tlsv1.2_wrapper_002.phpt | 3 +- 10 files changed, 227 insertions(+), 88 deletions(-) create mode 100644 ext/openssl/tests/stream_verify_host_001.phpt create mode 100644 ext/openssl/tests/stream_verify_host_002.phpt create mode 100644 ext/openssl/tests/stream_verify_host_003.phpt diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c index d57b3eafde5..f7acda15794 100644 --- a/ext/openssl/openssl.c +++ b/ext/openssl/openssl.c @@ -5077,62 +5077,82 @@ 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; + zend_bool must_verify_peer; + zend_bool must_verify_host; + zend_bool must_verify_fingerprint; + 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) { + must_verify_host = GET_VER_OPT("verify_host") + ? 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_host || 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; - } - - /* if the cert passed the usual checks, apply our own local policies now */ - - if (GET_VER_OPT("peer_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"); + 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; - } - } else { - 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); - - /* 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 a peer_fingerprint match is required this trumps host verification */ + if (must_verify_fingerprint) { + if (Z_TYPE_PP(val) == IS_STRING || Z_TYPE_PP(val) == IS_ARRAY) { + if (!php_x509_fingerprint_match(peer, *val TSRMLS_CC)) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "Peer fingerprint doesn't match" + ); + return FAILURE; + } + } else { + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "Expected peer fingerprint must be a string or an array" + ); + } } - if (cnmatch) { - if (matches_san_list(peer, cnmatch TSRMLS_CC)) { - return SUCCESS; - } else if (matches_common_name(peer, cnmatch TSRMLS_CC)) { - return SUCCESS; + /* verify the host name presented in the peer certificate */ + + if (must_verify_host) { + GET_VER_OPT_STRING("CN_match", cnmatch); + /* If no CN_match was specified assign the autodetected url name in client environments */ + if (cnmatch == NULL && sslsock->is_client) { + cnmatch = 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; + } else { + return FAILURE; + } } else { return FAILURE; } diff --git a/ext/openssl/tests/bug46127.phpt b/ext/openssl/tests/bug46127.phpt index 1de4eacd016..ef4a9be0315 100644 --- a/ext/openssl/tests/bug46127.phpt +++ b/ext/openssl/tests/bug46127.phpt @@ -13,6 +13,7 @@ function ssl_server($port) { $pem = dirname(__FILE__) . '/bug46127.pem'; $ssl = array( 'verify_peer' => false, + 'verify_host' => false, 'allow_self_signed' => true, 'local_cert' => $pem, // 'passphrase' => '', @@ -46,7 +47,8 @@ if ($pid == 0) { // child // client or failed sleep(1); $ctx = stream_context_create(['ssl' => [ - 'verify_peer' => false + 'verify_peer' => false, + 'verify_host' => false ]]); $sock = stream_socket_client("ssl://127.0.0.1:{$port}", $errno, $errstr, 30, STREAM_CLIENT_CONNECT, $ctx); if (!$sock) exit; diff --git a/ext/openssl/tests/peer_verification.phpt b/ext/openssl/tests/peer_verification.phpt index 7c3347fd652..b19012a9b8b 100644 --- a/ext/openssl/tests/peer_verification.phpt +++ b/ext/openssl/tests/peer_verification.phpt @@ -25,7 +25,10 @@ if ($pid == -1) { 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]]); + $ctx = stream_context_create(['ssl' => [ + 'verify_peer' => false, + 'verify_host' => 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 diff --git a/ext/openssl/tests/sni_001.phpt b/ext/openssl/tests/sni_001.phpt index 2f76a9f9187..0dbd18d3815 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', 'CN_match', $host); + } else { + stream_context_set_option($ctx, 'ssl', 'verify_host', 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_verify_host_001.phpt b/ext/openssl/tests/stream_verify_host_001.phpt new file mode 100644 index 00000000000..aa85ad559f9 --- /dev/null +++ b/ext/openssl/tests/stream_verify_host_001.phpt @@ -0,0 +1,35 @@ +--TEST-- +Verify host name by default in client transfers +--SKIPIF-- + [ + 'local_cert' => __DIR__ . '/bug54992.pem' +]]); +$server = stream_socket_server($serverUri, $errno, $errstr, $serverFlags, $serverCtx); + +$pid = pcntl_fork(); +if ($pid == -1) { + die('could not fork'); +} else if ($pid) { + + $clientFlags = STREAM_CLIENT_CONNECT; + $clientCtx = stream_context_create(['ssl' => [ + 'verify_peer' => false, + 'CN_match' => 'bug54992.local' + ]]); + + $client = stream_socket_client($serverUri, $errno, $errstr, 1, $clientFlags, $clientCtx); + var_dump($client); + +} else { + @pcntl_wait($status); + @stream_socket_accept($server, 1); +} +--EXPECTF-- +resource(%d) of type (stream) diff --git a/ext/openssl/tests/stream_verify_host_002.phpt b/ext/openssl/tests/stream_verify_host_002.phpt new file mode 100644 index 00000000000..1ac81e25434 --- /dev/null +++ b/ext/openssl/tests/stream_verify_host_002.phpt @@ -0,0 +1,36 @@ +--TEST-- +Allow host name mismatch when "verify_host" disabled +--SKIPIF-- + [ + 'local_cert' => __DIR__ . '/bug54992.pem' +]]); +$server = stream_socket_server($serverUri, $errno, $errstr, $serverFlags, $serverCtx); + +$pid = pcntl_fork(); +if ($pid == -1) { + die('could not fork'); +} else if ($pid) { + + $clientFlags = STREAM_CLIENT_CONNECT; + $clientCtx = stream_context_create(['ssl' => [ + 'verify_peer' => true, + 'cafile' => __DIR__ . '/bug54992-ca.pem', + 'verify_host' => false + ]]); + + $client = stream_socket_client($serverUri, $errno, $errstr, 1, $clientFlags, $clientCtx); + var_dump($client); + +} else { + @pcntl_wait($status); + @stream_socket_accept($server, 1); +} +--EXPECTF-- +resource(%d) of type (stream) diff --git a/ext/openssl/tests/stream_verify_host_003.phpt b/ext/openssl/tests/stream_verify_host_003.phpt new file mode 100644 index 00000000000..ce6430a14a1 --- /dev/null +++ b/ext/openssl/tests/stream_verify_host_003.phpt @@ -0,0 +1,40 @@ +--TEST-- +Host name mismatch triggers error +--SKIPIF-- + [ + 'local_cert' => __DIR__ . '/bug54992.pem' +]]); +$server = stream_socket_server($serverUri, $errno, $errstr, $serverFlags, $serverCtx); + +$pid = pcntl_fork(); +if ($pid == -1) { + die('could not fork'); +} else if ($pid) { + + $clientFlags = STREAM_CLIENT_CONNECT; + $clientCtx = stream_context_create(['ssl' => [ + 'verify_peer' => true, + 'cafile' => __DIR__ . '/bug54992-ca.pem' + ]]); + + $client = stream_socket_client($serverUri, $errno, $errstr, 1, $clientFlags, $clientCtx); + var_dump($client); + +} else { + @pcntl_wait($status); + @stream_socket_accept($server, 1); +} +--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 b7b8e257b44..6f6bedd6336 100644 --- a/ext/openssl/tests/streams_crypto_method.phpt +++ b/ext/openssl/tests/streams_crypto_method.phpt @@ -8,9 +8,7 @@ if (!extension_loaded('pcntl')) die('skip, pcntl required'); --FILE-- [ - 'local_cert' => dirname(__FILE__) . '/streams_crypto_method.pem', - 'allow_self_signed' => true, - 'verify_peer' => false + 'local_cert' => dirname(__FILE__) . '/streams_crypto_method.pem', ]]); $serverFlags = STREAM_SERVER_BIND | STREAM_SERVER_LISTEN; $server = stream_socket_server('sslv3://127.0.0.1:12345', $errno, $errstr, $serverFlags, $serverCtx); @@ -18,31 +16,29 @@ $server = stream_socket_server('sslv3://127.0.0.1:12345', $errno, $errstr, $serv $pid = pcntl_fork(); if ($pid == -1) { - die('could not fork'); + die('could not fork'); } else if ($pid) { - $clientCtx = stream_context_create(['ssl' => [ - 'crypto_method' => STREAM_CRYPTO_METHOD_SSLv3_CLIENT, - 'verify_peer' => false - ]]); + $clientCtx = stream_context_create(['ssl' => [ + 'crypto_method' => STREAM_CRYPTO_METHOD_SSLv3_CLIENT, + 'verify_peer' => false, + 'verify_host' => false + ]]); - $fp = fopen('https://127.0.0.1:12345/', 'r', false, $clientCtx); + $fp = fopen('https://127.0.0.1:12345/', 'r', false, $clientCtx); - if ($fp) { - fpassthru($fp); - fclose($fp); - } + if ($fp) { + fpassthru($fp); + fclose($fp); + } } else { - @pcntl_wait($status); - - $client = @stream_socket_accept($server); - - if ($client) { - $in = ''; - while (!preg_match('/\r?\n\r?\n/', $in)) { - $in .= fread($client, 2048); - } - - $response = << --EXPECTF-- diff --git a/ext/openssl/tests/tlsv1.1_wrapper_001.phpt b/ext/openssl/tests/tlsv1.1_wrapper_001.phpt index 56211f0b965..82048e525de 100644 --- a/ext/openssl/tests/tlsv1.1_wrapper_001.phpt +++ b/ext/openssl/tests/tlsv1.1_wrapper_001.phpt @@ -21,7 +21,8 @@ if ($pid == -1) { } elseif ($pid) { $flags = STREAM_CLIENT_CONNECT; $ctx = stream_context_create(array('ssl' => array( - 'verify_peer' => false + 'verify_peer' => false, + 'verify_host' => false ))); $client = stream_socket_client("tlsv1.1://127.0.0.1:64321", $errno, $errstr, 1, $flags, $ctx); diff --git a/ext/openssl/tests/tlsv1.2_wrapper_002.phpt b/ext/openssl/tests/tlsv1.2_wrapper_002.phpt index cb3f4106c72..d58d1a12624 100644 --- a/ext/openssl/tests/tlsv1.2_wrapper_002.phpt +++ b/ext/openssl/tests/tlsv1.2_wrapper_002.phpt @@ -21,7 +21,8 @@ if ($pid == -1) { } elseif ($pid) { $flags = STREAM_CLIENT_CONNECT; $ctx = stream_context_create(array('ssl' => array( - 'verify_peer' => false + 'verify_peer' => false, + 'verify_host' => false ))); $client = stream_socket_client("tlsv1.2://127.0.0.1:64321", $errno, $errstr, 1, $flags, $ctx);