Skip to content
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

Forcing TLS version fails with OpenSSL 1.1.1 in Debian Buster? #4097

Closed
sindarina opened this issue Jul 8, 2019 · 16 comments
Labels

Comments

@sindarina
Copy link

@sindarina sindarina commented Jul 8, 2019

I did this

curl -v --tlsv1.0 https://domain.example/

This negotiates TLSv1.3 if the endpoint supports it. Adding --tls-max 1.0 has the request fail altogether with 'no protocols available'.

I expected the following

Use TLSv1 to negotiate the connection, do not use higher versions.

curl/libcurl version

curl 7.64.0 (x86_64-pc-linux-gnu) libcurl/7.64.0 OpenSSL/1.1.1c zlib/1.2.11 libidn2/2.0.5 libpsl/0.20.2 (+libidn2/2.0.5) libssh2/1.8.0 nghttp2/1.36.0 librtmp/2.3
Release-Date: 2019-02-06
Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtmp rtsp scp sftp smb smbs smtp smtps telnet tftp 
Features: AsynchDNS IDN IPv6 Largefile GSS-API Kerberos SPNEGO NTLM NTLM_WB SSL libz TLS-SRP HTTP2 UnixSockets HTTPS-proxy PSL

operating system

Debian 10.0 'Buster'

@sindarina

This comment has been minimized.

Copy link
Author

@sindarina sindarina commented Jul 8, 2019

This works as expected on the previous major release, Debian Stretch, which has the following curl version, although that one does not support the --tls-max 1.0 parameter;

curl 7.52.1 (x86_64-pc-linux-gnu) libcurl/7.52.1 OpenSSL/1.0.2s zlib/1.2.8 libidn2/0.16 libpsl/0.17.0 (+libidn2/0.16) libssh2/1.7.0 nghttp2/1.18.1 librtmp/2.3
Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtmp rtsp scp sftp smb smbs smtp smtps telnet tftp 
Features: AsynchDNS IDN IPv6 Largefile GSS-API Kerberos SPNEGO NTLM NTLM_WB SSL libz TLS-SRP HTTP2 UnixSockets HTTPS-proxy PSL

It also works as expected with the current Apple-supplied version in Mojave (10.14.5), with and without the --tls-max 1.0 parameter;

curl 7.54.0 (x86_64-apple-darwin18.0) libcurl/7.54.0 LibreSSL/2.6.5 zlib/1.2.11 nghttp2/1.24.1
Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtsp smb smbs smtp smtps telnet tftp 
Features: AsynchDNS IPv6 Largefile GSS-API Kerberos SPNEGO NTLM NTLM_WB SSL libz HTTP2 UnixSockets HTTPS-proxy

Oh, and this fails against something like the following;

curl -v --tls-max 1.0 --tlsv1.0 https://www.cloudflare.com/robots.txt

So it seems unlikely it's a server-side configuration issue?

@sindarina

This comment has been minimized.

Copy link
Author

@sindarina sindarina commented Jul 8, 2019

OK, testing with the latest curl version available from MacPorts, it looks like at least the 'no protocols available' error is fixed, but I'm not sure the behaviour is as it should be. Version info;

curl 7.65.1 (x86_64-apple-darwin18.6.0) libcurl/7.65.1 OpenSSL/1.1.1c zlib/1.2.11 libidn2/2.2.0 libpsl/0.21.0 (+libidn2/2.1.1)
Release-Date: 2019-06-05
Protocols: dict file ftp ftps gopher http https imap imaps pop3 pop3s rtsp smb smbs smtp smtps telnet tftp 
Features: AsynchDNS HTTPS-proxy IDN IPv6 Largefile libz NTLM NTLM_WB PSL SSL TLS-SRP UnixSockets

The following negotiates TLSv1.3;

--tlsv1.0 https://www.cloudflare.com/robots.txt

While this behaves as expected;

--tls-max 1.0 --tlsv1.0 https://www.cloudflare.com/robots.txt

As does this;

--tls-max 1.0 https://www.cloudflare.com/robots.txt

So it would seem that something changed between 7.64 and 7.65.1, or that the way OpenSSL gets built on both platforms is somehow different?

Disabling ALPN/HTTP 2.0 with --no-alpn --http1.1 does not seem to make a difference on the Debian side.

@jay

This comment has been minimized.

Copy link
Member

@jay jay commented Jul 15, 2019

This sounds like #2918 can you confirm

@jay jay added the SSL/TLS label Jul 15, 2019
@sindarina

This comment has been minimized.

Copy link
Author

@sindarina sindarina commented Jul 15, 2019

No, not the same thing.

@jay

This comment has been minimized.

Copy link
Member

@jay jay commented Jul 16, 2019

curl -v --tlsv1.0 https://domain.example/

This negotiates TLSv1.3 if the endpoint supports it.

--tlsv1.0 means use TLS 1.0 or later. The negotiated version depends on what your SSL library and the server support. In older versions the option meant negotiate only TLS 1.0, as discussed in #2918.

No, not the same thing.

Can you explain how this is different from #2918

@sindarina

This comment has been minimized.

Copy link
Author

@sindarina sindarina commented Jul 16, 2019

Because, even if used as you said it should be used, it fails in unpredictable ways, and it is not clear from the documentation when and where that changed. The fact that you have multiple tickets for a similar issue shows that we're not the only ones running into this, either.

Even between 7.64 and 7.65 the behaviour seems inconsistent.

@jay

This comment has been minimized.

Copy link
Member

@jay jay commented Jul 16, 2019

Because, even if used as you said it should be used, it fails in unpredictable ways, and it is not clear from the documentation when and where that changed. The fact that you have multiple tickets for a similar issue shows that we're not the only ones running into this, either.

What multiple tickets? If the packaged build of curl in Debian can't connect to cloudflare when you request only TLS 1.0 then I don't see how that's curl's fault. You can use Wireshark and capture host www.cloudflare.com and examine the handshake. The default ciphers offered are going to be different depending on how the SSL library was built. It's possible the server does not have any ciphers in common.

@sindarina

This comment has been minimized.

Copy link
Author

@sindarina sindarina commented Jul 16, 2019

Before filing this ticket we of course took care to test this against multiple endpoints. Cloudflare is mentioned as an example because it is a stable, public target that has support for TLSv1 with multiple ciphers, something which can be, and was, verified before this ticket was created.

The rest of the information supplied also makes it pretty clear that we tested against these endpoints with multiple versions of curl, from multiple platforms, and the change of behaviour most definitely originates with curl. We even provided an example with a freshly compiled version of a recent build, so I'm not sure what else there is to prove.

I guess it's still useful for the next person who comes along confused by the unpredictable change in behaviour.

jay added a commit to jay/curl that referenced this issue Jul 16, 2019
Since 7.54 --tlsv1. options use the specified version or later, however
older versions of curl documented it as using just the specified version
which may or may not have happened depending on the TLS library.
Document this discrepancy to allay confusion for users familiar with the
old documentation that expect just the specified version.

Fixes curl#4097
Closes #xxxx
jay added a commit to jay/curl that referenced this issue Jul 16, 2019
Since 7.54 --tlsv1. options use the specified version or later, however
older versions of curl documented it as using just the specified version
which may or may not have happened depending on the TLS library.
Document this discrepancy to allay confusion for users familiar with the
old documentation that expect just the specified version.

Fixes curl#4097
Closes #xxxx
@jay

This comment has been minimized.

Copy link
Member

@jay jay commented Jul 16, 2019

I've submitted #4119 to update the documentation. Your examples using the latest version of curl show it's working as expected. If it wasn't I'd suspect how the SSL library is built or the server. TLS 1.0 is being phased out by some so it's possible either of those have dropped support. You can investigate further by using Wireshark.

@jay jay closed this in c7f3c07 Jul 17, 2019
caraitto added a commit to caraitto/curl that referenced this issue Jul 23, 2019
Since 7.54 --tlsv1. options use the specified version or later, however
older versions of curl documented it as using just the specified version
which may or may not have happened depending on the TLS library.
Document this discrepancy to allay confusion for users familiar with the
old documentation that expect just the specified version.

Fixes curl#4097
Closes curl#4119
@notti

This comment has been minimized.

Copy link

@notti notti commented Aug 7, 2019

I did this

curl -v --tlsv1.0 https://domain.example/

This negotiates TLSv1.3 if the endpoint supports it. Adding --tls-max 1.0 has the request fail altogether with 'no protocols available'.

operating system

Debian 10.0 'Buster'

Since I just ran into the same issue and couldn't find anything on the internet why this is failing, I tried tracking it down and found the cause (combination of debian and openssl):

  1. curl calls OPENSSL_load_builtin_modules in Curl_ossl_init
  2. curl sets up everything correctly if you supply --tlsv1.0 and --tls-max 1.0 and sets the right options via SSL_CTX_set_options in ossl_connect_step1 (SSL_OP_NO_COMPRESSION|SSL_OP_SINGLE_ECDH_USE|SSL_OP_NO_SSLv3|SSL_OP_NO_TICKET|SSL_OP_NO_TLSv1_1|SSL_OP_NO_TLSv1_2|SSL_OP_NO_TLSv1_3 in my case)
  3. everything continues correctly up until SSL_connect in ossl_connect_step2, where openssl tries to build a client hello. For this it first tries to find out which tls/ssl version this should be in ssl_get_min_max_version (openssl), which doesn't find a suitable tls/ssl version, erroring out with "no protocols available"

Step 1 is important here, since it loads the openssl default configuration (on debian from /usr/lib/ssl/openssl.cnf which is a symlink to /etc/ssl/openssl.cnf) and on debian this configuration contains:

[default_conf]
ssl_conf = ssl_sect

[ssl_sect]
system_default = system_default_sect

[system_default_sect]
MinProtocol = TLSv1.2
CipherString = DEFAULT@SECLEVEL=2

-> Step 1 sets the general minimum tls/ssl version to TLSv1.2, Step 2 disallows TLSv1_2 and TLSv1_3; Step 3 now basically has no viable TLS options left causing the error (this fails a bit late for my taste since the TCP connection was already established at this point...)

So I'd say everything works as intended - but the error message is a bit confusing...

Solutions:

Maybe a pointer in the documentation would help here.

@bagder

This comment has been minimized.

Copy link
Member

@bagder bagder commented Aug 7, 2019

Awesome detective work there @notti! The question left is then only what to say where in the docs... 😁

@notti

This comment has been minimized.

Copy link

@notti notti commented Aug 8, 2019

Hmm maybe something like the following to the tls min/max options (I'm not sure about the ciphers part):

Beware that system wide or user configuration of the used TLS library might add further restrictions on allowed minimum or maximum TLS version or allowed ciphers, which can cause a combinations containing no viable option (e.g., max TLS version < min TLS version) leading to an aborted connection attempt. 

Maybe handle SSL_R_NO_PROTOCOLS_AVAILABLE somewhere around

(reason == SSL_R_CERTIFICATE_VERIFY_FAILED)) {
and tell the user that "provided minimum and maximum TLS version in combination with system or user configuration left no viable option".

I think the same can happen with the other libraries. I can try to come up with a pull request (can't promise any timelines), if you think this looks good. The same can probably happen with the other ssl libs - but I don't have any experience with those (actually neither with openssl - just read code/documentation and used a debugger/strace/ltrace to track that down).

@bagder

This comment has been minimized.

Copy link
Member

@bagder bagder commented Aug 8, 2019

I can try to come up with a pull request (can't promise any timelines), if you think this looks good.

Yes, thanks. Please do!

@cnotin

This comment has been minimized.

Copy link
Contributor

@cnotin cnotin commented Aug 28, 2019

Thanks everyone for your interesting comments.
I think curl should be able to bypass the system default, and make openssl go down to TLSv1.0 when requested.
Did you consider it? Or maybe I'm missing something...

@cnotin

This comment has been minimized.

Copy link
Contributor

@cnotin cnotin commented Aug 28, 2019

I tried something...
First test, it fails as explained above:

root@kali:~/curl/curl# curl -V
curl 7.64.0 (x86_64-pc-linux-gnu) libcurl/7.64.0 OpenSSL/1.1.1a zlib/1.2.11 libidn2/2.0.5 libpsl/0.20.2 (+libidn2/2.0.5) libssh2/1.8.0 nghttp2/1.36.0 librtmp/2.3
Release-Date: 2019-02-06
Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtmp rtsp scp sftp smb smbs smtp smtps telnet tftp 
Features: AsynchDNS IDN IPv6 Largefile GSS-API Kerberos SPNEGO NTLM NTLM_WB SSL libz TLS-SRP HTTP2 UnixSockets HTTPS-proxy PSL 
root@kali:~/curl/curl# curl --tlsv1 https://tls-v1-0.badssl.com:1010/
curl: (35) error:1425F102:SSL routines:ssl_choose_client_version:unsupported protocol

I added the following code (I know it's not correct for many reasons, just a quick dirty test):

diff --git a/lib/vtls/openssl.c b/lib/vtls/openssl.c
index 20eae6c9e..a0d362d13 100644
--- a/lib/vtls/openssl.c
+++ b/lib/vtls/openssl.c
@@ -2511,6 +2511,9 @@ static CURLcode ossl_connect_step1(struct connectdata *conn, int sockindex)
   }
 
   SSL_CTX_set_options(BACKEND->ctx, ctx_options);
+#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
+  SSL_CTX_set_min_proto_version(BACKEND->ctx, TLS1_VERSION);
+#endif
 
 #ifdef HAS_NPN
   if(conn->bits.tls_enable_npn)

And it works :)

root@kali:~/curl/curl# make -sj && ./src/curl --tlsv1 https://tls-v1-0.badssl.com:1010/
Making all in lib
[...]
<!DOCTYPE html>
<html>
<head>
[...]

This was just a PoC to confirm that we can use SSL_CTX_set_min_proto_version for this!
This method is available from OpenSSL 1.1.0: https://www.openssl.org/docs/man1.1.0/man3/SSL_CTX_set_min_proto_version.html

So, to have a proper patch, this code must be adapted for all TLS versions. It should also move inside set_ssl_version_min_max() however we don't have access to BACKEND->ctx in it... Actually it didn't work when I tried, but when I see the following it makes me wonder:

SSL_CTX_set_max_proto_version(BACKEND->ctx, TLS1_3_VERSION);

What do you think? You know the code better than me :)

@cnotin

This comment has been minimized.

Copy link
Contributor

@cnotin cnotin commented Aug 28, 2019

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
5 participants
You can’t perform that action at this time.