Skip to main content

aws_smithy_http_client/client/
tls.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5use std::fmt;
6
7use crate::cfg::{cfg_rustls, cfg_s2n_tls};
8use crate::HttpClientError;
9
10/// Choice of underlying cryptography library
11#[derive(Debug, PartialEq, Clone)]
12#[non_exhaustive]
13pub enum Provider {
14    #[cfg(feature = "__rustls")]
15    /// TLS provider based on [rustls](https://github.com/rustls/rustls)
16    Rustls(rustls_provider::CryptoMode),
17    /// TLS provider based on [s2n-tls](https://github.com/aws/s2n-tls)
18    #[cfg(feature = "s2n-tls")]
19    S2nTls,
20}
21
22#[cfg(not(all(aws_sdk_unstable, feature = "__rustls")))]
23impl Eq for Provider {}
24
25/// TLS related configuration object
26#[derive(Debug, Clone)]
27pub struct TlsContext {
28    #[allow(unused)]
29    trust_store: TrustStore,
30    #[allow(unused)]
31    additional_server_names: Vec<ServerName>,
32}
33
34impl TlsContext {
35    /// Create a new [TlsContext] builder
36    pub fn builder() -> TlsContextBuilder {
37        TlsContextBuilder::new()
38    }
39}
40
41impl Default for TlsContext {
42    fn default() -> Self {
43        TlsContext::builder().build().expect("valid default config")
44    }
45}
46
47/// Builder for TLS related configuration
48#[derive(Debug)]
49pub struct TlsContextBuilder {
50    trust_store: TrustStore,
51    additional_server_names: Vec<ServerName>,
52}
53
54impl TlsContextBuilder {
55    fn new() -> Self {
56        TlsContextBuilder {
57            trust_store: TrustStore::default(),
58            additional_server_names: Vec::default(),
59        }
60    }
61
62    /// Configure the trust store to use for the TLS context
63    pub fn with_trust_store(mut self, trust_store: TrustStore) -> Self {
64        self.trust_store = trust_store;
65        self
66    }
67
68    /// Configure additional server names to accept during TLS certificate verification.
69    pub fn with_additional_server_names(
70        mut self,
71        additional_server_names: Vec<ServerName>,
72    ) -> Self {
73        self.additional_server_names = additional_server_names;
74        self
75    }
76
77    /// Build a new [TlsContext]
78    pub fn build(self) -> Result<TlsContext, HttpClientError> {
79        Ok(TlsContext {
80            trust_store: self.trust_store,
81            additional_server_names: self.additional_server_names,
82        })
83    }
84}
85
86/// PEM encoded certificate
87#[allow(unused)]
88#[derive(Debug, Clone)]
89struct CertificatePEM(Vec<u8>);
90
91impl From<&[u8]> for CertificatePEM {
92    fn from(value: &[u8]) -> Self {
93        CertificatePEM(value.to_vec())
94    }
95}
96
97/// Container for root certificates able to provide a root-of-trust for connection authentication
98///
99/// Platform native root certificates are enabled by default. To start with a clean trust
100/// store use [TrustStore::empty]
101#[derive(Debug, Clone)]
102pub struct TrustStore {
103    enable_native_roots: bool,
104    custom_certs: Vec<CertificatePEM>,
105}
106
107impl TrustStore {
108    /// Create a new empty trust store
109    pub fn empty() -> Self {
110        Self {
111            enable_native_roots: false,
112            custom_certs: Vec::new(),
113        }
114    }
115
116    /// Enable or disable using the platform's native trusted root certificate store
117    ///
118    /// Default: true
119    pub fn with_native_roots(mut self, enable_native_roots: bool) -> Self {
120        self.enable_native_roots = enable_native_roots;
121        self
122    }
123
124    /// Add the PEM encoded certificate to the trust store
125    ///
126    /// This may be called more than once to add multiple certificates.
127    /// NOTE: PEM certificate contents are not validated until passed to the configured
128    /// TLS provider.
129    pub fn with_pem_certificate(mut self, pem_bytes: impl Into<Vec<u8>>) -> Self {
130        // ideally we'd validate here but rustls-pki-types converts to DER when loading and S2N
131        // still expects PEM encoding. Store the raw bytes and let the TLS implementation validate
132        self.custom_certs.push(CertificatePEM(pem_bytes.into()));
133        self
134    }
135
136    /// Add the PEM encoded certificate to the trust store
137    ///
138    /// This may be called more than once to add multiple certificates.
139    /// NOTE: PEM certificate contents are not validated until passed to the configured
140    /// TLS provider.
141    pub fn add_pem_certificate(&mut self, pem_bytes: impl Into<Vec<u8>>) -> &mut Self {
142        self.custom_certs.push(CertificatePEM(pem_bytes.into()));
143        self
144    }
145}
146
147impl Default for TrustStore {
148    fn default() -> Self {
149        Self {
150            enable_native_roots: true,
151            custom_certs: Vec::new(),
152        }
153    }
154}
155
156/// A server name for TLS connections.
157///
158/// This represents a DNS hostname or IP address used for TLS Server Name
159/// Indication (SNI) and certificate verification.
160///
161/// # Examples
162///
163/// ```
164/// use aws_smithy_http_client::tls::ServerName;
165///
166/// let name = ServerName::try_from("example.com").unwrap();
167/// let ip_name = ServerName::try_from("127.0.0.1").unwrap();
168/// ```
169#[derive(Debug, Clone, PartialEq, Eq, Hash)]
170pub struct ServerName(rustls_pki_types::ServerName<'static>);
171
172/// Error returned when a server name string is invalid.
173#[derive(Debug, Clone, PartialEq, Eq)]
174pub struct InvalidServerName {
175    name: String,
176}
177
178impl fmt::Display for InvalidServerName {
179    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
180        write!(f, "invalid server name: {:?}", self.name)
181    }
182}
183
184impl std::error::Error for InvalidServerName {}
185
186impl TryFrom<String> for ServerName {
187    type Error = InvalidServerName;
188
189    fn try_from(name: String) -> Result<Self, Self::Error> {
190        match rustls_pki_types::ServerName::try_from(name.as_str()) {
191            Ok(sn) => Ok(ServerName(sn.to_owned())),
192            Err(_) => Err(InvalidServerName { name }),
193        }
194    }
195}
196
197impl TryFrom<&str> for ServerName {
198    type Error = InvalidServerName;
199
200    fn try_from(name: &str) -> Result<Self, Self::Error> {
201        match rustls_pki_types::ServerName::try_from(name) {
202            Ok(sn) => Ok(ServerName(sn.to_owned())),
203            Err(_) => Err(InvalidServerName {
204                name: name.to_owned(),
205            }),
206        }
207    }
208}
209
210cfg_rustls! {
211    /// rustls based support and adapters
212    pub mod rustls_provider;
213}
214
215cfg_s2n_tls! {
216    /// s2n-tls based support and adapters
217    pub(crate) mod s2n_tls_provider;
218}