s2n_quic_rustls/
client.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::{certificate, cipher_suite::default_crypto_provider, session::Session, Error};
5use core::convert::TryFrom;
6use rustls::{ClientConfig, ConfigBuilder, WantsVerifier};
7use s2n_codec::EncoderValue;
8use s2n_quic_core::{application::ServerName, crypto::tls};
9use std::sync::Arc;
10
11/// Create a QUIC client specific [rustls::ConfigBuilder].
12///
13/// Uses aws_lc_rs as the crypto provider and sets QUIC specific protocol versions.
14fn default_config_builder() -> Result<ConfigBuilder<ClientConfig, WantsVerifier>, rustls::Error> {
15    let tls13_cipher_suite_crypto_provider = default_crypto_provider()?;
16    ClientConfig::builder_with_provider(tls13_cipher_suite_crypto_provider.into())
17        .with_protocol_versions(crate::PROTOCOL_VERSIONS)
18}
19
20#[derive(Clone)]
21pub struct Client {
22    config: Arc<ClientConfig>,
23}
24
25impl Client {
26    /// Build a Client using a custom [rustls::ClientConfig]
27    ///
28    /// In addition to necessary configuration, the applications is responsible for correctly
29    /// setting:
30    /// - QUIC compliant application_protocol
31    /// - QUIC compliant TLS protocol version
32    /// - QUIC compliant ciphersuites
33    #[deprecated = "client and server builders should be used instead"]
34    pub fn new(config: ClientConfig) -> Self {
35        Self {
36            config: Arc::new(config),
37        }
38    }
39
40    pub fn builder() -> Builder {
41        Builder::new()
42    }
43}
44
45impl Default for Client {
46    fn default() -> Self {
47        // TODO this will currently panic since there is no default root cert
48        Self::builder()
49            .build()
50            .expect("could not create default client")
51    }
52}
53
54// TODO this should be removed after removing deprecated re-exports
55impl From<ClientConfig> for Client {
56    fn from(config: ClientConfig) -> Self {
57        Self::from(Arc::new(config))
58    }
59}
60
61// TODO this should be removed after removing deprecated re-exports
62impl From<Arc<ClientConfig>> for Client {
63    fn from(config: Arc<ClientConfig>) -> Self {
64        Self { config }
65    }
66}
67
68impl tls::Endpoint for Client {
69    type Session = Session;
70
71    fn new_server_session<Params: EncoderValue>(
72        &mut self,
73        _transport_parameters: &Params,
74        _connection_info: tls::ConnectionInfo,
75    ) -> Self::Session {
76        panic!("cannot create a server session from a client config");
77    }
78
79    fn new_client_session<Params: EncoderValue>(
80        &mut self,
81        transport_parameters: &Params,
82        server_name: ServerName,
83    ) -> Self::Session {
84        //= https://www.rfc-editor.org/rfc/rfc9001#section-8.2
85        //# Endpoints MUST send the quic_transport_parameters extension;
86        let transport_parameters = transport_parameters.encode_to_vec();
87
88        let rustls_server_name = rustls::pki_types::ServerName::try_from(server_name.to_string())
89            .expect("invalid server name");
90
91        let session = rustls::quic::ClientConnection::new(
92            self.config.clone(),
93            crate::QUIC_VERSION,
94            rustls_server_name,
95            transport_parameters,
96        )
97        .expect("could not create rustls client session");
98
99        Session::new(session.into(), Some(server_name))
100    }
101
102    fn max_tag_length(&self) -> usize {
103        s2n_quic_crypto::MAX_TAG_LEN
104    }
105}
106
107pub struct Builder {
108    cert_store: rustls::RootCertStore,
109    application_protocols: Vec<Vec<u8>>,
110    key_log: Option<Arc<dyn rustls::KeyLog>>,
111}
112
113impl Default for Builder {
114    fn default() -> Self {
115        Self::new()
116    }
117}
118
119impl Builder {
120    pub fn new() -> Self {
121        Self {
122            cert_store: rustls::RootCertStore::empty(),
123            application_protocols: vec![b"h3".to_vec()],
124            key_log: None,
125        }
126    }
127
128    pub fn with_certificate<C: certificate::IntoCertificate>(
129        mut self,
130        certificate: C,
131    ) -> Result<Self, Error> {
132        let certificates = certificate.into_certificate()?;
133        let root_certificate = certificates.0.first().ok_or_else(|| {
134            rustls::Error::General("Certificate chain needs to have at least one entry".to_string())
135        })?;
136        self.cert_store
137            .add(root_certificate.to_owned())
138            .map_err(|err| rustls::Error::General(err.to_string()))?;
139        Ok(self)
140    }
141
142    pub fn with_max_cert_chain_depth(self, len: u16) -> Result<Self, Error> {
143        // TODO is there a way to configure this?
144        let _ = len;
145        Ok(self)
146    }
147
148    pub fn with_application_protocols<P: Iterator<Item = I>, I: AsRef<[u8]>>(
149        mut self,
150        protocols: P,
151    ) -> Result<Self, rustls::Error> {
152        self.application_protocols = protocols.map(|p| p.as_ref().to_vec()).collect();
153        Ok(self)
154    }
155
156    pub fn with_key_logging(mut self) -> Result<Self, Error> {
157        self.key_log = Some(Arc::new(rustls::KeyLogFile::new()));
158        Ok(self)
159    }
160
161    pub fn build(self) -> Result<Client, Error> {
162        // TODO load system root store?
163        if self.cert_store.is_empty() {
164            //= https://www.rfc-editor.org/rfc/rfc9001#section-4.4
165            //# A client MUST authenticate the identity of the server.
166            return Err(
167                rustls::Error::General("missing trusted root certificate(s)".to_string()).into(),
168            );
169        }
170
171        let mut config = default_config_builder()?
172            .with_root_certificates(self.cert_store)
173            .with_no_client_auth();
174
175        config.max_fragment_size = None;
176        config.alpn_protocols = self.application_protocols;
177
178        if let Some(key_log) = self.key_log {
179            config.key_log = key_log;
180        }
181
182        #[allow(deprecated)]
183        Ok(Client::new(config))
184    }
185}