1#[cfg(all(feature = "tls-ring", feature = "tls-fips"))]
11compile_error!("Only one TLS crypto provider may be enabled. Choose one of: tls-ring, tls-fips");
12
13#[cfg(not(any(feature = "tls-ring", feature = "tls-fips")))]
14compile_error!("tls requires a crypto provider: enable tls-ring or tls-fips");
15
16use futures_util::ready;
19use rustls_pki_types::pem::PemObject;
20use rustls_pki_types::{CertificateDer, PrivateKeyDer};
21use std::fs::File;
22use std::future::Future;
23use std::io::{self, BufReader, Cursor, Read};
24use std::path::{Path, PathBuf};
25use std::pin::Pin;
26use std::sync::Arc;
27use std::task::{Context, Poll};
28use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
29use tokio::net::TcpStream;
30use tokio_rustls::rustls::{Error as TlsError, ServerConfig};
31
32#[derive(Debug)]
34pub enum TlsConfigError {
35 Io(io::Error),
37 CertParseError,
39 InvalidIdentityPem,
41 EmptyKey,
43 UnknownPrivateKeyFormat,
45 InvalidKey(TlsError),
47 IllegalSectionStart(Vec<u8>),
49 IllegalSectionEnd(Vec<u8>),
51}
52
53impl std::fmt::Display for TlsConfigError {
54 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55 match self {
56 TlsConfigError::Io(err) => err.fmt(f),
57 TlsConfigError::CertParseError => write!(f, "failed to parse certificate"),
58 TlsConfigError::InvalidIdentityPem => write!(f, "the identity PEM provided is invalid"),
59 TlsConfigError::UnknownPrivateKeyFormat => {
60 write!(f, "the private key format is unknown")
61 }
62 TlsConfigError::EmptyKey => write!(f, "the key provided is probably missing or empty"),
63 TlsConfigError::InvalidKey(err) => write!(f, "the key provided is invalid, {err}"),
64 TlsConfigError::IllegalSectionStart(line) => {
65 let line = String::from_utf8(line.clone()).unwrap_or_default();
66 write!(f, "illegal section start in PEM at '{line}'")
67 }
68 TlsConfigError::IllegalSectionEnd(end_marker) => {
69 let end_marker = String::from_utf8(end_marker.clone()).unwrap_or_default();
70 write!(f, "illegal section end in PEM at '{end_marker}'")
71 }
72 }
73 }
74}
75
76impl std::error::Error for TlsConfigError {}
77
78pub struct TlsConfigBuilder {
80 cert: Box<dyn Read + Send + Sync>,
81 key: Box<dyn Read + Send + Sync>,
82}
83
84impl std::fmt::Debug for TlsConfigBuilder {
85 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> ::std::fmt::Result {
86 f.debug_struct("TlsConfigBuilder").finish()
87 }
88}
89
90impl TlsConfigBuilder {
91 pub fn new() -> TlsConfigBuilder {
93 TlsConfigBuilder {
94 key: Box::new(io::empty()),
95 cert: Box::new(io::empty()),
96 }
97 }
98
99 pub fn key_path(mut self, path: impl AsRef<Path>) -> Self {
101 self.key = Box::new(LazyFile {
102 path: path.as_ref().into(),
103 file: None,
104 });
105 self
106 }
107
108 pub fn key(mut self, key: &[u8]) -> Self {
110 self.key = Box::new(Cursor::new(Vec::from(key)));
111 self
112 }
113
114 pub fn cert_path(mut self, path: impl AsRef<Path>) -> Self {
116 self.cert = Box::new(LazyFile {
117 path: path.as_ref().into(),
118 file: None,
119 });
120 self
121 }
122
123 pub fn cert(mut self, cert: &[u8]) -> Self {
125 self.cert = Box::new(Cursor::new(Vec::from(cert)));
126 self
127 }
128
129 pub fn build(mut self) -> Result<ServerConfig, TlsConfigError> {
131 let mut cert_rdr = BufReader::new(self.cert);
132 let cert = CertificateDer::pem_reader_iter(&mut cert_rdr)
133 .collect::<Result<Vec<_>, _>>()
134 .map_err(|_e| TlsConfigError::CertParseError)?;
135
136 let mut key_buf = Vec::new();
138 self.key
139 .read_to_end(&mut key_buf)
140 .map_err(TlsConfigError::Io)?;
141
142 if key_buf.is_empty() {
143 return Err(TlsConfigError::EmptyKey);
144 }
145
146 let reader = Cursor::new(key_buf);
147 let key = PrivateKeyDer::from_pem_reader(reader).map_err(|err| match err {
148 rustls_pki_types::pem::Error::Base64Decode(_) => TlsConfigError::InvalidIdentityPem,
149 rustls_pki_types::pem::Error::NoItemsFound => TlsConfigError::EmptyKey,
150 rustls_pki_types::pem::Error::IllegalSectionStart { line } => {
151 TlsConfigError::IllegalSectionStart(line)
152 }
153 rustls_pki_types::pem::Error::MissingSectionEnd { end_marker } => {
154 TlsConfigError::IllegalSectionEnd(end_marker)
155 }
156 rustls_pki_types::pem::Error::Io(err) => {
157 TlsConfigError::Io(io::Error::new(io::ErrorKind::InvalidData, err))
158 }
159 _ => TlsConfigError::InvalidIdentityPem,
160 })?;
161
162 let mut config = ServerConfig::builder()
163 .with_no_client_auth()
164 .with_single_cert(cert, key)
165 .map_err(TlsConfigError::InvalidKey)?;
166 config.alpn_protocols = vec!["h2".into(), "http/1.1".into()];
167 Ok(config)
168 }
169}
170
171impl Default for TlsConfigBuilder {
172 fn default() -> Self {
173 Self::new()
174 }
175}
176
177struct LazyFile {
178 path: PathBuf,
179 file: Option<File>,
180}
181
182impl LazyFile {
183 fn lazy_read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
184 if self.file.is_none() {
185 self.file = Some(File::open(&self.path)?);
186 }
187
188 match self.file.as_mut() {
189 Some(file) => file.read(buf),
190 None => Err(io::Error::other("file handle unavailable after open")),
191 }
192 }
193}
194
195impl Read for LazyFile {
196 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
197 self.lazy_read(buf).map_err(|err| {
198 let kind = err.kind();
199 io::Error::new(
200 kind,
201 format!("error reading file ({:?}): {}", self.path.display(), err),
202 )
203 })
204 }
205}
206
207enum State {
209 Handshaking(tokio_rustls::Accept<TcpStream>),
210 Streaming(tokio_rustls::server::TlsStream<TcpStream>),
211}
212
213pub struct TlsStream {
218 state: State,
219}
220
221impl TlsStream {
222 fn new(stream: TcpStream, config: Arc<ServerConfig>) -> TlsStream {
223 let accept = tokio_rustls::TlsAcceptor::from(config).accept(stream);
224 TlsStream {
225 state: State::Handshaking(accept),
226 }
227 }
228}
229
230impl AsyncRead for TlsStream {
231 fn poll_read(
232 self: Pin<&mut Self>,
233 cx: &mut Context<'_>,
234 buf: &mut ReadBuf<'_>,
235 ) -> Poll<io::Result<()>> {
236 let pin = self.get_mut();
237 match pin.state {
238 State::Handshaking(ref mut accept) => match ready!(Pin::new(accept).poll(cx)) {
239 Ok(mut stream) => {
240 let result = Pin::new(&mut stream).poll_read(cx, buf);
241 pin.state = State::Streaming(stream);
242 result
243 }
244 Err(err) => Poll::Ready(Err(err)),
245 },
246 State::Streaming(ref mut stream) => Pin::new(stream).poll_read(cx, buf),
247 }
248 }
249}
250
251impl AsyncWrite for TlsStream {
252 fn poll_write(
253 self: Pin<&mut Self>,
254 cx: &mut Context<'_>,
255 buf: &[u8],
256 ) -> Poll<io::Result<usize>> {
257 let pin = self.get_mut();
258 match pin.state {
259 State::Handshaking(ref mut accept) => match ready!(Pin::new(accept).poll(cx)) {
260 Ok(mut stream) => {
261 let result = Pin::new(&mut stream).poll_write(cx, buf);
262 pin.state = State::Streaming(stream);
263 result
264 }
265 Err(err) => Poll::Ready(Err(err)),
266 },
267 State::Streaming(ref mut stream) => Pin::new(stream).poll_write(cx, buf),
268 }
269 }
270
271 fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
272 match self.state {
273 State::Handshaking(_) => Poll::Ready(Ok(())),
274 State::Streaming(ref mut stream) => Pin::new(stream).poll_flush(cx),
275 }
276 }
277
278 fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
279 match self.state {
280 State::Handshaking(_) => Poll::Ready(Ok(())),
281 State::Streaming(ref mut stream) => Pin::new(stream).poll_shutdown(cx),
282 }
283 }
284}
285
286#[derive(Clone)]
288pub struct TlsAcceptor {
289 config: Arc<ServerConfig>,
290}
291
292impl TlsAcceptor {
293 pub fn new(config: ServerConfig) -> TlsAcceptor {
295 TlsAcceptor {
296 config: Arc::new(config),
297 }
298 }
299
300 pub async fn accept(&self, stream: TcpStream) -> io::Result<TlsStream> {
306 Ok(TlsStream::new(stream, self.config.clone()))
307 }
308}
309
310#[cfg(test)]
311mod tests {
312 use super::*;
313
314 #[test]
315 fn file_cert_key_rsa_pkcs1() {
316 TlsConfigBuilder::new()
317 .cert_path("tests/tls/local.dev_cert.rsa_pkcs1.pem")
318 .key_path("tests/tls/local.dev_key.rsa_pkcs1.pem")
319 .build()
320 .unwrap();
321 }
322
323 #[test]
324 fn bytes_cert_key_rsa_pkcs1() {
325 let cert = include_str!("../tests/tls/local.dev_cert.rsa_pkcs1.pem");
326 let key = include_str!("../tests/tls/local.dev_key.rsa_pkcs1.pem");
327
328 TlsConfigBuilder::new()
329 .key(key.as_bytes())
330 .cert(cert.as_bytes())
331 .build()
332 .unwrap();
333 }
334
335 #[test]
336 fn file_cert_key_pkcs8() {
337 TlsConfigBuilder::new()
338 .cert_path("tests/tls/local.dev_cert.pkcs8.pem")
339 .key_path("tests/tls/local.dev_key.pkcs8.pem")
340 .build()
341 .unwrap();
342 }
343
344 #[test]
345 fn bytes_cert_key_pkcs8() {
346 let cert = include_str!("../tests/tls/local.dev_cert.pkcs8.pem");
347 let key = include_str!("../tests/tls/local.dev_key.pkcs8.pem");
348
349 TlsConfigBuilder::new()
350 .key(key.as_bytes())
351 .cert(cert.as_bytes())
352 .build()
353 .unwrap();
354 }
355
356 #[test]
357 fn file_cert_key_sec1_ec() {
358 TlsConfigBuilder::new()
359 .cert_path("tests/tls/local.dev_cert.sec1_ec.pem")
360 .key_path("tests/tls/local.dev_key.sec1_ec.pem")
361 .build()
362 .unwrap();
363 }
364
365 #[test]
366 fn bytes_cert_key_sec1_ec() {
367 let cert = include_str!("../tests/tls/local.dev_cert.sec1_ec.pem");
368 let key = include_str!("../tests/tls/local.dev_key.sec1_ec.pem");
369
370 TlsConfigBuilder::new()
371 .key(key.as_bytes())
372 .cert(cert.as_bytes())
373 .build()
374 .unwrap();
375 }
376
377 #[test]
378 fn missing_cert_path_returns_io_error() {
379 let err = TlsConfigBuilder::new()
380 .cert_path("tests/tls/nonexistent_cert.pem")
381 .key_path("tests/tls/local.dev_key.pkcs8.pem")
382 .build()
383 .unwrap_err();
384 assert!(
385 matches!(err, TlsConfigError::CertParseError | TlsConfigError::Io(_)),
386 "expected Io or CertParseError, got: {err}"
387 );
388 }
389
390 #[test]
391 fn missing_key_path_returns_io_error() {
392 let err = TlsConfigBuilder::new()
393 .cert_path("tests/tls/local.dev_cert.pkcs8.pem")
394 .key_path("tests/tls/nonexistent_key.pem")
395 .build()
396 .unwrap_err();
397 assert!(
398 matches!(err, TlsConfigError::Io(_)),
399 "expected Io error, got: {err}"
400 );
401 }
402
403 #[test]
404 fn empty_key_bytes_returns_empty_key_error() {
405 let cert = include_str!("../tests/tls/local.dev_cert.pkcs8.pem");
406 let err = TlsConfigBuilder::new()
407 .cert(cert.as_bytes())
408 .key(b"")
409 .build()
410 .unwrap_err();
411 assert!(
412 matches!(err, TlsConfigError::EmptyKey),
413 "expected EmptyKey error, got: {err}"
414 );
415 }
416
417 #[test]
418 fn mismatched_cert_key_returns_invalid_key_error() {
419 let cert = include_str!("../tests/tls/local.dev_cert.rsa_pkcs1.pem");
421 let key = include_str!("../tests/tls/local.dev_key.sec1_ec.pem");
422 let err = TlsConfigBuilder::new()
423 .cert(cert.as_bytes())
424 .key(key.as_bytes())
425 .build()
426 .unwrap_err();
427 assert!(
428 matches!(err, TlsConfigError::InvalidKey(_)),
429 "expected InvalidKey error for mismatched cert/key, got: {err}"
430 );
431 }
432
433 #[cfg(feature = "tls-fips")]
434 #[test]
435 fn fips_mode_is_active() {
436 aws_lc_rs::try_fips_mode()
437 .expect("FIPS mode should be active when tls-fips feature is enabled");
438 }
439}