lbarnaud Wed, 21 Oct 2009 16:10:19 +0000 Revision: http://svn.php.net/viewvc?view=revision&revision=289831
Log: Added client-side Server Name Indication (SNI) support in OpenSSL extension. # # [DOC] # # New SSL context options : # # - SNI_enabled : Set to FALSE to disable SNI support (enabled by default) # - SNI_server_name : If not set, the server name will be guessed from the # stream URL (e.g. https://example.com/ will use example.com as hostname.), # else the given name will be used. # # SNI is to SSL/TLS what the Host header is for HTTP : it allows multiple # certificates on the same IP address. # # As for HTTP virtual hosts, this should be totaly transparent in most cases. # # Context options allows more control, e.g. : # # $context = stream_context_create(array( # 'ssl' => array('SNI_server_name' => 'foo.example.com'), # 'http' => array('header' => 'Host: foo.example.com'), # )); # file_get_contents('https://127.0.0.1/', false, $context); # # OpenSSL >= 0.9.8j supports SNI (by default since OpenSSL 0.9.8k). Changed paths: U php/php-src/trunk/ext/openssl/openssl.c A php/php-src/trunk/ext/openssl/tests/sni_001.phpt U php/php-src/trunk/ext/openssl/xp_ssl.c
Modified: php/php-src/trunk/ext/openssl/openssl.c =================================================================== --- php/php-src/trunk/ext/openssl/openssl.c 2009-10-21 13:06:40 UTC (rev 289830) +++ php/php-src/trunk/ext/openssl/openssl.c 2009-10-21 16:10:19 UTC (rev 289831) @@ -1036,6 +1036,11 @@ REGISTER_LONG_CONSTANT("OPENSSL_KEYTYPE_EC", OPENSSL_KEYTYPE_EC, CONST_CS|CONST_PERSISTENT); #endif +#if OPENSSL_VERSION_NUMBER >= 0x0090806fL && !defined(OPENSSL_NO_TLSEXT) + /* SNI support included in OpenSSL >= 0.9.8j */ + REGISTER_LONG_CONSTANT("OPENSSL_TLSEXT_SERVER_NAME", 1, CONST_CS|CONST_PERSISTENT); +#endif + /* Determine default SSL configuration file */ config_filename = getenv("OPENSSL_CONF"); if (config_filename == NULL) { Added: php/php-src/trunk/ext/openssl/tests/sni_001.phpt =================================================================== --- php/php-src/trunk/ext/openssl/tests/sni_001.phpt (rev 0) +++ php/php-src/trunk/ext/openssl/tests/sni_001.phpt 2009-10-21 16:10:19 UTC (rev 289831) @@ -0,0 +1,178 @@ +--TEST-- +SNI 001 +--SKIPIF-- +<?php + if (!extension_loaded('openssl')) die("skip openssl extension not available"); + if (!getenv('SNI_TESTS')) die("skip Set SNI_TESTS to enable this test (uses remote resources)"); +?> +--FILE-- +<?php +/* Server Name Indication (SNI) tests + * + * This test relies on https://sni.velox.ch/ and thus is disabled by default. + * + * sni.velox.ch uses 3 certificates : + * - CN=alice.sni.velox.ch (sent in response to server_name = alice.sni.velox.ch or not set) + * - CN=bob.sni.velox.ch (sent in response to server_name = bob.sni.velox.ch) + * - CN=*.sni.velox.ch (sent in response to server_name = mallory.sni.velox.ch or *.sni.velox.ch or sni.velox.ch) + * + * The test sends requests to the server, sending different names, and checks which certificate + * the server returned. + */ + +function context() { + return stream_context_create(array( + 'ssl' => array( + 'capture_peer_cert' => true, + ), + )); +} + +function get_CN($context) { + + $ary = stream_context_get_options($context); + assert($ary); + + $cert = $ary['ssl']['peer_certificate']; + assert($cert); + + $cert_ary = openssl_x509_parse($cert); + return $cert_ary['subject']['CN']; +} + +function do_http_test($url, $context) { + + $fh = fopen($url, 'r', false, $context); + assert($fh); + + var_dump(get_CN($context)); +} + +function do_ssl_test($url, $context) { + + $fh = stream_socket_client($url, $errno, $errstr, + ini_get("default_socket_timeout"), STREAM_CLIENT_CONNECT, $context); + assert($fh); + + var_dump(get_CN($context)); +} + +function do_enable_crypto_test($url, $context) { + + $fh = stream_socket_client($url, $errno, $errstr, + ini_get("default_socket_timeout"), STREAM_CLIENT_CONNECT, $context); + assert($fh); + + $r = stream_socket_enable_crypto($fh, true, STREAM_CRYPTO_METHOD_TLS_CLIENT); + assert($r); + + var_dump(get_CN($context)); +} + +/* Test https:// streams */ + +echo "-- auto host name (1) --\n"; +do_http_test('https://alice.sni.velox.ch/', context()); + +echo "-- auto host name (2) --\n"; +do_http_test('https://bob.sni.velox.ch/', context()); + +echo "-- auto host name (3) --\n"; +do_http_test('https://bob.sni.velox.ch./', context()); + +echo "-- user supplied server name --\n"; + +$context = context(); +stream_context_set_option($context, 'ssl', 'SNI_server_name', 'bob.sni.velox.ch'); +stream_context_set_option($context, 'http', 'header', b'Host: bob.sni.velox.ch'); +do_http_test('https://alice.sni.velox.ch/', $context); + +echo "-- sni disabled --\n"; + +$context = context(); +stream_context_set_option($context, 'ssl', 'SNI_enabled', false); +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()); + +echo "-- raw SSL stream (2) --\n"; +do_ssl_test('ssl://mallory.sni.velox.ch:443', context()); + +echo "-- raw SSL stream with user supplied sni --\n"; + +$context = context(); +stream_context_set_option($context, 'ssl', 'SNI_server_name', 'bob.sni.velox.ch'); + +do_ssl_test('ssl://mallory.sni.velox.ch:443', $context); + +echo "-- raw SSL stream with sni disabled --\n"; + +$context = context(); +stream_context_set_option($context, 'ssl', 'SNI_enabled', false); + +do_ssl_test('ssl://mallory.sni.velox.ch:443', $context); + +/* Test tcp:// socket streams with SSL enabled */ + +echo "-- stream_socket_enable_crypto (1) --\n"; + +do_enable_crypto_test('tcp://bob.sni.velox.ch:443', context()); + +echo "-- stream_socket_enable_crypto (2) --\n"; + +do_enable_crypto_test('tcp://mallory.sni.velox.ch:443', context()); + +echo "-- stream_socket_enable_crypto with user supplied sni --\n"; + +$context = context(); +stream_context_set_option($context, 'ssl', 'SNI_server_name', 'bob.sni.velox.ch'); + +do_enable_crypto_test('tcp://mallory.sni.velox.ch:443', $context); + +echo "-- stream_socket_enable_crypto with sni disabled --\n"; + +$context = context(); +stream_context_set_option($context, 'ssl', 'SNI_enabled', false); + +do_enable_crypto_test('tcp://mallory.sni.velox.ch:443', $context); + +echo "-- stream_socket_enable_crypto with long name --\n"; + +$context = context(); +stream_context_set_option($context, 'ssl', 'SNI_server_name', str_repeat('a.', 500) . '.sni.velox.ch'); + +do_enable_crypto_test('tcp://mallory.sni.velox.ch:443', $context); + +?> +--EXPECTF-- +-- auto host name (1) -- +%unicode|string%(18) "alice.sni.velox.ch" +-- auto host name (2) -- +%unicode|string%(16) "bob.sni.velox.ch" +-- auto host name (3) -- +%unicode|string%(16) "bob.sni.velox.ch" +-- user supplied server name -- +%unicode|string%(16) "bob.sni.velox.ch" +-- sni disabled -- +%unicode|string%(18) "alice.sni.velox.ch" +-- raw SSL stream (1) -- +%unicode|string%(16) "bob.sni.velox.ch" +-- raw SSL stream (2) -- +%unicode|string%(14) "*.sni.velox.ch" +-- raw SSL stream with user supplied sni -- +%unicode|string%(16) "bob.sni.velox.ch" +-- raw SSL stream with sni disabled -- +%unicode|string%(18) "alice.sni.velox.ch" +-- stream_socket_enable_crypto (1) -- +%unicode|string%(16) "bob.sni.velox.ch" +-- stream_socket_enable_crypto (2) -- +%unicode|string%(14) "*.sni.velox.ch" +-- stream_socket_enable_crypto with user supplied sni -- +%unicode|string%(16) "bob.sni.velox.ch" +-- stream_socket_enable_crypto with sni disabled -- +%unicode|string%(18) "alice.sni.velox.ch" +-- stream_socket_enable_crypto with long name -- +%unicode|string%(18) "alice.sni.velox.ch" Modified: php/php-src/trunk/ext/openssl/xp_ssl.c =================================================================== --- php/php-src/trunk/ext/openssl/xp_ssl.c 2009-10-21 13:06:40 UTC (rev 289830) +++ php/php-src/trunk/ext/openssl/xp_ssl.c 2009-10-21 16:10:19 UTC (rev 289831) @@ -20,6 +20,7 @@ #include "php.h" #include "ext/standard/file.h" +#include "ext/standard/url.h" #include "streams/php_streams_int.h" #include "ext/standard/php_smart_str.h" #include "php_network.h" @@ -54,6 +55,7 @@ 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; @@ -283,6 +285,9 @@ } } + if (sslsock->sni) { + pefree(sslsock->sni, php_stream_is_persistent(stream)); + } pefree(sslsock, php_stream_is_persistent(stream)); return 0; @@ -393,6 +398,12 @@ float timeout = sslsock->connect_timeout.tv_sec + sslsock->connect_timeout.tv_usec / 1000000; int blocked = sslsock->s.is_blocked; +#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); + } +#endif + if (!sslsock->state_set) { if (sslsock->is_client) { SSL_set_connect_state(sslsock->ssl_handle); @@ -758,7 +769,53 @@ php_openssl_sockop_set_option, }; +static char * get_sni(php_stream_context *ctx, char *resourcename, long resourcenamelen, int is_persistent) { + 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_with_converter_ex(val, UG(utf8_conv)); + return pestrdup(Z_STRVAL_PP(val), is_persistent); + } + } + + if (!resourcename) { + return NULL; + } + + url = php_url_parse_ex(resourcename, resourcenamelen); + if (!url) { + return NULL; + } + + if (url->host) { + const char * host = url->host; + char * sni = NULL; + size_t len = strlen(host); + + /* skip trailing dots */ + while (len && host[len-1] == '.') { + --len; + } + + if (len) { + sni = pestrndup(host, len, is_persistent); + } + + php_url_free(url); + return sni; + } + + php_url_free(url); + return NULL; +} + php_stream *php_openssl_ssl_socket_factory(const char *proto, long protolen, char *resourcename, long resourcenamelen, const char *persistent_id, int options, int flags, @@ -794,6 +851,8 @@ return NULL; } + sslsock->sni = get_sni(context, resourcename, resourcenamelen, !!persistent_id); + if (strncmp(proto, "ssl", protolen) == 0) { sslsock->enable_on_connect = 1; sslsock->method = STREAM_CRYPTO_METHOD_SSLv23_CLIENT;
-- PHP CVS Mailing List (http://www.php.net/) To unsubscribe, visit: http://www.php.net/unsub.php