From 7c0803a8ca75105bf60f7658dc8e21518ca2f8e7 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Mon, 30 Nov 2009 13:31:53 +0000 Subject: [PATCH] merge from trunk: openssl sni support (rev 289831) --- ext/openssl/openssl.c | 5 + ext/openssl/tests/sni_001.phpt | 178 +++++++++++++++++++++++++++++++++ ext/openssl/xp_ssl.c | 59 +++++++++++ 3 files changed, 242 insertions(+) create mode 100644 ext/openssl/tests/sni_001.phpt diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c index e0f7cdb032..7fb933d7c5 100644 --- a/ext/openssl/openssl.c +++ b/ext/openssl/openssl.c @@ -1041,6 +1041,11 @@ PHP_MINIT_FUNCTION(openssl) 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) { diff --git a/ext/openssl/tests/sni_001.phpt b/ext/openssl/tests/sni_001.phpt new file mode 100644 index 0000000000..3d7798cf85 --- /dev/null +++ b/ext/openssl/tests/sni_001.phpt @@ -0,0 +1,178 @@ +--TEST-- +SNI 001 +--SKIPIF-- + +--FILE-- + 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" diff --git a/ext/openssl/xp_ssl.c b/ext/openssl/xp_ssl.c index 2590461c8c..aa2e3810f2 100644 --- a/ext/openssl/xp_ssl.c +++ b/ext/openssl/xp_ssl.c @@ -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 @@ typedef struct _php_openssl_netstream_data_t { 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 @@ static int php_openssl_sockop_close(php_stream *stream, int close_handle TSRMLS_ } } + if (sslsock->sni) { + pefree(sslsock->sni, php_stream_is_persistent(stream)); + } pefree(sslsock, php_stream_is_persistent(stream)); return 0; @@ -393,6 +398,12 @@ static inline int php_openssl_enable_crypto(php_stream *stream, 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); @@ -759,6 +770,52 @@ php_stream_ops php_openssl_socket_ops = { php_openssl_sockop_set_option, }; +static char * get_sni(php_stream_context *ctx, char *resourcename, long 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; + } + + 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, @@ -795,6 +852,8 @@ php_stream *php_openssl_ssl_socket_factory(const char *proto, long 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; sslsock->method = STREAM_CRYPTO_METHOD_SSLv23_CLIENT; -- 2.40.0