diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 07d8fe2b7e..3e9d7ec271 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -142,7 +142,7 @@ jobs: - run: cargo test --locked -p quinn-proto --target wasm32-unknown-unknown --no-run - run: cargo check --locked -p quinn-udp --target wasm32-unknown-unknown --no-default-features --features=tracing,log - - run: cargo rustc --locked -p quinn --target wasm32-unknown-unknown --no-default-features --features=tracing-log,platform-verifier,rustls-ring --crate-type=cdylib + - run: cargo rustc --locked -p quinn --target wasm32-unknown-unknown --no-default-features --features=tracing-log,rustls-ring --crate-type=cdylib # If the Wasm file contains any 'import "env"' declarations, then # some non-Wasm-compatible code made it into the final code. diff --git a/Cargo.lock b/Cargo.lock index 7c69d5de27..1cdf0cc4c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -387,6 +387,7 @@ dependencies = [ "quinn", "rcgen", "rustls", + "rustls-aws-lc-rs", "tokio", "tracing", "tracing-subscriber", @@ -455,6 +456,7 @@ dependencies = [ "quinn", "rcgen", "rustls", + "rustls-aws-lc-rs", ] [[package]] @@ -1604,9 +1606,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.31" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "113b30b4cd05f7c06868fdb2854f66a7b9fece9a48425351cd532e810d74024f" +checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5" [[package]] name = "lru-slab" @@ -1842,6 +1844,8 @@ dependencies = [ "quinn-proto", "rcgen", "rustls", + "rustls-aws-lc-rs", + "rustls-util", "serde", "serde_json", "socket2", @@ -2005,6 +2009,9 @@ dependencies = [ "rcgen", "rustc-hash", "rustls", + "rustls-aws-lc-rs", + "rustls-ring", + "rustls-util", "smol", "socket2", "thiserror 2.0.18", @@ -2035,8 +2042,10 @@ dependencies = [ "ring", "rustc-hash", "rustls", + "rustls-aws-lc-rs", "rustls-pki-types", "rustls-platform-verifier", + "rustls-ring", "slab", "thiserror 2.0.18", "tinyvec", @@ -2217,25 +2226,34 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f9466fb2c14ea04357e91413efb882e2a6d4a406e625449bc0a5d360d53a21" +version = "0.24.0-dev.0" +source = "git+https://github.com/rustls/rustls.git?branch=main#d61b9270adc750375e8fee0cf71c0837c499beac" dependencies = [ - "aws-lc-rs", "log", "once_cell", - "ring", "rustls-pki-types", "rustls-webpki", "subtle", "zeroize", ] +[[package]] +name = "rustls-aws-lc-rs" +version = "0.1.0-dev.0" +source = "git+https://github.com/rustls/rustls.git?branch=main#d61b9270adc750375e8fee0cf71c0837c499beac" +dependencies = [ + "aws-lc-rs", + "rustls", + "rustls-pki-types", + "subtle", + "zeroize", +] + [[package]] name = "rustls-native-certs" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" dependencies = [ "openssl-probe", "rustls-pki-types", @@ -2255,9 +2273,8 @@ dependencies = [ [[package]] name = "rustls-platform-verifier" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" +version = "0.8.0" +source = "git+https://github.com/iadev09/rustls-platform-verifier.git?rev=05cefce8d045ca21010f2f9aa952d7a821c1f429#05cefce8d045ca21010f2f9aa952d7a821c1f429" dependencies = [ "core-foundation", "core-foundation-sys", @@ -2271,23 +2288,40 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] name = "rustls-platform-verifier-android" version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" +source = "git+https://github.com/iadev09/rustls-platform-verifier.git?rev=05cefce8d045ca21010f2f9aa952d7a821c1f429#05cefce8d045ca21010f2f9aa952d7a821c1f429" + +[[package]] +name = "rustls-ring" +version = "0.1.0-dev.0" +source = "git+https://github.com/rustls/rustls.git?branch=main#d61b9270adc750375e8fee0cf71c0837c499beac" +dependencies = [ + "ring", + "rustls", + "rustls-pki-types", + "subtle", +] + +[[package]] +name = "rustls-util" +version = "0.1.0" +source = "git+https://github.com/rustls/rustls.git?branch=main#d61b9270adc750375e8fee0cf71c0837c499beac" +dependencies = [ + "log", + "rustls", +] [[package]] name = "rustls-webpki" -version = "0.103.13" +version = "0.104.0-alpha.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +checksum = "bea702cca24d344fc70973022bf7eb920c224e318466eb49784272337dd24b1a" dependencies = [ - "aws-lc-rs", - "ring", "rustls-pki-types", "untrusted", ] @@ -2397,9 +2431,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.20.0" +version = "3.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e72c1c2cb7b223fafb600a619537a871c2818583d619401b785e7c0b746ccde2" +checksum = "76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c" dependencies = [ "serde_core", "serde_with_macros", @@ -2407,9 +2441,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.20.0" +version = "3.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b90c488738ecb4fb0262f41f43bc40efc5868d9fb744319ddf5f5317f417bfac" +checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660" dependencies = [ "darling", "proc-macro2", @@ -2501,9 +2535,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.4" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", "windows-sys 0.61.2", diff --git a/Cargo.toml b/Cargo.toml index e7bcdc23dc..779098cf21 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,8 +35,11 @@ rand = "0.10.1" rcgen = "0.14" ring = "0.17" rustc-hash = "2" -rustls = { version = "0.23.5", default-features = false, features = ["std"] } -rustls-platform-verifier = "0.7" +rustls = { version = "0.24.0-dev.0", git = "https://github.com/rustls/rustls.git", branch = "main", default-features = false, features = ["webpki"] } +rustls-aws-lc-rs = { version = "0.1.0-dev.0", git = "https://github.com/rustls/rustls.git", branch = "main", default-features = false, features = ["aws-lc-sys", "std"] } +rustls-platform-verifier = { version = "0.8.0", git = "https://github.com/iadev09/rustls-platform-verifier.git", rev = "05cefce8d045ca21010f2f9aa952d7a821c1f429", default-features = false } +rustls-ring = { version = "0.1.0-dev.0", git = "https://github.com/rustls/rustls.git", branch = "main", default-features = false, features = ["std"] } +rustls-util = { version = "0.1.0", git = "https://github.com/rustls/rustls.git", branch = "main" } rustls-pki-types = "1.7" serde = { version = "1.0", features = ["derive"] } serde_json = "1" diff --git a/bench/Cargo.toml b/bench/Cargo.toml index 3712da30d3..3b87b3cdb7 100644 --- a/bench/Cargo.toml +++ b/bench/Cargo.toml @@ -10,9 +10,10 @@ anyhow = { workspace = true } bytes = { workspace = true } clap = { workspace = true } hdrhistogram = { workspace = true } -quinn = { path = "../quinn", features = ["ring"] } +quinn = { path = "../quinn" } rcgen = { workspace = true } rustls = { workspace = true } +rustls-aws-lc-rs = { workspace = true } tokio = { workspace = true, features = ["rt"] } tracing = { workspace = true } tracing-subscriber = { workspace = true } diff --git a/bench/src/lib.rs b/bench/src/lib.rs index 86c30c8c13..5e0420ee80 100644 --- a/bench/src/lib.rs +++ b/bench/src/lib.rs @@ -63,17 +63,12 @@ pub async fn connect_client( let mut roots = RootCertStore::empty(); roots.add(server_cert)?; - let default_provider = rustls::crypto::ring::default_provider(); - let provider = rustls::crypto::CryptoProvider { - cipher_suites: vec![opt.cipher.as_rustls()], - ..default_provider - }; + let mut provider = rustls_aws_lc_rs::DEFAULT_PROVIDER; + provider.tls13_cipher_suites = vec![opt.cipher.as_rustls()].into(); - let crypto = rustls::ClientConfig::builder_with_provider(provider.into()) - .with_protocol_versions(&[&rustls::version::TLS13]) - .unwrap() + let crypto = rustls::ClientConfig::builder(Arc::new(provider)) .with_root_certificates(roots) - .with_no_client_auth(); + .with_no_client_auth()?; let mut client_config = quinn::ClientConfig::new(Arc::new(QuicClientConfig::try_from(crypto)?)); client_config.transport_config(Arc::new(transport_config(&opt))); @@ -230,8 +225,8 @@ pub enum CipherSuite { } impl CipherSuite { - pub fn as_rustls(self) -> rustls::SupportedCipherSuite { - use rustls::crypto::ring::cipher_suite; + pub fn as_rustls(self) -> &'static rustls::Tls13CipherSuite { + use rustls_aws_lc_rs::cipher_suite; match self { Self::Aes128 => cipher_suite::TLS13_AES_128_GCM_SHA256, Self::Aes256 => cipher_suite::TLS13_AES_256_GCM_SHA384, diff --git a/docs/book/Cargo.toml b/docs/book/Cargo.toml index 484f57050d..8a5821f71a 100644 --- a/docs/book/Cargo.toml +++ b/docs/book/Cargo.toml @@ -14,3 +14,4 @@ bytes = { workspace = true } quinn = { version = "0.12.0", path = "../../quinn" } rcgen.workspace = true rustls.workspace = true +rustls-aws-lc-rs.workspace = true diff --git a/docs/book/src/bin/certificate.rs b/docs/book/src/bin/certificate.rs index eb3d83fd41..5982c2bfab 100644 --- a/docs/book/src/bin/certificate.rs +++ b/docs/book/src/bin/certificate.rs @@ -1,16 +1,10 @@ use std::{error::Error, sync::Arc}; -use quinn::{ - ClientConfig, - crypto::rustls::{NoInitialCipherSuite, QuicClientConfig}, -}; +use quinn::{ClientConfig, crypto::rustls::QuicClientConfig}; use rustls::{ - DigitallySignedStruct, SignatureScheme, client::danger, crypto::{CryptoProvider, verify_tls12_signature, verify_tls13_signature}, - pki_types::{ - CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer, ServerName, UnixTime, pem::PemObject, - }, + pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer, pem::PemObject}, }; #[allow(unused_variables)] @@ -18,73 +12,64 @@ fn main() { let (self_signed_certs, self_signed_key) = generate_self_signed_cert().unwrap(); let (certs, key) = read_certs_from_file().unwrap(); let server_config = quinn::ServerConfig::with_single_cert(certs, key); - let client_config = quinn::ClientConfig::try_with_platform_verifier().unwrap(); + let mut roots = rustls::RootCertStore::empty(); + roots.add(self_signed_certs).unwrap(); + let client_config = quinn::ClientConfig::with_root_certificates(Arc::new(roots)).unwrap(); } #[allow(dead_code)] // Included in `certificate.md` -fn configure_client() -> Result { - let crypto = rustls::ClientConfig::builder() +fn configure_client() -> Result> { + let crypto = rustls::ClientConfig::builder(Arc::new(rustls_aws_lc_rs::DEFAULT_PROVIDER)) .dangerous() .with_custom_certificate_verifier(SkipServerVerification::new()) - .with_no_client_auth(); + .with_no_client_auth()?; Ok(ClientConfig::new(Arc::new(QuicClientConfig::try_from( crypto, )?))) } -// Implementation of `ServerCertVerifier` that verifies everything as trustworthy. +// Implementation of `ServerVerifier` that verifies everything as trustworthy. #[derive(Debug)] struct SkipServerVerification(Arc); impl SkipServerVerification { fn new() -> Arc { - Arc::new(Self(Arc::new(rustls::crypto::ring::default_provider()))) + Arc::new(Self(Arc::new(rustls_aws_lc_rs::DEFAULT_PROVIDER))) } } -impl danger::ServerCertVerifier for SkipServerVerification { - fn verify_server_cert( +impl danger::ServerVerifier for SkipServerVerification { + fn verify_identity( &self, - _end_entity: &CertificateDer<'_>, - _intermediates: &[CertificateDer<'_>], - _server_name: &ServerName<'_>, - _ocsp: &[u8], - _now: UnixTime, - ) -> Result { - Ok(danger::ServerCertVerified::assertion()) + _identity: &danger::ServerIdentity<'_>, + ) -> Result { + Ok(danger::PeerVerified::assertion()) } + fn verify_tls12_signature( &self, - message: &[u8], - cert: &CertificateDer<'_>, - dss: &DigitallySignedStruct, + input: &danger::SignatureVerificationInput<'_>, ) -> Result { - verify_tls12_signature( - message, - cert, - dss, - &self.0.signature_verification_algorithms, - ) + verify_tls12_signature(input, &self.0.signature_verification_algorithms) } fn verify_tls13_signature( &self, - message: &[u8], - cert: &CertificateDer<'_>, - dss: &DigitallySignedStruct, + input: &danger::SignatureVerificationInput<'_>, ) -> Result { - verify_tls13_signature( - message, - cert, - dss, - &self.0.signature_verification_algorithms, - ) + verify_tls13_signature(input, &self.0.signature_verification_algorithms) } - fn supported_verify_schemes(&self) -> Vec { + fn supported_verify_schemes(&self) -> Vec { self.0.signature_verification_algorithms.supported_schemes() } + + fn request_ocsp_response(&self) -> bool { + false + } + + fn hash_config(&self, _h: &mut dyn std::hash::Hasher) {} } fn generate_self_signed_cert() diff --git a/docs/book/src/quinn/certificate.md b/docs/book/src/quinn/certificate.md index 1c52a8c694..1b4c9ac97c 100644 --- a/docs/book/src/quinn/certificate.md +++ b/docs/book/src/quinn/certificate.md @@ -7,25 +7,26 @@ As QUIC uses TLS 1.3 for authentication of connections, the server needs to prov ## Insecure Connection For our example use case, the easiest way to allow the client to trust our server is to disable certificate verification (don't do this in production!). -When the [rustls][3] `dangerous_configuration` feature flag is enabled, a client can be configured to trust any server. +With [rustls][3]'s dangerous client configuration API, a client can be configured to trust any server. -Start by adding a [rustls][3] dependency with the `dangerous_configuration` feature flag to your `Cargo.toml` file. +Start by adding [rustls][3] and provider dependencies to your `Cargo.toml` file. ```toml -quinn = "0.11" -rustls = "0.23" +quinn = "0.12" +rustls = "0.24" +rustls-aws-lc-rs = "0.1" ``` -Then, allow the client to skip the certificate validation by implementing [ServerCertVerifier][ServerCertVerifier] and letting it assert verification for any server. +Then, allow the client to skip the certificate validation by implementing [ServerVerifier][ServerVerifier] and letting it assert verification for any server. ```rust -{{#include ../bin/certificate.rs:36:88}} +{{#include ../bin/certificate.rs:42:73}} ``` -After that, modify the [ClientConfig][ClientConfig] to use this [ServerCertVerifier][ServerCertVerifier] implementation. +After that, modify the [ClientConfig][ClientConfig] to use this [ServerVerifier][ServerVerifier] implementation. ```rust -{{#include ../bin/certificate.rs:25:34}} +{{#include ../bin/certificate.rs:21:30}} ``` Finally, if you plug this [ClientConfig][ClientConfig] into the [Endpoint::set_default_client_config()][set_default_client_config] your client endpoint should verify all connections as trustworthy. @@ -104,7 +105,7 @@ This is the only thing you need to do for your client to trust a server certific [6]: https://letsencrypt.org/getting-started/ [7]: https://certbot.eff.org/instructions [ClientConfig]: https://docs.rs/quinn/latest/quinn/struct.ClientConfig.html -[ServerCertVerifier]: https://docs.rs/rustls/latest/rustls/client/trait.ServerCertVerifier.html +[ServerVerifier]: https://docs.rs/rustls/latest/rustls/client/danger/trait.ServerVerifier.html [set_default_client_config]: https://docs.rs/quinn/latest/quinn/struct.Endpoint.html#method.set_default_client_config [generate_simple_self_signed]: https://docs.rs/rcgen/latest/rcgen/fn.generate_simple_self_signed.html [Certificate]: https://docs.rs/rcgen/latest/rcgen/struct.Certificate.html diff --git a/perf/Cargo.toml b/perf/Cargo.toml index 7c6fe7e119..9ce3bed502 100644 --- a/perf/Cargo.toml +++ b/perf/Cargo.toml @@ -34,6 +34,8 @@ quinn = { path = "../quinn" } quinn-proto = { path = "../quinn-proto" } rcgen = { workspace = true } rustls = { workspace = true } +rustls-aws-lc-rs = { workspace = true } +rustls-util = { workspace = true } serde = { workspace = true, optional = true } serde_json = { workspace = true, optional = true } socket2 = { workspace = true } diff --git a/perf/src/client.rs b/perf/src/client.rs index ff25d7d1f3..f6f9f89844 100644 --- a/perf/src/client.rs +++ b/perf/src/client.rs @@ -11,7 +11,6 @@ use anyhow::{Context, Result}; use bytes::Bytes; use clap::Parser; use quinn::{TokioRuntime, crypto::rustls::QuicClientConfig}; -use rustls::pki_types::{CertificateDer, ServerName, UnixTime}; use tokio::sync::Semaphore; use tracing::{debug, error, info}; @@ -104,22 +103,18 @@ pub async fn run(opt: Opt) -> Result<()> { let endpoint = quinn::Endpoint::new(endpoint_cfg, None, socket, Arc::new(TokioRuntime))?; - let default_provider = rustls::crypto::ring::default_provider(); - let provider = Arc::new(rustls::crypto::CryptoProvider { - cipher_suites: PERF_CIPHER_SUITES.into(), - ..default_provider - }); + let mut provider = rustls_aws_lc_rs::DEFAULT_PROVIDER; + provider.tls13_cipher_suites = PERF_CIPHER_SUITES.into(); + let provider = Arc::new(provider); - let mut crypto = rustls::ClientConfig::builder_with_provider(provider.clone()) - .with_protocol_versions(&[&rustls::version::TLS13]) - .unwrap() + let mut crypto = rustls::ClientConfig::builder(provider.clone()) .dangerous() .with_custom_certificate_verifier(SkipServerVerification::new(provider)) - .with_no_client_auth(); - crypto.alpn_protocols = vec![b"perf".to_vec()]; + .with_no_client_auth()?; + crypto.alpn_protocols = vec![b"perf".as_slice().into()]; if opt.common.keylog { - crypto.key_log = Arc::new(rustls::KeyLogFile::new()); + crypto.key_log = Arc::new(rustls_util::KeyLogFile::new()); } let transport = opt.common.build_transport_config( @@ -381,47 +376,35 @@ impl SkipServerVerification { } } -impl rustls::client::danger::ServerCertVerifier for SkipServerVerification { - fn verify_server_cert( +impl rustls::client::danger::ServerVerifier for SkipServerVerification { + fn verify_identity( &self, - _end_entity: &CertificateDer<'_>, - _intermediates: &[CertificateDer<'_>], - _server_name: &ServerName<'_>, - _ocsp: &[u8], - _now: UnixTime, - ) -> Result { - Ok(rustls::client::danger::ServerCertVerified::assertion()) + _identity: &rustls::client::danger::ServerIdentity<'_>, + ) -> Result { + Ok(rustls::client::danger::PeerVerified::assertion()) } fn verify_tls12_signature( &self, - message: &[u8], - cert: &CertificateDer<'_>, - dss: &rustls::DigitallySignedStruct, + input: &rustls::client::danger::SignatureVerificationInput<'_>, ) -> Result { - rustls::crypto::verify_tls12_signature( - message, - cert, - dss, - &self.0.signature_verification_algorithms, - ) + rustls::crypto::verify_tls12_signature(input, &self.0.signature_verification_algorithms) } fn verify_tls13_signature( &self, - message: &[u8], - cert: &CertificateDer<'_>, - dss: &rustls::DigitallySignedStruct, + input: &rustls::client::danger::SignatureVerificationInput<'_>, ) -> Result { - rustls::crypto::verify_tls13_signature( - message, - cert, - dss, - &self.0.signature_verification_algorithms, - ) + rustls::crypto::verify_tls13_signature(input, &self.0.signature_verification_algorithms) } - fn supported_verify_schemes(&self) -> Vec { + fn supported_verify_schemes(&self) -> Vec { self.0.signature_verification_algorithms.supported_schemes() } + + fn request_ocsp_response(&self) -> bool { + false + } + + fn hash_config(&self, _h: &mut dyn std::hash::Hasher) {} } diff --git a/perf/src/lib.rs b/perf/src/lib.rs index f547643cb0..1aa95273f4 100644 --- a/perf/src/lib.rs +++ b/perf/src/lib.rs @@ -11,7 +11,7 @@ use quinn::{ congestion::{self, ControllerFactory}, udp::UdpSocketState, }; -use rustls::crypto::ring::cipher_suite; +use rustls_aws_lc_rs::cipher_suite; use socket2::{Domain, Protocol, Socket, Type}; use tracing::warn; @@ -214,7 +214,7 @@ impl CongestionAlgorithm { } } -pub static PERF_CIPHER_SUITES: &[rustls::SupportedCipherSuite] = &[ +pub static PERF_CIPHER_SUITES: &[&rustls::Tls13CipherSuite] = &[ cipher_suite::TLS13_AES_128_GCM_SHA256, cipher_suite::TLS13_AES_256_GCM_SHA384, cipher_suite::TLS13_CHACHA20_POLY1305_SHA256, diff --git a/perf/src/noprotection.rs b/perf/src/noprotection.rs index 37eb118f42..2c4521a517 100644 --- a/perf/src/noprotection.rs +++ b/perf/src/noprotection.rs @@ -120,7 +120,7 @@ impl crypto::Session for NoProtectionSession { } fn export_keying_material( - &self, + &mut self, output: &mut [u8], label: &[u8], context: &[u8], diff --git a/perf/src/server.rs b/perf/src/server.rs index 18d1a4d8ef..eeece519e3 100644 --- a/perf/src/server.rs +++ b/perf/src/server.rs @@ -4,7 +4,10 @@ use anyhow::{Context, Result}; use bytes::Bytes; use clap::Parser; use quinn::{TokioRuntime, crypto::rustls::QuicServerConfig}; -use rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer, pem::PemObject}; +use rustls::{ + crypto::Identity, + pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer, pem::PemObject}, +}; use tracing::{debug, error, info}; use crate::{CommonOpt, PERF_CIPHER_SUITES, noprotection::NoProtectionServerConfig}; @@ -44,22 +47,17 @@ pub async fn run(opt: Opt) -> Result<()> { } }; - let default_provider = rustls::crypto::ring::default_provider(); - let provider = rustls::crypto::CryptoProvider { - cipher_suites: PERF_CIPHER_SUITES.into(), - ..default_provider - }; + let mut provider = rustls_aws_lc_rs::DEFAULT_PROVIDER; + provider.tls13_cipher_suites = PERF_CIPHER_SUITES.into(); - let mut crypto = rustls::ServerConfig::builder_with_provider(provider.into()) - .with_protocol_versions(&[&rustls::version::TLS13]) - .unwrap() + let mut crypto = rustls::ServerConfig::builder(Arc::new(provider)) .with_no_client_auth() - .with_single_cert(cert, key) + .with_single_cert(Arc::new(Identity::from_cert_chain(cert).unwrap()), key) .unwrap(); - crypto.alpn_protocols = vec![b"perf".to_vec()]; + crypto.alpn_protocols = vec![b"perf".as_slice().into()]; if opt.common.keylog { - crypto.key_log = Arc::new(rustls::KeyLogFile::new()); + crypto.key_log = Arc::new(rustls_util::KeyLogFile::new()); } let transport = opt.common.build_transport_config( diff --git a/quinn-proto/Cargo.toml b/quinn-proto/Cargo.toml index c018ef1541..72d80e20d7 100644 --- a/quinn-proto/Cargo.toml +++ b/quinn-proto/Cargo.toml @@ -21,10 +21,10 @@ bloom = ["dep:fastbloom"] # For backwards compatibility, `rustls` forwards to `rustls-ring` rustls = ["rustls-ring"] # Enable rustls with the `aws-lc-rs` crypto provider -rustls-aws-lc-rs = ["dep:rustls", "rustls?/aws-lc-rs", "aws-lc-rs"] -rustls-aws-lc-rs-fips = ["rustls-aws-lc-rs", "aws-lc-rs-fips"] +rustls-aws-lc-rs = ["dep:rustls", "dep:rustls-aws-lc-rs", "aws-lc-rs"] +rustls-aws-lc-rs-fips = ["rustls-aws-lc-rs", "aws-lc-rs-fips", "rustls-aws-lc-rs?/fips"] # Enable rustls with the `ring` crypto provider -rustls-ring = ["dep:rustls", "rustls?/ring", "ring"] +rustls-ring = ["dep:rustls", "dep:rustls-ring", "ring"] ring = ["dep:ring"] # Enable rustls ring provider and direct ring usage # Provides `ClientConfig::with_platform_verifier()` convenience method @@ -32,7 +32,7 @@ platform-verifier = ["dep:rustls-platform-verifier"] # Configure `tracing` to log events via `log` if no `tracing` subscriber exists. tracing-log = ["tracing/log"] # Enable rustls logging -rustls-log = ["rustls?/logging"] +rustls-log = ["rustls?/log"] # Enable qlog support qlog = ["dep:qlog"] @@ -53,7 +53,9 @@ rand = { workspace = true } rand_pcg = "0.10" ring = { workspace = true, optional = true } rustls = { workspace = true, optional = true } +rustls-aws-lc-rs = { workspace = true, optional = true } rustls-platform-verifier = { workspace = true, optional = true } +rustls-ring = { workspace = true, optional = true } slab = { workspace = true } thiserror = { workspace = true } tinyvec = { workspace = true, features = ["alloc"] } diff --git a/quinn-proto/src/config/mod.rs b/quinn-proto/src/config/mod.rs index 129ee1dbaa..58b46df2b5 100644 --- a/quinn-proto/src/config/mod.rs +++ b/quinn-proto/src/config/mod.rs @@ -632,8 +632,9 @@ impl ClientConfig { pub fn with_root_certificates( roots: Arc, ) -> Result { + let provider = configured_provider(); Ok(Self::new(Arc::new(crypto::rustls::QuicClientConfig::new( - WebPkiServerVerifier::builder_with_provider(roots, configured_provider()).build()?, + Arc::new(WebPkiServerVerifier::builder(roots, &provider).build()?), )))) } } diff --git a/quinn-proto/src/connection/assembler.rs b/quinn-proto/src/connection/assembler.rs index d92331169c..9419b1bd00 100644 --- a/quinn-proto/src/connection/assembler.rs +++ b/quinn-proto/src/connection/assembler.rs @@ -10,7 +10,7 @@ use crate::range_set::RangeSet; /// Helper to assemble unordered stream frames into an ordered stream #[derive(Debug, Default)] -pub(super) struct Assembler { +pub(crate) struct Assembler { state: State, data: BinaryHeap, /// Total number of buffered bytes, including duplicates in ordered mode. @@ -25,7 +25,7 @@ pub(super) struct Assembler { } impl Assembler { - pub(super) fn new() -> Self { + pub(crate) fn new() -> Self { Self::default() } @@ -57,7 +57,7 @@ impl Assembler { } /// Get the the next chunk - pub(super) fn read(&mut self, max_length: usize, ordered: bool) -> Option { + pub(crate) fn read(&mut self, max_length: usize, ordered: bool) -> Option { loop { let mut chunk = self.data.peek_mut()?; @@ -147,7 +147,7 @@ impl Assembler { // Note: If a packet contains many frames from the same stream, the estimated over-allocation // will be much higher because we are counting the same allocation multiple times. - pub(super) fn insert(&mut self, mut offset: u64, mut bytes: Bytes, allocation_size: usize) { + pub(crate) fn insert(&mut self, mut offset: u64, mut bytes: Bytes, allocation_size: usize) { debug_assert!( bytes.len() <= allocation_size, "allocation_size less than bytes.len(): {:?} < {:?}", @@ -209,10 +209,15 @@ impl Assembler { } /// Number of bytes consumed by the application - pub(super) fn bytes_read(&self) -> u64 { + pub(crate) fn bytes_read(&self) -> u64 { self.bytes_read } + pub(crate) fn skip_to(&mut self, offset: u64) { + self.bytes_read = self.bytes_read.max(offset); + self.end = self.end.max(offset); + } + /// Discard all buffered data pub(super) fn clear(&mut self) { self.data.clear(); diff --git a/quinn-proto/src/connection/mod.rs b/quinn-proto/src/connection/mod.rs index 1c77548992..f036acada0 100644 --- a/quinn-proto/src/connection/mod.rs +++ b/quinn-proto/src/connection/mod.rs @@ -41,7 +41,7 @@ use crate::{ mod ack_frequency; use ack_frequency::AckFrequencyState; -mod assembler; +pub(crate) mod assembler; pub use assembler::Chunk; mod cid_state; @@ -1300,6 +1300,20 @@ impl Connection { &*self.crypto } + /// Get a mutable session reference + pub fn crypto_session_mut(&mut self) -> &mut dyn crypto::Session { + &mut *self.crypto + } + + pub(crate) fn skip_initial_crypto(&mut self, offset: u64) { + self.spaces[SpaceId::Initial].crypto_stream.skip_to(offset); + } + + pub(crate) fn skip_initial_packet_number(&mut self, next: u64) { + let space = &mut self.spaces[SpaceId::Initial]; + space.next_packet_number = space.next_packet_number.max(next); + } + /// Whether the connection is in the process of being established /// /// If this returns `false`, the connection may be either established or closed, signaled by the diff --git a/quinn-proto/src/crypto.rs b/quinn-proto/src/crypto.rs index 2ac40fc1ee..7f2417c209 100644 --- a/quinn-proto/src/crypto.rs +++ b/quinn-proto/src/crypto.rs @@ -86,7 +86,7 @@ pub trait Session: Send + Sync + 'static { /// This function will fail, returning [ExportKeyingMaterialError], /// if the requested output length is too large. fn export_keying_material( - &self, + &mut self, output: &mut [u8], label: &[u8], context: &[u8], @@ -139,6 +139,19 @@ pub trait ServerConfig: Send + Sync { version: u32, params: &TransportParameters, ) -> Box; + + /// Continue a server session after a rustls QUIC acceptor has read the ClientHello. + #[cfg(any(feature = "rustls-aws-lc-rs", feature = "rustls-ring"))] + fn start_session_from_accepted( + self: Arc, + _version: u32, + _params: &TransportParameters, + _accepted: rustls::Accepted, + ) -> Result, TransportError> { + Err(TransportError::PROTOCOL_VIOLATION( + "server crypto config does not support rustls acceptor", + )) + } } /// Keys used to protect packet payloads diff --git a/quinn-proto/src/crypto/rustls.rs b/quinn-proto/src/crypto/rustls.rs index b1fd9da1e4..e7dbba8221 100644 --- a/quinn-proto/src/crypto/rustls.rs +++ b/quinn-proto/src/crypto/rustls.rs @@ -1,31 +1,36 @@ use std::{any::Any, io, str, sync::Arc}; -#[cfg(all(feature = "aws-lc-rs", not(feature = "ring")))] +use crate::{ + ConnectError, ConnectionId, Side, TransportError, TransportErrorCode, + crypto::{ + self, CryptoError, ExportKeyingMaterialError, HeaderKey, KeyPair, Keys, UnsupportedVersion, + }, + transport_parameters::TransportParameters, +}; +#[cfg(all(feature = "rustls-aws-lc-rs", not(feature = "rustls-ring")))] use aws_lc_rs::aead; use bytes::BytesMut; -#[cfg(feature = "ring")] +#[cfg(feature = "rustls-ring")] use ring::aead; pub use rustls::Error; #[cfg(feature = "__rustls-post-quantum-test")] -use rustls::NamedGroup; +use rustls::crypto::kx::NamedGroup; use rustls::{ - self, CipherSuite, - client::danger::ServerCertVerifier, + self, + client::danger::ServerVerifier, + crypto::{CipherSuite, Identity}, + error::AlertDescription, pki_types::{CertificateDer, PrivateKeyDer, ServerName}, - quic::{Connection, HeaderProtectionKey, KeyChange, PacketKey, Secrets, Suite, Version}, + quic::{ + Accepted as RustlsAccepted, Acceptor as RustlsAcceptor, ClientConnection, Connection as _, + DirectionalKeys, HeaderProtectionKey, KeyChange, PacketKey, Secrets, ServerConnection, + Side as QuicSide, Suite, Version, + }, }; #[cfg(feature = "platform-verifier")] use rustls_platform_verifier::BuilderVerifierExt; -use crate::{ - ConnectError, ConnectionId, Side, TransportError, TransportErrorCode, - crypto::{ - self, CryptoError, ExportKeyingMaterialError, HeaderKey, KeyPair, Keys, UnsupportedVersion, - }, - transport_parameters::TransportParameters, -}; - -impl From for rustls::Side { +impl From for QuicSide { fn from(s: Side) -> Self { match s { Side::Client => Self::Client, @@ -39,16 +44,158 @@ pub struct TlsSession { version: Version, got_handshake_data: bool, next_secrets: Option, - inner: Connection, + exporter: Option, + inner: QuicConnection, suite: Suite, } +enum QuicConnection { + Client(ClientConnection), + Server(ServerConnection), +} + +impl QuicConnection { + fn side(&self) -> Side { + match self { + Self::Client(_) => Side::Client, + Self::Server(_) => Side::Server, + } + } + + fn alpn_protocol(&self) -> Option<&[u8]> { + match self { + Self::Client(session) => session.alpn_protocol(), + Self::Server(session) => session.alpn_protocol(), + } + .map(AsRef::as_ref) + } + + fn peer_identity(&self) -> Option<&Identity<'static>> { + match self { + Self::Client(session) => session.peer_identity(), + Self::Server(session) => session.peer_identity(), + } + } + + fn zero_rtt_keys(&self) -> Option { + match self { + Self::Client(session) => session.zero_rtt_keys(), + Self::Server(session) => session.zero_rtt_keys(), + } + } + + fn is_early_data_accepted(&self) -> Option { + match self { + Self::Client(session) => Some(session.is_early_data_accepted()), + Self::Server(_) => None, + } + } + + fn is_handshaking(&self) -> bool { + match self { + Self::Client(session) => session.is_handshaking(), + Self::Server(session) => session.is_handshaking(), + } + } + + fn read_hs(&mut self, buf: &[u8]) -> Result<(), Error> { + match self { + Self::Client(session) => session.read_hs(buf), + Self::Server(session) => session.read_hs(buf), + } + } + + fn write_hs(&mut self, buf: &mut Vec) -> Option { + match self { + Self::Client(session) => session.write_hs(buf), + Self::Server(session) => session.write_hs(buf), + } + } + + fn quic_transport_parameters(&self) -> Option<&[u8]> { + match self { + Self::Client(session) => session.quic_transport_parameters(), + Self::Server(session) => session.quic_transport_parameters(), + } + } + + fn server_name(&self) -> Option<&str> { + match self { + Self::Client(_) => None, + Self::Server(session) => session.server_name().map(AsRef::as_ref), + } + } + + #[cfg(feature = "__rustls-post-quantum-test")] + fn negotiated_key_exchange_group(&self) -> Option { + match self { + Self::Client(session) => session.negotiated_key_exchange_group(), + Self::Server(session) => session.negotiated_key_exchange_group(), + } + .map(|group| group.name()) + } + + fn exporter(&mut self) -> Result { + match self { + Self::Client(session) => session.exporter(), + Self::Server(session) => session.exporter(), + } + } +} + impl TlsSession { fn side(&self) -> Side { - match self.inner { - Connection::Client(_) => Side::Client, - Connection::Server(_) => Side::Server, + self.inner.side() + } +} + +pub(crate) fn transport_error_from_rustls(e: Error) -> TransportError { + if let Ok(alert) = AlertDescription::try_from(&e) { + TransportError { + code: TransportErrorCode::crypto(alert.into()), + frame: None, + reason: e.to_string(), + crypto: Some(Arc::new(e)), } + } else { + TransportError::PROTOCOL_VIOLATION(format!("TLS error: {e}")) + } +} + +pub(crate) struct Acceptor { + inner: RustlsAcceptor, +} + +impl Acceptor { + pub(crate) fn new(version: u32) -> Result { + Ok(Self { + inner: RustlsAcceptor::new(interpret_version(version)?), + }) + } + + pub(crate) fn read_hs(&mut self, plaintext: &[u8]) -> Result<(), TransportError> { + self.inner + .read_hs(plaintext) + .map_err(transport_error_from_rustls) + } + + pub(crate) fn accept(&mut self) -> Result, TransportError> { + self.inner + .accept() + .map(|accepted| accepted.map(|inner| Accepted { inner })) + .map_err(transport_error_from_rustls) + } +} + +/// A rustls QUIC ClientHello and the state needed to continue the handshake. +pub struct Accepted { + inner: RustlsAccepted, +} + +impl Accepted { + /// Get the ClientHello for this connection. + pub fn client_hello(&self) -> rustls::server::ClientHello<'_> { + self.inner.client_hello() } } @@ -63,38 +210,40 @@ impl crypto::Session for TlsSession { } Some(Box::new(HandshakeData { protocol: self.inner.alpn_protocol().map(|x| x.into()), - server_name: match &self.inner { - Connection::Client(_) => None, - Connection::Server(session) => session.server_name().map(|x| x.into()), - }, + server_name: self.inner.server_name().map(str::to_owned), protocol_version: match &self.inner { - Connection::Client(session) => session.protocol_version(), - Connection::Server(session) => session.protocol_version(), + QuicConnection::Client(session) => session.protocol_version(), + QuicConnection::Server(session) => session.protocol_version(), } .map(|x| -> Box { Box::new(x) }), cipher_suite: match &self.inner { - Connection::Client(session) => session.negotiated_cipher_suite(), - Connection::Server(session) => session.negotiated_cipher_suite(), + QuicConnection::Client(session) => session.negotiated_cipher_suite(), + QuicConnection::Server(session) => session.negotiated_cipher_suite(), } .map(|suite| -> Box { Box::new(suite.suite()) }), #[cfg(feature = "__rustls-post-quantum-test")] negotiated_key_exchange_group: self .inner .negotiated_key_exchange_group() - .expect("key exchange group is negotiated") - .name(), + .expect("key exchange group is negotiated"), })) } /// For the rustls `TlsSession`, the `Any` type is `Vec` fn peer_identity(&self) -> Option> { - self.inner.peer_certificates().map(|v| -> Box { - Box::new( - v.iter() - .map(|v| v.clone().into_owned()) - .collect::>>(), - ) - }) + let Identity::X509(identity) = self.inner.peer_identity()? else { + return None; + }; + + let mut certs = Vec::with_capacity(1 + identity.intermediates.len()); + certs.push(identity.end_entity.clone().into_owned()); + certs.extend( + identity + .intermediates + .iter() + .map(|cert| cert.clone().into_owned()), + ); + Some(Box::new(certs)) } fn early_crypto(&self) -> Option<(Box, Box)> { @@ -103,10 +252,7 @@ impl crypto::Session for TlsSession { } fn early_data_accepted(&self) -> Option { - match self.inner { - Connection::Client(ref session) => Some(session.is_early_data_accepted()), - _ => None, - } + self.inner.is_early_data_accepted() } fn is_handshaking(&self) -> bool { @@ -114,26 +260,14 @@ impl crypto::Session for TlsSession { } fn read_handshake(&mut self, buf: &[u8]) -> Result { - self.inner.read_hs(buf).map_err(|e| { - if let Some(alert) = self.inner.alert() { - TransportError { - code: TransportErrorCode::crypto(alert.into()), - frame: None, - reason: e.to_string(), - crypto: Some(Arc::new(e)), - } - } else { - TransportError::PROTOCOL_VIOLATION(format!("TLS error: {e}")) - } - })?; + self.inner + .read_hs(buf) + .map_err(transport_error_from_rustls)?; if !self.got_handshake_data { // Hack around the lack of an explicit signal from rustls to reflect ClientHello being // ready on incoming connections, or ALPN negotiation completing on outgoing // connections. - let have_server_name = match self.inner { - Connection::Client(_) => false, - Connection::Server(ref session) => session.server_name().is_some(), - }; + let have_server_name = self.inner.server_name().is_some(); if self.inner.alpn_protocol().is_some() || have_server_name || !self.is_handshaking() { self.got_handshake_data = true; return Ok(true); @@ -197,7 +331,6 @@ impl crypto::Session for TlsSession { let (nonce, key) = match self.version { Version::V1 => (RETRY_INTEGRITY_NONCE_V1, RETRY_INTEGRITY_KEY_V1), - Version::V1Draft => (RETRY_INTEGRITY_NONCE_DRAFT, RETRY_INTEGRITY_KEY_DRAFT), _ => unreachable!(), }; @@ -209,25 +342,28 @@ impl crypto::Session for TlsSession { } fn export_keying_material( - &self, + &mut self, output: &mut [u8], label: &[u8], context: &[u8], ) -> Result<(), ExportKeyingMaterialError> { - self.inner - .export_keying_material(output, label, Some(context)) + if self.exporter.is_none() { + self.exporter = Some( + self.inner + .exporter() + .map_err(|_| ExportKeyingMaterialError)?, + ); + } + + self.exporter + .as_ref() + .expect("exporter is set") + .derive(label, Some(context), output) .map_err(|_| ExportKeyingMaterialError)?; Ok(()) } } -const RETRY_INTEGRITY_KEY_DRAFT: [u8; 16] = [ - 0xcc, 0xce, 0x18, 0x7e, 0xd0, 0x9a, 0x09, 0xd0, 0x57, 0x28, 0x15, 0x5a, 0x6c, 0xb9, 0x6b, 0xe1, -]; -const RETRY_INTEGRITY_NONCE_DRAFT: [u8; 12] = [ - 0xe5, 0x49, 0x30, 0xf9, 0x7f, 0x21, 0x36, 0xf0, 0x53, 0x0a, 0x8c, 0x1c, -]; - const RETRY_INTEGRITY_KEY_V1: [u8; 16] = [ 0xbe, 0x0c, 0x69, 0x0b, 0x9f, 0x66, 0x57, 0x5a, 0x1d, 0x76, 0x6b, 0x54, 0xe3, 0x68, 0xc8, 0x4e, ]; @@ -288,7 +424,8 @@ pub struct HandshakeData { /// A QUIC-compatible TLS client configuration /// /// Quinn implicitly constructs a `QuicClientConfig` with reasonable defaults within -/// [`ClientConfig::with_root_certificates()`][root_certs] and [`ClientConfig::try_with_platform_verifier()`][platform]. +/// [`ClientConfig::with_root_certificates()`][root_certs] and +/// [`ClientConfig::try_with_platform_verifier()`][platform]. /// Alternatively, `QuicClientConfig`'s [`TryFrom`] implementation can be used to wrap around a /// custom [`rustls::ClientConfig`], in which case care should be taken around certain points: /// @@ -311,17 +448,15 @@ pub struct QuicClientConfig { impl QuicClientConfig { #[cfg(feature = "platform-verifier")] pub(crate) fn with_platform_verifier() -> Result { - // Keep in sync with `inner()` below - let mut inner = rustls::ClientConfig::builder_with_provider(configured_provider()) - .with_protocol_versions(&[&rustls::version::TLS13]) - .unwrap() // The default providers support TLS 1.3 + let mut inner = rustls::ClientConfig::builder(configured_provider()) .with_platform_verifier()? - .with_no_client_auth(); + .with_no_client_auth() + .expect("default providers are valid for QUIC"); inner.enable_early_data = true; Ok(Self { - // We're confident that the *ring* default provider contains TLS13_AES_128_GCM_SHA256 - initial: initial_suite_from_provider(inner.crypto_provider()) + // We're confident that the default providers contain TLS13_AES_128_GCM_SHA256. + initial: initial_suite_from_provider(inner.provider()) .expect("no initial cipher suite found"), inner: Arc::new(inner), }) @@ -331,11 +466,11 @@ impl QuicClientConfig { /// /// QUIC requires that TLS 1.3 be enabled. Advanced users can use any [`rustls::ClientConfig`] that /// satisfies this requirement. - pub(crate) fn new(verifier: Arc) -> Self { + pub(crate) fn new(verifier: Arc) -> Self { let inner = Self::inner(verifier); Self { - // We're confident that the *ring* default provider contains TLS13_AES_128_GCM_SHA256 - initial: initial_suite_from_provider(inner.crypto_provider()) + // We're confident that the default providers contain TLS13_AES_128_GCM_SHA256. + initial: initial_suite_from_provider(inner.provider()) .expect("no initial cipher suite found"), inner: Arc::new(inner), } @@ -354,14 +489,12 @@ impl QuicClientConfig { } } - pub(crate) fn inner(verifier: Arc) -> rustls::ClientConfig { - // Keep in sync with `with_platform_verifier()` above - let mut config = rustls::ClientConfig::builder_with_provider(configured_provider()) - .with_protocol_versions(&[&rustls::version::TLS13]) - .unwrap() // The default providers support TLS 1.3 + pub(crate) fn inner(verifier: Arc) -> rustls::ClientConfig { + let mut config = rustls::ClientConfig::builder(configured_provider()) .dangerous() .with_custom_certificate_verifier(verifier) - .with_no_client_auth(); + .with_no_client_auth() + .expect("default providers are valid for QUIC"); config.enable_early_data = true; config @@ -380,8 +513,9 @@ impl crypto::ClientConfig for QuicClientConfig { version, got_handshake_data: false, next_secrets: None, - inner: Connection::Client( - rustls::quic::ClientConnection::new( + exporter: None, + inner: QuicConnection::Client( + ClientConnection::new( self.inner.clone(), version, ServerName::try_from(server_name) @@ -409,7 +543,7 @@ impl TryFrom> for QuicClientConfig { fn try_from(inner: Arc) -> Result { Ok(Self { - initial: initial_suite_from_provider(inner.crypto_provider()) + initial: initial_suite_from_provider(inner.provider()) .ok_or(NoInitialCipherSuite { specific: false })?, inner, }) @@ -464,7 +598,7 @@ impl QuicServerConfig { ) -> Result { let inner = Self::inner(cert_chain, key)?; Ok(Self { - // We're confident that the *ring* default provider contains TLS13_AES_128_GCM_SHA256 + // We're confident that the default providers contain TLS13_AES_128_GCM_SHA256. initial: initial_suite_from_provider(inner.crypto_provider()) .expect("no initial cipher suite found"), inner: Arc::new(inner), @@ -493,11 +627,9 @@ impl QuicServerConfig { cert_chain: Vec>, key: PrivateKeyDer<'static>, ) -> Result { - let mut inner = rustls::ServerConfig::builder_with_provider(configured_provider()) - .with_protocol_versions(&[&rustls::version::TLS13]) - .unwrap() // The *ring* default provider supports TLS 1.3 + let mut inner = rustls::ServerConfig::builder(configured_provider()) .with_no_client_auth() - .with_single_cert(cert_chain, key)?; + .with_single_cert(Arc::new(Identity::from_cert_chain(cert_chain)?), key)?; inner.max_early_data_size = u32::MAX; Ok(inner) @@ -536,14 +668,37 @@ impl crypto::ServerConfig for QuicServerConfig { version, got_handshake_data: false, next_secrets: None, - inner: Connection::Server( - rustls::quic::ServerConnection::new(self.inner.clone(), version, to_vec(params)) - .unwrap(), + exporter: None, + inner: QuicConnection::Server( + ServerConnection::new(self.inner.clone(), version, to_vec(params)).unwrap(), ), suite: self.initial, }) } + fn start_session_from_accepted( + self: Arc, + version: u32, + params: &TransportParameters, + accepted: Accepted, + ) -> Result, TransportError> { + // Safe: `start_session_from_accepted()` is never called if `initial_keys()` rejected + // `version`. + let version = interpret_version(version).unwrap(); + let inner = accepted + .inner + .into_connection(self.inner.clone(), to_vec(params)) + .map_err(transport_error_from_rustls)?; + Ok(Box::new(TlsSession { + version, + got_handshake_data: false, + next_secrets: None, + exporter: None, + inner: QuicConnection::Server(inner), + suite: self.initial, + })) + } + fn initial_keys( &self, version: u32, @@ -558,7 +713,6 @@ impl crypto::ServerConfig for QuicServerConfig { let version = interpret_version(version).unwrap(); let (nonce, key) = match version { Version::V1 => (RETRY_INTEGRITY_NONCE_V1, RETRY_INTEGRITY_KEY_V1), - Version::V1Draft => (RETRY_INTEGRITY_NONCE_DRAFT, RETRY_INTEGRITY_KEY_DRAFT), _ => unreachable!(), }; @@ -583,20 +737,19 @@ pub(crate) fn initial_suite_from_provider( provider: &Arc, ) -> Option { provider - .cipher_suites + .tls13_cipher_suites .iter() - .find_map(|cs| match (cs.suite(), cs.tls13()) { - (CipherSuite::TLS13_AES_128_GCM_SHA256, Some(suite)) => Some(suite.quic_suite()), + .find_map(|&suite| match suite.common.suite { + CipherSuite::TLS13_AES_128_GCM_SHA256 => suite.quic_suite(), _ => None, }) - .flatten() } pub(crate) fn configured_provider() -> Arc { #[cfg(all(feature = "rustls-aws-lc-rs", not(feature = "rustls-ring")))] - let provider = rustls::crypto::aws_lc_rs::default_provider(); + let provider = rustls_aws_lc_rs::DEFAULT_PROVIDER; #[cfg(feature = "rustls-ring")] - let provider = rustls::crypto::ring::default_provider(); + let provider = rustls_ring::DEFAULT_PROVIDER; Arc::new(provider) } @@ -629,7 +782,9 @@ impl crypto::PacketKey for Box { fn encrypt(&self, packet: u64, buf: &mut [u8], header_len: usize) { let (header, payload_tag) = buf.split_at_mut(header_len); let (payload, tag_storage) = payload_tag.split_at_mut(payload_tag.len() - self.tag_len()); - let tag = self.encrypt_in_place(packet, &*header, payload).unwrap(); + let tag = self + .encrypt_in_place(packet, &*header, payload, None) + .unwrap(); tag_storage.copy_from_slice(tag.as_ref()); } @@ -640,7 +795,7 @@ impl crypto::PacketKey for Box { payload: &mut BytesMut, ) -> Result<(), CryptoError> { let plain = self - .decrypt_in_place(packet, header, payload.as_mut()) + .decrypt_in_place(packet, header, payload.as_mut(), None) .map_err(|_| CryptoError)?; let plain_len = plain.len(); payload.truncate(plain_len); @@ -662,7 +817,6 @@ impl crypto::PacketKey for Box { fn interpret_version(version: u32) -> Result { match version { - 0xff00_001d..=0xff00_0020 => Ok(Version::V1Draft), 0x0000_0001 | 0xff00_0021..=0xff00_0022 => Ok(Version::V1), _ => Err(UnsupportedVersion), } diff --git a/quinn-proto/src/endpoint.rs b/quinn-proto/src/endpoint.rs index 9dcd15e3aa..6f8fdcc5a3 100644 --- a/quinn-proto/src/endpoint.rs +++ b/quinn-proto/src/endpoint.rs @@ -38,6 +38,9 @@ use crate::{ transport_parameters::{PreferredAddress, TransportParameters}, }; +#[cfg(any(feature = "rustls-aws-lc-rs", feature = "rustls-ring"))] +use crate::{connection::assembler::Assembler, range_set::ArrayRangeSet}; + /// The main entry point to the library /// /// This object performs no I/O whatsoever. Instead, it consumes incoming packets and @@ -512,6 +515,8 @@ impl Endpoint { crypto, token, incoming_idx, + #[cfg(any(feature = "rustls-aws-lc-rs", feature = "rustls-ring"))] + rustls_local_cid: None, improper_drop_warner: IncomingImproperDropWarner, })) } @@ -616,6 +621,400 @@ impl Endpoint { } let tls = server_config.crypto.clone().start_session(version, ¶ms); + self.accept_inner( + incoming_buffer, + ch, + loc_cid, + incoming.received_at, + incoming.addresses, + incoming.ecn, + incoming.packet, + incoming.rest, + incoming.crypto, + remote_address_validated, + packet_number, + src_cid, + dst_cid, + version, + buf, + server_config, + tls, + 0, + 0, + pref_addr_cid, + ) + } + + /// Poll the rustls acceptor for an incoming connection. + #[cfg(any(feature = "rustls-aws-lc-rs", feature = "rustls-ring"))] + pub fn poll_rustls_acceptor( + &mut self, + incoming: &mut Incoming, + acceptor: &mut RustlsAcceptor, + buf: &mut Vec, + ) -> Result<(Option, Option), Box> { + let server_config = self.server_config.as_ref().unwrap(); + let crypto_buffer_size = server_config.transport.crypto_buffer_size; + let InitialHeader { + src_cid, version, .. + } = incoming.packet.header.clone(); + + if !acceptor.processed_first_packet { + acceptor.processed_first_packet = true; + let packet_number = incoming.packet.header.number.expand(acceptor.rx_packet + 1); + match Self::decrypt_initial_payload( + &incoming.crypto, + packet_number, + &incoming.packet.header_data, + incoming.packet.payload.clone(), + ) { + Ok(payload) => { + match acceptor.read_initial_payload( + payload.freeze(), + incoming.packet.payload.len(), + crypto_buffer_size, + packet_number, + ) { + Ok(Some(accepted)) => return Ok((Some(accepted), None)), + Ok(None) => {} + Err(cause) => { + return Err(Box::new(AcceptError { + response: Some(self.initial_close( + version, + incoming.addresses, + &incoming.crypto, + src_cid, + cause.clone(), + buf, + )), + cause: cause.into(), + })); + } + } + } + Err(cause) => { + return Err(Box::new(AcceptError { + response: Some(self.initial_close( + version, + incoming.addresses, + &incoming.crypto, + src_cid, + cause.clone(), + buf, + )), + cause: cause.into(), + })); + } + } + } + + if !acceptor.processed_first_datagram_tail { + acceptor.processed_first_datagram_tail = true; + let mut remaining = incoming.rest.clone(); + while let Some(data) = remaining { + match PartialDecode::new( + data, + &FixedLengthConnectionIdParser::new(self.local_cid_generator.cid_len()), + &[version], + self.config.grease_quic_bit, + ) { + Ok((partial_decode, rest)) => { + remaining = rest; + if let Some(accepted) = acceptor + .read_initial_decode( + partial_decode, + &incoming.crypto, + crypto_buffer_size, + ) + .map_err(|cause| { + Box::new(AcceptError { + response: Some(self.initial_close( + version, + incoming.addresses, + &incoming.crypto, + src_cid, + cause.clone(), + buf, + )), + cause: cause.into(), + }) + })? + { + return Ok((Some(accepted), None)); + } + } + Err(_) => break, + } + } + } + + let incoming_buffer = &self.incoming_buffers[incoming.incoming_idx]; + for event in incoming_buffer + .datagrams + .iter() + .skip(acceptor.processed_buffered_datagrams) + { + acceptor.processed_buffered_datagrams += 1; + let Some(_) = event.first_decode.initial_header() else { + continue; + }; + match acceptor.read_initial_decode( + event.first_decode.clone(), + &incoming.crypto, + crypto_buffer_size, + ) { + Ok(Some(accepted)) => return Ok((Some(accepted), None)), + Ok(None) => {} + Err(cause) => { + return Err(Box::new(AcceptError { + response: Some(self.initial_close( + version, + incoming.addresses, + &incoming.crypto, + src_cid, + cause.clone(), + buf, + )), + cause: cause.into(), + })); + } + } + } + + let response = self.poll_rustls_initial_ack(incoming, acceptor, buf); + Ok((None, response)) + } + + #[cfg(any(feature = "rustls-aws-lc-rs", feature = "rustls-ring"))] + fn poll_rustls_initial_ack( + &mut self, + incoming: &mut Incoming, + acceptor: &mut RustlsAcceptor, + buf: &mut Vec, + ) -> Option { + if acceptor.pending_initial_acks.is_empty() { + return None; + } + + let src_cid = self.rustls_local_cid(incoming); + let packet_number = acceptor.next_initial_packet_number; + acceptor.next_initial_packet_number += 1; + + let header = Header::Initial(InitialHeader { + dst_cid: incoming.packet.header.src_cid, + src_cid, + number: PacketNumber::new(packet_number, 0), + token: Bytes::new(), + version: incoming.packet.header.version, + }); + + let partial_encode = header.encode(buf); + frame::Ack::encode(0, &acceptor.pending_initial_acks, None, buf); + buf.resize(buf.len() + incoming.crypto.packet.local.tag_len(), 0); + partial_encode.finish( + buf, + &*incoming.crypto.header.local, + Some((packet_number, &*incoming.crypto.packet.local)), + ); + acceptor.pending_initial_acks = ArrayRangeSet::new(); + Some(Transmit { + destination: incoming.addresses.remote, + ecn: None, + size: buf.len(), + segment_size: None, + src_ip: incoming.addresses.local_ip, + }) + } + + #[cfg(any(feature = "rustls-aws-lc-rs", feature = "rustls-ring"))] + fn rustls_local_cid(&mut self, incoming: &mut Incoming) -> ConnectionId { + if let Some(cid) = incoming.rustls_local_cid { + return cid; + } + + let cid = loop { + let cid = self.local_cid_generator.generate_cid(); + if cid.is_empty() + || (!self.index.connection_ids.contains_key(&cid) + && !self.index.connection_ids_initial.contains_key(&cid)) + { + break cid; + } + }; + self.index + .insert_initial_incoming(cid, incoming.incoming_idx); + incoming.rustls_local_cid = Some(cid); + cid + } + + /// Accept an incoming connection after rustls has read its ClientHello. + #[cfg(any(feature = "rustls-aws-lc-rs", feature = "rustls-ring"))] + pub fn accept_rustls( + &mut self, + mut incoming: Incoming, + accepted: RustlsAccepted, + now: Instant, + buf: &mut Vec, + server_config: Option>, + ) -> Result<(ConnectionHandle, Connection), Box> { + let remote_address_validated = incoming.remote_address_validated(); + incoming.improper_drop_warner.dismiss(); + let incoming_buffer = self.incoming_buffers.remove(incoming.incoming_idx); + self.all_incoming_buffers_total_bytes -= incoming_buffer.total_bytes; + + let packet_number = incoming.packet.header.number.expand(0); + let InitialHeader { + src_cid, + dst_cid, + version, + .. + } = incoming.packet.header.clone(); + let server_config = + server_config.unwrap_or_else(|| self.server_config.as_ref().unwrap().clone()); + + self.index.remove_initial(dst_cid); + if let Some(cid) = incoming.rustls_local_cid.as_ref() { + self.index.remove_initial_if_present(cid); + } + + if server_config + .transport + .max_idle_timeout + .is_some_and(|timeout| { + incoming.received_at + Duration::from_millis(timeout.into()) <= now + }) + { + debug!("abandoning accept of stale initial"); + return Err(Box::new(AcceptError { + cause: ConnectionError::TimedOut, + response: None, + })); + } + + if self.cids_exhausted() { + debug!("refusing connection"); + return Err(Box::new(AcceptError { + cause: ConnectionError::CidsExhausted, + response: Some(self.initial_close( + version, + incoming.addresses, + &incoming.crypto, + src_cid, + TransportError::CONNECTION_REFUSED(""), + buf, + )), + })); + } + + if incoming + .crypto + .packet + .remote + .decrypt( + packet_number, + &incoming.packet.header_data, + &mut incoming.packet.payload, + ) + .is_err() + { + debug!(packet_number, "failed to authenticate initial packet"); + return Err(Box::new(AcceptError { + cause: TransportError::PROTOCOL_VIOLATION("authentication failed").into(), + response: None, + })); + }; + + let ch = ConnectionHandle(self.connections.vacant_key()); + let loc_cid = incoming + .rustls_local_cid + .unwrap_or_else(|| self.new_cid(ch)); + let mut params = TransportParameters::new( + &server_config.transport, + &self.config, + self.local_cid_generator.as_ref(), + loc_cid, + Some(&server_config), + &mut self.rng, + ); + params.stateless_reset_token = Some(ResetToken::new(&*self.config.reset_key, loc_cid)); + params.original_dst_cid = Some(incoming.token.orig_dst_cid); + params.retry_src_cid = incoming.token.retry_src_cid; + let mut pref_addr_cid = None; + if server_config.has_preferred_address() { + let cid = self.new_cid(ch); + pref_addr_cid = Some(cid); + params.preferred_address = Some(PreferredAddress { + address_v4: server_config.preferred_address_v4, + address_v6: server_config.preferred_address_v6, + connection_id: cid, + stateless_reset_token: ResetToken::new(&*self.config.reset_key, cid), + }); + } + + let tls = server_config + .crypto + .clone() + .start_session_from_accepted(version, ¶ms, accepted.tls) + .map_err(|cause| { + Box::new(AcceptError { + response: Some(self.initial_close( + version, + incoming.addresses, + &incoming.crypto, + src_cid, + cause.clone(), + buf, + )), + cause: cause.into(), + }) + })?; + self.accept_inner( + incoming_buffer, + ch, + loc_cid, + incoming.received_at, + incoming.addresses, + incoming.ecn, + incoming.packet, + incoming.rest, + incoming.crypto, + remote_address_validated, + packet_number, + src_cid, + dst_cid, + version, + buf, + server_config, + tls, + accepted.consumed_initial_crypto, + accepted.next_initial_packet_number, + pref_addr_cid, + ) + } + + fn accept_inner( + &mut self, + incoming_buffer: IncomingBuffer, + ch: ConnectionHandle, + loc_cid: ConnectionId, + received_at: Instant, + addresses: FourTuple, + ecn: Option, + packet: InitialPacket, + rest: Option, + crypto: Keys, + remote_address_validated: bool, + packet_number: u64, + src_cid: ConnectionId, + dst_cid: ConnectionId, + version: u32, + buf: &mut Vec, + server_config: Arc, + tls: Box, + consumed_initial_crypto: u64, + next_initial_packet_number: u64, + pref_addr_cid: Option, + ) -> Result<(ConnectionHandle, Connection), Box> { let transport_config = server_config.transport.clone(); let mut conn = self.add_connection( ch, @@ -623,8 +1022,8 @@ impl Endpoint { dst_cid, loc_cid, src_cid, - incoming.addresses, - incoming.received_at, + addresses, + received_at, tls, transport_config, SideArgs::Server { @@ -633,15 +1032,19 @@ impl Endpoint { path_validated: remote_address_validated, }, ); + conn.skip_initial_crypto(consumed_initial_crypto); + if next_initial_packet_number != 0 { + conn.skip_initial_packet_number(next_initial_packet_number); + } self.index.insert_initial(dst_cid, ch); match conn.handle_first_packet( - incoming.received_at, - incoming.addresses.remote, - incoming.ecn, + received_at, + addresses.remote, + ecn, packet_number, - incoming.packet, - incoming.rest, + packet, + rest, ) { Ok(()) => { trace!(id = ch.0, icid = %dst_cid, "new connection"); @@ -658,8 +1061,8 @@ impl Endpoint { let response = match e { ConnectionError::TransportError(ref e) => Some(self.initial_close( version, - incoming.addresses, - &incoming.crypto, + addresses, + &crypto, src_cid, e.clone(), buf, @@ -671,6 +1074,21 @@ impl Endpoint { } } + #[cfg(any(feature = "rustls-aws-lc-rs", feature = "rustls-ring"))] + fn decrypt_initial_payload( + crypto: &Keys, + packet_number: u64, + header_data: &Bytes, + mut payload: BytesMut, + ) -> Result { + crypto + .packet + .remote + .decrypt(packet_number, header_data, &mut payload) + .map_err(|_| TransportError::PROTOCOL_VIOLATION("authentication failed"))?; + Ok(payload) + } + /// Check if we should refuse a connection attempt regardless of the packet's contents fn early_validate_first_packet( &mut self, @@ -780,6 +1198,10 @@ impl Endpoint { /// Clean up endpoint data structures associated with an `Incoming`. fn clean_up_incoming(&mut self, incoming: &Incoming) { self.index.remove_initial(incoming.packet.header.dst_cid); + #[cfg(any(feature = "rustls-aws-lc-rs", feature = "rustls-ring"))] + if let Some(cid) = incoming.rustls_local_cid.as_ref() { + self.index.remove_initial_if_present(cid); + } let incoming_buffer = self.incoming_buffers.remove(incoming.incoming_idx); self.all_incoming_buffers_total_bytes -= incoming_buffer.total_bytes; } @@ -1019,6 +1441,15 @@ impl ConnectionIndex { debug_assert!(removed.is_some()); } + /// Remove an initial destination CID route, if one exists. + #[cfg(any(feature = "rustls-aws-lc-rs", feature = "rustls-ring"))] + fn remove_initial_if_present(&mut self, dst_cid: &ConnectionId) { + if dst_cid.is_empty() { + return; + } + self.connection_ids_initial.remove(dst_cid); + } + /// Associate a connection with its initial destination CID fn insert_initial(&mut self, dst_cid: ConnectionId, connection: ConnectionHandle) { if dst_cid.is_empty() { @@ -1166,9 +1597,144 @@ pub struct Incoming { crypto: Keys, token: IncomingToken, incoming_idx: usize, + #[cfg(any(feature = "rustls-aws-lc-rs", feature = "rustls-ring"))] + rustls_local_cid: Option, improper_drop_warner: IncomingImproperDropWarner, } +/// State for reading a rustls QUIC ClientHello before choosing a server config. +#[cfg(any(feature = "rustls-aws-lc-rs", feature = "rustls-ring"))] +pub struct RustlsAcceptor { + tls: crypto::rustls::Acceptor, + crypto_stream: Assembler, + processed_first_packet: bool, + processed_first_datagram_tail: bool, + processed_buffered_datagrams: usize, + consumed_initial_crypto: u64, + rx_packet: u64, + pending_initial_acks: ArrayRangeSet, + next_initial_packet_number: u64, +} + +#[cfg(any(feature = "rustls-aws-lc-rs", feature = "rustls-ring"))] +impl RustlsAcceptor { + /// Create a rustls acceptor for an incoming connection. + pub fn new(incoming: &Incoming) -> Result { + Ok(Self { + tls: crypto::rustls::Acceptor::new(incoming.packet.header.version)?, + crypto_stream: Assembler::new(), + processed_first_packet: false, + processed_first_datagram_tail: false, + processed_buffered_datagrams: 0, + consumed_initial_crypto: 0, + rx_packet: 0, + pending_initial_acks: ArrayRangeSet::new(), + next_initial_packet_number: 0, + }) + } + + fn read_initial_decode( + &mut self, + partial_decode: PartialDecode, + crypto: &Keys, + crypto_buffer_size: usize, + ) -> Result, TransportError> { + let Ok(packet) = partial_decode.finish(Some(&*crypto.header.remote)) else { + return Ok(None); + }; + if !packet.reserved_bits_valid() { + return Ok(None); + } + let Header::Initial(header) = packet.header else { + return Ok(None); + }; + let packet_number = header.number.expand(self.rx_packet + 1); + let payload_len = packet.payload.len(); + let Ok(payload) = Endpoint::decrypt_initial_payload( + crypto, + packet_number, + &packet.header_data, + packet.payload, + ) else { + return Ok(None); + }; + self.read_initial_payload( + payload.freeze(), + payload_len, + crypto_buffer_size, + packet_number, + ) + } + + fn read_initial_payload( + &mut self, + payload: Bytes, + payload_len: usize, + crypto_buffer_size: usize, + packet_number: u64, + ) -> Result, TransportError> { + for result in frame::Iter::new(payload)? { + match result? { + frame::Frame::Crypto(frame) => { + let end = frame.offset + frame.data.len() as u64; + let max = end.saturating_sub(self.crypto_stream.bytes_read()); + if max > crypto_buffer_size as u64 { + return Err(TransportError::CRYPTO_BUFFER_EXCEEDED("")); + } + + self.crypto_stream + .insert(frame.offset, frame.data.clone(), payload_len); + while let Some(chunk) = self.crypto_stream.read(usize::MAX, true) { + self.tls.read_hs(&chunk.bytes)?; + self.consumed_initial_crypto += chunk.bytes.len() as u64; + if let Some(tls) = self.tls.accept()? { + return Ok(Some(RustlsAccepted { + tls, + consumed_initial_crypto: self.consumed_initial_crypto, + next_initial_packet_number: self.next_initial_packet_number, + })); + } + } + } + frame::Frame::Padding | frame::Frame::Ping | frame::Frame::Ack(_) => {} + frame::Frame::Close(frame::Close::Connection(reason)) => { + return Err(TransportError::NO_ERROR(String::from_utf8_lossy( + &reason.reason, + ))); + } + frame => { + let mut err = + TransportError::PROTOCOL_VIOLATION("illegal frame type in handshake"); + err.frame = Some(frame.ty()); + return Err(err); + } + } + } + + self.pending_initial_acks.insert_one(packet_number); + if packet_number > self.rx_packet { + self.rx_packet = packet_number; + } + Ok(None) + } +} + +/// A rustls ClientHello and the state required to continue the QUIC handshake. +#[cfg(any(feature = "rustls-aws-lc-rs", feature = "rustls-ring"))] +pub struct RustlsAccepted { + tls: crypto::rustls::Accepted, + consumed_initial_crypto: u64, + next_initial_packet_number: u64, +} + +#[cfg(any(feature = "rustls-aws-lc-rs", feature = "rustls-ring"))] +impl RustlsAccepted { + /// Get the rustls ClientHello for this connection. + pub fn client_hello(&self) -> rustls::server::ClientHello<'_> { + self.tls.client_hello() + } +} + impl Incoming { /// The local IP address which was used when the peer established the connection /// diff --git a/quinn-proto/src/lib.rs b/quinn-proto/src/lib.rs index 5982b69033..71d19ea2e4 100644 --- a/quinn-proto/src/lib.rs +++ b/quinn-proto/src/lib.rs @@ -73,6 +73,8 @@ mod endpoint; pub use crate::endpoint::{ AcceptError, ConnectError, ConnectionHandle, DatagramEvent, Endpoint, Incoming, RetryError, }; +#[cfg(any(feature = "rustls-aws-lc-rs", feature = "rustls-ring"))] +pub use crate::endpoint::{RustlsAccepted, RustlsAcceptor}; mod packet; pub use packet::{ @@ -157,15 +159,7 @@ pub mod fuzzing { } /// The QUIC protocol version implemented. -pub const DEFAULT_SUPPORTED_VERSIONS: &[u32] = &[ - 0x00000001, - 0xff00_001d, - 0xff00_001e, - 0xff00_001f, - 0xff00_0020, - 0xff00_0021, - 0xff00_0022, -]; +pub const DEFAULT_SUPPORTED_VERSIONS: &[u32] = &[0x00000001, 0xff00_0021, 0xff00_0022]; /// Whether an endpoint was the initiator of a connection #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] diff --git a/quinn-proto/src/packet.rs b/quinn-proto/src/packet.rs index e8ab1aabb5..6abf42dc85 100644 --- a/quinn-proto/src/packet.rs +++ b/quinn-proto/src/packet.rs @@ -21,8 +21,7 @@ use crate::{ /// across QUIC versions), which gives us the destination CID and allows us /// to inspect the version and packet type (which depends on the version). /// This information allows us to fully decode and decrypt the packet. -#[cfg_attr(test, derive(Clone))] -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct PartialDecode { plain_header: ProtectedHeader, buf: io::Cursor, @@ -938,17 +937,15 @@ mod tests { #[test] fn header_encoding() { use crate::Side; - use crate::crypto::rustls::{initial_keys, initial_suite_from_provider}; - #[cfg(all(feature = "rustls-aws-lc-rs", not(feature = "rustls-ring")))] - use rustls::crypto::aws_lc_rs::default_provider; - #[cfg(feature = "rustls-ring")] - use rustls::crypto::ring::default_provider; + use crate::crypto::rustls::{ + configured_provider, initial_keys, initial_suite_from_provider, + }; use rustls::quic::Version; let dcid = ConnectionId::new(&hex!("06b858ec6f80452b")); - let provider = default_provider(); + let provider = configured_provider(); - let suite = initial_suite_from_provider(&std::sync::Arc::new(provider)).unwrap(); + let suite = initial_suite_from_provider(&provider).unwrap(); let client = initial_keys(Version::V1, dcid, Side::Client, &suite); let mut buf = Vec::new(); let header = Header::Initial(InitialHeader { diff --git a/quinn-proto/src/tests/mod.rs b/quinn-proto/src/tests/mod.rs index c09da5d5db..48e564f95c 100644 --- a/quinn-proto/src/tests/mod.rs +++ b/quinn-proto/src/tests/mod.rs @@ -13,12 +13,10 @@ use hex_literal::hex; use rand::Rng; #[cfg(feature = "ring")] use ring::hmac; -#[cfg(all(feature = "rustls-aws-lc-rs", not(feature = "rustls-ring")))] -use rustls::crypto::aws_lc_rs::default_provider; -#[cfg(feature = "rustls-ring")] -use rustls::crypto::ring::default_provider; use rustls::{ - AlertDescription, RootCertStore, + RootCertStore, + crypto::Identity, + error::AlertDescription, pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer}, server::WebPkiClientVerifier, }; @@ -146,11 +144,11 @@ fn lifecycle() { } #[test] -fn draft_version_compat() { +fn compatible_version() { let _guard = subscribe(); let mut client_config = client_config(); - client_config.version(0xff00_0020); + client_config.version(0xff00_0022); let mut pair = Pair::default(); let (client_ch, server_ch) = pair.connect_with(client_config); @@ -296,14 +294,14 @@ fn export_keying_material() { // client keying material let mut client_buf = [0u8; 64]; pair.client_conn_mut(client_ch) - .crypto_session() + .crypto_session_mut() .export_keying_material(&mut client_buf, LABEL, CONTEXT) .unwrap(); // server keying material let mut server_buf = [0u8; 64]; pair.server_conn_mut(server_ch) - .crypto_session() + .crypto_session_mut() .export_keying_material(&mut server_buf, LABEL, CONTEXT) .unwrap(); @@ -436,7 +434,7 @@ fn reject_self_signed_server_cert() { assert_matches!(pair.client_conn_mut(client_ch).poll(), Some(Event::ConnectionLost { reason: ConnectionError::TransportError(ref error)}) - if error.code == TransportErrorCode::crypto(AlertDescription::UnknownCA.into())); + if error.code == TransportErrorCode::crypto(AlertDescription::UnknownCa.into())); } #[test] @@ -451,16 +449,17 @@ fn reject_missing_client_cert() { let key = PrivatePkcs8KeyDer::from(CERTIFIED_KEY.signing_key.serialize_der()); let cert = CERTIFIED_KEY.cert.der().clone(); - let provider = Arc::new(default_provider()); - let config = rustls::ServerConfig::builder_with_provider(provider.clone()) - .with_protocol_versions(&[&rustls::version::TLS13]) - .unwrap() - .with_client_cert_verifier( - WebPkiClientVerifier::builder_with_provider(Arc::new(store), provider) + let provider = crypto::rustls::configured_provider(); + let config = rustls::ServerConfig::builder(provider.clone()) + .with_client_cert_verifier(Arc::new( + WebPkiClientVerifier::builder(Arc::new(store), &provider) .build() .unwrap(), + )) + .with_single_cert( + Arc::new(Identity::from_cert_chain(vec![cert]).unwrap()), + PrivateKeyDer::from(key), ) - .with_single_cert(vec![cert], PrivateKeyDer::from(key)) .unwrap(); let config = QuicServerConfig::try_from(config).unwrap(); @@ -647,7 +646,7 @@ fn zero_rtt_rejection() { // the existing `ClientConfig` and change the ALPN protocols to make that happen. let this = Arc::get_mut(&mut client_crypto).expect("QuicClientConfig is shared"); let inner = Arc::get_mut(&mut this.inner).expect("QuicClientConfig.inner is shared"); - inner.alpn_protocols = vec!["bar".into()]; + inner.alpn_protocols = vec![b"bar".as_slice().into()]; // Changing protocols invalidates 0-RTT let client_config = ClientConfig::new(client_crypto); @@ -842,16 +841,11 @@ fn alpn_success() { .downcast::() .unwrap(); assert_eq!(hd.protocol.unwrap(), &b"bar"[..]); - assert_eq!( - hd.protocol_version - .unwrap() - .downcast_ref::(), - Some(&rustls::ProtocolVersion::TLSv1_3) - ); + assert!(hd.protocol_version.is_some()); assert!( hd.cipher_suite .unwrap() - .downcast_ref::() + .downcast_ref::() .is_some() ); } diff --git a/quinn-proto/src/tests/util.rs b/quinn-proto/src/tests/util.rs index 24c13d2a47..2d9eb00845 100644 --- a/quinn-proto/src/tests/util.rs +++ b/quinn-proto/src/tests/util.rs @@ -13,13 +13,13 @@ use std::{ use assert_matches::assert_matches; use bytes::BytesMut; use rustls::{ - KeyLogFile, client::WebPkiServerVerifier, + crypto::{CryptoProvider, Identity}, pki_types::{CertificateDer, PrivateKeyDer}, }; use tracing::{info_span, trace}; -use super::crypto::rustls::{QuicClientConfig, QuicServerConfig, configured_provider}; +use super::crypto::rustls::{QuicClientConfig, QuicServerConfig}; use super::*; use crate::{Duration, Instant}; @@ -610,9 +610,17 @@ fn server_crypto_inner( ) }); - let mut config = QuicServerConfig::inner(vec![cert], key).unwrap(); + let provider = test_provider(); + let mut config = rustls::ServerConfig::builder(provider) + .with_no_client_auth() + .with_single_cert( + Arc::new(Identity::from_cert_chain(vec![cert]).unwrap()), + key, + ) + .unwrap(); + config.max_early_data_size = u32::MAX; if let Some(alpn) = alpn { - config.alpn_protocols = alpn; + config.alpn_protocols = alpn.into_iter().map(Into::into).collect(); } config.try_into().unwrap() @@ -651,19 +659,48 @@ fn client_crypto_inner( roots.add(cert).unwrap(); } - let mut inner = QuicClientConfig::inner( - WebPkiServerVerifier::builder_with_provider(Arc::new(roots), configured_provider()) - .build() - .unwrap(), - ); - inner.key_log = Arc::new(KeyLogFile::new()); + let provider = test_provider(); + let verifier = WebPkiServerVerifier::builder(Arc::new(roots), &provider) + .build() + .unwrap(); + let mut inner = rustls::ClientConfig::builder(provider) + .dangerous() + .with_custom_certificate_verifier(Arc::new(verifier)) + .with_no_client_auth() + .expect("test provider is valid for QUIC"); + inner.enable_early_data = true; if let Some(alpn) = alpn { - inner.alpn_protocols = alpn; + inner.alpn_protocols = alpn.into_iter().map(Into::into).collect(); } inner.try_into().unwrap() } +#[cfg(all(feature = "rustls-aws-lc-rs-fips", not(feature = "rustls-ring")))] +fn test_provider() -> Arc { + Arc::new(CryptoProvider { + kx_groups: std::borrow::Cow::Owned(vec![rustls_aws_lc_rs::kx_group::SECP256R1]), + ..rustls_aws_lc_rs::DEFAULT_FIPS_PROVIDER + }) +} + +#[cfg(all( + feature = "rustls-aws-lc-rs", + not(feature = "rustls-aws-lc-rs-fips"), + not(feature = "rustls-ring") +))] +fn test_provider() -> Arc { + Arc::new(CryptoProvider { + kx_groups: std::borrow::Cow::Owned(vec![rustls_aws_lc_rs::kx_group::X25519]), + ..rustls_aws_lc_rs::DEFAULT_PROVIDER + }) +} + +#[cfg(feature = "rustls-ring")] +fn test_provider() -> Arc { + Arc::new(rustls_ring::DEFAULT_PROVIDER) +} + pub(super) fn min_opt(x: Option, y: Option) -> Option { match (x, y) { (Some(x), Some(y)) => Some(cmp::min(x, y)), diff --git a/quinn/Cargo.toml b/quinn/Cargo.toml index 5d873965f3..d33927e515 100644 --- a/quinn/Cargo.toml +++ b/quinn/Cargo.toml @@ -28,10 +28,10 @@ platform-verifier = ["proto/platform-verifier"] # For backwards compatibility, `rustls` forwards to `rustls-ring` rustls = ["rustls-ring"] # Enable rustls with the `aws-lc-rs` crypto provider -rustls-aws-lc-rs = ["dep:rustls", "aws-lc-rs", "proto/rustls-aws-lc-rs", "proto/aws-lc-rs"] -rustls-aws-lc-rs-fips = ["dep:rustls", "aws-lc-rs-fips", "proto/rustls-aws-lc-rs-fips", "proto/aws-lc-rs-fips"] +rustls-aws-lc-rs = ["__rustls", "dep:rustls-aws-lc-rs", "aws-lc-rs", "proto/rustls-aws-lc-rs", "proto/aws-lc-rs"] +rustls-aws-lc-rs-fips = ["rustls-aws-lc-rs", "aws-lc-rs-fips", "proto/rustls-aws-lc-rs-fips", "proto/aws-lc-rs-fips"] # Enable rustls with the `ring` crypto provider -rustls-ring = ["dep:rustls", "ring", "proto/rustls-ring", "proto/ring"] +rustls-ring = ["__rustls", "dep:rustls-ring", "ring", "proto/rustls-ring", "proto/ring"] # Enable the `ring` crypto provider. # Outside wasm*-unknown-unknown targets, this enables `Endpoint::client` and `Endpoint::server` conveniences. ring = ["proto/ring"] @@ -41,14 +41,15 @@ runtime-smol = ["dep:async-io", "dep:smol"] # Configure `tracing` to log events via `log` if no `tracing` subscriber exists. tracing-log = ["tracing/log", "proto/tracing-log", "udp/tracing-log"] # Enable rustls logging -rustls-log = ["rustls?/logging"] +rustls-log = ["rustls?/log"] # Enable qlog support qlog = ["proto/qlog"] # Internal (PRIVATE!) features used to aid testing. # Don't rely on these whatsoever. They may disappear at any time. -__rustls-post-quantum-test = ["rustls/prefer-post-quantum", "rustls-aws-lc-rs", "proto/__rustls-post-quantum-test"] +__rustls-post-quantum-test = ["rustls-aws-lc-rs", "proto/__rustls-post-quantum-test"] +__rustls = ["dep:rustls"] [dependencies] async-io = { workspace = true, optional = true } @@ -59,6 +60,8 @@ rustc-hash = { workspace = true } pin-project-lite = { workspace = true } proto = { package = "quinn-proto", path = "../quinn-proto", version = "0.12.0", default-features = false } rustls = { workspace = true, optional = true } +rustls-aws-lc-rs = { workspace = true, optional = true } +rustls-ring = { workspace = true, optional = true } smol = { workspace = true, optional = true } thiserror = { workspace = true } tracing = { workspace = true } @@ -78,6 +81,7 @@ bencher = { workspace = true } directories-next = { workspace = true } rand = { workspace = true } rcgen = { workspace = true } +rustls-util = { workspace = true } clap = { workspace = true } tokio = { workspace = true, features = ["rt", "rt-multi-thread", "time", "macros", "test-util"] } tracing-subscriber = { workspace = true } @@ -92,23 +96,23 @@ workspace = true [[example]] name = "server" -required-features = ["rustls-ring"] +required-features = ["__rustls"] [[example]] name = "client" -required-features = ["rustls-ring"] +required-features = ["__rustls"] [[example]] name = "insecure_connection" -required-features = ["rustls-ring"] +required-features = ["__rustls"] [[example]] name = "single_socket" -required-features = ["rustls-ring"] +required-features = ["__rustls"] [[example]] name = "connection" -required-features = ["rustls-ring"] +required-features = ["__rustls"] [[test]] name = "post_quantum" @@ -117,8 +121,8 @@ required-features = ["__rustls-post-quantum-test"] [[bench]] name = "bench" harness = false -required-features = ["rustls-ring"] +required-features = ["__rustls"] [package.metadata.docs.rs] # all non-default features except fips (cannot build on docs.rs environment) -features = ["lock_tracking", "rustls-aws-lc-rs", "rustls-ring", "runtime-tokio", "runtime-smol", "tracing-log", "rustls-log"] +features = ["lock_tracking", "rustls-aws-lc-rs", "rustls-ring", "platform-verifier", "runtime-tokio", "runtime-smol", "tracing-log", "rustls-log"] diff --git a/quinn/examples/client.rs b/quinn/examples/client.rs index 40d29c9dbe..40763534ac 100644 --- a/quinn/examples/client.rs +++ b/quinn/examples/client.rs @@ -20,6 +20,16 @@ use url::Url; mod common; +#[cfg(feature = "rustls-aws-lc-rs")] +fn default_provider() -> rustls::crypto::CryptoProvider { + rustls_aws_lc_rs::DEFAULT_PROVIDER +} + +#[cfg(all(not(feature = "rustls-aws-lc-rs"), feature = "rustls-ring"))] +fn default_provider() -> rustls::crypto::CryptoProvider { + rustls_ring::DEFAULT_PROVIDER +} + /// HTTP/0.9 over QUIC client #[derive(Parser, Debug)] #[clap(name = "client")] @@ -92,13 +102,13 @@ async fn run(options: Opt) -> Result<()> { } } } - let mut client_crypto = rustls::ClientConfig::builder() + let mut client_crypto = rustls::ClientConfig::builder(Arc::new(default_provider())) .with_root_certificates(roots) - .with_no_client_auth(); + .with_no_client_auth()?; client_crypto.alpn_protocols = common::ALPN_QUIC_HTTP.iter().map(|&x| x.into()).collect(); if options.keylog { - client_crypto.key_log = Arc::new(rustls::KeyLogFile::new()); + client_crypto.key_log = Arc::new(rustls_util::KeyLogFile::new()); } let client_config = diff --git a/quinn/examples/insecure_connection.rs b/quinn/examples/insecure_connection.rs index 39fa5ba231..df92ca6192 100644 --- a/quinn/examples/insecure_connection.rs +++ b/quinn/examples/insecure_connection.rs @@ -10,11 +10,20 @@ use std::{ use proto::crypto::rustls::QuicClientConfig; use quinn::{ClientConfig, Endpoint}; -use rustls::pki_types::{CertificateDer, ServerName, UnixTime}; mod common; use common::make_server_endpoint; +#[cfg(feature = "rustls-aws-lc-rs")] +fn default_provider() -> rustls::crypto::CryptoProvider { + rustls_aws_lc_rs::DEFAULT_PROVIDER +} + +#[cfg(all(not(feature = "rustls-aws-lc-rs"), feature = "rustls-ring"))] +fn default_provider() -> rustls::crypto::CryptoProvider { + rustls_ring::DEFAULT_PROVIDER +} + #[tokio::main] async fn main() -> Result<(), Box> { // server and client are running on the same thread asynchronously @@ -40,10 +49,10 @@ async fn run_client(server_addr: SocketAddr) -> Result<(), Box); impl SkipServerVerification { fn new() -> Arc { - Arc::new(Self(Arc::new(rustls::crypto::ring::default_provider()))) + Arc::new(Self(Arc::new(default_provider()))) } } -impl rustls::client::danger::ServerCertVerifier for SkipServerVerification { - fn verify_server_cert( +impl rustls::client::danger::ServerVerifier for SkipServerVerification { + fn verify_identity( &self, - _end_entity: &CertificateDer<'_>, - _intermediates: &[CertificateDer<'_>], - _server_name: &ServerName<'_>, - _ocsp: &[u8], - _now: UnixTime, - ) -> Result { - Ok(rustls::client::danger::ServerCertVerified::assertion()) + _identity: &rustls::client::danger::ServerIdentity<'_>, + ) -> Result { + Ok(rustls::client::danger::PeerVerified::assertion()) } fn verify_tls12_signature( &self, - message: &[u8], - cert: &CertificateDer<'_>, - dss: &rustls::DigitallySignedStruct, + input: &rustls::client::danger::SignatureVerificationInput<'_>, ) -> Result { - rustls::crypto::verify_tls12_signature( - message, - cert, - dss, - &self.0.signature_verification_algorithms, - ) + rustls::crypto::verify_tls12_signature(input, &self.0.signature_verification_algorithms) } fn verify_tls13_signature( &self, - message: &[u8], - cert: &CertificateDer<'_>, - dss: &rustls::DigitallySignedStruct, + input: &rustls::client::danger::SignatureVerificationInput<'_>, ) -> Result { - rustls::crypto::verify_tls13_signature( - message, - cert, - dss, - &self.0.signature_verification_algorithms, - ) + rustls::crypto::verify_tls13_signature(input, &self.0.signature_verification_algorithms) } - fn supported_verify_schemes(&self) -> Vec { + fn supported_verify_schemes(&self) -> Vec { self.0.signature_verification_algorithms.supported_schemes() } + + fn request_ocsp_response(&self) -> bool { + false + } + + fn hash_config(&self, _h: &mut dyn std::hash::Hasher) {} } diff --git a/quinn/examples/server.rs b/quinn/examples/server.rs index 3f0b6740f5..bb8a8bf8ab 100644 --- a/quinn/examples/server.rs +++ b/quinn/examples/server.rs @@ -13,12 +13,25 @@ use std::{ use anyhow::{Context, Result, anyhow, bail}; use clap::Parser; use proto::crypto::rustls::QuicServerConfig; -use rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer, pem::PemObject}; +use rustls::{ + crypto::Identity, + pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer, pem::PemObject}, +}; use tracing::{error, info, info_span}; use tracing_futures::Instrument as _; mod common; +#[cfg(feature = "rustls-aws-lc-rs")] +fn default_provider() -> rustls::crypto::CryptoProvider { + rustls_aws_lc_rs::DEFAULT_PROVIDER +} + +#[cfg(all(not(feature = "rustls-aws-lc-rs"), feature = "rustls-ring"))] +fn default_provider() -> rustls::crypto::CryptoProvider { + rustls_ring::DEFAULT_PROVIDER +} + #[derive(Parser, Debug)] #[clap(name = "server")] struct Opt { @@ -119,12 +132,12 @@ async fn run(options: Opt) -> Result<()> { (vec![cert], key) }; - let mut server_crypto = rustls::ServerConfig::builder() + let mut server_crypto = rustls::ServerConfig::builder(Arc::new(default_provider())) .with_no_client_auth() - .with_single_cert(certs, key)?; + .with_single_cert(Arc::new(Identity::from_cert_chain(certs)?), key)?; server_crypto.alpn_protocols = common::ALPN_QUIC_HTTP.iter().map(|&x| x.into()).collect(); if options.keylog { - server_crypto.key_log = Arc::new(rustls::KeyLogFile::new()); + server_crypto.key_log = Arc::new(rustls_util::KeyLogFile::new()); } let mut server_config = diff --git a/quinn/src/connection.rs b/quinn/src/connection.rs index 720058c615..076e1cbe19 100644 --- a/quinn/src/connection.rs +++ b/quinn/src/connection.rs @@ -674,7 +674,7 @@ impl Connection { .state .lock("export_keying_material") .inner - .crypto_session() + .crypto_session_mut() .export_keying_material(output, label, context) } diff --git a/quinn/src/endpoint.rs b/quinn/src/endpoint.rs index 6bb7a5951b..8cbd14cae3 100644 --- a/quinn/src/endpoint.rs +++ b/quinn/src/endpoint.rs @@ -400,7 +400,7 @@ impl Future for EndpointDriver { let now = endpoint.runtime.now(); let mut keep_going = false; - keep_going |= endpoint.drive_recv(cx, now)?; + keep_going |= endpoint.drive_recv(cx, now, &self.0.shared)?; keep_going |= endpoint.handle_events(cx, &self.0.shared); if !endpoint.recv_state.incoming.is_empty() { @@ -472,6 +472,72 @@ impl EndpointInner { } } + #[cfg(any(feature = "rustls-aws-lc-rs", feature = "rustls-ring"))] + pub(crate) fn poll_rustls_acceptor( + &self, + incoming: &mut proto::Incoming, + acceptor: &mut proto::RustlsAcceptor, + ) -> Result, ConnectionError> { + let mut state = self.state.lock().unwrap(); + if state.driver_lost || state.recv_state.connections.close.is_some() { + return Err(ConnectionError::LocallyClosed); + } + let mut response_buffer = Vec::new(); + match state + .inner + .poll_rustls_acceptor(incoming, acceptor, &mut response_buffer) + { + Ok((accepted, response)) => { + if let Some(transmit) = response { + respond(transmit, &response_buffer, &mut state.sender); + } + Ok(accepted) + } + Err(error) => { + state.stats.refused_handshakes += 1; + if let Some(transmit) = error.response { + respond(transmit, &response_buffer, &mut state.sender); + } + Err(error.cause) + } + } + } + + #[cfg(any(feature = "rustls-aws-lc-rs", feature = "rustls-ring"))] + pub(crate) fn accept_rustls( + &self, + incoming: proto::Incoming, + accepted: proto::RustlsAccepted, + server_config: Option>, + ) -> Result { + let mut state = self.state.lock().unwrap(); + let mut response_buffer = Vec::new(); + let now = state.runtime.now(); + match state.inner.accept_rustls( + incoming, + accepted, + now, + &mut response_buffer, + server_config, + ) { + Ok((handle, conn)) => { + state.stats.accepted_handshakes += 1; + let sender = state.socket.create_sender(); + let runtime = state.runtime.clone(); + Ok(state + .recv_state + .connections + .insert(handle, conn, sender, runtime)) + } + Err(error) => { + if let Some(transmit) = error.response { + respond(transmit, &response_buffer, &mut state.sender); + } + Err(error.cause) + } + } + } + pub(crate) fn refuse(&self, incoming: proto::Incoming) { let mut state = self.state.lock().unwrap(); state.stats.refused_handshakes += 1; @@ -515,14 +581,19 @@ pub(crate) struct State { #[derive(Debug)] pub(crate) struct Shared { - incoming: Notify, + pub(crate) incoming: Arc, idle: Notify, /// Number of live handles that can be used to initiate or handle I/O; excludes the driver ref_count: AtomicUsize, } impl State { - fn drive_recv(&mut self, cx: &mut Context<'_>, now: Instant) -> Result { + fn drive_recv( + &mut self, + cx: &mut Context<'_>, + now: Instant, + shared: &Shared, + ) -> Result { let get_time = || self.runtime.now(); self.recv_state.recv_limiter.start_cycle(get_time); if let Some(socket) = &mut self.prev_socket { @@ -549,6 +620,9 @@ impl State { ); self.recv_state.recv_limiter.finish_cycle(get_time); let poll_res = poll_res?; + if poll_res.received_datagram { + shared.incoming.notify_waiters(); + } if poll_res.received_connection_packet { // Traffic has arrived on self.socket, therefore there is no need for the abandoned // one anymore. TODO: Account for multiple outgoing connections. @@ -753,7 +827,7 @@ impl EndpointRef { let sender = socket.create_sender(); Self(Arc::new(EndpointInner { shared: Shared { - incoming: Notify::new(), + incoming: Arc::new(Notify::new()), idle: Notify::new(), ref_count: AtomicUsize::new(0), }, @@ -846,6 +920,7 @@ impl RecvState { now: Instant, ) -> Result { let mut received_connection_packet = false; + let mut received_datagram = false; let mut metas = [RecvMeta::default(); BATCH_SIZE]; let mut iovs: [IoSliceMut<'_>; BATCH_SIZE] = { let mut bufs = self @@ -861,6 +936,7 @@ impl RecvState { loop { match socket.poll_recv(cx, &mut iovs, &mut metas) { Poll::Ready(Ok(msgs)) => { + received_datagram |= msgs != 0; self.recv_limiter.record_work(msgs); for (meta, buf) in metas.iter().zip(iovs.iter()).take(msgs) { let mut data: BytesMut = buf[0..meta.len].into(); @@ -904,6 +980,7 @@ impl RecvState { } Poll::Pending => { return Ok(PollProgress { + received_datagram, received_connection_packet, keep_going: false, }); @@ -919,6 +996,7 @@ impl RecvState { } if !self.recv_limiter.allow_work(|| runtime.now()) { return Ok(PollProgress { + received_datagram, received_connection_packet, keep_going: true, }); @@ -940,6 +1018,8 @@ impl fmt::Debug for RecvState { #[derive(Default)] struct PollProgress { + /// Whether any datagram was received + received_datagram: bool, /// Whether a datagram was routed to an existing connection received_connection_packet: bool, /// Whether datagram handling was interrupted early by the work limiter for fairness diff --git a/quinn/src/incoming.rs b/quinn/src/incoming.rs index 47471bdfbf..328d829c79 100644 --- a/quinn/src/incoming.rs +++ b/quinn/src/incoming.rs @@ -8,6 +8,8 @@ use std::{ use proto::{ConnectionError, ConnectionId, ServerConfig}; use thiserror::Error; +#[cfg(any(feature = "rustls-aws-lc-rs", feature = "rustls-ring"))] +use tokio::sync::futures::OwnedNotified; use crate::{ connection::{Connecting, Connection}, @@ -40,6 +42,39 @@ impl Incoming { state.endpoint.accept(state.inner, Some(server_config)) } + /// Start reading this connection's rustls ClientHello before choosing a server config. + #[cfg(any(feature = "rustls-aws-lc-rs", feature = "rustls-ring"))] + pub fn acceptor(mut self) -> Result { + let state = self.0.take().unwrap(); + let acceptor = proto::RustlsAcceptor::new(&state.inner) + .map_err(|_| ConnectionError::VersionMismatch)?; + let notify = Box::pin(state.endpoint.shared.incoming.clone().notified_owned()); + Ok(Acceptor { + state: Some(AcceptorState { + inner: state.inner, + endpoint: state.endpoint, + acceptor, + notify, + }), + }) + } + + /// Accept this incoming connection with a server config selected after reading ClientHello. + /// + /// This is a typed helper over [`acceptor`][Self::acceptor]. Use + /// [`acceptor`][Self::acceptor] directly when the caller needs custom control flow between + /// reading ClientHello and continuing the handshake. + #[cfg(any(feature = "rustls-aws-lc-rs", feature = "rustls-ring"))] + pub async fn accept_with_selector( + self, + selector: &S, + ) -> Result> + where + S: ServerConfigSelector + ?Sized, + { + self.acceptor()?.await?.accept_with_selector(selector).await + } + /// Reject this incoming connection attempt pub fn refuse(mut self) { let state = self.0.take().unwrap(); @@ -115,6 +150,178 @@ struct State { endpoint: EndpointRef, } +/// Error returned by [`Incoming::accept_with_selector`] or +/// [`Accepted::accept_with_selector`]. +#[cfg(any(feature = "rustls-aws-lc-rs", feature = "rustls-ring"))] +#[derive(Debug, Error)] +pub enum AcceptWithSelectorError { + /// The QUIC connection failed while reading ClientHello or continuing the handshake. + #[error(transparent)] + Connection(#[from] ConnectionError), + /// The caller-provided selector failed. + #[error("server config selection failed")] + Selection(E), +} + +/// Selects the server config to use after rustls has read ClientHello. +/// +/// This is a convenience layer over the staged acceptor API. Implementors may do synchronous or +/// asynchronous work before returning a config. Returning `None` continues with the endpoint's +/// default server config. +#[cfg(any(feature = "rustls-aws-lc-rs", feature = "rustls-ring"))] +pub trait ServerConfigSelector { + /// Error returned by the selector. + type Error; + + /// Select a server config for the provided ClientHello. + fn select_server_config<'a>( + &'a self, + client_hello: rustls::server::ClientHello<'a>, + ) -> impl Future>, Self::Error>> + 'a; +} + +/// Future that resolves once rustls has read the incoming ClientHello. +#[cfg(any(feature = "rustls-aws-lc-rs", feature = "rustls-ring"))] +pub struct Acceptor { + state: Option, +} + +#[cfg(any(feature = "rustls-aws-lc-rs", feature = "rustls-ring"))] +struct AcceptorState { + inner: proto::Incoming, + endpoint: EndpointRef, + acceptor: proto::RustlsAcceptor, + notify: Pin>, +} + +#[cfg(any(feature = "rustls-aws-lc-rs", feature = "rustls-ring"))] +impl Future for Acceptor { + type Output = Result; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + loop { + let state = self.state.as_mut().expect("polled after completion"); + let poll_result = state + .endpoint + .poll_rustls_acceptor(&mut state.inner, &mut state.acceptor); + match poll_result { + Ok(Some(accepted)) => { + let state = self.state.take().unwrap(); + return Poll::Ready(Ok(Accepted { + state: Some(AcceptedState { + inner: state.inner, + endpoint: state.endpoint, + accepted, + }), + })); + } + Ok(None) => { + if state.notify.as_mut().poll(cx).is_ready() { + state.notify = + Box::pin(state.endpoint.shared.incoming.clone().notified_owned()); + continue; + } + return Poll::Pending; + } + Err(error) => { + let state = self.state.take().unwrap(); + state.endpoint.ignore(state.inner); + return Poll::Ready(Err(error)); + } + } + } + } +} + +#[cfg(any(feature = "rustls-aws-lc-rs", feature = "rustls-ring"))] +impl Drop for Acceptor { + fn drop(&mut self) { + if let Some(state) = self.state.take() { + state.endpoint.refuse(state.inner); + } + } +} + +/// An incoming connection whose ClientHello has been read. +#[cfg(any(feature = "rustls-aws-lc-rs", feature = "rustls-ring"))] +pub struct Accepted { + state: Option, +} + +#[cfg(any(feature = "rustls-aws-lc-rs", feature = "rustls-ring"))] +struct AcceptedState { + inner: proto::Incoming, + endpoint: EndpointRef, + accepted: proto::RustlsAccepted, +} + +#[cfg(any(feature = "rustls-aws-lc-rs", feature = "rustls-ring"))] +impl Accepted { + /// Get the rustls ClientHello for this connection. + pub fn client_hello(&self) -> rustls::server::ClientHello<'_> { + self.state.as_ref().unwrap().accepted.client_hello() + } + + /// Select a server config with `selector` and continue the QUIC handshake. + /// + /// This is a typed helper over [`client_hello`][Self::client_hello], + /// [`accept`][Self::accept], and [`accept_with`][Self::accept_with]. The lower-level staged + /// API remains available when callers need custom control flow. + pub async fn accept_with_selector( + self, + selector: &S, + ) -> Result> + where + S: ServerConfigSelector + ?Sized, + { + let server_config = selector + .select_server_config(self.client_hello()) + .await + .map_err(AcceptWithSelectorError::Selection)?; + + match server_config { + Some(server_config) => self.accept_with(server_config).map_err(Into::into), + None => self.accept().map_err(Into::into), + } + } + + /// Continue the QUIC handshake using the endpoint's configured server configuration. + pub fn accept(mut self) -> Result { + let state = self.state.take().unwrap(); + state + .endpoint + .accept_rustls(state.inner, state.accepted, None) + } + + /// Continue the QUIC handshake using a custom server configuration. + /// + /// See [`accept()`][Accepted::accept] for more details. + pub fn accept_with( + mut self, + server_config: Arc, + ) -> Result { + let state = self.state.take().unwrap(); + state + .endpoint + .accept_rustls(state.inner, state.accepted, Some(server_config)) + } + + /// Reject this incoming connection attempt. + pub fn refuse(mut self) { + let state = self.state.take().unwrap(); + state.endpoint.refuse(state.inner); + } +} + +#[cfg(any(feature = "rustls-aws-lc-rs", feature = "rustls-ring"))] +impl Drop for Accepted { + fn drop(&mut self) { + if let Some(state) = self.state.take() { + state.endpoint.refuse(state.inner); + } + } +} + /// Error for attempting to retry an [`Incoming`] which already bears a token from a previous retry #[derive(Debug, Error)] #[error("retry() with validated Incoming")] diff --git a/quinn/src/lib.rs b/quinn/src/lib.rs index 06271c10c5..13d0f6e95a 100644 --- a/quinn/src/lib.rs +++ b/quinn/src/lib.rs @@ -77,6 +77,8 @@ pub use crate::connection::{ SendDatagramError, }; pub use crate::endpoint::{Accept, Endpoint, EndpointStats}; +#[cfg(any(feature = "rustls-aws-lc-rs", feature = "rustls-ring"))] +pub use crate::incoming::{AcceptWithSelectorError, Accepted, Acceptor, ServerConfigSelector}; pub use crate::incoming::{Incoming, IncomingFuture, RetryError}; pub use crate::recv_stream::{ReadError, ReadExactError, ReadToEndError, RecvStream, ResetError}; #[cfg(feature = "runtime-smol")] diff --git a/quinn/src/tests.rs b/quinn/src/tests.rs index 6d964bd4fe..82c9636099 100755 --- a/quinn/src/tests.rs +++ b/quinn/src/tests.rs @@ -1,12 +1,7 @@ #![cfg(any(feature = "rustls-aws-lc-rs", feature = "rustls-ring"))] -#[cfg(all(feature = "rustls-aws-lc-rs", not(feature = "rustls-ring")))] -use rustls::crypto::aws_lc_rs::default_provider; -#[cfg(feature = "rustls-ring")] -use rustls::crypto::ring::default_provider; - use std::{ - convert::TryInto, + convert::{Infallible, TryInto}, future::Future, io, net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, UdpSocket}, @@ -26,6 +21,7 @@ use proto::{RandomConnectionIdGenerator, crypto::rustls::QuicClientConfig}; use rand::{Rng, SeedableRng, rngs::StdRng}; use rustls::{ RootCertStore, + crypto::Identity, pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer}, }; use tokio::time::{sleep, timeout}; @@ -37,7 +33,35 @@ use tracing::{error_span, info}; use tracing_futures::Instrument as _; use tracing_subscriber::EnvFilter; -use super::{ClientConfig, Endpoint, EndpointConfig, RecvStream, SendStream, TransportConfig}; +use super::{ + ClientConfig, Endpoint, EndpointConfig, RecvStream, SendStream, ServerConfigSelector, + TransportConfig, +}; + +#[cfg(all(feature = "rustls-aws-lc-rs-fips", not(feature = "rustls-ring")))] +fn default_provider() -> rustls::crypto::CryptoProvider { + rustls::crypto::CryptoProvider { + kx_groups: std::borrow::Cow::Owned(vec![rustls_aws_lc_rs::kx_group::SECP256R1]), + ..rustls_aws_lc_rs::DEFAULT_FIPS_PROVIDER + } +} + +#[cfg(all( + feature = "rustls-aws-lc-rs", + not(feature = "rustls-aws-lc-rs-fips"), + not(feature = "rustls-ring") +))] +fn default_provider() -> rustls::crypto::CryptoProvider { + rustls::crypto::CryptoProvider { + kx_groups: std::borrow::Cow::Owned(vec![rustls_aws_lc_rs::kx_group::X25519]), + ..rustls_aws_lc_rs::DEFAULT_PROVIDER + } +} + +#[cfg(feature = "rustls-ring")] +fn default_provider() -> rustls::crypto::CryptoProvider { + rustls_ring::DEFAULT_PROVIDER +} #[test] fn handshake_timeout() { @@ -272,6 +296,238 @@ fn endpoint_with_config(transport_config: TransportConfig) -> Endpoint { EndpointFactory::new().endpoint_with_config(transport_config) } +#[tokio::test] +async fn incoming_acceptor_exposes_client_hello() { + let _guard = subscribe(); + let cert = rcgen::generate_simple_self_signed(vec!["localhost".into()]).unwrap(); + let cert_der = CertificateDer::from(cert.cert); + let alpn = b"quinn-acceptor-test"; + + let server_config = |alpn: &[u8]| { + let mut server_crypto = rustls::ServerConfig::builder(Arc::new(default_provider())) + .with_no_client_auth() + .with_single_cert( + Arc::new(Identity::from_cert_chain(vec![cert_der.clone()]).unwrap()), + PrivatePkcs8KeyDer::from(cert.signing_key.serialize_der()).into(), + ) + .unwrap(); + server_crypto.alpn_protocols = vec![alpn.to_vec().into()]; + crate::ServerConfig::with_crypto(Arc::new( + crate::crypto::rustls::QuicServerConfig::try_from(server_crypto).unwrap(), + )) + }; + let default_server_config = server_config(b"default-alpn"); + let selected_server_config = server_config(alpn); + + let server = Endpoint::new( + EndpointConfig::default(), + Some(default_server_config), + UdpSocket::bind(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0)).unwrap(), + Arc::new(TokioRuntime), + ) + .unwrap(); + let server_addr = server.local_addr().unwrap(); + + let mut roots = RootCertStore::empty(); + roots.add(cert_der).unwrap(); + let mut client_crypto = rustls::ClientConfig::builder(Arc::new(default_provider())) + .with_root_certificates(roots) + .with_no_client_auth() + .unwrap(); + client_crypto.alpn_protocols = vec![alpn.as_slice().into()]; + let client = Endpoint::client(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0)).unwrap(); + client.set_default_client_config(ClientConfig::new(Arc::new( + QuicClientConfig::try_from(client_crypto).unwrap(), + ))); + + let server_task = async { + let incoming = server.accept().await.unwrap(); + let accepted = incoming.acceptor().unwrap().await.unwrap(); + let hello = accepted.client_hello(); + assert_eq!( + hello.server_name().map(|name| name.as_ref()), + Some("localhost") + ); + assert_eq!( + hello.alpn().unwrap().map(<[_]>::to_vec).collect::>(), + vec![alpn.to_vec()] + ); + accepted + .accept_with(Arc::new(selected_server_config)) + .unwrap() + .await + .unwrap() + }; + + let client_task = async { + client + .connect(server_addr, "localhost") + .unwrap() + .await + .unwrap() + }; + + let (server_conn, client_conn) = join!(server_task, client_task); + client_conn.close(0u32.into(), b"done"); + server_conn.closed().await; +} + +#[tokio::test] +async fn incoming_acceptor_can_continue_with_default_server_config() { + let _guard = subscribe(); + let cert = rcgen::generate_simple_self_signed(vec!["localhost".into()]).unwrap(); + let cert_der = CertificateDer::from(cert.cert); + let alpn = b"quinn-acceptor-default-test"; + + let mut server_crypto = rustls::ServerConfig::builder(Arc::new(default_provider())) + .with_no_client_auth() + .with_single_cert( + Arc::new(Identity::from_cert_chain(vec![cert_der.clone()]).unwrap()), + PrivatePkcs8KeyDer::from(cert.signing_key.serialize_der()).into(), + ) + .unwrap(); + server_crypto.alpn_protocols = vec![alpn.to_vec().into()]; + let server_config = crate::ServerConfig::with_crypto(Arc::new( + crate::crypto::rustls::QuicServerConfig::try_from(server_crypto).unwrap(), + )); + + let server = Endpoint::new( + EndpointConfig::default(), + Some(server_config), + UdpSocket::bind(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0)).unwrap(), + Arc::new(TokioRuntime), + ) + .unwrap(); + let server_addr = server.local_addr().unwrap(); + + let mut roots = RootCertStore::empty(); + roots.add(cert_der).unwrap(); + let mut client_crypto = rustls::ClientConfig::builder(Arc::new(default_provider())) + .with_root_certificates(roots) + .with_no_client_auth() + .unwrap(); + client_crypto.alpn_protocols = vec![alpn.as_slice().into()]; + let client = Endpoint::client(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0)).unwrap(); + client.set_default_client_config(ClientConfig::new(Arc::new( + QuicClientConfig::try_from(client_crypto).unwrap(), + ))); + + let server_task = async { + let incoming = server.accept().await.unwrap(); + let accepted = incoming.acceptor().unwrap().await.unwrap(); + assert_eq!( + accepted + .client_hello() + .server_name() + .map(|name| name.as_ref()), + Some("localhost") + ); + accepted.accept().unwrap().await.unwrap() + }; + + let client_task = async { + client + .connect(server_addr, "localhost") + .unwrap() + .await + .unwrap() + }; + + let (server_conn, client_conn) = join!(server_task, client_task); + client_conn.close(0u32.into(), b"done"); + server_conn.closed().await; +} + +#[tokio::test] +async fn incoming_acceptor_can_use_server_config_selector() { + let _guard = subscribe(); + let cert = rcgen::generate_simple_self_signed(vec!["localhost".into()]).unwrap(); + let cert_der = CertificateDer::from(cert.cert); + let alpn = b"quinn-selector-test"; + + let server_config = |alpn: &[u8]| { + let mut server_crypto = rustls::ServerConfig::builder(Arc::new(default_provider())) + .with_no_client_auth() + .with_single_cert( + Arc::new(Identity::from_cert_chain(vec![cert_der.clone()]).unwrap()), + PrivatePkcs8KeyDer::from(cert.signing_key.serialize_der()).into(), + ) + .unwrap(); + server_crypto.alpn_protocols = vec![alpn.to_vec().into()]; + crate::ServerConfig::with_crypto(Arc::new( + crate::crypto::rustls::QuicServerConfig::try_from(server_crypto).unwrap(), + )) + }; + let default_server_config = server_config(b"default-alpn"); + let selected_server_config = Arc::new(server_config(alpn)); + + struct TestSelector { + selected: Arc, + } + + impl ServerConfigSelector for TestSelector { + type Error = Infallible; + + async fn select_server_config<'a>( + &'a self, + client_hello: rustls::server::ClientHello<'a>, + ) -> Result>, Self::Error> { + assert_eq!( + client_hello.server_name().map(|name| name.as_ref()), + Some("localhost") + ); + tokio::task::yield_now().await; + Ok(Some(self.selected.clone())) + } + } + + let server = Endpoint::new( + EndpointConfig::default(), + Some(default_server_config), + UdpSocket::bind(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0)).unwrap(), + Arc::new(TokioRuntime), + ) + .unwrap(); + let server_addr = server.local_addr().unwrap(); + + let mut roots = RootCertStore::empty(); + roots.add(cert_der).unwrap(); + let mut client_crypto = rustls::ClientConfig::builder(Arc::new(default_provider())) + .with_root_certificates(roots) + .with_no_client_auth() + .unwrap(); + client_crypto.alpn_protocols = vec![alpn.as_slice().into()]; + let client = Endpoint::client(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0)).unwrap(); + client.set_default_client_config(ClientConfig::new(Arc::new( + QuicClientConfig::try_from(client_crypto).unwrap(), + ))); + + let selector = TestSelector { + selected: selected_server_config, + }; + let server_task = async { + let incoming = server.accept().await.unwrap(); + incoming + .accept_with_selector(&selector) + .await + .unwrap() + .await + .unwrap() + }; + + let client_task = async { + client + .connect(server_addr, "localhost") + .unwrap() + .await + .unwrap() + }; + + let (server_conn, client_conn) = join!(server_task, client_task); + client_conn.close(0u32.into(), b"done"); + server_conn.closed().await; +} + /// Constructs endpoints suitable for connecting to themselves and each other struct EndpointFactory { cert: rcgen::CertifiedKey, @@ -291,14 +547,24 @@ impl EndpointFactory { } fn endpoint_with_config(&self, transport_config: TransportConfig) -> Endpoint { + let cert = self.cert.cert.der().clone(); let key = PrivateKeyDer::Pkcs8(self.cert.signing_key.serialize_der().into()); let transport_config = Arc::new(transport_config); - let mut server_config = - crate::ServerConfig::with_single_cert(vec![self.cert.cert.der().clone()], key).unwrap(); + let mut server_crypto = rustls::ServerConfig::builder(Arc::new(default_provider())) + .with_no_client_auth() + .with_single_cert( + Arc::new(Identity::from_cert_chain(vec![cert.clone()]).unwrap()), + key, + ) + .unwrap(); + server_crypto.max_early_data_size = u32::MAX; + let mut server_config = crate::ServerConfig::with_crypto(Arc::new( + crate::crypto::rustls::QuicServerConfig::try_from(server_crypto).unwrap(), + )); server_config.transport_config(transport_config.clone()); let mut roots = RootCertStore::empty(); - roots.add(self.cert.cert.der().clone()).unwrap(); + roots.add(cert).unwrap(); let endpoint = Endpoint::new( self.endpoint_config.clone(), Some(server_config), @@ -306,7 +572,13 @@ impl EndpointFactory { Arc::new(TokioRuntime), ) .unwrap(); - let mut client_config = ClientConfig::with_root_certificates(Arc::new(roots)).unwrap(); + let mut client_crypto = rustls::ClientConfig::builder(Arc::new(default_provider())) + .with_root_certificates(roots) + .with_no_client_auth() + .unwrap(); + client_crypto.enable_early_data = true; + let mut client_config = + ClientConfig::new(Arc::new(QuicClientConfig::try_from(client_crypto).unwrap())); client_config.transport_config(transport_config); endpoint.set_default_client_config(client_config); @@ -528,13 +800,11 @@ fn run_echo(args: EchoArgs) { let mut roots = RootCertStore::empty(); roots.add(cert).unwrap(); - let mut client_crypto = - rustls::ClientConfig::builder_with_provider(default_provider().into()) - .with_safe_default_protocol_versions() - .unwrap() - .with_root_certificates(roots) - .with_no_client_auth(); - client_crypto.key_log = Arc::new(rustls::KeyLogFile::new()); + let mut client_crypto = rustls::ClientConfig::builder(Arc::new(default_provider())) + .with_root_certificates(roots) + .with_no_client_auth() + .unwrap(); + client_crypto.key_log = Arc::new(rustls_util::KeyLogFile::new()); let client = { let _guard = runtime.enter(); diff --git a/quinn/tests/post_quantum.rs b/quinn/tests/post_quantum.rs index 1d2970971b..93e70d3fef 100644 --- a/quinn/tests/post_quantum.rs +++ b/quinn/tests/post_quantum.rs @@ -7,7 +7,7 @@ use std::{ }; use rustls::{ - NamedGroup, + crypto::{Identity, kx::NamedGroup}, pki_types::{CertificateDer, PrivatePkcs8KeyDer}, }; use tracing::info; @@ -79,13 +79,10 @@ fn make_client_endpoint( ) -> Result> { let mut certs = rustls::RootCertStore::empty(); certs.add(server_cert)?; - let rustls_config = rustls::ClientConfig::builder_with_provider(Arc::new( - rustls::crypto::aws_lc_rs::default_provider(), - )) - .with_safe_default_protocol_versions() - .unwrap() - .with_root_certificates(certs) - .with_no_client_auth(); + let rustls_config = rustls::ClientConfig::builder(Arc::new(rustls_aws_lc_rs::DEFAULT_PROVIDER)) + .with_root_certificates(certs) + .with_no_client_auth() + .unwrap(); let client_cfg = quinn::ClientConfig::new(Arc::new(QuicClientConfig::try_from(rustls_config).unwrap())); @@ -103,14 +100,13 @@ fn make_server_endpoint( let cert = CertificateDer::from(cert.cert); let mut server_config = quinn::ServerConfig::with_crypto(Arc::new( QuicServerConfig::try_from( - rustls::ServerConfig::builder_with_provider(Arc::new( - rustls::crypto::aws_lc_rs::default_provider(), - )) - .with_safe_default_protocol_versions() - .unwrap() - .with_no_client_auth() - .with_single_cert(vec![cert.clone()], key.into()) - .unwrap(), + rustls::ServerConfig::builder(Arc::new(rustls_aws_lc_rs::DEFAULT_PROVIDER)) + .with_no_client_auth() + .with_single_cert( + Arc::new(Identity::from_cert_chain(vec![cert.clone()]).unwrap()), + key.into(), + ) + .unwrap(), ) .unwrap(), ));