1use crate::client::tls::Provider;
6use rustls::crypto::CryptoProvider;
7
8#[derive(Debug, Clone)]
10#[non_exhaustive]
11pub enum CryptoMode {
12 #[cfg(feature = "rustls-ring")]
14 Ring,
15 #[cfg(feature = "rustls-aws-lc")]
17 AwsLc,
18 #[cfg(feature = "rustls-aws-lc-fips")]
20 AwsLcFips,
21 #[cfg(all(aws_sdk_unstable, feature = "__rustls"))]
30 Custom(CryptoProvider),
31}
32
33impl std::cmp::PartialEq for CryptoMode {
34 fn eq(&self, other: &CryptoMode) -> bool {
35 match (self, other) {
36 #[cfg(feature = "rustls-ring")]
37 (Self::Ring, Self::Ring) => true,
38 #[cfg(feature = "rustls-aws-lc")]
39 (Self::AwsLc, Self::AwsLc) => true,
40 #[cfg(feature = "rustls-aws-lc-fips")]
41 (Self::AwsLcFips, Self::AwsLcFips) => true,
42 #[allow(unreachable_patterns)]
46 _ => false,
47 }
48 }
49}
50
51#[cfg(not(all(aws_sdk_unstable, feature = "__rustls")))]
52impl Eq for CryptoMode {}
53
54impl CryptoMode {
55 fn provider(self) -> CryptoProvider {
56 match self {
57 #[cfg(feature = "rustls-aws-lc")]
58 CryptoMode::AwsLc => rustls::crypto::aws_lc_rs::default_provider(),
59
60 #[cfg(feature = "rustls-ring")]
61 CryptoMode::Ring => rustls::crypto::ring::default_provider(),
62
63 #[cfg(feature = "rustls-aws-lc-fips")]
64 CryptoMode::AwsLcFips => {
65 let provider = rustls::crypto::default_fips_provider();
66 assert!(
67 provider.fips(),
68 "FIPS was requested but the provider did not support FIPS"
69 );
70 provider
71 }
72 #[cfg(all(aws_sdk_unstable, feature = "__rustls"))]
73 CryptoMode::Custom(provider) => provider,
74 }
75 }
76
77 #[cfg(all(aws_sdk_unstable, feature = "__rustls"))]
78 fn is_custom(&self) -> bool {
79 matches!(self, Self::Custom(_))
80 }
81
82 #[cfg(not(all(aws_sdk_unstable, feature = "__rustls")))]
83 fn is_custom(&self) -> bool {
84 false
85 }
86}
87
88impl Provider {
89 pub fn rustls(mode: CryptoMode) -> Provider {
92 Provider::Rustls(mode)
93 }
94}
95
96pub(crate) mod build_connector {
97 use crate::client::tls::rustls_provider::CryptoMode;
98 use crate::tls::TlsContext;
99 use client::connect::HttpConnector;
100 use hyper_util::client::legacy as client;
101 use rustls::client::danger::ServerCertVerified;
102 use rustls::client::danger::ServerCertVerifier;
103 use rustls::client::WebPkiServerVerifier;
104 use rustls::crypto::CryptoProvider;
105 use rustls_native_certs::CertificateResult;
106 use rustls_pki_types::pem::PemObject;
107 use rustls_pki_types::CertificateDer;
108 use rustls_pki_types::ServerName as RustlsServerName;
109 use std::sync::Arc;
110 use std::sync::LazyLock;
111
112 pub(crate) static NATIVE_ROOTS: LazyLock<Vec<CertificateDer<'static>>> = LazyLock::new(|| {
118 let CertificateResult { certs, errors, .. } = rustls_native_certs::load_native_certs();
119 if !errors.is_empty() {
120 tracing::warn!("native root CA certificate loading errors: {errors:?}")
121 }
122
123 if certs.is_empty() {
124 tracing::warn!("no native root CA certificates found!");
125 }
126
127 certs
130 });
131
132 pub(crate) fn restrict_ciphers(base: CryptoProvider) -> CryptoProvider {
133 let suites = &[
134 rustls::CipherSuite::TLS13_AES_256_GCM_SHA384,
135 rustls::CipherSuite::TLS13_AES_128_GCM_SHA256,
136 rustls::CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
138 rustls::CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
139 rustls::CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
140 rustls::CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
141 rustls::CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
142 ];
143 let supported_suites = suites
144 .iter()
145 .flat_map(|suite| {
146 base.cipher_suites
147 .iter()
148 .find(|s| &s.suite() == suite)
149 .cloned()
150 })
151 .collect::<Vec<_>>();
152 CryptoProvider {
153 cipher_suites: supported_suites,
154 ..base
155 }
156 }
157
158 impl TlsContext {
159 pub(crate) fn rustls_root_certs(&self) -> rustls::RootCertStore {
160 let mut roots = rustls::RootCertStore::empty();
161 if self.trust_store.enable_native_roots {
162 let (valid, _invalid) = roots.add_parsable_certificates(NATIVE_ROOTS.clone());
163 debug_assert!(valid > 0, "TrustStore configured to enable native roots but no valid root certificates parsed!");
164 }
165
166 for pem_cert in &self.trust_store.custom_certs {
167 let ders = CertificateDer::pem_slice_iter(&pem_cert.0)
168 .collect::<Result<Vec<_>, _>>()
169 .expect("valid PEM certificate");
170 for cert in ders {
171 roots.add(cert).expect("cert parsable")
172 }
173 }
174
175 roots
176 }
177
178 fn additional_server_names(&self) -> Vec<RustlsServerName<'static>> {
179 self.additional_server_names
180 .iter()
181 .map(|name| name.0.clone())
182 .collect()
183 }
184 }
185
186 pub(crate) fn create_rustls_client_config(
191 crypto_mode: CryptoMode,
192 tls_context: &TlsContext,
193 ) -> rustls::ClientConfig {
194 let skip_restrict = crypto_mode.is_custom();
195 let provider = Arc::new(if skip_restrict {
196 crypto_mode.provider()
197 } else {
198 restrict_ciphers(crypto_mode.provider())
199 });
200 let root_certs = tls_context.rustls_root_certs();
201 let additional_server_names = tls_context.additional_server_names();
202
203 let builder = rustls::ClientConfig::builder_with_provider(Arc::clone(&provider))
204 .with_safe_default_protocol_versions()
205 .expect("Error with the TLS configuration. Please file a bug report under https://github.com/smithy-lang/smithy-rs/issues.");
206
207 if additional_server_names.is_empty() {
208 builder
209 .with_root_certificates(root_certs)
210 .with_no_client_auth()
211 } else {
212 let web_pki_server_verifier = WebPkiServerVerifier::builder_with_provider(
213 Arc::new(root_certs),
214 Arc::clone(&provider),
215 )
216 .build()
217 .expect("at least one root certificate was provided as a trust anchor");
218
219 builder
220 .dangerous()
221 .with_custom_certificate_verifier(Arc::new(ServerVerifier {
222 web_pki_server_verifier,
223 additional_server_names,
224 }))
225 .with_no_client_auth()
226 }
227 }
228
229 #[derive(Debug)]
243 struct ServerVerifier {
244 web_pki_server_verifier: Arc<WebPkiServerVerifier>,
245 additional_server_names: Vec<RustlsServerName<'static>>,
246 }
247
248 impl ServerCertVerifier for ServerVerifier {
249 fn verify_server_cert(
250 &self,
251 end_entity: &CertificateDer<'_>,
252 intermediates: &[CertificateDer<'_>],
253 server_name: &RustlsServerName<'_>,
254 ocsp_response: &[u8],
255 now: rustls_pki_types::UnixTime,
256 ) -> Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
257 match self.web_pki_server_verifier.verify_server_cert(
258 end_entity,
259 intermediates,
260 server_name,
261 ocsp_response,
262 now,
263 ) {
264 Ok(server_cert_verified) => Ok(server_cert_verified),
265 Err(error) => {
266 let matched = self.additional_server_names.iter().any(|alt_name| {
267 self.web_pki_server_verifier
268 .verify_server_cert(
269 end_entity,
270 intermediates,
271 alt_name,
272 ocsp_response,
273 now,
274 )
275 .is_ok()
276 });
277 if matched {
278 Ok(ServerCertVerified::assertion())
279 } else {
280 Err(error)
281 }
282 }
283 }
284 }
285
286 fn verify_tls12_signature(
287 &self,
288 message: &[u8],
289 cert: &CertificateDer<'_>,
290 dss: &rustls::DigitallySignedStruct,
291 ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
292 self.web_pki_server_verifier
293 .verify_tls12_signature(message, cert, dss)
294 }
295
296 fn verify_tls13_signature(
297 &self,
298 message: &[u8],
299 cert: &CertificateDer<'_>,
300 dss: &rustls::DigitallySignedStruct,
301 ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
302 self.web_pki_server_verifier
303 .verify_tls13_signature(message, cert, dss)
304 }
305
306 fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
307 self.web_pki_server_verifier.supported_verify_schemes()
308 }
309 }
310
311 pub(crate) fn wrap_connector<R>(
312 conn: HttpConnector<R>,
313 crypto_mode: CryptoMode,
314 tls_context: &TlsContext,
315 proxy_config: crate::client::proxy::ProxyConfig,
316 ) -> super::connect::RustTlsConnector<R> {
317 wrap_connector_with_alpn(
318 conn,
319 crypto_mode,
320 tls_context,
321 proxy_config,
322 &[b"h2", b"http/1.1"],
323 )
324 }
325
326 pub(crate) fn wrap_connector_with_alpn<R>(
328 mut conn: HttpConnector<R>,
329 crypto_mode: CryptoMode,
330 tls_context: &TlsContext,
331 proxy_config: crate::client::proxy::ProxyConfig,
332 alpn_protocols: &[&[u8]],
333 ) -> super::connect::RustTlsConnector<R> {
334 let mut client_config = create_rustls_client_config(crypto_mode, tls_context);
335 client_config.alpn_protocols = alpn_protocols
336 .iter()
337 .map(|protocol| protocol.to_vec())
338 .collect();
339 conn.enforce_http(false);
340 let https_connector = hyper_rustls::HttpsConnector::from((conn, client_config.clone()));
341
342 super::connect::RustTlsConnector::new(https_connector, client_config, proxy_config)
343 }
344}
345
346pub(crate) mod connect {
347 use crate::client::connect::{Conn, ConnectPathInner, Connecting};
348 use crate::client::error_chain;
349 use crate::client::proxy::ProxyConfig;
350 use crate::client::tls::{TlsConnectError, TlsConnectErrorKind};
351 use aws_smithy_runtime_api::box_error::BoxError;
352 use http_1x::uri::Scheme;
353 use http_1x::Uri;
354 use hyper::rt::{Read, ReadBufCursor, Write};
355 use hyper_rustls::MaybeHttpsStream;
356 use hyper_util::client::legacy::connect::{Connected, Connection, HttpConnector};
357 use hyper_util::client::proxy::matcher::Matcher;
358 use hyper_util::rt::TokioIo;
359 use pin_project_lite::pin_project;
360 use std::error::Error;
361 use std::sync::Arc;
362 use std::{
363 io::{self, IoSlice},
364 pin::Pin,
365 task::{Context, Poll},
366 };
367 use tokio::io::{AsyncRead, AsyncWrite};
368 use tokio::net::TcpStream;
369 use tokio_rustls::client::TlsStream;
370 use tower::Service;
371
372 fn classify_rustls(error: BoxError) -> BoxError {
378 if error_chain(error.as_ref()).any(|error| error.is::<rustls::Error>()) {
379 Box::new(TlsConnectError::new(TlsConnectErrorKind::Protocol, error))
380 } else {
381 error
382 }
383 }
384
385 #[derive(Debug, Clone)]
386 pub(crate) struct RustTlsConnector<R> {
387 https: hyper_rustls::HttpsConnector<HttpConnector<R>>,
388 tls_config: Arc<rustls::ClientConfig>,
389 proxy_matcher: Option<Arc<Matcher>>, }
391
392 impl<R> RustTlsConnector<R> {
393 pub(super) fn new(
394 https: hyper_rustls::HttpsConnector<HttpConnector<R>>,
395 tls_config: rustls::ClientConfig,
396 proxy_config: ProxyConfig,
397 ) -> Self {
398 let proxy_matcher = if proxy_config.is_disabled() {
400 None
401 } else {
402 Some(Arc::new(proxy_config.into_hyper_util_matcher()))
403 };
404
405 Self {
406 https,
407 tls_config: Arc::new(tls_config),
408 proxy_matcher,
409 }
410 }
411 }
412
413 impl<R> Service<Uri> for RustTlsConnector<R>
414 where
415 R: Clone + Send + Sync + 'static,
416 R: Service<hyper_util::client::legacy::connect::dns::Name>,
417 R::Response: Iterator<Item = std::net::SocketAddr>,
418 R::Future: Send,
419 R::Error: Into<Box<dyn Error + Send + Sync>>,
420 {
421 type Response = Conn;
422 type Error = BoxError;
423 type Future = Connecting;
424
425 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
426 self.https.poll_ready(cx).map_err(Into::into)
427 }
428
429 fn call(&mut self, dst: Uri) -> Self::Future {
430 let proxy_intercept = if let Some(ref matcher) = self.proxy_matcher {
432 matcher.intercept(&dst)
433 } else {
434 None
435 };
436
437 if let Some(intercept) = proxy_intercept {
438 if dst.scheme() == Some(&Scheme::HTTPS) {
439 self.handle_https_through_proxy(dst, intercept)
441 } else {
442 self.handle_http_through_proxy(dst, intercept)
444 }
445 } else {
446 self.handle_direct_connection(dst)
448 }
449 }
450 }
451
452 impl<R> RustTlsConnector<R>
453 where
454 R: Clone + Send + Sync + 'static,
455 R: Service<hyper_util::client::legacy::connect::dns::Name>,
456 R::Response: Iterator<Item = std::net::SocketAddr>,
457 R::Future: Send,
458 R::Error: Into<Box<dyn Error + Send + Sync>>,
459 {
460 fn handle_direct_connection(&mut self, dst: Uri) -> Connecting {
461 let fut = self.https.call(dst);
462 Box::pin(async move {
463 let conn = fut.await.map_err(classify_rustls)?;
464 Ok(Conn {
465 inner: Box::new(conn),
466 connect_path: ConnectPathInner::Direct,
467 })
468 })
469 }
470
471 fn handle_http_through_proxy(
472 &mut self,
473 _dst: Uri,
474 intercept: hyper_util::client::proxy::matcher::Intercept,
475 ) -> Connecting {
476 let proxy_uri = intercept.uri().clone();
478 let connect_path = ConnectPathInner::forward_proxy(intercept.basic_auth().cloned());
479 let fut = self.https.call(proxy_uri);
480 Box::pin(async move {
481 let conn = fut.await?;
482 Ok(Conn {
483 inner: Box::new(conn),
484 connect_path,
485 })
486 })
487 }
488
489 fn handle_https_through_proxy(
490 &mut self,
491 dst: Uri,
492 intercept: hyper_util::client::proxy::matcher::Intercept,
493 ) -> Connecting {
494 use rustls_pki_types::ServerName as RustlsServerName;
495 let tunnel = hyper_util::client::legacy::connect::proxy::Tunnel::new(
500 intercept.uri().clone(),
501 self.https.clone(),
502 );
503
504 let mut tunnel = if let Some(auth) = intercept.basic_auth() {
506 tunnel.with_auth(auth.clone())
507 } else {
508 tunnel
509 };
510
511 let tls_config = self.tls_config.clone();
512 let dst_clone = dst.clone();
513
514 Box::pin(async move {
515 tracing::trace!("tunneling HTTPS over proxy");
517 let tunneled = tunnel.call(dst_clone.clone()).await?;
518
519 let host = dst_clone
521 .host()
522 .ok_or("missing host in URI for TLS handshake")?;
523
524 let server_name = RustlsServerName::try_from(host.to_owned()).map_err(|e| {
525 BoxError::from(format!("invalid server name for TLS handshake: {e}"))
526 })?;
527
528 let tls_connector = tokio_rustls::TlsConnector::from(tls_config)
529 .connect(server_name, TokioIo::new(tunneled))
530 .await
531 .map_err(|error| classify_rustls(error.into()))?;
532
533 Ok(Conn {
534 inner: Box::new(RustTlsConn {
535 inner: TokioIo::new(tls_connector),
536 }),
537 connect_path: ConnectPathInner::ProxyTunnel,
538 })
539 })
540 }
541 }
542
543 pin_project! {
544 pub(crate) struct RustTlsConn<T> {
545 #[pin] pub(super) inner: TokioIo<TlsStream<T>>
546 }
547 }
548
549 impl Connection for RustTlsConn<TokioIo<TokioIo<TcpStream>>> {
550 fn connected(&self) -> Connected {
551 if self.inner.inner().get_ref().1.alpn_protocol() == Some(b"h2") {
552 self.inner
553 .inner()
554 .get_ref()
555 .0
556 .inner()
557 .connected()
558 .negotiated_h2()
559 } else {
560 self.inner.inner().get_ref().0.inner().connected()
561 }
562 }
563 }
564
565 impl Connection for RustTlsConn<TokioIo<MaybeHttpsStream<TokioIo<TcpStream>>>> {
566 fn connected(&self) -> Connected {
567 if self.inner.inner().get_ref().1.alpn_protocol() == Some(b"h2") {
568 self.inner
569 .inner()
570 .get_ref()
571 .0
572 .inner()
573 .connected()
574 .negotiated_h2()
575 } else {
576 self.inner.inner().get_ref().0.inner().connected()
577 }
578 }
579 }
580 impl<T: AsyncRead + AsyncWrite + Unpin> Read for RustTlsConn<T> {
581 fn poll_read(
582 self: Pin<&mut Self>,
583 cx: &mut Context<'_>,
584 buf: ReadBufCursor<'_>,
585 ) -> Poll<tokio::io::Result<()>> {
586 let this = self.project();
587 Read::poll_read(this.inner, cx, buf)
588 }
589 }
590
591 impl<T: AsyncRead + AsyncWrite + Unpin> Write for RustTlsConn<T> {
592 fn poll_write(
593 self: Pin<&mut Self>,
594 cx: &mut Context<'_>,
595 buf: &[u8],
596 ) -> Poll<Result<usize, tokio::io::Error>> {
597 let this = self.project();
598 Write::poll_write(this.inner, cx, buf)
599 }
600
601 fn poll_write_vectored(
602 self: Pin<&mut Self>,
603 cx: &mut Context<'_>,
604 bufs: &[IoSlice<'_>],
605 ) -> Poll<Result<usize, io::Error>> {
606 let this = self.project();
607 Write::poll_write_vectored(this.inner, cx, bufs)
608 }
609
610 fn is_write_vectored(&self) -> bool {
611 self.inner.is_write_vectored()
612 }
613
614 fn poll_flush(
615 self: Pin<&mut Self>,
616 cx: &mut Context<'_>,
617 ) -> Poll<Result<(), tokio::io::Error>> {
618 let this = self.project();
619 Write::poll_flush(this.inner, cx)
620 }
621
622 fn poll_shutdown(
623 self: Pin<&mut Self>,
624 cx: &mut Context<'_>,
625 ) -> Poll<Result<(), tokio::io::Error>> {
626 let this = self.project();
627 Write::poll_shutdown(this.inner, cx)
628 }
629 }
630
631 #[cfg(test)]
632 mod tests {
633 use super::*;
634 use rustls::CertificateError;
635
636 #[test]
637 fn rustls_rejection_is_tagged_protocol() {
638 let rejection = io::Error::other(io::Error::new(
640 io::ErrorKind::InvalidData,
641 rustls::Error::InvalidCertificate(CertificateError::UnknownIssuer),
642 ));
643
644 let error = classify_rustls(rejection.into());
645
646 let tls = error
647 .downcast_ref::<TlsConnectError>()
648 .expect("a rustls rejection is tagged");
649 assert_eq!(TlsConnectErrorKind::Protocol, tls.kind());
650 assert!(
651 error_chain(tls).any(|error| error.is::<rustls::Error>()),
652 "the tag keeps the rustls error in its source chain"
653 );
654 }
655
656 #[test]
657 fn handshake_eof_passes_through_untagged() {
658 let eof = io::Error::new(io::ErrorKind::UnexpectedEof, "tls handshake eof");
659
660 let error = classify_rustls(eof.into());
661
662 assert!(!error.is::<TlsConnectError>());
663 let io = error
664 .downcast_ref::<io::Error>()
665 .expect("the I/O error is returned unchanged");
666 assert_eq!(io::ErrorKind::UnexpectedEof, io.kind());
667 }
668 }
669}