Skip to content

Commit

Permalink
example(bindings): Add session resumption example
Browse files Browse the repository at this point in the history
  • Loading branch information
goatgoose committed May 28, 2024
1 parent 16efec9 commit aa3db54
Show file tree
Hide file tree
Showing 8 changed files with 228 additions and 0 deletions.
1 change: 1 addition & 0 deletions bindings/rust-examples/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[workspace]
members = [
"client-hello-config-resolution",
"session-resumption",
]
resolver = "2"

Expand Down
12 changes: 12 additions & 0 deletions bindings/rust-examples/session-resumption/Cargo.toml
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"] }
13 changes: 13 additions & 0 deletions bindings/rust-examples/session-resumption/README.md
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
```
20 changes: 20 additions & 0 deletions bindings/rust-examples/session-resumption/certs/generate.sh
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"
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"
13 changes: 13 additions & 0 deletions bindings/rust-examples/session-resumption/certs/test-cert.pem
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-----
6 changes: 6 additions & 0 deletions bindings/rust-examples/session-resumption/certs/test-key.pem
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-----
117 changes: 117 additions & 0 deletions bindings/rust-examples/session-resumption/src/bin/client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
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 {
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 {
let stream = TcpStream::connect("127.0.0.1:9000").await?;
let ip = stream.peer_addr().unwrap().ip();

let builder = ModifiedBuilder::new(config.clone(), |conn| {
// 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(())
}
46 changes: 46 additions & 0 deletions bindings/rust-examples/session-resumption/src/bin/server.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
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();
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>>(())
});
}
}

0 comments on commit aa3db54

Please sign in to comment.