152 lines
6.5 KiB
Rust
152 lines
6.5 KiB
Rust
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
//! QUIC endpoint construction for client and server.
|
|
//!
|
|
//! Builds the `quinn` endpoints and configures ALPN and TLS 1.3. Both endpoints negotiate the `synvael` application protocol; a peer advertising any other ALPN identifier is rejected during the TLS handshake.
|
|
|
|
use std::net::SocketAddr;
|
|
use std::sync::Arc;
|
|
use std::time::Duration;
|
|
|
|
use quinn::crypto::rustls::{QuicClientConfig, QuicServerConfig};
|
|
use quinn::{ClientConfig, Endpoint, IdleTimeout, ServerConfig, TransportConfig, VarInt};
|
|
use rustls::DigitallySignedStruct;
|
|
use rustls::SignatureScheme;
|
|
use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier};
|
|
use rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer, ServerName, UnixTime};
|
|
use tracing::debug;
|
|
|
|
use crate::error::NetError;
|
|
|
|
/// The Application-Layer Protocol Negotiation identifier for the Synvael protocol.
|
|
pub const ALPN: &[u8] = b"synvael";
|
|
|
|
/// Interval between QUIC keep-alive probes, in milliseconds.
|
|
///
|
|
/// Kept well below [`MAX_IDLE_TIMEOUT_MS`] so several probes elapse before the idle timeout could fire. Keep-alives are required because chunk delivery deliberately stalls the stream when the client cannot mesh fast enough: during such a flow-control stall no application data flows in either direction, and without a probe the connection would be indistinguishable from a dead peer and closed on the idle timeout.
|
|
const KEEP_ALIVE_INTERVAL_MS: u32 = 5_000;
|
|
|
|
/// Maximum time with no received packets before a connection is considered lost, in milliseconds.
|
|
const MAX_IDLE_TIMEOUT_MS: u32 = 30_000;
|
|
|
|
/// Builds the QUIC transport configuration shared by both endpoints.
|
|
///
|
|
/// Enables keep-alive probes and sets an explicit idle timeout; see [`KEEP_ALIVE_INTERVAL_MS`] for why probes are mandatory given the chunk stream's backpressure behaviour. All other transport parameters retain their `quinn` defaults.
|
|
fn transport_config() -> Arc<TransportConfig> {
|
|
let mut transport = TransportConfig::default();
|
|
transport.keep_alive_interval(Some(Duration::from_millis(u64::from(
|
|
KEEP_ALIVE_INTERVAL_MS,
|
|
))));
|
|
// `VarInt::from_u32` is infallible, so no fallible `IdleTimeout::try_from(Duration)` conversion is needed.
|
|
transport.max_idle_timeout(Some(IdleTimeout::from(VarInt::from_u32(
|
|
MAX_IDLE_TIMEOUT_MS,
|
|
))));
|
|
Arc::new(transport)
|
|
}
|
|
|
|
/// Installs the process-wide default `rustls` `CryptoProvider` if one is not already installed.
|
|
fn ensure_crypto_provider() {
|
|
if rustls::crypto::ring::default_provider()
|
|
.install_default()
|
|
.is_err()
|
|
{
|
|
debug!("rustls crypto provider already installed; reusing existing default");
|
|
}
|
|
}
|
|
|
|
/// Builds a QUIC server endpoint bound to `bind`, using a freshly generated self-signed certificate and the `synvael` ALPN.
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// Returns [`NetError::Rcgen`] if certificate generation fails, [`NetError::Rustls`] if the TLS configuration cannot be built, [`NetError::NoInitialCipherSuite`] if the configuration lacks a TLS 1.3 cipher suite, or [`NetError::Io`] if the UDP socket cannot be bound.
|
|
pub fn server_endpoint(bind: SocketAddr) -> Result<Endpoint, NetError> {
|
|
ensure_crypto_provider();
|
|
|
|
// Generate a self-signed certificate for the "localhost" subject. The subject is not validated by the current client verifier and exists only to satisfy certificate structure.
|
|
let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()])?;
|
|
let cert_der = cert.cert.der().clone();
|
|
let key_der = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from(cert.signing_key.serialize_der()));
|
|
|
|
let mut tls_config = rustls::ServerConfig::builder()
|
|
.with_no_client_auth()
|
|
.with_single_cert(vec![cert_der], key_der)?;
|
|
tls_config.alpn_protocols = vec![ALPN.to_vec()];
|
|
|
|
let quic_config = QuicServerConfig::try_from(tls_config)?;
|
|
let mut server_config = ServerConfig::with_crypto(Arc::new(quic_config));
|
|
server_config.transport_config(transport_config());
|
|
|
|
Ok(Endpoint::server(server_config, bind)?)
|
|
}
|
|
|
|
/// Builds a QUIC client endpoint bound to an ephemeral local address, configured with the `synvael` ALPN and a permissive certificate verifier.
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// Returns [`NetError::NoInitialCipherSuite`] if the TLS configuration lacks a TLS 1.3 cipher suite, or [`NetError::Io`] if the local UDP socket cannot be bound.
|
|
pub fn client_endpoint() -> Result<Endpoint, NetError> {
|
|
ensure_crypto_provider();
|
|
|
|
let mut tls_config = rustls::ClientConfig::builder()
|
|
.dangerous()
|
|
.with_custom_certificate_verifier(Arc::new(AcceptAnyServerCert))
|
|
.with_no_client_auth();
|
|
tls_config.alpn_protocols = vec![ALPN.to_vec()];
|
|
|
|
let quic_config = QuicClientConfig::try_from(tls_config)?;
|
|
let mut client_config = ClientConfig::new(Arc::new(quic_config));
|
|
client_config.transport_config(transport_config());
|
|
|
|
let mut endpoint = Endpoint::client("0.0.0.0:0".parse().map_err(std::io::Error::other)?)?;
|
|
endpoint.set_default_client_config(client_config);
|
|
|
|
Ok(endpoint)
|
|
}
|
|
|
|
/// A certificate verifier that unconditionally accepts any server certificate, disabling server authentication.
|
|
// TODO: replace with trust-on-first-use + certificate pinning.
|
|
#[derive(Debug)]
|
|
struct AcceptAnyServerCert;
|
|
|
|
impl ServerCertVerifier for AcceptAnyServerCert {
|
|
fn verify_server_cert(
|
|
&self,
|
|
_end_entity: &CertificateDer<'_>,
|
|
_intermediates: &[CertificateDer<'_>],
|
|
_server_name: &ServerName<'_>,
|
|
_ocsp_response: &[u8],
|
|
_now: UnixTime,
|
|
) -> Result<ServerCertVerified, rustls::Error> {
|
|
Ok(ServerCertVerified::assertion())
|
|
}
|
|
|
|
fn verify_tls12_signature(
|
|
&self,
|
|
_message: &[u8],
|
|
_cert: &CertificateDer<'_>,
|
|
_dss: &DigitallySignedStruct,
|
|
) -> Result<HandshakeSignatureValid, rustls::Error> {
|
|
Ok(HandshakeSignatureValid::assertion())
|
|
}
|
|
|
|
fn verify_tls13_signature(
|
|
&self,
|
|
_message: &[u8],
|
|
_cert: &CertificateDer<'_>,
|
|
_dss: &DigitallySignedStruct,
|
|
) -> Result<HandshakeSignatureValid, rustls::Error> {
|
|
Ok(HandshakeSignatureValid::assertion())
|
|
}
|
|
|
|
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
|
|
// Advertise the schemes the installed provider can verify, so the shim does not artificially restrict handshake negotiation.
|
|
rustls::crypto::ring::default_provider()
|
|
.signature_verification_algorithms
|
|
.supported_schemes()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
#[path = "tests/endpoint.rs"]
|
|
mod tests;
|