aws_smithy_http_client/client/
tls.rs1use crate::cfg::{cfg_rustls, cfg_s2n_tls};
6use crate::HttpClientError;
7
8#[derive(Debug, Eq, PartialEq, Clone)]
10#[non_exhaustive]
11pub enum Provider {
12 #[cfg(any(
13 feature = "rustls-aws-lc",
14 feature = "rustls-aws-lc-fips",
15 feature = "rustls-ring"
16 ))]
17 Rustls(rustls_provider::CryptoMode),
19 #[cfg(feature = "s2n-tls")]
21 S2nTls,
22}
23
24#[derive(Debug, Clone)]
26pub struct TlsContext {
27 #[allow(unused)]
28 trust_store: TrustStore,
29}
30
31impl TlsContext {
32 pub fn builder() -> TlsContextBuilder {
34 TlsContextBuilder::new()
35 }
36}
37
38impl Default for TlsContext {
39 fn default() -> Self {
40 TlsContext::builder().build().expect("valid default config")
41 }
42}
43
44#[derive(Debug)]
46pub struct TlsContextBuilder {
47 trust_store: TrustStore,
48}
49
50impl TlsContextBuilder {
51 fn new() -> Self {
52 TlsContextBuilder {
53 trust_store: TrustStore::default(),
54 }
55 }
56
57 pub fn with_trust_store(mut self, trust_store: TrustStore) -> Self {
59 self.trust_store = trust_store;
60 self
61 }
62
63 pub fn build(self) -> Result<TlsContext, HttpClientError> {
65 Ok(TlsContext {
66 trust_store: self.trust_store,
67 })
68 }
69}
70
71#[allow(unused)]
73#[derive(Debug, Clone)]
74struct CertificatePEM(Vec<u8>);
75
76impl From<&[u8]> for CertificatePEM {
77 fn from(value: &[u8]) -> Self {
78 CertificatePEM(value.to_vec())
79 }
80}
81
82#[derive(Debug, Clone)]
87pub struct TrustStore {
88 enable_native_roots: bool,
89 custom_certs: Vec<CertificatePEM>,
90}
91
92impl TrustStore {
93 pub fn empty() -> Self {
95 Self {
96 enable_native_roots: false,
97 custom_certs: Vec::new(),
98 }
99 }
100
101 pub fn with_native_roots(mut self, enable_native_roots: bool) -> Self {
105 self.enable_native_roots = enable_native_roots;
106 self
107 }
108
109 pub fn with_pem_certificate(mut self, pem_bytes: impl Into<Vec<u8>>) -> Self {
115 self.custom_certs.push(CertificatePEM(pem_bytes.into()));
118 self
119 }
120
121 pub fn add_pem_certificate(&mut self, pem_bytes: impl Into<Vec<u8>>) -> &mut Self {
127 self.custom_certs.push(CertificatePEM(pem_bytes.into()));
128 self
129 }
130}
131
132impl Default for TrustStore {
133 fn default() -> Self {
134 Self {
135 enable_native_roots: true,
136 custom_certs: Vec::new(),
137 }
138 }
139}
140
141cfg_rustls! {
142 pub mod rustls_provider {
144 use crate::client::tls::Provider;
145 use rustls::crypto::CryptoProvider;
146
147 #[derive(Debug, Eq, PartialEq, Clone)]
149 #[non_exhaustive]
150 pub enum CryptoMode {
151 #[cfg(feature = "rustls-ring")]
153 Ring,
154 #[cfg(feature = "rustls-aws-lc")]
156 AwsLc,
157 #[cfg(feature = "rustls-aws-lc-fips")]
159 AwsLcFips,
160 }
161
162 impl CryptoMode {
163 fn provider(self) -> CryptoProvider {
164 match self {
165 #[cfg(feature = "rustls-aws-lc")]
166 CryptoMode::AwsLc => rustls::crypto::aws_lc_rs::default_provider(),
167
168 #[cfg(feature = "rustls-ring")]
169 CryptoMode::Ring => rustls::crypto::ring::default_provider(),
170
171 #[cfg(feature = "rustls-aws-lc-fips")]
172 CryptoMode::AwsLcFips => {
173 let provider = rustls::crypto::default_fips_provider();
174 assert!(
175 provider.fips(),
176 "FIPS was requested but the provider did not support FIPS"
177 );
178 provider
179 }
180 }
181 }
182 }
183
184 impl Provider {
185 pub fn rustls(mode: CryptoMode) -> Provider {
188 Provider::Rustls(mode)
189 }
190 }
191
192 pub(crate) mod build_connector {
193 use crate::client::tls::rustls_provider::CryptoMode;
194 use crate::tls::TlsContext;
195 use hyper_util::client::legacy as client;
196 use client::connect::HttpConnector;
197 use rustls::crypto::CryptoProvider;
198 use std::sync::Arc;
199 use rustls_pki_types::CertificateDer;
200 use rustls_pki_types::pem::PemObject;
201 use rustls_native_certs::CertificateResult;
202 use std::sync::LazyLock;
203
204 pub(crate) static NATIVE_ROOTS: LazyLock<Vec<CertificateDer<'static>>> = LazyLock::new(|| {
210 let CertificateResult { certs, errors, .. } = rustls_native_certs::load_native_certs();
211 if !errors.is_empty() {
212 tracing::warn!("native root CA certificate loading errors: {errors:?}")
213 }
214
215 if certs.is_empty() {
216 tracing::warn!("no native root CA certificates found!");
217 }
218
219 certs
222 });
223
224 fn restrict_ciphers(base: CryptoProvider) -> CryptoProvider {
225 let suites = &[
226 rustls::CipherSuite::TLS13_AES_256_GCM_SHA384,
227 rustls::CipherSuite::TLS13_AES_128_GCM_SHA256,
228 rustls::CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
230 rustls::CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
231 rustls::CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
232 rustls::CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
233 rustls::CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
234 ];
235 let supported_suites = suites
236 .iter()
237 .flat_map(|suite| {
238 base.cipher_suites
239 .iter()
240 .find(|s| &s.suite() == suite)
241 .cloned()
242 })
243 .collect::<Vec<_>>();
244 CryptoProvider {
245 cipher_suites: supported_suites,
246 ..base
247 }
248 }
249
250 impl TlsContext {
251 fn rustls_root_certs(&self) -> rustls::RootCertStore {
252 let mut roots = rustls::RootCertStore::empty();
253 if self.trust_store.enable_native_roots {
254 let (valid, _invalid) = roots.add_parsable_certificates(
255 NATIVE_ROOTS.clone()
256 );
257 debug_assert!(valid > 0, "TrustStore configured to enable native roots but no valid root certificates parsed!");
258 }
259
260 for pem_cert in &self.trust_store.custom_certs {
261 let ders = CertificateDer::pem_slice_iter(&pem_cert.0).collect::<Result<Vec<_>, _> >().expect("valid PEM certificate");
262 for cert in ders {
263 roots.add(cert).expect("cert parsable")
264 }
265 }
266
267 roots
268 }
269 }
270
271 pub(crate) fn wrap_connector<R>(
272 mut conn: HttpConnector<R>,
273 crypto_mode: CryptoMode,
274 tls_context: &TlsContext,
275 ) -> hyper_rustls::HttpsConnector<HttpConnector<R>> {
276 let root_certs = tls_context.rustls_root_certs();
277 conn.enforce_http(false);
278 hyper_rustls::HttpsConnectorBuilder::new()
279 .with_tls_config(
280 rustls::ClientConfig::builder_with_provider(Arc::new(restrict_ciphers(crypto_mode.provider())))
281 .with_safe_default_protocol_versions()
282 .expect("Error with the TLS configuration. Please file a bug report under https://github.com/smithy-lang/smithy-rs/issues.")
283 .with_root_certificates(root_certs)
284 .with_no_client_auth()
285 )
286 .https_or_http()
287 .enable_http1()
288 .enable_http2()
289 .wrap_connector(conn)
290 }
291 }
292 }
293}
294
295cfg_s2n_tls! {
296 pub(crate) mod s2n_tls_provider {
298 pub(crate) mod build_connector {
299 use hyper_util::client::legacy as client;
300 use client::connect::HttpConnector;
301 use s2n_tls::security::Policy;
302 use crate::tls::TlsContext;
303 use std::sync::LazyLock;
304
305 const S2N_POLICY_VERSION: &str = "20230317";
308
309 fn base_config() -> s2n_tls::config::Builder {
310 let mut builder = s2n_tls::config::Config::builder();
311 let policy = Policy::from_version(S2N_POLICY_VERSION).unwrap();
312 builder.set_security_policy(&policy).expect("valid s2n security policy");
313 builder.with_system_certs(false).unwrap();
315 builder
316 }
317
318 static CACHED_CONFIG: LazyLock<s2n_tls::config::Config> = LazyLock::new(|| {
319 let mut config = base_config();
320 config.with_system_certs(true).unwrap();
321 config.build().expect("valid s2n config")
323 });
324
325 impl TlsContext {
326 fn s2n_config(&self) -> s2n_tls::config::Config {
327 if self.trust_store.enable_native_roots && self.trust_store.custom_certs.is_empty() {
330 CACHED_CONFIG.clone()
331 } else {
332 let mut config = base_config();
333 config.with_system_certs(self.trust_store.enable_native_roots).unwrap();
334 for pem_cert in &self.trust_store.custom_certs {
335 config.trust_pem(pem_cert.0.as_slice()).expect("valid certificate");
336 }
337 config.build().expect("valid s2n config")
338 }
339 }
340 }
341
342 pub(crate) fn wrap_connector<R>(
343 mut http_connector: HttpConnector<R>,
344 tls_context: &TlsContext,
345 ) -> s2n_tls_hyper::connector::HttpsConnector<HttpConnector<R>> {
346 let config = tls_context.s2n_config();
347 http_connector.enforce_http(false);
348 let mut builder = s2n_tls_hyper::connector::HttpsConnector::builder_with_http(http_connector, config);
349 builder.with_plaintext_http(true);
350 builder.build()
351 }
352 }
353 }
354}