1use std::net::{IpAddr, SocketAddr};
4use std::sync::Arc;
5use std::time::Duration;
6
7#[cfg(any(feature = "ring", feature = "aws-lc-rs"))]
8use quinn::crypto::rustls::QuicClientConfig;
9use rustls::{client::danger::ServerCertVerifier, pki_types::CertificateDer};
10use tokio::net::lookup_host;
11use url::{Host, Url};
12
13#[cfg(any(feature = "ring", feature = "aws-lc-rs"))]
14use crate::ALPN;
15use crate::crypto;
16use crate::{ClientError, Session};
17
18pub enum CongestionControl {
22 Default,
24 Throughput,
26 LowLatency,
28}
29
30#[cfg(any(feature = "ring", feature = "aws-lc-rs"))]
31pub struct ClientBuilder {
35 provider: crypto::Provider,
36 transport: quinn::TransportConfig,
37 dns_timeout: Option<Duration>,
38 handshake_timeout: Option<Duration>,
39}
40
41#[cfg(any(feature = "ring", feature = "aws-lc-rs"))]
42impl ClientBuilder {
43 pub fn new() -> Self {
45 Self {
46 provider: crypto::default_provider(),
47 transport: quinn::TransportConfig::default(),
48 dns_timeout: None,
49 handshake_timeout: None,
50 }
51 }
52
53 pub fn with_congestion_control(mut self, algorithm: CongestionControl) -> Self {
55 match algorithm {
56 CongestionControl::LowLatency => self.transport.congestion_controller_factory(
57 Arc::new(quinn::congestion::NewRenoConfig::default()),
58 ),
59 CongestionControl::Throughput => self
60 .transport
61 .congestion_controller_factory(Arc::new(quinn::congestion::BbrConfig::default())),
62 CongestionControl::Default => self
63 .transport
64 .congestion_controller_factory(Arc::new(quinn::congestion::CubicConfig::default())),
65 };
66
67 self
68 }
69
70 pub fn with_transport_config(mut self, transport: quinn::TransportConfig) -> Self {
75 self.transport = transport;
76 self
77 }
78
79 pub fn with_dns_timeout(mut self, timeout: Duration) -> Self {
81 self.dns_timeout = Some(timeout);
82 self
83 }
84
85 pub fn with_handshake_timeout(mut self, timeout: Duration) -> Self {
87 self.handshake_timeout = Some(timeout);
88 self
89 }
90
91 pub fn with_system_roots(self) -> Result<Client, ClientError> {
93 let mut roots = rustls::RootCertStore::empty();
94
95 let native = rustls_native_certs::load_native_certs();
96
97 for err in native.errors {
99 tracing::warn!("failed to load root cert: {err:?}");
100 }
101
102 for cert in native.certs {
104 if let Err(err) = roots.add(cert) {
105 tracing::warn!("failed to add root cert: {err:?}");
106 }
107 }
108
109 let crypto = self
110 .builder()?
111 .with_root_certificates(roots)
112 .with_no_client_auth();
113
114 self.build(crypto)
115 }
116
117 pub fn with_server_certificates(
119 self,
120 certs: Vec<CertificateDer>,
121 ) -> Result<Client, ClientError> {
122 let hashes = certs.iter().map({
123 let provider = self.provider.clone();
124 move |cert| crypto::sha256(&provider, cert).as_ref().to_vec()
125 });
126
127 self.with_server_certificate_hashes(hashes.collect())
128 }
129
130 pub fn with_server_certificate_hashes(
132 self,
133 hashes: Vec<Vec<u8>>,
134 ) -> Result<Client, ClientError> {
135 let fingerprints = Arc::new(ServerFingerprints {
137 provider: self.provider.clone(),
138 fingerprints: hashes,
139 });
140
141 let crypto = self
143 .builder()?
144 .dangerous()
145 .with_custom_certificate_verifier(fingerprints.clone())
146 .with_no_client_auth();
147
148 self.build(crypto)
149 }
150
151 pub fn dangerous(self) -> DangerousClientBuilder {
157 DangerousClientBuilder { inner: self }
158 }
159
160 fn builder(
161 &self,
162 ) -> Result<rustls::ConfigBuilder<rustls::ClientConfig, rustls::WantsVerifier>, ClientError>
163 {
164 rustls::ClientConfig::builder_with_provider(self.provider.clone())
165 .with_protocol_versions(&[&rustls::version::TLS13])
166 .map_err(Into::into)
167 }
168
169 fn build(self, mut crypto: rustls::ClientConfig) -> Result<Client, ClientError> {
170 crypto.alpn_protocols = vec![ALPN.as_bytes().to_vec()];
171
172 let client_config = QuicClientConfig::try_from(crypto)
173 .map_err(|_| ClientError::InvalidCryptoConfiguration)?;
174 let mut client_config = quinn::ClientConfig::new(Arc::new(client_config));
175
176 client_config.transport_config(Arc::new(self.transport));
177
178 let client = quinn::Endpoint::client(SocketAddr::from(([0_u16; 8], 0)))
179 .map_err(|error| ClientError::Io(Arc::new(error)))?;
180 Ok(Client {
181 endpoint: client,
182 config: client_config,
183 dns_timeout: self.dns_timeout,
184 handshake_timeout: self.handshake_timeout,
185 })
186 }
187}
188
189#[cfg(any(feature = "ring", feature = "aws-lc-rs"))]
190impl Default for ClientBuilder {
191 fn default() -> Self {
192 Self::new()
193 }
194}
195
196#[cfg(any(feature = "ring", feature = "aws-lc-rs"))]
197pub struct DangerousClientBuilder {
203 inner: ClientBuilder,
204}
205
206#[cfg(any(feature = "ring", feature = "aws-lc-rs"))]
207impl DangerousClientBuilder {
208 pub fn with_no_certificate_verification(self) -> Result<Client, ClientError> {
216 let noop = NoCertificateVerification(self.inner.provider.clone());
217
218 let crypto = self
219 .inner
220 .builder()?
221 .dangerous()
222 .with_custom_certificate_verifier(Arc::new(noop))
223 .with_no_client_auth();
224
225 self.inner.build(crypto)
226 }
227}
228
229#[derive(Clone, Debug)]
231pub struct Client {
232 endpoint: quinn::Endpoint,
233 config: quinn::ClientConfig,
234 dns_timeout: Option<Duration>,
235 handshake_timeout: Option<Duration>,
236}
237
238impl Client {
239 pub fn new(endpoint: quinn::Endpoint, config: quinn::ClientConfig) -> Self {
243 Self {
244 endpoint,
245 config,
246 dns_timeout: None,
247 handshake_timeout: None,
248 }
249 }
250
251 pub async fn connect(&self, url: Url) -> Result<Session, ClientError> {
253 validate_url(&url)?;
254 let port = url.port().unwrap_or(443);
255
256 let (host, remote) = match url
257 .host()
258 .ok_or_else(|| ClientError::InvalidDnsName("".to_string()))?
259 {
260 Host::Domain(domain) => {
261 let domain = domain.to_string();
262 let lookup = lookup_host((domain.clone(), port));
264 let result = match self.dns_timeout {
265 Some(timeout) => tokio::time::timeout(timeout, lookup)
266 .await
267 .map_err(|_| ClientError::DnsTimeout)?,
268 None => lookup.await,
269 };
270 let mut remotes = match result {
271 Ok(remotes) => remotes,
272 Err(_) => return Err(ClientError::InvalidDnsName(domain)),
273 };
274
275 let remote = match remotes.next() {
277 Some(remote) => remote,
278 None => return Err(ClientError::InvalidDnsName(domain)),
279 };
280
281 (domain, remote)
282 }
283 Host::Ipv4(ipv4) => (ipv4.to_string(), SocketAddr::new(IpAddr::V4(ipv4), port)),
284 Host::Ipv6(ipv6) => (ipv6.to_string(), SocketAddr::new(IpAddr::V6(ipv6), port)),
285 };
286
287 let connecting = self
289 .endpoint
290 .connect_with(self.config.clone(), remote, &host)?;
291 let establish = async move {
292 let conn = connecting.await?;
293 Session::connect(conn, url).await
294 };
295
296 match self.handshake_timeout {
297 Some(timeout) => tokio::time::timeout(timeout, establish)
298 .await
299 .map_err(|_| ClientError::HandshakeTimeout)?,
300 None => establish.await,
301 }
302 }
303}
304
305fn validate_url(url: &Url) -> Result<(), ClientError> {
306 if url.scheme() != "https" {
307 return Err(ClientError::InvalidUrl(
308 "WebTransport requires an https URL".to_string(),
309 ));
310 }
311 if !url.username().is_empty() || url.password().is_some() {
312 return Err(ClientError::InvalidUrl(
313 "userinfo is not supported in the authority".to_string(),
314 ));
315 }
316 if url.fragment().is_some() {
317 return Err(ClientError::InvalidUrl(
318 "URL fragments are not sent in HTTP request targets".to_string(),
319 ));
320 }
321 if url.cannot_be_a_base() || url.host().is_none() {
322 return Err(ClientError::InvalidUrl(
323 "URL must contain a valid authority and path".to_string(),
324 ));
325 }
326 Ok(())
327}
328
329#[cfg_attr(not(any(feature = "ring", feature = "aws-lc-rs")), allow(dead_code))]
330#[derive(Debug)]
331struct ServerFingerprints {
332 provider: crypto::Provider,
333 fingerprints: Vec<Vec<u8>>,
334}
335
336impl ServerCertVerifier for ServerFingerprints {
337 fn verify_server_cert(
338 &self,
339 end_entity: &rustls::pki_types::CertificateDer<'_>,
340 _intermediates: &[rustls::pki_types::CertificateDer<'_>],
341 _server_name: &rustls::pki_types::ServerName<'_>,
342 _ocsp_response: &[u8],
343 _now: rustls::pki_types::UnixTime,
344 ) -> Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
345 let cert_hash = crypto::sha256(&self.provider, end_entity);
346 if self
347 .fingerprints
348 .iter()
349 .any(|fingerprint| fingerprint == cert_hash.as_ref())
350 {
351 return Ok(rustls::client::danger::ServerCertVerified::assertion());
352 }
353
354 Err(rustls::Error::InvalidCertificate(
355 rustls::CertificateError::UnknownIssuer,
356 ))
357 }
358
359 fn verify_tls12_signature(
360 &self,
361 message: &[u8],
362 cert: &rustls::pki_types::CertificateDer<'_>,
363 dss: &rustls::DigitallySignedStruct,
364 ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
365 rustls::crypto::verify_tls12_signature(
366 message,
367 cert,
368 dss,
369 &self.provider.signature_verification_algorithms,
370 )
371 }
372
373 fn verify_tls13_signature(
374 &self,
375 message: &[u8],
376 cert: &rustls::pki_types::CertificateDer<'_>,
377 dss: &rustls::DigitallySignedStruct,
378 ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
379 rustls::crypto::verify_tls13_signature(
380 message,
381 cert,
382 dss,
383 &self.provider.signature_verification_algorithms,
384 )
385 }
386
387 fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
388 self.provider
389 .signature_verification_algorithms
390 .supported_schemes()
391 }
392}
393
394#[derive(Debug)]
395pub struct NoCertificateVerification(Arc<rustls::crypto::CryptoProvider>);
399
400impl rustls::client::danger::ServerCertVerifier for NoCertificateVerification {
401 fn verify_server_cert(
402 &self,
403 _end_entity: &CertificateDer<'_>,
404 _intermediates: &[CertificateDer<'_>],
405 _server_name: &rustls::pki_types::ServerName<'_>,
406 _ocsp: &[u8],
407 _now: rustls::pki_types::UnixTime,
408 ) -> Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
409 Ok(rustls::client::danger::ServerCertVerified::assertion())
410 }
411
412 fn verify_tls12_signature(
413 &self,
414 message: &[u8],
415 cert: &CertificateDer<'_>,
416 dss: &rustls::DigitallySignedStruct,
417 ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
418 rustls::crypto::verify_tls12_signature(
419 message,
420 cert,
421 dss,
422 &self.0.signature_verification_algorithms,
423 )
424 }
425
426 fn verify_tls13_signature(
427 &self,
428 message: &[u8],
429 cert: &CertificateDer<'_>,
430 dss: &rustls::DigitallySignedStruct,
431 ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
432 rustls::crypto::verify_tls13_signature(
433 message,
434 cert,
435 dss,
436 &self.0.signature_verification_algorithms,
437 )
438 }
439
440 fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
441 self.0.signature_verification_algorithms.supported_schemes()
442 }
443}
444
445#[cfg(test)]
446mod tests {
447 use super::*;
448
449 #[test]
450 fn validates_webtransport_urls() {
451 assert!(validate_url(&Url::parse("https://example.com/chat").unwrap()).is_ok());
452 assert!(validate_url(&Url::parse("http://example.com/chat").unwrap()).is_err());
453 assert!(validate_url(&Url::parse("https://user@example.com/chat").unwrap()).is_err());
454 assert!(validate_url(&Url::parse("https://example.com/chat#fragment").unwrap()).is_err());
455 }
456
457 #[test]
458 fn builder_records_operation_timeouts() {
459 let builder = ClientBuilder::new()
460 .with_dns_timeout(Duration::from_secs(2))
461 .with_handshake_timeout(Duration::from_secs(7));
462 assert_eq!(builder.dns_timeout, Some(Duration::from_secs(2)));
463 assert_eq!(builder.handshake_timeout, Some(Duration::from_secs(7)));
464 }
465}