Skip to content

OpenSSL min and max proto version options #3317

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 1, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ PHP NEWS
. Fixed bug #76532 (Integer overflow and excessive memory usage
in mb_strimwidth). (MarcusSchwarz)

- OpenSSL:
. Add min_proto_version and max_proto_version ssl stream options as well as
related constants for possible TLS protocol values. (Jakub Zelenka)

- PCRE:
. Fixed bug #76512 (\w no longer includes unicode characters). (cmb)
. Fixed bug #76514 (Regression in preg_match makes it fail with
Expand Down
6 changes: 3 additions & 3 deletions ext/openssl/tests/stream_crypto_flags_003.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ $serverCode = <<<'CODE'
'local_cert' => __DIR__ . '/bug54992.pem',

// Only accept TLSv1.2 connections
'crypto_method' => STREAM_CRYPTO_METHOD_SSLv3_SERVER | STREAM_CRYPTO_METHOD_TLSv1_2_SERVER,
'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_0_SERVER | STREAM_CRYPTO_METHOD_TLSv1_2_SERVER,
]]);

$server = stream_socket_server($serverUri, $errno, $errstr, $serverFlags, $serverCtx);
Expand Down Expand Up @@ -51,6 +51,6 @@ include 'ServerClientTestCase.inc';
ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
--EXPECTF--
resource(%d) of type (stream)
bool(false)
bool(false)
resource(%d) of type (stream)
resource(%d) of type (stream)

63 changes: 63 additions & 0 deletions ext/openssl/tests/tls_min_v1.0_max_v1.1_wrapper.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
--TEST--
tls stream wrapper with min version 1.0 and max version 1.1
--SKIPIF--
<?php
if (!extension_loaded("openssl")) die("skip openssl not loaded");
if (!function_exists("proc_open")) die("skip no proc_open");
?>
--FILE--
<?php
$serverCode = <<<'CODE'
$flags = STREAM_SERVER_BIND|STREAM_SERVER_LISTEN;
$ctx = stream_context_create(['ssl' => [
'local_cert' => __DIR__ . '/streams_crypto_method.pem',
'min_proto_version' => STREAM_CRYPTO_PROTO_TLSv1_0,
'max_proto_version' => STREAM_CRYPTO_PROTO_TLSv1_1,
]]);

$server = stream_socket_server('tls://127.0.0.1:64321', $errno, $errstr, $flags, $ctx);
phpt_notify();

for ($i=0; $i < 6; $i++) {
@stream_socket_accept($server, 3);
}
CODE;

$clientCode = <<<'CODE'
$flags = STREAM_CLIENT_CONNECT;
$ctx = stream_context_create(['ssl' => [
'verify_peer' => false,
'verify_peer_name' => false,
]]);

phpt_wait();

$client = stream_socket_client("tlsv1.0://127.0.0.1:64321", $errno, $errstr, 3, $flags, $ctx);
var_dump($client);

$client = @stream_socket_client("sslv3://127.0.0.1:64321", $errno, $errstr, 3, $flags, $ctx);
var_dump($client);

$client = @stream_socket_client("tlsv1.1://127.0.0.1:64321", $errno, $errstr, 3, $flags, $ctx);
var_dump($client);

$client = @stream_socket_client("tlsv1.2://127.0.0.1:64321", $errno, $errstr, 3, $flags, $ctx);
var_dump($client);

$client = @stream_socket_client("ssl://127.0.0.1:64321", $errno, $errstr, 3, $flags, $ctx);
var_dump($client);

$client = @stream_socket_client("tls://127.0.0.1:64321", $errno, $errstr, 3, $flags, $ctx);
var_dump($client);
CODE;

include 'ServerClientTestCase.inc';
ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
?>
--EXPECTF--
resource(%d) of type (stream)
bool(false)
resource(%d) of type (stream)
bool(false)
resource(%d) of type (stream)
resource(%d) of type (stream)
178 changes: 115 additions & 63 deletions ext/openssl/xp_ssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
| Authors: Wez Furlong <wez@thebrainroom.com> |
| Daniel Lowrey <rdlowrey@php.net> |
| Chris Wright <daverandom@php.net> |
| Jakub Zelenka <bukka@php.net> |
+----------------------------------------------------------------------+
*/

Expand Down Expand Up @@ -52,9 +53,22 @@
#undef X509_EXTENSIONS
#endif

/* Flags for determining allowed stream crypto methods */
#define STREAM_CRYPTO_IS_CLIENT (1<<0)
#define STREAM_CRYPTO_METHOD_SSLv2 (1<<1)
#define STREAM_CRYPTO_METHOD_SSLv3 (1<<2)
#define STREAM_CRYPTO_METHOD_TLSv1_0 (1<<3)
#define STREAM_CRYPTO_METHOD_TLSv1_1 (1<<4)
#define STREAM_CRYPTO_METHOD_TLSv1_2 (1<<5)

#ifndef OPENSSL_NO_SSL3
#define HAVE_SSL3 1
#define PHP_OPENSSL_MIN_PROTO_VERSION STREAM_CRYPTO_METHOD_SSLv3
#else
#define PHP_OPENSSL_MIN_PROTO_VERSION STREAM_CRYPTO_METHOD_TLSv1_0
#endif
#define PHP_OPENSSL_MAX_PROTO_VERSION STREAM_CRYPTO_METHOD_TLSv1_2


#define HAVE_TLS11 1
#define HAVE_TLS12 1
Expand All @@ -74,14 +88,6 @@
#define HAVE_SEC_LEVEL 1
#endif

/* Flags for determining allowed stream crypto methods */
#define STREAM_CRYPTO_IS_CLIENT (1<<0)
#define STREAM_CRYPTO_METHOD_SSLv2 (1<<1)
#define STREAM_CRYPTO_METHOD_SSLv3 (1<<2)
#define STREAM_CRYPTO_METHOD_TLSv1_0 (1<<3)
#define STREAM_CRYPTO_METHOD_TLSv1_1 (1<<4)
#define STREAM_CRYPTO_METHOD_TLSv1_2 (1<<5)

/* Simplify ssl context option retrieval */
#define GET_VER_OPT(name) \
(PHP_STREAM_CONTEXT(stream) && (val = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "ssl", name)) != NULL)
Expand Down Expand Up @@ -945,46 +951,6 @@ static int php_openssl_set_local_cert(SSL_CTX *ctx, php_stream *stream) /* {{{ *
}
/* }}} */

static const SSL_METHOD *php_openssl_select_crypto_method(zend_long method_value, int is_client) /* {{{ */
{
if (method_value == STREAM_CRYPTO_METHOD_SSLv2) {
php_error_docref(NULL, E_WARNING,
"SSLv2 unavailable in this PHP version");
return NULL;
} else if (method_value == STREAM_CRYPTO_METHOD_SSLv3) {
#ifdef HAVE_SSL3
return is_client ? SSLv3_client_method() : SSLv3_server_method();
#else
php_error_docref(NULL, E_WARNING,
"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) {
#ifdef HAVE_TLS11
return is_client ? TLSv1_1_client_method() : TLSv1_1_server_method();
#else
php_error_docref(NULL, E_WARNING,
"TLSv1.1 unavailable in the OpenSSL library against which PHP is linked");
return NULL;
#endif
} else if (method_value == STREAM_CRYPTO_METHOD_TLSv1_2) {
#ifdef HAVE_TLS12
return is_client ? TLSv1_2_client_method() : TLSv1_2_server_method();
#else
php_error_docref(NULL, E_WARNING,
"TLSv1.2 unavailable in the OpenSSL library against which PHP is linked");
return NULL;
#endif
} else {
php_error_docref(NULL, E_WARNING,
"Invalid crypto method");
return NULL;
}
}
/* }}} */

#define PHP_SSL_MAX_VERSION_LEN 32

static char *php_openssl_cipher_get_version(const SSL_CIPHER *c, char *buffer, size_t max_len) /* {{{ */
Expand All @@ -1000,6 +966,7 @@ static char *php_openssl_cipher_get_version(const SSL_CIPHER *c, char *buffer, s
}
/* }}} */

#if PHP_OPENSSL_API_VERSION < 0x10100
static int php_openssl_get_crypto_method_ctx_flags(int method_flags) /* {{{ */
{
int ssl_ctx_options = SSL_OP_ALL;
Expand Down Expand Up @@ -1029,6 +996,89 @@ static int php_openssl_get_crypto_method_ctx_flags(int method_flags) /* {{{ */
return ssl_ctx_options;
}
/* }}} */
#endif

static inline int php_openssl_get_min_proto_version_flag(int flags) /* {{{ */
{
int ver;
for (ver = PHP_OPENSSL_MIN_PROTO_VERSION; ver <= PHP_OPENSSL_MAX_PROTO_VERSION; ver <<= 1) {
if (flags & ver) {
return ver;
}
}
return STREAM_CRYPTO_METHOD_TLSv1_2;
}
/* }}} */

static inline int php_openssl_get_max_proto_version_flag(int flags) /* {{{ */
{
int ver;
for (ver = PHP_OPENSSL_MAX_PROTO_VERSION; ver >= PHP_OPENSSL_MIN_PROTO_VERSION; ver >>= 1) {
if (flags & ver) {
return ver;
}
}
return STREAM_CRYPTO_METHOD_TLSv1_2;
}
/* }}} */

#if PHP_OPENSSL_API_VERSION >= 0x10100
static inline int php_openssl_map_proto_version(int flag) /* {{{ */
{
switch (flag) {
#ifdef HAVE_SSL3
case STREAM_CRYPTO_METHOD_SSLv3:
return SSL3_VERSION;
#endif
case STREAM_CRYPTO_METHOD_TLSv1_0:
return TLS1_VERSION;
case STREAM_CRYPTO_METHOD_TLSv1_1:
return TLS1_1_VERSION;
/* case STREAM_CRYPTO_METHOD_TLSv1_2: */
default:
return TLS1_2_VERSION;

}
}
/* }}} */

static int php_openssl_get_min_proto_version(int flags) /* {{{ */
{
return php_openssl_map_proto_version(php_openssl_get_min_proto_version_flag(flags));
}
/* }}} */

static int php_openssl_get_max_proto_version(int flags) /* {{{ */
{
return php_openssl_map_proto_version(php_openssl_get_max_proto_version_flag(flags));
}
/* }}} */
#endif

static int php_openssl_get_proto_version_flags(int flags, int min, int max) /* {{{ */
{
int ver;

if (!min) {
min = php_openssl_get_min_proto_version_flag(flags);
}
if (!max) {
max = php_openssl_get_max_proto_version_flag(flags);
}

for (ver = PHP_OPENSSL_MIN_PROTO_VERSION; ver <= PHP_OPENSSL_MAX_PROTO_VERSION; ver <<= 1) {
if (ver >= min && ver <= max) {
if (!(flags & ver)) {
flags |= ver;
}
} else if (flags & ver) {
flags &= ~ver;
}
}

return flags;
}
/* }}} */

static void php_openssl_limit_handshake_reneg(const SSL *ssl) /* {{{ */
{
Expand Down Expand Up @@ -1538,6 +1588,8 @@ int php_openssl_setup_crypto(php_stream *stream,
const SSL_METHOD *method;
int ssl_ctx_options;
int method_flags;
zend_long min_version = 0;
zend_long max_version = 0;
char *cipherlist = NULL;
char *alpn_protocols = NULL;
zval *val;
Expand All @@ -1558,23 +1610,18 @@ int php_openssl_setup_crypto(php_stream *stream,
sslsock->is_client = cparam->inputs.method & STREAM_CRYPTO_IS_CLIENT;
method_flags = ((cparam->inputs.method >> 1) << 1);

/* Should we use a specific crypto method or is generic SSLv23 okay? */
if ((method_flags & (method_flags-1)) == 0) {
ssl_ctx_options = SSL_OP_ALL;
method = php_openssl_select_crypto_method(method_flags, sslsock->is_client);
if (method == NULL) {
return FAILURE;
}
} else {
method = sslsock->is_client ? SSLv23_client_method() : SSLv23_server_method();
ssl_ctx_options = php_openssl_get_crypto_method_ctx_flags(method_flags);
if (ssl_ctx_options == -1) {
return FAILURE;
}
}

method = sslsock->is_client ? SSLv23_client_method() : SSLv23_server_method();
sslsock->ctx = SSL_CTX_new(method);

GET_VER_OPT_LONG("min_proto_version", min_version);
GET_VER_OPT_LONG("max_proto_version", max_version);
method_flags = php_openssl_get_proto_version_flags(method_flags, min_version, max_version);
#if PHP_OPENSSL_API_VERSION < 0x10100
ssl_ctx_options = php_openssl_get_crypto_method_ctx_flags(method_flags);
#else
ssl_ctx_options = SSL_OP_ALL;
#endif

if (sslsock->ctx == NULL) {
php_error_docref(NULL, E_WARNING, "SSL context creation failure");
return FAILURE;
Expand Down Expand Up @@ -1663,6 +1710,11 @@ int php_openssl_setup_crypto(php_stream *stream,

SSL_CTX_set_options(sslsock->ctx, ssl_ctx_options);

#if PHP_OPENSSL_API_VERSION >= 0x10100
SSL_CTX_set_min_proto_version(sslsock->ctx, php_openssl_get_min_proto_version(method_flags));
SSL_CTX_set_max_proto_version(sslsock->ctx, php_openssl_get_max_proto_version(method_flags));
#endif

if (sslsock->is_client == 0 &&
PHP_STREAM_CONTEXT(stream) &&
FAILURE == php_openssl_set_server_specific_opts(stream, sslsock->ctx)
Expand Down
5 changes: 5 additions & 0 deletions ext/standard/file.c
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,11 @@ 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_PROTO_SSLv3", STREAM_CRYPTO_METHOD_SSLv3_SERVER, CONST_CS|CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("STREAM_CRYPTO_PROTO_TLSv1_0", STREAM_CRYPTO_METHOD_TLSv1_0_SERVER, CONST_CS|CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("STREAM_CRYPTO_PROTO_TLSv1_1", STREAM_CRYPTO_METHOD_TLSv1_1_SERVER, CONST_CS|CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("STREAM_CRYPTO_PROTO_TLSv1_2", STREAM_CRYPTO_METHOD_TLSv1_2_SERVER, 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);
Expand Down