s2n_quic_tls/
client.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::{
5    certificate::{IntoCertificate, IntoPrivateKey},
6    keylog::KeyLogHandle,
7    params::Params,
8    session::Session,
9    ConfigLoader,
10};
11use s2n_codec::EncoderValue;
12use s2n_quic_core::{application::ServerName, crypto::tls, endpoint};
13use s2n_tls::{
14    callbacks::VerifyHostNameCallback,
15    config::{self, Config},
16    enums::ClientAuthType,
17    error::Error,
18};
19use std::sync::Arc;
20
21pub struct Client<L: ConfigLoader = Config> {
22    loader: L,
23    #[allow(dead_code)] // we need to hold on to the handle to ensure it is cleaned up correctly
24    keylog: Option<KeyLogHandle>,
25    params: Params,
26}
27
28impl Client {
29    pub fn builder() -> Builder {
30        Builder::default()
31    }
32}
33
34impl<L: ConfigLoader> Client<L> {
35    /// Creates a [`Client`] from a [`ConfigLoader`]
36    ///
37    /// The caller is responsible for building the `Config`
38    /// correctly for QUIC settings. This includes:
39    /// * setting a security policy that supports TLS 1.3
40    /// * enabling QUIC support
41    /// * setting at least one application protocol
42    pub fn from_loader(loader: L) -> Self {
43        Self {
44            loader,
45            keylog: None,
46            params: Default::default(),
47        }
48    }
49}
50
51impl Default for Client {
52    fn default() -> Self {
53        Self::builder()
54            .build()
55            .expect("could not create a default client")
56    }
57}
58
59impl<L: ConfigLoader> ConfigLoader for Client<L> {
60    #[inline]
61    fn load(&mut self, cx: crate::ConnectionContext) -> s2n_tls::config::Config {
62        self.loader.load(cx)
63    }
64}
65
66pub struct Builder {
67    config: config::Builder,
68    keylog: Option<KeyLogHandle>,
69}
70
71impl Default for Builder {
72    fn default() -> Self {
73        let mut config = config::Builder::default();
74        config.enable_quic().unwrap();
75        // https://github.com/aws/s2n-tls/blob/main/docs/USAGE-GUIDE.md#s2n_config_set_cipher_preferences
76        config.set_security_policy(crate::DEFAULT_POLICY).unwrap();
77        config.set_application_protocol_preference([b"h3"]).unwrap();
78
79        Self {
80            config,
81            keylog: None,
82        }
83    }
84}
85
86impl Builder {
87    pub fn config_mut(&mut self) -> &mut s2n_tls::config::Builder {
88        &mut self.config
89    }
90
91    pub fn with_application_protocols<P: IntoIterator<Item = I>, I: AsRef<[u8]>>(
92        mut self,
93        protocols: P,
94    ) -> Result<Self, Error> {
95        self.config.set_application_protocol_preference(protocols)?;
96        Ok(self)
97    }
98
99    pub fn with_certificate<C: IntoCertificate>(mut self, certificate: C) -> Result<Self, Error> {
100        let certificate = certificate.into_certificate()?;
101        let certificate = certificate
102            .0
103            .as_pem()
104            .expect("pem is currently the only certificate format supported");
105        self.config.trust_pem(certificate)?;
106        Ok(self)
107    }
108
109    /// Clears the default trust store for this client
110    ///
111    /// By default, the trust store is initialized with common
112    /// trust store locations for the host operating system.
113    /// By invoking this method, the trust store will be cleared.
114    ///
115    /// Note that call ordering matters. The caller should call this
116    /// method before making any calls to `with_trust_client_certificate_signed_by()`.
117    /// Calling this method after a method that modifies the trust store will clear it.
118    pub fn with_empty_trust_store(mut self) -> Result<Self, Error> {
119        self.config.wipe_trust_store()?;
120        Ok(self)
121    }
122
123    /// Add the cert and key to the key store.
124    ///
125    /// This must be set when the server requires client authentication (mutual TLS).
126    /// The client will offer the certificate to the server when it is requested
127    /// as part of the TLS handshake.
128    pub fn with_client_identity<C: IntoCertificate, PK: IntoPrivateKey>(
129        mut self,
130        certificate: C,
131        private_key: PK,
132    ) -> Result<Self, Error> {
133        let certificate = certificate.into_certificate()?;
134        let private_key = private_key.into_private_key()?;
135        self.config.load_pem(
136            certificate
137                .0
138                .as_pem()
139                .expect("pem is currently the only certificate format supported"),
140            private_key
141                .0
142                .as_pem()
143                .expect("pem is currently the only certificate format supported"),
144        )?;
145        self.config.set_client_auth_type(ClientAuthType::Required)?;
146        Ok(self)
147    }
148
149    /// Set the host name verification callback.
150    ///
151    /// This will be invoked when a server certificate is presented during a TLS
152    /// handshake. If this function is invoked, the default server name validation
153    /// logic is disabled; this should only be used in very specific cases where normal
154    /// TLS hostname validation is not appropriate.
155    pub fn with_verify_host_name_callback<T: 'static + VerifyHostNameCallback>(
156        mut self,
157        handler: T,
158    ) -> Result<Self, Error> {
159        self.config.set_verify_host_callback(handler)?;
160        Ok(self)
161    }
162
163    pub fn with_max_cert_chain_depth(mut self, len: u16) -> Result<Self, Error> {
164        self.config.set_max_cert_chain_depth(len)?;
165        Ok(self)
166    }
167
168    pub fn with_key_logging(mut self) -> Result<Self, Error> {
169        use crate::keylog::KeyLog;
170
171        self.keylog = KeyLog::try_open();
172
173        unsafe {
174            // Safety: the KeyLog is stored on `self` to ensure it outlives `config`
175            if let Some(keylog) = self.keylog.as_ref() {
176                self.config
177                    .set_key_log_callback(Some(KeyLog::callback), Arc::as_ptr(keylog) as *mut _)?;
178            } else {
179                // disable key logging if it failed to create a file
180                self.config
181                    .set_key_log_callback(None, core::ptr::null_mut())?;
182            }
183        }
184
185        Ok(self)
186    }
187
188    pub fn build(self) -> Result<Client, Error> {
189        #[cfg(feature = "fips")]
190        assert!(s2n_tls::init::fips_mode()?.is_enabled());
191
192        Ok(Client {
193            loader: self.config.build()?,
194            keylog: self.keylog,
195            params: Default::default(),
196        })
197    }
198}
199
200impl<L: ConfigLoader> tls::Endpoint for Client<L> {
201    type Session = Session;
202
203    fn new_server_session<Params: EncoderValue>(&mut self, _params: &Params) -> Self::Session {
204        panic!("cannot create a server session from a client config");
205    }
206
207    fn new_client_session<Params: EncoderValue>(
208        &mut self,
209        params: &Params,
210        server_name: ServerName,
211    ) -> Self::Session {
212        let config = self.loader.load(crate::ConnectionContext {
213            server_name: Some(&server_name),
214        });
215        self.params.with(params, |params| {
216            Session::new(endpoint::Type::Client, config, params, Some(server_name)).unwrap()
217        })
218    }
219
220    fn max_tag_length(&self) -> usize {
221        s2n_quic_crypto::MAX_TAG_LEN
222    }
223}