Skip to content
Draft
Show file tree
Hide file tree
Changes from 9 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
12 changes: 7 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,17 @@ default = ["http2", "native-tls", "static-curl", "text-decoding"]
cookies = ["httpdate"]
http2 = ["curl/http2"]
json = ["serde", "serde_json"]
native-tls = ["curl/ssl", "curl-sys/ssl"]
native-tls = ["tls", "curl/ssl", "curl-sys/ssl"]
nightly = []
online-tests = []
psl = ["httpdate", "parking_lot", "publicsuffix"]
rustls-tls = ["rustls-ffi", "curl/rustls", "curl/static-curl"]
rustls-tls = ["rustls-ffi", "static-curl", "tls", "curl/rustls"]
rustls-tls-native-certs = ["rustls-tls", "data-encoding", "rustls-native-certs"]
spnego = ["curl-sys/spnego"]
static-curl = ["curl/static-curl"]
static-ssl = ["curl/static-ssl"]
static-ssl = ["curl/static-ssl"] # todo
text-decoding = ["encoding_rs", "mime"]
tls = []
unstable-interceptors = []

[dependencies]
Expand All @@ -50,11 +52,11 @@ url = "2.1"
waker-fn = "1"

[dependencies.curl]
version = "0.4.43"
version = "0.4.44"
default-features = false

[dependencies.curl-sys]
version = "0.4.55"
version = "0.4.56"
default-features = false

[dependencies.data-encoding]
Expand Down
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,6 @@ The Isahc logo and related assets are licensed under a [Creative Commons Attribu
[multi interface]: https://curl.haxx.se/libcurl/c/libcurl-multi.html
[rfc4559]: https://tools.ietf.org/html/rfc4559
[rust]: https://www.rustlang.org
[rustls]: https://github.com/ctz/rustls
[serde]: https://serde.rs
[Surf]: https://github.com/http-rs/surf
[ureq]: https://github.com/algesten/ureq
31 changes: 25 additions & 6 deletions examples/badssl.rs
Original file line number Diff line number Diff line change
@@ -1,36 +1,55 @@
//! This example contains a number of manual tests against badssl.com
//! demonstrating several dangerous SSL/TLS options.

use isahc::{config::SslOption, prelude::*, Request};
use isahc::{error::ErrorKind, prelude::*, tls::TlsConfig, Request};

fn main() {
println!("ssl: {:?}", curl::Version::get().ssl_version());

// accept expired cert
Request::get("https://expired.badssl.com")
.ssl_options(SslOption::DANGER_ACCEPT_INVALID_CERTS)
.tls_config(
TlsConfig::builder()
.danger_accept_invalid_certs(true)
.build(),
)
.body(())
.unwrap()
.send()
.expect("cert should have been accepted");

// accepting invalid certs alone does not allow invalid hosts
Request::get("https://wrong.host.badssl.com")
.ssl_options(SslOption::DANGER_ACCEPT_INVALID_CERTS)
let error = Request::get("https://wrong.host.badssl.com")
.tls_config(
TlsConfig::builder()
.danger_accept_invalid_certs(true)
.build(),
)
.body(())
.unwrap()
.send()
.expect_err("cert should have been rejected");
assert_eq!(error, ErrorKind::BadServerCertificate);

// accept cert with wrong host
Request::get("https://wrong.host.badssl.com")
.ssl_options(SslOption::DANGER_ACCEPT_INVALID_HOSTS)
.tls_config(
TlsConfig::builder()
.danger_accept_invalid_hosts(true)
.build(),
)
.body(())
.unwrap()
.send()
.expect("cert should have been accepted");

// accepting certs with wrong host alone does not allow invalid certs
Request::get("https://expired.badssl.com")
.ssl_options(SslOption::DANGER_ACCEPT_INVALID_HOSTS)
.tls_config(
TlsConfig::builder()
.danger_accept_invalid_hosts(true)
.build(),
)
.body(())
.unwrap()
.send()
Expand Down
18 changes: 9 additions & 9 deletions src/auth.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Types for working with HTTP authentication methods.

use crate::config::{proxy::Proxy, request::SetOpt};
use crate::config::{proxy::SetOptProxy, request::SetOpt};
use std::{
fmt,
ops::{BitOr, BitOrAssign},
Expand Down Expand Up @@ -31,10 +31,10 @@ impl SetOpt for Credentials {
}
}

impl SetOpt for Proxy<Credentials> {
fn set_opt<H>(&self, easy: &mut curl::easy::Easy2<H>) -> Result<(), curl::Error> {
easy.proxy_username(&self.0.username)?;
easy.proxy_password(&self.0.password)
impl SetOptProxy for Credentials {
fn set_opt_proxy<H>(&self, easy: &mut curl::easy::Easy2<H>) -> Result<(), curl::Error> {
easy.proxy_username(&self.username)?;
easy.proxy_password(&self.password)
}
}

Expand Down Expand Up @@ -173,19 +173,19 @@ impl SetOpt for Authentication {
}
}

impl SetOpt for Proxy<Authentication> {
fn set_opt<H>(&self, easy: &mut curl::easy::Easy2<H>) -> Result<(), curl::Error> {
impl SetOptProxy for Authentication {
fn set_opt_proxy<H>(&self, easy: &mut curl::easy::Easy2<H>) -> Result<(), curl::Error> {
#[cfg(feature = "spnego")]
{
if self.0.contains(Authentication::negotiate()) {
if self.contains(Authentication::negotiate()) {
// Ensure auth engine is enabled, even though credentials do not
// need to be specified.
easy.proxy_username("")?;
easy.proxy_password("")?;
}
}

easy.proxy_auth(&self.0.as_auth())
easy.proxy_auth(&self.as_auth())
}
}

Expand Down
156 changes: 73 additions & 83 deletions src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
// to update the client code to apply the option when configuring an easy
// handle.

use self::{proxy::Proxy, request::SetOpt};
use self::request::SetOpt;
use crate::{
auth::{Authentication, Credentials},
is_http_version_supported,
Expand All @@ -27,12 +27,10 @@ pub(crate) mod dns;
pub(crate) mod proxy;
pub(crate) mod redirect;
pub(crate) mod request;
pub(crate) mod tls;

pub use dial::{Dialer, DialerParseError};
pub use dns::{DnsCache, ResolveMap};
pub use redirect::RedirectPolicy;
pub use tls::{CaCertificate, ClientCertificate, PrivateKey, SslOption};

/// Provides additional methods when building a request for configuring various
/// execution-related options on how the request should be sent.
Expand Down Expand Up @@ -537,7 +535,7 @@ pub trait Configurable: request::WithRequestConfig {
#[must_use = "builders have no effect if unused"]
fn proxy_authentication(self, authentication: Authentication) -> Self {
self.with_config(move |config| {
config.proxy_authentication = Some(Proxy(authentication));
config.proxy_authentication = Some(authentication);
})
}

Expand All @@ -549,7 +547,7 @@ pub trait Configurable: request::WithRequestConfig {
#[must_use = "builders have no effect if unused"]
fn proxy_credentials(self, credentials: Credentials) -> Self {
self.with_config(move |config| {
config.proxy_credentials = Some(Proxy(credentials));
config.proxy_credentials = Some(credentials);
})
}

Expand All @@ -573,108 +571,106 @@ pub trait Configurable: request::WithRequestConfig {
})
}

/// Set a custom SSL/TLS client certificate to use for client connections.
/// Set various options for this request that control SSL/TLS behavior.
///
/// Most options are for disabling security checks that introduce security
/// risks, but may be required as a last resort. Note that the most secure
/// options are typically the default and do not need to be specified.
///
/// If a format is not supported by the underlying SSL/TLS engine, an error
/// will be returned when attempting to send a request using the offending
/// certificate.
/// The default value is [`TlsConfig::default`].
///
/// The default value is none.
/// # Warning
///
/// You should think very carefully before using this method. Using *any*
/// options that alter how certificates are validated can introduce
/// significant security vulnerabilities.
///
/// # Examples
///
/// ```no_run
/// use isahc::{
/// config::{ClientCertificate, PrivateKey},
/// prelude::*,
/// Request,
/// };
/// use isahc::{prelude::*, tls::TlsConfig, Request};
///
/// let response = Request::get("localhost:3999")
/// .ssl_client_certificate(ClientCertificate::pem_file(
/// "client.pem",
/// PrivateKey::pem_file("key.pem", String::from("secret")),
/// ))
/// let response = Request::get("https://badssl.com")
/// .tls_config(TlsConfig::builder()
/// .danger_accept_invalid_certs(true)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking about adding enable alike API:

.danger_accept_invalid_certs()

Or add API like accept_invalid_certs() but under inside feature insecure_tls?

/// .danger_accept_revoked_certs(true)
/// .build())
/// .body(())?
/// .send()?;
/// # Ok::<(), isahc::Error>(())
/// ```
///
/// ```
/// use isahc::{
/// config::{ClientCertificate, PrivateKey},
/// prelude::*,
/// HttpClient,
/// };
/// use isahc::{prelude::*, tls::TlsConfig, HttpClient};
///
/// let client = HttpClient::builder()
/// .ssl_client_certificate(ClientCertificate::pem_file(
/// "client.pem",
/// PrivateKey::pem_file("key.pem", String::from("secret")),
/// ))
/// .tls_config(TlsConfig::builder()
/// .danger_accept_invalid_certs(true)
/// .danger_accept_revoked_certs(true)
/// .build())
/// .build()?;
/// # Ok::<(), isahc::Error>(())
/// ```
#[cfg(feature = "tls")]
#[must_use = "builders have no effect if unused"]
fn ssl_client_certificate(self, certificate: ClientCertificate) -> Self {
fn tls_config(self, tls_config: crate::tls::TlsConfig) -> Self {
self.with_config(move |config| {
config.ssl_client_certificate = Some(certificate);
config.tls_config = Some(tls_config);
})
}

/// Set a custom SSL/TLS CA certificate bundle to use for client
/// connections.
/// Add a custom client certificate to use for client authentication, also
/// known as *mutual TLS*.
///
/// SSL/TLS is often used by the client to verify that the server is
/// legitimate (one-way), but with *mutual TLS* (mTLS) it is _also_ used by
/// the server to verify that _you_ are legitimate (two-way). If the server
/// asks the client to present an approved certificate before continuing,
/// then this sets the certificate chain that will be used to prove
/// authenticity.
///
/// If a certificate or key format given is not supported by the underlying
/// SSL/TLS engine, an error will be returned when attempting to send a
/// request using the offending certificate or key.
///
/// The default value is none.
/// By default, no client certificate is set.
///
/// # Notes
/// # Backend support
///
/// On Windows it may be necessary to combine this with
/// [`SslOption::DANGER_ACCEPT_REVOKED_CERTS`] in order to work depending on
/// the contents of your CA bundle.
/// Support for mutual TLS varies between the available TLS backends. Here
/// are some current limitations of note:
///
/// - Schannel and Secure Transport require certificates and private keys to
/// be presented together inside a PKCS #12 archive. This can be an actual
/// archive or one in memory.
/// - Mutual TLS with Rustls is not supported at all.
///
/// # Examples
///
/// ```
/// use isahc::{config::CaCertificate, prelude::*, HttpClient};
/// ```no_run
/// use isahc::tls::{Identity, PrivateKey, TlsConfig};
///
/// let client = HttpClient::builder()
/// .ssl_ca_certificate(CaCertificate::file("ca.pem"))
/// .build()?;
/// let config = TlsConfig::builder()
/// .identity(Identity::from_pem_file(
/// "client.pem",
/// PrivateKey::pem_file("key.pem", String::from("secret"))
/// ))
/// .build();
/// # Ok::<(), isahc::Error>(())
/// ```
#[cfg(feature = "tls")]
#[must_use = "builders have no effect if unused"]
fn ssl_ca_certificate(self, certificate: CaCertificate) -> Self {
fn tls_identity(self, identity: crate::tls::Identity) -> Self {
self.with_config(move |config| {
config.ssl_ca_certificate = Some(certificate);
config.identity = Some(identity);
})
}

/// Set a list of ciphers to use for SSL/TLS connections.
/// Set various options that control SSL/TLS behavior for a proxy.
///
/// The list of valid cipher names is dependent on the underlying SSL/TLS
/// engine in use. You can find an up-to-date list of potential cipher names
/// at <https://curl.haxx.se/docs/ssl-ciphers.html>.
///
/// The default is unset and will result in the system defaults being used.
#[must_use = "builders have no effect if unused"]
fn ssl_ciphers<I, T>(self, ciphers: I) -> Self
where
I: IntoIterator<Item = T>,
T: Into<String>,
{
self.with_config(move |config| {
config.ssl_ciphers = Some(ciphers.into_iter().map(T::into).collect());
})
}

/// Set various options for this request that control SSL/TLS behavior.
///
/// Most options are for disabling security checks that introduce security
/// risks, but may be required as a last resort. Note that the most secure
/// options are already the default and do not need to be specified.
///
/// The default value is [`SslOption::NONE`].
/// By default, the same TLS configuration is used for validating all
/// SSL/TLS connections, but this method allows you to use separate
/// configuration specifically for proxy server connections.
///
/// # Warning
///
Expand All @@ -684,28 +680,22 @@ pub trait Configurable: request::WithRequestConfig {
///
/// # Examples
///
/// ```no_run
/// use isahc::{config::SslOption, prelude::*, Request};
///
/// let response = Request::get("https://badssl.com")
/// .ssl_options(SslOption::DANGER_ACCEPT_INVALID_CERTS | SslOption::DANGER_ACCEPT_REVOKED_CERTS)
/// .body(())?
/// .send()?;
/// # Ok::<(), isahc::Error>(())
/// ```
///
/// ```
/// use isahc::{config::SslOption, prelude::*, HttpClient};
/// use isahc::{prelude::*, tls::TlsConfig, HttpClient};
///
/// let client = HttpClient::builder()
/// .ssl_options(SslOption::DANGER_ACCEPT_INVALID_CERTS | SslOption::DANGER_ACCEPT_REVOKED_CERTS)
/// .proxy_tls_config(TlsConfig::builder()
/// .danger_accept_invalid_certs(true)
/// .danger_accept_revoked_certs(true)
/// .build())
/// .build()?;
/// # Ok::<(), isahc::Error>(())
/// ```
#[cfg(feature = "tls")]
#[must_use = "builders have no effect if unused"]
fn ssl_options(self, options: SslOption) -> Self {
fn proxy_tls_config(self, tls_config: crate::tls::TlsConfig) -> Self {
self.with_config(move |config| {
config.ssl_options = Some(options);
config.proxy_tls_config = Some(tls_config);
})
}

Expand Down
Loading