diff --git a/quest/m1/quic-api/README.md b/quest/m1/quic-api/README.md index 034e06aa6af..4c0dea2bdf6 100644 --- a/quest/m1/quic-api/README.md +++ b/quest/m1/quic-api/README.md @@ -46,7 +46,6 @@ side by side and fix any name or signature that still differs, and check ## Required -- [Crypto](/quest/m1/quic-api/crypto.md) - crypto traits sealed to rustls, concrete peer identity and handshake data - [Waits](/quest/m1/quic-api/waits.md) - moq-quic owns every wait as a `poll_*` on `&kio::Waiter`, deleting `StreamEvent` and the drivers' and qmux's waiter tables - [Poll forms](/quest/m1/quic-api/poll.md) - both drivers' waits poll as `&self, &kio::Waiter` forwards to moq-quic, the async forms wrap them, and `send::Finished` is a future - [Native WebTransport](/quest/m1/quic-api/native-web.md) - `quic::web` in both crates implements moq-net's poll traits, deleting the generic `web_transport_trait` adapters and the dependency on it diff --git a/quest/m1/quic-api/crypto.md b/quest/m1/quic-api/crypto.md deleted file mode 100644 index 8f2c5c11891..00000000000 --- a/quest/m1/quic-api/crypto.md +++ /dev/null @@ -1,41 +0,0 @@ -# [M] moq-quic's crypto is rustls, not a plugin - -## Goal - -moq-quic's crypto traits are crate-private, its configs take rustls types, -and a connection reports its peer and handshake as concrete types: no -`Box` for a caller to downcast, and `transport_parameters` is -private. - -## Plan - -Decided 2026-10-23 (maintainer): seal to rustls. `crypto::Session`, -`ClientConfig`, `ServerConfig`, and the key traits have one real -implementation; the plaintext one serves the fuzz target and benches and -stays behind the `fuzz` feature. - -- `peer_identity()` returns `Option>>`. -- `handshake_data()` returns a concrete struct (negotiated ALPN, server - name), replacing the downcasts to rustls `HandshakeData` in moq-tokio's - backend and moq-uring's handshake. -- Client and server configs are built from `Arc` and - `Arc` directly; `QuicClientConfig`/ - `QuicServerConfig` and their `TryFrom` go private if nothing else needs - them. -- `pub mod transport_parameters` goes private; only the trait signatures - needed it. - -What it deletes, from the 2026-10-23 consumer audit: the `!Send`-scoped -`handshake_data().downcast().unwrap()` in moq-tokio's backend, -`PeerIdentity::from_any` in moq-tokio's `tls.rs` and its tests, `quic::web`'s -`OnceLock` that caches the negotiated ALPN only because the downcast -allocates, moq-uring's `peer_chain` returning `Vec>` that -moq-relay's `uring.rs` converts back to `CertificateDer`, the -`QuicClientConfig`/`QuicServerConfig::try_from` calls with their -`NoInitialCipherSuite` handling or `unwrap` (moq-tokio's backend and web -client and server, moq-uring, moq-relay's `cluster.rs`), and about 18 -`#[cfg(any(feature = "aws-lc-rs", feature = "ring"))]` gates in `quic::web` -that a crypto-provider-agnostic surface no longer needs. - -Mirror the accessors in both drivers. The Impact bullet says how -to migrate in moq-quic, moq-tokio, and moq-uring. diff --git a/quest/m2/rind/transport.md b/quest/m2/rind/transport.md index 6900e1a123a..02536ea7781 100644 --- a/quest/m2/rind/transport.md +++ b/quest/m2/rind/transport.md @@ -40,7 +40,3 @@ Decided with the maintainer on 2026-10-26; see the Public API: moq-tokio's and moq-quic's `aws-lc-rs` and `ring` features are removed (breaking; migrate by dropping them, since rind is always on). Unsupported CPUs now fail at startup. Wire: none. - -## Required - -- [moq-quic's crypto is rustls](/quest/m1/quic-api/crypto.md) - the sealed crypto surface rind plugs into, without the per-provider gates diff --git a/rs/moq-quic/Cargo.toml b/rs/moq-quic/Cargo.toml index 831127705b1..fa878be1fe4 100644 --- a/rs/moq-quic/Cargo.toml +++ b/rs/moq-quic/Cargo.toml @@ -16,10 +16,10 @@ features = ["rustls-aws-lc-rs", "ring"] [features] default = ["ring"] -aws-lc-rs = ["dep:aws-lc-rs", "aws-lc-rs?/aws-lc-sys", "aws-lc-rs?/prebuilt-nasm", "dep:rustls", "rustls/aws-lc-rs"] +aws-lc-rs = ["dep:aws-lc-rs", "aws-lc-rs?/aws-lc-sys", "aws-lc-rs?/prebuilt-nasm", "rustls/aws-lc-rs"] # Enable rustls with the aws-lc-rs crypto provider. rustls-aws-lc-rs = ["aws-lc-rs"] -ring = ["dep:ring", "dep:rustls", "rustls/ring"] +ring = ["dep:ring", "rustls/ring"] # Enable qlog support qlog = ["dep:qlog"] # The sans-IO QMux connection, which runs the stream state machine over a reliable, ordered @@ -29,10 +29,6 @@ mux = [] # driver that runs QUIC on kio's simulator. Hidden from the docs; meant for # dev-dependencies. sim = [] -# Internal (PRIVATE!) features used to aid testing. -# Don't rely on these whatsoever. They may disappear at any time. - -__rustls-post-quantum-test = [] # Exposes the `fuzz/` harness's target bodies, and the plaintext crypto the connection # target and the benches run on, through the hidden `fuzz` module. ring keys the endpoint's @@ -54,7 +50,7 @@ qlog = { version = "0.18", optional = true } rand = { workspace = true } ring = { version = "0.17", optional = true } rustc-hash = { workspace = true } -rustls = { version = "0.23.5", default-features = false, features = ["std"], optional = true } +rustls = { version = "0.23.5", default-features = false, features = ["std"] } slab = { workspace = true } thiserror = { workspace = true } tinyvec = { version = "1.1", features = ["alloc"] } diff --git a/rs/moq-quic/MIGRATE.md b/rs/moq-quic/MIGRATE.md index e89dfec0323..9327fc8dbda 100644 --- a/rs/moq-quic/MIGRATE.md +++ b/rs/moq-quic/MIGRATE.md @@ -4,6 +4,19 @@ How to update your code for each breaking change, newest first. ## Unreleased +### Crypto is sealed to rustls ([#7028](https://github.com/moq-dev/moq/pull/7028)) + +Build configs with `client::Config::new(Arc::new(rustls_client))` and `server::Config::new(Arc::new(rustls_server))`; both are infallible. +`crypto::rustls::QuicClientConfig`, `QuicServerConfig`, their `TryFrom` and `with_initial`, and `NoInitialCipherSuite` are gone, as are the public `client::Config::crypto` and `server::Config::crypto` fields. +A custom crypto implementation is no longer supported: the `crypto` traits, `Keys`, and `KeyPair` are crate-private. +Replace `conn.crypto_session().handshake_data()` and its downcast to `crypto::rustls::HandshakeData` with `conn.handshake_data()`, which returns `Option`. +Its `protocol` field is now `alpn`, and `protocol_version` and `cipher_suite` are dropped. +Replace `crypto_session().peer_identity()` and its downcast with `conn.peer_identity()`, which returns `Option>>`. +`Connection::export_keying_material` and `transport_parameters` are no longer public. +A rustls config without TLS 1.3 or the AES-128-GCM-SHA256 suite now fails each connection: the client's connect returns `ConnectError::Tls`, and the server closes the connection or drops its Initial. +`ConnectError` no longer implements `Eq`. +rustls is a required dependency; the `ring` and `aws-lc-rs` features pick only the provider. + ### An evicted stream fails `received_reset` ([#7624](https://github.com/moq-dev/moq/pull/7624)) `recv::Stream::received_reset` returns `Err(Error::Evicted)` for a stream evicted to free a stuck receive window, until the application stops it, rather than `Err(Error::Closed)`. diff --git a/rs/moq-quic/benches/common/mod.rs b/rs/moq-quic/benches/common/mod.rs index 2f915dd34a8..c7f6f1ad8a8 100644 --- a/rs/moq-quic/benches/common/mod.rs +++ b/rs/moq-quic/benches/common/mod.rs @@ -643,13 +643,13 @@ fn transport(receive_window: VarInt, send_window: u64, probe: bool) -> transport } fn server_config(receive_window: VarInt) -> server::Config { - let mut config = server::Config::new(Arc::new(Plaintext)); + let mut config = Plaintext::server(); config.transport = transport(receive_window, SEND_WINDOW, false); config } fn client_config(send_window: u64, probe: bool) -> client::Config { - let mut config = client::Config::new(Arc::new(Plaintext)); + let mut config = Plaintext::client(); config.transport = transport(VarInt::MAX, send_window, probe); config } diff --git a/rs/moq-quic/src/config/client.rs b/rs/moq-quic/src/config/client.rs index d67e5f11a0c..debed151e96 100644 --- a/rs/moq-quic/src/config/client.rs +++ b/rs/moq-quic/src/config/client.rs @@ -22,7 +22,7 @@ pub struct Config { pub transport: transport::Config, /// Cryptographic configuration to use - pub crypto: Arc, + pub(crate) crypto: Arc, /// QUIC protocol version to use pub version: Version, @@ -35,8 +35,8 @@ pub struct Config { } impl Config { - /// Dial with `crypto` and the default settings - pub fn new(crypto: Arc) -> Self { + /// Dial with any crypto, such as the fuzz target's plaintext + pub(crate) fn from_crypto(crypto: Arc) -> Self { Self { transport: transport::Config::default(), crypto, @@ -49,13 +49,22 @@ impl Config { #[cfg(any(feature = "aws-lc-rs", feature = "ring"))] impl Config { + /// Run the handshake with `tls` and the default settings + /// + /// The config must enable TLS 1.3, or connecting fails with [`ConnectError::Tls`]. + /// Early data stays off: if `tls` enables it, the connection uses a copy that does not. + /// + /// [`ConnectError::Tls`]: crate::endpoint::ConnectError::Tls + pub fn new(tls: Arc) -> Self { + Self::from_crypto(Arc::new(QuicClientConfig::new(tls))) + } + /// Dial trusting the specified trust anchors pub fn with_root_certificates( roots: Arc, ) -> Result { - Ok(Self::new(Arc::new(QuicClientConfig::new( - WebPkiServerVerifier::builder_with_provider(roots, configured_provider()).build()?, - )))) + let verifier = WebPkiServerVerifier::builder_with_provider(roots, configured_provider()).build()?; + Ok(Self::new(Arc::new(QuicClientConfig::inner(verifier)))) } } diff --git a/rs/moq-quic/src/config/server/mod.rs b/rs/moq-quic/src/config/server/mod.rs index 53bb6474f40..428d138d985 100644 --- a/rs/moq-quic/src/config/server/mod.rs +++ b/rs/moq-quic/src/config/server/mod.rs @@ -29,9 +29,7 @@ pub struct Config { pub transport: transport::Config, /// TLS configuration used for incoming connections - /// - /// Must be set to use TLS 1.3 only. - pub crypto: Arc, + pub(crate) crypto: Arc, /// Configuration for sending and handling validation tokens pub validation_token: validation_token::Config, @@ -94,8 +92,16 @@ pub struct Config { #[cfg(any(feature = "aws-lc-rs", feature = "ring"))] impl Config { - /// Serve `crypto` with the default settings and a random handshake token key - pub fn new(crypto: Arc) -> Self { + /// Run the handshake with `tls`, with the default settings and a random handshake token key + /// + /// The config must enable TLS 1.3, or every incoming connection fails its handshake. + /// Early data stays off: if `tls` accepts it, the connection uses a copy that does not. + pub fn new(tls: Arc) -> Self { + Self::from_crypto(Arc::new(QuicServerConfig::new(tls))) + } + + /// Serve any crypto, such as the fuzz target's plaintext + pub(crate) fn from_crypto(crypto: Arc) -> Self { #[cfg(all(feature = "aws-lc-rs", not(feature = "ring")))] use aws_lc_rs::hkdf; use rand::Rng; @@ -127,7 +133,7 @@ impl Config { cert_chain: Vec>, key: PrivateKeyDer<'static>, ) -> Result { - Ok(Self::new(Arc::new(QuicServerConfig::new(cert_chain, key)?))) + Ok(Self::new(Arc::new(QuicServerConfig::inner(cert_chain, key)?))) } } diff --git a/rs/moq-quic/src/connection/mod.rs b/rs/moq-quic/src/connection/mod.rs index e0000595b5b..34f91704018 100644 --- a/rs/moq-quic/src/connection/mod.rs +++ b/rs/moq-quic/src/connection/mod.rs @@ -1535,9 +1535,28 @@ impl Connection { self.update_keys(None, false); } - /// Get a session reference - pub fn crypto_session(&self) -> &dyn crypto::Session { - &*self.crypto + /// What the handshake negotiated, once the connection emits [`Event::HandshakeDataReady`] + pub fn handshake_data(&self) -> Option { + self.crypto.handshake_data() + } + + /// The certificate chain the peer presented, end entity first, if it presented one + /// + /// Complete only once the handshake finishes, signaled by [`Event::Connected`]. + pub fn peer_identity(&self) -> Option>> { + self.crypto.peer_identity() + } + + /// Fill `output` with keying material exported from the TLS session (RFC 5705), using + /// `label` and `context` for domain separation + #[cfg(test)] + pub(crate) fn export_keying_material( + &self, + output: &mut [u8], + label: &[u8], + context: &[u8], + ) -> Result<(), crypto::ExportKeyingMaterialError> { + self.crypto.export_keying_material(output, label, context) } /// Whether the connection is in the process of being established diff --git a/rs/moq-quic/src/crypto.rs b/rs/moq-quic/src/crypto.rs index 06837f1b893..c110afe3e7e 100644 --- a/rs/moq-quic/src/crypto.rs +++ b/rs/moq-quic/src/crypto.rs @@ -1,15 +1,12 @@ -//! Traits and implementations for the QUIC cryptography protocol +//! What a connection's TLS handshake negotiated. //! -//! The protocol logic in moq-quic is contained in types that abstract over the actual -//! cryptographic protocol used. This module contains the traits used for this -//! abstraction layer as well as a single implementation of these traits that uses -//! rustls with ring or aws-lc-rs to implement the TLS protocol support. -//! -//! Note that usage of any protocol (version) other than TLS 1.3 does not conform to any -//! published versions of the specification, and will not be supported in QUIC v1. +//! The handshake runs on rustls with ring or aws-lc-rs. The traits here are crate-private: +//! rustls is their one real implementation, and the plaintext one behind the `fuzz` feature +//! serves the fuzz target and the benchmarks. -use std::{any::Any, str, sync::Arc}; +use std::{str, sync::Arc}; +use ::rustls::pki_types::CertificateDer; use bytes::BytesMut; use crate::{ @@ -19,23 +16,40 @@ use crate::{ /// Cryptography interface based on *ring* #[cfg(any(feature = "aws-lc-rs", feature = "ring"))] -pub(crate) mod ring_like; +mod ring_like; /// TLS interface based on rustls #[cfg(any(feature = "aws-lc-rs", feature = "ring"))] -pub mod rustls; +pub(crate) mod rustls; + +/// What the handshake negotiated, available once the connection emits `HandshakeDataReady`. +#[derive(Clone, Debug, PartialEq, Eq)] +#[non_exhaustive] +pub struct HandshakeData { + /// The negotiated application protocol, if ALPN is in use. + /// + /// Always set when the client offered protocols, since rustls refuses a handshake without one. + pub alpn: Option>, + /// The server name the client sent, seen only by the server. + pub server_name: Option, +} + +/// The requested keying material is longer than the handshake's hash can export. +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(not(test), expect(dead_code, reason = "only tests export keying material"))] +pub(crate) struct ExportKeyingMaterialError; /// A cryptographic session (commonly TLS) -pub trait Session: Send + Sync + 'static { +pub(crate) trait Session: Send + Sync + 'static { /// Create the initial set of keys given the client's initial destination ConnectionId fn initial_keys(&self, dst_cid: ConnectionId, side: Side) -> Keys; /// Get data negotiated during the handshake, if available /// /// Returns `None` until the connection emits `HandshakeDataReady`. - fn handshake_data(&self) -> Option>; + fn handshake_data(&self) -> Option; - /// Get the peer's identity, if available - fn peer_identity(&self) -> Option>; + /// The certificate chain the peer presented, end entity first, if any + fn peer_identity(&self) -> Option>>; /// Returns `true` until the connection is fully established. fn is_handshaking(&self) -> bool; @@ -73,6 +87,7 @@ pub trait Session: Send + Sync + 'static { /// /// This function will fail, returning [ExportKeyingMaterialError], /// if the requested output length is too large. + #[cfg_attr(not(test), expect(dead_code, reason = "only tests export keying material"))] fn export_keying_material( &self, output: &mut [u8], @@ -82,7 +97,7 @@ pub trait Session: Send + Sync + 'static { } /// A pair of keys for bidirectional communication -pub struct KeyPair { +pub(crate) struct KeyPair { /// Key for encrypting data pub local: T, /// Key for decrypting data @@ -90,7 +105,7 @@ pub struct KeyPair { } /// A complete set of keys for a certain packet space -pub struct Keys { +pub(crate) struct Keys { /// Header protection keys pub header: KeyPair>, /// Packet protection keys @@ -98,7 +113,7 @@ pub struct Keys { } /// Client-side configuration for the crypto protocol -pub trait ClientConfig: Send + Sync { +pub(crate) trait ClientConfig: Send + Sync { /// Start a client session with this configuration fn start_session( self: Arc, @@ -109,9 +124,11 @@ pub trait ClientConfig: Send + Sync { } /// Server-side configuration for the crypto protocol -pub trait ServerConfig: Send + Sync { +pub(crate) trait ServerConfig: Send + Sync { /// Create the initial set of keys given the client's initial destination ConnectionId - fn initial_keys(&self, version: Version, dst_cid: ConnectionId) -> Keys; + /// + /// `None` when the config cannot protect Initial packets, so no connection can start. + fn initial_keys(&self, version: Version, dst_cid: ConnectionId) -> Option; /// Generate the integrity tag for a retry packet fn retry_tag(&self, version: Version, orig_dst_cid: ConnectionId, packet: &[u8]) -> [u8; 16]; @@ -119,11 +136,15 @@ pub trait ServerConfig: Send + Sync { /// Start a server session with this configuration /// /// Sessions must reject early data; the transport only processes 1-RTT application packets. - fn start_session(self: Arc, version: Version, params: &TransportParameters) -> Box; + fn start_session( + self: Arc, + version: Version, + params: &TransportParameters, + ) -> Result, TransportError>; } /// Keys used to protect packet payloads -pub trait PacketKey: Send + Sync { +pub(crate) trait PacketKey: Send + Sync { /// Encrypt the packet payload with the given packet number fn encrypt(&self, packet: u64, buf: &mut [u8], header_len: usize); /// Decrypt the packet payload with the given packet number @@ -138,7 +159,7 @@ pub trait PacketKey: Send + Sync { } /// Keys used to protect packet headers -pub trait HeaderKey: Send + Sync { +pub(crate) trait HeaderKey: Send + Sync { /// Decrypt the given packet's header fn decrypt(&self, pn_offset: usize, packet: &mut [u8]); /// Encrypt the given packet's header @@ -148,29 +169,21 @@ pub trait HeaderKey: Send + Sync { } /// A key for signing with HMAC-based algorithms -pub trait HmacKey: Send + Sync { +pub(crate) trait HmacKey: Send + Sync { /// Method for signing a message fn sign(&self, data: &[u8], signature_out: &mut [u8]); /// Length of `sign`'s output fn signature_len(&self) -> usize; - /// Method for verifying a message - fn verify(&self, data: &[u8], signature: &[u8]) -> Result<(), CryptoError>; } -/// Error returned by [Session::export_keying_material]. -/// -/// This error occurs if the requested output length is too large. -#[derive(Debug, PartialEq, Eq)] -pub struct ExportKeyingMaterialError; - /// A pseudo random key for HKDF -pub trait HandshakeTokenKey: Send + Sync { +pub(crate) trait HandshakeTokenKey: Send + Sync { /// Derive AEAD using hkdf fn aead_from_hkdf(&self, random_bytes: &[u8]) -> Box; } /// A key for sealing data with AEAD-based algorithms -pub trait AeadKey { +pub(crate) trait AeadKey { /// Method for sealing message `data` fn seal(&self, data: &mut Vec, additional_data: &[u8]) -> Result<(), CryptoError>; /// Method for opening a sealed message `data` @@ -179,4 +192,4 @@ pub trait AeadKey { /// Generic crypto errors #[derive(Debug)] -pub struct CryptoError; +pub(crate) struct CryptoError; diff --git a/rs/moq-quic/src/crypto/ring_like.rs b/rs/moq-quic/src/crypto/ring_like.rs index 0a925759f96..e669f1d8dcf 100644 --- a/rs/moq-quic/src/crypto/ring_like.rs +++ b/rs/moq-quic/src/crypto/ring_like.rs @@ -13,10 +13,6 @@ impl crypto::HmacKey for hmac::Key { fn signature_len(&self) -> usize { 32 } - - fn verify(&self, data: &[u8], signature: &[u8]) -> Result<(), CryptoError> { - Ok(hmac::verify(self, data, signature)?) - } } impl crypto::HandshakeTokenKey for hkdf::Prk { diff --git a/rs/moq-quic/src/crypto/rustls.rs b/rs/moq-quic/src/crypto/rustls.rs index d7f0547eb42..b69c875df1f 100644 --- a/rs/moq-quic/src/crypto/rustls.rs +++ b/rs/moq-quic/src/crypto/rustls.rs @@ -1,15 +1,12 @@ -use std::{any::Any, io, str, sync::Arc}; +use std::{io, str, sync::Arc}; #[cfg(all(feature = "aws-lc-rs", not(feature = "ring")))] use aws_lc_rs::aead; use bytes::BytesMut; #[cfg(feature = "ring")] use ring::aead; -pub use rustls::Error; -#[cfg(feature = "__rustls-post-quantum-test")] -use rustls::NamedGroup; use rustls::{ - self, CipherSuite, + self, client::danger::ServerCertVerifier, pki_types::{CertificateDer, PrivateKeyDer, ServerName}, quic::{Connection, HeaderProtectionKey, KeyChange, PacketKey, Secrets, Suite, Version}, @@ -17,7 +14,7 @@ use rustls::{ use crate::{ ConnectionId, Side, TransportError, TransportErrorCode, - crypto::{self, CryptoError, ExportKeyingMaterialError, HeaderKey, KeyPair, Keys}, + crypto::{self, CryptoError, ExportKeyingMaterialError, HandshakeData, HeaderKey, KeyPair, Keys}, endpoint::ConnectError, transport_parameters::TransportParameters, }; @@ -32,12 +29,12 @@ impl From for rustls::Side { } /// A rustls TLS session -pub struct TlsSession { +pub(crate) struct TlsSession { version: Version, got_handshake_data: bool, next_secrets: Option, inner: Connection, - suite: Suite, + initial: Suite, } impl TlsSession { @@ -51,47 +48,25 @@ impl TlsSession { impl crypto::Session for TlsSession { fn initial_keys(&self, dst_cid: ConnectionId, side: Side) -> Keys { - initial_keys(self.version, dst_cid, side, &self.suite) + initial_keys(self.version, dst_cid, side, &self.initial) } - fn handshake_data(&self) -> Option> { + fn handshake_data(&self) -> Option { if !self.got_handshake_data { return None; } - Some(Box::new(HandshakeData { - protocol: self.inner.alpn_protocol().map(|x| x.into()), + Some(HandshakeData { + alpn: 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()), }, - protocol_version: match &self.inner { - Connection::Client(session) => session.protocol_version(), - Connection::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(), - } - .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(), - })) + }) } - /// 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::>>(), - ) - }) + fn peer_identity(&self) -> Option>> { + let chain = self.inner.peer_certificates()?; + Some(chain.iter().map(|cert| cert.clone().into_owned()).collect()) } fn is_handshaking(&self) -> bool { @@ -239,70 +214,25 @@ impl HeaderKey for Box { } } -/// Authentication data for (rustls) TLS session -#[non_exhaustive] -pub struct HandshakeData { - /// The negotiated application protocol, if ALPN is in use - /// - /// Guaranteed to be set if a nonempty list of protocols was specified for this connection. - pub protocol: Option>, - /// The server name specified by the client, if any - /// - /// Always `None` for outgoing connections - pub server_name: Option, - /// The protocol version negotiated with the peer, if any - pub protocol_version: Option>, - /// The cipher suite negotiated with the peer, if any - pub cipher_suite: Option>, - /// The key exchange group negotiated with the peer - #[cfg(feature = "__rustls-post-quantum-test")] - pub negotiated_key_exchange_group: NamedGroup, -} - -/// A QUIC-compatible TLS client configuration -/// -/// [`client::Config::with_root_certificates()`][root_certs] constructs a QUIC-compatible TLS policy. -/// 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: -/// -/// TLS 1.3 is required. Early data is disabled, while the inner config's resumption -/// cache still enables resumed 1-RTT handshakes. -/// -/// [root_certs]: crate::client::Config::with_root_certificates() -pub struct QuicClientConfig { +/// A rustls client config made safe for QUIC: early data stays off, while the config's +/// resumption cache still enables resumed 1-RTT handshakes. +pub(crate) struct QuicClientConfig { pub(crate) inner: Arc, - initial: Suite, + initial: Option, } impl QuicClientConfig { - /// Initialize a sane QUIC-compatible TLS client configuration - /// - /// 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 { - 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()).expect("no initial cipher suite found"), - inner: Arc::new(inner), - } - } - - /// Initialize a QUIC-compatible TLS client configuration with a separate initial cipher suite - /// - /// This is useful if you want to avoid the initial cipher suite for traffic encryption. - pub fn with_initial(mut inner: Arc, initial: Suite) -> Result { + /// Wraps a caller's config, copying it only if it enabled early data. + pub(crate) fn new(mut inner: Arc) -> Self { if inner.enable_early_data { Arc::make_mut(&mut inner).enable_early_data = false; } - match initial.suite.common.suite { - CipherSuite::TLS13_AES_128_GCM_SHA256 => Ok(Self { inner, initial }), - _ => Err(NoInitialCipherSuite { specific: true }), - } + let initial = initial_suite(inner.crypto_provider()); + Self { inner, initial } } + /// A TLS 1.3 config on this crate's provider that verifies the server with `verifier`. pub(crate) fn inner(verifier: Arc) -> rustls::ClientConfig { - // Keep in sync with `with_platform_verifier()` above rustls::ClientConfig::builder_with_provider(configured_provider()) .with_protocol_versions(&[&rustls::version::TLS13]) .unwrap() // The default providers support TLS 1.3 @@ -320,149 +250,49 @@ impl crypto::ClientConfig for QuicClientConfig { params: &TransportParameters, ) -> Result, ConnectError> { let version = rustls_version(version); + let initial = self.initial.ok_or_else(|| ConnectError::Tls(no_initial_suite()))?; + let server_name = ServerName::try_from(server_name) + .map_err(|_| ConnectError::InvalidServerName(server_name.into()))? + .to_owned(); + let inner = rustls::quic::ClientConnection::new(self.inner.clone(), version, server_name, to_vec(params)) + .map_err(ConnectError::Tls)?; Ok(Box::new(TlsSession { version, got_handshake_data: false, next_secrets: None, - inner: Connection::Client( - rustls::quic::ClientConnection::new( - self.inner.clone(), - version, - ServerName::try_from(server_name) - .map_err(|_| ConnectError::InvalidServerName(server_name.into()))? - .to_owned(), - to_vec(params), - ) - .unwrap(), - ), - suite: self.initial, + inner: Connection::Client(inner), + initial, })) } } -impl TryFrom for QuicClientConfig { - type Error = NoInitialCipherSuite; - - fn try_from(inner: rustls::ClientConfig) -> Result { - Arc::new(inner).try_into() - } -} - -impl TryFrom> for QuicClientConfig { - type Error = NoInitialCipherSuite; - - fn try_from(mut inner: Arc) -> Result { - if inner.enable_early_data { - Arc::make_mut(&mut inner).enable_early_data = false; - } - Ok(Self { - initial: initial_suite_from_provider(inner.crypto_provider()) - .ok_or(NoInitialCipherSuite { specific: false })?, - inner, - }) - } -} - -/// The initial cipher suite (AES-128-GCM-SHA256) is not available -/// -/// When the cipher suite is supplied `with_initial()`, it must be -/// [`CipherSuite::TLS13_AES_128_GCM_SHA256`]. When the cipher suite is derived from a config's -/// [`CryptoProvider`][provider], that provider must reference a cipher suite with the same ID. -/// -/// [provider]: rustls::crypto::CryptoProvider -#[derive(Clone, Debug)] -pub struct NoInitialCipherSuite { - /// Whether the initial cipher suite was supplied by the caller - specific: bool, -} - -impl std::fmt::Display for NoInitialCipherSuite { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(match self.specific { - true => "invalid cipher suite specified", - false => "no initial cipher suite found", - }) - } -} - -impl std::error::Error for NoInitialCipherSuite {} - -/// A QUIC-compatible TLS server configuration -/// -/// The transport constructs a `QuicServerConfig` with reasonable defaults within -/// [`server::Config::with_single_cert()`][single]. Alternatively, `QuicServerConfig`'s [`TryFrom`] -/// implementation or `with_initial` method can be used to wrap around a custom -/// [`rustls::ServerConfig`], in which case care should be taken around certain points: -/// -/// TLS 1.3 is required. Early data is disabled, while session tickets still enable -/// resumed 1-RTT handshakes. -/// -/// [single]: crate::server::Config::with_single_cert() -pub struct QuicServerConfig { - inner: Arc, - initial: Suite, +/// A rustls server config made safe for QUIC: early data stays off, while session tickets +/// still enable resumed 1-RTT handshakes. +pub(crate) struct QuicServerConfig { + pub(crate) inner: Arc, + initial: Option, } impl QuicServerConfig { - pub(crate) fn new(cert_chain: Vec>, key: PrivateKeyDer<'static>) -> Result { - let inner = Self::inner(cert_chain, key)?; - Ok(Self { - // We're confident that the *ring* default provider contains TLS13_AES_128_GCM_SHA256 - initial: initial_suite_from_provider(inner.crypto_provider()).expect("no initial cipher suite found"), - inner: Arc::new(inner), - }) - } - - /// Initialize a QUIC-compatible TLS client configuration with a separate initial cipher suite - /// - /// This is useful if you want to avoid the initial cipher suite for traffic encryption. - pub fn with_initial(mut inner: Arc, initial: Suite) -> Result { + /// Wraps a caller's config, copying it only if it accepted early data. + pub(crate) fn new(mut inner: Arc) -> Self { if inner.max_early_data_size != 0 { Arc::make_mut(&mut inner).max_early_data_size = 0; } - match initial.suite.common.suite { - CipherSuite::TLS13_AES_128_GCM_SHA256 => Ok(Self { inner, initial }), - _ => Err(NoInitialCipherSuite { specific: true }), - } + let initial = initial_suite(inner.crypto_provider()); + Self { inner, initial } } - /// Initialize a sane QUIC-compatible TLS server configuration - /// - /// TLS 1.3 is required and early data stays disabled. + /// A TLS 1.3 config on this crate's provider that presents `cert_chain`. pub(crate) fn inner( cert_chain: Vec>, key: PrivateKeyDer<'static>, - ) -> Result { - let inner = rustls::ServerConfig::builder_with_provider(configured_provider()) + ) -> Result { + rustls::ServerConfig::builder_with_provider(configured_provider()) .with_protocol_versions(&[&rustls::version::TLS13]) - .unwrap() // The *ring* default provider supports TLS 1.3 + .unwrap() // The default providers support TLS 1.3 .with_no_client_auth() - .with_single_cert(cert_chain, key)?; - - Ok(inner) - } -} - -impl TryFrom for QuicServerConfig { - type Error = NoInitialCipherSuite; - - fn try_from(inner: rustls::ServerConfig) -> Result { - Arc::new(inner).try_into() - } -} - -impl TryFrom> for QuicServerConfig { - type Error = NoInitialCipherSuite; - - fn try_from(mut inner: Arc) -> Result { - if inner.max_early_data_size != 0 { - Arc::make_mut(&mut inner).max_early_data_size = 0; - } - Ok(Self { - initial: initial_suite_from_provider(inner.crypto_provider()) - .ok_or(NoInitialCipherSuite { specific: false })?, - inner, - }) + .with_single_cert(cert_chain, key) } } @@ -471,21 +301,35 @@ impl crypto::ServerConfig for QuicServerConfig { self: Arc, version: crate::Version, params: &TransportParameters, - ) -> Box { + ) -> Result, TransportError> { let version = rustls_version(version); - Box::new(TlsSession { + // The endpoint never starts a session when `initial_keys` found no suite. + let initial = self.initial.expect("Initial keys came from this config's suite"); + // Only a config that cannot do QUIC at all (no TLS 1.3) fails here. + let inner = rustls::quic::ServerConnection::new(self.inner.clone(), version, to_vec(params)).map_err(|e| { + TransportError { + code: TransportErrorCode::INTERNAL_ERROR, + frame: None, + reason: format!("TLS config: {e}"), + crypto: Some(Arc::new(e)), + } + })?; + Ok(Box::new(TlsSession { version, got_handshake_data: false, next_secrets: None, - inner: Connection::Server( - rustls::quic::ServerConnection::new(self.inner.clone(), version, to_vec(params)).unwrap(), - ), - suite: self.initial, - }) + inner: Connection::Server(inner), + initial, + })) } - fn initial_keys(&self, version: crate::Version, dst_cid: ConnectionId) -> Keys { - initial_keys(rustls_version(version), dst_cid, Side::Server, &self.initial) + fn initial_keys(&self, version: crate::Version, dst_cid: ConnectionId) -> Option { + Some(initial_keys( + rustls_version(version), + dst_cid, + Side::Server, + self.initial.as_ref()?, + )) } fn retry_tag(&self, version: crate::Version, orig_dst_cid: ConnectionId, packet: &[u8]) -> [u8; 16] { @@ -511,15 +355,22 @@ impl crypto::ServerConfig for QuicServerConfig { } } -pub(crate) fn initial_suite_from_provider(provider: &Arc) -> Option { +/// The suite that protects Initial packets, which RFC 9001 fixes as AES-128-GCM-SHA256. +/// +/// Taken from the config's own provider, so a restricted provider (such as a FIPS module) +/// protects Initial packets too. `None` when the provider lacks the suite: the config then +/// fails each connection rather than borrowing another provider's. +pub(crate) fn initial_suite(provider: &rustls::crypto::CryptoProvider) -> Option { provider .cipher_suites .iter() - .find_map(|cs| match (cs.suite(), cs.tls13()) { - (CipherSuite::TLS13_AES_128_GCM_SHA256, Some(suite)) => Some(suite.quic_suite()), - _ => None, - }) - .flatten() + .find(|suite| suite.suite() == rustls::CipherSuite::TLS13_AES_128_GCM_SHA256)? + .tls13()? + .quic_suite() +} + +fn no_initial_suite() -> rustls::Error { + rustls::Error::General("the provider lacks TLS13_AES_128_GCM_SHA256, which QUIC Initial packets need".into()) } pub(crate) fn configured_provider() -> Arc { @@ -627,62 +478,79 @@ mod tests { ) } - /// A ticket issued with early data still resumes after either public config entry point - /// disables it, and a caller's shared configs retain their original policy. + /// A ticket issued with early data still resumes once the wrappers disable it, and a + /// caller's shared configs retain their original policy. #[test] fn supplied_configs_reject_early_data_and_keep_resumption() { - for explicit_suite in [false, true] { - let identity = rcgen::generate_simple_self_signed(vec!["localhost".to_string()]).unwrap(); - let cert = identity.cert.der().clone(); - let mut roots = rustls::RootCertStore::empty(); - roots.add(cert.clone()).unwrap(); - let mut client = rustls::ClientConfig::builder_with_provider(configured_provider()) - .with_protocol_versions(&[&rustls::version::TLS13]) - .unwrap() - .with_root_certificates(roots) - .with_no_client_auth(); - client.enable_early_data = true; - let mut server = QuicServerConfig::inner( - vec![cert], + let identity = rcgen::generate_simple_self_signed(vec!["localhost".to_string()]).unwrap(); + let cert = identity.cert.der().clone(); + let mut roots = rustls::RootCertStore::empty(); + roots.add(cert.clone()).unwrap(); + let mut client = rustls::ClientConfig::builder_with_provider(configured_provider()) + .with_protocol_versions(&[&rustls::version::TLS13]) + .unwrap() + .with_root_certificates(roots) + .with_no_client_auth(); + client.enable_early_data = true; + let mut server = QuicServerConfig::inner( + vec![cert], + PrivateKeyDer::Pkcs8(identity.signing_key.serialize_der().into()), + ) + .unwrap(); + server.max_early_data_size = u32::MAX; + let client = Arc::new(client); + let server = Arc::new(server); + assert_eq!( + handshake(client.clone(), server.clone()), + (rustls::HandshakeKind::Full, false, false) + ); + let wrapped_server = QuicServerConfig::new(server.clone()); + assert_eq!(server.max_early_data_size, u32::MAX); + // A third-party client holds an older ticket and really offers early data. + assert_eq!( + handshake(client.clone(), wrapped_server.inner.clone()), + (rustls::HandshakeKind::Resumed, true, false) + ); + let wrapped_client = QuicClientConfig::new(client.clone()); + assert!(client.enable_early_data); + assert_eq!( + handshake(wrapped_client.inner, wrapped_server.inner), + (rustls::HandshakeKind::Resumed, false, false) + ); + } + + /// A provider without the Initial suite fails each connection instead of borrowing + /// another provider's suite. + #[test] + fn provider_without_initial_suite_fails_connections() { + let mut provider = (*configured_provider()).clone(); + provider + .cipher_suites + .retain(|suite| suite.suite() != rustls::CipherSuite::TLS13_AES_128_GCM_SHA256); + let provider = Arc::new(provider); + let identity = rcgen::generate_simple_self_signed(vec!["localhost".to_string()]).unwrap(); + + let client = rustls::ClientConfig::builder_with_provider(provider.clone()) + .with_protocol_versions(&[&rustls::version::TLS13]) + .unwrap() + .with_root_certificates(rustls::RootCertStore::empty()) + .with_no_client_auth(); + let client = Arc::new(QuicClientConfig::new(Arc::new(client))); + let params = TransportParameters::default(); + let err = crypto::ClientConfig::start_session(client, crate::Version::V1, "localhost", ¶ms).err(); + assert!(matches!(err, Some(ConnectError::Tls(_)))); + + let server = rustls::ServerConfig::builder_with_provider(provider) + .with_protocol_versions(&[&rustls::version::TLS13]) + .unwrap() + .with_no_client_auth() + .with_single_cert( + vec![identity.cert.der().clone()], PrivateKeyDer::Pkcs8(identity.signing_key.serialize_der().into()), ) .unwrap(); - server.max_early_data_size = u32::MAX; - let client = Arc::new(client); - let server = Arc::new(server); - assert_eq!( - handshake(client.clone(), server.clone()), - (rustls::HandshakeKind::Full, false, false) - ); - let wrapped_server = if explicit_suite { - QuicServerConfig::with_initial( - server.clone(), - initial_suite_from_provider(server.crypto_provider()).unwrap(), - ) - .unwrap() - } else { - QuicServerConfig::try_from(server.clone()).unwrap() - }; - assert_eq!(server.max_early_data_size, u32::MAX); - // A third-party client holds an older ticket and really offers early data. - assert_eq!( - handshake(client.clone(), wrapped_server.inner.clone()), - (rustls::HandshakeKind::Resumed, true, false) - ); - let wrapped_client = if explicit_suite { - QuicClientConfig::with_initial( - client.clone(), - initial_suite_from_provider(client.crypto_provider()).unwrap(), - ) - .unwrap() - } else { - QuicClientConfig::try_from(client.clone()).unwrap() - }; - assert!(client.enable_early_data); - assert_eq!( - handshake(wrapped_client.inner, wrapped_server.inner), - (rustls::HandshakeKind::Resumed, false, false) - ); - } + let server = QuicServerConfig::new(Arc::new(server)); + let dcid = ConnectionId::new(&[1; 8]); + assert!(crypto::ServerConfig::initial_keys(&server, crate::Version::V1, dcid).is_none()); } } diff --git a/rs/moq-quic/src/endpoint.rs b/rs/moq-quic/src/endpoint.rs index dcf994331ab..c5d49503637 100644 --- a/rs/moq-quic/src/endpoint.rs +++ b/rs/moq-quic/src/endpoint.rs @@ -457,7 +457,11 @@ impl Endpoint { // Decoding refused every other version. let version = Version::from_wire(header.version)?; - let crypto = server_config.crypto.initial_keys(version, dst_cid); + let Some(crypto) = server_config.crypto.initial_keys(version, dst_cid) else { + // Without the suite there is no key to protect even a close with. + warn!("dropping initial: the server's TLS provider lacks TLS13_AES_128_GCM_SHA256"); + return None; + }; if let Err(reason) = self.early_validate_first_packet(header) { return Some(DatagramEvent::Response(self.initial_close( @@ -1406,7 +1410,7 @@ impl Drop for AcceptDropGuard { /// Errors in the parameters being used to create a new connection /// /// These arise before any I/O has been performed. -#[derive(Debug, Error, Clone, PartialEq, Eq)] +#[derive(Debug, Error, Clone, PartialEq)] #[non_exhaustive] pub enum ConnectError { /// The endpoint can no longer create new connections @@ -1430,6 +1434,9 @@ pub enum ConnectError { /// The client configuration is invalid #[error("invalid client config: {0}")] Config(#[from] ConfigError), + /// rustls refused the client config, such as one without TLS 1.3 + #[error("TLS config: {0}")] + Tls(rustls::Error), } /// Error type for attempting to accept an [`Incoming`] @@ -1495,11 +1502,14 @@ impl Accepting { } = &args; let pref_addr_cid = args.side_args.pref_addr_cid(); - let tls = server_crypto.start_session(version, ¶ms); - let mut conn = Connection::new(tls, args); - let result = conn - .handle_first_packet(now, remote, ecn, packet_number, packet, rest) - .map(|()| conn); + let result = server_crypto + .start_session(version, ¶ms) + .map_err(ConnectionError::TransportError) + .and_then(|tls| { + let mut conn = Connection::new(tls, args); + conn.handle_first_packet(now, remote, ecn, packet_number, packet, rest) + .map(|()| conn) + }); Accepted { incoming_idx, diff --git a/rs/moq-quic/src/fuzz/connection.rs b/rs/moq-quic/src/fuzz/connection.rs index 68404899cd3..36312b4a87d 100644 --- a/rs/moq-quic/src/fuzz/connection.rs +++ b/rs/moq-quic/src/fuzz/connection.rs @@ -10,7 +10,6 @@ use std::{ collections::{HashMap, HashSet, VecDeque}, net::{Ipv6Addr, SocketAddr}, num::NonZeroU64, - sync::Arc, }; use arbitrary::{Arbitrary, Unstructured}; @@ -18,12 +17,12 @@ use bytes::{Buf, Bytes, BytesMut}; use super::plaintext::{Clear, Plaintext, TAG_LEN}; use crate::{ - Dir, Duration, Event, Instant, Limits, Side, StreamId, client, + Dir, Duration, Event, Instant, Limits, Side, StreamId, connection::Connection, endpoint::{self, ConnectionHandle, DatagramEvent, Dial, Endpoint, Received}, frame::{self, Frame}, packet::{FixedLengthConnectionIdParser, PartialDecode}, - recv, server, transport, + recv, transport, }; /// The connection ID length both endpoints issue, which a captured packet needs to parse. @@ -219,7 +218,7 @@ struct Peer { impl Peer { fn new(side: Side, port: u16) -> Self { let server = side.is_server().then(|| { - let mut config = server::Config::new(Arc::new(Plaintext)); + let mut config = Plaintext::server(); config.transport = transport(); config }); @@ -323,7 +322,7 @@ impl Pair { capture: None, }; - let mut config = client::Config::new(Arc::new(Plaintext)); + let mut config = Plaintext::client(); config.transport = transport(); let (ch, conn) = pair .client diff --git a/rs/moq-quic/src/fuzz/plaintext.rs b/rs/moq-quic/src/fuzz/plaintext.rs index 554ae9725b0..e4e029a5f1a 100644 --- a/rs/moq-quic/src/fuzz/plaintext.rs +++ b/rs/moq-quic/src/fuzz/plaintext.rs @@ -6,15 +6,16 @@ //! body. Packets leave in the clear with a zeroed tag the size of AES-GCM's, so they are //! as large as real ones, and any packet with room for a tag decrypts. -use std::{any::Any, sync::Arc}; +use std::sync::Arc; use bytes::BytesMut; use crate::{ - ConnectionId, Side, TransportError, Version, + ConnectionId, Side, TransportError, Version, client, coding::{BufExt, BufMutExt}, - crypto::{self, CryptoError, ExportKeyingMaterialError, HeaderKey, KeyPair, Keys, PacketKey}, + crypto::{self, CryptoError, ExportKeyingMaterialError, HandshakeData, HeaderKey, KeyPair, Keys, PacketKey}, endpoint::ConnectError, + server, transport_parameters::TransportParameters, }; @@ -26,9 +27,21 @@ const MESSAGES: usize = 2; /// Client and server crypto that skips TLS and leaves every packet in the clear. /// -/// Pass it to `client::Config::new` and `server::Config::new`. +/// Build each side's config with [`Plaintext::client`] and [`Plaintext::server`]. pub struct Plaintext; +impl Plaintext { + /// A client config whose connections run plaintext crypto. + pub fn client() -> client::Config { + client::Config::from_crypto(Arc::new(Self)) + } + + /// A server config whose connections run plaintext crypto. + pub fn server() -> server::Config { + server::Config::from_crypto(Arc::new(Self)) + } +} + impl crypto::ClientConfig for Plaintext { fn start_session( self: Arc, @@ -41,16 +54,20 @@ impl crypto::ClientConfig for Plaintext { } impl crypto::ServerConfig for Plaintext { - fn initial_keys(&self, _version: Version, _dst_cid: ConnectionId) -> Keys { - keys() + fn initial_keys(&self, _version: Version, _dst_cid: ConnectionId) -> Option { + Some(keys()) } fn retry_tag(&self, _version: Version, _orig_dst_cid: ConnectionId, _packet: &[u8]) -> [u8; 16] { [0; 16] } - fn start_session(self: Arc, _version: Version, params: &TransportParameters) -> Box { - Box::new(Session::new(Side::Server, params)) + fn start_session( + self: Arc, + _version: Version, + params: &TransportParameters, + ) -> Result, TransportError> { + Ok(Box::new(Session::new(Side::Server, params))) } } @@ -105,11 +122,11 @@ impl crypto::Session for Session { keys() } - fn handshake_data(&self) -> Option> { + fn handshake_data(&self) -> Option { None } - fn peer_identity(&self) -> Option> { + fn peer_identity(&self) -> Option>> { None } diff --git a/rs/moq-quic/src/lib.rs b/rs/moq-quic/src/lib.rs index 15de0164411..eb91bc22806 100644 --- a/rs/moq-quic/src/lib.rs +++ b/rs/moq-quic/src/lib.rs @@ -13,6 +13,14 @@ #![warn(missing_docs)] #![cfg_attr(test, allow(dead_code, reason = "test builds compile helpers only some tests call"))] +#![cfg_attr( + not(any(feature = "aws-lc-rs", feature = "ring")), + allow( + dead_code, + unused_imports, + reason = "without a crypto provider only the mux connection is reachable" + ) +)] use std::{ net::{IpAddr, SocketAddr}, @@ -35,7 +43,7 @@ mod range_set; pub mod sim; #[cfg(all(test, any(feature = "aws-lc-rs", feature = "ring")))] mod tests; -pub mod transport_parameters; +mod transport_parameters; mod varint; pub use varint::{VarInt, VarIntBoundsExceeded}; @@ -54,7 +62,7 @@ pub mod stream; pub use connection::mux; pub(crate) use stream::{Dir, Id as StreamId}; -#[cfg(any(feature = "ring", feature = "aws-lc-rs"))] +// The configs take and report rustls types, so callers name the same version. pub use rustls; mod config; diff --git a/rs/moq-quic/src/packet.rs b/rs/moq-quic/src/packet.rs index fbe53e21a9e..91f01cd60ff 100644 --- a/rs/moq-quic/src/packet.rs +++ b/rs/moq-quic/src/packet.rs @@ -911,17 +911,11 @@ mod tests { #[test] fn header_encoding() { use crate::Side; - use crate::crypto::rustls::{initial_keys, initial_suite_from_provider}; - #[cfg(all(feature = "aws-lc-rs", not(feature = "ring")))] - use rustls::crypto::aws_lc_rs::default_provider; - #[cfg(feature = "ring")] - use rustls::crypto::ring::default_provider; + use crate::crypto::rustls::{configured_provider, initial_keys, initial_suite}; use rustls::quic::Version; let dcid = ConnectionId::new(&hex!("06b858ec6f80452b")); - let provider = default_provider(); - - let suite = initial_suite_from_provider(&std::sync::Arc::new(provider)).unwrap(); + let suite = initial_suite(&configured_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/rs/moq-quic/src/tests/characterize/paths.rs b/rs/moq-quic/src/tests/characterize/paths.rs index 4a76081a84c..259a1b12bd7 100644 --- a/rs/moq-quic/src/tests/characterize/paths.rs +++ b/rs/moq-quic/src/tests/characterize/paths.rs @@ -1,7 +1,5 @@ //! Packet-level defenses and path changes -use std::any::Any; - use super::*; use crate::crypto::{self, KeyPair, Keys}; @@ -65,10 +63,10 @@ impl crypto::Session for LimitedSession { fn initial_keys(&self, dst_cid: ConnectionId, side: Side) -> Keys { limit(self.0.initial_keys(dst_cid, side)) } - fn handshake_data(&self) -> Option> { + fn handshake_data(&self) -> Option { self.0.handshake_data() } - fn peer_identity(&self) -> Option> { + fn peer_identity(&self) -> Option>> { self.0.peer_identity() } fn is_handshaking(&self) -> bool { @@ -104,7 +102,8 @@ impl crypto::Session for LimitedSession { fn aead_integrity_limit() { let _guard = subscribe(); let mut pair = pair_with(|_| {}); - let mut config = client::Config::new(Arc::new(Limited(Arc::new(client_crypto())))); + let quic = Arc::new(crypto::rustls::QuicClientConfig::new(Arc::new(client_crypto()))); + let mut config = client::Config::from_crypto(Arc::new(Limited(quic))); config.transport = transport(); let (client_ch, server_ch) = pair.connect_with(config); diff --git a/rs/moq-quic/src/tests/mod.rs b/rs/moq-quic/src/tests/mod.rs index 3b85b6dd901..33eb30ab4c3 100644 --- a/rs/moq-quic/src/tests/mod.rs +++ b/rs/moq-quic/src/tests/mod.rs @@ -32,7 +32,6 @@ use crate::{ cid_generator::{ConnectionIdGenerator, RandomConnectionIdGenerator}, congestion::{self, Controller, Factory, Metrics}, connection::{Connection, Error as ConnectionError}, - crypto::rustls::QuicServerConfig, endpoint::{Accepting, ConnectionHandle, DatagramEvent, Dial, Endpoint, Incoming, Received}, frame::FrameStruct, packet::{Header, InitialHeader, PacketNumber}, @@ -369,13 +368,10 @@ fn close_before_a_client_certificate_leaves_ends() { PrivatePkcs8KeyDer::from(identity.signing_key.serialize_der()).into(), ) .unwrap(); - let mut client = client::Config::new(Arc::new(crypto::rustls::QuicClientConfig::try_from(client).unwrap())); + let mut client = client::Config::new(Arc::new(client)); client.transport = cubic_transport(); - let mut pair = Pair::new( - Default::default(), - server::Config::new(Arc::new(QuicServerConfig::try_from(server).unwrap())), - ); + let mut pair = Pair::new(Default::default(), server::Config::new(Arc::new(server))); let client_ch = connect_before_the_finished_leaves(&mut pair, client); let now = pair.time; @@ -908,14 +904,12 @@ fn export_keying_material() { // client keying material let mut client_buf = [0u8; 64]; pair.client_conn_mut(client_ch) - .crypto_session() .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() .export_keying_material(&mut server_buf, LABEL, CONTEXT) .unwrap(); @@ -1082,7 +1076,6 @@ fn reject_missing_client_cert() { ) .with_single_cert(vec![cert], PrivateKeyDer::from(key)) .unwrap(); - let config = QuicServerConfig::try_from(config).unwrap(); let mut pair = Pair::new(Default::default(), server::Config::new(Arc::new(config))); @@ -1368,19 +1361,12 @@ fn alpn_success() { assert_matches!(pair.server_conn_mut(server_ch).poll(), Some(Event::HandshakeConfirmed)); assert_matches!(pair.server_conn_mut(server_ch).poll(), Some(Event::Connected)); - let hd = pair - .client_conn_mut(client_ch) - .crypto_session() - .handshake_data() - .unwrap() - .downcast::() - .unwrap(); - assert_eq!(hd.protocol.unwrap(), &b"bar"[..]); - assert_eq!( - hd.protocol_version.unwrap().downcast_ref::(), - Some(&rustls::ProtocolVersion::TLSv1_3) - ); - assert!(hd.cipher_suite.unwrap().downcast_ref::().is_some()); + let client = pair.client_conn_mut(client_ch).handshake_data().unwrap(); + assert_eq!(client.alpn.as_deref(), Some(&b"bar"[..])); + assert_eq!(client.server_name, None); + let server = pair.server_conn_mut(server_ch).handshake_data().unwrap(); + assert_eq!(server.alpn.as_deref(), Some(&b"bar"[..])); + assert_eq!(server.server_name.as_deref(), Some("localhost")); } #[test] @@ -5764,7 +5750,8 @@ fn application_close_in_initial_is_rejected() { // connection ID, which travels in the clear, so anyone who observes the handshake can do this. let keys = server_config() .crypto - .initial_keys(Version::from_wire(version).unwrap(), orig_dst_cid); + .initial_keys(Version::from_wire(version).unwrap(), orig_dst_cid) + .unwrap(); let number = PacketNumber::U8(0); let header = Header::Initial(InitialHeader { dst_cid: client_cid, diff --git a/rs/moq-quic/src/tests/util.rs b/rs/moq-quic/src/tests/util.rs index 1e89a0cca4b..803780685e4 100644 --- a/rs/moq-quic/src/tests/util.rs +++ b/rs/moq-quic/src/tests/util.rs @@ -681,22 +681,25 @@ pub(super) fn server_config_with_cert(cert: CertificateDer<'static>, key: Privat config } -pub(super) fn server_crypto() -> QuicServerConfig { +pub(super) fn server_crypto() -> rustls::ServerConfig { server_crypto_inner(None, None) } -pub(super) fn server_crypto_with_alpn(alpn: Vec>) -> QuicServerConfig { +pub(super) fn server_crypto_with_alpn(alpn: Vec>) -> rustls::ServerConfig { server_crypto_inner(None, Some(alpn)) } -pub(super) fn server_crypto_with_cert(cert: CertificateDer<'static>, key: PrivateKeyDer<'static>) -> QuicServerConfig { +pub(super) fn server_crypto_with_cert( + cert: CertificateDer<'static>, + key: PrivateKeyDer<'static>, +) -> rustls::ServerConfig { server_crypto_inner(Some((cert, key)), None) } fn server_crypto_inner( identity: Option<(CertificateDer<'static>, PrivateKeyDer<'static>)>, alpn: Option>>, -) -> QuicServerConfig { +) -> rustls::ServerConfig { let (cert, key) = identity.unwrap_or_else(|| { ( CERTIFIED_KEY.cert.der().clone(), @@ -709,7 +712,7 @@ fn server_crypto_inner( config.alpn_protocols = alpn; } - config.try_into().unwrap() + config } pub(super) fn client_config() -> client::Config { @@ -743,15 +746,18 @@ pub(super) fn client_config_with_certs(certs: Vec>) -> c client::Config::new(Arc::new(client_crypto_inner(Some(certs), None))) } -pub(super) fn client_crypto() -> QuicClientConfig { +pub(super) fn client_crypto() -> rustls::ClientConfig { client_crypto_inner(None, None) } -pub(super) fn client_crypto_with_alpn(protocols: Vec>) -> QuicClientConfig { +pub(super) fn client_crypto_with_alpn(protocols: Vec>) -> rustls::ClientConfig { client_crypto_inner(None, Some(protocols)) } -fn client_crypto_inner(certs: Option>>, alpn: Option>>) -> QuicClientConfig { +fn client_crypto_inner( + certs: Option>>, + alpn: Option>>, +) -> rustls::ClientConfig { let mut roots = RootCertStore::empty(); for cert in certs.unwrap_or_else(|| vec![CERTIFIED_KEY.cert.der().clone()]) { roots.add(cert).unwrap(); @@ -767,7 +773,7 @@ fn client_crypto_inner(certs: Option>>, alpn: Option inner.alpn_protocols = alpn; } - inner.try_into().unwrap() + inner } pub(super) fn min_opt(x: Option, y: Option) -> Option { diff --git a/rs/moq-relay/src/cluster.rs b/rs/moq-relay/src/cluster.rs index 2c900359c63..69212a577eb 100644 --- a/rs/moq-relay/src/cluster.rs +++ b/rs/moq-relay/src/cluster.rs @@ -3868,10 +3868,9 @@ mod tests { .iter() .map(|alpn| alpn.as_bytes().to_vec()) .collect(); - let crypto = driver::crypto::rustls::QuicServerConfig::try_from(tls).expect("quic crypto"); let mut transport = driver::transport::Config::default(); transport.idle_timeout = Some(idle); - let mut server_config = driver::server::Config::new(Arc::new(crypto)); + let mut server_config = driver::server::Config::new(Arc::new(tls)); server_config.transport = transport; driver::endpoint::Endpoint::new( driver::endpoint::Config::default().with_server(server_config), diff --git a/rs/moq-relay/src/uring.rs b/rs/moq-relay/src/uring.rs index 211b0448ad1..6b9e7789332 100644 --- a/rs/moq-relay/src/uring.rs +++ b/rs/moq-relay/src/uring.rs @@ -739,13 +739,7 @@ async fn serve_connection( let sni = conn.server_name().map(str::to_owned); // TLS validated this against the configured roots before the connection // existed, so a chain here is an authenticated peer. - let identity = conn.peer_chain().map(|chain| { - let chain = chain - .into_iter() - .map(moq_tokio::rustls::pki_types::CertificateDer::from) - .collect(); - moq_tokio::tls::PeerIdentity::from_chain(chain) - }); + let identity = conn.peer_identity().map(moq_tokio::tls::PeerIdentity::from_chain); // Browsers speak WebTransport; everyone else raw QUIC. The moq version // rides the ALPN for raw QUIC and the WebTransport subprotocol for `h3`. diff --git a/rs/moq-relay/tests/integration/bitrate_caps.rs b/rs/moq-relay/tests/integration/bitrate_caps.rs index 6dd07b48c7b..366683f4c68 100644 --- a/rs/moq-relay/tests/integration/bitrate_caps.rs +++ b/rs/moq-relay/tests/integration/bitrate_caps.rs @@ -146,13 +146,12 @@ async fn window_opens_once_admitted(mut relay: Relay, port: u16) { connect.tls.insecure = Some(true); let mut tls = connect.tls.build().expect("tls config"); tls.alpn_protocols = vec![b"moq-lite-06".to_vec()]; - let crypto = driver::crypto::rustls::QuicClientConfig::try_from(tls).expect("quic crypto"); let endpoint = driver::endpoint::Endpoint::new(Default::default(), std::net::UdpSocket::bind("127.0.0.1:0").unwrap()) .expect("endpoint"); let conn = endpoint .connect( - &driver::client::Config::new(std::sync::Arc::new(crypto)), + &driver::client::Config::new(std::sync::Arc::new(tls)), ([127, 0, 0, 1], port).into(), "localhost", ) diff --git a/rs/moq-relay/tests/integration/peer_limits.rs b/rs/moq-relay/tests/integration/peer_limits.rs index 3bf7f4c4f4d..56a16a65a10 100644 --- a/rs/moq-relay/tests/integration/peer_limits.rs +++ b/rs/moq-relay/tests/integration/peer_limits.rs @@ -59,13 +59,12 @@ async fn probe(port: u16, peer: bool) -> Seen { connect.tls.insecure = Some(true); let mut tls = connect.tls.build().expect("tls config"); tls.alpn_protocols = vec![b"moq-lite-06".to_vec()]; - let crypto = driver::crypto::rustls::QuicClientConfig::try_from(tls).expect("quic crypto"); let endpoint = driver::endpoint::Endpoint::new(Default::default(), std::net::UdpSocket::bind("127.0.0.1:0").unwrap()) .expect("endpoint"); let conn = endpoint .connect( - &driver::client::Config::new(std::sync::Arc::new(crypto)), + &driver::client::Config::new(std::sync::Arc::new(tls)), ([127, 0, 0, 1], port).into(), "localhost", ) @@ -239,10 +238,9 @@ pub(crate) fn bare_server(connect: &moq_tokio::connect::Config) -> driver::endpo .iter() .map(|alpn| alpn.as_bytes().to_vec()) .collect(); - let crypto = driver::crypto::rustls::QuicServerConfig::try_from(tls).expect("quic crypto"); let mut transport = driver::transport::Config::default(); transport.idle_timeout = Some(BARE_IDLE); - let mut config = driver::server::Config::new(std::sync::Arc::new(crypto)); + let mut config = driver::server::Config::new(std::sync::Arc::new(tls)); config.transport = transport; driver::endpoint::Endpoint::new( driver::endpoint::Config::default().with_server(config), diff --git a/rs/moq-relay/tests/integration/refusals.rs b/rs/moq-relay/tests/integration/refusals.rs index a3c7193fa72..83e66e392a6 100644 --- a/rs/moq-relay/tests/integration/refusals.rs +++ b/rs/moq-relay/tests/integration/refusals.rs @@ -425,12 +425,11 @@ async fn web_peer(addr: SocketAddr, alpn: &str) -> moq_tokio::quic::web::Session tls.insecure = Some(true); let mut tls = tls.build().unwrap(); tls.alpn_protocols = vec![b"h3".to_vec()]; - let crypto = driver::crypto::rustls::QuicClientConfig::try_from(tls).unwrap(); let endpoint = driver::endpoint::Endpoint::new(Default::default(), std::net::UdpSocket::bind("127.0.0.1:0").unwrap()).unwrap(); let conn = endpoint .connect( - &driver::client::Config::new(std::sync::Arc::new(crypto)), + &driver::client::Config::new(std::sync::Arc::new(tls)), addr, "localhost", ) diff --git a/rs/moq-relay/tests/integration/runtime_uring.rs b/rs/moq-relay/tests/integration/runtime_uring.rs index 884590cab3c..baa79df6865 100644 --- a/rs/moq-relay/tests/integration/runtime_uring.rs +++ b/rs/moq-relay/tests/integration/runtime_uring.rs @@ -950,10 +950,9 @@ async fn uring_forwards_the_setup_token() { } else { b"moqt-18".to_vec() }]; - let crypto = driver::crypto::rustls::QuicClientConfig::try_from(tls).unwrap(); let conn = client .connect( - &driver::client::Config::new(std::sync::Arc::new(crypto)), + &driver::client::Config::new(std::sync::Arc::new(tls)), addr, "localhost", ) @@ -1152,10 +1151,9 @@ fn uring_workers_bound_stalled_handshakes() { let crypto = |alpn: &[u8]| { let mut tls = tls.clone(); tls.alpn_protocols = vec![alpn.to_vec()]; - let crypto = driver::crypto::rustls::QuicClientConfig::try_from(tls).expect("QUIC crypto"); let mut transport = driver::transport::Config::default(); transport.congestion = std::sync::Arc::new(moq_congestion::Unlimited); - let mut config = driver::client::Config::new(std::sync::Arc::new(crypto)); + let mut config = driver::client::Config::new(std::sync::Arc::new(tls)); config.transport = transport; config }; diff --git a/rs/moq-relay/tests/integration/tenant_matrix.rs b/rs/moq-relay/tests/integration/tenant_matrix.rs index fe5575701d7..665a81661e2 100644 --- a/rs/moq-relay/tests/integration/tenant_matrix.rs +++ b/rs/moq-relay/tests/integration/tenant_matrix.rs @@ -322,12 +322,11 @@ async fn raw_setup(addr: SocketAddr, target: &str) -> (driver::endpoint::Endpoin connect.tls.insecure = Some(true); let mut tls = connect.tls.build().expect("tls config"); tls.alpn_protocols = vec![b"moq-lite-06".to_vec()]; - let crypto = driver::crypto::rustls::QuicClientConfig::try_from(tls).expect("quic crypto"); let endpoint = driver::endpoint::Endpoint::new(Default::default(), std::net::UdpSocket::bind("127.0.0.1:0").unwrap()) .expect("endpoint"); let conn = endpoint - .connect(&driver::client::Config::new(Arc::new(crypto)), addr, "localhost") + .connect(&driver::client::Config::new(Arc::new(tls)), addr, "localhost") .expect("connect") .await .expect("handshake"); diff --git a/rs/moq-tokio/MIGRATE.md b/rs/moq-tokio/MIGRATE.md index 599f9c579fa..348136bc965 100644 --- a/rs/moq-tokio/MIGRATE.md +++ b/rs/moq-tokio/MIGRATE.md @@ -4,6 +4,13 @@ How to update your code for each breaking change, newest first. ## Unreleased +### QUIC handshake data needs no downcast ([#7028](https://github.com/moq-dev/moq/pull/7028)) + +`quic::driver::Connecting::handshake_data` returns `crypto::HandshakeData` and `Connection::handshake_data` returns `Option`, so drop the `downcast`. +`Connection::peer_identity` returns `Option>>`. +Pass a rustls config straight to `quic::driver::client::Config::new` or `server::Config::new` instead of wrapping it in `crypto::rustls::QuicClientConfig::try_from` or `QuicServerConfig::try_from`. +`quic::Error::NoInitialCipherSuite` is removed. + ### Smaller default windows, which qmux shares ([#7624](https://github.com/moq-dev/moq/pull/7624)) `quic::DEFAULT_RECEIVE_WINDOW` is 4 MiB, `DEFAULT_STREAM_RECEIVE_WINDOW` 1 MiB, and `DEFAULT_SEND_WINDOW` 4 MiB, down from 128 MiB, 4 MiB, and 16 MiB. `Error::ReceiveWindowTooSmall` now refuses only a `receive_window` below 1 MiB. diff --git a/rs/moq-tokio/src/quic/backend.rs b/rs/moq-tokio/src/quic/backend.rs index 90337dc74e6..6255d9f36c8 100644 --- a/rs/moq-tokio/src/quic/backend.rs +++ b/rs/moq-tokio/src/quic/backend.rs @@ -184,10 +184,6 @@ pub enum Error { #[error("failed to build client certificate verifier")] ClientVerifier(#[source] rustls::server::VerifierBuilderError), - /// The TLS config lacks a cipher suite QUIC can use for initial packets. - #[error("no cipher suite available for QUIC initial packets")] - NoInitialCipherSuite, - /// The connection could not be started, usually a bad config or address. #[error("{0}")] Connect(String), @@ -282,12 +278,6 @@ crate::error::from_message! { hex::FromHexError => InvalidFingerprint, } -impl From for Error { - fn from(_: driver::crypto::rustls::NoInitialCipherSuite) -> Self { - Self::NoInitialCipherSuite - } -} - impl From for Error { fn from(err: web::ClientError) -> Self { Self::Client { @@ -440,7 +430,6 @@ impl Client { config.key_log = keylog.clone(); } - let config: driver::crypto::rustls::QuicClientConfig = config.try_into()?; let mut config = driver::client::Config::new(Arc::new(config)); config.transport = self.transport.clone(); @@ -493,7 +482,6 @@ impl Error { | Self::UnsupportedScheme(_) | Self::InvalidDnsName | Self::Config(_) - | Self::NoInitialCipherSuite | Self::ConnectRejected(_) | Self::Limits(_) => true, Self::Tls(err) => err.is_terminal(), @@ -624,7 +612,6 @@ impl Server { tls.key_log = keylog; } - let tls: driver::crypto::rustls::QuicServerConfig = tls.try_into()?; let mut server = driver::server::Config::new(Arc::new(tls)); server.transport = transport; // Advertise the preferred_address transport parameter (RFC 9000 ยง9.6). @@ -779,19 +766,10 @@ pub(crate) async fn accept( ) -> Result> { let mut conn = conn.accept()?; - // Scoped so the `!Send` boxed handshake data is gone before the next await. - let (alpn, host) = { - let handshake = conn - .handshake_data() - .await? - .downcast::() - .unwrap(); - (handshake.protocol, handshake.server_name) - }; - - let alpn = alpn.ok_or(Error::MissingAlpn)?; + let handshake = conn.handshake_data().await?; + let alpn = handshake.alpn.ok_or(Error::MissingAlpn)?; let alpn = String::from_utf8(alpn)?; - let host = host.unwrap_or_default(); + let host = handshake.server_name.unwrap_or_default(); let remote = conn.remote_address(); tracing::debug!(%host, ip = %remote, %alpn, "accepting"); @@ -818,7 +796,7 @@ pub(crate) async fn accept( .await .map_err(|err| Error::RecvRequest(crate::error::message(err)))?; let url = Some(request.url.clone()); - let identity = crate::tls::PeerIdentity::from_any(request.conn().peer_identity()); + let identity = request.conn().peer_identity().map(crate::tls::PeerIdentity::from_chain); // The authority the client put in its CONNECT URL. let authority = request.url.host_str().filter(|h| !h.is_empty()).map(str::to_owned); @@ -845,7 +823,7 @@ pub(crate) async fn accept( // this also covers versions omitted from `moq_net::ALPNS` but enabled // in this server's configuration. alpn if alpns.contains(&alpn) => { - let identity = crate::tls::PeerIdentity::from_any(conn.peer_identity()); + let identity = conn.peer_identity().map(crate::tls::PeerIdentity::from_chain); // Raw QUIC carries no request URL; the path rides the SETUP. The TLS SNI is the // only authority the client can offer here, and it is optional. let authority = (!host.is_empty()).then_some(host); diff --git a/rs/moq-tokio/src/quic/driver/connection.rs b/rs/moq-tokio/src/quic/driver/connection.rs index 65174659d3c..f8d53bc5be5 100644 --- a/rs/moq-tokio/src/quic/driver/connection.rs +++ b/rs/moq-tokio/src/quic/driver/connection.rs @@ -1,7 +1,6 @@ //! A single connection: its streams, datagrams, and why it ends. use std::{ - any::Any, fmt, future::Future, io, @@ -30,8 +29,9 @@ use crate::quic::driver::{ use moq_congestion::Controller; pub use moq_quic::connection::{Error, Stats}; use moq_quic::{ - EndpointEvent, StreamEvent, TransportError, TransportErrorCode, + EndpointEvent, StreamEvent, TransportError, TransportErrorCode, crypto, endpoint::ConnectionHandle, + rustls::pki_types::CertificateDer, stream::{Dir, Id as StreamId}, }; @@ -97,15 +97,10 @@ impl Connecting { /// Parameters negotiated during the handshake /// - /// The dynamic type returned is determined by the configured - /// [`Session`](moq_quic::crypto::Session). For the default `rustls` session, the return value can - /// be [`downcast`](Box::downcast) to a - /// [`crypto::rustls::HandshakeData`](crate::quic::driver::crypto::rustls::HandshakeData). - /// /// Peer certificates are not available at this stage; see [`Connection::peer_identity()`]. /// /// This operation is cancel-safe. - pub async fn handshake_data(&mut self) -> Result, Error> { + pub async fn handshake_data(&mut self) -> Result { // Taking &mut self allows us to use a single oneshot channel rather than dealing with // potentially many tasks waiting on the same event. It's a bit of a hack, but keeps things // simple. @@ -119,7 +114,6 @@ impl Connecting { let inner = conn.state.lock(); inner .inner - .crypto_session() .handshake_data() .ok_or_else(|| inner.error.clone().expect("spurious handshake data ready notification")) } @@ -567,25 +561,18 @@ impl Connection { /// Parameters negotiated during the handshake /// /// Guaranteed to return `Some` on fully established connections or after - /// [`Connecting::handshake_data()`] succeeds. See that method's documentations for details on - /// the returned value. - /// - /// [`Connection::handshake_data()`]: crate::quic::driver::connection::Connecting::handshake_data - pub fn handshake_data(&self) -> Option> { - self.0.state.lock().inner.crypto_session().handshake_data() + /// [`Connecting::handshake_data()`] succeeds. + pub fn handshake_data(&self) -> Option { + self.0.state.lock().inner.handshake_data() } - /// Cryptographic identity of the peer - /// - /// The dynamic type returned is determined by the configured - /// [`Session`](moq_quic::crypto::Session). For the default `rustls` session, the return value can - /// be [`downcast`](Box::downcast) to a Vec<[rustls::pki_types::CertificateDer]>. + /// The certificate chain the peer presented, end entity first, if it presented one /// /// For custom asynchronous validation (e.g. checking a certificate against an external /// database), call this after awaiting [`Connecting`] and use [`close()`][Self::close] to /// reject untrusted peers. - pub fn peer_identity(&self) -> Option> { - self.0.state.lock().inner.crypto_session().peer_identity() + pub fn peer_identity(&self) -> Option>> { + self.0.state.lock().inner.peer_identity() } /// A stable identifier for this connection @@ -596,29 +583,6 @@ impl Connection { self.0.stable_id() } - /// Derive keying material from this connection's TLS session secrets. - /// - /// When both peers call this method with the same `label` and `context` - /// arguments and `output` buffers of equal length, they will get the - /// same sequence of bytes in `output`. These bytes are cryptographically - /// strong and pseudorandom, and are suitable for use as keying material. - /// - /// See [RFC5705](https://tools.ietf.org/html/rfc5705) for more information. - #[cfg(test)] - pub(super) fn export_keying_material( - &self, - output: &mut [u8], - label: &[u8], - context: &[u8], - ) -> Result<(), moq_quic::crypto::ExportKeyingMaterialError> { - self.0 - .state - .lock() - .inner - .crypto_session() - .export_keying_material(output, label, context) - } - /// See [`moq_quic::connection::Connection::set_limits`] pub fn set_limits(&self, limits: moq_quic::Limits) -> Result<(), moq_quic::LimitsError> { let mut conn = self.0.state.lock(); diff --git a/rs/moq-tokio/src/quic/driver/tests.rs b/rs/moq-tokio/src/quic/driver/tests.rs index 8cebf6fb8c1..2e3ced56f6c 100755 --- a/rs/moq-tokio/src/quic/driver/tests.rs +++ b/rs/moq-tokio/src/quic/driver/tests.rs @@ -23,12 +23,7 @@ use crate::quic::driver::runtime::{AsyncUdpSocket, wrap_udp_socket}; use crate::quic::driver::{Duration, Instant}; use bytes::{Buf, Bytes}; use kio::clock::{sleep, timeout}; -use moq_quic::{ - ConnectionId, RandomConnectionIdGenerator, Version, - crypto::rustls::{QuicClientConfig, QuicServerConfig}, - crypto::{Keys, ServerConfig as ProtoServerConfig, Session}, - transport_parameters::TransportParameters, -}; +use moq_quic::RandomConnectionIdGenerator; use moq_sock::udp::RecvMeta; use rand::{Rng, SeedableRng, rngs::StdRng}; use rustls::{ @@ -202,53 +197,6 @@ fn read_after_close() { }); } -#[test] -fn export_keying_material() { - let _guard = subscribe(); - let runtime = rt_basic(); - let endpoint = { - let _guard = runtime.enter(); - endpoint() - }; - - runtime.block_on(async move { - let outgoing_conn_fut = tokio::spawn({ - let endpoint = endpoint.clone(); - async move { - endpoint - .dial(endpoint.local_addr(), "localhost") - .unwrap() - .await - .expect("connect") - } - }); - let incoming_conn_fut = tokio::spawn({ - let endpoint = endpoint.clone(); - async move { - endpoint - .accept() - .await - .expect("endpoint") - .accept() - .expect("accept") - .await - .expect("connection") - } - }); - let outgoing_conn = outgoing_conn_fut.await.unwrap(); - let incoming_conn = incoming_conn_fut.await.unwrap(); - let mut i_buf = [0u8; 64]; - incoming_conn - .export_keying_material(&mut i_buf, b"asdf", b"qwer") - .unwrap(); - let mut o_buf = [0u8; 64]; - outgoing_conn - .export_keying_material(&mut o_buf, b"asdf", b"qwer") - .unwrap(); - assert_eq!(&i_buf[..], &o_buf[..]); - }); -} - #[tokio::test] async fn ip_blocking() { let _guard = subscribe(); @@ -356,13 +304,13 @@ impl EndpointFactory { } } -#[derive(Default)] +#[derive(Debug, Default)] struct HandshakeBlocker { state: Mutex, changed: Condvar, } -#[derive(Default)] +#[derive(Debug, Default)] struct HandshakeBlockerState { started: bool, released: bool, @@ -402,23 +350,17 @@ impl Drop for HandshakeReleaseGuard { } } -struct BlockingServerConfig { - inner: Arc, +/// Blocks the server's handshake on the ClientHello, which `Incoming::accept` processes +#[derive(Debug)] +struct BlockingResolver { + key: Arc, blocker: Arc, } -impl ProtoServerConfig for BlockingServerConfig { - fn initial_keys(&self, version: Version, dst_cid: ConnectionId) -> Keys { - self.inner.initial_keys(version, dst_cid) - } - - fn retry_tag(&self, version: Version, orig_dst_cid: ConnectionId, packet: &[u8]) -> [u8; 16] { - self.inner.retry_tag(version, orig_dst_cid, packet) - } - - fn start_session(self: Arc, version: Version, params: &TransportParameters) -> Box { +impl rustls::server::ResolvesServerCert for BlockingResolver { + fn resolve(&self, _hello: rustls::server::ClientHello<'_>) -> Option> { self.blocker.block(); - self.inner.clone().start_session(version, params) + Some(self.key.clone()) } } @@ -491,25 +433,24 @@ fn blocking_server_pair( ) -> (Endpoint, Endpoint, Arc) { let factory = EndpointFactory::new(); let key = PrivateKeyDer::Pkcs8(factory.cert.signing_key.serialize_der().into()); - let mut server_crypto = rustls::ServerConfig::builder_with_provider(default_provider().into()) + let provider = Arc::new(default_provider()); + let key = rustls::sign::CertifiedKey::from_der(vec![factory.cert.cert.der().clone()], key, &provider).unwrap(); + let blocker = Arc::new(HandshakeBlocker::default()); + let mut server_crypto = rustls::ServerConfig::builder_with_provider(provider) .with_protocol_versions(&[&rustls::version::TLS13]) .unwrap() .with_no_client_auth() - .with_single_cert(vec![factory.cert.cert.der().clone()], key) - .unwrap(); + .with_cert_resolver(Arc::new(BlockingResolver { + key: Arc::new(key), + blocker: blocker.clone(), + })); server_crypto.alpn_protocols = vec![server_alpn.to_vec()]; - let mut server_config = - crate::quic::driver::server::Config::new(Arc::new(QuicServerConfig::try_from(server_crypto).unwrap())); + let mut server_config = crate::quic::driver::server::Config::new(Arc::new(server_crypto)); // Loopback answers in microseconds. The default 333 ms initial RTT would hold every // connection closed before its handshake in draining for three seconds. let mut transport = transport::Config::default(); transport.initial_rtt = Duration::from_millis(10); server_config.transport = transport.clone(); - let blocker = Arc::new(HandshakeBlocker::default()); - server_config.crypto = Arc::new(BlockingServerConfig { - inner: server_config.crypto.clone(), - blocker: blocker.clone(), - }); let server = Endpoint::new( factory.endpoint_config.clone().with_server(server_config), @@ -533,7 +474,7 @@ fn blocking_server_pair( .with_root_certificates(roots) .with_no_client_auth(); client_crypto.alpn_protocols = vec![client_alpn.to_vec()]; - let mut client_config = client::Config::new(Arc::new(QuicClientConfig::try_from(client_crypto).unwrap())); + let mut client_config = client::Config::new(Arc::new(client_crypto)); client_config.transport = transport; client.set_default_client_config(client_config); @@ -857,7 +798,7 @@ fn run_echo(args: EchoArgs) { let _guard = error_span!("client").entered(); client_endpoint(args.client_addr).unwrap() }; - let mut client_config = client::Config::new(Arc::new(QuicClientConfig::try_from(client_crypto).unwrap())); + let mut client_config = client::Config::new(Arc::new(client_crypto)); client_config.transport = transport_config; client.set_default_client_config(client_config); diff --git a/rs/moq-tokio/src/quic/web/session.rs b/rs/moq-tokio/src/quic/web/session.rs index c1c9b0a34eb..21faf43a7eb 100644 --- a/rs/moq-tokio/src/quic/web/session.rs +++ b/rs/moq-tokio/src/quic/web/session.rs @@ -4,7 +4,7 @@ use std::{ io::Cursor, ops::Deref, pin::Pin, - sync::{Arc, Mutex, OnceLock}, + sync::{Arc, Mutex}, task::{Context, Poll, Waker}, time::Duration, }; @@ -20,34 +20,6 @@ use crate::quic::web::{ proto::{ConnectRequest, ConnectResponse, Frame, StreamUni, VarInt}, }; -/// The ALPN the QUIC handshake negotiated, or `None` if there was none, the handshake -/// has not completed, or it is not valid UTF-8. -/// -/// The driver only exposes the ALPN via a downcast of the boxed handshake data, so the result -/// is owned rather than borrowed from the connection. -#[cfg(any(feature = "aws-lc-rs", feature = "ring"))] -fn negotiated_alpn(conn: &crate::quic::driver::connection::Connection) -> Option { - let data = conn.handshake_data()?; - let data = data - .downcast::() - .ok()?; - - String::from_utf8(data.protocol?).ok() -} - -/// `crate::quic::driver::crypto::rustls` only exists once a TLS backend is enabled, so there is no -/// handshake data type to name here. -/// -/// This crate's own builders are gated the same way, so normally there is no connection -/// to read an ALPN from either. A caller that enables a provider on `moq-quic` directly and -/// brings its own endpoint can still establish one, and [`Session::protocol`] reports -/// `None` for those raw sessions. Enable this crate's `aws-lc-rs` or `ring` feature to -/// get the ALPN. -#[cfg(not(any(feature = "aws-lc-rs", feature = "ring")))] -fn negotiated_alpn(_conn: &crate::quic::driver::connection::Connection) -> Option { - None -} - /// An established WebTransport session, acting like a full QUIC connection. See /// [`crate::quic::driver::connection::Connection`]. /// @@ -93,17 +65,10 @@ pub struct Session { // The response sent by the server, or None for a raw QUIC session. response: Option, - // The ALPN negotiated by the QUIC handshake, for raw QUIC sessions. The driver only - // exposes it behind an allocating downcast, so `protocol()` has nothing in the - // connection to borrow from and needs somewhere to keep the result. - // - // Resolved on first read rather than at construction: `raw()` accepts any - // connection, including one whose handshake has not completed and so has no ALPN - // yet. Only a successful read is latched, so an early call cannot pin `None` for - // the life of the session. Shared across clones. - // - // Unused by HTTP/3 sessions, which read the subprotocol out of the response. - alpn: Arc>, + // The ALPN the QUIC handshake negotiated, for raw QUIC sessions, copied out of the + // connection's lock so `protocol()` can borrow it. HTTP/3 sessions read the + // subprotocol out of the response instead. + alpn: Option>, } impl Session { @@ -145,7 +110,7 @@ impl Session { close: close.clone(), request: Some(connect.request.clone()), response: Some(connect.response.clone()), - alpn: Default::default(), + alpn: None, }; // Run a background task to read capsules from the CONNECT recv stream. @@ -560,7 +525,13 @@ impl Session { /// return `None`. [`Self::protocol`] reports the ALPN negotiated by the QUIC /// handshake, when one was negotiated. pub fn raw(conn: crate::quic::driver::connection::Connection) -> Self { + // A connection exists only once its handshake completed, so the ALPN is settled. + let alpn = conn + .handshake_data() + .and_then(|data| String::from_utf8(data.alpn?).ok()) + .map(Arc::from); Self { + alpn, conn, session_id: None, header_uni: Default::default(), @@ -572,7 +543,6 @@ impl Session { close: Arc::new(CloseReason::new(true)), request: None, response: None, - alpn: Default::default(), } } @@ -592,21 +562,12 @@ impl Session { /// /// For an HTTP/3 session this is the subprotocol the server selected via /// `WT-Available-Protocols`; for a raw QUIC session it is the negotiated ALPN. - /// `None` if neither was negotiated, the ALPN is not valid UTF-8, or the raw - /// connection is still handshaking. + /// `None` if neither was negotiated or the ALPN is not valid UTF-8. pub fn protocol(&self) -> Option<&str> { - if let Some(response) = &self.response { - return response.protocol.as_deref(); + match &self.response { + Some(response) => response.protocol.as_deref(), + None => self.alpn.as_deref(), } - - if let Some(alpn) = self.alpn.get() { - return Some(alpn); - } - - // Latch only a successful read, so a call made while the connection is still - // handshaking cannot pin `None` for the life of the session. - let alpn = negotiated_alpn(&self.conn)?; - Some(self.alpn.get_or_init(|| alpn)) } /// Return connection-level statistics. diff --git a/rs/moq-tokio/src/tls.rs b/rs/moq-tokio/src/tls.rs index f25bda0f1da..9f50d918554 100644 --- a/rs/moq-tokio/src/tls.rs +++ b/rs/moq-tokio/src/tls.rs @@ -1654,23 +1654,13 @@ impl Serve { /// /// Returned by [`crate::server::Request::peer_identity`] when the peer presented a /// certificate that chained to a configured [`Listen::root`]. Owns the chain -/// (leaf first) so callers can inspect it, e.g. [`expiry`](Self::expiry), -/// without re-parsing the type-erased QUIC identity. +/// (leaf first) so callers can inspect it, e.g. [`expiry`](Self::expiry). #[derive(Debug, Clone)] pub struct PeerIdentity { chain: Vec>, } impl PeerIdentity { - /// Wrap the type-erased identity from the QUIC connection. - /// Returns `None` if the peer presented no certificate or the identity is - /// not a certificate chain. - #[cfg(feature = "quic")] - pub(crate) fn from_any(identity: Option>) -> Option { - let chain = identity?.downcast::>>().ok()?; - Some(Self { chain: *chain }) - } - /// Wrap a certificate chain a QUIC backend already validated, leaf first. /// /// For a listener living outside this crate (the io_uring workers, say) @@ -2288,7 +2278,6 @@ mod tests { params.self_signed(&key).unwrap().into() } - #[cfg(feature = "quic")] #[test] fn peer_identity_expiry_reads_not_after() { // notAfter at a whole second so the round-trip is exact. @@ -2299,10 +2288,7 @@ mod tests { params.not_after = not_after; let cert: CertificateDer<'static> = params.self_signed(&key).unwrap().into(); - // The QUIC backend hands back the chain as a boxed Vec. - let identity: Box = Box::new(vec![cert]); - let parsed = PeerIdentity::from_any(Some(identity)).expect("chain parsed"); - let expiry = parsed.expiry().expect("expiry parsed"); + let expiry = PeerIdentity::from_chain(vec![cert]).expiry().expect("expiry parsed"); assert_eq!( expiry.saturating_duration_since(kio::clock::Wall::UNIX_EPOCH).as_secs(), 2_000_000_000 @@ -2328,15 +2314,6 @@ mod tests { } } - #[cfg(feature = "quic")] - #[test] - fn peer_identity_none_without_chain() { - assert!(PeerIdentity::from_any(None).is_none()); - // A wrong downcast type (not a cert chain) yields None rather than panicking. - let bogus: Box = Box::new(42u32); - assert!(PeerIdentity::from_any(Some(bogus)).is_none()); - } - #[test] fn fingerprint_verifier_matches_and_rejects() { let provider = crypto::provider(); diff --git a/rs/moq-tokio/tests/integration/receive_credit.rs b/rs/moq-tokio/tests/integration/receive_credit.rs index f1f3f899b3e..475dd3650ee 100644 --- a/rs/moq-tokio/tests/integration/receive_credit.rs +++ b/rs/moq-tokio/tests/integration/receive_credit.rs @@ -150,8 +150,7 @@ pub(crate) async fn dial( .with_custom_certificate_verifier(Arc::new(AnyCert)) .with_no_client_auth(); tls.alpn_protocols = vec![version.alpn().as_bytes().to_vec()]; - let quic = driver::crypto::rustls::QuicClientConfig::try_from(tls).expect("quic config"); - let mut client = driver::client::Config::new(Arc::new(quic)); + let mut client = driver::client::Config::new(Arc::new(tls)); client.transport = transport; let endpoint = @@ -432,8 +431,7 @@ async fn reversed(version: &str) -> Reversed { .with_single_cert(vec![cert.der().clone()], key.into()) .expect("tls"); tls.alpn_protocols = vec![alpn.clone()]; - let quic = driver::crypto::rustls::QuicServerConfig::try_from(tls).expect("quic config"); - let mut config = driver::server::Config::new(Arc::new(quic)); + let mut config = driver::server::Config::new(Arc::new(tls)); config.transport = transport(WINDOW as u64); let server = driver::endpoint::Endpoint::new( driver::endpoint::Config::default().with_server(config), @@ -447,13 +445,12 @@ async fn reversed(version: &str) -> Reversed { .with_custom_certificate_verifier(Arc::new(AnyCert)) .with_no_client_auth(); tls.alpn_protocols = vec![alpn]; - let quic = driver::crypto::rustls::QuicClientConfig::try_from(tls).expect("quic config"); let client = driver::endpoint::Endpoint::new(Default::default(), std::net::UdpSocket::bind("127.0.0.1:0").unwrap()) .expect("client endpoint"); let (conn, accepted) = tokio::join!( async { client - .connect(&driver::client::Config::new(Arc::new(quic)), addr, "localhost") + .connect(&driver::client::Config::new(Arc::new(tls)), addr, "localhost") .expect("connect") .await .expect("handshake") @@ -809,8 +806,7 @@ async fn javascript_partial_frame_gives_way(version: &str) { .with_single_cert(vec![cert.der().clone()], key.into()) .expect("tls"); tls.alpn_protocols = vec![web::ALPN.as_bytes().to_vec()]; - let quic = driver::crypto::rustls::QuicServerConfig::try_from(tls).expect("quic config"); - let mut config = driver::server::Config::new(Arc::new(quic)); + let mut config = driver::server::Config::new(Arc::new(tls)); config.transport = transport(WINDOW as u64); let endpoint = driver::endpoint::Endpoint::new( driver::endpoint::Config::default().with_server(config), diff --git a/rs/moq-tokio/tests/integration/receive_ts.rs b/rs/moq-tokio/tests/integration/receive_ts.rs index cd3a7b7de87..388f43f6ae9 100644 --- a/rs/moq-tokio/tests/integration/receive_ts.rs +++ b/rs/moq-tokio/tests/integration/receive_ts.rs @@ -148,8 +148,7 @@ async fn run(preset: &str) -> Run { .dangerous() .with_custom_certificate_verifier(Arc::new(crate::receive_credit::AnyCert)) .with_no_client_auth(); - let quic = driver::crypto::rustls::QuicClientConfig::try_from(tls).expect("quic config"); - let mut client = driver::client::Config::new(Arc::new(quic)); + let mut client = driver::client::Config::new(Arc::new(tls)); client.transport.congestion = Arc::new(recorder.clone()); client.transport.receive_timestamps = std::num::NonZeroU32::new(32); let endpoint = diff --git a/rs/moq-tokio/tests/integration/reset_secret.rs b/rs/moq-tokio/tests/integration/reset_secret.rs index e0614d0e8cf..058f15bf75c 100644 --- a/rs/moq-tokio/tests/integration/reset_secret.rs +++ b/rs/moq-tokio/tests/integration/reset_secret.rs @@ -98,14 +98,13 @@ fn restart(reset_secret: bool) -> Result (driver::server::Config, driver::client::Config) { .with_single_cert(vec![cert.der().clone()], key) .unwrap(); server.alpn_protocols = alpn.clone(); - let server = driver::crypto::rustls::QuicServerConfig::try_from(server).unwrap(); let mut roots = rustls::RootCertStore::empty(); roots.add(cert.der().clone()).unwrap(); @@ -261,7 +260,6 @@ fn tls(version: Version) -> (driver::server::Config, driver::client::Config) { .with_root_certificates(roots) .with_no_client_auth(); client.alpn_protocols = alpn; - let client = driver::crypto::rustls::QuicClientConfig::try_from(client).unwrap(); ( driver::server::Config::new(Arc::new(server)), diff --git a/rs/moq-tokio/tests/integration/stream_code.rs b/rs/moq-tokio/tests/integration/stream_code.rs index 191b48a45af..94537b9dddb 100644 --- a/rs/moq-tokio/tests/integration/stream_code.rs +++ b/rs/moq-tokio/tests/integration/stream_code.rs @@ -77,14 +77,13 @@ async fn refused_code(server: &Server, alpn: &str) -> Option { .with_root_certificates(roots) .with_no_client_auth(); tls.alpn_protocols = vec![alpn.as_bytes().to_vec()]; - let quic = driver::crypto::rustls::QuicClientConfig::try_from(tls).expect("quic config"); let endpoint = driver::endpoint::Endpoint::new(Default::default(), std::net::UdpSocket::bind("127.0.0.1:0").unwrap()) .expect("endpoint"); let conn = endpoint .connect( - &driver::client::Config::new(Arc::new(quic)), + &driver::client::Config::new(Arc::new(tls)), ([127, 0, 0, 1], server.port).into(), "localhost", ) diff --git a/rs/moq-uring/MIGRATE.md b/rs/moq-uring/MIGRATE.md index ea4b96c710b..a3b7d16c341 100644 --- a/rs/moq-uring/MIGRATE.md +++ b/rs/moq-uring/MIGRATE.md @@ -4,6 +4,11 @@ How to update your code for each breaking change, newest first. ## Unreleased +### `peer_chain` is `peer_identity` ([#7028](https://github.com/moq-dev/moq/pull/7028)) + +`quic::driver::Connection::peer_chain()` is now `peer_identity()` and returns `Option>>`, mirroring moq-tokio. +Use `cert.to_vec()` for raw DER. + ### Smaller default windows ([#7624](https://github.com/moq-dev/moq/pull/7624)) `quic::transport::Config::default()` takes a 4 MiB connection window, a 1 MiB stream window, and 4 MiB of unacknowledged outgoing data, down from 128 MiB, 4 MiB, and 16 MiB, matching moq-tokio's `[quic]` defaults. Set `limits` explicitly to keep the old windows. diff --git a/rs/moq-uring/src/quic/driver/connection.rs b/rs/moq-uring/src/quic/driver/connection.rs index 27e981b38b6..2b67da3cb5d 100644 --- a/rs/moq-uring/src/quic/driver/connection.rs +++ b/rs/moq-uring/src/quic/driver/connection.rs @@ -340,21 +340,16 @@ impl Connection { self.shared.conn.borrow().stats() } - /// The peer's certificate chain in DER, leaf first, or `None` if it - /// presented none. + /// The certificate chain the peer presented, end entity first, or `None` + /// if it presented none. /// /// A server only sees one when it asked /// ([`ClientAuth`](crate::quic::server::ClientAuth)), and TLS already /// validated it against the configured roots by the time this connection /// exists: an invalid chain fails the handshake instead. So a `Some` here /// is an authenticated peer, and the chain is what names it. - pub fn peer_chain(&self) -> Option>> { - let conn = self.shared.conn.borrow(); - let identity = conn.crypto_session().peer_identity()?; - let chain = identity - .downcast::>>() - .ok()?; - Some(chain.iter().map(|cert| cert.to_vec()).collect()) + pub fn peer_identity(&self) -> Option>> { + self.shared.conn.borrow().peer_identity() } /// The peer's address as of the handshake; a peer that migrates later @@ -520,13 +515,9 @@ pub(crate) async fn establish(shared: Shared, deadline: Option) -> Resu let (alpn, server_name, remote) = { let conn = shared.conn.borrow(); - let handshake = conn - .crypto_session() - .handshake_data() - .and_then(|data| data.downcast::().ok()); - let (alpn, server_name) = match handshake { + let (alpn, server_name) = match conn.handshake_data() { Some(data) => ( - data.protocol.map(|proto| String::from_utf8_lossy(&proto).into_owned()), + data.alpn.map(|proto| String::from_utf8_lossy(&proto).into_owned()), data.server_name, ), None => (None, None), diff --git a/rs/moq-uring/src/quic/driver/mod.rs b/rs/moq-uring/src/quic/driver/mod.rs index 2b0f92ff8b9..0ffb2abac61 100644 --- a/rs/moq-uring/src/quic/driver/mod.rs +++ b/rs/moq-uring/src/quic/driver/mod.rs @@ -21,7 +21,6 @@ pub(crate) use connection::Shared; use std::{num::NonZeroU64, sync::Arc}; -use moq_quic::crypto::rustls::{QuicClientConfig, QuicServerConfig}; use rustls::pki_types::pem::PemObject; use rustls::pki_types::{CertificateDer, PrivateKeyDer}; @@ -170,8 +169,7 @@ pub(crate) fn client_config(config: &client::Config) -> Result Result Result { - let crypto = QuicServerConfig::try_from(config.tls.clone()).map_err(|err| Error::Tls(err.to_string()))?; - let mut server = moq_quic::server::Config::new(Arc::new(crypto)); + let mut server = moq_quic::server::Config::new(config.tls.clone()); server.transport = transport_config(&config.transport)?; #[cfg(feature = "qlog")] with_qlog(&mut server.transport, &config.transport, qlog::Side::Server); diff --git a/rs/moq-uring/tests/integration/support.rs b/rs/moq-uring/tests/integration/support.rs index 17622f08887..e7143502042 100644 --- a/rs/moq-uring/tests/integration/support.rs +++ b/rs/moq-uring/tests/integration/support.rs @@ -85,7 +85,6 @@ pub async fn web_connect( tls.insecure = Some(true); let mut crypto = tls.build().expect("tls"); crypto.alpn_protocols = vec![web::ALPN.as_bytes().to_vec()]; - let crypto = driver::crypto::rustls::QuicClientConfig::try_from(crypto).expect("quic crypto"); let mut config = driver::client::Config::new(std::sync::Arc::new(crypto)); config.transport = transport;