Skip to content

Commit d404b5e

Browse files
committed
Add ssl/tls streams options for min and max proto version
1 parent ce0721b commit d404b5e

File tree

5 files changed

+190
-66
lines changed

5 files changed

+190
-66
lines changed

NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ PHP NEWS
3131
. Fixed bug #76532 (Integer overflow and excessive memory usage
3232
in mb_strimwidth). (MarcusSchwarz)
3333

34+
- OpenSSL:
35+
. Add min_proto_version and max_proto_version ssl stream options as well as
36+
related constants for possible TLS protocol values. (Jakub Zelenka)
37+
3438
- PCRE:
3539
. Fixed bug #76512 (\w no longer includes unicode characters). (cmb)
3640
. Fixed bug #76514 (Regression in preg_match makes it fail with

ext/openssl/tests/stream_crypto_flags_003.phpt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ $serverCode = <<<'CODE'
1414
'local_cert' => __DIR__ . '/bug54992.pem',
1515

1616
// Only accept TLSv1.2 connections
17-
'crypto_method' => STREAM_CRYPTO_METHOD_SSLv3_SERVER | STREAM_CRYPTO_METHOD_TLSv1_2_SERVER,
17+
'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_0_SERVER | STREAM_CRYPTO_METHOD_TLSv1_2_SERVER,
1818
]]);
1919

2020
$server = stream_socket_server($serverUri, $errno, $errstr, $serverFlags, $serverCtx);
@@ -51,6 +51,6 @@ include 'ServerClientTestCase.inc';
5151
ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
5252
--EXPECTF--
5353
resource(%d) of type (stream)
54-
bool(false)
55-
bool(false)
54+
resource(%d) of type (stream)
55+
resource(%d) of type (stream)
5656

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
--TEST--
2+
tls stream wrapper with min version 1.0 and max version 1.1
3+
--SKIPIF--
4+
<?php
5+
if (!extension_loaded("openssl")) die("skip openssl not loaded");
6+
if (!function_exists("proc_open")) die("skip no proc_open");
7+
?>
8+
--FILE--
9+
<?php
10+
$serverCode = <<<'CODE'
11+
$flags = STREAM_SERVER_BIND|STREAM_SERVER_LISTEN;
12+
$ctx = stream_context_create(['ssl' => [
13+
'local_cert' => __DIR__ . '/streams_crypto_method.pem',
14+
'min_proto_version' => STREAM_CRYPTO_PROTO_TLSv1_0,
15+
'max_proto_version' => STREAM_CRYPTO_PROTO_TLSv1_1,
16+
]]);
17+
18+
$server = stream_socket_server('tls://127.0.0.1:64321', $errno, $errstr, $flags, $ctx);
19+
phpt_notify();
20+
21+
for ($i=0; $i < 6; $i++) {
22+
@stream_socket_accept($server, 3);
23+
}
24+
CODE;
25+
26+
$clientCode = <<<'CODE'
27+
$flags = STREAM_CLIENT_CONNECT;
28+
$ctx = stream_context_create(['ssl' => [
29+
'verify_peer' => false,
30+
'verify_peer_name' => false,
31+
]]);
32+
33+
phpt_wait();
34+
35+
$client = stream_socket_client("tlsv1.0://127.0.0.1:64321", $errno, $errstr, 3, $flags, $ctx);
36+
var_dump($client);
37+
38+
$client = @stream_socket_client("sslv3://127.0.0.1:64321", $errno, $errstr, 3, $flags, $ctx);
39+
var_dump($client);
40+
41+
$client = @stream_socket_client("tlsv1.1://127.0.0.1:64321", $errno, $errstr, 3, $flags, $ctx);
42+
var_dump($client);
43+
44+
$client = @stream_socket_client("tlsv1.2://127.0.0.1:64321", $errno, $errstr, 3, $flags, $ctx);
45+
var_dump($client);
46+
47+
$client = @stream_socket_client("ssl://127.0.0.1:64321", $errno, $errstr, 3, $flags, $ctx);
48+
var_dump($client);
49+
50+
$client = @stream_socket_client("tls://127.0.0.1:64321", $errno, $errstr, 3, $flags, $ctx);
51+
var_dump($client);
52+
CODE;
53+
54+
include 'ServerClientTestCase.inc';
55+
ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
56+
?>
57+
--EXPECTF--
58+
resource(%d) of type (stream)
59+
bool(false)
60+
resource(%d) of type (stream)
61+
bool(false)
62+
resource(%d) of type (stream)
63+
resource(%d) of type (stream)

ext/openssl/xp_ssl.c

Lines changed: 115 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
| Authors: Wez Furlong <wez@thebrainroom.com> |
1616
| Daniel Lowrey <rdlowrey@php.net> |
1717
| Chris Wright <daverandom@php.net> |
18+
| Jakub Zelenka <bukka@php.net> |
1819
+----------------------------------------------------------------------+
1920
*/
2021

@@ -52,9 +53,22 @@
5253
#undef X509_EXTENSIONS
5354
#endif
5455

56+
/* Flags for determining allowed stream crypto methods */
57+
#define STREAM_CRYPTO_IS_CLIENT (1<<0)
58+
#define STREAM_CRYPTO_METHOD_SSLv2 (1<<1)
59+
#define STREAM_CRYPTO_METHOD_SSLv3 (1<<2)
60+
#define STREAM_CRYPTO_METHOD_TLSv1_0 (1<<3)
61+
#define STREAM_CRYPTO_METHOD_TLSv1_1 (1<<4)
62+
#define STREAM_CRYPTO_METHOD_TLSv1_2 (1<<5)
63+
5564
#ifndef OPENSSL_NO_SSL3
5665
#define HAVE_SSL3 1
66+
#define PHP_OPENSSL_MIN_PROTO_VERSION STREAM_CRYPTO_METHOD_SSLv3
67+
#else
68+
#define PHP_OPENSSL_MIN_PROTO_VERSION STREAM_CRYPTO_METHOD_TLSv1_0
5769
#endif
70+
#define PHP_OPENSSL_MAX_PROTO_VERSION STREAM_CRYPTO_METHOD_TLSv1_2
71+
5872

5973
#define HAVE_TLS11 1
6074
#define HAVE_TLS12 1
@@ -74,14 +88,6 @@
7488
#define HAVE_SEC_LEVEL 1
7589
#endif
7690

77-
/* Flags for determining allowed stream crypto methods */
78-
#define STREAM_CRYPTO_IS_CLIENT (1<<0)
79-
#define STREAM_CRYPTO_METHOD_SSLv2 (1<<1)
80-
#define STREAM_CRYPTO_METHOD_SSLv3 (1<<2)
81-
#define STREAM_CRYPTO_METHOD_TLSv1_0 (1<<3)
82-
#define STREAM_CRYPTO_METHOD_TLSv1_1 (1<<4)
83-
#define STREAM_CRYPTO_METHOD_TLSv1_2 (1<<5)
84-
8591
/* Simplify ssl context option retrieval */
8692
#define GET_VER_OPT(name) \
8793
(PHP_STREAM_CONTEXT(stream) && (val = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "ssl", name)) != NULL)
@@ -945,46 +951,6 @@ static int php_openssl_set_local_cert(SSL_CTX *ctx, php_stream *stream) /* {{{ *
945951
}
946952
/* }}} */
947953

948-
static const SSL_METHOD *php_openssl_select_crypto_method(zend_long method_value, int is_client) /* {{{ */
949-
{
950-
if (method_value == STREAM_CRYPTO_METHOD_SSLv2) {
951-
php_error_docref(NULL, E_WARNING,
952-
"SSLv2 unavailable in this PHP version");
953-
return NULL;
954-
} else if (method_value == STREAM_CRYPTO_METHOD_SSLv3) {
955-
#ifdef HAVE_SSL3
956-
return is_client ? SSLv3_client_method() : SSLv3_server_method();
957-
#else
958-
php_error_docref(NULL, E_WARNING,
959-
"SSLv3 unavailable in the OpenSSL library against which PHP is linked");
960-
return NULL;
961-
#endif
962-
} else if (method_value == STREAM_CRYPTO_METHOD_TLSv1_0) {
963-
return is_client ? TLSv1_client_method() : TLSv1_server_method();
964-
} else if (method_value == STREAM_CRYPTO_METHOD_TLSv1_1) {
965-
#ifdef HAVE_TLS11
966-
return is_client ? TLSv1_1_client_method() : TLSv1_1_server_method();
967-
#else
968-
php_error_docref(NULL, E_WARNING,
969-
"TLSv1.1 unavailable in the OpenSSL library against which PHP is linked");
970-
return NULL;
971-
#endif
972-
} else if (method_value == STREAM_CRYPTO_METHOD_TLSv1_2) {
973-
#ifdef HAVE_TLS12
974-
return is_client ? TLSv1_2_client_method() : TLSv1_2_server_method();
975-
#else
976-
php_error_docref(NULL, E_WARNING,
977-
"TLSv1.2 unavailable in the OpenSSL library against which PHP is linked");
978-
return NULL;
979-
#endif
980-
} else {
981-
php_error_docref(NULL, E_WARNING,
982-
"Invalid crypto method");
983-
return NULL;
984-
}
985-
}
986-
/* }}} */
987-
988954
#define PHP_SSL_MAX_VERSION_LEN 32
989955

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

969+
#if PHP_OPENSSL_API_VERSION < 0x10100
1003970
static int php_openssl_get_crypto_method_ctx_flags(int method_flags) /* {{{ */
1004971
{
1005972
int ssl_ctx_options = SSL_OP_ALL;
@@ -1029,6 +996,89 @@ static int php_openssl_get_crypto_method_ctx_flags(int method_flags) /* {{{ */
1029996
return ssl_ctx_options;
1030997
}
1031998
/* }}} */
999+
#endif
1000+
1001+
static inline int php_openssl_get_min_proto_version_flag(int flags) /* {{{ */
1002+
{
1003+
int ver;
1004+
for (ver = PHP_OPENSSL_MIN_PROTO_VERSION; ver <= PHP_OPENSSL_MAX_PROTO_VERSION; ver <<= 1) {
1005+
if (flags & ver) {
1006+
return ver;
1007+
}
1008+
}
1009+
return STREAM_CRYPTO_METHOD_TLSv1_2;
1010+
}
1011+
/* }}} */
1012+
1013+
static inline int php_openssl_get_max_proto_version_flag(int flags) /* {{{ */
1014+
{
1015+
int ver;
1016+
for (ver = PHP_OPENSSL_MAX_PROTO_VERSION; ver >= PHP_OPENSSL_MIN_PROTO_VERSION; ver >>= 1) {
1017+
if (flags & ver) {
1018+
return ver;
1019+
}
1020+
}
1021+
return STREAM_CRYPTO_METHOD_TLSv1_2;
1022+
}
1023+
/* }}} */
1024+
1025+
#if PHP_OPENSSL_API_VERSION >= 0x10100
1026+
static inline int php_openssl_map_proto_version(int flag) /* {{{ */
1027+
{
1028+
switch (flag) {
1029+
#ifdef HAVE_SSL3
1030+
case STREAM_CRYPTO_METHOD_SSLv3:
1031+
return SSL3_VERSION;
1032+
#endif
1033+
case STREAM_CRYPTO_METHOD_TLSv1_0:
1034+
return TLS1_VERSION;
1035+
case STREAM_CRYPTO_METHOD_TLSv1_1:
1036+
return TLS1_1_VERSION;
1037+
/* case STREAM_CRYPTO_METHOD_TLSv1_2: */
1038+
default:
1039+
return TLS1_2_VERSION;
1040+
1041+
}
1042+
}
1043+
/* }}} */
1044+
1045+
static int php_openssl_get_min_proto_version(int flags) /* {{{ */
1046+
{
1047+
return php_openssl_map_proto_version(php_openssl_get_min_proto_version_flag(flags));
1048+
}
1049+
/* }}} */
1050+
1051+
static int php_openssl_get_max_proto_version(int flags) /* {{{ */
1052+
{
1053+
return php_openssl_map_proto_version(php_openssl_get_max_proto_version_flag(flags));
1054+
}
1055+
/* }}} */
1056+
#endif
1057+
1058+
static int php_openssl_get_proto_version_flags(int flags, int min, int max) /* {{{ */
1059+
{
1060+
int ver;
1061+
1062+
if (!min) {
1063+
min = php_openssl_get_min_proto_version_flag(flags);
1064+
}
1065+
if (!max) {
1066+
max = php_openssl_get_max_proto_version_flag(flags);
1067+
}
1068+
1069+
for (ver = PHP_OPENSSL_MIN_PROTO_VERSION; ver <= PHP_OPENSSL_MAX_PROTO_VERSION; ver <<= 1) {
1070+
if (ver >= min && ver <= max) {
1071+
if (!(flags & ver)) {
1072+
flags |= ver;
1073+
}
1074+
} else if (flags & ver) {
1075+
flags &= ~ver;
1076+
}
1077+
}
1078+
1079+
return flags;
1080+
}
1081+
/* }}} */
10321082

10331083
static void php_openssl_limit_handshake_reneg(const SSL *ssl) /* {{{ */
10341084
{
@@ -1538,6 +1588,8 @@ int php_openssl_setup_crypto(php_stream *stream,
15381588
const SSL_METHOD *method;
15391589
int ssl_ctx_options;
15401590
int method_flags;
1591+
zend_long min_version = 0;
1592+
zend_long max_version = 0;
15411593
char *cipherlist = NULL;
15421594
char *alpn_protocols = NULL;
15431595
zval *val;
@@ -1558,23 +1610,18 @@ int php_openssl_setup_crypto(php_stream *stream,
15581610
sslsock->is_client = cparam->inputs.method & STREAM_CRYPTO_IS_CLIENT;
15591611
method_flags = ((cparam->inputs.method >> 1) << 1);
15601612

1561-
/* Should we use a specific crypto method or is generic SSLv23 okay? */
1562-
if ((method_flags & (method_flags-1)) == 0) {
1563-
ssl_ctx_options = SSL_OP_ALL;
1564-
method = php_openssl_select_crypto_method(method_flags, sslsock->is_client);
1565-
if (method == NULL) {
1566-
return FAILURE;
1567-
}
1568-
} else {
1569-
method = sslsock->is_client ? SSLv23_client_method() : SSLv23_server_method();
1570-
ssl_ctx_options = php_openssl_get_crypto_method_ctx_flags(method_flags);
1571-
if (ssl_ctx_options == -1) {
1572-
return FAILURE;
1573-
}
1574-
}
1575-
1613+
method = sslsock->is_client ? SSLv23_client_method() : SSLv23_server_method();
15761614
sslsock->ctx = SSL_CTX_new(method);
15771615

1616+
GET_VER_OPT_LONG("min_proto_version", min_version);
1617+
GET_VER_OPT_LONG("max_proto_version", max_version);
1618+
method_flags = php_openssl_get_proto_version_flags(method_flags, min_version, max_version);
1619+
#if PHP_OPENSSL_API_VERSION < 0x10100
1620+
ssl_ctx_options = php_openssl_get_crypto_method_ctx_flags(method_flags);
1621+
#else
1622+
ssl_ctx_options = SSL_OP_ALL;
1623+
#endif
1624+
15781625
if (sslsock->ctx == NULL) {
15791626
php_error_docref(NULL, E_WARNING, "SSL context creation failure");
15801627
return FAILURE;
@@ -1663,6 +1710,11 @@ int php_openssl_setup_crypto(php_stream *stream,
16631710

16641711
SSL_CTX_set_options(sslsock->ctx, ssl_ctx_options);
16651712

1713+
#if PHP_OPENSSL_API_VERSION >= 0x10100
1714+
SSL_CTX_set_min_proto_version(sslsock->ctx, php_openssl_get_min_proto_version(method_flags));
1715+
SSL_CTX_set_max_proto_version(sslsock->ctx, php_openssl_get_max_proto_version(method_flags));
1716+
#endif
1717+
16661718
if (sslsock->is_client == 0 &&
16671719
PHP_STREAM_CONTEXT(stream) &&
16681720
FAILURE == php_openssl_set_server_specific_opts(stream, sslsock->ctx)

ext/standard/file.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,11 @@ PHP_MINIT_FUNCTION(file)
228228
REGISTER_LONG_CONSTANT("STREAM_CRYPTO_METHOD_TLSv1_1_SERVER", STREAM_CRYPTO_METHOD_TLSv1_1_SERVER, CONST_CS|CONST_PERSISTENT);
229229
REGISTER_LONG_CONSTANT("STREAM_CRYPTO_METHOD_TLSv1_2_SERVER", STREAM_CRYPTO_METHOD_TLSv1_2_SERVER, CONST_CS|CONST_PERSISTENT);
230230

231+
REGISTER_LONG_CONSTANT("STREAM_CRYPTO_PROTO_SSLv3", STREAM_CRYPTO_METHOD_SSLv3_SERVER, CONST_CS|CONST_PERSISTENT);
232+
REGISTER_LONG_CONSTANT("STREAM_CRYPTO_PROTO_TLSv1_0", STREAM_CRYPTO_METHOD_TLSv1_0_SERVER, CONST_CS|CONST_PERSISTENT);
233+
REGISTER_LONG_CONSTANT("STREAM_CRYPTO_PROTO_TLSv1_1", STREAM_CRYPTO_METHOD_TLSv1_1_SERVER, CONST_CS|CONST_PERSISTENT);
234+
REGISTER_LONG_CONSTANT("STREAM_CRYPTO_PROTO_TLSv1_2", STREAM_CRYPTO_METHOD_TLSv1_2_SERVER, CONST_CS|CONST_PERSISTENT);
235+
231236
REGISTER_LONG_CONSTANT("STREAM_SHUT_RD", STREAM_SHUT_RD, CONST_CS|CONST_PERSISTENT);
232237
REGISTER_LONG_CONSTANT("STREAM_SHUT_WR", STREAM_SHUT_WR, CONST_CS|CONST_PERSISTENT);
233238
REGISTER_LONG_CONSTANT("STREAM_SHUT_RDWR", STREAM_SHUT_RDWR, CONST_CS|CONST_PERSISTENT);

0 commit comments

Comments
 (0)