rustls/crypto/aws_lc_rs/
kx.rs

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