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}