mirror of
https://github.com/kristoferssolo/tls-pq-bench.git
synced 2026-03-21 16:26:22 +00:00
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:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -680,7 +680,9 @@ dependencies = [
|
||||
"clap",
|
||||
"common",
|
||||
"miette",
|
||||
"rustls",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -55,6 +55,7 @@ impl fmt::Display for BenchRecord {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[test]
|
||||
fn bench_record_serializes_to_ndjson() {
|
||||
@@ -73,7 +74,6 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn key_exchange_mode_from_str() {
|
||||
use std::str::FromStr;
|
||||
assert_eq!(
|
||||
KeyExchangeMode::from_str("x25519").expect("should parse"),
|
||||
KeyExchangeMode::X25519
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -5,12 +5,12 @@ authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
common.workspace = true
|
||||
clap.workspace = true
|
||||
common.workspace = true
|
||||
miette.workspace = true
|
||||
rustls.workspace = true
|
||||
tokio.workspace = true
|
||||
tokio-rustls.workspace = true
|
||||
tokio.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -13,7 +13,7 @@ use common::{
|
||||
use miette::miette;
|
||||
use rustls::{
|
||||
ServerConfig,
|
||||
crypto::aws_lc_rs::{self, kx_group},
|
||||
crypto::aws_lc_rs::{self, kx_group::X25519},
|
||||
pki_types::{CertificateDer, PrivateKeyDer},
|
||||
server::Acceptor,
|
||||
version::TLS13,
|
||||
@@ -46,7 +46,7 @@ fn build_tls_config(
|
||||
// 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::X25519 => vec![X25519],
|
||||
KeyExchangeMode::X25519Mlkem768 => {
|
||||
todo!("Configure hybrid PQ key exchange")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user