diff options
author | Daniel Lowrey <rdlowrey@php.net> | 2015-03-04 13:56:58 -0700 |
---|---|---|
committer | Daniel Lowrey <rdlowrey@php.net> | 2015-03-04 13:56:58 -0700 |
commit | b5d97140c09f5aca752c8cb4bbd5a091fe5e330f (patch) | |
tree | 231ee2070af86074021554c23dfc291f71b0ba59 /ext | |
parent | c9bd24de7adb20dad4b94e9295f6df6cdb0ce594 (diff) | |
parent | 8680fc83316be812748d3bedfc785603f263a568 (diff) | |
download | php-git-b5d97140c09f5aca752c8cb4bbd5a091fe5e330f.tar.gz |
Merge branch 'tls-alpn'
* tls-alpn:
Improve test to target specific issue
Misc updates/cleanup
Add TLS ALPN extension support in crypto client/server streams
Add stream_socket_crypto_info() function
Update for compatibility with newer openssl libs
Diffstat (limited to 'ext')
-rw-r--r-- | ext/openssl/openssl.c | 62 | ||||
-rw-r--r-- | ext/openssl/tests/bug64802.phpt | 47 | ||||
-rw-r--r-- | ext/openssl/xp_ssl.c | 481 | ||||
-rw-r--r-- | ext/standard/basic_functions.c | 6 | ||||
-rw-r--r-- | ext/standard/file.c | 6 | ||||
-rw-r--r-- | ext/standard/streamsfuncs.c | 40 | ||||
-rw-r--r-- | ext/standard/streamsfuncs.h | 1 |
7 files changed, 471 insertions, 172 deletions
diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c index 1cb69585ae..956f36171f 100644 --- a/ext/openssl/openssl.c +++ b/ext/openssl/openssl.c @@ -42,6 +42,10 @@ /* OpenSSL includes */ #include <openssl/evp.h> +#include <openssl/bn.h> +#include <openssl/rsa.h> +#include <openssl/dsa.h> +#include <openssl/dh.h> #include <openssl/x509.h> #include <openssl/x509v3.h> #include <openssl/crypto.h> @@ -3350,22 +3354,46 @@ static EVP_PKEY * php_openssl_generate_private_key(struct php_x509_request * req if ((req->priv_key = EVP_PKEY_new()) != NULL) { switch(req->priv_key_type) { case OPENSSL_KEYTYPE_RSA: - if (EVP_PKEY_assign_RSA(req->priv_key, RSA_generate_key(req->priv_key_bits, 0x10001, NULL, NULL))) { - return_val = req->priv_key; + { + RSA* rsaparam; +#if OPENSSL_VERSION_NUMBER < 0x10002000L + /* OpenSSL 1.0.2 deprecates RSA_generate_key */ + rsaparam = (RSA*)RSA_generate_key(req->priv_key_bits, RSA_F4, NULL, NULL); +#else + { + BIGNUM *bne = (BIGNUM *)BN_new(); + if (BN_set_word(bne, RSA_F4) != 1) { + BN_free(bne); + php_error_docref(NULL, E_WARNING, "failed setting exponent"); + return NULL; + } + rsaparam = RSA_new(); + RSA_generate_key_ex(rsaparam, req->priv_key_bits, bne, NULL); + BN_free(bne); + } +#endif + if (rsaparam && EVP_PKEY_assign_RSA(req->priv_key, rsaparam)) { + return_val = req->priv_key; + } } break; #if !defined(NO_DSA) && defined(HAVE_DSA_DEFAULT_METHOD) case OPENSSL_KEYTYPE_DSA: { - DSA *dsapar = DSA_generate_parameters(req->priv_key_bits, NULL, 0, NULL, NULL, NULL, NULL); - if (dsapar) { - DSA_set_method(dsapar, DSA_get_default_method()); - if (DSA_generate_key(dsapar)) { - if (EVP_PKEY_assign_DSA(req->priv_key, dsapar)) { + DSA *dsaparam = NULL; +#if OPENSSL_VERSION_NUMBER < 0x10002000L + dsaparam = DSA_generate_parameters(req->priv_key_bits, NULL, 0, NULL, NULL, NULL, NULL); +#else + DSA_generate_parameters_ex(dsaparam, req->priv_key_bits, NULL, 0, NULL, NULL, NULL); +#endif + if (dsaparam) { + DSA_set_method(dsaparam, DSA_get_default_method()); + if (DSA_generate_key(dsaparam)) { + if (EVP_PKEY_assign_DSA(req->priv_key, dsaparam)) { return_val = req->priv_key; } } else { - DSA_free(dsapar); + DSA_free(dsaparam); } } } @@ -3374,17 +3402,21 @@ static EVP_PKEY * php_openssl_generate_private_key(struct php_x509_request * req #if !defined(NO_DH) case OPENSSL_KEYTYPE_DH: { - DH *dhpar = DH_generate_parameters(req->priv_key_bits, 2, NULL, NULL); int codes = 0; - - if (dhpar) { - DH_set_method(dhpar, DH_get_default_method()); - if (DH_check(dhpar, &codes) && codes == 0 && DH_generate_key(dhpar)) { - if (EVP_PKEY_assign_DH(req->priv_key, dhpar)) { + DH *dhparam = NULL; +#if OPENSSL_VERSION_NUMBER < 0x10002000L + dhparam = DH_generate_parameters(req->priv_key_bits, 2, NULL, NULL); +#else + DH_generate_parameters_ex(dhparam, req->priv_key_bits, 2, NULL); +#endif + if (dhparam) { + DH_set_method(dhparam, DH_get_default_method()); + if (DH_check(dhparam, &codes) && codes == 0 && DH_generate_key(dhparam)) { + if (EVP_PKEY_assign_DH(req->priv_key, dhparam)) { return_val = req->priv_key; } } else { - DH_free(dhpar); + DH_free(dhparam); } } } diff --git a/ext/openssl/tests/bug64802.phpt b/ext/openssl/tests/bug64802.phpt index 3a1e4fb03c..3b273998d0 100644 --- a/ext/openssl/tests/bug64802.phpt +++ b/ext/openssl/tests/bug64802.phpt @@ -8,50 +8,21 @@ if (!defined('OPENSSL_KEYTYPE_EC')) die("skip no EC available"); --FILE-- <?php $cert = file_get_contents(__DIR__.'/bug64802.pem'); -$r = openssl_x509_parse($cert,$use_short_names=true); -sort($r['subject']); -var_dump( $r['subject'] ); +$r = openssl_x509_parse($cert,$use_short_names=false); +var_dump($r['subject']['commonName']); ?> --EXPECTF-- -array(11) { +array(6) { [0]=> - string(14) "1550 Bryant st" + string(9) "www.rd.io" [1]=> - string(5) "94103" + string(8) "rdio.com" [2]=> - string(7) "4586007" + string(5) "rd.io" [3]=> - string(2) "CA" + string(12) "api.rdio.com" [4]=> - string(26) "COMODO EV Multi-Domain SSL" + string(9) "api.rd.io" [5]=> - string(20) "Private Organization" - [6]=> - string(10) "Rdio, Inc." - [7]=> - string(13) "San Francisco" - [8]=> - string(2) "US" - [9]=> - array(2) { - [0]=> - string(2) "US" - [1]=> - string(8) "Delaware" - } - [10]=> - array(6) { - [0]=> - string(9) "www.rd.io" - [1]=> - string(8) "rdio.com" - [2]=> - string(5) "rd.io" - [3]=> - string(12) "api.rdio.com" - [4]=> - string(9) "api.rd.io" - [5]=> - string(12) "www.rdio.com" - } + string(12) "www.rdio.com" } diff --git a/ext/openssl/xp_ssl.c b/ext/openssl/xp_ssl.c index aac84b4426..cb52c0420c 100644 --- a/ext/openssl/xp_ssl.c +++ b/ext/openssl/xp_ssl.c @@ -32,10 +32,16 @@ #include "php_openssl.h" #include "php_network.h" #include <openssl/ssl.h> +#include <openssl/rsa.h> #include <openssl/x509.h> #include <openssl/x509v3.h> #include <openssl/err.h> +#if OPENSSL_VERSION_NUMBER >= 0x10002000L +#include <openssl/bn.h> +#include <openssl/dh.h> +#endif + #ifdef PHP_WIN32 #include "win32/winutil.h" #include "win32/time.h" @@ -50,14 +56,34 @@ #include <sys/select.h> #endif +/* OpenSSL 1.0.2 removes SSLv2 support entirely*/ +#if OPENSSL_VERSION_NUMBER < 0x10002000L && !defined(OPENSSL_NO_SSL2) +#define HAVE_SSL2 1 +#endif + +#ifndef OPENSSL_NO_SSL3 +#define HAVE_SSL3 1 +#endif + +#if OPENSSL_VERSION_NUMBER >= 0x10001001L +#define HAVE_TLS11 1 +#define HAVE_TLS12 1 +#endif + #if !defined(OPENSSL_NO_ECDH) && OPENSSL_VERSION_NUMBER >= 0x0090800fL #define HAVE_ECDH 1 #endif -#if OPENSSL_VERSION_NUMBER >= 0x00908070L && !defined(OPENSSL_NO_TLSEXT) -#define HAVE_SNI 1 +#if !defined(OPENSSL_NO_TLSEXT) +#if OPENSSL_VERSION_NUMBER >= 0x00908070L +#define HAVE_TLS_SNI 1 +#endif +#if OPENSSL_VERSION_NUMBER >= 0x10002000L +#define HAVE_TLS_ALPN 1 +#endif #endif + /* Flags for determining allowed stream crypto methods */ #define STREAM_CRYPTO_IS_CLIENT (1<<0) #define STREAM_CRYPTO_METHOD_SSLv2 (1<<1) @@ -74,6 +100,10 @@ /* Used for peer verification in windows */ #define PHP_X509_NAME_ENTRY_TO_UTF8(ne, i, out) ASN1_STRING_to_UTF8(&out, X509_NAME_ENTRY_get_data(X509_NAME_get_entry(ne, i))) +#ifndef OPENSSL_NO_RSA +static RSA *tmp_rsa_cb(SSL *s, int is_export, int keylength); +#endif + extern php_stream* php_openssl_get_stream_from_ssl_handle(const SSL *ssl); extern zend_string* php_openssl_x509_fingerprint(X509 *peer, const char *method, zend_bool raw); extern int php_openssl_get_ssl_stream_data_index(); @@ -99,6 +129,14 @@ typedef struct _php_openssl_handshake_bucket_t { unsigned should_close; } php_openssl_handshake_bucket_t; +#ifdef HAVE_TLS_ALPN +/* Holds the available server ALPN protocols for negotiation */ +typedef struct _php_openssl_alpn_ctx_t { + unsigned char *data; + unsigned short len; +} php_openssl_alpn_ctx; +#endif + /* 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! @@ -115,6 +153,9 @@ typedef struct _php_openssl_netstream_data_t { php_openssl_handshake_bucket_t *reneg; php_openssl_sni_cert_t *sni_certs; unsigned sni_cert_count; +#ifdef HAVE_TLS_ALPN + php_openssl_alpn_ctx *alpn_ctx; +#endif char *url_name; unsigned state_set:1; unsigned _spare:31; @@ -897,37 +938,37 @@ static int set_local_cert(SSL_CTX *ctx, php_stream *stream) /* {{{ */ static const SSL_METHOD *php_select_crypto_method(zend_long method_value, int is_client) /* {{{ */ { if (method_value == STREAM_CRYPTO_METHOD_SSLv2) { -#ifndef OPENSSL_NO_SSL2 - return is_client ? SSLv2_client_method() : SSLv2_server_method(); +#ifdef HAVE_SSL2 + return is_client ? (SSL_METHOD *)SSLv2_client_method() : (SSL_METHOD *)SSLv2_server_method(); #else php_error_docref(NULL, E_WARNING, - "SSLv2 support is not compiled into the OpenSSL library PHP is linked against"); + "SSLv2 unavailable in the OpenSSL library against which PHP is linked"); return NULL; #endif } else if (method_value == STREAM_CRYPTO_METHOD_SSLv3) { -#ifndef OPENSSL_NO_SSL3 +#ifdef HAVE_SSL3 return is_client ? SSLv3_client_method() : SSLv3_server_method(); #else php_error_docref(NULL, E_WARNING, - "SSLv3 support is not compiled into the OpenSSL library PHP is linked against"); + "SSLv3 unavailable in the OpenSSL library against which PHP is linked"); return NULL; #endif } else if (method_value == STREAM_CRYPTO_METHOD_TLSv1_0) { return is_client ? TLSv1_client_method() : TLSv1_server_method(); } else if (method_value == STREAM_CRYPTO_METHOD_TLSv1_1) { -#if OPENSSL_VERSION_NUMBER >= 0x10001001L +#ifdef HAVE_TLS11 return is_client ? TLSv1_1_client_method() : TLSv1_1_server_method(); #else php_error_docref(NULL, E_WARNING, - "TLSv1.1 support is not compiled into the OpenSSL library PHP is linked against"); + "TLSv1.1 unavailable in the OpenSSL library against which PHP is linked"); return NULL; #endif } else if (method_value == STREAM_CRYPTO_METHOD_TLSv1_2) { -#if OPENSSL_VERSION_NUMBER >= 0x10001001L +#ifdef HAVE_TLS12 return is_client ? TLSv1_2_client_method() : TLSv1_2_server_method(); #else php_error_docref(NULL, E_WARNING, - "TLSv1.2 support is not compiled into the OpenSSL library PHP is linked against"); + "TLSv1.2 unavailable in the OpenSSL library against which PHP is linked"); return NULL; #endif } else { @@ -942,26 +983,25 @@ static int php_get_crypto_method_ctx_flags(int method_flags) /* {{{ */ { int ssl_ctx_options = SSL_OP_ALL; -#ifndef OPENSSL_NO_SSL2 +#ifdef HAVE_SSL2 if (!(method_flags & STREAM_CRYPTO_METHOD_SSLv2)) { ssl_ctx_options |= SSL_OP_NO_SSLv2; } #endif -#ifndef OPENSSL_NO_SSL3 +#ifdef HAVE_SSL3 if (!(method_flags & STREAM_CRYPTO_METHOD_SSLv3)) { ssl_ctx_options |= SSL_OP_NO_SSLv3; } #endif -#ifndef OPENSSL_NO_TLS1 if (!(method_flags & STREAM_CRYPTO_METHOD_TLSv1_0)) { ssl_ctx_options |= SSL_OP_NO_TLSv1; } -#endif -#if OPENSSL_VERSION_NUMBER >= 0x10001001L +#ifdef HAVE_TLS11 if (!(method_flags & STREAM_CRYPTO_METHOD_TLSv1_1)) { ssl_ctx_options |= SSL_OP_NO_TLSv1_1; } - +#endif +#ifdef HAVE_TLS12 if (!(method_flags & STREAM_CRYPTO_METHOD_TLSv1_2)) { ssl_ctx_options |= SSL_OP_NO_TLSv1_2; } @@ -1082,45 +1122,53 @@ static void init_server_reneg_limit(php_stream *stream, php_openssl_netstream_da } /* }}} */ -static int set_server_rsa_key(php_stream *stream, SSL_CTX *ctx) /* {{{ */ +#ifndef OPENSSL_NO_RSA +static RSA *tmp_rsa_cb(SSL *s, int is_export, int keylength) { - zval *val; - int rsa_key_size; - RSA* rsa; + BIGNUM *bn = NULL; + static RSA *rsa_tmp = NULL; - if ((val = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "ssl", "rsa_key_size")) != NULL) { - rsa_key_size = (int) Z_LVAL_P(val); - if ((rsa_key_size != 1) && (rsa_key_size & (rsa_key_size - 1))) { - php_error_docref(NULL, E_WARNING, "RSA key size requires a power of 2: %d", rsa_key_size); - rsa_key_size = 2048; - } - } else { - rsa_key_size = 2048; + if (!rsa_tmp && ((bn = BN_new()) == NULL)) { + php_error_docref(NULL, E_WARNING, "allocation error generating RSA key"); } - - rsa = RSA_generate_key(rsa_key_size, RSA_F4, NULL, NULL); - - if (!SSL_CTX_set_tmp_rsa(ctx, rsa)) { - php_error_docref(NULL, E_WARNING, "Failed setting RSA key"); - RSA_free(rsa); - return FAILURE; + if (!rsa_tmp && bn) { + if (!BN_set_word(bn, RSA_F4) || ((rsa_tmp = RSA_new()) == NULL) || + !RSA_generate_key_ex(rsa_tmp, keylength, bn, NULL)) { + if (rsa_tmp) { + RSA_free(rsa_tmp); + } + rsa_tmp = NULL; + } + BN_free(bn); } - RSA_free(rsa); - - return SUCCESS; + return (rsa_tmp); } -/* }}} */ +#endif -static int set_server_dh_param(SSL_CTX *ctx, char *dh_path) /* {{{ */ +#ifndef OPENSSL_NO_DH +static int set_server_dh_param(php_stream * stream, SSL_CTX *ctx) /* {{{ */ { DH *dh; BIO* bio; + zval *zdhpath; + + zdhpath = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "ssl", "dh_param"); + if (zdhpath == NULL) { +#if 0 + /* Coming in OpenSSL 1.1 ... eventually we'll want to enable this + * in the absence of an explicit dh_param. + */ + SSL_CTX_set_dh_auto(ctx, 1); +#endif + return SUCCESS; + } - bio = BIO_new_file(dh_path, "r"); + convert_to_string_ex(zdhpath); + bio = BIO_new_file(Z_STRVAL_P(zdhpath), "r"); if (bio == NULL) { - php_error_docref(NULL, E_WARNING, "Invalid dh_param file: %s", dh_path); + php_error_docref(NULL, E_WARNING, "invalid dh_param"); return FAILURE; } @@ -1128,12 +1176,12 @@ static int set_server_dh_param(SSL_CTX *ctx, char *dh_path) /* {{{ */ BIO_free(bio); if (dh == NULL) { - php_error_docref(NULL, E_WARNING, "Failed reading DH params from file: %s", dh_path); + php_error_docref(NULL, E_WARNING, "failed reading DH params"); return FAILURE; } if (SSL_CTX_set_tmp_dh(ctx, dh) < 0) { - php_error_docref(NULL, E_WARNING, "DH param assignment failed"); + php_error_docref(NULL, E_WARNING, "failed assigning DH params"); DH_free(dh); return FAILURE; } @@ -1143,32 +1191,35 @@ static int set_server_dh_param(SSL_CTX *ctx, char *dh_path) /* {{{ */ return SUCCESS; } /* }}} */ +#endif #ifdef HAVE_ECDH static int set_server_ecdh_curve(php_stream *stream, SSL_CTX *ctx) /* {{{ */ { - zval *val; + zval *zvcurve; int curve_nid; - char *curve_str; EC_KEY *ecdh; - if ((val = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "ssl", "ecdh_curve")) != NULL) { - convert_to_string_ex(val); - curve_str = Z_STRVAL_P(val); - curve_nid = OBJ_sn2nid(curve_str); + zvcurve = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "ssl", "ecdh_curve"); + if (zvcurve == NULL) { +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + SSL_CTX_set_ecdh_auto(ctx, 1); + return SUCCESS; +#else + curve_nid = NID_X9_62_prime256v1; +#endif + } else { + convert_to_string_ex(zvcurve); + curve_nid = OBJ_sn2nid(Z_STRVAL_P(zvcurve)); if (curve_nid == NID_undef) { - php_error_docref(NULL, E_WARNING, "Invalid ECDH curve: %s", curve_str); + php_error_docref(NULL, E_WARNING, "invalid ecdh_curve specified"); return FAILURE; } - } else { - curve_nid = NID_X9_62_prime256v1; } ecdh = EC_KEY_new_by_curve_name(curve_nid); if (ecdh == NULL) { - php_error_docref(NULL, E_WARNING, - "Failed generating ECDH curve"); - + php_error_docref(NULL, E_WARNING, "failed generating ECDH curve"); return FAILURE; } @@ -1182,55 +1233,35 @@ static int set_server_ecdh_curve(php_stream *stream, SSL_CTX *ctx) /* {{{ */ static int set_server_specific_opts(php_stream *stream, SSL_CTX *ctx) /* {{{ */ { - zval *val; + zval *zv; long ssl_ctx_options = SSL_CTX_get_options(ctx); #ifdef HAVE_ECDH - if (FAILURE == set_server_ecdh_curve(stream, ctx)) { - return FAILURE; - } -#else - val = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "ssl", "ecdh_curve"); - if (val != NULL) { - php_error_docref(NULL, E_WARNING, - "ECDH curve support not compiled into the OpenSSL lib against which PHP is linked"); - + if (set_server_ecdh_curve(stream, ctx) == FAILURE) { return FAILURE; } #endif - if ((val = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "ssl", "dh_param")) != NULL) { - convert_to_string_ex(val); - if (FAILURE == set_server_dh_param(ctx, Z_STRVAL_P(val))) { - return FAILURE; - } - } - - if (FAILURE == set_server_rsa_key(stream, ctx)) { - return FAILURE; - } - - if (NULL != (val = php_stream_context_get_option( - PHP_STREAM_CONTEXT(stream), "ssl", "honor_cipher_order")) && - zend_is_true(val) - ) { - ssl_ctx_options |= SSL_OP_CIPHER_SERVER_PREFERENCE; +#ifndef OPENSSL_NO_RSA + SSL_CTX_set_tmp_rsa_callback(ctx, tmp_rsa_cb); +#endif + /* We now use tmp_rsa_cb to generate a key of appropriate size whenever necessary */ + if (php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "ssl", "rsa_key_size") != NULL) { + php_error_docref(NULL, E_WARNING, "rsa_key_size context option has been removed"); } - if (NULL != (val = php_stream_context_get_option( - PHP_STREAM_CONTEXT(stream), "ssl", "single_dh_use")) && - zend_is_true(val) - ) { +#ifndef OPENSSL_NO_DH + set_server_dh_param(stream, ctx); + zv = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "ssl", "single_dh_use"); + if (zv != NULL && zend_is_true(zv)) { ssl_ctx_options |= SSL_OP_SINGLE_DH_USE; } +#endif -#ifdef HAVE_ECDH - if (NULL != (val = php_stream_context_get_option( - PHP_STREAM_CONTEXT(stream), "ssl", "single_ecdh_use")) && - zend_is_true(val)) { - ssl_ctx_options |= SSL_OP_SINGLE_ECDH_USE; + zv = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "ssl", "honor_cipher_order"); + if (zv != NULL && zend_is_true(zv)) { + ssl_ctx_options |= SSL_OP_CIPHER_SERVER_PREFERENCE; } -#endif SSL_CTX_set_options(ctx, ssl_ctx_options); @@ -1238,7 +1269,7 @@ static int set_server_specific_opts(php_stream *stream, SSL_CTX *ctx) /* {{{ */ } /* }}} */ -#ifdef HAVE_SNI +#ifdef HAVE_TLS_SNI static int server_sni_callback(SSL *ssl_handle, int *al, void *arg) /* {{{ */ { php_stream *stream; @@ -1385,6 +1416,65 @@ static void enable_client_sni(php_stream *stream, php_openssl_netstream_data_t * /* }}} */ #endif +#ifdef HAVE_TLS_ALPN +/*- + * parses a comma-separated list of strings into a string suitable for SSL_CTX_set_next_protos_advertised + * outlen: (output) set to the length of the resulting buffer on success. + * err: (maybe NULL) on failure, an error message line is written to this BIO. + * in: a NULL terminated string like "abc,def,ghi" + * + * returns: an emalloced buffer or NULL on failure. + */ +static unsigned char *alpn_protos_parse(unsigned short *outlen, const char *in) +{ + size_t len; + unsigned char *out; + size_t i, start = 0; + + len = strlen(in); + if (len >= 65535) { + return NULL; + } + + out = emalloc(strlen(in) + 1); + if (!out) { + return NULL; + } + + for (i = 0; i <= len; ++i) { + if (i == len || in[i] == ',') { + if (i - start > 255) { + efree(out); + return NULL; + } + out[start] = i - start; + start = i + 1; + } else { + out[i + 1] = in[i]; + } + } + + *outlen = len + 1; + + return out; +} + +static int server_alpn_callback(SSL *ssl_handle, const unsigned char **out, unsigned char *outlen, + const unsigned char *in, unsigned int inlen, void *arg) +{ + php_openssl_netstream_data_t *sslsock = arg; + + if (SSL_select_next_proto + ((unsigned char **)out, outlen, sslsock->alpn_ctx->data, sslsock->alpn_ctx->len, in, + inlen) != OPENSSL_NPN_NEGOTIATED) { + return SSL_TLSEXT_ERR_NOACK; + } + + return SSL_TLSEXT_ERR_OK; +} + +#endif + int php_openssl_setup_crypto(php_stream *stream, php_openssl_netstream_data_t *sslsock, php_stream_xport_crypto_param *cparam @@ -1394,6 +1484,7 @@ int php_openssl_setup_crypto(php_stream *stream, int ssl_ctx_options; int method_flags; char *cipherlist = NULL; + char *alpn_protocols = NULL; zval *val; if (sslsock->ssl_handle) { @@ -1478,6 +1569,38 @@ int php_openssl_setup_crypto(php_stream *stream, return FAILURE; } } + + GET_VER_OPT_STRING("alpn_protocols", alpn_protocols); + if (alpn_protocols) { +#ifdef HAVE_TLS_ALPN + { + unsigned short alpn_len; + unsigned char *alpn = alpn_protos_parse(&alpn_len, alpn_protocols); + + if (alpn == NULL) { + php_error_docref(NULL, E_WARNING, "Failed parsing comma-separated TLS ALPN protocol string"); + SSL_CTX_free(sslsock->ctx); + sslsock->ctx = NULL; + return FAILURE; + } + + if (sslsock->is_client) { + SSL_CTX_set_alpn_protos(sslsock->ctx, alpn, alpn_len); + } else { + sslsock->alpn_ctx = (php_openssl_alpn_ctx *) emalloc(sizeof(php_openssl_alpn_ctx)); + sslsock->alpn_ctx->data = (unsigned char*)estrndup((const char*)alpn, alpn_len); + sslsock->alpn_ctx->len = alpn_len; + SSL_CTX_set_alpn_select_cb(sslsock->ctx, server_alpn_callback, sslsock); + } + + efree(alpn); + } +#else + php_error_docref(NULL, E_WARNING, + "alpn_protocols support is not compiled into the OpenSSL library against which PHP is linked"); +#endif + } + if (FAILURE == set_local_cert(sslsock->ctx, stream)) { return FAILURE; } @@ -1492,6 +1615,7 @@ int php_openssl_setup_crypto(php_stream *stream, } sslsock->ssl_handle = SSL_new(sslsock->ctx); + if (sslsock->ssl_handle == NULL) { php_error_docref(NULL, E_WARNING, "SSL handle creation failure"); SSL_CTX_free(sslsock->ctx); @@ -1505,15 +1629,15 @@ int php_openssl_setup_crypto(php_stream *stream, handle_ssl_error(stream, 0, 1); } -#ifdef HAVE_SNI +#ifdef HAVE_TLS_SNI /* Enable server-side SNI */ - if (sslsock->is_client == 0 && enable_server_sni(stream, sslsock) == FAILURE) { + if (!sslsock->is_client && enable_server_sni(stream, sslsock) == FAILURE) { return FAILURE; } #endif /* Enable server-side handshake renegotiation rate-limiting */ - if (sslsock->is_client == 0) { + if (!sslsock->is_client) { init_server_reneg_limit(stream, sslsock); } @@ -1546,13 +1670,29 @@ static zend_array *capture_session_meta(SSL *ssl_handle) /* {{{ */ const SSL_CIPHER *cipher = SSL_get_current_cipher(ssl_handle); switch (proto) { -#if OPENSSL_VERSION_NUMBER >= 0x10001001L - case TLS1_2_VERSION: proto_str = "TLSv1.2"; break; - case TLS1_1_VERSION: proto_str = "TLSv1.1"; break; +#ifdef HAVE_TLS12 + case TLS1_2_VERSION: + proto_str = "TLSv1.2"; + break; +#endif +#ifdef HAVE_TLS11 + case TLS1_1_VERSION: + proto_str = "TLSv1.1"; + break; +#endif + case TLS1_VERSION: + proto_str = "TLSv1"; + break; +#ifdef HAVE_SSL3 + case SSL3_VERSION: + proto_str = "SSLv3"; + break; +#endif +#ifdef HAVE_SSL2 + case SSL2_VERSION: + proto_str = "SSLv2"; + break; #endif - case TLS1_VERSION: proto_str = "TLSv1"; break; - case SSL3_VERSION: proto_str = "SSLv3"; break; - case SSL2_VERSION: proto_str = "SSLv2"; break; default: proto_str = "UNKNOWN"; } @@ -1566,6 +1706,105 @@ static zend_array *capture_session_meta(SSL *ssl_handle) /* {{{ */ } /* }}} */ +static int php_openssl_crypto_info(php_stream *stream, + php_openssl_netstream_data_t *sslsock, + php_stream_xport_crypto_param *cparam + ) /* {{{ */ +{ + zval *zresult; + const SSL_CIPHER *cipher; + char *cipher_name, *cipher_version, *crypto_protocol; + int cipher_bits; + const unsigned char *alpn_proto = NULL; + unsigned int alpn_proto_len = 0; + int needs_array_return; + + if (!sslsock->ssl_active) { + php_error_docref(NULL, E_WARNING, "SSL/TLS not currently enabled for this stream"); + return FAILURE; + } + + zresult = cparam->inputs.zresult; + needs_array_return = (cparam->inputs.infotype == STREAM_CRYPTO_INFO_ALL) ? 1 : 0; + + if (cparam->inputs.infotype & STREAM_CRYPTO_INFO_ALPN_PROTOCOL) { +#ifdef HAVE_TLS_ALPN + SSL_get0_alpn_selected(sslsock->ssl_handle, &alpn_proto, &alpn_proto_len); +#endif + if (!needs_array_return) { + if (alpn_proto_len > 0) { + ZVAL_STRINGL(zresult, (const char*)alpn_proto, alpn_proto_len); + } + return SUCCESS; + } + } + + if (cparam->inputs.infotype & STREAM_CRYPTO_INFO_CIPHER) { + cipher = SSL_get_current_cipher(sslsock->ssl_handle); + + if (cparam->inputs.infotype & STREAM_CRYPTO_INFO_CIPHER_NAME) { + cipher_name = (char *)SSL_CIPHER_get_name(cipher); + if (!needs_array_return) { + ZVAL_STRING(zresult, cipher_name); + return SUCCESS; + } + } + + if (cparam->inputs.infotype & STREAM_CRYPTO_INFO_CIPHER_BITS) { + cipher_bits = SSL_CIPHER_get_bits(cipher, NULL); + if (!needs_array_return) { + ZVAL_LONG(zresult, cipher_bits); + return SUCCESS; + } + } + + if (cparam->inputs.infotype & STREAM_CRYPTO_INFO_CIPHER_VERSION) { + cipher_version = (char *)SSL_CIPHER_get_version(cipher); + if (!needs_array_return) { + ZVAL_STRING(zresult, cipher_version); + return SUCCESS; + } + } + } + + if (cparam->inputs.infotype & STREAM_CRYPTO_INFO_PROTOCOL) { + switch (SSL_version(sslsock->ssl_handle)) { +#ifdef HAVE_TLS12 + case TLS1_2_VERSION: crypto_protocol = "TLSv1.2"; break; +#endif +#ifdef HAVE_TLS11 + case TLS1_1_VERSION: crypto_protocol = "TLSv1.1"; break; +#endif + case TLS1_VERSION: crypto_protocol = "TLSv1"; break; +#ifdef HAVE_SSL3 + case SSL3_VERSION: crypto_protocol = "SSLv3"; break; +#endif +#ifdef HAVE_SSL2 + case SSL2_VERSION: crypto_protocol = "SSLv2"; break; +#endif + default: crypto_protocol = "UNKNOWN"; + } + + if (!needs_array_return) { + ZVAL_STRING(zresult, crypto_protocol); + return SUCCESS; + } + } + + /* If we're still here we need to return an array with everything */ + array_init(zresult); + add_assoc_string(zresult, "protocol", crypto_protocol); + add_assoc_string(zresult, "cipher_name", cipher_name); + add_assoc_long(zresult, "cipher_bits", cipher_bits); + add_assoc_string(zresult, "cipher_version", cipher_version); + if (alpn_proto) { + add_assoc_stringl(zresult, "alpn_protocol", (char *) alpn_proto, alpn_proto_len); + } + + return SUCCESS; +} +/* }}} */ + static int capture_peer_certs(php_stream *stream, php_openssl_netstream_data_t *sslsock, X509 *peer_cert) /* {{{ */ { zval *val, zcert; @@ -1627,7 +1866,7 @@ static int php_openssl_enable_crypto(php_stream *stream, int blocked = sslsock->s.is_blocked, has_timeout = 0; -#ifdef HAVE_SNI +#ifdef HAVE_TLS_SNI if (sslsock->is_client) { enable_client_sni(stream, sslsock); } @@ -1711,10 +1950,8 @@ static int php_openssl_enable_crypto(php_stream *stream, if (PHP_STREAM_CONTEXT(stream)) { zval *val; - if (NULL != (val = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), - "ssl", "capture_session_meta")) && - zend_is_true(val) + "ssl", "capture_session_meta")) && zend_is_true(val) ) { zval meta_arr; ZVAL_ARR(&meta_arr, capture_session_meta(sslsock->ssl_handle)); @@ -1727,6 +1964,7 @@ static int php_openssl_enable_crypto(php_stream *stream, n = 0; } else { n = -1; + /* We want to capture the peer cert even if verification fails*/ peer_cert = SSL_get_peer_certificate(sslsock->ssl_handle); if (peer_cert && PHP_STREAM_CONTEXT(stream)) { cert_captured = capture_peer_certs(stream, sslsock, peer_cert); @@ -2171,6 +2409,11 @@ static int php_openssl_sockop_set_option(php_stream *stream, int option, int val cparam->outputs.returncode = php_openssl_enable_crypto(stream, sslsock, cparam); return PHP_STREAM_OPTION_RETURN_OK; break; + case STREAM_XPORT_CRYPTO_OP_INFO: + return (php_openssl_crypto_info(stream, sslsock, cparam) == SUCCESS) + ? PHP_STREAM_OPTION_RETURN_OK + : PHP_STREAM_OPTION_RETURN_ERR; + break; default: /* fall through */ break; @@ -2356,20 +2599,20 @@ php_stream *php_openssl_ssl_socket_factory(const char *proto, size_t protolen, sslsock->enable_on_connect = 1; sslsock->method = get_crypto_method(context, STREAM_CRYPTO_METHOD_ANY_CLIENT); } else if (strncmp(proto, "sslv2", protolen) == 0) { -#ifdef OPENSSL_NO_SSL2 - php_error_docref(NULL, E_WARNING, "SSLv2 support is not compiled into the OpenSSL library PHP is linked against"); - return NULL; -#else +#ifdef HAVE_SSL2 sslsock->enable_on_connect = 1; sslsock->method = STREAM_CRYPTO_METHOD_SSLv2_CLIENT; +#else + php_error_docref(NULL, E_WARNING, "SSLv2 support is not compiled into the OpenSSL library against which PHP is linked"); + return NULL; #endif } else if (strncmp(proto, "sslv3", protolen) == 0) { -#ifdef OPENSSL_NO_SSL3 - php_error_docref(NULL, E_WARNING, "SSLv3 support is not compiled into the OpenSSL library PHP is linked against"); - return NULL; -#else +#ifdef HAVE_SSL3 sslsock->enable_on_connect = 1; sslsock->method = STREAM_CRYPTO_METHOD_SSLv3_CLIENT; +#else + php_error_docref(NULL, E_WARNING, "SSLv3 support is not compiled into the OpenSSL library against which PHP is linked"); + return NULL; #endif } else if (strncmp(proto, "tls", protolen) == 0) { sslsock->enable_on_connect = 1; @@ -2378,19 +2621,19 @@ php_stream *php_openssl_ssl_socket_factory(const char *proto, size_t protolen, sslsock->enable_on_connect = 1; sslsock->method = STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT; } else if (strncmp(proto, "tlsv1.1", protolen) == 0) { -#if OPENSSL_VERSION_NUMBER >= 0x10001001L +#ifdef HAVE_TLS11 sslsock->enable_on_connect = 1; sslsock->method = STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT; #else - php_error_docref(NULL, E_WARNING, "TLSv1.1 support is not compiled into the OpenSSL library PHP is linked against"); + php_error_docref(NULL, E_WARNING, "TLSv1.1 support is not compiled into the OpenSSL library against which PHP is linked"); return NULL; #endif } else if (strncmp(proto, "tlsv1.2", protolen) == 0) { -#if OPENSSL_VERSION_NUMBER >= 0x10001001L +#ifdef HAVE_TLS12 sslsock->enable_on_connect = 1; sslsock->method = STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT; #else - php_error_docref(NULL, E_WARNING, "TLSv1.2 support is not compiled into the OpenSSL library PHP is linked against"); + php_error_docref(NULL, E_WARNING, "TLSv1.2 support is not compiled into the OpenSSL library against which PHP is linked"); return NULL; #endif } diff --git a/ext/standard/basic_functions.c b/ext/standard/basic_functions.c index f1c671d532..6760177a00 100644 --- a/ext/standard/basic_functions.c +++ b/ext/standard/basic_functions.c @@ -2103,6 +2103,11 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_stream_socket_enable_crypto, 0, 0, 2) ZEND_ARG_INFO(0, sessionstream) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_INFO(arginfo_stream_socket_crypto_info, 0) + ZEND_ARG_INFO(0, stream) + ZEND_ARG_INFO(0, infotype) +ZEND_END_ARG_INFO() + #ifdef HAVE_SHUTDOWN ZEND_BEGIN_ARG_INFO(arginfo_stream_socket_shutdown, 0) ZEND_ARG_INFO(0, stream) @@ -3086,6 +3091,7 @@ const zend_function_entry basic_functions[] = { /* {{{ */ PHP_FE(stream_socket_recvfrom, arginfo_stream_socket_recvfrom) PHP_FE(stream_socket_sendto, arginfo_stream_socket_sendto) PHP_FE(stream_socket_enable_crypto, arginfo_stream_socket_enable_crypto) + PHP_FE(stream_socket_crypto_info, arginfo_stream_socket_crypto_info) #ifdef HAVE_SHUTDOWN PHP_FE(stream_socket_shutdown, arginfo_stream_socket_shutdown) #endif diff --git a/ext/standard/file.c b/ext/standard/file.c index dae4235a99..d3ed5e12e8 100644 --- a/ext/standard/file.c +++ b/ext/standard/file.c @@ -231,6 +231,12 @@ PHP_MINIT_FUNCTION(file) REGISTER_LONG_CONSTANT("STREAM_CRYPTO_METHOD_TLSv1_1_SERVER", STREAM_CRYPTO_METHOD_TLSv1_1_SERVER, CONST_CS|CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_CRYPTO_METHOD_TLSv1_2_SERVER", STREAM_CRYPTO_METHOD_TLSv1_2_SERVER, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("STREAM_CRYPTO_INFO_PROTOCOL", STREAM_CRYPTO_INFO_PROTOCOL, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("STREAM_CRYPTO_INFO_CIPHER_NAME", STREAM_CRYPTO_INFO_CIPHER_NAME, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("STREAM_CRYPTO_INFO_CIPHER_BITS", STREAM_CRYPTO_INFO_CIPHER_BITS, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("STREAM_CRYPTO_INFO_CIPHER_VERSION", STREAM_CRYPTO_INFO_CIPHER_VERSION, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("STREAM_CRYPTO_INFO_ALPN_PROTOCOL", STREAM_CRYPTO_INFO_ALPN_PROTOCOL, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("STREAM_SHUT_RD", STREAM_SHUT_RD, CONST_CS|CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_SHUT_WR", STREAM_SHUT_WR, CONST_CS|CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_SHUT_RDWR", STREAM_SHUT_RDWR, CONST_CS|CONST_PERSISTENT); diff --git a/ext/standard/streamsfuncs.c b/ext/standard/streamsfuncs.c index a86aae25f0..11dca39bc1 100644 --- a/ext/standard/streamsfuncs.c +++ b/ext/standard/streamsfuncs.c @@ -1485,6 +1485,46 @@ PHP_FUNCTION(stream_socket_enable_crypto) } /* }}} */ +/* {{{ proto int stream_socket_crypto_info(resource stream [, int infotype]) + Retrieve information about the stream's crypto session */ +PHP_FUNCTION(stream_socket_crypto_info) +{ + zval *zstream = NULL; + php_stream *stream = NULL; + zend_long infotype = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r|l", &zstream, &infotype) == FAILURE) { + RETURN_FALSE; + } + + php_stream_from_zval(stream, zstream); + + if (infotype == 0) { + infotype = STREAM_CRYPTO_INFO_ALL; + } else { + switch (infotype) { + case STREAM_CRYPTO_INFO_CIPHER_NAME: + case STREAM_CRYPTO_INFO_CIPHER_BITS: + case STREAM_CRYPTO_INFO_CIPHER_VERSION: + case STREAM_CRYPTO_INFO_CIPHER: + case STREAM_CRYPTO_INFO_PROTOCOL: + case STREAM_CRYPTO_INFO_ALPN_PROTOCOL: + case STREAM_CRYPTO_INFO_ALL: + break; + default: + php_error_docref(NULL, E_WARNING, "unknown crypto info type"); + RETURN_FALSE; + } + } + + if (php_stream_xport_crypto_info(stream, infotype, return_value) != PHP_STREAM_OPTION_RETURN_OK) { + RETURN_FALSE; + } + + /* return_value populated by php_stream_xport_crypto_info() upon success */ +} +/* }}} */ + /* {{{ proto string stream_resolve_include_path(string filename) Determine what file will be opened by calls to fopen() with a relative path */ PHP_FUNCTION(stream_resolve_include_path) diff --git a/ext/standard/streamsfuncs.h b/ext/standard/streamsfuncs.h index 37c6594dd2..07fe5fa572 100644 --- a/ext/standard/streamsfuncs.h +++ b/ext/standard/streamsfuncs.h @@ -57,6 +57,7 @@ PHP_FUNCTION(stream_filter_prepend); PHP_FUNCTION(stream_filter_append); PHP_FUNCTION(stream_filter_remove); PHP_FUNCTION(stream_socket_enable_crypto); +PHP_FUNCTION(stream_socket_crypto_info); PHP_FUNCTION(stream_socket_shutdown); PHP_FUNCTION(stream_resolve_include_path); PHP_FUNCTION(stream_is_local); |