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