rustls/crypto/kx/
mod.rs

1use alloc::boxed::Box;
2use alloc::vec::Vec;
3use core::fmt::Debug;
4use core::ops::Deref;
5
6use zeroize::Zeroize;
7
8use crate::enums::ProtocolVersion;
9use crate::error::Error;
10
11pub mod ffdhe;
12use ffdhe::FfdheGroup;
13
14/// A supported key exchange group.
15///
16/// This type carries both configuration and implementation. Specifically,
17/// it has a TLS-level name expressed using the [`NamedGroup`] enum, and
18/// a function which produces a [`ActiveKeyExchange`].
19///
20/// Compare with [`NamedGroup`], which carries solely a protocol identifier.
21pub trait SupportedKxGroup: Send + Sync + Debug {
22    /// Start a key exchange.
23    ///
24    /// This will prepare an ephemeral secret key in the supported group, and a corresponding
25    /// public key. The key exchange can be completed by calling [`ActiveKeyExchange::complete()`]
26    /// or discarded.
27    ///
28    /// Most implementations will want to return the `StartedKeyExchange::Single(_)` variant.
29    /// Hybrid key exchange algorithms, which are constructed from two underlying algorithms,
30    /// may wish to return `StartedKeyExchange::Hybrid(_)` variant which additionally allows
31    /// one part of the key exchange to be completed separately.  See the documentation
32    /// on [`HybridKeyExchange`] for more detail.
33    ///
34    /// # Errors
35    ///
36    /// This can fail if the random source fails during ephemeral key generation.
37    fn start(&self) -> Result<StartedKeyExchange, Error>;
38
39    /// Start and complete a key exchange, in one operation.
40    ///
41    /// The default implementation for this calls `start()` and then calls
42    /// `complete()` on the result.  This is suitable for Diffie-Hellman-like
43    /// key exchange algorithms, where there is not a data dependency between
44    /// our key share (named "pub_key" in this API) and the peer's (`peer_pub_key`).
45    ///
46    /// If there is such a data dependency (like key encapsulation mechanisms), this
47    /// function should be implemented.
48    fn start_and_complete(&self, peer_pub_key: &[u8]) -> Result<CompletedKeyExchange, Error> {
49        let kx = self.start()?.into_single();
50
51        Ok(CompletedKeyExchange {
52            group: kx.group(),
53            pub_key: kx.pub_key().to_vec(),
54            secret: kx.complete(peer_pub_key)?,
55        })
56    }
57
58    /// FFDHE group the `SupportedKxGroup` operates in, if any.
59    ///
60    /// The default implementation returns `None`, so non-FFDHE groups (the
61    /// most common) do not need to do anything.
62    ///
63    /// FFDHE groups must implement this. [`ffdhe`] contains suitable values to return, for
64    /// example [`ffdhe::FFDHE2048`].
65    fn ffdhe_group(&self) -> Option<FfdheGroup<'static>> {
66        None
67    }
68
69    /// Named group the SupportedKxGroup operates in.
70    ///
71    /// If the `NamedGroup` enum does not have a name for the algorithm you are implementing,
72    /// you can use [`NamedGroup::Unknown`].
73    fn name(&self) -> NamedGroup;
74
75    /// Return `true` if this is backed by a FIPS-approved implementation.
76    fn fips(&self) -> bool {
77        false
78    }
79}
80
81/// Return value from [`SupportedKxGroup::start()`].
82#[non_exhaustive]
83pub enum StartedKeyExchange {
84    /// A single [`ActiveKeyExchange`].
85    Single(Box<dyn ActiveKeyExchange>),
86    /// A [`HybridKeyExchange`] that can potentially be split.
87    Hybrid(Box<dyn HybridKeyExchange>),
88}
89
90impl StartedKeyExchange {
91    /// Collapses this object into its underlying [`ActiveKeyExchange`].
92    ///
93    /// This removes the ability to do the hybrid key exchange optimization,
94    /// but still allows the key exchange as a whole to be completed.
95    pub fn into_single(self) -> Box<dyn ActiveKeyExchange> {
96        match self {
97            Self::Single(s) => s,
98            Self::Hybrid(h) => h.into_key_exchange(),
99        }
100    }
101
102    /// Accesses the [`HybridKeyExchange`], and checks it was also usable separately.
103    ///
104    /// Returns:
105    ///
106    /// - the [`HybridKeyExchange`]
107    /// - the stand-alone `SupportedKxGroup` for the hybrid's component group.
108    ///
109    /// This returns `None` for:
110    ///
111    /// - non-hybrid groups,
112    /// - if the hybrid component group is not present in `supported`
113    /// - if the hybrid component group is not usable with `version`
114    pub(crate) fn as_hybrid_checked(
115        &self,
116        supported: &[&'static dyn SupportedKxGroup],
117        version: ProtocolVersion,
118    ) -> Option<(&dyn HybridKeyExchange, &'static dyn SupportedKxGroup)> {
119        let Self::Hybrid(hybrid) = self else {
120            return None;
121        };
122
123        let component_group = hybrid.component().0;
124        if !component_group.usable_for_version(version) {
125            return None;
126        }
127
128        supported
129            .iter()
130            .find(|g| g.name() == component_group)
131            .copied()
132            .map(|g| (hybrid.as_ref(), g))
133    }
134}
135
136impl Deref for StartedKeyExchange {
137    type Target = dyn ActiveKeyExchange;
138
139    fn deref(&self) -> &Self::Target {
140        match self {
141            Self::Single(s) => s.as_ref(),
142            Self::Hybrid(h) => h.as_key_exchange(),
143        }
144    }
145}
146
147/// An in-progress key exchange originating from a [`SupportedKxGroup`].
148pub trait ActiveKeyExchange: Send + Sync {
149    /// Completes the key exchange, given the peer's public key.
150    ///
151    /// This method must return an error if `peer_pub_key` is invalid: either
152    /// misencoded, or an invalid public key (such as, but not limited to, being
153    /// in a small order subgroup).
154    ///
155    /// If the key exchange algorithm is FFDHE, the result must be left-padded with zeros,
156    /// as required by [RFC 8446](https://www.rfc-editor.org/rfc/rfc8446#section-7.4.1)
157    /// (see [`complete_for_tls_version()`](Self::complete_for_tls_version) for more details).
158    ///
159    /// The shared secret is returned as a [`SharedSecret`] which can be constructed
160    /// from a `&[u8]`.
161    ///
162    /// This consumes and so terminates the [`ActiveKeyExchange`].
163    fn complete(self: Box<Self>, peer_pub_key: &[u8]) -> Result<SharedSecret, Error>;
164
165    /// Completes the key exchange for the given TLS version, given the peer's public key.
166    ///
167    /// Note that finite-field Diffie–Hellman key exchange has different requirements for the derived
168    /// shared secret in TLS 1.2 and TLS 1.3 (ECDHE key exchange is the same in TLS 1.2 and TLS 1.3):
169    ///
170    /// In TLS 1.2, the calculated secret is required to be stripped of leading zeros
171    /// [(RFC 5246)](https://www.rfc-editor.org/rfc/rfc5246#section-8.1.2).
172    ///
173    /// In TLS 1.3, the calculated secret is required to be padded with leading zeros to be the same
174    /// byte-length as the group modulus [(RFC 8446)](https://www.rfc-editor.org/rfc/rfc8446#section-7.4.1).
175    ///
176    /// The default implementation of this method delegates to [`complete()`](Self::complete) assuming it is
177    /// implemented for TLS 1.3 (i.e., for FFDHE KX, removes padding as needed). Implementers of this trait
178    /// are encouraged to just implement [`complete()`](Self::complete) assuming TLS 1.3, and let the default
179    /// implementation of this method handle TLS 1.2-specific requirements.
180    ///
181    /// This method must return an error if `peer_pub_key` is invalid: either
182    /// misencoded, or an invalid public key (such as, but not limited to, being
183    /// in a small order subgroup).
184    ///
185    /// The shared secret is returned as a [`SharedSecret`] which can be constructed
186    /// from a `&[u8]`.
187    ///
188    /// This consumes and so terminates the [`ActiveKeyExchange`].
189    fn complete_for_tls_version(
190        self: Box<Self>,
191        peer_pub_key: &[u8],
192        tls_version: ProtocolVersion,
193    ) -> Result<SharedSecret, Error> {
194        if tls_version == ProtocolVersion::TLSv1_3 {
195            return self.complete(peer_pub_key);
196        }
197
198        let group = self.group();
199        let mut complete_res = self.complete(peer_pub_key)?;
200        if group.key_exchange_algorithm() == KeyExchangeAlgorithm::DHE {
201            complete_res.strip_leading_zeros();
202        }
203        Ok(complete_res)
204    }
205
206    /// Return the public key being used.
207    ///
208    /// For ECDHE, the encoding required is defined in
209    /// [RFC8446 section 4.2.8.2](https://www.rfc-editor.org/rfc/rfc8446#section-4.2.8.2).
210    ///
211    /// For FFDHE, the encoding required is defined in
212    /// [RFC8446 section 4.2.8.1](https://www.rfc-editor.org/rfc/rfc8446#section-4.2.8.1).
213    fn pub_key(&self) -> &[u8];
214
215    /// FFDHE group the `ActiveKeyExchange` is operating in.
216    ///
217    /// The default implementation returns `None`, so non-FFDHE groups (the
218    /// most common) do not need to do anything.
219    ///
220    /// FFDHE groups must implement this. [`ffdhe`] contains suitable values to return, for
221    /// example [`ffdhe::FFDHE2048`].
222    fn ffdhe_group(&self) -> Option<FfdheGroup<'static>> {
223        None
224    }
225
226    /// Return the group being used.
227    fn group(&self) -> NamedGroup;
228}
229
230/// An in-progress hybrid key exchange originating from a [`SupportedKxGroup`].
231///
232/// "Hybrid" means a key exchange algorithm which is constructed from two
233/// (or more) independent component algorithms. Usually one is post-quantum-secure,
234/// and the other is "classical".  See
235/// <https://datatracker.ietf.org/doc/draft-ietf-tls-hybrid-design/11/>
236///
237/// There is no requirement for a hybrid scheme (or any other!) to implement
238/// `HybridKeyExchange` if it is not desirable for it to be "split" like this.
239/// It only enables an optimization; described below.
240///
241/// # Background
242/// Rustls always sends a presumptive key share in its `ClientHello`, using
243/// (absent any other information) the first item in
244/// [`CryptoProvider::kx_groups`][super::CryptoProvider::kx_groups].
245/// If the server accepts the client's selection, it can complete the handshake
246/// using that key share.  If not, the server sends a `HelloRetryRequest` instructing
247/// the client to send a different key share instead.
248///
249/// This request costs an extra round trip, and wastes the key exchange computation
250/// (in [`SupportedKxGroup::start()`]) the client already did.  We would
251/// like to avoid those wastes if possible.
252///
253/// It is early days for post-quantum-secure hybrid key exchange deployment.
254/// This means (commonly) continuing to offer both the hybrid and classical
255/// key exchanges, so the handshake can be completed without a `HelloRetryRequest`
256/// for servers that support the offered hybrid or classical schemes.
257///
258/// Implementing `HybridKeyExchange` enables two optimizations:
259///
260/// 1. Sending both the hybrid and classical key shares in the `ClientHello`.
261///
262/// 2. Performing the classical key exchange setup only once.  This is important
263///    because the classical key exchange setup is relatively expensive.
264///    This optimization is permitted and described in
265///    <https://www.ietf.org/archive/id/draft-ietf-tls-hybrid-design-11.html#section-3.2>
266///
267/// Both of these only happen if the classical algorithm appears separately in
268/// the client's [`CryptoProvider::kx_groups`][super::CryptoProvider::kx_groups],
269/// and if the hybrid algorithm appears first in that list.
270///
271/// # How it works
272/// This function is only called by rustls for clients.  It is called when
273/// constructing the initial `ClientHello`.  rustls follows these steps:
274///
275/// 1. If the return value is `None`, nothing further happens.
276/// 2. If the given [`NamedGroup`] does not appear in
277///    [`CryptoProvider::kx_groups`][super::CryptoProvider::kx_groups], nothing further happens.
278/// 3. The given key share is added to the `ClientHello`, after the hybrid entry.
279///
280/// Then, one of three things may happen when the server replies to the `ClientHello`:
281///
282/// 1. The server sends a `HelloRetryRequest`.  Everything is thrown away and
283///    we start again.
284/// 2. The server agrees to our hybrid key exchange: rustls calls
285///    [`ActiveKeyExchange::complete()`] consuming `self`.
286/// 3. The server agrees to our classical key exchange: rustls calls
287///    [`HybridKeyExchange::complete_component()`] which
288///    discards the hybrid key data, and completes just the classical key exchange.
289pub trait HybridKeyExchange: ActiveKeyExchange {
290    /// Returns the [`NamedGroup`] and public key "share" for the component.
291    fn component(&self) -> (NamedGroup, &[u8]);
292
293    /// Completes the classical component of the key exchange, given the peer's public key.
294    ///
295    /// This method must return an error if `peer_pub_key` is invalid: either
296    /// misencoded, or an invalid public key (such as, but not limited to, being
297    /// in a small order subgroup).
298    ///
299    /// The shared secret is returned as a [`SharedSecret`] which can be constructed
300    /// from a `&[u8]`.
301    ///
302    /// See the documentation on [`HybridKeyExchange`] for explanation.
303    fn complete_component(self: Box<Self>, peer_pub_key: &[u8]) -> Result<SharedSecret, Error>;
304
305    /// Obtain the value as a `dyn ActiveKeyExchange`
306    fn as_key_exchange(&self) -> &(dyn ActiveKeyExchange + 'static);
307
308    /// Remove the ability to do hybrid key exchange on this object.
309    fn into_key_exchange(self: Box<Self>) -> Box<dyn ActiveKeyExchange>;
310}
311
312/// The result from [`SupportedKxGroup::start_and_complete()`].
313#[expect(clippy::exhaustive_structs)]
314pub struct CompletedKeyExchange {
315    /// Which group was used.
316    pub group: NamedGroup,
317
318    /// Our key share (sometimes a public key).
319    pub pub_key: Vec<u8>,
320
321    /// The computed shared secret.
322    pub secret: SharedSecret,
323}
324
325enum_builder! {
326    /// The `NamedGroup` TLS protocol enum.  Values in this enum are taken
327    /// from the various RFCs covering TLS, and are listed by IANA.
328    /// The `Unknown` item is used when processing unrecognized ordinals.
329    ///
330    /// This enum is used for recognizing key exchange groups advertised
331    /// by a peer during a TLS handshake. It is **not** a list of groups that
332    /// Rustls supports. The supported groups are determined via the
333    /// [`CryptoProvider`][crate::crypto::CryptoProvider] interface.
334    #[repr(u16)]
335    #[expect(non_camel_case_types)]
336    pub enum NamedGroup {
337        secp256r1 => 0x0017,
338        secp384r1 => 0x0018,
339        secp521r1 => 0x0019,
340        X25519 => 0x001d,
341        X448 => 0x001e,
342        /// <https://www.iana.org/go/rfc8734>
343        brainpoolP256r1tls13 => 0x001f,
344        /// <https://www.iana.org/go/rfc8734>
345        brainpoolP384r1tls13 => 0x0020,
346        /// <https://www.iana.org/go/rfc8734>
347        brainpoolP512r1tls13 => 0x0021,
348        /// <https://www.iana.org/go/rfc8998>
349        curveSM2 => 0x0029,
350        FFDHE2048 => 0x0100,
351        FFDHE3072 => 0x0101,
352        FFDHE4096 => 0x0102,
353        FFDHE6144 => 0x0103,
354        FFDHE8192 => 0x0104,
355        /// <https://datatracker.ietf.org/doc/draft-ietf-tls-mlkem/>
356        MLKEM512 => 0x0200,
357        /// <https://datatracker.ietf.org/doc/draft-ietf-tls-mlkem/>
358        MLKEM768 => 0x0201,
359        /// <https://datatracker.ietf.org/doc/draft-ietf-tls-mlkem/>
360        MLKEM1024 => 0x0202,
361        /// <https://datatracker.ietf.org/doc/draft-ietf-tls-ecdhe-mlkem/>
362        secp256r1MLKEM768 => 0x11eb,
363        /// <https://datatracker.ietf.org/doc/draft-ietf-tls-ecdhe-mlkem/>
364        X25519MLKEM768 => 0x11ec,
365        /// <https://datatracker.ietf.org/doc/draft-ietf-tls-ecdhe-mlkem/>
366        secp384r1MLKEM1024 => 0x11ed,
367    }
368}
369
370impl NamedGroup {
371    /// Return the key exchange algorithm associated with this `NamedGroup`
372    pub fn key_exchange_algorithm(self) -> KeyExchangeAlgorithm {
373        match u16::from(self) {
374            x if (0x100..0x200).contains(&x) => KeyExchangeAlgorithm::DHE,
375            _ => KeyExchangeAlgorithm::ECDHE,
376        }
377    }
378
379    /// Returns whether this `NamedGroup` is usable for the given protocol version.
380    pub fn usable_for_version(&self, version: ProtocolVersion) -> bool {
381        match version {
382            ProtocolVersion::TLSv1_3 => true,
383            _ => !matches!(
384                self,
385                Self::MLKEM512
386                    | Self::MLKEM768
387                    | Self::MLKEM1024
388                    | Self::X25519MLKEM768
389                    | Self::secp256r1MLKEM768
390                    | Self::secp384r1MLKEM1024
391                    | Self::brainpoolP256r1tls13
392                    | Self::brainpoolP384r1tls13
393                    | Self::brainpoolP512r1tls13
394                    | Self::curveSM2
395            ),
396        }
397    }
398}
399
400/// The result from [`ActiveKeyExchange::complete()`] or [`HybridKeyExchange::complete_component()`].
401pub struct SharedSecret {
402    buf: Vec<u8>,
403    offset: usize,
404}
405
406impl SharedSecret {
407    /// Returns the shared secret as a slice of bytes.
408    pub fn secret_bytes(&self) -> &[u8] {
409        &self.buf[self.offset..]
410    }
411
412    /// Removes leading zeros from `secret_bytes()` by adjusting the `offset`.
413    ///
414    /// This function does not re-allocate.
415    fn strip_leading_zeros(&mut self) {
416        let start = self
417            .secret_bytes()
418            .iter()
419            .enumerate()
420            .find(|(_i, x)| **x != 0)
421            .map(|(i, _x)| i)
422            .unwrap_or_else(|| self.secret_bytes().len());
423        self.offset += start;
424    }
425}
426
427impl Drop for SharedSecret {
428    fn drop(&mut self) {
429        self.buf.zeroize();
430    }
431}
432
433impl From<&[u8]> for SharedSecret {
434    fn from(source: &[u8]) -> Self {
435        Self {
436            buf: source.to_vec(),
437            offset: 0,
438        }
439    }
440}
441
442impl From<Vec<u8>> for SharedSecret {
443    fn from(buf: Vec<u8>) -> Self {
444        Self { buf, offset: 0 }
445    }
446}
447
448/// Describes supported key exchange mechanisms.
449#[derive(Clone, Copy, Debug, PartialEq)]
450#[non_exhaustive]
451pub enum KeyExchangeAlgorithm {
452    /// Diffie-Hellman Key exchange (with only known parameters as defined in [RFC 7919]).
453    ///
454    /// [RFC 7919]: https://datatracker.ietf.org/doc/html/rfc7919
455    DHE,
456    /// Key exchange performed via elliptic curve Diffie-Hellman.
457    ECDHE,
458}
459
460#[cfg(test)]
461mod tests {
462    use std::vec;
463
464    use super::{NamedGroup, SharedSecret};
465    use crate::msgs::enums::tests::test_enum16;
466
467    #[test]
468    fn test_shared_secret_strip_leading_zeros() {
469        let test_cases = [
470            (vec![0, 1], vec![1]),
471            (vec![1], vec![1]),
472            (vec![1, 0, 2], vec![1, 0, 2]),
473            (vec![0, 0, 1, 2], vec![1, 2]),
474            (vec![0, 0, 0], vec![]),
475            (vec![], vec![]),
476        ];
477        for (buf, expected) in test_cases {
478            let mut secret = SharedSecret::from(&buf[..]);
479            assert_eq!(secret.secret_bytes(), buf);
480            secret.strip_leading_zeros();
481            assert_eq!(secret.secret_bytes(), expected);
482        }
483    }
484
485    #[test]
486    fn test_enums() {
487        test_enum16::<NamedGroup>(NamedGroup::secp256r1, NamedGroup::FFDHE8192);
488    }
489}