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}
189