From 5e1716c4d69d5ddddd100db93dc9680094dad0e6 Mon Sep 17 00:00:00 2001 From: Martin Algesten Date: Sun, 5 Jan 2025 11:50:32 +0100 Subject: [PATCH] Rustls with other CryptoProviders --- CHANGELOG.md | 2 + Cargo.lock | 212 +++++++++++++++++++++++++++++++ Cargo.toml | 33 ++++- README.md | 39 +++++- src/error.rs | 6 +- src/lib.rs | 40 +++++- src/tls/mod.rs | 85 ++++++++++++- src/tls/rustls.rs | 25 +++- src/unversioned/transport/mod.rs | 2 +- 9 files changed, 420 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d3ee39a..b2d4a3fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Unreleased + * `TlsConfig::unversioned_rustls_crypto_provider()` (#931) + * Feature `rustls-no-provider` to compile without ring (#931) * Body::content_length (#927) * Handle Authorization: Basic from URI (#923) * Remove many uses of Box::new() from Connector chain (#919) diff --git a/Cargo.lock b/Cargo.lock index 1c4125bb..c9d4d569 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -113,6 +113,33 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "aws-lc-rs" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdd82dba44d209fddb11c190e0a94b78651f95299598e472215667417a03ff1d" +dependencies = [ + "aws-lc-sys", + "mirai-annotations", + "paste", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df7a4168111d7eb622a31b214057b8509c0a7e1794f44c546d742330dc793972" +dependencies = [ + "bindgen", + "cc", + "cmake", + "dunce", + "fs_extra", + "libc", + "paste", +] + [[package]] name = "base64" version = "0.22.1" @@ -125,6 +152,29 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "bindgen" +version = "0.69.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools", + "lazy_static", + "lazycell", + "log", + "prettyplease", + "proc-macro2 1.0.92", + "quote 1.0.38", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.94", + "which", +] + [[package]] name = "bitflags" version = "2.6.0" @@ -159,6 +209,8 @@ version = "1.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d6dbb628b8f8555f86d0323c2eb39e3ec81901f4b83e091db8a6a76d316a333" dependencies = [ + "jobserver", + "libc", "shlex", ] @@ -168,12 +220,41 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "cmake" +version = "0.1.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb1e43aa7fd152b1f968787f7dbcdeb306d1867ff373c69955211876c053f91a" +dependencies = [ + "cc", +] + [[package]] name = "colorchoice" version = "1.0.3" @@ -283,6 +364,18 @@ dependencies = [ "litrs", ] +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + [[package]] name = "encoding_rs" version = "0.8.35" @@ -377,6 +470,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "getrandom" version = "0.2.15" @@ -388,12 +487,27 @@ dependencies = [ "wasi", ] +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + [[package]] name = "hashbrown" version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +[[package]] +name = "home" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "http" version = "1.2.0" @@ -572,6 +686,15 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.14" @@ -598,12 +721,43 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +[[package]] +name = "jobserver" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +dependencies = [ + "libc", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "libc" version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +[[package]] +name = "libloading" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +dependencies = [ + "cfg-if", + "windows-targets", +] + [[package]] name = "linux-raw-sys" version = "0.4.14" @@ -634,6 +788,12 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.8.2" @@ -643,6 +803,12 @@ dependencies = [ "adler2", ] +[[package]] +name = "mirai-annotations" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9be0862c1b3f26a88803c4a49de6889c10e608b3ee9344e6ef5b45fb37ad3d1" + [[package]] name = "native-tls" version = "0.2.12" @@ -660,6 +826,16 @@ dependencies = [ "tempfile", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -754,6 +930,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -781,6 +963,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "prettyplease" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" +dependencies = [ + "proc-macro2 1.0.92", + "syn 2.0.94", +] + [[package]] name = "proc-macro2" version = "0.4.30" @@ -861,6 +1053,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustix" version = "0.38.42" @@ -880,6 +1078,7 @@ version = "0.23.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5065c3f250cbd332cd894be57c40fa52387247659b14a2d6041d121547903b1b" dependencies = [ + "aws-lc-rs", "log", "once_cell", "ring", @@ -950,6 +1149,7 @@ version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -1327,6 +1527,18 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index 57a7e393..f927d541 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,25 +16,44 @@ exclude = ["/cargo_deny.sh", "/deny.toml", "/test.sh"] rust-version = "1.71.1" [package.metadata.docs.rs] -features = ["rustls", "platform-verifier", "native-tls", "socks-proxy", "cookies", "gzip", "brotli", "charset", "json", "_test"] +features = ["rustls", "platform-verifier", "native-tls", "socks-proxy", "cookies", "gzip", "brotli", "charset", "json", "_test", "_doc"] [features] default = ["rustls", "gzip", "json"] -rustls = ["dep:rustls", "_tls", "dep:webpki-roots"] -platform-verifier = ["dep:rustls-platform-verifier"] + +# SUPPORTED FEATURES + +rustls = ["rustls-no-provider", "_ring"] native-tls = ["dep:native-tls", "dep:der", "_tls", "dep:webpki-root-certs"] +platform-verifier = ["dep:rustls-platform-verifier"] socks-proxy = ["dep:socks"] cookies = ["dep:cookie_store", "_url"] gzip = ["dep:flate2"] brotli = ["dep:brotli-decompressor"] charset = ["dep:encoding_rs"] json = ["dep:serde", "dep:serde_json", "cookie_store?/serde_json"] + +# EXPERIMENTAL FEATURES. +# Might be removed or changed in a minor version. + +# Rustls CryptoProviders are not picked up from feature flags alone. They must be +# configured on Agent. This feature flag makes it possible to compile ureq with +# rustls, but without ring. +rustls-no-provider = ["dep:rustls", "_tls", "dep:webpki-roots", "_rustls"] + +# Supported as long as native-tls supports this. vendored = ["native-tls?/vendored"] -# Underscore prefixed features are internal +# INTERNAL FEATURES. DO NOT USE. + +# Ring has a higher chance of compiling cleanly without additional developer environment. +# Supported as long as rustls supports this. +_ring = ["rustls?/ring"] _url = ["dep:url"] _tls = ["dep:rustls-pemfile", "dep:rustls-pki-types"] _test = [] +_rustls = [] +_doc = ["rustls?/aws-lc-rs"] [dependencies] base64 = "0.22.1" @@ -54,8 +73,7 @@ rustls-platform-verifier = { version = "0.3.4", optional = true, default-feature webpki-roots = { version = "0.26.3", optional = true, default-features = false } webpki-root-certs = { version = "0.26.4", optional = true, default-features = false } -# ring has a higher chance of compiling cleanly without additional developer environment -rustls = { version = "0.23.18", optional = true, default-features = false, features = ["ring", "logging", "std", "tls12"] } +rustls = { version = "0.23.18", optional = true, default-features = false, features = ["logging", "std", "tls12"] } native-tls = { version = "0.2.12", optional = true, default-features = false } der = { version = "0.7.9", optional = true, default-features = false, features = ["pem", "std"] } @@ -82,7 +100,8 @@ env_logger = "0.11.6" auto-args = "0.3.0" serde = { version = "1.0.204", features = ["std", "derive"] } assert_no_alloc = "1.1.2" - +# Enable aws-lc-rs for tests so we can demonstrate using ureq without compiling ring. +rustls = { version = "*", features = ["aws-lc-rs"] } [[example]] name = "cureq" diff --git a/README.md b/README.md index 3d76910e..07e6112c 100644 --- a/README.md +++ b/README.md @@ -135,8 +135,8 @@ You can control them when including ureq as a dependency. The default enabled features are: **rustls**, **gzip** and **json**. -* **rustls** enabled the rustls TLS implementation. This is the default for the the crate level - convenience calls (`ureq::get` etc) +* **rustls** enables the rustls TLS implementation. This is the default for the the crate level + convenience calls (`ureq::get` etc). It uses `ring` as the TLS provider. * **native-tls** enables the native tls backend for TLS. Due to the risk of diamond dependencies accidentally switching on an unwanted TLS implementation, `native-tls` is never picked up as a default or used by the crate level convenience calls (`ureq::get` etc) – it must be configured @@ -152,6 +152,15 @@ The default enabled features are: **rustls**, **gzip** and **json**. (e.g. `Content-Type: text/plain; charset=iso-8859-1`). Without this, the library defaults to Rust's built in `utf-8` * **json** enables JSON sending and receiving via serde_json + +#### Experimental + +These features are experimental and might change in a minor version. + +* **rustls-no-provider** Enables rustls, but does not enable any [`CryptoProvider`] such as `ring`. + Providers other than the default (currently `ring`) are never picked up from feature flags alone. + It must be configured on the agent. + * **vendored** compiles and statically links to a copy of non-Rust vendors (e.g. OpenSSL from `native-tls`) ## TLS (https) @@ -160,13 +169,26 @@ The default enabled features are: **rustls**, **gzip** and **json**. By default, ureq uses [`rustls` crate] with the `ring` cryptographic provider. As of Sep 2024, the `ring` provider has a higher chance of compiling successfully. If the user -installs another [default provider], that choice is respected. +installs another process [default provider], that choice is respected. + +ureq does not guarantee to default to ring indefinitely. `rustls` as a feature flag will always +work, but the specific crypto backend might change in a minor version. ```rust // This uses rustls ureq::get("https://www.google.com/").call().unwrap(); ``` +#### rustls without ring + +ureq never changes TLS backend from feature flags alone. It is possible to compile ureq +without ring, but it requires specific feature flags and configuring the [`Agent`]. + +Since rustls is not semver 1.x, this requires non-semver-guaranteed API. I.e. ureq might +change this behavior without a major version bump. + +Read more at [`TlsConfigBuilder::unversioned_rustls_crypto_provider`][crate::tls::TlsConfigBuilder::unversioned_rustls_crypto_provider]. + ### native-tls As an alternative, ureq ships with [`native-tls`] as a TLS provider. This must be @@ -397,6 +419,17 @@ These allow the user write their own bespoke transports and (DNS name) resolver. these parts are not yet solidified. They live under the [`unversioned`] module, and do not follow semver. See module doc for more info. +### Breaking changes in dependencies + +ureq relies on non-semver 1.x crates such as `rustls` and `native-tls`. Some scenarios, such +as configuring `rustls` to not use `ring`, a user of ureq might need to interact with these +crates directly instead of going via ureq's provided API. + +Such changes can break when ureq updates dependencies. This is not considered a breaking change +for ureq and will not be reflected by a major version bump. + +We strive to mark ureq's API with the word "unversioned" to identify places where this risk arises. + ### Minimum Supported Rust Version (MSRV) From time to time we will need to update our minimum supported Rust version (MSRV). This is not diff --git a/src/error.rs b/src/error.rs index 36752552..9fe616d5 100644 --- a/src/error.rs +++ b/src/error.rs @@ -81,7 +81,7 @@ pub enum Error { /// *Note:* The wrapped error struct is not considered part of ureq API. /// Breaking changes in that struct will not be reflected in ureq /// major versions. - #[cfg(feature = "rustls")] + #[cfg(feature = "_rustls")] Rustls(rustls::Error), /// An error originating in Native-TLS. @@ -205,7 +205,7 @@ impl fmt::Display for Error { Error::Tls(v) => write!(f, "{}", v), #[cfg(feature = "_tls")] Error::Pem(v) => write!(f, "PEM: {:?}", v), - #[cfg(feature = "rustls")] + #[cfg(feature = "_rustls")] Error::Rustls(v) => write!(f, "rustls: {}", v), #[cfg(feature = "native-tls")] Error::NativeTls(v) => write!(f, "native-tls: {}", v), @@ -245,7 +245,7 @@ impl From for Error { } } -#[cfg(feature = "rustls")] +#[cfg(feature = "_rustls")] impl From for Error { fn from(value: rustls::Error) -> Self { Self::Rustls(value) diff --git a/src/lib.rs b/src/lib.rs index a3b5829b..5e03a79a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -139,8 +139,8 @@ //! //! The default enabled features are: **rustls**, **gzip** and **json**. //! -//! * **rustls** enabled the rustls TLS implementation. This is the default for the the crate level -//! convenience calls (`ureq::get` etc) +//! * **rustls** enables the rustls TLS implementation. This is the default for the the crate level +//! convenience calls (`ureq::get` etc). It currently uses `ring` as the TLS provider. //! * **native-tls** enables the native tls backend for TLS. Due to the risk of diamond dependencies //! accidentally switching on an unwanted TLS implementation, `native-tls` is never picked up as //! a default or used by the crate level convenience calls (`ureq::get` etc) – it must be configured @@ -156,6 +156,15 @@ //! (e.g. `Content-Type: text/plain; charset=iso-8859-1`). Without this, the //! library defaults to Rust's built in `utf-8` //! * **json** enables JSON sending and receiving via serde_json +//! +//! ### Experimental +//! +//! These features are experimental and might change in a minor version. +//! +//! * **rustls-no-provider** Enables rustls, but does not enable any [`CryptoProvider`] such as `ring`. +//! Providers other than the default (currently `ring`) are never picked up from feature flags alone. +//! It must be configured on the agent. +//! //! * **vendored** compiles and statically links to a copy of non-Rust vendors (e.g. OpenSSL from `native-tls`) //! //! # TLS (https) @@ -164,7 +173,10 @@ //! //! By default, ureq uses [`rustls` crate] with the `ring` cryptographic provider. //! As of Sep 2024, the `ring` provider has a higher chance of compiling successfully. If the user -//! installs another [default provider], that choice is respected. +//! installs another process [default provider], that choice is respected. +//! +//! ureq does not guarantee to default to ring indefinitely. `rustls` as a feature flag will always +//! work, but the specific crypto backend might change in a minor version. //! //! ``` //! # #[cfg(feature = "rustls")] @@ -174,6 +186,16 @@ //! # } Ok::<_, ureq::Error>(()) //! ``` //! +//! ### rustls without ring +//! +//! ureq never changes TLS backend from feature flags alone. It is possible to compile ureq +//! without ring, but it requires specific feature flags and configuring the [`Agent`]. +//! +//! Since rustls is not semver 1.x, this requires non-semver-guaranteed API. I.e. ureq might +//! change this behavior without a major version bump. +//! +//! Read more at [`TlsConfigBuilder::unversioned_rustls_crypto_provider`][crate::tls::TlsConfigBuilder::unversioned_rustls_crypto_provider]. +//! //! ## native-tls //! //! As an alternative, ureq ships with [`native-tls`] as a TLS provider. This must be @@ -417,6 +439,17 @@ //! these parts are not yet solidified. They live under the [`unversioned`] module, and do not //! follow semver. See module doc for more info. //! +//! ## Breaking changes in dependencies +//! +//! ureq relies on non-semver 1.x crates such as `rustls` and `native-tls`. Some scenarios, such +//! as configuring `rustls` to not use `ring`, a user of ureq might need to interact with these +//! crates directly instead of going via ureq's provided API. +//! +//! Such changes can break when ureq updates dependencies. This is not considered a breaking change +//! for ureq and will not be reflected by a major version bump. +//! +//! We strive to mark ureq's API with the word "unversioned" to identify places where this risk arises. +//! //! ## Minimum Supported Rust Version (MSRV) //! //! From time to time we will need to update our minimum supported Rust version (MSRV). This is not @@ -438,6 +471,7 @@ //! [`native-tls`]: https://crates.io/crates/native-tls //! [rustls-platform-verifier]: https://crates.io/crates/rustls-platform-verifier //! [webpki-roots]: https://crates.io/crates/webpki-roots +//! [`CryptoProvider`]: https://docs.rs/rustls/latest/rustls/crypto/struct.CryptoProvider.html #![forbid(unsafe_code)] #![warn(clippy::all)] diff --git a/src/tls/mod.rs b/src/tls/mod.rs index dd21cb18..9b12ecea 100644 --- a/src/tls/mod.rs +++ b/src/tls/mod.rs @@ -6,7 +6,7 @@ use std::sync::Arc; mod cert; pub use cert::{parse_pem, Certificate, PemItem, PrivateKey}; -#[cfg(feature = "rustls")] +#[cfg(feature = "_rustls")] pub(crate) mod rustls; #[cfg(feature = "native-tls")] @@ -43,7 +43,7 @@ impl TlsProvider { pub(crate) fn is_feature_enabled(&self) -> bool { match self { TlsProvider::Rustls => { - cfg!(feature = "rustls") + cfg!(feature = "_rustls") } TlsProvider::NativeTls => { cfg!(feature = "native-tls") @@ -70,6 +70,8 @@ pub struct TlsConfig { root_certs: RootCerts, use_sni: bool, disable_verification: bool, + #[cfg(feature = "_rustls")] + rustls_crypto_provider: Option>, } impl TlsConfig { @@ -120,6 +122,24 @@ impl TlsConfig { pub fn disable_verification(&self) -> bool { self.disable_verification } + + /// Specific `CryptoProvider` to use for `rustls`. + /// + /// # UNSTABLE API + /// + /// **NOTE: This API is not guaranteed for semver.** + /// + /// `rustls` is not (yet) semver 1.x and ureq can't promise that this API is upheld. + /// If `rustls` makes a breaking change regarding `CryptoProvider` their configuration, + /// or incompatible data types between rustls versions, ureq will _NOT_ bump a major version. + /// + /// ureq will update to the latest `rustls` minor version using ureq minor versions. + #[cfg(feature = "_rustls")] + pub fn unversioned_rustls_crypto_provider( + &self, + ) -> &Option> { + &self.rustls_crypto_provider + } } /// Builder of [`TlsConfig`] @@ -172,6 +192,66 @@ impl TlsConfigBuilder { self } + /// Specific `CryptoProvider` to use for `rustls`. + /// + /// # UNSTABLE API + /// + /// **NOTE: This API is not guaranteed for semver.** + /// + /// `rustls` is not (yet) semver 1.x and ureq can't promise that this API is upheld. + /// If `rustls` makes a breaking change regarding `CryptoProvider` their configuration, + /// or incompatible data types between rustls versions, ureq will _NOT_ bump a major version. + /// + /// ureq will update to the latest `rustls` minor version using ureq minor versions. + /// + /// # Feature flags + /// + /// This requires either feature **rustls** or **rustls-no-provider**, you probably + /// want the latter when configuring an explicit crypto provider since + /// **rustls** compiles with `ring`, while **rustls-no-provider** does not. + /// + /// # Example + /// + /// This example uses `aws-lc-rs` for the [`Agent`][crate::Agent]. The following + /// depdendencies would compile ureq without `ring` and only aws-lc-rs. + /// + /// * `Cargo.toml` + /// + /// ```text + /// ureq = { version = "3", default-features = false, features = ["rustls-no-provider"] } + /// rustls = { version = "0.23", features = ["aws-lc-rs"] } + /// ``` + /// + /// * Agent + /// + /// ``` + /// use std::sync::Arc; + /// use ureq::{Agent}; + /// use ureq::tls::{TlsConfig, TlsProvider}; + /// use rustls::crypto; + /// + /// let crypto = Arc::new(crypto::aws_lc_rs::default_provider()); + /// + /// let agent = Agent::config_builder() + /// .tls_config( + /// TlsConfig::builder() + /// // requires rustls or rustls-no-provider feature + /// .provider(TlsProvider::Rustls) + /// .unversioned_rustls_crypto_provider(crypto) + /// .build() + /// ) + /// .build() + /// .new_agent(); + /// ``` + #[cfg(feature = "_rustls")] + pub fn unversioned_rustls_crypto_provider( + mut self, + v: Arc<::rustls::crypto::CryptoProvider>, + ) -> Self { + self.config.rustls_crypto_provider = Some(v); + self + } + /// Finalize the config pub fn build(self) -> TlsConfig { self.config @@ -244,6 +324,7 @@ impl Default for TlsConfig { root_certs: RootCerts::WebPki, use_sni: true, disable_verification: false, + rustls_crypto_provider: None, } } } diff --git a/src/tls/rustls.rs b/src/tls/rustls.rs index 3ea88518..e18d89bc 100644 --- a/src/tls/rustls.rs +++ b/src/tls/rustls.rs @@ -5,6 +5,7 @@ use std::sync::Arc; use once_cell::sync::OnceCell; use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}; +use rustls::crypto::CryptoProvider; use rustls::{ClientConfig, ClientConnection, RootCertStore, StreamOwned, ALL_VERSIONS}; use rustls_pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs1KeyDer, PrivatePkcs8KeyDer}; use rustls_pki_types::{PrivateSec1KeyDer, ServerName}; @@ -90,11 +91,25 @@ impl Connector for RustlsConnector { } fn build_config(tls_config: &TlsConfig) -> Arc { - // Improve chances of ureq working out-of-the-box by not requiring the user - // to select a default crypto provider. - let provider = rustls::crypto::CryptoProvider::get_default() - .cloned() - .unwrap_or(Arc::new(rustls::crypto::ring::default_provider())); + // 1. Prefer provider set by TlsConfig. + // 2. Use process wide default set in rustls library. + // 3. Pick ring, if it is enabled (the default behavior). + // 4. Error (never pick up a default from feature flags alone). + let provider = tls_config + .rustls_crypto_provider + .clone() + .or(rustls::crypto::CryptoProvider::get_default().cloned()) + .unwrap_or(ring_if_enabled()); + + #[cfg(feature = "_ring")] + fn ring_if_enabled() -> Arc { + Arc::new(rustls::crypto::ring::default_provider()) + } + + #[cfg(not(feature = "_ring"))] + fn ring_if_enabled() -> Arc { + panic!("No CryptoProvider for Rustls. Enable the feature `ring` or configure the Agent."); + } let builder = ClientConfig::builder_with_provider(provider.clone()) .with_protocol_versions(ALL_VERSIONS) diff --git a/src/unversioned/transport/mod.rs b/src/unversioned/transport/mod.rs index d7f81a35..97d02fe8 100644 --- a/src/unversioned/transport/mod.rs +++ b/src/unversioned/transport/mod.rs @@ -328,7 +328,7 @@ impl Default for DefaultConnector { let inner = inner.chain(TcpConnector::default()); // If rustls is enabled, prefer that - #[cfg(feature = "rustls")] + #[cfg(feature = "_rustls")] let inner = inner.chain(RustlsConnector::default()); // Panic if the config calls for rustls, the uri scheme is https and that