Skip to content

By default any TLS method should be used, not TLSv1.0 only #2518

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

Closed
wants to merge 5 commits into from

Conversation

kelunik
Copy link
Member

@kelunik kelunik commented May 10, 2017

Not only does this default harm security as it disables the newer standard, it is also unexpected.

See also https://externals.io/thread/864.

I think this fix should also be backported to any supported version as it harms security.

@DennisBirkholz
Copy link
Contributor

I find using the STREAM_CRYPTO_METHOD_SSLv23_CLIENT constant irritating as it implied to me SSL 2 is also possible (which actually is not). I would prefer the STREAM_CRYPTO_METHOD_TLS_ANY_CLIENT constant which is actually the same value but makes the intention clearer.

@kelunik
Copy link
Member Author

kelunik commented May 10, 2017

@DennisBirkholz You're right, I've just updated it.

We should probably also think about deprecating STREAM_CRYPTO_METHOD_SSLv23_CLIENT as it's confusing.

@nikic
Copy link
Member

nikic commented May 10, 2017

Which SSL/TLS versions specifically does this allow? Only TLS or also SSLv3?

@arjenschol
Copy link

I've reported this October last year: https://bugs.php.net/bug.php?id=73388

@kelunik
Copy link
Member Author

kelunik commented May 10, 2017

@nikic It allows any TLS version now instead of just TLSv1.0 previously.

@bukka
Copy link
Member

bukka commented May 10, 2017

@nikic It accepts just TLS 1.0 by default and allows explicitly setting other protocols in crypto_method context option.

@kelunik I think it makes sense to do it for 7.2. The only problem that I see is that some clients connecting to the old servers needed explicitly specify TLS 1.0 as they didn't work with the new version and the connection hanged (due to some issues with old OpenSSL version IIRC) so it would break. It might have been reason why it was reverted in 5.6 but going forward I think we should change it to 7.2 and not limit it to 1.0 by default.

Btw. this needs a test!

@weltling
Copy link
Contributor

weltling commented May 11, 2017

That were my exact question - would this change cause PHP to the best TLS available, and then downgrade if not? Fe if a server supports TLS 1.0 only, and PHP would start with TLS 1.2 and then step down to try the other supported?

Probably some stats were to lookup as well, which i've also asked on ML. Fe I'm reading these

https://jve.linuxwall.info/blog/index.php?post/2016/08/04/TLS-stats-from-1.6-billion-connections-to-mozilla.org
https://www.trustworthyinternet.org/ssl-pulse/

where the one says browsers prefer TLS 1.2, but the other tells servers still support TLS 1.0 to a huge extent. Not sure how these stats are representative. Probably one should do more research.

In any case, probably if the internet goes wild, we should of course keep up. Still, it doesn't look like there's an urgent action need, so there should be tests and possible other evaluations, so then we provide the most comfortable solution to the user land.

Thanks.

@kelunik
Copy link
Member Author

kelunik commented May 14, 2017

@weltling Browsers and servers prefer TLS 1.2, but support 1.0 for older clients that aren't capable of TLS 1.2. The used version is negotiated during the handshake. We already migrated the https wrapper in PHP 5.6 apparently, as the https wrapper negotiates TLS 1.2.

php -r '$fd = fopen("https://github.com/", "r"); var_dump(stream_get_meta_data($fd)["crypto"]);'

array(4) {
  ["protocol"]=>
  string(7) "TLSv1.2"
  ["cipher_name"]=>
  string(27) "ECDHE-RSA-AES128-GCM-SHA256"
  ["cipher_bits"]=>
  int(128)
  ["cipher_version"]=>
  string(11) "TLSv1/SSLv3"
}

php -r '$fd = stream_socket_client("tls://github.com:443"); var_dump(stream_get_meta_data($fd)["crypto"]);'

array(4) {
  ["protocol"]=>
  string(5) "TLSv1"
  ["cipher_name"]=>
  string(20) "ECDHE-RSA-AES128-SHA"
  ["cipher_bits"]=>
  int(128)
  ["cipher_version"]=>
  string(11) "TLSv1/SSLv3"
}

@weltling
Copy link
Contributor

@kelunik yeah, the question is - if a server supports TLS 1.0 only, the connection might fail with a client advertising TLS 1.2, so in that case we break the backward compatibility in the other direction. Fe a server like this one https://www.ssllabs.com/ssltest/analyze.html?d=ssb.epcc.edu. It's kinda expected from github to behave more or less correct, so it's not quite fair to use it for the tests. We have to care also about servers with no TLS 1.2 support and the protocol intolerance, "version negotiated during the handshake" is an ideal case.

Perhaps, if TLS 1.2 indeed becomes that wide spread today, the simplest would be to use the exact TLS 1.2 as default in PHP 7.2, other TLS protocols can be still used by the explicit scheme. Or maybe even - use TLS 1.2 first, and then retry with TLS 1.0. Not exactly the unsafe thing browsers do with retrying literally anything possible and would be safe enough with only one possible connection retry, so good from the perf perspective, too. Leaving the decision up to the linked SSL library can per se not guarantee the expected behaviors.

Thanks.

@kelunik
Copy link
Member Author

kelunik commented May 14, 2017

@weltling I'll try to get a list of servers and do some tests, but the server you mentioned doesn't connect with the current version of PHP regardless of the change.

@bukka
Copy link
Member

bukka commented May 14, 2017

Maybe it would be better to deprecate all tls*:// streams and have just ssl:// and possibly later add and support only min and max proto option that I suggested in the bug report. Currently the naming is just too confusing and it's true that if we change tls:// we might just break some apps. The ssl:// already support all TLS versions. I know the name is not the best as it works with TLS only. However it supports all needs and if it's correctly documented, then it should be fine IMHO.

@kelunik
Copy link
Member Author

kelunik commented May 14, 2017

@bukka If we do that, then we should disable SSL for ssl://. Those shouldn't be negotiated anymore. However, deprecating tls:// just for the sake of deprecation breaks way more than just giving it the purpose that's expected.

@bukka
Copy link
Member

bukka commented May 14, 2017

@kelunik SSL is already disabled, isn't it?

@bukka
Copy link
Member

bukka commented May 14, 2017

From what I see, your changes are just making tls:// the same as ssl://. Or did I miss anything?

@kelunik
Copy link
Member Author

kelunik commented May 14, 2017

ssl:// accepts ANY SSL / TLS version, see [1] and [2].

My changes change tls:// to STREAM_CRYPTO_METHOD_TLS_ANY_CLIENT, ssl:// uses STREAM_CRYPTO_METHOD_ANY_CLIENT, note the TLS_ in there.

@bukka
Copy link
Member

bukka commented May 14, 2017

Ah I see, I missed that _TLS bit in it :) I guess I would personally be for BC in that case as it's just doesn't make sense to use tls just for 1.0 especially if there is tlsv1.0:// but I also feel that this should probably go through RFC.

@weltling
Copy link
Contributor

@kelunik it depends on the linked SSL lib abilities, I can connect to that host from several Linux machines disregarding the PHP version. Earlier PHP Windows builds work too, while the more or less recent don't - because after DROWN OpenSSL disabled weak ciphers by default and we use default build options. Haven't tried LibreSSL, though, no plan yet about it.

Thanks.

@kelunik
Copy link
Member Author

kelunik commented May 14, 2017

@weltling Which versions did you test? We provide an explicit list and do not rely on OpenSSL's list by default.

php-src/ext/openssl/xp_ssl.c

Lines 1540 to 1543 in 8b361f1

#ifndef USE_OPENSSL_SYSTEM_CIPHERS
if (!cipherlist) {
cipherlist = OPENSSL_DEFAULT_STREAM_CIPHERS;
}

@weltling
Copy link
Contributor

@kelunik with OpenSSL 1.0.1m and on, or 1.0.2g, vanilla build, PHP connects to that host. Fe OpenSSL 1.0.2g is delivered in PHP 7.0.5 for Windows. OpenSSL 1.0.2j vanilla build already refuses, fe in PHP 7.0.13 Windows build. On Linux the situation is a bit different, as also to see from your link - a particular distribution can decide, which ciphers to use in PHP.

Also, even we define the list, there's no guarantee it is present in the linked SSL lib, and vice versa. Fe where in PHP we block RC4, linked OpenSSL might still provide another cipher supported by the server, where the OPenSSL console client still is able to connect like openssl s_client -tls1 -connect ssb.epcc.edu:443 using RC4. I don't think the linked server is exemplary, as there can be various configurations, but it is good to illustrate the BC aspect.

Thanks.

@arjenschol
Copy link

So we now have a confusing STREAM_CRYPTO_METHOD_SSLv23_CLIENT constant which is TLS 1.0, 1.1 and 1.2 and STREAM_CRYPTO_METHOD_TLS_CLIENT which is TLS 1.0 only..

Proposing deprecating tls:// and keep ssl:// isn't going to solve the confusion, even with proper documentation. It's just counter-intuitive...

This could only be implemented 'safe' (with no developer confusing) if tls:// is removed, and not only deprecated. This would be an even more BC-break than changing STREAM_CRYPTO_METHOD_TLS_CLIENT to support TLS-1.0-1.3.

I don't understand why bad behaviour is supported with BC. As I mentioned in the bugreport, we started using STREAM_CRYPTO_METHOD_TLS_CLIENT because the 5.6 changelog mentions it (see http://php.net/manual/en/migration56.openssl.php) This was changed, and we faced a BC break: we couldn't connect to TLS 1.1 and 1.2 anymore. No one cares.

The world is moving to TLS 1.2 only and the PHP TLS constant does not support it by default.

I think a BC break is allowed here because it affects security, so we should design an optimal solution:

  1. Deprecate ssl://
  2. Change tls:// to default to STREAM_CRYPTO_METHOD_TLS_ANY_CLIENT
  3. Make STREAM_CRYPTO_METHOD_TLS_CLIENT an alias for STREAM_CRYPTO_METHOD_TLS_ANY_CLIENT No need to expose STREAM_CRYPTO_METHOD_TLS_ANY_CLIENT as usespace constant, as it's the default.
  4. And deprecate STREAM_CRYPTO_METHOD_TLS_CLIENT
  5. New TLS versions are added to STREAM_CRYPTO_METHOD_TLS_ANY_CLIENT
  6. Unsafe versions are removed from STREAM_CRYPTO_METHOD_TLS_ANY_CLIENT.

When you need support for old TLS version (or have handshake downgrade problems), use one of the specific STREAM_CRYPTO_METHOD_TLSv1_*_CLIENT constants.

We should not focus on hosts like https://www.ssllabs.com/ssltest/analyze.html?d=ssb.epcc.edu This server has rating F, SSLv2 still enabled etc. etc. There is no 'secure' in their SSL config. The used cipher 3DES_EDE_CBC is not supported in OpenSSL 1.1 anymore.

@kelunik
Copy link
Member Author

kelunik commented May 15, 2017

@arjenschol I think it might be a bit early to deprecate older constants like SSLv23, because that will make it actually harder to have sane values, because older versions still need to use that constant and new ones would have to use the TLS constant, otherwise older versions would always use TLSv1.0.

I've just opened a RFC for this: https://wiki.php.net/rfc/improved-tls-constants

@kelunik
Copy link
Member Author

kelunik commented May 15, 2017

I've just changed ssl:// to default to STREAM_CRYPTO_METHOD_TLS_ANY_CLIENT as well. I think that change specifically should be backported to older versions, too.

@kelunik
Copy link
Member Author

kelunik commented May 29, 2017

Two weeks have passed, the RFC is now in voting. https://wiki.php.net/rfc/improved-tls-constants

@krakjoe krakjoe added the RFC label Jun 1, 2017
@frederikbosch
Copy link
Contributor

May I highlight that the constant STREAM_CRYPTO_METHOD_TLS_ANY_CLIENT is not available to the end-user at this moment. This is consistent with the documentation. The end-user should be able to use the constant, e.g. to upgrade an smtp connection (starttls). As such I think the PR should be extended to fix this.

Note: I also find it quite strange that the constant is not available. If look at the current php-src for 7.1.5 it seems the constant is there but as shown above it cannot be accessed.

@frederikbosch
Copy link
Contributor

To clarify my comment above, I think the STREAM_CRYPTO_METHOD_TLS_ANY_CLIENT constant must be exposed to userland because the user should be able to use it in the stream_socket_enable_crypto function.

The current documented behavior of the third parameter (crypto_type) of the stream_socket_enable_crypto function is as follows.

  1. If omitted, the crypto_type context option on the stream's SSL context will be used instead.
  2. And since PHP 5.6.0 The crypto_type is now optional.

That is simply not true and wrong.

7.1.5-1+deb.sury.org~xenial+2
PHP Warning:  stream_socket_enable_crypto(): When enabling encryption you must specify the crypto type in /workspace/mail/src/Protocol/PlainTcpConnection.php on line 46

So I'd say we do what was promised in PHP 5.6.0.

  1. crypto_type becomes optional and uses context by default.
  2. default crypto_type context parameter is STREAM_CRYPTO_METHOD_TLS_ANY_CLIENT.

But I would also suggest to expose STREAM_CRYPTO_METHOD_TLS_ANY_CLIENT, because you do not want to make the stream_socket_enable_crypto call conditional.

The following code is not preferable.

if ($type === null) {
  stream_socket_enable_crypto($this->resource, true);
} else {
  stream_socket_enable_crypto($this->resource, true, $type);
}

I'd rather see the following, which is only possible if STREAM_CRYPTO_METHOD_TLS_ANY_CLIENT is exposed.

stream_socket_enable_crypto($this->resource, true, $type);

The above logic probably also holds for the STREAM_CRYPTO_METHOD_TLS_ANY_SERVER constant.

@kelunik
Copy link
Member Author

kelunik commented Jun 4, 2017

@frederikbosch Why is simply passing null not preferable? Could be handled just like passing no parameter at all and just have a sane default that might change in future versions. Passing STREAM_CRYPTO_METHOD_TLS_ANY_SERVER is not preferable, as it would allow TLS 1.0 even if we consider that completely broken in the future.

kelunik added 2 commits June 4, 2017 11:10
Not only does this default harm security as it disables the newer standard,
it is also unexpected.
@kelunik kelunik force-pushed the fix-default-tls-method branch from 8cf8c10 to 582ccb2 Compare June 4, 2017 09:10
@frederikbosch
Copy link
Contributor

@kelunik Passing null would be fine, though I am not fan of that because it is not explicit on what is going on. Then I'd rather see two additional constants: STREAM_CRYPTO_METHOD_SERVER_SAFE and STREAM_CRYPTO_METHOD_CLIENT_SAFE. That makes things explicit. But, again, null would be also just fine.

@frederikbosch
Copy link
Contributor

@kelunik Basically SSL (as protocol) is gone by default with regard to streams after this PR is merged. However, we still have to write this.

$context = stream_context_create([
    'ssl' => []
]);

Instead of this.

$context = stream_context_create([
    'tls' => []
]);

Is that logical?

@kelunik
Copy link
Member Author

kelunik commented Jun 5, 2017

@frederikbosch Better have one way than two identical ways of doing things. Having both just makes the situation worse. If we're going to switch to tls, there should be a good reason for that. One such reason might be refactoring the options while providing ssl as a legacy interface.

@kelunik
Copy link
Member Author

kelunik commented Jun 7, 2017

@bukka @weltling Are you fine with the patch as is?

@bukka
Copy link
Member

bukka commented Jun 7, 2017

LGTM! just minor detail that I think most openssl ext test using tabs for tests indent so it would be better to keep it consistent.

@weltling
Copy link
Contributor

weltling commented Jun 8, 2017

@kelunik yeah, the RFC passed and @bukka is fine, so should be merged in near time. Involve @remicollet and @sgolemon to be aware of this.

Thanks.

@kelunik
Copy link
Member Author

kelunik commented Jun 9, 2017

@bukka I actually copied the test from the other TLS wrapper tests, which also use spaces. In fact, many of the tests use spaces, but there are also tests with tabs. If we want to have that consistent, we should do that in a separate PR I guess.

@php-pulls
Copy link

Comment on behalf of pollita at php.net:

bec91e1

@php-pulls php-pulls closed this Jun 10, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants