Skip to main content

compio_quic/
builder.rs

1use std::{future::Future, io, sync::Arc};
2
3use compio_net::ToSocketAddrsAsync;
4use quinn_proto::{
5    ClientConfig, ServerConfig,
6    crypto::rustls::{QuicClientConfig, QuicServerConfig},
7};
8
9use crate::{Endpoint, endpoint};
10
11/// Helper to construct an [`Endpoint`] for use with outgoing connections only.
12///
13/// To get one, call `new_with_xxx` methods.
14///
15/// [builder]: https://rust-unofficial.github.io/patterns/patterns/creational/builder.html
16#[derive(Debug)]
17pub struct ClientBuilder<T>(T);
18
19impl ClientBuilder<rustls::RootCertStore> {
20    /// Create a builder with an empty [`rustls::RootCertStore`].
21    pub fn new_with_empty_roots() -> Self {
22        ClientBuilder(rustls::RootCertStore::empty())
23    }
24
25    /// Create a builder with [`rustls_native_certs`].
26    #[cfg(feature = "native-certs")]
27    pub fn new_with_native_certs() -> io::Result<Self> {
28        let mut roots = rustls::RootCertStore::empty();
29        let mut certs = rustls_native_certs::load_native_certs();
30        if certs.certs.is_empty() {
31            return Err(io::Error::other(
32                certs
33                    .errors
34                    .pop()
35                    .expect("certs and errors should not be both empty"),
36            ));
37        }
38        roots.add_parsable_certificates(certs.certs);
39        Ok(ClientBuilder(roots))
40    }
41
42    /// Create a builder with [`webpki_roots`].
43    #[cfg(feature = "webpki-roots")]
44    pub fn new_with_webpki_roots() -> Self {
45        let roots =
46            rustls::RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
47        ClientBuilder(roots)
48    }
49
50    /// Add a custom certificate.
51    pub fn with_custom_certificate(
52        mut self,
53        der: rustls::pki_types::CertificateDer,
54    ) -> Result<Self, rustls::Error> {
55        self.0.add(der)?;
56        Ok(self)
57    }
58
59    /// Don't configure revocation.
60    pub fn with_no_crls(self) -> ClientBuilder<rustls::ClientConfig> {
61        ClientBuilder::new_with_root_certificates(self.0)
62    }
63
64    /// Verify the revocation state of presented client certificates against the
65    /// provided certificate revocation lists (CRLs).
66    pub fn with_crls(
67        self,
68        crls: impl IntoIterator<Item = rustls::pki_types::CertificateRevocationListDer<'static>>,
69    ) -> Result<ClientBuilder<rustls::ClientConfig>, rustls::client::VerifierBuilderError> {
70        let verifier = rustls::client::WebPkiServerVerifier::builder(Arc::new(self.0))
71            .with_crls(crls)
72            .build()?;
73        Ok(ClientBuilder::new_with_webpki_verifier(verifier))
74    }
75}
76
77impl ClientBuilder<rustls::ClientConfig> {
78    /// Create a builder with the provided [`rustls::ClientConfig`].
79    pub fn new_with_rustls_client_config(
80        client_config: rustls::ClientConfig,
81    ) -> ClientBuilder<rustls::ClientConfig> {
82        ClientBuilder(client_config)
83    }
84
85    /// Do not verify the server's certificate. It is vulnerable to MITM
86    /// attacks, but convenient for testing.
87    pub fn new_with_no_server_verification() -> ClientBuilder<rustls::ClientConfig> {
88        ClientBuilder(
89            rustls::ClientConfig::builder_with_protocol_versions(&[&rustls::version::TLS13])
90                .dangerous()
91                .with_custom_certificate_verifier(Arc::new(verifier::SkipServerVerification::new()))
92                .with_no_client_auth(),
93        )
94    }
95
96    /// Create a builder with [`rustls_platform_verifier`].
97    #[cfg(feature = "platform-verifier")]
98    pub fn new_with_platform_verifier() -> Result<ClientBuilder<rustls::ClientConfig>, rustls::Error>
99    {
100        use rustls_platform_verifier::BuilderVerifierExt;
101
102        Ok(ClientBuilder(
103            rustls::ClientConfig::builder_with_protocol_versions(&[&rustls::version::TLS13])
104                .with_platform_verifier()?
105                .with_no_client_auth(),
106        ))
107    }
108
109    /// Create a builder with the provided [`rustls::RootCertStore`].
110    pub fn new_with_root_certificates(
111        roots: rustls::RootCertStore,
112    ) -> ClientBuilder<rustls::ClientConfig> {
113        ClientBuilder(
114            rustls::ClientConfig::builder_with_protocol_versions(&[&rustls::version::TLS13])
115                .with_root_certificates(roots)
116                .with_no_client_auth(),
117        )
118    }
119
120    /// Create a builder with a custom [`rustls::client::WebPkiServerVerifier`].
121    pub fn new_with_webpki_verifier(
122        verifier: Arc<rustls::client::WebPkiServerVerifier>,
123    ) -> ClientBuilder<rustls::ClientConfig> {
124        ClientBuilder(
125            rustls::ClientConfig::builder_with_protocol_versions(&[&rustls::version::TLS13])
126                .with_webpki_verifier(verifier)
127                .with_no_client_auth(),
128        )
129    }
130
131    /// Set the ALPN protocols to use.
132    pub fn with_alpn_protocols(mut self, protocols: &[&str]) -> Self {
133        self.0.alpn_protocols = protocols.iter().map(|p| p.as_bytes().to_vec()).collect();
134        self
135    }
136
137    /// Logging key material to a file for debugging. The file's name is given
138    /// by the `SSLKEYLOGFILE` environment variable.
139    ///
140    /// If `SSLKEYLOGFILE` is not set, or such a file cannot be opened or cannot
141    /// be written, this does nothing.
142    pub fn with_key_log(mut self) -> Self {
143        self.0.key_log = Arc::new(rustls::KeyLogFile::new());
144        self
145    }
146
147    /// Build a [`ClientConfig`].
148    pub fn build(mut self) -> ClientConfig {
149        self.0.enable_early_data = true;
150        ClientConfig::new(Arc::new(
151            QuicClientConfig::try_from(self.0).expect("should support TLS13_AES_128_GCM_SHA256"),
152        ))
153    }
154
155    /// Create a new [`Endpoint`].
156    ///
157    /// See [`Endpoint::client`] for more information.
158    #[track_caller]
159    pub fn bind(self, addr: impl ToSocketAddrsAsync) -> impl Future<Output = io::Result<Endpoint>> {
160        let meta = endpoint::worker_meta();
161        async move {
162            let mut endpoint = Endpoint::client_at(addr, meta).await?;
163            endpoint.default_client_config = Some(self.build());
164            Ok(endpoint)
165        }
166    }
167}
168
169/// Helper to construct an [`Endpoint`] for use with incoming connections.
170///
171/// To get one, call `new_with_xxx` methods.
172///
173/// [builder]: https://rust-unofficial.github.io/patterns/patterns/creational/builder.html
174#[derive(Debug)]
175pub struct ServerBuilder<T>(T);
176
177impl ServerBuilder<rustls::ServerConfig> {
178    /// Create a builder with the provided [`rustls::ServerConfig`].
179    pub fn new_with_rustls_server_config(server_config: rustls::ServerConfig) -> Self {
180        Self(server_config)
181    }
182
183    /// Create a builder with a single certificate chain and matching private
184    /// key. Using this method gets the same result as calling
185    /// [`ServerConfig::with_single_cert`].
186    pub fn new_with_single_cert(
187        cert_chain: Vec<rustls::pki_types::CertificateDer<'static>>,
188        key_der: rustls::pki_types::PrivateKeyDer<'static>,
189    ) -> Result<Self, rustls::Error> {
190        let server_config =
191            rustls::ServerConfig::builder_with_protocol_versions(&[&rustls::version::TLS13])
192                .with_no_client_auth()
193                .with_single_cert(cert_chain, key_der)?;
194        Ok(Self::new_with_rustls_server_config(server_config))
195    }
196
197    /// Set the ALPN protocols to use.
198    pub fn with_alpn_protocols(mut self, protocols: &[&str]) -> Self {
199        self.0.alpn_protocols = protocols.iter().map(|p| p.as_bytes().to_vec()).collect();
200        self
201    }
202
203    /// Logging key material to a file for debugging. The file's name is given
204    /// by the `SSLKEYLOGFILE` environment variable.
205    ///
206    /// If `SSLKEYLOGFILE` is not set, or such a file cannot be opened or cannot
207    /// be written, this does nothing.
208    pub fn with_key_log(mut self) -> Self {
209        self.0.key_log = Arc::new(rustls::KeyLogFile::new());
210        self
211    }
212
213    /// Build a [`ServerConfig`].
214    pub fn build(mut self) -> ServerConfig {
215        self.0.max_early_data_size = u32::MAX;
216        ServerConfig::with_crypto(Arc::new(
217            QuicServerConfig::try_from(self.0).expect("should support TLS13_AES_128_GCM_SHA256"),
218        ))
219    }
220
221    /// Create a new [`Endpoint`].
222    ///
223    /// See [`Endpoint::server`] for more information.
224    #[track_caller]
225    pub fn bind(self, addr: impl ToSocketAddrsAsync) -> impl Future<Output = io::Result<Endpoint>> {
226        Endpoint::server_at(addr, self.build(), endpoint::worker_meta())
227    }
228}
229
230mod verifier {
231    use rustls::{
232        DigitallySignedStruct, Error, SignatureScheme,
233        client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
234        crypto::WebPkiSupportedAlgorithms,
235        pki_types::{CertificateDer, ServerName, UnixTime},
236    };
237
238    #[derive(Debug)]
239    pub struct SkipServerVerification(WebPkiSupportedAlgorithms);
240
241    impl SkipServerVerification {
242        pub fn new() -> Self {
243            Self(
244                rustls::crypto::CryptoProvider::get_default()
245                    .map(|provider| provider.signature_verification_algorithms)
246                    .unwrap_or_else(|| {
247                        #[cfg(feature = "ring")]
248                        use rustls::crypto::ring::default_provider;
249                        default_provider().signature_verification_algorithms
250                    }),
251            )
252        }
253    }
254
255    impl ServerCertVerifier for SkipServerVerification {
256        fn verify_server_cert(
257            &self,
258            _end_entity: &CertificateDer<'_>,
259            _intermediates: &[CertificateDer<'_>],
260            _server_name: &ServerName<'_>,
261            _ocsp: &[u8],
262            _now: UnixTime,
263        ) -> Result<ServerCertVerified, Error> {
264            Ok(ServerCertVerified::assertion())
265        }
266
267        fn verify_tls12_signature(
268            &self,
269            message: &[u8],
270            cert: &CertificateDer<'_>,
271            dss: &DigitallySignedStruct,
272        ) -> Result<HandshakeSignatureValid, Error> {
273            rustls::crypto::verify_tls12_signature(message, cert, dss, &self.0)
274        }
275
276        fn verify_tls13_signature(
277            &self,
278            message: &[u8],
279            cert: &CertificateDer<'_>,
280            dss: &DigitallySignedStruct,
281        ) -> Result<HandshakeSignatureValid, Error> {
282            rustls::crypto::verify_tls13_signature(message, cert, dss, &self.0)
283        }
284
285        fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
286            self.0.supported_schemes()
287        }
288    }
289}