diff --git a/bindings/rust-examples/client-hello-config-resolution/README.md b/bindings/rust-examples/client-hello-config-resolution/README.md index a0e22f99629..302770d5dae 100644 --- a/bindings/rust-examples/client-hello-config-resolution/README.md +++ b/bindings/rust-examples/client-hello-config-resolution/README.md @@ -41,3 +41,6 @@ TlsStream { The server said Hello, you are speaking to www.wombat.com ``` Once again there is a successful handshake showing that the server responded with the proper certificate. In this case, the config that the server configured for `www.wombat.com` did not support TLS 1.3, so the TLS 1.2 was negotiated instead. + +## Async Config Resolution +The [async load server](src/bin/async_load_server.rs) has the same functionality as the default [server](src/bin/server.rs), but implements the config resolution in an asynchronous manner. This allows the certificates to be loaded from disk without blocking the tokio runtime. A similar technique could be used to retrieve certificates over the network without blocking the runtime. diff --git a/bindings/rust-examples/client-hello-config-resolution/src/bin/async_load_server.rs b/bindings/rust-examples/client-hello-config-resolution/src/bin/async_load_server.rs new file mode 100644 index 00000000000..177c6ffc0de --- /dev/null +++ b/bindings/rust-examples/client-hello-config-resolution/src/bin/async_load_server.rs @@ -0,0 +1,123 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +use s2n_tls::{ + callbacks::{ClientHelloCallback, ConfigResolver, ConnectionFuture}, + security::{Policy, DEFAULT_TLS13}, +}; +use s2n_tls_tokio::TlsAcceptor; +use std::{error::Error, pin::Pin}; +use tokio::{io::AsyncWriteExt, net::*, try_join}; + +const PORT: u16 = 1738; + +#[derive(Clone)] +pub struct AsyncAnimalConfigResolver { + // the directory that contains the relevant certs + cert_directory: String, +} + +impl AsyncAnimalConfigResolver { + fn new(cert_directory: String) -> Self { + AsyncAnimalConfigResolver { cert_directory } + } +} + +impl ClientHelloCallback for AsyncAnimalConfigResolver { + fn on_client_hello( + &self, + connection: &mut s2n_tls::connection::Connection, + ) -> Result>>, s2n_tls::error::Error> { + let sni = match connection.server_name() { + Some(sni) => sni, + None => { + println!("connection contained no SNI"); + return Err(s2n_tls::error::Error::application("no sni".into())); + } + }; + + // simple, limited logic to parse "animal" from "www.animal.com". + let mut tokens = sni.split('.'); + tokens.next(); // "www" + let animal = match tokens.next() { + Some(animal) => animal.to_owned(), // "animal" + None => { + println!("unable to parse sni"); + return Err(s2n_tls::error::Error::application( + format!("unable to parse sni: {}", sni).into(), + )); + } + }; + + let config_future = server_config(animal, self.cert_directory.clone()); + let config_resolver = ConfigResolver::new(config_future); + connection.server_name_extension_used(); + Ok(Some(Box::pin(config_resolver))) + } +} + +// This method will lookup the appropriate certificates and read them from disk +// in an async manner which won't block the tokio task. +// +// Note that this method takes `String` instead of `&str` like the synchronous +// version in server.rs. ConfigResolver requires a future that is `'static`. +async fn server_config( + animal: String, + cert_directory: String, +) -> Result { + println!("asynchronously setting connection config associated with {animal}"); + + let cert_path = format!("{}/{}-chain.pem", cert_directory, animal); + let key_path = format!("{}/{}-key.pem", cert_directory, animal); + // we asynchronously read the cert chain and key from disk + let (cert, key) = try_join!(tokio::fs::read(cert_path), tokio::fs::read(key_path)) + // we map any IO errors to the s2n-tls Error type, as required by the ConfigResolver bounds. + .map_err(|io_error| s2n_tls::error::Error::application(Box::new(io_error)))?; + + let mut config = s2n_tls::config::Builder::new(); + // we can set different policies for different configs. "20190214" doesn't + // support TLS 1.3, so any customer requesting www.wombat.com won't be able + // to negotiate TLS 1.3 + let security_policy = match animal.as_str() { + "wombat" => Policy::from_version("20190214")?, + _ => DEFAULT_TLS13, + }; + config.set_security_policy(&security_policy)?; + config.load_pem(&cert, &key)?; + config.build() +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + let cert_directory = format!("{}/certs", env!("CARGO_MANIFEST_DIR")); + let resolver = AsyncAnimalConfigResolver::new(cert_directory); + let mut initial_config = s2n_tls::config::Builder::new(); + initial_config.set_client_hello_callback(resolver)?; + + let server = TlsAcceptor::new(initial_config.build()?); + + let listener = TcpListener::bind(&format!("0.0.0.0:{PORT}")).await?; + loop { + let server = server.clone(); + let (stream, _) = listener.accept().await?; + tokio::spawn(async move { + // handshake with the client + let handshake = server.accept(stream).await; + let mut tls = match handshake { + Ok(tls) => tls, + Err(e) => { + println!("error during handshake: {:?}", e); + return Ok(()); + } + }; + + let connection = tls.as_ref(); + let offered_sni = connection.server_name().unwrap(); + let _ = tls + .write(format!("Hello, you are speaking to {offered_sni}").as_bytes()) + .await?; + tls.shutdown().await?; + Ok::<(), Box>(()) + }); + } +} diff --git a/bindings/rust-examples/client-hello-config-resolution/src/bin/server.rs b/bindings/rust-examples/client-hello-config-resolution/src/bin/server.rs index 7ad887cfa0b..958fdb51633 100644 --- a/bindings/rust-examples/client-hello-config-resolution/src/bin/server.rs +++ b/bindings/rust-examples/client-hello-config-resolution/src/bin/server.rs @@ -30,6 +30,7 @@ impl Default for AnimalConfigResolver { // using the ConfigResolver: https://docs.rs/s2n-tls/latest/s2n_tls/callbacks/struct.ConfigResolver.html# // This is useful if servers need to read from disk or make network calls as part // of the configuration, and want to avoid blocking the tokio task while doing so. +// An example of this implementation is contained in the "async_load_server". impl ClientHelloCallback for AnimalConfigResolver { fn on_client_hello( &self,