blob: 74ba8df72569a902a2eb2b08e0ede1ea4552fe3d [file] [log] [blame]
Cindy Lin6ec3c2b2024-05-16 07:39:23 +00001// Copyright 2024, The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Elliptic curve Diffie–Hellman.
16
17use bssl_crypto::x25519;
18use mls_rs_core::crypto::{CipherSuite, HpkePublicKey, HpkeSecretKey};
19use mls_rs_core::error::IntoAnyError;
20use mls_rs_crypto_traits::{Curve, DhType};
21
22use core::array::TryFromSliceError;
23use thiserror::Error;
24
25/// Errors returned from ECDH.
26#[derive(Debug, Error)]
27pub enum EcdhError {
28 /// Error returned when conversion from slice to array fails.
29 #[error(transparent)]
30 TryFromSliceError(#[from] TryFromSliceError),
31 /// Error returned when the public key is invalid.
32 #[error("ECDH public key was invalid")]
33 InvalidPubKey,
34 /// Error returned when the private key length is invalid.
35 #[error("ECDH private key of invalid length {len}, expected length {expected_len}")]
36 InvalidPrivKeyLen {
37 /// Invalid key length.
38 len: usize,
39 /// Expected key length.
40 expected_len: usize,
41 },
42 /// Error returned when the public key length is invalid.
43 #[error("ECDH public key of invalid length {len}, expected length {expected_len}")]
44 InvalidPubKeyLen {
45 /// Invalid key length.
46 len: usize,
47 /// Expected key length.
48 expected_len: usize,
49 },
50 /// Error returned when unsupported cipher suite is requested.
51 #[error("unsupported cipher suite")]
52 UnsupportedCipherSuite,
53}
54
55impl IntoAnyError for EcdhError {
56 fn into_dyn_error(self) -> Result<Box<dyn std::error::Error + Send + Sync>, Self> {
57 Ok(self.into())
58 }
59}
60
61/// DhType implementation backed by BoringSSL.
62#[derive(Clone, Debug, Eq, PartialEq)]
63pub struct Ecdh(Curve);
64
65impl Ecdh {
66 /// Creates a new Ecdh.
67 pub fn new(cipher_suite: CipherSuite) -> Option<Self> {
68 Curve::from_ciphersuite(cipher_suite, /*for_sig=*/ false).map(Self)
69 }
70}
71
72#[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
73#[cfg_attr(all(target_arch = "wasm32", mls_build_async), maybe_async::must_be_async(?Send))]
74#[cfg_attr(all(not(target_arch = "wasm32"), mls_build_async), maybe_async::must_be_async)]
75impl DhType for Ecdh {
76 type Error = EcdhError;
77
78 async fn dh(
79 &self,
80 secret_key: &HpkeSecretKey,
81 public_key: &HpkePublicKey,
82 ) -> Result<Vec<u8>, Self::Error> {
83 if self.0 != Curve::X25519 {
84 return Err(EcdhError::UnsupportedCipherSuite);
85 }
86 if secret_key.len() != x25519::PRIVATE_KEY_LEN {
87 return Err(EcdhError::InvalidPrivKeyLen {
88 len: secret_key.len(),
89 expected_len: x25519::PRIVATE_KEY_LEN,
90 });
91 }
92 if public_key.len() != x25519::PUBLIC_KEY_LEN {
93 return Err(EcdhError::InvalidPubKeyLen {
94 len: public_key.len(),
95 expected_len: x25519::PUBLIC_KEY_LEN,
96 });
97 }
98
99 let private_key = x25519::PrivateKey(secret_key[..x25519::PRIVATE_KEY_LEN].try_into()?);
100 match private_key.compute_shared_key(public_key[..x25519::PUBLIC_KEY_LEN].try_into()?) {
101 Some(x) => Ok(x.to_vec()),
102 None => Err(EcdhError::InvalidPubKey),
103 }
104 }
105
106 async fn to_public(&self, secret_key: &HpkeSecretKey) -> Result<HpkePublicKey, Self::Error> {
107 if self.0 != Curve::X25519 {
108 return Err(EcdhError::UnsupportedCipherSuite);
109 }
110 if secret_key.len() != x25519::PRIVATE_KEY_LEN {
111 return Err(EcdhError::InvalidPrivKeyLen {
112 len: secret_key.len(),
113 expected_len: x25519::PRIVATE_KEY_LEN,
114 });
115 }
116
117 let private_key = x25519::PrivateKey(secret_key[..x25519::PRIVATE_KEY_LEN].try_into()?);
118 Ok(private_key.to_public().to_vec().into())
119 }
120
121 async fn generate(&self) -> Result<(HpkeSecretKey, HpkePublicKey), Self::Error> {
122 if self.0 != Curve::X25519 {
123 return Err(EcdhError::UnsupportedCipherSuite);
124 }
125
126 let (public_key, private_key) = x25519::PrivateKey::generate();
127 Ok((private_key.0.to_vec().into(), public_key.to_vec().into()))
128 }
129
130 fn bitmask_for_rejection_sampling(&self) -> Option<u8> {
131 self.0.curve_bitmask()
132 }
133
134 fn public_key_validate(&self, key: &HpkePublicKey) -> Result<(), Self::Error> {
135 if self.0 != Curve::X25519 {
136 return Err(EcdhError::UnsupportedCipherSuite);
137 }
138
139 // bssl_crypto does not implement validation of curve25519 public keys.
140 // Note: Neither does x25519_dalek used by RustCrypto's implementation of this function.
141 if key.len() != x25519::PUBLIC_KEY_LEN {
142 return Err(EcdhError::InvalidPubKeyLen {
143 len: key.len(),
144 expected_len: x25519::PUBLIC_KEY_LEN,
145 });
146 }
147 Ok(())
148 }
149
150 fn secret_key_size(&self) -> usize {
151 self.0.secret_key_size()
152 }
153}
154
155#[cfg(all(not(mls_build_async), test))]
156mod test {
157 use super::{DhType, Ecdh, EcdhError};
158 use crate::test_helpers::decode_hex;
159 use assert_matches::assert_matches;
160 use mls_rs_core::crypto::{CipherSuite, HpkePublicKey, HpkeSecretKey};
161
162 #[test]
163 fn dh() {
164 // https://github.com/C2SP/wycheproof/blob/cd27d6419bedd83cbd24611ec54b6d4bfdb0cdca/testvectors/x25519_test.json#L23
165 let private_key = HpkeSecretKey::from(
166 decode_hex::<32>("c8a9d5a91091ad851c668b0736c1c9a02936c0d3ad62670858088047ba057475")
167 .to_vec(),
168 );
169 let public_key = HpkePublicKey::from(
170 decode_hex::<32>("504a36999f489cd2fdbc08baff3d88fa00569ba986cba22548ffde80f9806829")
171 .to_vec(),
172 );
173 let expected_shared_secret: [u8; 32] =
174 decode_hex("436a2c040cf45fea9b29a0cb81b1f41458f863d0d61b453d0a982720d6d61320");
175
176 let x25519 = Ecdh::new(CipherSuite::CURVE25519_AES128).unwrap();
177 assert_eq!(x25519.dh(&private_key, &public_key).unwrap(), expected_shared_secret);
178 }
179
180 #[test]
181 fn dh_invalid_key() {
182 let x25519 = Ecdh::new(CipherSuite::CURVE25519_AES128).unwrap();
183
184 let private_key_short =
185 HpkeSecretKey::from(decode_hex::<16>("c8a9d5a91091ad851c668b0736c1c9a0").to_vec());
186 let public_key = HpkePublicKey::from(
187 decode_hex::<32>("504a36999f489cd2fdbc08baff3d88fa00569ba986cba22548ffde80f9806829")
188 .to_vec(),
189 );
190 assert_matches!(
191 x25519.dh(&private_key_short, &public_key),
192 Err(EcdhError::InvalidPrivKeyLen { .. })
193 );
194
195 let private_key = HpkeSecretKey::from(
196 decode_hex::<32>("c8a9d5a91091ad851c668b0736c1c9a02936c0d3ad62670858088047ba057475")
197 .to_vec(),
198 );
199 let public_key_short =
200 HpkePublicKey::from(decode_hex::<16>("504a36999f489cd2fdbc08baff3d88fa").to_vec());
201 assert_matches!(
202 x25519.dh(&private_key, &public_key_short),
203 Err(EcdhError::InvalidPubKeyLen { .. })
204 );
205 }
206
207 #[test]
208 fn to_public() {
209 // https://www.rfc-editor.org/rfc/rfc7748.html#section-6.1
210 let private_key = HpkeSecretKey::from(
211 decode_hex::<32>("77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a")
212 .to_vec(),
213 );
214 let expected_public_key = HpkePublicKey::from(
215 decode_hex::<32>("8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a")
216 .to_vec(),
217 );
218
219 let x25519 = Ecdh::new(CipherSuite::CURVE25519_CHACHA).unwrap();
220 assert_eq!(x25519.to_public(&private_key).unwrap(), expected_public_key);
221 }
222
223 #[test]
224 fn to_public_invalid_key() {
225 let private_key_short =
226 HpkeSecretKey::from(decode_hex::<16>("c8a9d5a91091ad851c668b0736c1c9a0").to_vec());
227
228 let x25519 = Ecdh::new(CipherSuite::CURVE25519_CHACHA).unwrap();
229 assert_matches!(
230 x25519.to_public(&private_key_short),
231 Err(EcdhError::InvalidPrivKeyLen { .. })
232 );
233 }
234
235 #[test]
236 fn generate() {
237 let x25519 = Ecdh::new(CipherSuite::CURVE25519_AES128).unwrap();
238 assert!(x25519.generate().is_ok());
239 }
240
241 #[test]
242 fn public_key_validate() {
243 // https://www.rfc-editor.org/rfc/rfc7748.html#section-6.1
244 let public_key = HpkePublicKey::from(
245 decode_hex::<32>("8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a")
246 .to_vec(),
247 );
248
249 let x25519 = Ecdh::new(CipherSuite::CURVE25519_AES128).unwrap();
250 assert!(x25519.public_key_validate(&public_key).is_ok());
251 }
252
253 #[test]
254 fn public_key_validate_invalid_key() {
255 let public_key_short =
256 HpkePublicKey::from(decode_hex::<16>("504a36999f489cd2fdbc08baff3d88fa").to_vec());
257
258 let x25519 = Ecdh::new(CipherSuite::CURVE25519_AES128).unwrap();
259 assert_matches!(
260 x25519.public_key_validate(&public_key_short),
261 Err(EcdhError::InvalidPubKeyLen { .. })
262 );
263 }
264
265 #[test]
266 fn unsupported_cipher_suites() {
267 for suite in vec![
268 CipherSuite::P256_AES128,
269 CipherSuite::P384_AES256,
270 CipherSuite::P521_AES256,
271 CipherSuite::CURVE448_CHACHA,
272 CipherSuite::CURVE448_AES256,
273 ] {
274 assert_matches!(
275 Ecdh::new(suite).unwrap().generate(),
276 Err(EcdhError::UnsupportedCipherSuite)
277 );
278 }
279 }
280}