From 991e5334e8a3cd834bee2dc8193e8965fcff72ce Mon Sep 17 00:00:00 2001 From: Sam Clark <3758302+goatgoose@users.noreply.github.com> Date: Tue, 28 May 2024 10:46:49 -0400 Subject: [PATCH] example(bindings): Add session resumption example --- bindings/rust-examples/Cargo.toml | 1 + .../session-resumption/Cargo.toml | 12 ++ .../session-resumption/README.md | 13 ++ .../session-resumption/certs/generate.sh | 20 +++ .../session-resumption/certs/test-cert.pem | 13 ++ .../session-resumption/certs/test-key.pem | 6 + .../session-resumption/src/bin/client.rs | 120 ++++++++++++++++++ .../session-resumption/src/bin/server.rs | 49 +++++++ 8 files changed, 234 insertions(+) create mode 100644 bindings/rust-examples/session-resumption/Cargo.toml create mode 100644 bindings/rust-examples/session-resumption/README.md create mode 100755 bindings/rust-examples/session-resumption/certs/generate.sh create mode 100644 bindings/rust-examples/session-resumption/certs/test-cert.pem create mode 100644 bindings/rust-examples/session-resumption/certs/test-key.pem create mode 100644 bindings/rust-examples/session-resumption/src/bin/client.rs create mode 100644 bindings/rust-examples/session-resumption/src/bin/server.rs diff --git a/bindings/rust-examples/Cargo.toml b/bindings/rust-examples/Cargo.toml index 0b4ca8be1aa..a7b5301bd34 100644 --- a/bindings/rust-examples/Cargo.toml +++ b/bindings/rust-examples/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ "client-hello-config-resolution", + "session-resumption", ] resolver = "2" diff --git a/bindings/rust-examples/session-resumption/Cargo.toml b/bindings/rust-examples/session-resumption/Cargo.toml new file mode 100644 index 00000000000..38160d76af9 --- /dev/null +++ b/bindings/rust-examples/session-resumption/Cargo.toml @@ -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"] } diff --git a/bindings/rust-examples/session-resumption/README.md b/bindings/rust-examples/session-resumption/README.md new file mode 100644 index 00000000000..950509ff374 --- /dev/null +++ b/bindings/rust-examples/session-resumption/README.md @@ -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 +``` diff --git a/bindings/rust-examples/session-resumption/certs/generate.sh b/bindings/rust-examples/session-resumption/certs/generate.sh new file mode 100755 index 00000000000..41a153ef6ac --- /dev/null +++ b/bindings/rust-examples/session-resumption/certs/generate.sh @@ -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" diff --git a/bindings/rust-examples/session-resumption/certs/test-cert.pem b/bindings/rust-examples/session-resumption/certs/test-cert.pem new file mode 100644 index 00000000000..60c096f48cd --- /dev/null +++ b/bindings/rust-examples/session-resumption/certs/test-cert.pem @@ -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----- diff --git a/bindings/rust-examples/session-resumption/certs/test-key.pem b/bindings/rust-examples/session-resumption/certs/test-key.pem new file mode 100644 index 00000000000..70924f9d8be --- /dev/null +++ b/bindings/rust-examples/session-resumption/certs/test-key.pem @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDCNXvpHT/NBaXX8KIHy +5r62KOLHN9AMb7xCxZtfYIqTNJRdZSCf3hs6kdGMZWa/NoKhZANiAAQWkWmzouoX +BIsUYs8ix4Xs118Xic0Js5fvDdtXaODcFVt/JgqKfeH5EV9smAjxPzQfe+s23W39 +jSOH4OkxYzMo4rlHx827aP/cVAm8VFYZP80LP5UKFsGX5ozC1SBAI5U= +-----END PRIVATE KEY----- diff --git a/bindings/rust-examples/session-resumption/src/bin/client.rs b/bindings/rust-examples/session-resumption/src/bin/client.rs new file mode 100644 index 00000000000..7fbd75e5994 --- /dev/null +++ b/bindings/rust-examples/session-resumption/src/bin/client.rs @@ -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 { + ip_addr: IpAddr, + tickets_received: u32, +} + +#[derive(Default, Clone)] +pub struct SessionTicketHandler { + session_tickets: Arc>>>, +} + +impl SessionTicketCallback for SessionTicketHandler { + fn on_session_ticket(&self, connection: &mut Connection, session_ticket: &SessionTicket) { + let app_context = connection + .application_context_mut::() + .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> { + 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::() + .unwrap(); + assert_eq!(app_ctx.tickets_received, 1); + } + + Ok(()) +} diff --git a/bindings/rust-examples/session-resumption/src/bin/server.rs b/bindings/rust-examples/session-resumption/src/bin/server.rs new file mode 100644 index 00000000000..deb2b12da28 --- /dev/null +++ b/bindings/rust-examples/session-resumption/src/bin/server.rs @@ -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> { + 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>(()) + }); + } +}