rustls/crypto/ring/
kx.rs

1#![allow(clippy::duplicate_mod)]
2
3use alloc::boxed::Box;
4use core::fmt;
5
6use super::ring_like::agreement;
7use super::ring_like::rand::SystemRandom;
8use crate::crypto::{ActiveKeyExchange, FfdheGroup, SharedSecret, SupportedKxGroup};
9use crate::error::{Error, PeerMisbehaved};
10use crate::msgs::enums::NamedGroup;
11use crate::rand::GetRandomFailed;
12
13/// A key-exchange group supported by *ring*.
14///
15/// All possible instances of this class are provided by the library in
16/// the [`ALL_KX_GROUPS`] array.
17struct KxGroup {
18    /// The IANA "TLS Supported Groups" name of the group
19    name: NamedGroup,
20
21    /// The corresponding ring agreement::Algorithm
22    agreement_algorithm: &'static agreement::Algorithm,
23
24    /// Whether the algorithm is allowed by FIPS
25    ///
26    /// `SupportedKxGroup::fips()` is true if and only if the algorithm is allowed,
27    /// _and_ the implementation is FIPS-validated.
28    fips_allowed: bool,
29
30    /// aws-lc-rs 1.9 and later accepts more formats of public keys than
31    /// just uncompressed.
32    ///
33    /// That is not compatible with TLS:
34    /// - TLS1.3 outlaws other encodings,
35    /// - TLS1.2 negotiates other encodings (we only offer uncompressed), and
36    ///   defaults to uncompressed if negotiation is not done.
37    ///
38    /// This function should return `true` if the basic shape of its argument
39    /// is consistent with an uncompressed point encoding.  It does not need
40    /// to verify that the point is on the curve (if the curve requires that
41    /// for security); aws-lc-rs/ring must do that.
42    pub_key_validator: fn(&[u8]) -> bool,
43}
44
45impl SupportedKxGroup for KxGroup {
46    fn start(&self) -> Result<Box<dyn ActiveKeyExchange>, Error> {
47        let rng = SystemRandom::new();
48        let priv_key = agreement::EphemeralPrivateKey::generate(self.agreement_algorithm, &rng)
49            .map_err(|_| GetRandomFailed)?;
50
51        let pub_key = priv_key
52            .compute_public_key()
53            .map_err(|_| GetRandomFailed)?;
54
55        Ok(Box::new(KeyExchange {
56            name: self.name,
57            agreement_algorithm: self.agreement_algorithm,
58            priv_key,
59            pub_key,
60            pub_key_validator: self.pub_key_validator,
61        }))
62    }
63
64    fn ffdhe_group(&self) -> Option<FfdheGroup<'static>> {
65        None
66    }
67
68    fn name(&self) -> NamedGroup {
69        self.name
70    }
71
72    fn fips(&self) -> bool {
73        self.fips_allowed && super::fips()
74    }
75}
76
77impl fmt::Debug for KxGroup {
78    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79        self.name.fmt(f)
80    }
81}
82
83/// Ephemeral ECDH on curve25519 (see RFC7748)
84pub static X25519: &dyn SupportedKxGroup = &KxGroup {
85    name: NamedGroup::X25519,
86    agreement_algorithm: &agreement::X25519,
87
88    // "Curves that are included in SP 800-186 but not included in SP 800-56Arev3 are
89    //  not approved for key agreement. E.g., the ECDH X25519 and X448 key agreement
90    //  schemes (defined in RFC 7748) that use Curve25519 and Curve448, respectively,
91    //  are not compliant to SP 800-56Arev3."
92    // -- <https://csrc.nist.gov/csrc/media/Projects/cryptographic-module-validation-program/documents/fips%20140-3/FIPS%20140-3%20IG.pdf>
93    fips_allowed: false,
94
95    pub_key_validator: |point: &[u8]| point.len() == 32,
96};
97
98/// Ephemeral ECDH on secp256r1 (aka NIST-P256)
99pub static SECP256R1: &dyn SupportedKxGroup = &KxGroup {
100    name: NamedGroup::secp256r1,
101    agreement_algorithm: &agreement::ECDH_P256,
102    fips_allowed: true,
103    pub_key_validator: uncompressed_point,
104};
105
106/// Ephemeral ECDH on secp384r1 (aka NIST-P384)
107pub static SECP384R1: &dyn SupportedKxGroup = &KxGroup {
108    name: NamedGroup::secp384r1,
109    agreement_algorithm: &agreement::ECDH_P384,
110    fips_allowed: true,
111    pub_key_validator: uncompressed_point,
112};
113
114fn uncompressed_point(point: &[u8]) -> bool {
115    // See `UncompressedPointRepresentation`, which is a retelling of
116    // SEC1 section 2.3.3 "Elliptic-Curve-Point-to-Octet-String Conversion"
117    // <https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.8.2>
118    matches!(point.first(), Some(0x04))
119}
120
121/// A list of all the key exchange groups supported by rustls.
122pub static ALL_KX_GROUPS: &[&dyn SupportedKxGroup] = &[X25519, SECP256R1, SECP384R1];
123
124/// An in-progress key exchange.  This has the algorithm,
125/// our private key, and our public key.
126struct KeyExchange {
127    name: NamedGroup,
128    agreement_algorithm: &'static agreement::Algorithm,
129    priv_key: agreement::EphemeralPrivateKey,
130    pub_key: agreement::PublicKey,
131    pub_key_validator: fn(&[u8]) -> bool,
132}
133
134impl ActiveKeyExchange for KeyExchange {
135    /// Completes the key exchange, given the peer's public key.
136    fn complete(self: Box<Self>, peer: &[u8]) -> Result<SharedSecret, Error> {
137        if !(self.pub_key_validator)(peer) {
138            return Err(PeerMisbehaved::InvalidKeyShare.into());
139        }
140        let peer_key = agreement::UnparsedPublicKey::new(self.agreement_algorithm, peer);
141        super::ring_shim::agree_ephemeral(self.priv_key, &peer_key)
142            .map_err(|_| PeerMisbehaved::InvalidKeyShare.into())
143    }
144
145    fn ffdhe_group(&self) -> Option<FfdheGroup<'static>> {
146        None
147    }
148
149    /// Return the group being used.
150    fn group(&self) -> NamedGroup {
151        self.name
152    }
153
154    /// Return the public key being used.
155    fn pub_key(&self) -> &[u8] {
156        self.pub_key.as_ref()
157    }
158}
159
160#[cfg(test)]
161mod tests {
162    use std::format;
163
164    #[test]
165    fn kxgroup_fmt_yields_name() {
166        assert_eq!("X25519", format!("{:?}", super::X25519));
167    }
168}
169
170#[cfg(bench)]
171mod benchmarks {
172    #[bench]
173    fn bench_x25519(b: &mut test::Bencher) {
174        bench_any(b, super::X25519);
175    }
176
177    #[bench]
178    fn bench_ecdh_p256(b: &mut test::Bencher) {
179        bench_any(b, super::SECP256R1);
180    }
181
182    #[bench]
183    fn bench_ecdh_p384(b: &mut test::Bencher) {
184        bench_any(b, super::SECP384R1);
185    }
186
187    fn bench_any(b: &mut test::Bencher, kxg: &dyn super::SupportedKxGroup) {
188        b.iter(|| {
189            let akx = kxg.start().unwrap();
190            let pub_key = akx.pub_key().to_vec();
191            test::black_box(akx.complete(&pub_key).unwrap());
192        });
193    }
194}