feat(runner): add TLS 1.3 client with X25519

- Configure rustls client with aws_lc_rs, X25519-only key exchange
- Skip certificate verification for benchmarking (NoVerifier)
- Measure TLS handshake latency (TCP + TLS combined)
- TLS 1.3 protocol enforced
This commit is contained in:
2026-01-28 18:27:08 +02:00
parent e7c97070ca
commit b03cc2a386
6 changed files with 131 additions and 17 deletions

View File

@@ -5,9 +5,11 @@ authors.workspace = true
edition.workspace = true
[dependencies]
common.workspace = true
clap.workspace = true
common.workspace = true
miette.workspace = true
rustls.workspace = true
tokio-rustls.workspace = true
tokio.workspace = true
[lints]

View File

@@ -12,14 +12,23 @@ use common::{
protocol::{read_payload, write_request},
};
use miette::miette;
use rustls::{
ClientConfig, DigitallySignedStruct, SignatureScheme,
client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
crypto::aws_lc_rs::{self, kx_group::X25519},
pki_types::{CertificateDer, ServerName, UnixTime},
version::TLS13,
};
use std::{
fs::File,
io::{BufWriter, Write, stdout},
net::SocketAddr,
path::PathBuf,
sync::Arc,
time::Instant,
};
use tokio::net::TcpStream;
use tokio_rustls::TlsConnector;
/// TLS benchmark runner.
#[derive(Debug, Parser)]
@@ -60,25 +69,108 @@ struct IterationResult {
ttlb_ns: u64,
}
/// Run a single benchmark iteration over plain TCP.
/// Certificate verifier that accepts any certificate.
/// Used for benchmarking where we don't need to verify the server's identity.
#[derive(Debug)]
struct NoVerifier;
impl ServerCertVerifier for NoVerifier {
fn verify_server_cert(
&self,
_end_entity: &CertificateDer<'_>,
_intermediates: &[CertificateDer<'_>],
_server_name: &ServerName<'_>,
_ocsp_response: &[u8],
_now: UnixTime,
) -> Result<ServerCertVerified, rustls::Error> {
Ok(ServerCertVerified::assertion())
}
fn verify_tls12_signature(
&self,
_message: &[u8],
_cert: &CertificateDer<'_>,
_dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, rustls::Error> {
Ok(HandshakeSignatureValid::assertion())
}
fn verify_tls13_signature(
&self,
_message: &[u8],
_cert: &CertificateDer<'_>,
_dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, rustls::Error> {
Ok(HandshakeSignatureValid::assertion())
}
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
vec![
SignatureScheme::ECDSA_NISTP256_SHA256,
SignatureScheme::ECDSA_NISTP384_SHA384,
SignatureScheme::ECDSA_NISTP521_SHA512,
SignatureScheme::ED25519,
SignatureScheme::RSA_PSS_SHA256,
SignatureScheme::RSA_PSS_SHA384,
SignatureScheme::RSA_PSS_SHA512,
SignatureScheme::RSA_PKCS1_SHA256,
SignatureScheme::RSA_PKCS1_SHA384,
SignatureScheme::RSA_PKCS1_SHA512,
]
}
}
/// Build TLS client config for the given key exchange mode.
fn build_tls_config(mode: KeyExchangeMode) -> miette::Result<Arc<ClientConfig>> {
// Select crypto provider with appropriate key exchange groups
let mut provider = aws_lc_rs::default_provider();
provider.kx_groups = match mode {
KeyExchangeMode::X25519 => vec![X25519],
KeyExchangeMode::X25519Mlkem768 => {
todo!("Configure hybrid PQ key exchange")
}
};
let config = ClientConfig::builder_with_provider(Arc::new(provider))
.with_protocol_versions(&[&TLS13])
.map_err(|e| miette!("failed to set TLS versions: {e}"))?
.dangerous()
.with_custom_certificate_verifier(Arc::new(NoVerifier))
.with_no_client_auth();
Ok(Arc::new(config))
}
/// Run a single benchmark iteration over TLS.
#[allow(clippy::cast_possible_truncation)] // nanoseconds won't overflow u64 for reasonable durations
async fn run_iteration(server: SocketAddr, payload_bytes: u64) -> miette::Result<IterationResult> {
async fn run_iteration(
server: SocketAddr,
payload_bytes: u64,
tls_connector: &TlsConnector,
server_name: &ServerName<'static>,
) -> miette::Result<IterationResult> {
let start = Instant::now();
// Connect (this is the "handshake" for plain TCP)
let mut stream = TcpStream::connect(server)
// TCP connect
let stream = TcpStream::connect(server)
.await
.map_err(|e| miette!("connection failed: {e}"))?;
.map_err(|e| miette!("TCP connection failed: {e}"))?;
// TLS handshake
let mut tls_stream = tls_connector
.connect(server_name.clone(), stream)
.await
.map_err(|e| miette!("TLS handshake failed: {e}"))?;
let handshake_ns = start.elapsed().as_nanos() as u64;
// Send request
write_request(&mut stream, payload_bytes)
write_request(&mut tls_stream, payload_bytes)
.await
.map_err(|e| miette!("write request failed: {e}"))?;
// Read response
read_payload(&mut stream, payload_bytes)
read_payload(&mut tls_stream, payload_bytes)
.await
.map_err(|e| miette!("read payload failed: {e}"))?;
@@ -90,7 +182,11 @@ async fn run_iteration(server: SocketAddr, payload_bytes: u64) -> miette::Result
})
}
async fn run_benchmark(args: Args) -> miette::Result<()> {
async fn run_benchmark(
args: Args,
tls_connector: TlsConnector,
server_name: ServerName<'static>,
) -> miette::Result<()> {
let total_iters = args.warmup + args.iters;
// Open output file or use stdout
@@ -104,7 +200,7 @@ async fn run_benchmark(args: Args) -> miette::Result<()> {
};
eprintln!(
"Running {} warmup + {} measured iterations (concurrency: {}, TLS disabled)",
"Running {} warmup + {} measured iterations (concurrency: {}, TLS 1.3)",
args.warmup, args.iters, args.concurrency
);
eprintln!();
@@ -113,7 +209,13 @@ async fn run_benchmark(args: Args) -> miette::Result<()> {
for i in 0..total_iters {
let is_warmup = i < args.warmup;
let result = run_iteration(args.server, args.payload_bytes).await?;
let result = run_iteration(
args.server,
args.payload_bytes,
&tls_connector,
&server_name,
)
.await?;
if !is_warmup {
let record = BenchRecord {
@@ -159,5 +261,13 @@ async fn main() -> miette::Result<()> {
);
eprintln!();
run_benchmark(args).await
// Build TLS config (skips certificate verification for benchmarking)
let tls_config = build_tls_config(args.mode)?;
let tls_connector = TlsConnector::from(tls_config);
// Server name for TLS (use "localhost" for local testing)
let server_name = ServerName::try_from("localhost".to_string())
.map_err(|e| miette!("invalid server name: {e}"))?;
run_benchmark(args, tls_connector, server_name).await
}