diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 38b2618..2a242ed 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -6,7 +6,7 @@ jobs: lint: name: Lint runs-on: ubuntu-latest - env: + env: RUSTFLAGS: -D warnings steps: - uses: actions/checkout@v2 @@ -39,7 +39,7 @@ jobs: with: command: doc # Keep in sync with Cargo.toml's [package.metadata.docs.rs] - args: --no-default-features --no-deps --features "tls json charset cookies socks-proxy" + args: --no-default-features --no-deps --features "tls native-tls json charset cookies socks-proxy" build_and_test: name: Test runs-on: ubuntu-latest @@ -47,7 +47,8 @@ jobs: matrix: tls: - "" - - tls + - "tls" + - "native-tls" feature: - "" - json @@ -69,4 +70,4 @@ jobs: uses: actions-rs/cargo@v1 with: command: test - args: --no-default-features --features "${{ matrix.tls }} ${{ matrix.feature }}" + args: --no-default-features --features "${{ matrix.tls }}" "${{ matrix.feature }}" diff --git a/Cargo.toml b/Cargo.toml index 27c8d6e..b820382 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,14 +12,14 @@ edition = "2018" [package.metadata.docs.rs] # Keep in sync with .github/workflows/test.yml -features = [ "tls", "json", "charset", "cookies", "socks-proxy" ] +features = ["tls", "native-tls", "json", "charset", "cookies", "socks-proxy"] [features] default = ["tls"] +tls = ["webpki", "webpki-roots", "rustls"] +native-certs = ["rustls-native-certs"] json = ["serde", "serde_json"] charset = ["encoding_rs"] -tls = ["rustls", "webpki", "webpki-roots"] -native-certs = ["rustls-native-certs"] cookies = ["cookie", "cookie_store"] socks-proxy = ["socks"] @@ -30,15 +30,16 @@ cookie = { version = "0.15", default-features = false, optional = true} once_cell = "1" url = "2" socks = { version = "0.3", optional = true } -rustls = { version = "0.20.1", optional = true } -webpki = { version = "0.22", optional = true } -webpki-roots = { version = "0.22", optional = true } -rustls-native-certs = { version = "0.6", optional = true } serde = { version = "1", optional = true } serde_json = { version = "1", optional = true } encoding_rs = { version = "0.8", optional = true } cookie_store = { version = "0.15", optional = true, default-features = false, features = ["preserve_order"] } log = "0.4" +webpki = { version = "0.22", optional = true } +webpki-roots = { version = "0.22", optional = true } +rustls = { version = "0.20", optional = true } +rustls-native-certs = { version = "0.6", optional = true } +native-tls = { version = "0.2", optional = true } [dev-dependencies] serde = { version = "1", features = ["derive"] } @@ -51,4 +52,4 @@ name = "smoke-test" [[example]] name = "cureq" -required-features = ["charset", "cookies", "socks-proxy"] +required-features = ["charset", "cookies", "socks-proxy", "native-tls"] diff --git a/README.md b/README.md index c742d5a..aaa007a 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ HTTPS, and charset decoding. Ureq is in pure Rust for safety and ease of understanding. It avoids using `unsafe` directly. It [uses blocking I/O][blocking] instead of async I/O, because that keeps the API simple and and keeps dependencies to a minimum. For TLS, ureq uses -[rustls]. +[rustls or native-tls](#tls). Version 2.0.0 was released recently and changed some APIs. See the [changelog] for details. @@ -101,12 +101,18 @@ You can control them when including ureq as a dependency. `ureq = { version = "*", features = ["json", "charset"] }` * `tls` enables https. This is enabled by default. +* `native-certs` makes the default TLS implementation use the OS' trust store (see TLS doc below). * `cookies` enables cookies. * `json` enables [Response::into_json()] and [Request::send_json()] via serde_json. * `charset` enables interpreting the charset part of the Content-Type header (e.g. `Content-Type: text/plain; charset=iso-8859-1`). Without this, the library defaults to Rust's built in `utf-8`. * `socks-proxy` enables proxy config using the `socks4://`, `socks4a://`, `socks5://` and `socks://` (equal to `socks5://`) prefix. +* `native-tls` enables an adapter so you can pass a `native_tls::TlsConnector` instance + to `AgentBuilder::tls_connector`. 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 on the agent. The `native-certs` feature + does nothing for `native-tls`. ## Plain requests @@ -211,6 +217,40 @@ fn proxy_example_2() -> std::result::Result<(), ureq::Error> { } ``` +## HTTPS / TLS / SSL + +On platforms that support rustls, ureq uses rustls. On other platforms, native-tls can +be manually configured using [`AgentBuilder::tls_connector`]. + +You might want to use native-tls if you need to interoperate with servers that +only support less-secure TLS configurations (rustls doesn't support TLS 1.0 and 1.1, for +instance). You might also want to use it if you need to validate certificates for IP addresses, +which are not currently supported in rustls. + +Here's an example of constructing an Agent that uses native-tls. It requires the +"native-tls" feature to be enabled. + +```rust + use std::sync::Arc; + use ureq::Agent; + + let agent = ureq::AgentBuilder::new() + .tls_connector(Arc::new(native_tls::TlsConnector::new().unwrap())) + .build(); +``` + +### Trusted Roots + +When you use rustls (`tls` feature), ureq defaults to trusting +[webpki-roots](https://docs.rs/webpki-roots/), a +copy of the Mozilla Root program that is bundled into your program (and so won't update if your +program isn't updated). You can alternately configure +[rustls-native-certs](https://docs.rs/rustls-native-certs/) which extracts the roots from your +OS' trust store. That means it will update when your OS is updated, and also that it will +include locally installed roots. + +When you use `native-tls`, ureq will use your OS' certificate verifier and root store. + ## Blocking I/O for simplicity Ureq uses blocking I/O rather than Rust's newer [asynchronous (async) I/O][async]. Async I/O diff --git a/examples/cureq/main.rs b/examples/cureq/main.rs index 24186df..3d584bc 100644 --- a/examples/cureq/main.rs +++ b/examples/cureq/main.rs @@ -3,13 +3,14 @@ use std::fmt; use std::io; use std::thread; use std::time::Duration; +use std::time::SystemTime; use std::{env, sync::Arc}; -use rustls::{ - Certificate, ClientConfig, RootCertStore, ServerCertVerified, ServerCertVerifier, TLSError, -}; +use rustls::client::ServerCertVerified; +use rustls::client::ServerCertVerifier; +use rustls::ServerName; +use rustls::{Certificate, ClientConfig}; use ureq; -use webpki::DNSNameRef; #[derive(Debug)] struct StringError(String); @@ -100,11 +101,13 @@ struct AcceptAll {} impl ServerCertVerifier for AcceptAll { fn verify_server_cert( &self, - _roots: &RootCertStore, - _presented_certs: &[Certificate], - _dns_name: DNSNameRef<'_>, + _end_entity: &Certificate, + _intermediates: &[Certificate], + _server_name: &ServerName, + _scts: &mut dyn Iterator, _ocsp_response: &[u8], - ) -> Result { + _now: SystemTime, + ) -> Result { Ok(ServerCertVerified::assertion()) } } @@ -132,6 +135,7 @@ fn main2() -> Result<(), Error> { -k Ignore certificate errors -m