cloud_sdk_reqwest/blocking/
config.rs1use core::fmt;
2
3#[cfg(any(
4 feature = "blocking-rustls",
5 feature = "blocking-rustls-fips",
6 feature = "blocking-rustls-webpki-roots"
7))]
8use rustls::ClientConfig;
9#[cfg(any(
10 feature = "blocking-rustls-fips",
11 feature = "blocking-rustls-webpki-roots"
12))]
13use rustls::RootCertStore;
14#[cfg(feature = "blocking-rustls-fips")]
15use rustls::client::WebPkiServerVerifier;
16#[cfg(feature = "blocking-rustls-fips")]
17use rustls::crypto::CryptoProvider;
18#[cfg(feature = "blocking-rustls-fips")]
19use rustls::pki_types::CertificateRevocationListDer;
20#[cfg(any(
21 feature = "blocking-rustls-fips",
22 feature = "blocking-rustls-webpki-roots"
23))]
24use std::sync::Arc;
25#[cfg(feature = "blocking-rustls-fips")]
26use std::vec::Vec;
27
28#[cfg(all(
29 not(feature = "blocking-rustls-fips"),
30 not(feature = "blocking-rustls-webpki-roots")
31))]
32use crate::shared::platform_client_config;
33use crate::shared::{
34 BearerCredential, BuildError, HttpsEndpoint, RawHyperClient, RequestTimeouts, UserAgent,
35};
36
37use super::{BlockingClient, RawBlockingClient};
38
39#[cfg(feature = "blocking-rustls-fips")]
41pub struct FipsTlsPolicy {
42 roots: Arc<RootCertStore>,
43 crls: Vec<CertificateRevocationListDer<'static>>,
44}
45
46#[cfg(feature = "blocking-rustls-fips")]
47impl FipsTlsPolicy {
48 pub fn new(
51 roots: RootCertStore,
52 crls: Vec<CertificateRevocationListDer<'static>>,
53 ) -> Result<Self, BuildError> {
54 if roots.is_empty() {
55 return Err(BuildError::FipsTrustRootsRequired);
56 }
57 if crls.is_empty() {
58 return Err(BuildError::FipsCertificateRevocationListsRequired);
59 }
60 Ok(Self {
61 roots: Arc::new(roots),
62 crls,
63 })
64 }
65}
66
67#[cfg(feature = "blocking-rustls-fips")]
68impl fmt::Debug for FipsTlsPolicy {
69 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
70 formatter
71 .debug_struct("FipsTlsPolicy")
72 .field("trust_anchors", &self.roots.len())
73 .field("crls", &self.crls.len())
74 .finish_non_exhaustive()
75 }
76}
77
78pub struct BlockingClientBuilder {
81 endpoint: HttpsEndpoint,
82 credential: BearerCredential,
83 user_agent: UserAgent,
84 timeouts: RequestTimeouts,
85 #[cfg(feature = "blocking-rustls-fips")]
86 fips_tls_policy: Option<FipsTlsPolicy>,
87}
88
89pub struct RawBlockingClientBuilder {
91 endpoint: HttpsEndpoint,
92 user_agent: UserAgent,
93 timeouts: RequestTimeouts,
94 #[cfg(feature = "blocking-rustls-fips")]
95 fips_tls_policy: Option<FipsTlsPolicy>,
96}
97
98impl BlockingClientBuilder {
99 #[must_use]
101 pub const fn new(
102 endpoint: HttpsEndpoint,
103 credential: BearerCredential,
104 user_agent: UserAgent,
105 timeouts: RequestTimeouts,
106 ) -> Self {
107 Self {
108 endpoint,
109 credential,
110 user_agent,
111 timeouts,
112 #[cfg(feature = "blocking-rustls-fips")]
113 fips_tls_policy: None,
114 }
115 }
116
117 #[cfg(feature = "blocking-rustls-fips")]
119 #[must_use]
120 pub fn with_fips_tls_policy(mut self, policy: FipsTlsPolicy) -> Self {
121 self.fips_tls_policy = Some(policy);
122 self
123 }
124
125 pub fn build(self) -> Result<BlockingClient, BuildError> {
127 self.build_inner(true)
128 }
129
130 fn build_inner(self, https_only: bool) -> Result<BlockingClient, BuildError> {
131 if !self.credential.scope.matches_endpoint(&self.endpoint) {
132 return Err(BuildError::CredentialEndpointMismatch);
133 }
134 let settings = ClientSettings {
135 timeouts: self.timeouts,
136 #[cfg(feature = "blocking-rustls-fips")]
137 fips_tls_policy: self.fips_tls_policy.as_ref(),
138 #[cfg(not(feature = "blocking-rustls-fips"))]
139 _lifetime: core::marker::PhantomData,
140 };
141 let client = configured_raw_client(
142 self.endpoint.clone(),
143 &self.user_agent,
144 settings,
145 https_only,
146 )?;
147 Ok(BlockingClient::new(
148 client,
149 self.endpoint,
150 self.credential,
151 !https_only,
152 ))
153 }
154
155 #[cfg(test)]
156 pub(super) fn build_for_loopback(self) -> Result<BlockingClient, BuildError> {
157 self.build_inner(false)
158 }
159}
160
161impl RawBlockingClientBuilder {
162 #[must_use]
164 pub const fn new(
165 endpoint: HttpsEndpoint,
166 user_agent: UserAgent,
167 timeouts: RequestTimeouts,
168 ) -> Self {
169 Self {
170 endpoint,
171 user_agent,
172 timeouts,
173 #[cfg(feature = "blocking-rustls-fips")]
174 fips_tls_policy: None,
175 }
176 }
177
178 #[cfg(feature = "blocking-rustls-fips")]
180 #[must_use]
181 pub fn with_fips_tls_policy(mut self, policy: FipsTlsPolicy) -> Self {
182 self.fips_tls_policy = Some(policy);
183 self
184 }
185
186 pub fn build(self) -> Result<RawBlockingClient, BuildError> {
188 self.build_inner(true)
189 }
190
191 fn build_inner(self, https_only: bool) -> Result<RawBlockingClient, BuildError> {
192 let settings = ClientSettings {
193 timeouts: self.timeouts,
194 #[cfg(feature = "blocking-rustls-fips")]
195 fips_tls_policy: self.fips_tls_policy.as_ref(),
196 #[cfg(not(feature = "blocking-rustls-fips"))]
197 _lifetime: core::marker::PhantomData,
198 };
199 configured_raw_client(self.endpoint, &self.user_agent, settings, https_only)
200 }
201
202 #[cfg(test)]
203 pub(super) fn build_for_loopback(self) -> Result<RawBlockingClient, BuildError> {
204 self.build_inner(false)
205 }
206}
207
208impl fmt::Debug for BlockingClientBuilder {
209 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
210 let mut debug = formatter.debug_struct("BlockingClientBuilder");
211 debug
212 .field("endpoint", &"[redacted]")
213 .field("credential", &"[redacted]")
214 .field("user_agent", &self.user_agent)
215 .field("timeouts", &self.timeouts);
216 #[cfg(feature = "blocking-rustls-fips")]
217 debug.field("fips_tls_policy", &self.fips_tls_policy);
218 debug.finish()
219 }
220}
221
222impl fmt::Debug for RawBlockingClientBuilder {
223 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
224 let mut debug = formatter.debug_struct("RawBlockingClientBuilder");
225 debug
226 .field("endpoint", &"[redacted]")
227 .field("user_agent", &self.user_agent)
228 .field("timeouts", &self.timeouts);
229 #[cfg(feature = "blocking-rustls-fips")]
230 debug.field("fips_tls_policy", &self.fips_tls_policy);
231 debug.finish()
232 }
233}
234
235#[derive(Clone, Copy)]
236pub(super) struct ClientSettings<'a> {
237 pub(super) timeouts: RequestTimeouts,
238 #[cfg(feature = "blocking-rustls-fips")]
239 pub(super) fips_tls_policy: Option<&'a FipsTlsPolicy>,
240 #[cfg(not(feature = "blocking-rustls-fips"))]
241 pub(super) _lifetime: core::marker::PhantomData<&'a ()>,
242}
243
244pub(super) fn configured_raw_client(
245 endpoint: HttpsEndpoint,
246 user_agent: &UserAgent,
247 settings: ClientSettings<'_>,
248 https_only: bool,
249) -> Result<RawBlockingClient, BuildError> {
250 let tls_config = raw_tls_config(settings)?;
251 let client = RawHyperClient::new(
252 endpoint.clone(),
253 user_agent,
254 settings.timeouts,
255 tls_config,
256 https_only,
257 )?;
258 Ok(RawBlockingClient::new(client, endpoint))
259}
260
261#[cfg(all(
262 not(feature = "blocking-rustls-fips"),
263 not(feature = "blocking-rustls-webpki-roots")
264))]
265fn raw_tls_config(_settings: ClientSettings<'_>) -> Result<ClientConfig, BuildError> {
266 platform_client_config()
267}
268
269#[cfg(all(
270 not(feature = "blocking-rustls-fips"),
271 feature = "blocking-rustls-webpki-roots"
272))]
273fn raw_tls_config(_settings: ClientSettings<'_>) -> Result<ClientConfig, BuildError> {
274 webpki_roots_client_config()
275}
276
277#[cfg(feature = "blocking-rustls-fips")]
278fn raw_tls_config(settings: ClientSettings<'_>) -> Result<ClientConfig, BuildError> {
279 let policy = settings
280 .fips_tls_policy
281 .ok_or(BuildError::FipsTlsPolicyRequired)?;
282 fips_client_config(policy)
283}
284
285#[cfg(all(
286 not(feature = "blocking-rustls-fips"),
287 feature = "blocking-rustls-webpki-roots"
288))]
289fn webpki_roots_client_config() -> Result<ClientConfig, BuildError> {
290 let provider = Arc::new(rustls::crypto::aws_lc_rs::default_provider());
291 let mut roots = RootCertStore::empty();
292 roots.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
293 Ok(ClientConfig::builder_with_provider(provider)
294 .with_safe_default_protocol_versions()
295 .map_err(|_| BuildError::WebPkiRootsProtocolConfigurationFailed)?
296 .with_root_certificates(roots)
297 .with_no_client_auth())
298}
299
300#[cfg(all(
301 test,
302 not(feature = "blocking-rustls-fips"),
303 feature = "blocking-rustls-webpki-roots"
304))]
305pub(super) fn test_webpki_roots_configuration() -> Result<(usize, bool), BuildError> {
306 let config = webpki_roots_client_config()?;
307 Ok((webpki_roots::TLS_SERVER_ROOTS.len(), config.fips()))
308}
309
310#[cfg(feature = "blocking-rustls-fips")]
311fn fips_client_config(policy: &FipsTlsPolicy) -> Result<ClientConfig, BuildError> {
312 let provider = Arc::new(rustls::crypto::default_fips_provider());
313 validate_fips_provider(provider.as_ref())?;
314 let config = client_config_with_provider(provider, policy)?;
315 validate_fips_config(&config)?;
316 Ok(config)
317}
318
319#[cfg(feature = "blocking-rustls-fips")]
320fn client_config_with_provider(
321 provider: Arc<CryptoProvider>,
322 policy: &FipsTlsPolicy,
323) -> Result<ClientConfig, BuildError> {
324 let verifier = WebPkiServerVerifier::builder_with_provider(
325 Arc::clone(&policy.roots),
326 Arc::clone(&provider),
327 )
328 .with_crls(policy.crls.iter().cloned())
329 .enforce_revocation_expiration()
330 .build()
331 .map_err(|_| BuildError::FipsRevocationVerifierFailed)?;
332 Ok(ClientConfig::builder_with_provider(provider)
333 .with_safe_default_protocol_versions()
334 .map_err(|_| BuildError::FipsProtocolConfigurationFailed)?
335 .dangerous()
336 .with_custom_certificate_verifier(verifier)
337 .with_no_client_auth())
338}
339
340#[cfg(feature = "blocking-rustls-fips")]
341fn validate_fips_provider(provider: &CryptoProvider) -> Result<(), BuildError> {
342 if provider.fips() {
343 Ok(())
344 } else {
345 Err(BuildError::FipsProviderRejected)
346 }
347}
348
349#[cfg(feature = "blocking-rustls-fips")]
350fn validate_fips_config(config: &ClientConfig) -> Result<(), BuildError> {
351 if config.fips() {
352 Ok(())
353 } else {
354 Err(BuildError::FipsClientConfigurationRejected)
355 }
356}
357
358#[cfg(all(test, feature = "blocking-rustls-fips"))]
359pub(super) fn test_fips_configuration(policy: &FipsTlsPolicy) -> Result<bool, BuildError> {
360 fips_client_config(policy).map(|config| config.fips())
361}
362
363#[cfg(all(test, feature = "blocking-rustls-fips"))]
364fn non_fips_provider() -> CryptoProvider {
365 #[derive(Debug)]
366 struct NonFipsRandom;
367
368 impl rustls::crypto::SecureRandom for NonFipsRandom {
369 fn fill(&self, _buffer: &mut [u8]) -> Result<(), rustls::crypto::GetRandomFailed> {
370 Err(rustls::crypto::GetRandomFailed)
371 }
372 }
373
374 static NON_FIPS_RANDOM: NonFipsRandom = NonFipsRandom;
375 let mut provider = rustls::crypto::default_fips_provider();
376 provider.secure_random = &NON_FIPS_RANDOM;
377 provider
378}
379
380#[cfg(all(test, feature = "blocking-rustls-fips"))]
381pub(super) fn test_non_fips_rejection(policy: &FipsTlsPolicy) -> Result<bool, BuildError> {
382 let provider = non_fips_provider();
383 let provider_rejected =
384 validate_fips_provider(&provider) == Err(BuildError::FipsProviderRejected);
385 let config = client_config_with_provider(Arc::new(provider), policy)?;
386 let config_rejected =
387 validate_fips_config(&config) == Err(BuildError::FipsClientConfigurationRejected);
388 Ok(provider_rejected && config_rejected)
389}
390
391#[cfg(all(test, feature = "blocking-rustls-fips"))]
392pub(super) fn test_non_fips_global_independence(policy: &FipsTlsPolicy) -> bool {
393 let provider = non_fips_provider();
394 if provider.fips() || provider.install_default().is_err() {
395 return false;
396 }
397 let global_is_non_fips = CryptoProvider::get_default().is_some_and(|value| !value.fips());
398 global_is_non_fips && test_fips_configuration(policy) == Ok(true)
399}