rustls_mbedcrypto_provider/
kx.rs

1/* Copyright (c) Fortanix, Inc.
2 *
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 */
7
8use std::sync::Mutex;
9use std::sync::OnceLock;
10
11use super::agreement;
12use crate::error::mbedtls_err_to_rustls_error;
13use crate::rng::MbedRng;
14
15use alloc::boxed::Box;
16use alloc::fmt;
17use alloc::format;
18use alloc::vec::Vec;
19use crypto::SupportedKxGroup;
20use mbedtls::bignum::Mpi;
21use mbedtls::rng::Random;
22use mbedtls::rng::RngCallback;
23use mbedtls::{
24    ecp::EcPoint,
25    pk::{EcGroup, Pk as PkMbed},
26};
27use rustls::crypto;
28use rustls::crypto::ActiveKeyExchange;
29use rustls::ffdhe_groups;
30use rustls::ffdhe_groups::FfdheGroup;
31use rustls::Error;
32use rustls::NamedGroup;
33/// A wrapper type representing implementation of an EC key-exchange group
34/// supported by *mbedtls*.
35///
36/// All possible instances of this type are provided by the library in the
37/// `ALL_KX_GROUPS` array.
38pub struct EcdhKxGroupWrapper<T: RngCallback> {
39    /// An EC key-exchange group
40    kx_group: EcdhKxGroup,
41
42    /// Callback to produce RNGs when needed
43    rng_provider: fn() -> Option<T>,
44}
45
46/// An EC key-exchange group supported by *mbedtls*.
47#[derive(Debug, Clone, Copy)]
48struct EcdhKxGroup {
49    /// The corresponding agreement algorithm
50    agreement_algorithm: &'static agreement::Algorithm,
51
52    /// The IANA "TLS Supported Groups" name of the group
53    name: NamedGroup,
54}
55
56impl<T: RngCallback> EcdhKxGroupWrapper<T> {
57    /// Create a new [`EcdhKxGroupWrapper`] with given RNG provider callback.
58    pub const fn with_rng_provider<F: RngCallback>(&self, rng_provider: fn() -> Option<F>) -> EcdhKxGroupWrapper<F> {
59        EcdhKxGroupWrapper { rng_provider, kx_group: self.kx_group }
60    }
61}
62
63impl<T: RngCallback> fmt::Debug for EcdhKxGroupWrapper<T> {
64    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65        f.debug_struct("EcdhKxGroupWrapper")
66            .field("kx_group", &self.kx_group)
67            .finish()
68    }
69}
70
71impl<T: RngCallback + 'static> SupportedKxGroup for EcdhKxGroupWrapper<T> {
72    fn start(&self) -> Result<Box<dyn ActiveKeyExchange>, Error> {
73        let mut rng = (self.rng_provider)().ok_or(Error::FailedToGetRandomBytes)?;
74
75        #[allow(unused_mut)]
76        let mut priv_key = generate_ec_key(
77            self.kx_group
78                .agreement_algorithm
79                .group_id,
80            &mut rng,
81        )?;
82
83        // Only run fips check on applied NamedGroups
84        #[cfg(feature = "fips")]
85        match self.name() {
86            NamedGroup::secp256r1 | NamedGroup::secp384r1 | NamedGroup::secp521r1 => {
87                crate::fips_utils::fips_ec_pct(
88                    &mut priv_key,
89                    self.kx_group
90                        .agreement_algorithm
91                        .group_id,
92                    &mut rng,
93                )?;
94            }
95            _ => (),
96        }
97
98        Ok(Box::new(EcdhKeyExchangeImpl {
99            name: self.kx_group.name,
100            agreement_algorithm: self.kx_group.agreement_algorithm,
101            priv_key,
102            pub_key: OnceLock::new(),
103            rng_provider: self.rng_provider,
104        }))
105    }
106
107    fn name(&self) -> NamedGroup {
108        self.kx_group.name
109    }
110}
111
112#[inline]
113fn generate_ec_key<F: Random>(group_id: mbedtls::pk::EcGroupId, rng: &mut F) -> Result<PkMbed, Error> {
114    PkMbed::generate_ec(rng, group_id)
115        .map_err(|err| Error::General(format!("Got error when generating ec key, mbedtls error: {err}")))
116}
117
118/// Ephemeral ECDH on curve25519 (see RFC7748)
119pub static X25519: &dyn SupportedKxGroup = X25519_KX_GROUP;
120/// Ephemeral ECDH on curve25519 (see RFC7748)
121pub static X25519_KX_GROUP: &EcdhKxGroupWrapper<MbedRng> = &EcdhKxGroupWrapper {
122    kx_group: EcdhKxGroup { name: NamedGroup::X25519, agreement_algorithm: &agreement::X25519 },
123    rng_provider: crate::rng::rng_new,
124};
125
126/// Ephemeral ECDH on secp256r1 (aka NIST-P256)
127pub static SECP256R1: &dyn SupportedKxGroup = SECP256R1_KX_GROUP;
128/// Ephemeral ECDH on secp256r1 (aka NIST-P256)
129pub static SECP256R1_KX_GROUP: &EcdhKxGroupWrapper<MbedRng> = &EcdhKxGroupWrapper {
130    kx_group: EcdhKxGroup { name: NamedGroup::secp256r1, agreement_algorithm: &agreement::ECDH_P256 },
131    rng_provider: crate::rng::rng_new,
132};
133
134/// Ephemeral ECDH on secp384r1 (aka NIST-P384)
135pub static SECP384R1: &dyn SupportedKxGroup = SECP384R1_KX_GROUP;
136/// Ephemeral ECDH on secp384r1 (aka NIST-P384)
137pub static SECP384R1_KX_GROUP: &EcdhKxGroupWrapper<MbedRng> = &EcdhKxGroupWrapper {
138    kx_group: EcdhKxGroup { name: NamedGroup::secp384r1, agreement_algorithm: &agreement::ECDH_P384 },
139    rng_provider: crate::rng::rng_new,
140};
141
142/// Ephemeral ECDH on secp521r1 (aka NIST-P521)
143pub static SECP521R1: &dyn SupportedKxGroup = SECP521R1_KX_GROUP;
144/// Ephemeral ECDH on secp521r1 (aka NIST-P521)
145pub static SECP521R1_KX_GROUP: &EcdhKxGroupWrapper<MbedRng> = &EcdhKxGroupWrapper {
146    kx_group: EcdhKxGroup { name: NamedGroup::secp521r1, agreement_algorithm: &agreement::ECDH_P521 },
147    rng_provider: crate::rng::rng_new,
148};
149
150/// DHE group [FFDHE2048](https://www.rfc-editor.org/rfc/rfc7919.html#appendix-A.1)
151pub static FFDHE2048: &dyn SupportedKxGroup = FFDHE2048_KX_GROUP;
152/// DHE group [FFDHE2048](https://www.rfc-editor.org/rfc/rfc7919.html#appendix-A.1)
153pub static FFDHE2048_KX_GROUP: &FfdheKxGroupWrapper<MbedRng> = &FfdheKxGroupWrapper {
154    dhe_kx_group: FfdheKxGroup {
155        named_group: NamedGroup::FFDHE2048,
156        group: ffdhe_groups::FFDHE2048,
157        priv_key_len: 36,
158    },
159    rng_provider: crate::rng::rng_new,
160};
161
162/// DHE group [FFDHE3072](https://www.rfc-editor.org/rfc/rfc7919.html#appendix-A.2)
163pub static FFDHE3072: &dyn SupportedKxGroup = FFDHE3072_KX_GROUP;
164/// DHE group [FFDHE3072](https://www.rfc-editor.org/rfc/rfc7919.html#appendix-A.2)
165pub static FFDHE3072_KX_GROUP: &FfdheKxGroupWrapper<MbedRng> = &FfdheKxGroupWrapper {
166    dhe_kx_group: FfdheKxGroup {
167        named_group: NamedGroup::FFDHE3072,
168        group: ffdhe_groups::FFDHE3072,
169        priv_key_len: 40,
170    },
171    rng_provider: crate::rng::rng_new,
172};
173
174/// DHE group [FFDHE4096](https://www.rfc-editor.org/rfc/rfc7919.html#appendix-A.3)
175pub static FFDHE4096: &dyn SupportedKxGroup = FFDHE4096_KX_GROUP;
176/// DHE group [FFDHE3072](https://www.rfc-editor.org/rfc/rfc7919.html#appendix-A.3)
177pub static FFDHE4096_KX_GROUP: &FfdheKxGroupWrapper<MbedRng> = &FfdheKxGroupWrapper {
178    dhe_kx_group: FfdheKxGroup {
179        named_group: NamedGroup::FFDHE4096,
180        group: ffdhe_groups::FFDHE4096,
181        priv_key_len: 48,
182    },
183    rng_provider: crate::rng::rng_new,
184};
185
186/// DHE group [FFDHE6144](https://www.rfc-editor.org/rfc/rfc7919.html#appendix-A.4)
187pub static FFDHE6144: &dyn SupportedKxGroup = FFDHE6144_KX_GROUP;
188/// DHE group [FFDHE6144](https://www.rfc-editor.org/rfc/rfc7919.html#appendix-A.4)
189pub static FFDHE6144_KX_GROUP: &FfdheKxGroupWrapper<MbedRng> = &FfdheKxGroupWrapper {
190    dhe_kx_group: FfdheKxGroup {
191        named_group: NamedGroup::FFDHE6144,
192        group: ffdhe_groups::FFDHE6144,
193        priv_key_len: 56,
194    },
195    rng_provider: crate::rng::rng_new,
196};
197
198/// DHE group [FFDHE8192](https://www.rfc-editor.org/rfc/rfc7919.html#appendix-A.5)
199pub static FFDHE8192: &dyn SupportedKxGroup = FFDHE8192_KX_GROUP;
200/// DHE group [FFDHE8192](https://www.rfc-editor.org/rfc/rfc7919.html#appendix-A.5)
201pub static FFDHE8192_KX_GROUP: &FfdheKxGroupWrapper<MbedRng> = &FfdheKxGroupWrapper {
202    dhe_kx_group: FfdheKxGroup {
203        named_group: NamedGroup::FFDHE8192,
204        group: ffdhe_groups::FFDHE8192,
205        priv_key_len: 64,
206    },
207    rng_provider: crate::rng::rng_new,
208};
209
210/// A list of all the key exchange groups supported by mbedtls.
211pub static ALL_KX_GROUPS: &[&dyn SupportedKxGroup] = &[
212    // ECDHE groups:
213    X25519, SECP256R1, SECP384R1, SECP521R1, // fmt
214    // (FF)DHE groups:
215    FFDHE2048, FFDHE3072, FFDHE4096, FFDHE6144, FFDHE8192,
216];
217
218/// An in-progress ECDH key exchange.  This has the algorithm,
219/// our private key, and our public key and RNG provider.
220struct EcdhKeyExchangeImpl<T: RngCallback> {
221    /// The corresponding [`agreement::Algorithm`]
222    agreement_algorithm: &'static agreement::Algorithm,
223    /// The IANA "TLS Supported Groups" name of the group
224    name: NamedGroup,
225    /// Private key
226    priv_key: PkMbed,
227    /// Public key in binary format [`EcPoint`] without compression
228    pub_key: OnceLock<Vec<u8>>,
229    /// Callback to produce RNGs when needed
230    rng_provider: fn() -> Option<T>,
231}
232
233impl<T: RngCallback> EcdhKeyExchangeImpl<T> {
234    fn get_pub_key(&self) -> mbedtls::Result<Vec<u8>> {
235        let group = EcGroup::new(self.agreement_algorithm.group_id)?;
236        self.priv_key
237            .ec_public()?
238            .to_binary(&group, false)
239    }
240}
241
242impl<T: RngCallback> ActiveKeyExchange for EcdhKeyExchangeImpl<T> {
243    /// Completes the key exchange, given the peer's public key.
244    fn complete(mut self: Box<Self>, peer_public_key: &[u8]) -> Result<crypto::SharedSecret, Error> {
245        let group_id = self.agreement_algorithm.group_id;
246
247        if peer_public_key.len() != self.agreement_algorithm.public_key_len {
248            return Err(rustls::PeerMisbehaved::InvalidKeyShare.into());
249        }
250
251        let peer_pk = parse_peer_public_key(group_id, peer_public_key).map_err(mbedtls_err_to_rustls_error)?;
252
253        let mut rng = (self.rng_provider)().ok_or(crypto::GetRandomFailed)?;
254
255        // Only run fips check on applied NamedGroups
256        #[cfg(feature = "fips")]
257        match self.name {
258            NamedGroup::secp256r1 | NamedGroup::secp384r1 | NamedGroup::secp521r1 => {
259                crate::fips_utils::fips_check_ec_pub_key(&peer_pk, &mut rng)?
260            }
261            _ => (),
262        }
263
264        let mut shared_key = [0u8; mbedtls::pk::ECDSA_MAX_LEN];
265        let shared_key = &mut shared_key[..self
266            .agreement_algorithm
267            .max_signature_len];
268        let len = self
269            .priv_key
270            .agree(&peer_pk, shared_key, &mut rng)
271            .map_err(mbedtls_err_to_rustls_error)?;
272        Ok(crypto::SharedSecret::from(&shared_key[..len]))
273    }
274
275    /// Return the public key being used.
276    fn pub_key(&self) -> &[u8] {
277        self.pub_key
278            .get_or_init(|| self.get_pub_key().unwrap_or_default())
279    }
280
281    /// Return the group being used.
282    fn group(&self) -> NamedGroup {
283        self.name
284    }
285}
286
287/// A wrapper type representing the implementation of a FFDHE key-exchange group
288/// supported by *mbedtls*.
289///
290/// All possible instances of this type are provided by the library in the
291/// `ALL_KX_GROUPS` array.
292pub struct FfdheKxGroupWrapper<T: RngCallback> {
293    /// A FFDHE key-exchange group
294    pub(crate) dhe_kx_group: FfdheKxGroup,
295    /// Callback to produce RNGs when needed
296    rng_provider: fn() -> Option<T>,
297}
298
299/// A FFDHE key-exchange group supported by *mbedtls*.
300#[derive(Debug, Clone, Copy)]
301pub(crate) struct FfdheKxGroup {
302    /// FFDHE Group parameters
303    pub(crate) group: FfdheGroup<'static>,
304    /// The IANA "TLS Supported Groups" name of the group
305    pub(crate) named_group: NamedGroup,
306    /// Private key length
307    pub(crate) priv_key_len: usize,
308}
309
310impl<T: RngCallback> FfdheKxGroupWrapper<T> {
311    /// Create a new [`FfdheKxGroupWrapper`] with given RNG provider callback.
312    pub const fn with_rng_provider<F: RngCallback>(&self, rng_provider: fn() -> Option<F>) -> FfdheKxGroupWrapper<F> {
313        FfdheKxGroupWrapper { dhe_kx_group: self.dhe_kx_group, rng_provider }
314    }
315}
316
317impl<T: RngCallback> fmt::Debug for FfdheKxGroupWrapper<T> {
318    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
319        f.debug_struct("FfdheKxGroupWrapper")
320            .field("dhe_kx_group", &self.dhe_kx_group)
321            .finish()
322    }
323}
324
325impl<T: RngCallback> SupportedKxGroup for FfdheKxGroupWrapper<T> {
326    fn start(&self) -> Result<Box<dyn ActiveKeyExchange>, Error> {
327        let g = Mpi::from_binary(self.dhe_kx_group.group.g).map_err(mbedtls_err_to_rustls_error)?;
328        let p = Mpi::from_binary(self.dhe_kx_group.group.p).map_err(mbedtls_err_to_rustls_error)?;
329
330        let mut rng = (self.rng_provider)().ok_or(crypto::GetRandomFailed)?;
331        let mut x = vec![0; self.dhe_kx_group.priv_key_len];
332        rng.random(&mut x)
333            .map_err(|_| crypto::GetRandomFailed)?;
334        let x = Mpi::from_binary(&x).map_err(|e| Error::General(format!("failed to make Bignum from random bytes: {e}")))?;
335        let x_pub = g
336            .mod_exp(&x, &p)
337            .map_err(mbedtls_err_to_rustls_error)?;
338
339        #[cfg(feature = "fips")]
340        crate::fips_utils::ffdhe_pct(self, &x, &x_pub)?;
341
342        Ok(Box::new(DheActiveKeyExchangeImpl::new(
343            self.dhe_kx_group.named_group,
344            self.dhe_kx_group.group,
345            Mutex::new(p),
346            Mutex::new(x),
347            x_pub
348                .to_binary_padded(self.dhe_kx_group.group.p.len())
349                .map_err(mbedtls_err_to_rustls_error)?,
350        )))
351    }
352
353    fn name(&self) -> NamedGroup {
354        self.dhe_kx_group.named_group
355    }
356}
357
358pub(crate) struct DheActiveKeyExchangeImpl {
359    named_group: NamedGroup,
360    group: FfdheGroup<'static>,
361    // Using Mutex just because `Mpi` is not currently `Sync`
362    // TODO remove the Mutex once we switch to a version of mbedtls where `Mpi` is `Sync`
363    p: Mutex<Mpi>,
364    x: Mutex<Mpi>,
365    x_pub: Vec<u8>,
366}
367
368impl DheActiveKeyExchangeImpl {
369    pub(crate) fn new(
370        named_group: NamedGroup,
371        group: FfdheGroup<'static>,
372        p: Mutex<Mpi>,
373        x: Mutex<Mpi>,
374        x_pub: Vec<u8>,
375    ) -> Self {
376        Self { named_group, group, p, x, x_pub }
377    }
378}
379
380impl ActiveKeyExchange for DheActiveKeyExchangeImpl {
381    fn complete(self: Box<Self>, peer_pub_key: &[u8]) -> Result<crypto::SharedSecret, Error> {
382        let y_pub = Mpi::from_binary(peer_pub_key).map_err(mbedtls_err_to_rustls_error)?;
383
384        let x = self
385            .x
386            .into_inner()
387            .expect("Mpi Mutex poisoned");
388        let p = self
389            .p
390            .into_inner()
391            .expect("Mpi Mutex poisoned");
392
393        let one = Mpi::new(1).map_err(mbedtls_err_to_rustls_error)?;
394
395        let mut p_minus_one = p;
396        p_minus_one -= &one;
397
398        // https://www.rfc-editor.org/rfc/rfc7919.html#section-5.1:
399        // Peers MUST validate each other's public key Y [...] by ensuring that 1 < Y < p-1.
400        if !(one < y_pub && y_pub < p_minus_one) {
401            return Err(Error::General(
402                "Invalid DHE key exchange public key received; pub key must be in range (1, p-1)".into(),
403            ));
404        }
405
406        p_minus_one += &one;
407        let p = p_minus_one;
408
409        #[cfg(feature = "fips")]
410        crate::fips_utils::ffdhe_pub_key_check(&self.group, self.named_group, &y_pub)?;
411
412        let secret = y_pub
413            .mod_exp(&x, &p)
414            .map_err(mbedtls_err_to_rustls_error)?;
415
416        Ok(crypto::SharedSecret::from(
417            secret
418                .to_binary_padded(self.group.p.len())
419                .map_err(mbedtls_err_to_rustls_error)?
420                .as_ref(),
421        ))
422    }
423
424    fn pub_key(&self) -> &[u8] {
425        &self.x_pub
426    }
427
428    fn group(&self) -> NamedGroup {
429        self.named_group
430    }
431}
432
433#[inline]
434fn parse_peer_public_key(group_id: mbedtls::pk::EcGroupId, peer_public_key: &[u8]) -> Result<PkMbed, mbedtls::Error> {
435    let ec_group = EcGroup::new(group_id)?;
436    let public_point = EcPoint::from_binary_no_compress(&ec_group, peer_public_key)?;
437    PkMbed::public_from_ec_components(ec_group, public_point)
438}
439
440#[cfg(test)]
441mod tests {
442    use super::*;
443
444    #[test]
445    fn test_kx_group_fmt_debug() {
446        let debug_str = format!("{X25519_KX_GROUP:?}");
447        assert_eq!(
448            debug_str,
449            "EcdhKxGroupWrapper { kx_group: EcdhKxGroup { agreement_algorithm: Algorithm { group_id: Curve25519 }, name: X25519 } }",
450        )
451    }
452
453    #[test]
454    fn test_dhe_kx_group_fmt_debug() {
455        let debug_str = format!("{FFDHE2048_KX_GROUP:?}");
456        assert!(debug_str.contains("FfdheKxGroup"), "debug_str: {debug_str}");
457        assert!(debug_str.contains("FFDHE2048"), "debug_str: {debug_str}");
458        assert!(debug_str.contains("FfdheGroup"), "debug_str: {debug_str}");
459        assert!(!debug_str.contains("rng_provider"), "debug_str: {debug_str}");
460    }
461
462    #[test]
463    fn test_static_with_rng_provider() {
464        fn get_ftx_rng() -> Option<MbedRng> {
465            None
466        }
467        // Test that with_rng_provider works as expected.
468        assert!((X25519_KX_GROUP
469            .with_rng_provider(get_ftx_rng)
470            .rng_provider)()
471        .is_none());
472        assert!((FFDHE2048_KX_GROUP
473            .with_rng_provider(get_ftx_rng)
474            .rng_provider)()
475        .is_none());
476        // Test that with_rng_provider could use with static.
477        static _X25519: &dyn SupportedKxGroup = &X25519_KX_GROUP.with_rng_provider(get_ftx_rng);
478        static _FFDHE2048: &dyn SupportedKxGroup = &FFDHE2048_KX_GROUP.with_rng_provider(get_ftx_rng);
479    }
480}
481
482#[cfg(bench)]
483mod benchmarks {
484
485    #[bench]
486    fn bench_ecdh_p256(b: &mut test::Bencher) {
487        bench_any(b, super::SECP256R1);
488    }
489
490    #[bench]
491    fn bench_ecdh_p384(b: &mut test::Bencher) {
492        bench_any(b, super::SECP384R1);
493    }
494
495    #[bench]
496    fn bench_ecdh_p521(b: &mut test::Bencher) {
497        bench_any(b, super::SECP521R1);
498    }
499
500    #[bench]
501    fn bench_x25519(b: &mut test::Bencher) {
502        bench_any(b, super::X25519);
503    }
504
505    fn bench_any(b: &mut test::Bencher, kxg: &dyn super::SupportedKxGroup) {
506        b.iter(|| {
507            let akx = kxg.start().unwrap();
508            let pub_key = akx.pub_key().to_vec();
509            test::black_box(akx.complete(&pub_key).unwrap());
510        });
511    }
512
513    #[bench]
514    fn bench_ecdh_p256_start(b: &mut test::Bencher) {
515        let kxg = super::SECP256R1;
516        b.iter(|| {
517            test::black_box(kxg.start().unwrap());
518        });
519    }
520
521    #[bench]
522    fn bench_ecdh_p256_gen_private_key(b: &mut test::Bencher) {
523        let mut rng = crate::rng::rng_new().unwrap();
524        b.iter(|| {
525            test::black_box(super::generate_ec_key(mbedtls::pk::EcGroupId::SecP256R1, &mut rng).unwrap());
526        });
527    }
528
529    #[bench]
530    fn bench_ecdh_p256_parse_peer_pub_key(b: &mut test::Bencher) {
531        let kxg = super::SECP256R1;
532        let akx = kxg.start().unwrap();
533        let pub_key = akx.pub_key().to_vec();
534        b.iter(|| {
535            test::black_box(super::parse_peer_public_key(mbedtls::pk::EcGroupId::SecP256R1, &pub_key).unwrap());
536        });
537    }
538}