Skip to content

Consider switching to rustls as the default #2025

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

Open
nyurik opened this issue Nov 9, 2023 · 29 comments
Open

Consider switching to rustls as the default #2025

nyurik opened this issue Nov 9, 2023 · 29 comments
Labels
B-breaking-change Blocked: breaking change. B-rfc Blocked: Request for comments. More discussion would help move this along.

Comments

@nyurik
Copy link
Contributor

nyurik commented Nov 9, 2023

I would like to propose Reqwest switches to rustls crate, probably rustls-tls-native-roots variant of it, as the new default option for tls. Rustls seems like a highly stable and secure product, and most importantly - it does not require to compile non-rust code during the build process. This would obviously be a breaking change, and would have to be discussed and coordinated appropriately. Thx!


🚨 🚨 As a general call for comments, if you (any of you) need native-tls over rustls, or if the default change were to harm you, or you know of specific situations as such, please chime in here. 🚨 🚨

@gibbz00
Copy link
Contributor

gibbz00 commented Nov 12, 2023

Linking a good summary on the pros and cons of webkpi-roots vs native-certs from @djc himself (for anyone interested):

rust-lang/rustup#3400 (comment)

@djc
Copy link
Contributor

djc commented Mar 6, 2024

I (obviously) think it would be great to consider this for a next semver-breaking change.

Should consider this in tandem with the decision what rustls crypto provider to enable by default (see #2136).

@seanmonstar seanmonstar added B-rfc Blocked: Request for comments. More discussion would help move this along. B-breaking-change Blocked: breaking change. labels Mar 6, 2024
@seanmonstar
Copy link
Owner

seanmonstar commented Mar 20, 2024

I'll include some thoughts in here which after some discussion, could probably become better documented "design principles".

  • Regarding rustls as default, I think it's similar to whether to have hickory-dns by default. In that, I think both of those are usually better options, but there are obscure cases where they won't work, so currently reqwest defaults aim to work in more cases.
    • I do think that principle is worth solidifying and documenting (and arguing/discussing): should reqwest defaults aim for "works in strictly more cases", or "works better in the most common cases"?
  • Another point: native-tls should mean smaller binaries, since it uses what's already on the system (unless you use vendored).

@djc
Copy link
Contributor

djc commented Mar 20, 2024

I do think that principle is worth solidifying and documenting (and arguing/discussing): should reqwest defaults aim for "works in strictly more cases", or "works better in the most common cases"?

This feels like a simplistic way of trading off non-functional requirements; if the latter option is 100% better in 99% of cases, would you still pick the option that works in more cases? I'd argue there's some kind of expected value thing here which requires coming up with a weight for any number of non-functional requirements, like maybe:

  • Reliability: unlikely to break
  • Performance: works fast
  • Ergonomics: ease of integration

It looks like native-tls doesn't support TLS 1.3 which seems likely to affect performance and security and it uses OpenSSL on Linux which has a pretty spotty security history. rustls doesn't support TLS 1.1 (and earlier) which are officially deprecated anyway but which I guess could come up but should be increasingly rare. Also at this stage I think rustls is in a really good situation in terms of maintenance with two full-time maintainers so it's likely that issues (that are not by design) can fixed very quickly.

I would also argue that, as a Rust HTTP client built on hyper, a Rust HTTP protocol stack, all other things being equal it is nicer for integration and ergonomics (building etc) to use Rust for as much of the internals as possible, and doing so will improve quality of the Rust components over time because they get more exposure (many eyes, shallow bugs).

(I find the case for trust-dns a little harder to make because it is more resource-constrained on maintenance.)

As for binary size: I think it is already the cases that users who are sensitive to binary size are more likely to pick other clients (like ureq or curl), so I'm not sure it should be an important driver.

@seanmonstar
Copy link
Owner

This feels like a simplistic way of trading off non-functional requirements

I mean yea, including more criteria can help make a better decision. It was just an axis I started with.

(It looks like native-tls supporting TLS 1.3 could be close, FWIW.)

But I support much of what you said regarding rustls.

all other things being equal it is nicer for integration and ergonomics (building etc) to use Rust for as much of the internals as possible

In general I'd agree with the sentiment, but in this case, I think native-tls actually has a better story here for most targets. Most users won't have to compile anything "not Rust". With rustls, whether using ring or aws-lc, you need to compile C. rustls' new default backend requires having cmake. I don't think those things are the end of the world, but they do add some friction.


I'll also add, the way reqwest's features are structured, where default-tls only exposes things supported by both backends, and needing to enable either native-tls or rustls-tls to access specific things... that was with the intention of being able to eventually switch the default to rustls. It is something I'd like to do. I just wanted to make sure it's good for the most amount of people, not just what I like.

@djc
Copy link
Contributor

djc commented Mar 20, 2024

With rustls, whether using ring or aws-lc, you need to compile C. rustls' new default backend requires having cmake. I don't think those things are the end of the world, but they do add some friction.

That's fair! FWIW, I'm not a fan of the decision to make aws-lc-rs the default crypto provider for rustls -- I think the additional friction isn't worth the benefits for most downstream users. But of course reqwest could just decide to keep ring as the default for rustls' crypto provider.

It is something I'd like to do. I just wanted to make sure it's good for the most amount of people, not just what I like.

Sure, I think you're making valid points -- I just also want to argue the other side.

@seanmonstar
Copy link
Owner

seanmonstar commented Mar 20, 2024

🚨 🚨 As a general call for comments, if you (any of you) need native-tls over rustls, or if the default change were to harm you, or you know of specific situations as such, please chime in here. 🚨 🚨

@ofek

This comment has been minimized.

@djc

This comment has been minimized.

@seanmonstar seanmonstar changed the title Consider switching to rustls as the default, and make openssl optional Consider switching to rustls as the default Mar 3, 2025
@simonsan
Copy link

simonsan commented Mar 4, 2025

With rustls, whether using ring or aws-lc, you need to compile C. rustls' new default backend requires having cmake. I don't think those things are the end of the world, but they do add some friction.

Since aws-lc-rs is a hell to cross-compile, I've actually added it to denied crates. :/ It'd be in favour of using ring over that, even if ring recently got some maintenance changes, but seems to be continued to be maintained for now. I hope the Top priority is that reqwest remain easy to use. It should be the best default for most people. is taken in a way, that introducing CMake as a requirement to build a Rust crate is not what is considered to be a best default and easy to use/build.

@djc
Copy link
Contributor

djc commented Mar 4, 2025

It is possible you might not be a part of "most people" in this particular regard. I'm sure other rustls crypto providers (as well as native-tls) will still be an option.

(So far it seemed like ring is on security-only maintenance. That means no further development for things like new cipher suites -- like post-quantum cryptography.)

(When is the last time you've tried aws-lc-rs? Have you filed issues against it to describe your challenges with cross-compilation?)

@ofek

This comment has been minimized.

@djc

This comment has been minimized.

@simonsan
Copy link

simonsan commented Mar 5, 2025

(When is the last time you've tried aws-lc-rs? Have you filed issues against it to describe your challenges with cross-compilation?)

Three months ago when it broke our armv7 and netbsd CD builds during a release when updating dependencies. 😅

And if I remember right, there was already an issue opened for some parts of the breakage, and I think I also opened another one.

@briansmith
Copy link

(So far it seemed like ring is on security-only maintenance. That means no further development for things like new cipher suites -- like post-quantum cryptography.)

This isn't true.

@briansmith
Copy link

introducing CMake as a requirement to build a Rust crate is not what is considered to be a best default and easy to use/build.

CMake doesn't have to be necessary for building that other thing. I suggest filing an issue in its issue tracker about making it easier to build with fewer prerequisites.

I don't think people should make choices like this based on ease of building.

@simonsan
Copy link

simonsan commented Mar 5, 2025

introducing CMake as a requirement to build a Rust crate is not what is considered to be a best default and easy to use/build.

CMake doesn't have to be necessary for building that other thing. I suggest filing an issue in its issue tracker about making it easier to build with fewer prerequisites.

Will do!

I don't think people should make choices like this based on ease of building.

While I agree with the sentiment, I need to say that when you consider the CI/CD part of it, it should be for sure a concern. Not everyone can spend hours on diagnosing build issues, when the Rust build ecosystem is usually cargo build-it.

@briansmith
Copy link

@seanmonstar wrote:

In general I'd agree with the sentiment, but in this case, I think native-tls actually has a better story here for most targets. Most users won't have to compile anything "not Rust". With rustls, whether using ring or aws-lc, you need to compile C.

I agree 100% with this.

The main issue with native-tls, IMO, is that it defaults to OpenSSL when there isn't a native TLS stack. I don't think that's justified. But for targets like Windows, Android, macOS/iOS and other Apple targets, where there is a native TLS stack and where the native TLS stack is quite good, I think it isn't clear that switching away from the native TLS stack is an advantage. 10 years ago using native TLS libraries was a lot more scary than today.

@briansmith
Copy link

Also, I saw above a mention of rustls-platform-verifier. rustls-platform-verifier was created a for 2 particular purposes:

  1. A product was being ported to Linux where it was intended to use Rustls. In order to reduce the risk of the Linux port's launch, we decided to use Rustls on other targets so we could notice any (compatibility) issues caused by Rustls before the launch.
  2. There was too much compatibility risk to using webpki as the certificate verifier for that product on macOS and Windows, so we had to keep using the native certificate verifier to minimize risk. We accepted that certificate verification on Linux is a tire fire that should be dealt with separately.

However, long-term the combination of Rustls + rustls-platform-verifier is questionable because certificate verification is still probably the riskiest attack surface in native crypto/TLS stacks today. For teams doing a high-risk "rewrite it in Rust" ground-up rewrite like the creators of rustls-platform-verifier, it was the right trade-off. Long-term, probably not. My understanding is that even Microsoft Edge isn't using the native certificate verifier any more.

@djc
Copy link
Contributor

djc commented Mar 6, 2025

(So far it seemed like ring is on security-only maintenance. That means no further development for things like new cipher suites -- like post-quantum cryptography.)

This isn't true.

You made a widely advertised announcement that resulted in the rustls team taking over security-only maintenance. I have not seen an announcement from you that feature development for ring has been restarted, so as far as I understand the current status quo is still that there will be no further feature development. There has been a 0.17.13 announcement which includes a new feature, but in my view that doesn't provide a good indication of future activity.

introducing CMake as a requirement to build a Rust crate is not what is considered to be a best default and easy to use/build.

CMake doesn't have to be necessary for building that other thing. I suggest filing an issue in its issue tracker about making it easier to build with fewer prerequisites.

I don't think people should make choices like this based on ease of building.

In the absence of other significantly distinguishing features, we've seen a significant number of folks for whom ease of building is an important issue. If you think other distinctions between (for example) aws-lc-rs and ring are a better basis for selection, I'd be interested in your arguments (might make good material for the ring README, for example).

The main issue with native-tls, IMO, is that it defaults to OpenSSL when there isn't a native TLS stack. I don't think that's justified. But for targets like Windows, Android, macOS/iOS and other Apple targets, where there is a native TLS stack and where the native TLS stack is quite good, I think it isn't clear that switching away from the native TLS stack is an advantage. 10 years ago using native TLS libraries was a lot more scary than today.

While the underlying TLS stacks might be quite good, it seems that to this day native-tls does not support TLS 1.3 which by itself seems like a substantial disadvantage.

However, long-term the combination of Rustls + rustls-platform-verifier is questionable because certificate verification is still probably the riskiest attack surface in native crypto/TLS stacks today. For teams doing a high-risk "rewrite it in Rust" ground-up rewrite like the creators of rustls-platform-verifier, it was the right trade-off. Long-term, probably not. My understanding is that even Microsoft Edge isn't using the native certificate verifier any more.

One reason we now recommend rustls-platform-verifier is that on non-Linux (including Android) platforms, the platform verifier is likely to have more precise trust information (like precise trust before/trust after mechanics) that rustls-native-certs doesn't provide (it just has "certificate included in the OS set" or not). Additionally rustls-native-certs does not provide any mechanism for refreshing the set of trusted roots at run-time (after the initial load), which could be an issue for long-running processes. While the latter is arguably straightforward to solve, I don't know that there are good platform APIs which can be used to extract the precise trust information, nor is it necessarily clear exactly what precise trust mechanics are employed by the platform vendors.

@briansmith
Copy link

While the underlying TLS stacks might be quite good, it seems that to this day native-tls does not support TLS 1.3 which by itself seems like a substantial disadvantage.

Most applications don't care about TLS 1.3 vs TLS 1.2 though, TBF. I updated the native-tls issue with what I found about the Windows support. Because of the popularity of old Windows and Android versions, and Linux, there definitely is a need for a non-native solution. But I don't think the typical iOS application is better off with packaging its own crypto/TLS library.

RE: rustls-platform-verifier, I initiated its creation before it was open source, so I understand the trade-offs. My point was that the native certificate verifier is probably the most dangerous component of the native TLS stack. I believe subsequent to the creation of rustls-platform-verifier, the Chrome team / BoringSSL did find a way to reliably hook into the native certificate stores. Again, my understanding is that even Edge doesn't use the native API on Windows anymore; from https://textslashplain.com/2022/12/06/tls-certificate-verification-changes-in-edge/:

Starting in Edge version 109, Edge will instead rely on code and trust data shipped in the browser for these purposes — certificate chain validation will use Chromium code, and root trust determination will (non-exclusively) depend on a trust list generated by Microsoft and shipped with the browser.

Anyway, subsequent to me posting my comment, I saw an announcement that this was pretty much already decided, so I won't belabor the point any further.

@safinaskar
Copy link

safinaskar commented May 9, 2025

On easy of building.

For me easy of building is very important. Also I like static binaries, so I often compile my code with --target=x86_64-unknown-linux-musl. So, here are lists of Debian packages I need to install to be able to build and run reqwest-enabled program. I assume that curl is already installed on build machine, because it is needed to run curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh. I assume that ca-certificates (i. e. system TLS certificate store) is installed on build machine, too, because it is needed to run that command, too. I assume that gcc and libc6-dev are installed on build machine, because they are needed to build simple helloworld Rust program. All tests were done on latest Debian sid amd64, with latest stable rust (on build machine) and latest reqwest. Build and run machines are two separate machines.

  • Option 1. reqwest with rustls. reqwest = { version = "0.12.15", features = ["rustls-tls-native-roots"], default-features = false }. (Update 2025-05-31: this uses ring.) Extra dependencies on build machine: zero! 🎉 On run machine: ca-certificates
  • Option 2. The same, but with --target=x86_64-unknown-linux-musl. Extra dependencies on build machine: musl-dev (surprisingly, musl-dev is needed for reqwest, but is not needed for building musl rust helloworld). On run machine: ca-certificates
  • Option 3. reqwest with default features. reqwest = { version = "0.12.15" }. Extra dependencies on build machine: pkgconf-bin, libssl-dev. On run machine: ca-certificates
  • Option 4. The same, but with --target=x86_64-unknown-linux-musl. I was not able to build at all! Error message is: "pkg-config has not been configured to support cross-compilation". I don't know how to fix this. And I don't want to deal with this. I will better switch to rustls. (If you want, I can report this as a separate issue.)

Obviously, I support making rustls default

@seanmonstar
Copy link
Owner

🚨 🚨 Alrighty, an update: it seems pretty clear to me:

We should change the default to rustls.

Because native-tls means:

rustls with aws-lc doesn't require cmake on the common platforms.

And for anyone who needs native-tls, well you can always set cargo features to build it.

Now on to the next bikeshed:

Does changing the default require a breaking change?

Theoretically, I put a lot of effort into making it so the default-tls only ever enabled features that both native-tls and rustls supported. You needed to enable the more specific feature to access options that only one backend understood. The purpose of that was to hopefully allow changing the default without breakage.

I still suspect there might be a little bit of breakage, though. Is it enough to be considered breaking according to the typical Rust library stability promises? I kind of wish there was a decent way to "try" publishing it that way, and see what breakage existed in reality (like how the Rust Project can do a crater run).

Is a bump to reqwest v0.13 a big deal anyways?

@CryZe
Copy link

CryZe commented May 27, 2025

rustls with aws-lc doesn't require cmake on the common platforms.

It seems to still require it on Windows (as well as nasm), unless that is not considered a common platform.

@seanmonstar
Copy link
Owner

Oh, I must have missed that. Yes, Windows is common in my assessment. Is there an issue tracking when it will no longer be needed on Windows?

@ctz
Copy link

ctz commented May 27, 2025

It seems to still require it on Windows (as well as nasm),

My understanding is that nasm has not been required on Windows since aws-lc-rs 1.9.0 (September 2024 - ref)

@tshepang
Copy link

Is a bump to reqwest v0.13 a big deal anyways?

I think not, especially if accompanied with some text on what that possible breakage is. If there is no real breakage, people would be less anxious bumping.

If there is breakage where semver number implies there isn't, people would lose some trust. Better be safe, while only risking a bit of churn.

@djc
Copy link
Contributor

djc commented May 27, 2025

Is a bump to reqwest v0.13 a big deal anyways?

It'll start an era with quite a bit of duplicate dependencies while library crates with limited maintenance capacity upgrade. Might not be too bad though?

What will be the default verifier?

@safinaskar
Copy link

@seanmonstar

We should change the default to rustls

Cool! Some notes:

  • As well as I understand, you propose to make aws-lc default. Note: currently, reqwest uses ring when we choose rustls (
    rustls-tls-webpki-roots = ["rustls-tls-webpki-roots-no-provider", "__rustls-ring"]
    ). And there is no even option to choose aws-lc. So, aws-lc with reqwest currently completely untested. (There is nothing wrong in it, I just want to remind you.)
  • Okay, let me test build requirements of reqwest with aws-lc (my previous message ( Consider switching to rustls as the default #2025 (comment) ) used ring with rustls, because this is how reqwest currently works). Well, currently there is no option to choose aws-lc with reqwest, so I will test hyper-rustls instead. You should read my previous comment first ( Consider switching to rustls as the default #2025 (comment) ). So, here we go:
    • Option 5. hyper-rustls with aws-lc. Extra dependencies on build machine: zero. On run machine: ca-certificates
    • Option 6. The same, but with --target=x86_64-unknown-linux-musl. Extra dependencies on build machine: musl-dev. On run machine: ca-certificates
  • So, conclusion: rustls with aws-lc is relatively easy to build, similarly to rustls with ring. Here are all sources used for last 2 experiments (i. e. options 5 and 6):

Cargo.toml:

[package]
name = "client-example"
version = "0.1.0"
edition = "2024"

[dependencies]
http = "1.3.1"
http-body-util = "0.1.3"
hyper = "1.6.0"
hyper-rustls = "0.27.6"
hyper-util = "0.1.13"
rustls = "0.23.27"
tokio = { version = "1.45.1", features = ["full"] }

main.rs:

//! Based on https://github.com/rustls/hyper-rustls/blob/e6a23710aa02b81ccf03d54801df8faace53eb68/examples/client.rs

//! Simple HTTPS GET client based on hyper-rustls

use http::Uri;
use http_body_util::{BodyExt, Empty};
use hyper::body::Bytes;
use hyper_rustls::ConfigBuilderExt;
use hyper_util::{client::legacy::Client, rt::TokioExecutor};

use std::str::FromStr;

#[tokio::main]
async fn main() {
    // Set a process wide default crypto provider.
    let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();

    // Root-CA store is native cert store
    // Prepare the TLS client config
    // Default TLS client config with native roots
    let tls = rustls::ClientConfig::builder()
        .with_native_roots()
        .unwrap()
        .with_no_client_auth();

    // Prepare the HTTPS connector
    let https = hyper_rustls::HttpsConnectorBuilder::new()
        .with_tls_config(tls)
        .https_or_http()
        .enable_http1()
        .build();

    // Build the hyper client from the HTTPS connector.
    let client: Client<_, Empty<Bytes>> = Client::builder(TokioExecutor::new()).build(https);

    // sends a GET request, inspects
    // the returned headers, collects the whole body and prints it to
    // stdout
    let res = client
        .get(Uri::from_str("https://www.rust-lang.org").unwrap())
        .await
        .unwrap();
    println!("Status:\n{}", res.status());
    println!("Headers:\n{:#?}", res.headers());

    let body = res
        .into_body()
        .collect()
        .await
        .unwrap()
        .to_bytes();
    println!("Body:\n{}", String::from_utf8_lossy(&body));
}

Docker file for build machine:

FROM debian:sid
ENV LC_ALL C.UTF-8
RUN apt-get update && apt-get install -y --no-install-recommends curl ca-certificates gcc libc6-dev
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | bash -s -- -y
RUN . "$HOME/.cargo/env"; rustup target add x86_64-unknown-linux-musl

Docker file for run machine:

FROM debian:sid
ENV LC_ALL C.UTF-8
RUN apt-get update
  • aws-lc is twice slower to build than ring!!! Here is script to reproduce:
#!/bin/bash

set -e

mkdir prog
cd prog
cargo init
cargo add --no-default-features -F aws_lc_rs rustls

cargo build
rm -r Cargo.lock target
time -p cargo build
rm -r Cargo.lock target
time -p cargo build

cargo build --release
rm -r Cargo.lock target
time -p cargo build --release
rm -r Cargo.lock target
time -p cargo build --release

cargo remove rustls
cargo add --no-default-features -F ring rustls

cargo build
rm -r Cargo.lock target
time -p cargo build
rm -r Cargo.lock target
time -p cargo build

cargo build --release
rm -r Cargo.lock target
time -p cargo build --release
rm -r Cargo.lock target
time -p cargo build --release
  • Results:
    • aws_lc_rs, dev: 8.38, 8.29
    • aws_lc_rs, release: 16.98, 16.87
    • ring, dev: 5.34, 4.94
    • ring, release: 9.15, 9.16
  • I don't see anything wrong in it. This is just another thing to keep in mind

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
B-breaking-change Blocked: breaking change. B-rfc Blocked: Request for comments. More discussion would help move this along.
Projects
Status: No status
Development

No branches or pull requests