rust-lang / cargo

The Rust package manager
https://doc.rust-lang.org/cargo
Apache License 2.0
12.48k stars 2.37k forks source link

cargo should force strong TLS 1.2 cipher suites to reduce downgrade attacks because crates.io offers many "weak" TLS 1.0-1.2 cipher suites #8113

Open x448 opened 4 years ago

x448 commented 4 years ago

Describe the problem you are trying to solve

crates.io is using servers that offer TLS 1.0, TLS 1.1, and TLS 1.2 with a numerous "weak" cipher suites enabled according to SSL Report.

The downgrade protection mechanism in TLS 1.2 relies on downgradeable parameters. So we should force TLS 1.2 to only allow strong cipher suites (reduce possible downgrade attacks).

cargo should add protection against TLS 1.2 downgrade attacks instead of relying solely on crates.io (vendors can change or make mistakes, etc. so a server-only fix isn't enough).

A similar issue was resolved in rustup-init.sh in https://github.com/rust-lang/rustup/pull/2287 on April 22, 2020.

From EUROCRYPT 2016, Protecting TLS from Legacy Crypto (pdf, iacr.org):

What can go wrong?

  • We get lazy and forget to remove weak algorithms
  • Downgrade attacks that exploit obsolete legacy crypto
(click to expand) :camera: SSL Report (Qualys) for crates.io

![image](https://user-images.githubusercontent.com/57072051/79284524-84f84c00-7e80-11ea-8abe-daecd649d551.png)

Describe the solution you'd like

cargo should force use of strong TLS 1.2 cipher suites (if supported by required libraries).

cargo can provide an [http] option to enable this (as opt-in, until there's enough confidence to make it the default behavior).

[http]
require-ecdhe-aead = true    # Force ECDHE+AEAD in TLS 1.2 (already true for TLS 1.3).

Ideally, I'd like to see the same 9 cipher suites as Firefox 68 ESR with all weak cipher suites disabled via about:config.

(click to expand) :camera: SSL Client Test of Firefox 68 ESR (hardened)

![image](https://user-images.githubusercontent.com/57072051/79347577-769a4680-7ef9-11ea-84c4-94fd7fda2e89.png)

Example of client-side OpenSSL cipher suites list tested with curl and wget --ciphers option:

TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384

DHE is excluded from cipher suites because servers often use bad DH params (see RFC 7919).

Notes

(click to expand) Links to RFC and etc. about TLS

* __2008__. TLS 1.2 was defined in [RFC 5246](https://tools.ietf.org/html/rfc5246) in August 2008. * __2015__. [Imperfect Forward Secrecy: How Diffie-Hellman Fails in Practice (pdf, weakdh.org)](https://weakdh.org/imperfect-forward-secrecy-ccs15.pdf) 22nd ACM Conference on Computer and Communications Security (CCS ’15), Denver, CO, October 2015. Best Paper Award Winner. * __2016__. [RFC 7919](https://tools.ietf.org/html/rfc7919) Negotiated Finite Field Diffie-Hellman Ephemeral Parameters for Transport Layer Security (TLS). * __2016__. [Protecting TLS from Legacy Crypto (pdf, iacr.org)](https://www.iacr.org/conferences/eurocrypt2016/slides-conf/bhargavan.pdf) KarthikeyanBhargavan+ many, many others.(INRIA, Microsoft Research, LORIA, IMDEA,Univof Pennsylvania, Univof Michigan, JHU) * __2018__. TLS 1.3 was defined in [RFC 8446](https://tools.ietf.org/html/rfc8446)

Eh2406 commented 4 years ago

Is this a request for cargo (belongs here) or for crates.io (belongs https://github.com/rust-lang/crates.io)? If it is for cargo, can you elaborate on what you would like to see changed? Edit: how does the ssl-version flag relate? https://doc.rust-lang.org/cargo/reference/config.html#httpssl-version

x448 commented 4 years ago

@Eh2406 I updated the issue, primarily to address your excellent question about httpssl-version.

If it is for cargo, can you elaborate on what you would like to see changed?

I'd like a cargo option (client-side) in the [http] section similar to this:

[http]
require-ecdhe-aead = true    # Require ECDHE+AEAD in TLS (already true for TLS 1.3).

If option is set to true, I'd like the resulting TLS cipher suite enabled by cargo match the screenshot provided under "Describe the solution you'd like".

how does the ssl-version flag relate? https://doc.rust-lang.org/cargo/reference/config.html#httpssl-version

If cargo specifies require-ecdhe-aead = true, then:

So the recommended httpssl-version should be min = TLS 1.2 and max = TLS 1.3 along with the proposed require-ecdhe-aead = true. There should be some releases of Rust with this as opt-in before making it default.

x448 commented 4 years ago

I updated the issue and title to incorporate excellent feedback received during
https://github.com/rust-lang/rustup/pull/2287.

ehuss commented 4 years ago

We discussed this a bit and in general sounds like a good idea, with the following thoughts:

One of the big issues will be figuring out how to make all of the above work on the platforms we support. Presumably if we're running on a platform that doesn't support these strong restrictions, Cargo should probably fall back to some weaker stance. I fear that may be tricky. Would you be willing to help dig into how to support that for different platforms? Some rough notes about what we currently do:

x448 commented 4 years ago

@ehuss Thanks for looking into this!

I would probably prefer a config option for explicitly setting the ciphers instead of a require-ecdhe-aead boolean. Is there a particular reason you suggest the boolean as opposed to an explicit list?

I agree. My initial reasoning forgot our users are programmers and I tried to simplify to reduce user error (typos, using OpenSSL vs Schannel syntax, etc.)

In https://github.com/rust-lang/rustup/pull/2287/commits/59114b88850dec9b49c52260262ae497836364eb we added RUSTUP_TLS_CIPHERSUITES to rustup-init.sh and use it if it's not empty. Otherwise, we force strong TLS 1.2-1.3 cipher suites if support is detected and fall back to simple TLS 1.2 requirement if needed.

I fear that may be tricky. Would you be willing to help dig into how to support that for different platforms?

I agree it can get tricky, especially if it includes Schannel or old unsupported OS versions.

Yes, I'd like to help. Experience includes replacing Schannel with stripped down OpenSSL and eventually LibreSSL (static linked because it auto-updated via TLS to linux/*bsd cloud). My software used TLS 1.2 ECDHE + AEAD (exclusively) on customer's Windows XP-7 clients and Windows 2008R2-2016 servers.

Some rough notes about what we currently do:

I think there are opportunities to simplify and increase reuse.

MacOS: 10.14 has curl+LibreSSL with support for strong cipher suites. I barely know how to use MacOS (only used MacOS and Windows ~3 times this year).

Windows: Schannel capabilities can vary too much (it can depend on whether it has certain updates, registry settings enabling some ciphers, etc.). :point_right: curl's official binary for Windows is statically linked to OpenSSL 1.1.1f. What prevents us from replacing Schannel with statically linked OpenSSL 1.1.1g in cargo for Windows?

Linux: Static linking to libcurl and OpenSSL sounds good to me, esp. if we're reasonably quick about updating OpenSSL with important security fixes. I think OpenSSL 1.0.1, 1.0.2, and 1.1.0 are EOL and were the first three versions to support TLS 1.2. Any version of OpenSSL with TLS 1.2 will support most or all of the strong cipher suites. When ready, we should ask package maintainers (if any) to test with desired strong cipher suites for compatibility.

Other platforms: Maybe we can ask users requiring old/obscure platforms to run a simple compatibility-test program that uses cargo's libcurl+openssl file downloading API.

The web server used for compatibility tests should be configured to log the TLS version, cipher suite, and user-agent. The test client should include client+platform version info in the user-agent.

Example nginx server logging TLS version and cipher suite:

    log_format  combined_ssl  '$remote_addr - $remote_user [$time_local] '
                              '"$request" '
                              '$status $body_bytes_sent '
                              '$ssl_protocol/$ssl_cipher '
                              '"$http_referer" '
                              '"$http_user_agent" "$http_x_forwarded_for"';
x448 commented 4 years ago

Schannel on Windows 7-8.1 doesn't appear to support ECDHE_RSA with AES_GCM according to
https://docs.microsoft.com/en-us/windows/win32/secauthn/tls-cipher-suites-in-windows-7
https://docs.microsoft.com/en-us/windows/win32/secauthn/tls-cipher-suites-in-windows-8-1

The only two strong TLS 1.2 cipher suites offered by crates.io use ECDHE_RSA + AES_GCM.

image

alexcrichton commented 4 years ago

I ran some tests against badssl.com using curl and various bits of configuration. Looks like at least with respect to TLS versions we have good platform support. I believe the dynamically linked libcurl (at least recent versions on OSX) support selecting the protocol and denying previous versions. Statically linked OpenSSL works as expected and schannel appears to work too.

For ciphersuite negotiation I was less certain to figure out how things were working. Looks like it's configured with OpenSSL, something is at least passed down for schannel, but "cipher_list" isn't mentioned anywhere in sectransp.c. On my mac curl --version says (SecureTransport) LibreSSL/2.8.3, so I'm not entirely sure what's going on there.

Some things I've thought while looking into various pieces:

x448 commented 4 years ago

@alexcrichton just a quick FYI, there were 3 scenarios tested during TLS hardening for rustup-init.sh:

  1. nginx server with only weak cipher suites
  2. nginx server with only strong cipher suites
  3. nginx server with server-preferred-order enabled and all weak cipher suites listed before all strong cipher suites

In the 3rd test, the client using hardened cipher suites (if supported) must ignore all the weak ones preferred by the server and use the first matching strong cipher suite.

I only tested manually, but the server could probably be setup to provide TLS version and cipher suite in the https content or headers.

x448 commented 4 years ago

@alexcrichton

That means that we probably could ship OpenSSL on Windows for builds of Cargo, and still have things work.

Official curl binary for Windows uses OpenSSL 1.1.1f so we can avoid all the edge cases they already encountered and resolved.

curl-rust and/or cargo can be compared with how curl does things on Windows. It may be useful for https://github.com/alexcrichton/curl-rust/pull/284 to also look at curl+openssl provided by homebrew or macports to avoid known issues.

but how can we test Cargo can't connect, by default at least, to servers with only weaker ciphersuites?

One way is to setup testing subdomains with nginx + Let's Encrypt certs:

  1. weakonly.test.rust-lang.org (must not connect)
  2. strongonly.test.rust-lang.org (must connect)
  3. weak-then-strong-server-pref.test.rust-lang.org (must ignore weak and use strong cipher)

For test 3, nginx can return TLS connection info in headers like this:

add_header X-TLS-used "$ssl_protocol/$ssl_cipher";
add_header X-TLS-client-curves "$ssl_curves";
add_header X-TLS-client-ciphers "$ssl_ciphers";

Which can be seen with:

> curl -I "https://REDACTED"
HTTP/1.1 200 OK
...
X-TLS-used: TLSv1.2/ECDHE-RSA-CHACHA20-POLY1305
X-TLS-client-curves: X25519:prime256v1:secp521r1:secp384r1
X-TLS-client-ciphers: ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:DHE-RSA-AES256-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES128-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:0x00ff

A diagnostic client program using cargo's download function would be useful. It can print, send, and receive more detailed info than cargo during TLS connection tests.

We can ask the rust community to run it on a variety of platform versions during development and when we're helping with any download issues for cargo & rustup (https://github.com/rust-lang/rustup/issues/2294).

alexcrichton commented 4 years ago

@x448 would you be willing to help implement the changes necessary for this? I'd be curious to test this nginx setup for windows/mac/linux to see how we respond today and what the various capabilities are we have available to us. The example program I had above tests some things about bad certificates and such but unfortunately curl didn't have great introspection mechanisms for which TLS ciphers were in use.

I can help out testing on platforms you don't have access to, but it'd be great for having something like a docker image with nginx running which prints out all the debug info about cargo connecting to it.