|
php.net | support | documentation | report a bug | advanced search | search howto | statistics | random bug | login |
[2014-11-04 19:42 UTC] james at jamesreno dot com
Description:
------------
When the MySQLi extension is compiled against mysqlnd there is no method to disable peer_name validation. Since MySQL 5.6 now enables peer_name validation by DEFAULT those of us connecting to servers with self-signed certs via SSL are no longer able too.
I have tried to signal the default ssl stream context to disable peer_name validation but mysqli extension will NOT honor it.
If the remote-server's name does not match the name you are connecting to (as in, for example, a mysql cluster and connecting to a single node directly) you will not be able to connect at all in any way shape or form with mysqli. -- The old mysql extension is not effected by this change as it honors the my.cnf mysql client's validation settings.
Test script:
---------------
<?php
stream_context_set_default(array(
'ssl' => array(
'peer_name' => 'generic-server',
'verify_peer' => FALSE,
'verify_peer_name' => FALSE,
'allow_self_signed' => TRUE,
),
));
$mysqli = mysqli_init();
mysqli_ssl_set($mysqli,"/etc/pki/mysql/client.key","/etc/pki/mysql/client.crt","/etc/pki/mysql/ca-cert.pem",NULL,NULL);
$conn = mysqli_real_connect($mysqli,'dbserver.local','test','test1234','',NULL,'',MYSQLI_CLIENT_SSL);
var_dump($conn);
?>
Expected result:
----------------
I expect to be able to disable peer_name validation for those situations were the certificate name cant possibly be verified (ie: self-signed certs) and be able to connect to the mysql server.
Actual result:
--------------
MySQLi will NOT connect to mysql server and throws 4 warnings:
Warning: mysqli_real_connect(): Peer certificate CN=`generic-server' did not match expected CN=`dbserver.local'
Warning: mysqli_real_connect(): Cannot connect to MySQL by using SSL
Warning: mysqli_real_connect(): [2002] (trying to connect via tcp://dbserver.local:3306)
Warning: mysqli_real_connect(): (HY000/2002):
PatchesPull Requests
Pull requests:
HistoryAllCommentsChangesGit/SVN commits
|
|||||||||||||||||||||||||||||||||||||
Copyright © 2001-2025 The PHP GroupAll rights reserved. |
Last updated: Sat Oct 25 17:00:01 2025 UTC |
stream_context_set_default(array('ssl'=>array('verify_peer'=>false, 'verify_peer_name'=>false, 'allow_self_signed'=>true))); has to work anyways but it does not http://php.net/manual/de/function.stream-context-set-default.php Set the default stream context which will be used whenever file operations (fopen(), file_get_contents(), etc...) are called without a context parameter. Uses the same syntax as stream_context_create() is pretty clear in the documentation and so PHP is once again not consistent, but that should anyways be only a temporary workaround because in production environments you want the peer verification for file_get_contents() to remote server and using stream_context_set_default() for the sake of mysql-over-tls would disable that too"Warning: mysqli_real_connect(): Peer certificate CN=.... did not match expected CN" comes from openssl/xp_ssl.c That code uses php stream functions like: stream = php_stream_alloc_rel(&php_openssl_socket_ops, sslsock, persistent_id, "r+"); ... then php_openssl_socket_ops structure has php_openssl_sockop_set_option function which then call few functions and is some cases raises above error. apply_peer_verification_policy actually checks verify_peer: must_verify_peer = GET_VER_OPT("verify_peer") ? zend_is_true(*val) : sslsock->is_client; so it should be possible to switch this check off. Unfortunately for me php 5.6.12 is ignoring setting for this: $opts = array('ssl'=>array('verify_peer'=> false, 'verify_peer_name' => false)); stream_context_set_default($opts); $cb = mysqli_init(); mysqli_ssl_set($cb, null, null, null, null, null); mysqli_real_connect($cb,$db_host, $db_user, $db_pass, $db_name, false, false, MYSQLI_CLIENT_SSL) and yet I'm getting "Warning: mysqli_real_connect(): Peer certificate CN=.... did not match expected CN" So the question is - why openssl code ignores verify_peer setting from default context?Ok, the problem comes from generic openssl code: ext/openssl/xp_ssl.c, apply_peer_verification_policy() function must_verify_peer = GET_VER_OPT("verify_peer") ? zend_is_true(*val) : sslsock->is_client; 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; Now code: $opts = array('ssl'=>array('verify_peer'=> false, 'verify_peer_name' => false)); stream_context_set_default($opts); nicely sets GET_VER_OPT("verify_peer") and GET_VER_OPT("verify_peer_name")) to 0, which is fine. Unfortunately above code fallback to sslsock->is_client which is 1. That means that if we are client (is_client==1) then openssl extension ignores our verify_peer and verify_peer_name settings. Why is that? No idea. If I'm looking correctly then this commit changed behaviour: commit ce8dc0ede2e8084beef1e7b03c8960e938c8399f Author: Daniel Lowrey <rdlowrey@php.net> Date: Fri Feb 14 15:17:30 2014 -0700 Bug #47030 (separate host and peer verification) Previously it behaved differently for is_client == 1."nicely sets GET_VER_OPT("verify_peer") and GET_VER_OPT("verify_peer_name")) to 0, which is fine." or rather these are 0 by default.... so default context options are actually not being passed here :-/More, mysqli internally uses mysqlnd, which creates new context in mysqlnd_net::enable_ssl thus ignoring default context. Whit patch below I'm getting default context being used. diff --git a/ext/mysqlnd/mysqlnd_net.c b/ext/mysqlnd/mysqlnd_net.c index 8683248..2f63961 100644 --- a/ext/mysqlnd/mysqlnd_net.c +++ b/ext/mysqlnd/mysqlnd_net.c @@ -29,6 +29,7 @@ #include "mysqlnd_ext_plugin.h" #include "php_network.h" #include "zend_ini.h" +#include "ext/standard/file.h" #ifdef MYSQLND_COMPRESSION_ENABLED #include <zlib.h> #endif @@ -859,7 +860,7 @@ static enum_func_status MYSQLND_METHOD(mysqlnd_net, enable_ssl)(MYSQLND_NET * const net TSRMLS_DC) { #ifdef MYSQLND_SSL_SUPPORTED - php_stream_context * context = php_stream_context_alloc(TSRMLS_C); + php_stream_context * context = FG(default_context) ? FG(default_context) : php_stream_context_alloc(TSRMLS_C); php_stream * net_stream = net->data->m.get_stream(net TSRMLS_CC); DBG_ENTER("mysqlnd_net::enable_ssl"); Unfortunately above method means that mysqlnd will change some settings in default context. What we need to do is to leave creation of new context but then copy all options from default context to our new context, pseudocode: php_stream_context * context = php_stream_context_alloc(TSRMLS_C); copy_all_options_from_default_context_to_new_context(context, default_context) Don't see any internal function that could do that, so someone with code knowledge would have to write it.Note, this is only to show the idea. It's a ugly patch, no error checking, exposing internal ext/standard function in unfriendly way. Anyway with this patch newly created mysqlnd stream inherits all options from default stream thus obeying what we want - ssl verify_peer, verify_peer_name etc settings. mysqli then works just fine and with verify_peer_name==false no longer yelds " Peer certificate CN=... did not match expected CN=..." error. diff -urbB ../1/php-5.6.12/ext/mysqlnd/mysqlnd_net.c ../php-5.6.12/ext/mysqlnd/mysqlnd_net.c --- ../1/php-5.6.12/ext/mysqlnd/mysqlnd_net.c 2015-08-06 09:55:57.000000000 +0200 +++ ../php-5.6.12/ext/mysqlnd/mysqlnd_net.c 2015-08-10 12:44:58.377480518 +0200 @@ -29,6 +29,7 @@ #include "mysqlnd_ext_plugin.h" #include "php_network.h" #include "zend_ini.h" +#include "ext/standard/file.h" #ifdef MYSQLND_COMPRESSION_ENABLED #include <zlib.h> #endif @@ -39,7 +40,7 @@ #include <winsock.h> #endif - +extern int parse_context_options(php_stream_context *context, zval *options TSRMLS_DC); /* {{{ mysqlnd_set_sock_no_delay */ static int mysqlnd_set_sock_no_delay(php_stream * stream TSRMLS_DC) @@ -858,12 +859,14 @@ static enum_func_status MYSQLND_METHOD(mysqlnd_net, enable_ssl)(MYSQLND_NET * const net TSRMLS_DC) { #ifdef MYSQLND_SSL_SUPPORTED php_stream_context * context = php_stream_context_alloc(TSRMLS_C); php_stream * net_stream = net->data->m.get_stream(net TSRMLS_CC); + parse_context_options(context, FG(default_context)->options TSRMLS_CC) ; + DBG_ENTER("mysqlnd_net::enable_ssl"); if (!context) { DBG_RETURN(FAIL); } diff -urbB ../1/php-5.6.12/ext/standard/streamsfuncs.c ../php-5.6.12/ext/standard/streamsfuncs.c --- ../1/php-5.6.12/ext/standard/streamsfuncs.c 2015-08-06 09:55:57.000000000 +0200 +++ ../php-5.6.12/ext/standard/streamsfuncs.c 2015-08-10 12:44:41.237035776 +0200 @@ -913,7 +913,7 @@ } } -static int parse_context_options(php_stream_context *context, zval *options TSRMLS_DC) +int parse_context_options(php_stream_context *context, zval *options TSRMLS_DC) { HashPosition pos, opos; zval **wval, **oval;Tested 5.6.14 with patches: afd31489d0d9999f701467e99ef2b40794eed196 8292260515a904b4d515484145c78f33a06ae1ae Now it doesn't verify SSL certificate at all. Connection is being made even if peer certificate doesn't match hostname. What's worse even settting: $opts = array('ssl'=>array('verify_peer'=> true, 'verify_peer_name' => true)); stream_context_set_default($opts); doesn't turn ssl verification. So looks to be back to 5.5 state by default, doesn't obey default context stream options BUT... What these patches seem to implement is MYSQLI_CLIENT_SSL_VERIFY_SERVER_CERT (CLIENT_SSL_VERIFY_SERVER_CERT in libmysqlclient API) mysql_connect option. It works now though but is NOT default on. So the question is - why major feature announced at http://php.net/manual/en/migration56.new-features.php: "6. These include enabling peer verification by default, supporting certificate fingerprint matching, mitigating against TLS renegotiation attacks, and many new SSL context options to allow more fine grained control over protocol and verification settings when using encrypted streams." is not being applied to mysql SSL/TLS connections? No strong opinion though. mysql 5.7 probably still doesn't default that option to be on (would be nice to verify that). So summary, php5.6 + these two patches: - back to 5.5 state by default - $opts = array('ssl'=>array('verify_peer'=> true/false, 'verify_peer_name' => true/false)); stream_context_set_default($opts); will not work - not obeyed by mysql ssl connection - CLIENT_SSL_VERIFY_SERVER_CERT support got implemented (as MYSQLI_CLIENT_SSL_VERIFY_SERVER_CERT) and works but turning ON verify_peer/verify_peer_name (is off by default; as it was in 5.5 state)Hi, >Tested 5.6.14 with patches: >afd31489d0d9999f701467e99ef2b40794eed196 >8292260515a904b4d515484145c78f33a06ae1ae >Now it doesn't verify SSL certificate at all. Connection is being made even if peer certificate doesn't match >hostname. What's worse even settting: >$opts = array('ssl'=>array('verify_peer'=> true, 'verify_peer_name' => true)); >stream_context_set_default($opts); yes, defaults never had power over the mysqlnd connnections. >doesn't turn ssl verification. So looks to be back to 5.5 state by default, doesn't obey default context >stream options BUT... yes >What these patches seem to implement is MYSQLI_CLIENT_SSL_VERIFY_SERVER_CERT (CLIENT_SSL_VERIFY_SERVER_CERT >in libmysqlclient API) mysql_connect option. It works now though but is NOT default on. yes, because we need 3 states. OFF (no check), ON (check), DEFAULT (the default behavior of the PHP streams). As I am pushing to 5.6.14 I don't want to change behavior in the 14th release of a stable branch. So it is either OFF or ON. For 7.0 I need to push a change for the third state. >So the question is - why major feature announced at http://php.net/manual/en/migration56.new-features.php: >"6. These include enabling peer verification by default, supporting certificate fingerprint matching, >mitigating against TLS renegotiation attacks, and many new SSL context options to allow more fine grained >control over protocol and verification settings when using encrypted streams." >is not being applied to mysql SSL/TLS connections? Because I want to give the developers a change to migrate to 5.6 from 5.5 . In 7.0 the behavior will be the advertised in this page. The developers then need to change their applications. >No strong opinion though. mysql 5.7 probably still doesn't default that option to be on (would be nice to >verify that). What MySQL 5.7 has to do with this? >So summary, php5.6 + these two patches: >- back to 5.5 state by default yes >- $opts = array('ssl'=>array('verify_peer'=> true/false, 'verify_peer_name' => true/false)); >stream_context_set_default($opts); will not work - not obeyed by mysql ssl connection >- CLIENT_SSL_VERIFY_SERVER_CERT support got implemented (as MYSQLI_CLIENT_SSL_VERIFY_SERVER_CERT) and works >but turning ON verify_peer/verify_peer_name (is off by default; as it was in 5.5 state)\ yes, for explicit check one needs to set this flag, for 5.6 . For 7.0 I will push a change that make verify the default value. Thanks for the input. This is what I needed to hear :) Andreynonsense, besides that's not useable in backwards compatible code MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT is *NOT* known in PHP 5.6.15 [30-Oct-2015 02:49:36 Europe/Vienna] PHP Notice: Use of undefined constant MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT - assumed 'MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT' in /Volumes/dune/www-servers/phpincludes/global_mysql_class.inc.php on line 266 __________________________________ if($this->ssl && $this->host != 'localhost') { $flags = MYSQLI_CLIENT_SSL | MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT; $this->conn->ssl_set($this->ssl_key, $this->ssl_crt, $this->ssl_ca, NULL, 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:RSA-AES256-SHA'); } switch($persistent) { case 1: $rw = @mysqli_real_connect($this->conn, 'p:' . $this->host, $this->user, $this->pwd, $this->db, $this->port, '', $flags); break; default: $rw = @mysqli_real_connect($this->conn, $this->host, $this->user, $this->pwd, $this->db, $this->port, '', $flags); break; }why in the world can't this crap just accept stream_context_set_default(array('ssl'=>array('verify_peer'=>false, 'verify_peer_name'=>false, 'allow_self_signed'=>true)));Here's a script I tried on PHP 7.1.11-0ubuntu0.17.10.1. <?php $pdo = new PDO('mysql:host=...;dbname=...', 'user', 'pass', array( PDO::MYSQL_ATTR_SSL_CA => '/path/to/my/ca.pem', PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT => true ) ); $statement = $pdo->query("SHOW SESSION STATUS LIKE 'ssl_cipher';"); while ($row = $statement->fetch(PDO::FETCH_ASSOC)) print_r($row); ?> When PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT => true, then - I will get "PHP Fatal error: Uncaught PDOException: PDO::__construct(): Peer certificate CN=`development-vm' did not match expected CN=`127.0.0.1'" when the ca.pem is the one that signed the server's cert, but the hostname does not match - I will get "PHP Fatal error: Uncaught PDOException: PDO::__construct(): SSL operation failed with code 1. OpenSSL Error messages: error:14090086:SSL routines:ssl3_get_server_certificate:certificate verify failed" when I use a different ca.pem - I will get "PHP Fatal error: Uncaught PDOException: failed loading cafile stream: ..." when I provide an invalid ca.pem path. When PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT => false, in all three cases (sic!) it will successfully establish a connection and show "DHE-RSA-AES256-SHA" as the cipher in use. The only explanation I have for this is that with PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT set to "false", the client will use SSL *but* accept *any* certificate the server provides.