-
Notifications
You must be signed in to change notification settings - Fork 724
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
example(bindings): add async ConfigResolver
- Loading branch information
Showing
3 changed files
with
136 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
132 changes: 132 additions & 0 deletions
132
bindings/rust-examples/client-hello-config-resolution/src/bin/async_load_server.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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>>(()) | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters