From fcc8e256089b26bfa299c83940abca09992fa313 Mon Sep 17 00:00:00 2001 From: stormshield-gt <143998166+stormshield-gt@users.noreply.github.com.> Date: Mon, 28 Oct 2024 08:48:10 +0100 Subject: [PATCH 1/3] crypto: expose negotiated_key_exchange_group in the handshake data --- quinn-proto/src/crypto/rustls.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/quinn-proto/src/crypto/rustls.rs b/quinn-proto/src/crypto/rustls.rs index 02d26a1c6..880346cc6 100644 --- a/quinn-proto/src/crypto/rustls.rs +++ b/quinn-proto/src/crypto/rustls.rs @@ -11,7 +11,7 @@ use rustls::{ client::danger::ServerCertVerifier, pki_types::{CertificateDer, PrivateKeyDer, ServerName}, quic::{Connection, HeaderProtectionKey, KeyChange, PacketKey, Secrets, Suite, Version}, - CipherSuite, + CipherSuite, NamedGroup, }; use crate::{ @@ -69,6 +69,11 @@ impl crypto::Session for TlsSession { .negotiated_cipher_suite() .expect("cipher is negotiated") .suite(), + negotiated_key_exchange_group: self + .inner + .negotiated_key_exchange_group() + .expect("key exchange group is negotiated") + .name(), })) } @@ -263,6 +268,8 @@ pub struct HandshakeData { pub server_name: Option, /// The ciphersuite negotiated with the peer pub negotiated_cipher_suite: CipherSuite, + /// The key exchange group negotiated with the peer + pub negotiated_key_exchange_group: NamedGroup, } /// A QUIC-compatible TLS client configuration From 89d233bc06c75082c10493f84c5775eb201b9a9c Mon Sep 17 00:00:00 2001 From: stormshield-gt <143998166+stormshield-gt@users.noreply.github.com.> Date: Mon, 28 Oct 2024 08:53:23 +0100 Subject: [PATCH 2/3] Add a tests for post-quantum key exchange with severals min MTU --- Cargo.toml | 1 + quinn/Cargo.toml | 5 ++ quinn/tests/many_connections.rs | 10 ++- quinn/tests/post_quantum.rs | 118 ++++++++++++++++++++++++++++++++ 4 files changed, 128 insertions(+), 6 deletions(-) create mode 100644 quinn/tests/post_quantum.rs diff --git a/Cargo.toml b/Cargo.toml index fea4af59a..98423e152 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,7 @@ rustc-hash = "2" rustls = { version = "0.23.5", default-features = false, features = ["std"] } rustls-pemfile = "2" rustls-platform-verifier = "0.4" +rustls-post-quantum = "0.1" serde = { version = "1.0", features = ["derive"] } serde_json = "1" slab = "0.4.6" diff --git a/quinn/Cargo.toml b/quinn/Cargo.toml index 173f1093f..fb05280f9 100644 --- a/quinn/Cargo.toml +++ b/quinn/Cargo.toml @@ -64,6 +64,7 @@ directories-next = { workspace = true } rand = { workspace = true } rcgen = { workspace = true } rustls-pemfile = { workspace = true } +rustls-post-quantum = { workspace = true } clap = { workspace = true } tokio = { workspace = true, features = ["rt", "rt-multi-thread", "time", "macros"] } tracing-subscriber = { workspace = true } @@ -90,6 +91,10 @@ required-features = ["rustls-ring"] name = "connection" required-features = ["rustls-ring"] +[[test]] +name = "post_quantum" +required-features = ["rustls-aws-lc-rs"] + [[bench]] name = "bench" harness = false diff --git a/quinn/tests/many_connections.rs b/quinn/tests/many_connections.rs index 5083487a6..45185c3e9 100644 --- a/quinn/tests/many_connections.rs +++ b/quinn/tests/many_connections.rs @@ -19,12 +19,10 @@ struct Shared { #[test] #[ignore] fn connect_n_nodes_to_1_and_send_1mb_data() { - tracing::subscriber::set_global_default( - tracing_subscriber::FmtSubscriber::builder() - .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) - .finish(), - ) - .unwrap(); + let _ = tracing_subscriber::FmtSubscriber::builder() + .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) + .with_test_writer() + .try_init(); let runtime = Builder::new_current_thread().enable_all().build().unwrap(); let _guard = runtime.enter(); diff --git a/quinn/tests/post_quantum.rs b/quinn/tests/post_quantum.rs new file mode 100644 index 000000000..dd0f4ecdc --- /dev/null +++ b/quinn/tests/post_quantum.rs @@ -0,0 +1,118 @@ +#![cfg(feature = "rustls-aws-lc-rs")] + +use std::{ + error::Error, + net::{Ipv4Addr, SocketAddr}, + sync::Arc, +}; + +use rustls::{ + pki_types::{CertificateDer, PrivatePkcs8KeyDer}, + NamedGroup, +}; +use tracing::info; + +use quinn::{ + crypto::rustls::{HandshakeData, QuicClientConfig, QuicServerConfig}, + Endpoint, +}; + +#[tokio::test] +async fn post_quantum_key_worse_case_header() { + check_post_quantum_key_exchange(1274, 8081).await; +} + +#[tokio::test] +async fn post_quantum_key_exchange_large_mtu() { + check_post_quantum_key_exchange(1433, 8082).await; +} + +async fn check_post_quantum_key_exchange(min_mtu: u16, server_port: u16) { + let _ = tracing_subscriber::FmtSubscriber::builder() + .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) + .with_test_writer() + .try_init(); + + let _ = rustls_post_quantum::provider().install_default(); + let server_addr = (Ipv4Addr::LOCALHOST, server_port).into(); + + let (endpoint, server_cert) = make_server_endpoint(server_addr, min_mtu).unwrap(); + // accept a single connection + tokio::spawn(async move { + let incoming_conn = endpoint.accept().await.unwrap(); + let conn = incoming_conn.await.unwrap(); + info!( + "[server] connection accepted: addr={}", + conn.remote_address() + ); + assert_eq!( + conn.handshake_data() + .unwrap() + .downcast::() + .unwrap() + .negotiated_key_exchange_group, + X25519_KYBER768_DRAFT00 + ) + // Dropping all handles associated with a connection implicitly closes it + }); + + let endpoint = make_client_endpoint((Ipv4Addr::UNSPECIFIED, 0).into(), server_cert).unwrap(); + // connect to server + let connection = endpoint + .connect(server_addr, "localhost") + .unwrap() + .await + .unwrap(); + info!("[client] connected: addr={}", connection.remote_address()); + + // Waiting for a stream will complete with an error when the server closes the connection + let _ = connection.accept_uni().await; + + // Make sure the server has a chance to clean up + endpoint.wait_idle().await; +} + +fn make_client_endpoint( + bind_addr: SocketAddr, + server_cert: CertificateDer<'static>, +) -> Result> { + let mut certs = rustls::RootCertStore::empty(); + certs.add(server_cert)?; + let rustls_config = rustls::ClientConfig::builder() + .with_root_certificates(certs) + .with_no_client_auth(); + + let client_cfg = + quinn::ClientConfig::new(Arc::new(QuicClientConfig::try_from(rustls_config).unwrap())); + let mut endpoint = Endpoint::client(bind_addr)?; + endpoint.set_default_client_config(client_cfg); + Ok(endpoint) +} + +fn make_server_endpoint( + bind_addr: SocketAddr, + min_mtu: u16, +) -> Result<(Endpoint, CertificateDer<'static>), Box> { + let cert = rcgen::generate_simple_self_signed(vec!["localhost".into()]).unwrap(); + let server_cert = CertificateDer::from(cert.cert); + let priv_key = PrivatePkcs8KeyDer::from(cert.key_pair.serialize_der()); + let mut server_config = quinn::ServerConfig::with_crypto(Arc::new( + QuicServerConfig::try_from( + rustls::ServerConfig::builder() + .with_no_client_auth() + .with_single_cert(vec![server_cert.clone()], priv_key.into()) + .unwrap(), + ) + .unwrap(), + )); + + let transport_config = Arc::get_mut(&mut server_config.transport).unwrap(); + transport_config.max_concurrent_uni_streams(0_u8.into()); + transport_config.min_mtu(min_mtu); + + let endpoint = Endpoint::server(server_config, bind_addr)?; + Ok((endpoint, server_cert)) +} + +/// +const X25519_KYBER768_DRAFT00: NamedGroup = NamedGroup::Unknown(0x06399); From b35549f74e8c48768959ee609631c594d62fc1ac Mon Sep 17 00:00:00 2001 From: stormshield-gt <143998166+stormshield-gt@users.noreply.github.com.> Date: Wed, 6 Nov 2024 17:35:53 +0100 Subject: [PATCH 3/3] do not compile aws-lc-rs by default --- .github/workflows/rust.yml | 6 +++--- quinn/Cargo.toml | 9 +++++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 96cf37f6a..5526d2e74 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -64,10 +64,10 @@ jobs: - uses: Swatinem/rust-cache@v2 # Prevent feature unification from selecting *ring* as the crypto provider - run: RUST_BACKTRACE=1 cargo test --manifest-path quinn-proto/Cargo.toml --no-default-features --features rustls-aws-lc-rs - - run: RUST_BACKTRACE=1 cargo test --manifest-path quinn/Cargo.toml --no-default-features --features rustls-aws-lc-rs,runtime-tokio + - run: RUST_BACKTRACE=1 cargo test --manifest-path quinn/Cargo.toml --no-default-features --features rustls-aws-lc-rs,runtime-tokio,__rustls-post-quantum-test # FIPS - run: RUST_BACKTRACE=1 cargo test --manifest-path quinn-proto/Cargo.toml --no-default-features --features rustls-aws-lc-rs-fips - - run: RUST_BACKTRACE=1 cargo test --manifest-path quinn/Cargo.toml --no-default-features --features rustls-aws-lc-rs-fips,runtime-tokio + - run: RUST_BACKTRACE=1 cargo test --manifest-path quinn/Cargo.toml --no-default-features --features rustls-aws-lc-rs-fips,__rustls-post-quantum-test,runtime-tokio msrv: runs-on: ubuntu-latest @@ -174,7 +174,7 @@ jobs: env: RUSTFLAGS: -Dwarnings # skip FIPS features outside of Linux - SKIP_FEATURES: ${{ matrix.os != 'ubuntu-latest' && 'rustls-aws-lc-rs-fips,aws-lc-rs-fips' || '' }} + SKIP_FEATURES: ${{ matrix.os != 'ubuntu-latest' && 'rustls-aws-lc-rs-fips,aws-lc-rs-fips,__rustls-post-quantum-test' || '' }} steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable diff --git a/quinn/Cargo.toml b/quinn/Cargo.toml index fb05280f9..f34c07e2b 100644 --- a/quinn/Cargo.toml +++ b/quinn/Cargo.toml @@ -39,6 +39,11 @@ runtime-smol = ["async-io", "smol"] # Configure `tracing` to log events via `log` if no `tracing` subscriber exists. log = ["tracing/log", "proto/log", "udp/log"] +# Internal (PRIVATE!) features used to aid testing. +# Don't rely on these whatsoever. They may disappear at any time. + +__rustls-post-quantum-test = ["dep:rustls-post-quantum", "rustls-aws-lc-rs"] + [dependencies] async-io = { workspace = true, optional = true } async-std = { workspace = true, optional = true } @@ -49,6 +54,7 @@ rustc-hash = { workspace = true } pin-project-lite = { workspace = true } proto = { package = "quinn-proto", path = "../quinn-proto", version = "0.11.7", default-features = false } rustls = { workspace = true, optional = true } +rustls-post-quantum = { workspace = true, optional = true } smol = { workspace = true, optional = true } socket2 = { workspace = true } thiserror = { workspace = true } @@ -64,7 +70,6 @@ directories-next = { workspace = true } rand = { workspace = true } rcgen = { workspace = true } rustls-pemfile = { workspace = true } -rustls-post-quantum = { workspace = true } clap = { workspace = true } tokio = { workspace = true, features = ["rt", "rt-multi-thread", "time", "macros"] } tracing-subscriber = { workspace = true } @@ -93,7 +98,7 @@ required-features = ["rustls-ring"] [[test]] name = "post_quantum" -required-features = ["rustls-aws-lc-rs"] +required-features = ["__rustls-post-quantum-test"] [[bench]] name = "bench"