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

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 this issue Apr 15, 2020 · 10 comments
Labels
A-configuration Area: cargo config files and env vars A-networking Area: networking issues, curl, etc. A-security Area: security C-feature-request Category: proposal for a feature. Before PR, ping rust-lang/cargo if this is not `Feature accepted` S-triage Status: This issue is waiting on initial triage.

Comments

@x448
Copy link

x448 commented Apr 15, 2020

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 rust-lang/rustup#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) 📷 SSL Report (Qualys) for crates.io

image

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) 📷 SSL Client Test of Firefox 68 ESR (hardened)

image

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

@x448 x448 added the C-feature-request Category: proposal for a feature. Before PR, ping rust-lang/cargo if this is not `Feature accepted` label Apr 15, 2020
@ehuss ehuss added A-networking Area: networking issues, curl, etc. A-configuration Area: cargo config files and env vars labels Apr 15, 2020
@Eh2406
Copy link
Contributor

Eh2406 commented Apr 15, 2020

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
Copy link
Author

x448 commented Apr 15, 2020

@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:

  • For TLS 1.3, option should be ignored because TLS 1.3 already requires ECDHE+AEAD.
  • For TLS 1.2, all the weak cipher suites will get disabled and it'll require ECDHE+AEAD
  • For TLS 1.0-1.1, it should prevent connection. Industry groups advised against using TLS 1.0-1.1 and both deadlines passed.

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 x448 changed the title add option to disable all "weak" TLS 1.0-1.2 cipher suites offered by crates.io (TLS 1.3 isn't available) 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 Apr 22, 2020
@x448
Copy link
Author

x448 commented Apr 22, 2020

I updated the issue and title to incorporate excellent feedback received during
rust-lang/rustup#2287.

@ehuss
Copy link
Contributor

ehuss commented Apr 22, 2020

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

  • We should probably set the minimum TLS to 1.2.
  • We should set the default ciphers to a stronger set.
  • 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?

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:

  • Mac: We always dynamically link libcurl. Mac recently switched from Secure Transport to LibreSSL, but we technically "support" back to 10.7. We'd need to figure out how far back we actually support (it looks like TLS 1.2 would require 10.8, which I think is fine).
  • Windows: We statically link libcurl, using Schannel. What kind of issues will there be with Windows 7? Windows 8?
  • Linux: We statically link libcurl, using (IIRC) static OpenSSL. Some Linux distributions build with dynamic libcurl using whatever the local system has.
  • Other platforms: ???

@ehuss ehuss added the A-security Area: security label Apr 22, 2020
@x448
Copy link
Author

x448 commented Apr 23, 2020

@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 rust-lang/rustup@59114b8 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.). 👉 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
Copy link
Author

x448 commented Apr 23, 2020

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
Copy link
Member

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:

  • Turns out we actually by default in the curl crate load system certs into OpenSSL. That means that we probably could ship OpenSSL on Windows for builds of Cargo, and still have things work. That's a "probably", I have no idea whether that cert loading is actually correct. No idea how to test either. I don't think this is necessary just yet though, I don't see a reason to stop using schannel.
  • I suspect that the actual literal names of ciphersuites vary greatly between TLS libraries. If https://curl.haxx.se/docs/ssl-ciphers.html is anything to go by it's just all over the place. This will likely make it difficult to support in Cargo.
  • Has the infrastructure team been contacted to disable TLS 1.1 on crates.io?
  • I found ways to test that we can't connect to servers with older TLS versions, but how can we test Cargo can't connect, by default at least, to servers with only weaker ciphersuites?

@x448
Copy link
Author

x448 commented Apr 23, 2020

@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
Copy link
Author

x448 commented Apr 25, 2020

@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 alexcrichton/curl-rust#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 (rust-lang/rustup#2294).

@alexcrichton
Copy link
Member

@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.

@epage epage added the S-triage Status: This issue is waiting on initial triage. label Nov 4, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-configuration Area: cargo config files and env vars A-networking Area: networking issues, curl, etc. A-security Area: security C-feature-request Category: proposal for a feature. Before PR, ping rust-lang/cargo if this is not `Feature accepted` S-triage Status: This issue is waiting on initial triage.
Projects
None yet
Development

No branches or pull requests

5 participants