Skip to content

Commit ad84ebf

Browse files
jmayclingoatgoose
andauthored
feat(examples): add key log example (#5314)
Co-authored-by: Sam Clark <3758302+goatgoose@users.noreply.github.com>
1 parent 92f7827 commit ad84ebf

File tree

4 files changed

+190
-1
lines changed

4 files changed

+190
-1
lines changed

.github/workflows/ci_rust.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,10 @@ jobs:
193193
working-directory: ${{env.EXAMPLE_WORKSPACE}}
194194
run: cargo build
195195

196+
- name: test examples
197+
working-directory: ${{env.EXAMPLE_WORKSPACE}}
198+
run: cargo test
199+
196200
generate-openssl-102:
197201
runs-on: ubuntu-latest
198202
steps:

bindings/rust-examples/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[workspace]
22
members = [
33
"client-hello-config-resolution",
4-
"hyper-server-client",
4+
"hyper-server-client", "key-logging",
55
"tokio-server-client",
66
]
77
resolver = "2"
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[package]
2+
name = "key-logging"
3+
version.workspace = true
4+
authors.workspace = true
5+
publish.workspace = true
6+
license.workspace = true
7+
edition.workspace = true
8+
9+
[dependencies]
10+
anyhow = "1.0.98"
11+
s2n-tls = { path = "../../rust/extended/s2n-tls" }
12+
s2n-tls-tokio = { path = "../../rust/extended/s2n-tls-tokio" }
13+
tokio = { version = "1", features = ["full"] }
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
//! This example shows how to setup key logging for our rust bindings.
5+
//!
6+
//! This builds off the the basic client and server configuration, so be sure to
7+
//! first check out the `tokio-server-client` example if you are generally unfamiliar
8+
//! with s2n-tls APIs.
9+
10+
use s2n_tls::ffi::*;
11+
use std::{
12+
ffi::{self},
13+
fs::{File, OpenOptions},
14+
io::{BufWriter, Write},
15+
sync::{Arc, Mutex},
16+
};
17+
18+
pub type KeyLogHandle = Arc<TlsKeyLogger>;
19+
20+
/// The TlsKeyLogger can be used to log the keys from a TLS session, which can
21+
/// then be used to decrypt the TLS session with a tool like [wireshark](https://wiki.wireshark.org/TLS).
22+
/// This is incredibly useful when attempting to debug failures in TLS connections.
23+
pub struct TlsKeyLogger(Mutex<BufWriter<File>>);
24+
25+
impl TlsKeyLogger {
26+
/// Use `from_env` when you want to set the path at runtime. The keys will be
27+
/// written to the path contained in the `SSLKEYLOGFILE` environment variable
28+
/// ```text
29+
/// SSLKEYLOGFILE=my_secrets.key ./my_tls_application_binary
30+
/// ```
31+
pub fn from_env() -> Option<KeyLogHandle> {
32+
let path = std::env::var("SSLKEYLOGFILE").ok()?;
33+
Self::from_path(&path).ok()
34+
}
35+
36+
pub fn from_path(path: &str) -> std::io::Result<KeyLogHandle> {
37+
let file = OpenOptions::new().append(true).create(true).open(path)?;
38+
let file = BufWriter::new(file);
39+
let file = Mutex::new(file);
40+
let keylog = Self(file);
41+
let keylog = Arc::new(keylog);
42+
Ok(keylog)
43+
}
44+
45+
pub unsafe extern "C" fn callback(
46+
ctx: *mut ffi::c_void,
47+
_conn: *mut s2n_connection,
48+
logline: *mut u8,
49+
len: usize,
50+
) -> ffi::c_int {
51+
let handle = &mut *(ctx as *mut Self);
52+
let logline = core::slice::from_raw_parts(logline, len);
53+
54+
// ignore any errors
55+
let _ = handle.on_logline(logline);
56+
57+
0
58+
}
59+
60+
fn on_logline(&mut self, logline: &[u8]) -> Option<()> {
61+
let mut file = self.0.lock().ok()?;
62+
file.write_all(logline).ok()?;
63+
file.write_all(b"\n").ok()?;
64+
65+
// ensure keys are immediately written so tools can use them
66+
file.flush().ok()?;
67+
68+
Some(())
69+
}
70+
}
71+
72+
#[cfg(test)]
73+
mod tests {
74+
use s2n_tls::{config::Config, security::DEFAULT_TLS13};
75+
use s2n_tls_tokio::{TlsAcceptor, TlsConnector};
76+
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
77+
use tokio::{
78+
io::{AsyncReadExt, AsyncWriteExt},
79+
net::{TcpListener, TcpStream},
80+
};
81+
82+
use super::*;
83+
84+
/// NOTE: these materials are to be used for demonstration purposes only!
85+
const CA: &[u8] = include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/../certs/ca-cert.pem"));
86+
const CHAIN: &[u8] = include_bytes!(concat!(
87+
env!("CARGO_MANIFEST_DIR"),
88+
"/../certs/localhost-chain.pem"
89+
));
90+
const KEY: &[u8] = include_bytes!(concat!(
91+
env!("CARGO_MANIFEST_DIR"),
92+
"/../certs/localhost-key.pem"
93+
));
94+
const SERVER_MESSAGE: &[u8] = b"hello world";
95+
96+
async fn launch_server() -> anyhow::Result<SocketAddr> {
97+
let mut config = Config::builder();
98+
config.set_security_policy(&DEFAULT_TLS13)?;
99+
config.load_pem(CHAIN, KEY)?;
100+
config.set_max_blinding_delay(0)?;
101+
102+
let server = TlsAcceptor::new(config.build()?);
103+
104+
let listener = TcpListener::bind(&SocketAddrV4::new(Ipv4Addr::LOCALHOST, 0)).await?;
105+
let addr = listener.local_addr()?;
106+
107+
tokio::spawn(async move {
108+
loop {
109+
let (stream, _peer_addr) = listener.accept().await.unwrap();
110+
111+
let server = server.clone();
112+
tokio::spawn(async move {
113+
let mut tls = server.accept(stream).await?;
114+
tls.write_all(SERVER_MESSAGE).await?;
115+
tls.shutdown().await?;
116+
Ok::<(), anyhow::Error>(())
117+
});
118+
}
119+
});
120+
121+
Ok(addr)
122+
}
123+
124+
#[tokio::test]
125+
async fn client_key_logging() -> anyhow::Result<()> {
126+
const KEY_PATH: &str = "s2n_client.keys";
127+
128+
// do some tls stuff
129+
{
130+
let key_logger = TlsKeyLogger::from_path(KEY_PATH).unwrap();
131+
132+
let mut client_config = s2n_tls::config::Builder::new();
133+
client_config.trust_pem(CA)?;
134+
client_config.set_security_policy(&DEFAULT_TLS13)?;
135+
unsafe {
136+
// The s2n-tls API currently requires a raw C callback and a raw C "context"
137+
// pointer, although we have plans to improve this in the future:
138+
// https://github.com/aws/s2n-tls/issues/4805. (Please +1 if interested)
139+
//
140+
// The callback is the "extern C" function that we defined for the TlsKeyLogger,
141+
// and we get the underlying pointer to the KeyLogger to use as the
142+
// context pointer.
143+
client_config.set_key_log_callback(
144+
Some(TlsKeyLogger::callback),
145+
Arc::as_ptr(&key_logger) as *mut _,
146+
)
147+
}?;
148+
let server_addr = launch_server().await?;
149+
150+
let client = TlsConnector::new(client_config.build()?);
151+
println!("connecting TCP stream");
152+
let stream = TcpStream::connect(server_addr).await?;
153+
154+
let mut tls = client.connect("localhost", stream).await.unwrap();
155+
let mut buffer = [0; SERVER_MESSAGE.len()];
156+
tls.read_exact(&mut buffer).await?;
157+
assert_eq!(buffer, SERVER_MESSAGE);
158+
}
159+
160+
// the keys are now available
161+
{
162+
let keys = std::fs::read_to_string(KEY_PATH)?;
163+
assert!(keys.contains("CLIENT_HANDSHAKE_TRAFFIC_SECRET"));
164+
assert!(keys.contains("SERVER_HANDSHAKE_TRAFFIC_SECRET"));
165+
}
166+
167+
// clean up after ourselves
168+
std::fs::remove_file(KEY_PATH)?;
169+
170+
Ok(())
171+
}
172+
}

0 commit comments

Comments
 (0)