diff --git a/quinn-proto/src/crypto/rustls.rs b/quinn-proto/src/crypto/rustls.rs index 8114a6b22..aad4d0d3c 100644 --- a/quinn-proto/src/crypto/rustls.rs +++ b/quinn-proto/src/crypto/rustls.rs @@ -257,10 +257,19 @@ pub struct HandshakeData { /// A QUIC-compatible TLS client configuration /// -/// Can be constructed via [`ClientConfig::with_root_certificates()`][root_certs], -/// [`ClientConfig::with_platform_verifier()`][platform] or by using the [`TryFrom`] implementation with a -/// custom [`rustls::ClientConfig`]. A pre-existing `ClientConfig` must have TLS 1.3 support enabled for -/// this to work. 0-RTT support is available if `enable_early_data` is set to `true`. +/// Quinn implicitly constructs a `QuicClientConfig` with reasonable defaults within +/// [`ClientConfig::with_root_certificates()`][root_certs] and [`ClientConfig::with_platform_verifier()`][platform]. +/// Alternatively, `QuicClientConfig`'s [`TryFrom`] implementation can be used to wrap around a +/// custom [`rustls::ClientConfig`], in which case care should be taken around certain points: +/// +/// - If `enable_early_data` is not set to true, then sending 0-RTT data will not be possible on +/// outgoing connections. +/// - The [`rustls::ClientConfig`] must have TLS 1.3 support enabled for conversion to succeed. +/// +/// The object in the `resumption` field of the inner [`rustls::ClientConfig`] determines whether +/// calling `into_0rtt` on outgoing connections returns `Ok` or `Err`. It typically allows +/// `into_0rtt` to proceed if it recognizes the server name, and defaults to an in-memory cache of +/// 256 server names. /// /// [root_certs]: crate::config::ClientConfig::with_root_certificates() /// [platform]: crate::config::ClientConfig::with_platform_verifier() @@ -386,10 +395,14 @@ impl std::error::Error for NoInitialCipherSuite {} /// A QUIC-compatible TLS server configuration /// -/// Can be constructed via [`ServerConfig::with_single_cert()`][single] or by using the -/// [`TryFrom`] implementation with a custom [`rustls::ServerConfig`]. A pre-existing -/// `ServerConfig` must have TLS 1.3 support enabled for this to work. 0-RTT support is -/// available to clients if `max_early_data_size` is set to `u32::MAX`. +/// Quinn implicitly constructs a `QuicServerConfig` with reasonable defaults within +/// [`ServerConfig::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: +/// +/// - If `max_early_data_size` is not set to `u32::MAX`, the server will not be able to accept +/// incoming 0-RTT data. QUIC prohibits `max_early_data_size` values other than 0 or `u32::MAX`. +/// - The `rustls::ServerConfig` must have TLS 1.3 support enabled for conversion to succeed. /// /// [single]: crate::config::ServerConfig::with_single_cert() pub struct QuicServerConfig { diff --git a/quinn/src/connection.rs b/quinn/src/connection.rs index 2e75eac1b..7d5cec775 100644 --- a/quinn/src/connection.rs +++ b/quinn/src/connection.rs @@ -79,29 +79,48 @@ impl Connecting { /// Convert into a 0-RTT or 0.5-RTT connection at the cost of weakened security /// - /// Opens up the connection for use before the handshake finishes, allowing the API user to - /// send data with 0-RTT encryption if the necessary key material is available. This is useful - /// for reducing start-up latency by beginning transmission of application data without waiting - /// for the handshake's cryptographic security guarantees to be established. + /// Returns `Ok` immediately if the local endpoint is able to attempt sending 0/0.5-RTT data. + /// If so, the returned [`Connection`] can be used to send application data without waiting for + /// the rest of the handshake to complete, at the cost of weakened cryptographic security + /// guarantees. The returned [`ZeroRttAccepted`] future resolves when the handshake does + /// complete, at which point subsequently opened streams and written data will have full + /// cryptographic protection. /// - /// When the `ZeroRttAccepted` future completes, the connection has been fully established. + /// ## Outgoing /// - /// # Security + /// For outgoing connections, the initial attempt to convert to a [`Connection`] which sends + /// 0-RTT data will proceed if the [`crypto::ClientConfig`][crate::crypto::ClientConfig] + /// attempts to resume a previous TLS session. However, **the remote endpoint may not actually + /// _accept_ the 0-RTT data**--yet still accept the connection attempt in general. This + /// possibility is conveyed through the [`ZeroRttAccepted`] future--when the handshake + /// completes, it resolves to true if the 0-RTT data was accepted and false if it was rejected. + /// If it was rejected, the existence of streams opened and other application data sent prior + /// to the handshake completing will not be conveyed to the remote application, and local + /// operations on them will return `ZeroRttRejected` errors. /// - /// On outgoing connections, this enables transmission of 0-RTT data, which might be vulnerable - /// to replay attacks, and should therefore never invoke non-idempotent operations. + /// A server may reject 0-RTT data at its discretion, but accepting 0-RTT data requires the + /// relevant resumption state to be stored in the server, which servers may limit or lose for + /// various reasons including not persisting resumption state across server restarts. /// - /// On incoming connections, this enables transmission of 0.5-RTT data, which might be - /// intercepted by a man-in-the-middle. If this occurs, the handshake will not complete - /// successfully. + /// If manually providing a [`crypto::ClientConfig`][crate::crypto::ClientConfig], check your + /// implementation's docs for 0-RTT pitfalls. /// - /// # Errors + /// ## Incoming /// - /// Outgoing connections are only 0-RTT-capable when a cryptographic session ticket cached from - /// a previous connection to the same server is available, and includes a 0-RTT key. If no such - /// ticket is found, `self` is returned unmodified. + /// For incoming connections, conversion to 0.5-RTT will always fully succeed. `into_0rtt` will + /// always return `Ok` and the [`ZeroRttAccepted`] will always resolve to true. /// - /// For incoming connections, a 0.5-RTT connection will always be successfully constructed. + /// If manually providing a [`crypto::ServerConfig`][crate::crypto::ServerConfig], check your + /// implementation's docs for 0-RTT pitfalls. + /// + /// ## Security + /// + /// On outgoing connections, this enables transmission of 0-RTT data, which is vulnerable to + /// replay attacks, and should therefore never invoke non-idempotent operations. + /// + /// On incoming connections, this enables transmission of 0.5-RTT data, which may be sent + /// before TLS client authentication has occurred, and should therefore not be used to send + /// data for which client authentication is being used. pub fn into_0rtt(mut self) -> Result<(Connection, ZeroRttAccepted), Self> { // This lock borrows `self` and would normally be dropped at the end of this scope, so we'll // have to release it explicitly before returning `self` by value. diff --git a/quinn/src/send_stream.rs b/quinn/src/send_stream.rs index 10aeb21df..839fcbf66 100644 --- a/quinn/src/send_stream.rs +++ b/quinn/src/send_stream.rs @@ -189,12 +189,16 @@ impl SendStream { conn.inner.send_stream(self.stream).priority() } - /// Completes when the stream is stopped or read to completion by the peer + /// Completes when the peer stops the stream or reads the stream to completion /// - /// Yields `Some` with the stop error code when the stream is stopped by the peer. Yields `None` - /// when the stream is [`finish()`](Self::finish)ed locally and all stream data has been - /// received (but not necessarily processed) by the peer, after which it is no longer meaningful - /// for the stream to be stopped. + /// Yields `Some` with the stop error code if the peer stops the stream. Yields `None` if the + /// local side [`finish()`](Self::finish)es the stream and then the peer acknowledges receipt + /// of all stream data (although not necessarily the processing of it), after which the peer + /// closing the stream is no longer meaningful. + /// + /// For a variety of reasons, the peer may not send acknowledgements immediately upon receiving + /// data. As such, relying on `stopped` to know when the peer has read a stream to completion + /// may introduce more latency than using an application-level response of some sort. pub async fn stopped(&mut self) -> Result, StoppedError> { Stopped { stream: self }.await }