rustls/crypto/kx/mod.rs
1use alloc::boxed::Box;
2use alloc::vec::Vec;
3use core::fmt::Debug;
4use core::ops::Deref;
5
6use pki_types::FipsStatus;
7use zeroize::Zeroize;
8
9use crate::enums::ProtocolVersion;
10use crate::error::{Error, PeerMisbehaved};
11
12pub mod ffdhe;
13use ffdhe::FfdheGroup;
14
15/// A generalization of hybrid key exchange.
16#[expect(clippy::exhaustive_structs)]
17#[derive(Debug)]
18pub struct Hybrid {
19 /// Classical key exchange component.
20 pub classical: &'static dyn SupportedKxGroup,
21 /// Post-quantum key exchange component.
22 pub post_quantum: &'static dyn SupportedKxGroup,
23 /// TLS NamedGroup for this hybrid key exchange.
24 pub name: NamedGroup,
25 /// Layout of the hybrid key exchange.
26 pub layout: HybridLayout,
27}
28
29impl SupportedKxGroup for Hybrid {
30 fn start(&self) -> Result<StartedKeyExchange, Error> {
31 let classical = self.classical.start()?.into_single();
32 let post_quantum = self.post_quantum.start()?.into_single();
33
34 let combined_pub_key = self
35 .layout
36 .concat(post_quantum.pub_key(), classical.pub_key());
37
38 Ok(StartedKeyExchange::Hybrid(Box::new(ActiveHybrid {
39 classical,
40 post_quantum,
41 name: self.name,
42 layout: self.layout,
43 combined_pub_key,
44 })))
45 }
46
47 fn start_and_complete(&self, client_share: &[u8]) -> Result<CompletedKeyExchange, Error> {
48 let (post_quantum_share, classical_share) = self
49 .layout
50 .split_received_client_share(client_share)
51 .ok_or(PeerMisbehaved::InvalidKeyShare)?;
52
53 let cl = self
54 .classical
55 .start_and_complete(classical_share)?;
56 let pq = self
57 .post_quantum
58 .start_and_complete(post_quantum_share)?;
59
60 let combined_pub_key = self
61 .layout
62 .concat(&pq.pub_key, &cl.pub_key);
63 let secret = self
64 .layout
65 .concat(pq.secret.secret_bytes(), cl.secret.secret_bytes());
66
67 Ok(CompletedKeyExchange {
68 group: self.name,
69 pub_key: combined_pub_key,
70 secret: SharedSecret::from(secret),
71 })
72 }
73
74 fn name(&self) -> NamedGroup {
75 self.name
76 }
77
78 fn fips(&self) -> FipsStatus {
79 // Behold! The Night Mare: SP800-56C rev 2:
80 //
81 // "In addition to the currently approved techniques for the generation of the
82 // shared secret Z as specified in SP 800-56A and SP 800-56B, this Recommendation
83 // permits the use of a "hybrid" shared secret of the form Z′ = Z || T, a
84 // concatenation consisting of a "standard" shared secret Z that was generated
85 // during the execution of a key-establishment scheme (as currently specified in
86 // [SP 800-56A] or [SP 800-56B])"
87 //
88 // NIST plan to adjust this and allow both orders: see
89 // <https://csrc.nist.gov/pubs/sp/800/227/ipd> (Jan 2025) lines 1070-1080.
90 //
91 // But, for now, we follow the SP800-56C logic: the element appearing first is the
92 // one that controls approval.
93 match self.layout.post_quantum_first {
94 true => self.post_quantum.fips(),
95 false => self.classical.fips(),
96 }
97 }
98}
99
100struct ActiveHybrid {
101 classical: Box<dyn ActiveKeyExchange>,
102 post_quantum: Box<dyn ActiveKeyExchange>,
103 name: NamedGroup,
104 layout: HybridLayout,
105 combined_pub_key: Vec<u8>,
106}
107
108impl ActiveKeyExchange for ActiveHybrid {
109 fn complete(self: Box<Self>, peer_pub_key: &[u8]) -> Result<SharedSecret, Error> {
110 let (post_quantum_share, classical_share) = self
111 .layout
112 .split_received_server_share(peer_pub_key)
113 .ok_or(PeerMisbehaved::InvalidKeyShare)?;
114
115 let cl = self
116 .classical
117 .complete(classical_share)?;
118 let pq = self
119 .post_quantum
120 .complete(post_quantum_share)?;
121
122 let secret = self
123 .layout
124 .concat(pq.secret_bytes(), cl.secret_bytes());
125 Ok(SharedSecret::from(secret))
126 }
127
128 fn pub_key(&self) -> &[u8] {
129 &self.combined_pub_key
130 }
131
132 fn group(&self) -> NamedGroup {
133 self.name
134 }
135}
136
137impl HybridKeyExchange for ActiveHybrid {
138 fn component(&self) -> (NamedGroup, &[u8]) {
139 (self.classical.group(), self.classical.pub_key())
140 }
141
142 fn complete_component(self: Box<Self>, peer_pub_key: &[u8]) -> Result<SharedSecret, Error> {
143 self.classical.complete(peer_pub_key)
144 }
145
146 fn into_key_exchange(self: Box<Self>) -> Box<dyn ActiveKeyExchange> {
147 self
148 }
149
150 fn as_key_exchange(&self) -> &(dyn ActiveKeyExchange + 'static) {
151 self
152 }
153}
154
155/// Layout of a hybrid key exchange's key shares and secrets.
156#[expect(clippy::exhaustive_structs)]
157#[derive(Clone, Copy, Debug)]
158pub struct HybridLayout {
159 /// Length of classical key share.
160 pub classical_share_len: usize,
161
162 /// Length of post-quantum key share sent by client
163 pub post_quantum_client_share_len: usize,
164
165 /// Length of post-quantum key share sent by server
166 pub post_quantum_server_share_len: usize,
167
168 /// Whether the post-quantum element comes first in shares and secrets.
169 ///
170 /// For dismal and unprincipled reasons, SECP256R1MLKEM768 has the
171 /// classical element first, while X25519MLKEM768 has it second.
172 pub post_quantum_first: bool,
173}
174
175impl HybridLayout {
176 fn split_received_client_share<'a>(&self, share: &'a [u8]) -> Option<(&'a [u8], &'a [u8])> {
177 self.split(share, self.post_quantum_client_share_len)
178 }
179
180 fn split_received_server_share<'a>(&self, share: &'a [u8]) -> Option<(&'a [u8], &'a [u8])> {
181 self.split(share, self.post_quantum_server_share_len)
182 }
183
184 /// Return the PQ and classical component of a key share.
185 fn split<'a>(
186 &self,
187 share: &'a [u8],
188 post_quantum_share_len: usize,
189 ) -> Option<(&'a [u8], &'a [u8])> {
190 if share.len() != self.classical_share_len + post_quantum_share_len {
191 return None;
192 }
193
194 Some(match self.post_quantum_first {
195 true => {
196 let (first_share, second_share) = share.split_at(post_quantum_share_len);
197 (first_share, second_share)
198 }
199 false => {
200 let (first_share, second_share) = share.split_at(self.classical_share_len);
201 (second_share, first_share)
202 }
203 })
204 }
205
206 fn concat(&self, post_quantum: &[u8], classical: &[u8]) -> Vec<u8> {
207 match self.post_quantum_first {
208 true => [post_quantum, classical].concat(),
209 false => [classical, post_quantum].concat(),
210 }
211 }
212}
213
214/// A supported key exchange group.
215///
216/// This type carries both configuration and implementation. Specifically,
217/// it has a TLS-level name expressed using the [`NamedGroup`] enum, and
218/// a function which produces a [`ActiveKeyExchange`].
219///
220/// Compare with [`NamedGroup`], which carries solely a protocol identifier.
221pub trait SupportedKxGroup: Send + Sync + Debug {
222 /// Start a key exchange.
223 ///
224 /// This will prepare an ephemeral secret key in the supported group, and a corresponding
225 /// public key. The key exchange can be completed by calling [`ActiveKeyExchange::complete()`]
226 /// or discarded.
227 ///
228 /// Most implementations will want to return the `StartedKeyExchange::Single(_)` variant.
229 /// Hybrid key exchange algorithms, which are constructed from two underlying algorithms,
230 /// may wish to return `StartedKeyExchange::Hybrid(_)` variant which additionally allows
231 /// one part of the key exchange to be completed separately. See the documentation
232 /// on [`HybridKeyExchange`] for more detail.
233 ///
234 /// # Errors
235 ///
236 /// This can fail if the random source fails during ephemeral key generation.
237 fn start(&self) -> Result<StartedKeyExchange, Error>;
238
239 /// Start and complete a key exchange, in one operation.
240 ///
241 /// The default implementation for this calls `start()` and then calls
242 /// `complete()` on the result. This is suitable for Diffie-Hellman-like
243 /// key exchange algorithms, where there is not a data dependency between
244 /// our key share (named "pub_key" in this API) and the peer's (`peer_pub_key`).
245 ///
246 /// If there is such a data dependency (like key encapsulation mechanisms), this
247 /// function should be implemented.
248 fn start_and_complete(&self, peer_pub_key: &[u8]) -> Result<CompletedKeyExchange, Error> {
249 let kx = self.start()?.into_single();
250
251 Ok(CompletedKeyExchange {
252 group: kx.group(),
253 pub_key: kx.pub_key().to_vec(),
254 secret: kx.complete(peer_pub_key)?,
255 })
256 }
257
258 /// FFDHE group the `SupportedKxGroup` operates in, if any.
259 ///
260 /// The default implementation returns `None`, so non-FFDHE groups (the
261 /// most common) do not need to do anything.
262 ///
263 /// FFDHE groups must implement this. [`ffdhe`] contains suitable values to return, for
264 /// example [`ffdhe::FFDHE2048`].
265 fn ffdhe_group(&self) -> Option<FfdheGroup<'static>> {
266 None
267 }
268
269 /// Named group the SupportedKxGroup operates in.
270 ///
271 /// If the `NamedGroup` enum does not have a name for the algorithm you are implementing,
272 /// you can use [`NamedGroup::Unknown`].
273 fn name(&self) -> NamedGroup;
274
275 /// Return `true` if this is backed by a FIPS-approved implementation.
276 fn fips(&self) -> FipsStatus {
277 FipsStatus::Unvalidated
278 }
279}
280
281/// Return value from [`SupportedKxGroup::start()`].
282#[non_exhaustive]
283pub enum StartedKeyExchange {
284 /// A single [`ActiveKeyExchange`].
285 Single(Box<dyn ActiveKeyExchange>),
286 /// A [`HybridKeyExchange`] that can potentially be split.
287 Hybrid(Box<dyn HybridKeyExchange>),
288}
289
290impl StartedKeyExchange {
291 /// Collapses this object into its underlying [`ActiveKeyExchange`].
292 ///
293 /// This removes the ability to do the hybrid key exchange optimization,
294 /// but still allows the key exchange as a whole to be completed.
295 pub fn into_single(self) -> Box<dyn ActiveKeyExchange> {
296 match self {
297 Self::Single(s) => s,
298 Self::Hybrid(h) => h.into_key_exchange(),
299 }
300 }
301
302 /// Accesses the [`HybridKeyExchange`], and checks it was also usable separately.
303 ///
304 /// Returns:
305 ///
306 /// - the [`HybridKeyExchange`]
307 /// - the stand-alone `SupportedKxGroup` for the hybrid's component group.
308 ///
309 /// This returns `None` for:
310 ///
311 /// - non-hybrid groups,
312 /// - if the hybrid component group is not present in `supported`
313 /// - if the hybrid component group is not usable with `version`
314 pub(crate) fn as_hybrid_checked(
315 &self,
316 supported: &[&'static dyn SupportedKxGroup],
317 version: ProtocolVersion,
318 ) -> Option<(&dyn HybridKeyExchange, &'static dyn SupportedKxGroup)> {
319 let Self::Hybrid(hybrid) = self else {
320 return None;
321 };
322
323 let component_group = hybrid.component().0;
324 if !component_group.usable_for_version(version) {
325 return None;
326 }
327
328 supported
329 .iter()
330 .find(|g| g.name() == component_group)
331 .copied()
332 .map(|g| (hybrid.as_ref(), g))
333 }
334}
335
336impl Deref for StartedKeyExchange {
337 type Target = dyn ActiveKeyExchange;
338
339 fn deref(&self) -> &Self::Target {
340 match self {
341 Self::Single(s) => s.as_ref(),
342 Self::Hybrid(h) => h.as_key_exchange(),
343 }
344 }
345}
346
347/// An in-progress key exchange originating from a [`SupportedKxGroup`].
348pub trait ActiveKeyExchange: Send + Sync {
349 /// Completes the key exchange, given the peer's public key.
350 ///
351 /// This method must return an error if `peer_pub_key` is invalid: either
352 /// misencoded, or an invalid public key (such as, but not limited to, being
353 /// in a small order subgroup).
354 ///
355 /// If the key exchange algorithm is FFDHE, the result must be left-padded with zeros,
356 /// as required by [RFC 8446](https://www.rfc-editor.org/rfc/rfc8446#section-7.4.1)
357 /// (see [`complete_for_tls_version()`](Self::complete_for_tls_version) for more details).
358 ///
359 /// The shared secret is returned as a [`SharedSecret`] which can be constructed
360 /// from a `&[u8]`.
361 ///
362 /// This consumes and so terminates the [`ActiveKeyExchange`].
363 fn complete(self: Box<Self>, peer_pub_key: &[u8]) -> Result<SharedSecret, Error>;
364
365 /// Completes the key exchange for the given TLS version, given the peer's public key.
366 ///
367 /// Note that finite-field Diffie–Hellman key exchange has different requirements for the derived
368 /// shared secret in TLS 1.2 and TLS 1.3 (ECDHE key exchange is the same in TLS 1.2 and TLS 1.3):
369 ///
370 /// In TLS 1.2, the calculated secret is required to be stripped of leading zeros
371 /// [(RFC 5246)](https://www.rfc-editor.org/rfc/rfc5246#section-8.1.2).
372 ///
373 /// In TLS 1.3, the calculated secret is required to be padded with leading zeros to be the same
374 /// byte-length as the group modulus [(RFC 8446)](https://www.rfc-editor.org/rfc/rfc8446#section-7.4.1).
375 ///
376 /// The default implementation of this method delegates to [`complete()`](Self::complete) assuming it is
377 /// implemented for TLS 1.3 (i.e., for FFDHE KX, removes padding as needed). Implementers of this trait
378 /// are encouraged to just implement [`complete()`](Self::complete) assuming TLS 1.3, and let the default
379 /// implementation of this method handle TLS 1.2-specific requirements.
380 ///
381 /// This method must return an error if `peer_pub_key` is invalid: either
382 /// misencoded, or an invalid public key (such as, but not limited to, being
383 /// in a small order subgroup).
384 ///
385 /// The shared secret is returned as a [`SharedSecret`] which can be constructed
386 /// from a `&[u8]`.
387 ///
388 /// This consumes and so terminates the [`ActiveKeyExchange`].
389 fn complete_for_tls_version(
390 self: Box<Self>,
391 peer_pub_key: &[u8],
392 tls_version: ProtocolVersion,
393 ) -> Result<SharedSecret, Error> {
394 if tls_version == ProtocolVersion::TLSv1_3 {
395 return self.complete(peer_pub_key);
396 }
397
398 let group = self.group();
399 let mut complete_res = self.complete(peer_pub_key)?;
400 if group.key_exchange_algorithm() == KeyExchangeAlgorithm::DHE {
401 complete_res.strip_leading_zeros();
402 }
403 Ok(complete_res)
404 }
405
406 /// Return the public key being used.
407 ///
408 /// For ECDHE, the encoding required is defined in
409 /// [RFC8446 section 4.2.8.2](https://www.rfc-editor.org/rfc/rfc8446#section-4.2.8.2).
410 ///
411 /// For FFDHE, the encoding required is defined in
412 /// [RFC8446 section 4.2.8.1](https://www.rfc-editor.org/rfc/rfc8446#section-4.2.8.1).
413 fn pub_key(&self) -> &[u8];
414
415 /// FFDHE group the `ActiveKeyExchange` is operating in.
416 ///
417 /// The default implementation returns `None`, so non-FFDHE groups (the
418 /// most common) do not need to do anything.
419 ///
420 /// FFDHE groups must implement this. [`ffdhe`] contains suitable values to return, for
421 /// example [`ffdhe::FFDHE2048`].
422 fn ffdhe_group(&self) -> Option<FfdheGroup<'static>> {
423 None
424 }
425
426 /// Return the group being used.
427 fn group(&self) -> NamedGroup;
428}
429
430/// An in-progress hybrid key exchange originating from a [`SupportedKxGroup`].
431///
432/// "Hybrid" means a key exchange algorithm which is constructed from two
433/// (or more) independent component algorithms. Usually one is post-quantum-secure,
434/// and the other is "classical". See
435/// <https://datatracker.ietf.org/doc/draft-ietf-tls-hybrid-design/11/>
436///
437/// There is no requirement for a hybrid scheme (or any other!) to implement
438/// `HybridKeyExchange` if it is not desirable for it to be "split" like this.
439/// It only enables an optimization; described below.
440///
441/// # Background
442/// Rustls always sends a presumptive key share in its `ClientHello`, using
443/// (absent any other information) the first item in
444/// [`CryptoProvider::kx_groups`][super::CryptoProvider::kx_groups].
445/// If the server accepts the client's selection, it can complete the handshake
446/// using that key share. If not, the server sends a `HelloRetryRequest` instructing
447/// the client to send a different key share instead.
448///
449/// This request costs an extra round trip, and wastes the key exchange computation
450/// (in [`SupportedKxGroup::start()`]) the client already did. We would
451/// like to avoid those wastes if possible.
452///
453/// It is early days for post-quantum-secure hybrid key exchange deployment.
454/// This means (commonly) continuing to offer both the hybrid and classical
455/// key exchanges, so the handshake can be completed without a `HelloRetryRequest`
456/// for servers that support the offered hybrid or classical schemes.
457///
458/// Implementing `HybridKeyExchange` enables two optimizations:
459///
460/// 1. Sending both the hybrid and classical key shares in the `ClientHello`.
461///
462/// 2. Performing the classical key exchange setup only once. This is important
463/// because the classical key exchange setup is relatively expensive.
464/// This optimization is permitted and described in
465/// <https://www.ietf.org/archive/id/draft-ietf-tls-hybrid-design-11.html#section-3.2>
466///
467/// Both of these only happen if the classical algorithm appears separately in
468/// the client's [`CryptoProvider::kx_groups`][super::CryptoProvider::kx_groups],
469/// and if the hybrid algorithm appears first in that list.
470///
471/// # How it works
472/// This function is only called by rustls for clients. It is called when
473/// constructing the initial `ClientHello`. rustls follows these steps:
474///
475/// 1. If the return value is `None`, nothing further happens.
476/// 2. If the given [`NamedGroup`] does not appear in
477/// [`CryptoProvider::kx_groups`][super::CryptoProvider::kx_groups], nothing further happens.
478/// 3. The given key share is added to the `ClientHello`, after the hybrid entry.
479///
480/// Then, one of three things may happen when the server replies to the `ClientHello`:
481///
482/// 1. The server sends a `HelloRetryRequest`. Everything is thrown away and
483/// we start again.
484/// 2. The server agrees to our hybrid key exchange: rustls calls
485/// [`ActiveKeyExchange::complete()`] consuming `self`.
486/// 3. The server agrees to our classical key exchange: rustls calls
487/// [`HybridKeyExchange::complete_component()`] which
488/// discards the hybrid key data, and completes just the classical key exchange.
489pub trait HybridKeyExchange: ActiveKeyExchange {
490 /// Returns the [`NamedGroup`] and public key "share" for the component.
491 fn component(&self) -> (NamedGroup, &[u8]);
492
493 /// Completes the classical component of the key exchange, given the peer's public key.
494 ///
495 /// This method must return an error if `peer_pub_key` is invalid: either
496 /// misencoded, or an invalid public key (such as, but not limited to, being
497 /// in a small order subgroup).
498 ///
499 /// The shared secret is returned as a [`SharedSecret`] which can be constructed
500 /// from a `&[u8]`.
501 ///
502 /// See the documentation on [`HybridKeyExchange`] for explanation.
503 fn complete_component(self: Box<Self>, peer_pub_key: &[u8]) -> Result<SharedSecret, Error>;
504
505 /// Obtain the value as a `dyn ActiveKeyExchange`
506 fn as_key_exchange(&self) -> &(dyn ActiveKeyExchange + 'static);
507
508 /// Remove the ability to do hybrid key exchange on this object.
509 fn into_key_exchange(self: Box<Self>) -> Box<dyn ActiveKeyExchange>;
510}
511
512/// The result from [`SupportedKxGroup::start_and_complete()`].
513#[expect(clippy::exhaustive_structs)]
514pub struct CompletedKeyExchange {
515 /// Which group was used.
516 pub group: NamedGroup,
517
518 /// Our key share (sometimes a public key).
519 pub pub_key: Vec<u8>,
520
521 /// The computed shared secret.
522 pub secret: SharedSecret,
523}
524
525enum_builder! {
526 /// The `NamedGroup` TLS protocol enum. Values in this enum are taken
527 /// from the various RFCs covering TLS, and are listed by IANA.
528 /// The `Unknown` item is used when processing unrecognized ordinals.
529 ///
530 /// This enum is used for recognizing key exchange groups advertised
531 /// by a peer during a TLS handshake. It is **not** a list of groups that
532 /// Rustls supports. The supported groups are determined via the
533 /// [`CryptoProvider`][crate::crypto::CryptoProvider] interface.
534 #[repr(u16)]
535 #[expect(non_camel_case_types)]
536 pub enum NamedGroup {
537 secp256r1 => 0x0017,
538 secp384r1 => 0x0018,
539 secp521r1 => 0x0019,
540 X25519 => 0x001d,
541 X448 => 0x001e,
542 /// <https://www.iana.org/go/rfc8734>
543 brainpoolP256r1tls13 => 0x001f,
544 /// <https://www.iana.org/go/rfc8734>
545 brainpoolP384r1tls13 => 0x0020,
546 /// <https://www.iana.org/go/rfc8734>
547 brainpoolP512r1tls13 => 0x0021,
548 /// <https://www.iana.org/go/rfc8998>
549 curveSM2 => 0x0029,
550 FFDHE2048 => 0x0100,
551 FFDHE3072 => 0x0101,
552 FFDHE4096 => 0x0102,
553 FFDHE6144 => 0x0103,
554 FFDHE8192 => 0x0104,
555 /// <https://datatracker.ietf.org/doc/draft-ietf-tls-mlkem/>
556 MLKEM512 => 0x0200,
557 /// <https://datatracker.ietf.org/doc/draft-ietf-tls-mlkem/>
558 MLKEM768 => 0x0201,
559 /// <https://datatracker.ietf.org/doc/draft-ietf-tls-mlkem/>
560 MLKEM1024 => 0x0202,
561 /// <https://datatracker.ietf.org/doc/draft-ietf-tls-ecdhe-mlkem/>
562 secp256r1MLKEM768 => 0x11eb,
563 /// <https://datatracker.ietf.org/doc/draft-ietf-tls-ecdhe-mlkem/>
564 X25519MLKEM768 => 0x11ec,
565 /// <https://datatracker.ietf.org/doc/draft-ietf-tls-ecdhe-mlkem/>
566 secp384r1MLKEM1024 => 0x11ed,
567 }
568}
569
570impl NamedGroup {
571 /// Return the key exchange algorithm associated with this `NamedGroup`
572 pub fn key_exchange_algorithm(self) -> KeyExchangeAlgorithm {
573 match u16::from(self) {
574 x if (0x100..0x200).contains(&x) => KeyExchangeAlgorithm::DHE,
575 _ => KeyExchangeAlgorithm::ECDHE,
576 }
577 }
578
579 /// Returns whether this `NamedGroup` is usable for the given protocol version.
580 pub fn usable_for_version(&self, version: ProtocolVersion) -> bool {
581 match version {
582 ProtocolVersion::TLSv1_3 => true,
583 _ => !matches!(
584 self,
585 Self::MLKEM512
586 | Self::MLKEM768
587 | Self::MLKEM1024
588 | Self::X25519MLKEM768
589 | Self::secp256r1MLKEM768
590 | Self::secp384r1MLKEM1024
591 | Self::brainpoolP256r1tls13
592 | Self::brainpoolP384r1tls13
593 | Self::brainpoolP512r1tls13
594 | Self::curveSM2
595 ),
596 }
597 }
598}
599
600/// The result from [`ActiveKeyExchange::complete()`] or [`HybridKeyExchange::complete_component()`].
601pub struct SharedSecret {
602 buf: Vec<u8>,
603 offset: usize,
604}
605
606impl SharedSecret {
607 /// Returns the shared secret as a slice of bytes.
608 pub fn secret_bytes(&self) -> &[u8] {
609 &self.buf[self.offset..]
610 }
611
612 /// Removes leading zeros from `secret_bytes()` by adjusting the `offset`.
613 ///
614 /// This function does not re-allocate.
615 fn strip_leading_zeros(&mut self) {
616 let start = self
617 .secret_bytes()
618 .iter()
619 .enumerate()
620 .find(|(_i, x)| **x != 0)
621 .map(|(i, _x)| i)
622 .unwrap_or_else(|| self.secret_bytes().len());
623 self.offset += start;
624 }
625}
626
627impl Drop for SharedSecret {
628 fn drop(&mut self) {
629 self.buf.zeroize();
630 }
631}
632
633impl From<&[u8]> for SharedSecret {
634 fn from(source: &[u8]) -> Self {
635 Self {
636 buf: source.to_vec(),
637 offset: 0,
638 }
639 }
640}
641
642impl From<Vec<u8>> for SharedSecret {
643 fn from(buf: Vec<u8>) -> Self {
644 Self { buf, offset: 0 }
645 }
646}
647
648/// Describes supported key exchange mechanisms.
649#[derive(Clone, Copy, Debug, PartialEq)]
650#[non_exhaustive]
651pub enum KeyExchangeAlgorithm {
652 /// Diffie-Hellman Key exchange (with only known parameters as defined in [RFC 7919]).
653 ///
654 /// [RFC 7919]: https://datatracker.ietf.org/doc/html/rfc7919
655 DHE,
656 /// Key exchange performed via elliptic curve Diffie-Hellman.
657 ECDHE,
658}
659
660#[cfg(test)]
661mod tests {
662 use std::vec;
663
664 use super::{NamedGroup, SharedSecret};
665 use crate::msgs::test_enum16;
666
667 #[test]
668 fn test_shared_secret_strip_leading_zeros() {
669 let test_cases = [
670 (vec![0, 1], vec![1]),
671 (vec![1], vec![1]),
672 (vec![1, 0, 2], vec![1, 0, 2]),
673 (vec![0, 0, 1, 2], vec![1, 2]),
674 (vec![0, 0, 0], vec![]),
675 (vec![], vec![]),
676 ];
677 for (buf, expected) in test_cases {
678 let mut secret = SharedSecret::from(&buf[..]);
679 assert_eq!(secret.secret_bytes(), buf);
680 secret.strip_leading_zeros();
681 assert_eq!(secret.secret_bytes(), expected);
682 }
683 }
684
685 #[test]
686 fn test_enums() {
687 test_enum16::<NamedGroup>(NamedGroup::secp256r1, NamedGroup::FFDHE8192);
688 }
689}