Skip to content

Commit 0b39bb0

Browse files
authored
feat(tls): Add CA Certificate settings (#112)
1 parent db4e566 commit 0b39bb0

File tree

11 files changed

+250
-116
lines changed

11 files changed

+250
-116
lines changed

.gitignore

+1-2
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,4 @@ Cargo.lock
66
.direnv
77
result
88
curl
9-
.idea
10-
examples
9+
.idea

Cargo.toml

+4
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,10 @@ path = "examples/set_local_address.rs"
229229
name = "set_interface"
230230
path = "examples/set_interface.rs"
231231

232+
[[example]]
233+
name = "set_ca_cert"
234+
path = "examples/set_ca_cert.rs"
235+
232236
[[example]]
233237
name = "websocket"
234238
path = "examples/websocket.rs"

examples/data/root-ca.pem

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIDXTCCAkWgAwIBAgIJAOIvDiVb18eVMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
3+
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
4+
aWRnaXRzIFB0eSBMdGQwHhcNMTYwODE0MTY1NjExWhcNMjYwODEyMTY1NjExWjBF
5+
MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
6+
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
7+
CgKCAQEArVHWFn52Lbl1l59exduZntVSZyDYpzDND+S2LUcO6fRBWhV/1Kzox+2G
8+
ZptbuMGmfI3iAnb0CFT4uC3kBkQQlXonGATSVyaFTFR+jq/lc0SP+9Bd7SBXieIV
9+
eIXlY1TvlwIvj3Ntw9zX+scTA4SXxH6M0rKv9gTOub2vCMSHeF16X8DQr4XsZuQr
10+
7Cp7j1I4aqOJyap5JTl5ijmG8cnu0n+8UcRlBzy99dLWJG0AfI3VRJdWpGTNVZ92
11+
aFff3RpK3F/WI2gp3qV1ynRAKuvmncGC3LDvYfcc2dgsc1N6Ffq8GIrkgRob6eBc
12+
klDHp1d023Lwre+VaVDSo1//Y72UFwIDAQABo1AwTjAdBgNVHQ4EFgQUbNOlA6sN
13+
XyzJjYqciKeId7g3/ZowHwYDVR0jBBgwFoAUbNOlA6sNXyzJjYqciKeId7g3/Zow
14+
DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAVVaR5QWLZIRR4Dw6TSBn
15+
BQiLpBSXN6oAxdDw6n4PtwW6CzydaA+creiK6LfwEsiifUfQe9f+T+TBSpdIYtMv
16+
Z2H2tjlFX8VrjUFvPrvn5c28CuLI0foBgY8XGSkR2YMYzWw2jPEq3Th/KM5Catn3
17+
AFm3bGKWMtGPR4v+90chEN0jzaAmJYRrVUh9vea27bOCn31Nse6XXQPmSI6Gyncy
18+
OAPUsvPClF3IjeL1tmBotWqSGn1cYxLo+Lwjk22A9h6vjcNQRyZF2VLVvtwYrNU3
19+
mwJ6GCLsLHpwW/yjyvn8iEltnJvByM/eeRnfXV6WDObyiZsE/n6DxIRJodQzFqy9
20+
GA==
21+
-----END CERTIFICATE-----

examples/psk_impersonate.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use std::error::Error;
55
async fn main() -> Result<(), Box<dyn Error>> {
66
// Build a client to mimic Edge127
77
let client = rquest::Client::builder()
8-
.impersonate(Impersonate::SafariIos17_2)
8+
.impersonate(Impersonate::Edge127)
99
.enable_ech_grease()
1010
.permute_extensions()
1111
.build()?;

examples/set_ca_cert.rs

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
use rquest::tls::Impersonate;
2+
use std::error::Error;
3+
4+
#[tokio::main]
5+
async fn main() -> Result<(), Box<dyn Error>> {
6+
// Build a client to mimic Edge127
7+
let client = rquest::Client::builder()
8+
.impersonate(Impersonate::Edge127)
9+
.enable_ech_grease()
10+
.permute_extensions()
11+
.ca_cert_file("examples/data/root-ca.pem")
12+
.build()?;
13+
14+
// Use the API you're already familiar with
15+
let resp = client.get("https://tls.peet.ws/api/all").send().await?;
16+
println!("{}", resp.text().await?);
17+
18+
Ok(())
19+
}

src/async_impl/client.rs

+9-2
Original file line numberDiff line numberDiff line change
@@ -97,11 +97,11 @@ struct Config {
9797
cookie_store: Option<Arc<dyn cookie::CookieStore>>,
9898
hickory_dns: bool,
9999
error: Option<crate::Error>,
100-
https_only: bool,
101100
http_version_pref: HttpVersionPref,
102101
dns_overrides: HashMap<String, Vec<SocketAddr>>,
103102
dns_resolver: Option<Arc<dyn Resolve>>,
104103
builder: hyper::client::Builder,
104+
https_only: bool,
105105
#[cfg(feature = "boring-tls")]
106106
tls_info: bool,
107107
#[cfg(feature = "boring-tls")]
@@ -150,11 +150,11 @@ impl ClientBuilder {
150150
hickory_dns: cfg!(feature = "hickory-dns"),
151151
#[cfg(feature = "cookies")]
152152
cookie_store: None,
153-
https_only: false,
154153
http_version_pref: HttpVersionPref::All,
155154
dns_overrides: HashMap::new(),
156155
dns_resolver: None,
157156
builder: hyper::Client::builder(),
157+
https_only: false,
158158
#[cfg(feature = "boring-tls")]
159159
tls_info: false,
160160
#[cfg(feature = "boring-tls")]
@@ -1115,6 +1115,13 @@ impl ClientBuilder {
11151115
self
11161116
}
11171117

1118+
/// Set CA certificate file path.
1119+
#[cfg(feature = "boring-tls")]
1120+
pub fn ca_cert_file<P: AsRef<std::path::Path>>(mut self, ca_cert_file: P) -> ClientBuilder {
1121+
self.config.ssl_settings.ca_cert_file = Some(ca_cert_file.as_ref().to_path_buf());
1122+
self
1123+
}
1124+
11181125
/// Enables the [hickory-dns](hickory-dns) async resolver instead of a default threadpool using `getaddrinfo`.
11191126
///
11201127
/// If the `hickory-dns` feature is turned on, the default option is enabled.

src/blocking/client.rs

+42-2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ use crate::tls;
1919
#[cfg(feature = "boring-tls")]
2020
use crate::tls::Impersonate;
2121
use crate::{async_impl, header, redirect, IntoUrl, Method, Proxy};
22+
#[cfg(feature = "http2")]
23+
use hyper::{PseudoOrder, SettingsOrder, StreamDependency};
2224

2325
/// A `Client` to make Requests with.
2426
///
@@ -119,8 +121,8 @@ impl ClientBuilder {
119121

120122
/// Enable TLS pre_shared_key
121123
#[cfg_attr(docsrs, doc(cfg(feature = "boring-tls")))]
122-
pub fn pre_shared_key(self) -> ClientBuilder {
123-
self.with_inner(move |inner| inner.pre_shared_key())
124+
pub fn pre_shared_key(self, enable: bool) -> ClientBuilder {
125+
self.with_inner(move |inner| inner.pre_shared_key(enable))
124126
}
125127

126128
// Higher-level options
@@ -568,6 +570,38 @@ impl ClientBuilder {
568570
self.with_inner(|inner| inner.http2_header_table_size(sz))
569571
}
570572

573+
/// Sets the pseudo header order for HTTP2.
574+
/// This is an array of 4 elements, each element is a `PseudoOrder` enum.
575+
/// Default is `None`.
576+
#[cfg(feature = "http2")]
577+
pub fn http2_headers_pseudo_order(
578+
self,
579+
order: impl Into<Option<[PseudoOrder; 4]>>,
580+
) -> ClientBuilder {
581+
self.with_inner(|inner| inner.http2_headers_pseudo_order(order))
582+
}
583+
584+
/// Sets the priority for HTTP2 headers.
585+
/// Default is `None`.
586+
#[cfg(feature = "http2")]
587+
pub fn http2_headers_priority(
588+
self,
589+
priority: impl Into<Option<StreamDependency>>,
590+
) -> ClientBuilder {
591+
self.with_inner(|inner| inner.http2_headers_priority(priority))
592+
}
593+
594+
/// Sets the settings order for HTTP2.
595+
/// This is an array of 2 elements, each element is a `SettingsOrder` enum.
596+
/// Default is `None`.
597+
#[cfg(feature = "http2")]
598+
pub fn http2_settings_order(
599+
self,
600+
order: impl Into<Option<[SettingsOrder; 2]>>,
601+
) -> ClientBuilder {
602+
self.with_inner(|inner| inner.http2_settings_order(order))
603+
}
604+
571605
// TCP options
572606

573607
/// Set whether sockets have `TCP_NODELAY` enabled.
@@ -690,6 +724,12 @@ impl ClientBuilder {
690724
self.with_inner(|inner| inner.tls_info(tls_info))
691725
}
692726

727+
/// Set CA certificate file path.
728+
#[cfg(feature = "boring-tls")]
729+
pub fn ca_cert_file<P: AsRef<std::path::Path>>(self, path: P) -> ClientBuilder {
730+
self.with_inner(|inner| inner.ca_cert_file(path))
731+
}
732+
693733
/// Enables the [hickory-dns](hickory-dns) async resolver instead of a default threadpool using `getaddrinfo`.
694734
///
695735
/// If the `hickory-dns` feature is turned on, the default option is enabled.

src/tls/extension.rs

+17
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use boring::ssl::{
77
SslVerifyMode, SslVersion,
88
};
99
use foreign_types::ForeignTypeRef;
10+
use std::path::Path;
1011

1112
fn sv_handler(r: c_int) -> Result<c_int, ErrorStack> {
1213
if r == 0 {
@@ -62,6 +63,12 @@ pub trait SslExtension {
6263
self,
6364
cert_compression_alg: CertCompressionAlgorithm,
6465
) -> TlsResult<SslConnectorBuilder>;
66+
67+
/// Configure the ca certificate file for the given `SslConnectorBuilder`.
68+
fn configure_ca_cert_file<P: AsRef<Path>>(
69+
self,
70+
ca_cert_file: Option<P>,
71+
) -> TlsResult<SslConnectorBuilder>;
6572
}
6673

6774
/// Context Extension trait for `ConnectConfiguration`.
@@ -310,6 +317,16 @@ impl SslExtension for SslConnectorBuilder {
310317
.map(|_| self)
311318
}
312319
}
320+
321+
fn configure_ca_cert_file<P: AsRef<Path>>(
322+
mut self,
323+
ca_cert_file: Option<P>,
324+
) -> TlsResult<SslConnectorBuilder> {
325+
if let Some(file) = ca_cert_file {
326+
self.set_ca_file(file)?;
327+
}
328+
Ok(self)
329+
}
313330
}
314331

315332
impl SslConnectExtension for ConnectConfiguration {

src/tls/mod.rs

+16-107
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,8 @@ mod cert_compression;
1010
pub mod connector;
1111
pub mod extension;
1212
mod profile;
13+
mod settings;
1314

14-
pub(crate) use self::profile::tls_settings;
15-
use crate::async_impl::client::HttpVersionPref;
1615
use crate::connect::HttpConnector;
1716
use crate::tls::extension::{SslConnectExtension, SslExtension};
1817
use boring::ssl::{SslConnector, SslMethod};
@@ -21,112 +20,21 @@ use boring::{
2120
ssl::{ConnectConfiguration, SslConnectorBuilder},
2221
};
2322
use connector::{HttpsConnector, HttpsLayer, HttpsLayerSettings};
24-
use hyper::{PseudoOrder, SettingsOrder, StreamDependency};
23+
pub(crate) use profile::tls_settings;
2524
pub use profile::Impersonate;
2625
use profile::TypedImpersonate;
26+
27+
pub use settings::{Http2Settings, SslBuilderSettings, SslContextSettings, SslSettings};
2728
use std::any::Any;
2829
use std::fmt::{self, Debug};
2930

3031
type TlsResult<T> = std::result::Result<T, ErrorStack>;
3132

32-
/// The TLS connector configuration.
33-
#[derive(Clone)]
34-
pub struct SslSettings {
35-
/// The client to impersonate.
36-
pub impersonate: Impersonate,
37-
/// The minimum TLS version to use.
38-
pub min_tls_version: Option<Version>,
39-
/// The maximum TLS version to use.
40-
pub max_tls_version: Option<Version>,
41-
/// Enable ECH grease.
42-
pub enable_ech_grease: bool,
43-
/// Permute extensions.
44-
pub permute_extensions: bool,
45-
/// Verify certificates.
46-
pub certs_verification: bool,
47-
/// Use a pre-shared key.
48-
pub pre_shared_key: bool,
49-
/// The HTTP version preference.
50-
pub http_version_pref: HttpVersionPref,
51-
}
52-
53-
/// Connection settings
54-
pub struct SslBuilderSettings {
55-
/// The SSL connector builder.
56-
pub ssl_builder: SslConnectorBuilder,
57-
/// Enable PSK.
58-
pub enable_psk: bool,
59-
/// HTTP/2 settings.
60-
pub http2: Http2Settings,
61-
}
62-
63-
impl Debug for SslBuilderSettings {
64-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65-
f.debug_struct("TlsSettings")
66-
.field("tls_builder", &self.ssl_builder.type_id())
67-
.field("http2", &self.http2)
68-
.finish()
69-
}
70-
}
71-
72-
/// HTTP/2 settings.
73-
#[derive(Debug)]
74-
pub struct Http2Settings {
75-
/// The initial stream window size.
76-
pub initial_stream_window_size: Option<u32>,
77-
/// The initial connection window size.
78-
pub initial_connection_window_size: Option<u32>,
79-
/// The maximum concurrent streams.
80-
pub max_concurrent_streams: Option<u32>,
81-
/// The maximum header list size.
82-
pub max_header_list_size: Option<u32>,
83-
/// The header table size.
84-
pub header_table_size: Option<u32>,
85-
/// Enable push.
86-
pub enable_push: Option<bool>,
87-
/// The priority of the headers.
88-
pub headers_priority: Option<StreamDependency>,
89-
/// The pseudo header order.
90-
pub headers_pseudo_header: Option<[PseudoOrder; 4]>,
91-
/// The settings order.
92-
pub settings_order: Option<[SettingsOrder; 2]>,
93-
}
94-
95-
impl Default for SslSettings {
96-
fn default() -> Self {
97-
Self {
98-
min_tls_version: None,
99-
max_tls_version: None,
100-
impersonate: Default::default(),
101-
enable_ech_grease: false,
102-
permute_extensions: false,
103-
certs_verification: true,
104-
pre_shared_key: false,
105-
http_version_pref: HttpVersionPref::All,
106-
}
107-
}
108-
}
109-
110-
impl Debug for SslSettings {
111-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112-
f.debug_struct("TlsConnector")
113-
.field("min_tls_version", &self.min_tls_version)
114-
.field("max_tls_version", &self.max_tls_version)
115-
.field("impersonate", &self.impersonate)
116-
.field("enable_ech_grease", &self.enable_ech_grease)
117-
.field("permute_extensions", &self.permute_extensions)
118-
.field("certs_verification", &self.certs_verification)
119-
.field("pre_shared_key", &self.pre_shared_key)
120-
.field("http_version_pref", &self.http_version_pref)
121-
.finish()
122-
}
123-
}
124-
12533
/// A wrapper around a `SslConnectorBuilder` that allows for additional settings.
12634
#[derive(Clone)]
12735
pub struct TlsConnector {
128-
/// The TLS connector builder settings.
129-
settings: SslSettings,
36+
/// The TLS connector context settings.
37+
settings: SslContextSettings,
13038
/// The TLS connector layer.
13139
layer: HttpsLayer,
13240
}
@@ -139,7 +47,7 @@ impl TlsConnector {
13947
None => SslConnector::builder(SslMethod::tls_client())?,
14048
};
14149
Ok(Self {
142-
settings: settings.clone(),
50+
settings: SslContextSettings::from(&settings),
14351
layer: Self::build_layer(settings, ssl)?,
14452
})
14553
}
@@ -154,8 +62,8 @@ impl TlsConnector {
15462
let mut http = HttpsConnector::with_connector_layer(http, self.layer.clone());
15563

15664
// Set the callback to add application settings.
157-
let builder = self.settings.clone();
158-
http.set_callback(move |conf, _| configure_ssl_context(conf, &builder));
65+
let ctx = self.settings.clone();
66+
http.set_callback(move |conf, _| configure_ssl_context(conf, &ctx));
15967

16068
Ok(http)
16169
}
@@ -166,7 +74,8 @@ impl TlsConnector {
16674
.configure_alpn_protos(&settings.http_version_pref)?
16775
.configure_cert_verification(settings.certs_verification)?
16876
.configure_min_tls_version(settings.min_tls_version)?
169-
.configure_max_tls_version(settings.max_tls_version)?;
77+
.configure_max_tls_version(settings.max_tls_version)?
78+
.configure_ca_cert_file(settings.ca_cert_file.as_deref())?;
17079

17180
// Create the `HttpsLayerSettings` with the default session cache capacity.
17281
let settings = HttpsLayerSettings::builder()
@@ -178,11 +87,11 @@ impl TlsConnector {
17887
}
17988

18089
/// Add application settings to the given `ConnectConfiguration`.
181-
fn configure_ssl_context(conf: &mut ConnectConfiguration, ctx: &SslSettings) -> TlsResult<()> {
182-
if matches!(
183-
ctx.impersonate.profile(),
184-
TypedImpersonate::Chrome | TypedImpersonate::Edge
185-
) {
90+
fn configure_ssl_context(
91+
conf: &mut ConnectConfiguration,
92+
ctx: &SslContextSettings,
93+
) -> TlsResult<()> {
94+
if matches!(ctx.typed, TypedImpersonate::Chrome | TypedImpersonate::Edge) {
18695
conf.configure_permute_extensions(ctx.permute_extensions)?
18796
.configure_enable_ech_grease(ctx.enable_ech_grease)?
18897
.configure_add_application_settings(ctx.http_version_pref)?;

0 commit comments

Comments
 (0)