-
Notifications
You must be signed in to change notification settings - Fork 712
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
example(bindings): Add session resumption example #4573
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
[workspace] | ||
members = [ | ||
"client-hello-config-resolution", | ||
"session-resumption", | ||
] | ||
resolver = "2" | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
[package] | ||
name = "session-resumption" | ||
version.workspace = true | ||
authors.workspace = true | ||
publish.workspace = true | ||
license.workspace = true | ||
edition.workspace = true | ||
|
||
[dependencies] | ||
s2n-tls = { path = "../../rust/s2n-tls" } | ||
s2n-tls-tokio = { path = "../../rust/s2n-tls-tokio" } | ||
tokio = { version = "1", features = ["full"] } |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
This example demonstrates the s2n-tls [session resumption](https://aws.github.io/s2n-tls/usage-guide/ch11-resumption.html) feature. First, the client connects to the server and retrieves a session ticket. Then, the client connects to the server again, but uses the retrieved session ticket to resume the previous session. | ||
|
||
This example also demonstrates how the application context can be used to associate arbitrary information, such as an IP address, with an s2n-tls connection. The IP address is used in this example to determine whether a particular session ticket is viable for use on a subsequent connection. | ||
|
||
To run this example, first start the server in one terminal: | ||
``` | ||
cargo run --bin server | ||
``` | ||
|
||
Then run the client in another terminal: | ||
``` | ||
cargo run --bin client | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
#!/usr/bin/env bash | ||
|
||
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
# SPDX-License-Identifier: Apache-2.0 | ||
|
||
# immediately bail if any command fails | ||
set -e | ||
|
||
echo "generating self-signed certificate" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of generating new certificates, should we move the "certs" folder from the |
||
openssl req -new -noenc -x509 \ | ||
-newkey ec \ | ||
-pkeyopt ec_paramgen_curve:P-384 \ | ||
-keyout test-key.pem \ | ||
-out test-cert.pem \ | ||
-days 65536 \ | ||
-SHA384 \ | ||
-subj "/C=US/CN=s2n" \ | ||
-addext "basicConstraints = critical,CA:true" \ | ||
-addext "keyUsage = critical,keyCertSign" \ | ||
-addext "subjectAltName = DNS:127.0.0.1" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
-----BEGIN CERTIFICATE----- | ||
MIIB7zCCAXagAwIBAgIUfg8ui1w1uHtk3bsIWhWitT3cicAwCgYIKoZIzj0EAwMw | ||
GzELMAkGA1UEBhMCVVMxDDAKBgNVBAMMA3MybjAgFw0yNDA1MTUxNTQ4MTBaGA8y | ||
MjAzMTAyMTE1NDgxMFowGzELMAkGA1UEBhMCVVMxDDAKBgNVBAMMA3MybjB2MBAG | ||
ByqGSM49AgEGBSuBBAAiA2IABBaRabOi6hcEixRizyLHhezXXxeJzQmzl+8N21do | ||
4NwVW38mCop94fkRX2yYCPE/NB976zbdbf2NI4fg6TFjMyjiuUfHzbto/9xUCbxU | ||
Vhk/zQs/lQoWwZfmjMLVIEAjlaN5MHcwHQYDVR0OBBYEFEvgyZ9xCcmq66Jv4IWu | ||
J66psVP1MB8GA1UdIwQYMBaAFEvgyZ9xCcmq66Jv4IWuJ66psVP1MA8GA1UdEwEB | ||
/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgIEMBQGA1UdEQQNMAuCCTEyNy4wLjAuMTAK | ||
BggqhkjOPQQDAwNnADBkAjBec3GrlGlPF2Hg3EWP6iFQicbsZJtpZIC0OEpunn57 | ||
vAuvCAD+1PolgVdggV1wyp4CMCwxEse93GCdBXZJb11bA//BAamrmLAaoX/AmqvY | ||
vmUPPK9C3WbBT0IfTYkJG2XXQg== | ||
-----END CERTIFICATE----- |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
-----BEGIN PRIVATE KEY----- | ||
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDCNXvpHT/NBaXX8KIHy | ||
5r62KOLHN9AMb7xCxZtfYIqTNJRdZSCf3hs6kdGMZWa/NoKhZANiAAQWkWmzouoX | ||
BIsUYs8ix4Xs118Xic0Js5fvDdtXaODcFVt/JgqKfeH5EV9smAjxPzQfe+s23W39 | ||
jSOH4OkxYzMo4rlHx827aP/cVAm8VFYZP80LP5UKFsGX5ozC1SBAI5U= | ||
-----END PRIVATE KEY----- |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
use s2n_tls::{ | ||
callbacks::{SessionTicket, SessionTicketCallback}, | ||
connection::{Connection, ModifiedBuilder}, | ||
security::DEFAULT_TLS13, | ||
}; | ||
use s2n_tls_tokio::TlsConnector; | ||
use std::{ | ||
collections::HashMap, | ||
error::Error, | ||
net::IpAddr, | ||
sync::{Arc, Mutex}, | ||
}; | ||
use tokio::{ | ||
io::{AsyncReadExt, AsyncWriteExt}, | ||
net::TcpStream, | ||
}; | ||
|
||
struct ApplicationContext { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit, in this case, we are the application so this naming feels a bit odd. |
||
ip_addr: IpAddr, | ||
tickets_received: u32, | ||
} | ||
|
||
#[derive(Default, Clone)] | ||
pub struct SessionTicketHandler { | ||
session_tickets: Arc<Mutex<HashMap<IpAddr, Vec<u8>>>>, | ||
} | ||
|
||
impl SessionTicketCallback for SessionTicketHandler { | ||
fn on_session_ticket(&self, connection: &mut Connection, session_ticket: &SessionTicket) { | ||
let app_context = connection | ||
.application_context_mut::<ApplicationContext>() | ||
.unwrap(); | ||
|
||
let size = session_ticket.len().unwrap(); | ||
let mut data = vec![0; size]; | ||
session_ticket.data(&mut data).unwrap(); | ||
|
||
// Associate the received session ticket with the connection's IP address. | ||
let mut session_tickets = self.session_tickets.lock().unwrap(); | ||
session_tickets.insert(app_context.ip_addr, data); | ||
|
||
// Indicate that the connection has received a session ticket. | ||
app_context.tickets_received += 1; | ||
} | ||
} | ||
|
||
#[tokio::main] | ||
async fn main() -> Result<(), Box<dyn Error>> { | ||
let cert_path = format!("{}/certs/test-cert.pem", env!("CARGO_MANIFEST_DIR")); | ||
let cert = std::fs::read(cert_path).unwrap(); | ||
|
||
let session_ticket_handler = SessionTicketHandler::default(); | ||
|
||
let config = { | ||
let mut builder = s2n_tls::config::Builder::new(); | ||
builder.set_security_policy(&DEFAULT_TLS13).unwrap(); | ||
builder.trust_pem(&cert).unwrap(); | ||
builder | ||
.set_session_ticket_callback(session_ticket_handler.clone()) | ||
.unwrap(); | ||
builder.enable_session_tickets(true).unwrap(); | ||
builder.build()? | ||
}; | ||
|
||
for connection_idx in 0..3 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: what does |
||
let stream = TcpStream::connect("127.0.0.1:9000").await?; | ||
let ip = stream.peer_addr().unwrap().ip(); | ||
|
||
let builder = ModifiedBuilder::new(config.clone(), |conn| { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd vote for a comment explaining this.
|
||
// Associate the IP address with the new connection. | ||
conn.set_application_context(ApplicationContext { | ||
ip_addr: ip, | ||
tickets_received: 0, | ||
}); | ||
|
||
// If a session ticket exists that corresponds with the IP address, resume the | ||
// connection. | ||
let session_tickets = session_ticket_handler.session_tickets.lock().unwrap(); | ||
if let Some(session_ticket) = session_tickets.get(&ip) { | ||
conn.set_session_ticket(session_ticket)?; | ||
} | ||
|
||
Ok(conn) | ||
}); | ||
let client = TlsConnector::new(builder); | ||
|
||
let handshake = client.connect("127.0.0.1", stream).await; | ||
let mut tls = match handshake { | ||
Ok(tls) => tls, | ||
Err(e) => { | ||
println!("error during handshake: {e}"); | ||
return Ok(()); | ||
} | ||
}; | ||
|
||
let mut response = String::new(); | ||
tls.read_to_string(&mut response).await?; | ||
println!("server response: {response}"); | ||
|
||
tls.shutdown().await?; | ||
|
||
let connection = tls.as_ref(); | ||
if connection_idx == 0 { | ||
assert!(!connection.resumed()); | ||
} else { | ||
assert!(connection.resumed()); | ||
println!("connection resumed!"); | ||
} | ||
|
||
let app_ctx = connection | ||
.application_context::<ApplicationContext>() | ||
.unwrap(); | ||
assert_eq!(app_ctx.tickets_received, 1); | ||
} | ||
|
||
Ok(()) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
use s2n_tls::security::DEFAULT_TLS13; | ||
use s2n_tls_tokio::TlsAcceptor; | ||
use std::{error::Error, time::SystemTime}; | ||
use tokio::{io::AsyncWriteExt, net::TcpListener}; | ||
|
||
const KEY: [u8; 16] = [0; 16]; | ||
const KEY_NAME: [u8; 3] = [1, 3, 4]; | ||
|
||
#[tokio::main] | ||
async fn main() -> Result<(), Box<dyn Error>> { | ||
let cert_path = format!("{}/certs/test-cert.pem", env!("CARGO_MANIFEST_DIR")); | ||
let key_path = format!("{}/certs/test-key.pem", env!("CARGO_MANIFEST_DIR")); | ||
let cert = std::fs::read(cert_path).unwrap(); | ||
let key = std::fs::read(key_path).unwrap(); | ||
|
||
let mut config = s2n_tls::config::Builder::new(); | ||
config.set_security_policy(&DEFAULT_TLS13).unwrap(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since we're already returning |
||
config | ||
.add_session_ticket_key(&KEY_NAME, &KEY, SystemTime::now()) | ||
.unwrap(); | ||
config.load_pem(&cert, &key).unwrap(); | ||
let config = config.build()?; | ||
let server = TlsAcceptor::new(config); | ||
|
||
let listener = TcpListener::bind("0.0.0.0:9000").await?; | ||
loop { | ||
let server = server.clone(); | ||
let (stream, _) = listener.accept().await?; | ||
|
||
tokio::spawn(async move { | ||
let handshake = server.accept(stream).await; | ||
let mut tls = match handshake { | ||
Ok(tls) => tls, | ||
Err(e) => { | ||
println!("error during handshake: {e}"); | ||
return Ok(()); | ||
} | ||
}; | ||
|
||
let _ = tls.write("hello from server.".as_bytes()).await?; | ||
tls.shutdown().await?; | ||
|
||
Ok::<(), Box<dyn Error + Send + Sync>>(()) | ||
}); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yay for more examples 🕺 !
Two broad points
Note that if your goal is to just have an example of using the
set_application_context
method, a doc comment might be sufficient?