mirror of
https://github.com/kristoferssolo/tls-pq-bench.git
synced 2026-03-21 16:26:22 +00:00
feat(bench-server): add TLS 1.3 with X25519 key exchange
- Generate self-signed certificates on startup using rcgen - Configure rustls with aws_lc_rs crypto provider - Filter key exchange groups to X25519-only for mode=x25519 - Print CA certificate for client trust configuration - TLS 1.3 protocol enforced
This commit is contained in:
140
Cargo.lock
generated
140
Cargo.lock
generated
@@ -112,6 +112,28 @@ version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "aws-lc-rs"
|
||||
version = "1.15.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b7b6141e96a8c160799cc2d5adecd5cbbe5054cb8c7c4af53da0f83bb7ad256"
|
||||
dependencies = [
|
||||
"aws-lc-sys",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-lc-sys"
|
||||
version = "0.37.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c34dda4df7017c8db52132f0f8a2e0f8161649d15723ed63fc00c82d0f2081a"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cmake",
|
||||
"dunce",
|
||||
"fs_extra",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.76"
|
||||
@@ -170,7 +192,9 @@ dependencies = [
|
||||
"bench-common",
|
||||
"clap",
|
||||
"miette",
|
||||
"rustls",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -192,6 +216,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"jobserver",
|
||||
"libc",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
@@ -241,6 +267,15 @@ version = "0.7.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32"
|
||||
|
||||
[[package]]
|
||||
name = "cmake"
|
||||
version = "0.1.57"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.4"
|
||||
@@ -287,6 +322,12 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dunce"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.14"
|
||||
@@ -303,6 +344,12 @@ version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db"
|
||||
|
||||
[[package]]
|
||||
name = "fs_extra"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.17"
|
||||
@@ -314,6 +361,18 @@ dependencies = [
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasip2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.32.3"
|
||||
@@ -344,6 +403,16 @@ version = "1.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
|
||||
dependencies = [
|
||||
"getrandom 0.3.4",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
@@ -495,6 +564,12 @@ dependencies = [
|
||||
"asn1-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell_polyfill"
|
||||
version = "1.70.2"
|
||||
@@ -570,6 +645,12 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "5.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||
|
||||
[[package]]
|
||||
name = "rcgen"
|
||||
version = "0.14.7"
|
||||
@@ -601,7 +682,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"getrandom",
|
||||
"getrandom 0.2.17",
|
||||
"libc",
|
||||
"untrusted",
|
||||
"windows-sys 0.52.0",
|
||||
@@ -635,6 +716,20 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.23.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b"
|
||||
dependencies = [
|
||||
"aws-lc-rs",
|
||||
"once_cell",
|
||||
"rustls-pki-types",
|
||||
"rustls-webpki",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pki-types"
|
||||
version = "1.14.0"
|
||||
@@ -644,6 +739,18 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.103.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53"
|
||||
dependencies = [
|
||||
"aws-lc-rs",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
@@ -752,6 +859,12 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||
|
||||
[[package]]
|
||||
name = "supports-color"
|
||||
version = "3.0.2"
|
||||
@@ -894,6 +1007,16 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-rustls"
|
||||
version = "0.26.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
|
||||
dependencies = [
|
||||
"rustls",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.22"
|
||||
@@ -936,6 +1059,15 @@ version = "0.11.1+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "wasip2"
|
||||
version = "1.0.2+wasi-0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
|
||||
dependencies = [
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
@@ -1098,6 +1230,12 @@ version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
|
||||
|
||||
[[package]]
|
||||
name = "x509-parser"
|
||||
version = "0.18.0"
|
||||
|
||||
@@ -8,17 +8,20 @@ authors = ["Kristofers Solo <dev@kristofers.xyz>"]
|
||||
edition = "2024"
|
||||
|
||||
[workspace.dependencies]
|
||||
aws-lc-rs = "1"
|
||||
bench-common = { path = "bench-common" }
|
||||
claims = "0.8"
|
||||
clap = { version = "4.5", features = ["derive"] }
|
||||
miette = { version = "7", features = ["fancy"] }
|
||||
rcgen = "0.14"
|
||||
rstest = "0.26"
|
||||
rustls = { version = "0.23", default-features = false, features = ["std", "tls12", "aws_lc_rs"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
strum = { version = "0.27", features = ["derive"] }
|
||||
thiserror = "2"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
tokio-rustls = { version = "0.26", default-features = false, features = ["tls12"] }
|
||||
|
||||
[workspace.lints.clippy]
|
||||
nursery = "warn"
|
||||
|
||||
@@ -3,9 +3,8 @@
|
||||
//! Generates a CA certificate and server certificate for TLS benchmarking.
|
||||
//! These certificates are NOT suitable for production use.
|
||||
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||
|
||||
use rcgen::{BasicConstraints, CertificateParams, DnType, IsCa, Issuer, KeyPair, SanType};
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||
|
||||
/// Generated certificate material for TLS server.
|
||||
#[derive(Clone)]
|
||||
|
||||
@@ -6,15 +6,19 @@
|
||||
//!
|
||||
//! Outputs NDJSON records to stdout or a file.
|
||||
|
||||
use bench_common::protocol::{read_payload, write_request};
|
||||
use bench_common::{BenchRecord, KeyExchangeMode};
|
||||
use bench_common::{
|
||||
BenchRecord, KeyExchangeMode,
|
||||
protocol::{read_payload, write_request},
|
||||
};
|
||||
use clap::Parser;
|
||||
use miette::miette;
|
||||
use std::fs::File;
|
||||
use std::io::{BufWriter, Write, stdout};
|
||||
use std::net::SocketAddr;
|
||||
use std::path::PathBuf;
|
||||
use std::time::Instant;
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{BufWriter, Write, stdout},
|
||||
net::SocketAddr,
|
||||
path::PathBuf,
|
||||
time::Instant,
|
||||
};
|
||||
use tokio::net::TcpStream;
|
||||
|
||||
/// TLS benchmark runner.
|
||||
|
||||
@@ -8,7 +8,9 @@ edition.workspace = true
|
||||
bench-common.workspace = true
|
||||
clap.workspace = true
|
||||
miette.workspace = true
|
||||
rustls.workspace = true
|
||||
tokio.workspace = true
|
||||
tokio-rustls.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -1,14 +1,29 @@
|
||||
//! TLS benchmark server.
|
||||
//!
|
||||
//! Listens for connections and serves the benchmark protocol:
|
||||
//! Listens for TLS connections and serves the benchmark protocol:
|
||||
//! - Reads 8-byte little-endian u64 (requested payload size N)
|
||||
//! - Responds with exactly N bytes (deterministic pattern)
|
||||
|
||||
use bench_common::protocol::{read_request, write_payload};
|
||||
use bench_common::KeyExchangeMode;
|
||||
use bench_common::{
|
||||
KeyExchangeMode,
|
||||
cert::{CaCertificate, ServerCertificate},
|
||||
protocol::{read_request, write_payload},
|
||||
};
|
||||
use clap::Parser;
|
||||
use std::net::SocketAddr;
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use miette::miette;
|
||||
use rustls::{
|
||||
ServerConfig,
|
||||
crypto::aws_lc_rs::{self, kx_group},
|
||||
pki_types::{CertificateDer, PrivateKeyDer},
|
||||
server::Acceptor,
|
||||
version::TLS13,
|
||||
};
|
||||
use std::{fmt::Write, io::ErrorKind, net::SocketAddr, sync::Arc};
|
||||
use tokio::{
|
||||
io::AsyncWriteExt,
|
||||
net::{TcpListener, TcpStream},
|
||||
};
|
||||
use tokio_rustls::LazyConfigAcceptor;
|
||||
|
||||
/// TLS benchmark server.
|
||||
#[derive(Debug, Parser)]
|
||||
@@ -23,12 +38,66 @@ struct Args {
|
||||
listen: SocketAddr,
|
||||
}
|
||||
|
||||
async fn handle_connection(mut stream: TcpStream, peer: SocketAddr) {
|
||||
/// Build TLS server config for the given key exchange mode.
|
||||
fn build_tls_config(
|
||||
mode: KeyExchangeMode,
|
||||
server_cert: &ServerCertificate,
|
||||
) -> miette::Result<Arc<ServerConfig>> {
|
||||
// Select crypto provider with appropriate key exchange groups
|
||||
let mut provider = aws_lc_rs::default_provider();
|
||||
provider.kx_groups = match mode {
|
||||
KeyExchangeMode::X25519 => vec![kx_group::X25519],
|
||||
KeyExchangeMode::X25519Mlkem768 => {
|
||||
// TODO: Configure hybrid PQ key exchange
|
||||
return Err(miette!("X25519Mlkem768 not yet implemented"));
|
||||
}
|
||||
};
|
||||
|
||||
// Convert certificate chain
|
||||
let certs: Vec<CertificateDer<'static>> = server_cert
|
||||
.cert_chain_der
|
||||
.iter()
|
||||
.map(|der| CertificateDer::from(der.clone()))
|
||||
.collect();
|
||||
|
||||
// Convert private key
|
||||
let key = PrivateKeyDer::try_from(server_cert.private_key_der.clone())
|
||||
.map_err(|e| miette!("invalid private key: {e}"))?;
|
||||
|
||||
let config = ServerConfig::builder_with_provider(Arc::new(provider))
|
||||
.with_protocol_versions(&[&TLS13])
|
||||
.map_err(|e| miette!("failed to set TLS versions: {e}"))?
|
||||
.with_no_client_auth()
|
||||
.with_single_cert(certs, key)
|
||||
.map_err(|e| miette!("failed to configure certificate: {e}"))?;
|
||||
|
||||
Ok(Arc::new(config))
|
||||
}
|
||||
|
||||
async fn handle_connection(stream: TcpStream, peer: SocketAddr, tls_config: Arc<ServerConfig>) {
|
||||
// Perform TLS handshake
|
||||
let acceptor = LazyConfigAcceptor::new(Acceptor::default(), stream);
|
||||
let start_handshake = match acceptor.await {
|
||||
Ok(sh) => sh,
|
||||
Err(e) => {
|
||||
eprintln!("[{peer}] TLS accept error: {e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut tls_stream = match start_handshake.into_stream(tls_config).await {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
eprintln!("[{peer}] TLS handshake error: {e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Handle protocol
|
||||
loop {
|
||||
let payload_size = match read_request(&mut stream).await {
|
||||
let payload_size = match read_request(&mut tls_stream).await {
|
||||
Ok(size) => size,
|
||||
Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => {
|
||||
// Client closed connection
|
||||
Err(e) if e.kind() == ErrorKind::UnexpectedEof => {
|
||||
break;
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -37,20 +106,28 @@ async fn handle_connection(mut stream: TcpStream, peer: SocketAddr) {
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(e) = write_payload(&mut stream, payload_size).await {
|
||||
if let Err(e) = write_payload(&mut tls_stream, payload_size).await {
|
||||
eprintln!("[{peer}] write error: {e}");
|
||||
break;
|
||||
}
|
||||
|
||||
// Flush to ensure data is sent
|
||||
if let Err(e) = tls_stream.flush().await {
|
||||
eprintln!("[{peer}] flush error: {e}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn run_server(args: Args) -> miette::Result<()> {
|
||||
async fn run_server(args: Args, tls_config: Arc<ServerConfig>) -> miette::Result<()> {
|
||||
let listener = TcpListener::bind(args.listen)
|
||||
.await
|
||||
.map_err(|e| miette::miette!("failed to bind to {}: {e}", args.listen))?;
|
||||
.map_err(|e| miette!("failed to bind to {}: {e}", args.listen))?;
|
||||
|
||||
eprintln!("Listening on {} (TCP, TLS disabled)", args.listen);
|
||||
eprintln!("Mode: {} (not yet implemented)", args.mode);
|
||||
eprintln!(
|
||||
"Listening on {} (TLS 1.3, mode: {})",
|
||||
args.listen, args.mode
|
||||
);
|
||||
|
||||
loop {
|
||||
let (stream, peer) = match listener.accept().await {
|
||||
@@ -61,7 +138,8 @@ async fn run_server(args: Args) -> miette::Result<()> {
|
||||
}
|
||||
};
|
||||
|
||||
tokio::spawn(handle_connection(stream, peer));
|
||||
let config = tls_config.clone();
|
||||
tokio::spawn(handle_connection(stream, peer, config));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,5 +152,53 @@ async fn main() -> miette::Result<()> {
|
||||
eprintln!(" listen: {}", args.listen);
|
||||
eprintln!();
|
||||
|
||||
run_server(args).await
|
||||
// Generate certificates
|
||||
eprintln!("Generating self-signed certificates...");
|
||||
let ca = CaCertificate::generate().map_err(|e| miette!("failed to generate CA: {e}"))?;
|
||||
let server_cert = ca
|
||||
.sign_server_cert("localhost")
|
||||
.map_err(|e| miette!("failed to generate server cert: {e}"))?;
|
||||
|
||||
// Build TLS config
|
||||
let tls_config = build_tls_config(args.mode, &server_cert)?;
|
||||
|
||||
// Print CA certificate for client configuration
|
||||
eprintln!("CA certificate (base64 DER):");
|
||||
eprintln!("{}", base64_encode(&ca.cert_der));
|
||||
eprintln!();
|
||||
|
||||
run_server(args, tls_config).await
|
||||
}
|
||||
|
||||
/// Simple base64 encoding for certificate display.
|
||||
fn base64_encode(data: &[u8]) -> String {
|
||||
const ALPHABET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
let mut result = String::new();
|
||||
for chunk in data.chunks(3) {
|
||||
let mut n = 0u32;
|
||||
for (i, &byte) in chunk.iter().enumerate() {
|
||||
n |= u32::from(byte) << (16 - 8 * i);
|
||||
}
|
||||
|
||||
for i in 0..=chunk.len() {
|
||||
let idx = ((n >> (18 - 6 * i)) & 0x3F) as usize;
|
||||
result.push(ALPHABET[idx] as char);
|
||||
}
|
||||
|
||||
for _ in chunk.len()..3 {
|
||||
result.push('=');
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap at 76 characters
|
||||
let mut wrapped = String::new();
|
||||
for (i, c) in result.chars().enumerate() {
|
||||
if i > 0 && i % 76 == 0 {
|
||||
let _ = writeln!(wrapped);
|
||||
}
|
||||
wrapped.push(c);
|
||||
}
|
||||
|
||||
wrapped
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user