Skip to content

Commit

Permalink
example(bindings): add async ConfigResolver
Browse files Browse the repository at this point in the history
  • Loading branch information
jmayclin committed Mar 27, 2024
1 parent ba825b8 commit d226350
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// 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 }
}

// This method will lookup the appropriate certificates and read them from disc
// in an async manner which won't block the tokio task.
async fn server_config(
&self,
animal: String,
) -> Result<s2n_tls::config::Config, s2n_tls::error::Error> {
let cert_path = format!("{}/{}-chain.pem", self.cert_directory, animal);
let key_path = format!("{}/{}-key.pem", self.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()
}
}

impl ClientHelloCallback for AsyncAnimalConfigResolver {
fn on_client_hello(
&self,
connection: &mut s2n_tls::connection::Connection,
) -> Result<Option<Pin<Box<dyn ConnectionFuture>>>, 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(),
));
}
};

// A ConfigResolver can be constructed from a future that returns
// `Result<Config, s2n_tls::error::Error>`, with the main additional
// requirements that the future is `'static`, which generally means that
// it can't have any interior references. This will prevent you from
// doing something like
// ```
// let config_resolver = ConfigResolver::new(self.server_config(animal));
// ```
// because the compiler will complain that `&self` doesn't live long enough.
//
// One easy way to get around this is to create a new async block that
// owns all of the necessary data. Here we do this by first cloning the
// async resolver and then passing it into a closure which owns all of
// it's data (using the `move` keyword).
let async_resolver_clone = self.clone();
let config_resolver =
ConfigResolver::new(async move { async_resolver_clone.server_config(animal).await });
Ok(Some(Box::pin(config_resolver)))
}
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
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<dyn Error + Send + Sync>>(())
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit d226350

Please sign in to comment.