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

Restore support for native-tls #389

Closed
wants to merge 10 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -48,6 +48,8 @@ jobs:
tls:
- ""
- tls
- native-tls
- "tls native-tls"
feature:
- ""
- json
Expand Down
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ 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"]
Expand All @@ -39,6 +39,7 @@ serde_json = { version = "1", optional = true }
encoding_rs = { version = "0.8", optional = true }
cookie_store = { version = "0.13.0", optional = true, default-features = false, features = ["preserve_order"] }
log = "0.4.11"
native-tls = { version = "0.2.7", optional = true }

[dev-dependencies]
serde = { version = "1", features = ["derive"] }
Expand Down
30 changes: 29 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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](https://docs.rs/native-tls/).

Version 2.0.0 was released recently and changed some APIs. See the [changelog] for details.

Expand Down Expand Up @@ -211,6 +211,34 @@ fn proxy_example_2() -> std::result::Result<(), ureq::Error> {
}
```

## HTTPS / TLS / SSL

By default, ureq uses rustls. You can add native-tls support by enableing the native-tls feature
and calling AgentBuilder::tls_connector():

```rust
use ureq::Agent;

let agent = ureq::AgentBuilder::new()
.tls_connector(native_tls::TlsConnector::new())
.build();
```

You might want to use native-tls if rustls is not supported on your platform, or if you need to
interoperate with servers that only support less-secure ciphersuites and/or TLS versions older
than 1.2. You can turn off TLS support entirely with `--no-default-features`.

### Trusted Roots

When you use rustls, 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
Expand Down
5 changes: 3 additions & 2 deletions examples/cureq/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ use rustls::{
Certificate, ClientConfig, RootCertStore, ServerCertVerified, ServerCertVerifier, TLSError,
};
use ureq;
use webpki::DNSNameRef;

#[derive(Debug)]
struct StringError(String);
Expand Down Expand Up @@ -97,12 +96,13 @@ fn perform(

struct AcceptAll {}

#[cfg(feature = "tls")]
impl ServerCertVerifier for AcceptAll {
fn verify_server_cert(
&self,
_roots: &RootCertStore,
_presented_certs: &[Certificate],
_dns_name: DNSNameRef<'_>,
_dns_name: webpki::DNSNameRef<'_>,
_ocsp_response: &[u8],
) -> Result<ServerCertVerified, TLSError> {
Ok(ServerCertVerified::assertion())
Expand Down Expand Up @@ -157,6 +157,7 @@ Fetch url and copy it to stdout.
let wait_seconds: u64 = wait_string.parse().expect("invalid --wait flag");
wait = Duration::from_secs(wait_seconds);
}
#[cfg(feature = "tls")]
"-k" => {
let mut client_config = ClientConfig::new();
client_config
Expand Down
39 changes: 32 additions & 7 deletions src/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ pub(crate) struct AgentConfig {
pub timeout: Option<Duration>,
pub redirects: u32,
pub user_agent: String,
#[cfg(feature = "tls")]
pub tls_config: Option<TLSClientConfig>,
}

Expand Down Expand Up @@ -210,7 +209,6 @@ impl AgentBuilder {
timeout: None,
redirects: 5,
user_agent: format!("ureq/{}", env!("CARGO_PKG_VERSION")),
#[cfg(feature = "tls")]
tls_config: None,
},
max_idle_connections: DEFAULT_MAX_IDLE_CONNECTIONS,
Expand Down Expand Up @@ -469,7 +467,9 @@ impl AgentBuilder {
self
}

/// Set the TLS client config to use for the connection. See [`ClientConfig`](https://docs.rs/rustls/latest/rustls/struct.ClientConfig.html).
/// Use rustls for connections from this agent and set the
/// [`rustls::ClientConfig`](https://docs.rs/rustls/0.19.1/rustls/struct.ClientConfig.html)
/// to use. Overrides any previous calls to [AgentBuilder::tls_connector]
///
/// Example:
/// ```
Expand All @@ -485,7 +485,29 @@ impl AgentBuilder {
/// ```
#[cfg(feature = "tls")]
pub fn tls_config(mut self, tls_config: Arc<rustls::ClientConfig>) -> Self {
self.config.tls_config = Some(TLSClientConfig(tls_config));
self.config.tls_config = Some(TLSClientConfig::Rustls(tls_config));
self
}

/// Use native-tls for connections from this agent and set the
/// [`native_tls::TlsConnector`](https://docs.rs/native-tls/0.2.7/native_tls/struct.TlsConnector.html)
/// to use. Overrides any previous calls to [AgentBuilder::tls_config].
///
/// Example:
/// ```
/// # fn main() -> Result<(), ureq::Error> {
/// # ureq::is_test(true);
/// use std::sync::Arc;
/// let tls_connector = Arc::new(native_tls::TlsConnector::new().unwrap());
/// let agent = ureq::builder()
/// .tls_connector(tls_connector.clone())
/// .build();
/// # Ok(())
/// # }
/// ```
#[cfg(feature = "native-tls")]
pub fn tls_connector(mut self, connector: Arc<native_tls::TlsConnector>) -> Self {
self.config.tls_config = Some(TLSClientConfig::Native(connector));
self
}

Expand Down Expand Up @@ -522,11 +544,14 @@ impl AgentBuilder {
}
}

#[cfg(feature = "tls")]
#[derive(Clone)]
pub(crate) struct TLSClientConfig(pub(crate) Arc<rustls::ClientConfig>);
pub(crate) enum TLSClientConfig {
#[cfg(feature = "tls")]
Rustls(Arc<rustls::ClientConfig>),
#[cfg(feature = "native-tls")]
Native(Arc<native_tls::TlsConnector>),
}

#[cfg(feature = "tls")]
impl std::fmt::Debug for TLSClientConfig {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TLSClientConfig").finish()
Expand Down
2 changes: 1 addition & 1 deletion src/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ impl fmt::Display for HeaderLine {

#[derive(Clone, PartialEq)]
/// Wrapper type for a header field.
/// https://tools.ietf.org/html/rfc7230#section-3.2
/// <https://tools.ietf.org/html/rfc7230#section-3.2>
pub struct Header {
// Line contains the unmodified bytes of single header field.
// It does not contain the final CRLF.
Expand Down
37 changes: 36 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
//! 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](https://docs.rs/native-tls/).
//!
//! Version 2.0.0 was released recently and changed some APIs. See the [changelog] for details.
//!
Expand Down Expand Up @@ -234,6 +234,41 @@
//! # fn main() {}
//! ```
//!
//! # HTTPS / TLS / SSL
//!
//! By default, ureq uses rustls. You can add native-tls support by enabling the native-tls feature
//! and calling AgentBuilder::tls_connector():
//!
//! ```no_run
//! # #[cfg(feature = "native-tls")]
//! # fn build() -> std::result::Result<(), ureq::Error> {
//! # ureq::is_test(true);
//! use std::sync::Arc;
//! use ureq::Agent;
//!
//! let agent = ureq::AgentBuilder::new()
//! .tls_connector(Arc::new(native_tls::TlsConnector::new()))
//! .build();
//! # Ok(())
//! # }
//! # fn main() {}
//! ```
//!
//! You might want to use native-tls if rustls is not supported on your platform, or if you need to
//! interoperate with servers that only support less-secure ciphersuites and/or TLS versions older
//! than 1.2. You can turn off TLS support entirely with `--no-default-features`.
//!
//! ## Trusted Roots
//!
//! When you use rustls, 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
Expand Down
4 changes: 2 additions & 2 deletions src/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -491,8 +491,8 @@ impl Response {
}

#[cfg(test)]
pub fn to_write_vec(self) -> Vec<u8> {
self.stream.to_write_vec()
pub fn as_write_vec(&self) -> &[u8] {
self.stream.as_write_vec()
}

#[cfg(test)]
Expand Down
Loading