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, 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 name(&self) -> NamedGroup {
62        self.name
63    }
64
65    fn fips(&self) -> bool {
66        self.fips_allowed && super::fips()
67    }
68}
69
70impl fmt::Debug for KxGroup {
71    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72        self.name.fmt(f)
73    }
74}
75
76/// Ephemeral ECDH on curve25519 (see RFC7748)
77pub static X25519: &dyn SupportedKxGroup = &KxGroup {
78    name: NamedGroup::X25519,
79    agreement_algorithm: &agreement::X25519,
80
81    // "Curves that are included in SP 800-186 but not included in SP 800-56Arev3 are
82    //  not approved for key agreement. E.g., the ECDH X25519 and X448 key agreement
83    //  schemes (defined in RFC 7748) that use Curve25519 and Curve448, respectively,
84    //  are not compliant to SP 800-56Arev3."
85    // -- <https://csrc.nist.gov/csrc/media/Projects/cryptographic-module-validation-program/documents/fips%20140-3/FIPS%20140-3%20IG.pdf>
86    fips_allowed: false,
87
88    pub_key_validator: |point: &[u8]| point.len() == 32,
89};
90
91/// Ephemeral ECDH on secp256r1 (aka NIST-P256)
92pub static SECP256R1: &dyn SupportedKxGroup = &KxGroup {
93    name: NamedGroup::secp256r1,
94    agreement_algorithm: &agreement::ECDH_P256,
95    fips_allowed: true,
96    pub_key_validator: uncompressed_point,
97};
98
99/// Ephemeral ECDH on secp384r1 (aka NIST-P384)
100pub static SECP384R1: &dyn SupportedKxGroup = &KxGroup {
101    name: NamedGroup::secp384r1,
102    agreement_algorithm: &agreement::ECDH_P384,
103    fips_allowed: true,
104    pub_key_validator: uncompressed_point,
105};
106
107fn uncompressed_point(point: &[u8]) -> bool {
108    // See `UncompressedPointRepresentation`, which is a retelling of
109    // SEC1 section 2.3.3 "Elliptic-Curve-Point-to-Octet-String Conversion"
110    // <https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.8.2>
111    matches!(point.first(), Some(0x04))
112}
113
114/// An in-progress key exchange.  This has the algorithm,
115/// our private key, and our public key.
116struct KeyExchange {
117    name: NamedGroup,
118    agreement_algorithm: &'static agreement::Algorithm,
119    priv_key: agreement::EphemeralPrivateKey,
120    pub_key: agreement::PublicKey,
121    pub_key_validator: fn(&[u8]) -> bool,
122}
123
124impl ActiveKeyExchange for KeyExchange {
125    /// Completes the key exchange, given the peer's public key.
126    fn complete(self: Box<Self>, peer: &[u8]) -> Result<SharedSecret, Error> {
127        if !(self.pub_key_validator)(peer) {
128            return Err(PeerMisbehaved::InvalidKeyShare.into());
129        }
130        let peer_key = agreement::UnparsedPublicKey::new(self.agreement_algorithm, peer);
131        super::ring_shim::agree_ephemeral(self.priv_key, &peer_key)
132            .map_err(|_| PeerMisbehaved::InvalidKeyShare.into())
133    }
134
135    /// Return the group being used.
136    fn group(&self) -> NamedGroup {
137        self.name
138    }
139
140    /// Return the public key being used.
141    fn pub_key(&self) -> &[u8] {
142        self.pub_key.as_ref()
143    }
144}
145
146#[cfg(test)]
147mod tests {
148    use std::format;
149
150    #[test]
151    fn kxgroup_fmt_yields_name() {
152        assert_eq!("X25519", format!("{:?}", super::X25519));
153    }
154}
155
156#[cfg(bench)]
157mod benchmarks {
158    #[bench]
159    fn bench_x25519(b: &mut test::Bencher) {
160        bench_any(b, super::X25519);
161    }
162
163    #[bench]
164    fn bench_ecdh_p256(b: &mut test::Bencher) {
165        bench_any(b, super::SECP256R1);
166    }
167
168    #[bench]
169    fn bench_ecdh_p384(b: &mut test::Bencher) {
170        bench_any(b, super::SECP384R1);
171    }
172
173    fn bench_any(b: &mut test::Bencher, kxg: &dyn super::SupportedKxGroup) {
174        b.iter(|| {
175            let akx = kxg.start().unwrap();
176            let pub_key = akx.pub_key().to_vec();
177            test::black_box(akx.complete(&pub_key).unwrap());
178        });
179    }
180}