Skip to main content

cloud_sdk_reqwest/blocking/
config.rs

1use 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/// Deployment-managed trust anchors and complete CRLs for FIPS TLS.
40#[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    /// Creates a policy that checks the complete certificate chain, rejects
49    /// unknown revocation status, and rejects expired CRLs.
50    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
78/// Builder requiring endpoint, bearer token, user agent, and all timeout
79/// dimensions before a client can be constructed.
80pub 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
89/// Builder for a raw client with no credential, provider, media, or retry policy.
90pub 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    /// Creates a complete blocking-client configuration.
100    #[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    /// Supplies mandatory deployment-managed roots and CRLs for FIPS TLS.
118    #[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    /// Builds a hardened HTTPS-only client.
126    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    /// Creates a complete raw blocking configuration without credentials.
163    #[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    /// Supplies mandatory deployment-managed roots and CRLs for FIPS TLS.
179    #[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    /// Builds an HTTPS-only executor with no implicit authorization.
187    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}