1 // 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 
17 use bssl_crypto::x25519;
18 use mls_rs_core::crypto::{CipherSuite, HpkePublicKey, HpkeSecretKey};
19 use mls_rs_core::error::IntoAnyError;
20 use mls_rs_crypto_traits::{Curve, DhType};
21 
22 use core::array::TryFromSliceError;
23 use thiserror::Error;
24 
25 /// Errors returned from ECDH.
26 #[derive(Debug, Error)]
27 pub 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 
55 impl IntoAnyError for EcdhError {
into_dyn_error(self) -> Result<Box<dyn std::error::Error + Send + Sync>, Self>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)]
63 pub struct Ecdh(Curve);
64 
65 impl Ecdh {
66     /// Creates a new Ecdh.
new(cipher_suite: CipherSuite) -> Option<Self>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)]
75 impl DhType for Ecdh {
76     type Error = EcdhError;
77 
dh( &self, secret_key: &HpkeSecretKey, public_key: &HpkePublicKey, ) -> Result<Vec<u8>, Self::Error>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 
to_public(&self, secret_key: &HpkeSecretKey) -> Result<HpkePublicKey, Self::Error>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 
generate(&self) -> Result<(HpkeSecretKey, HpkePublicKey), Self::Error>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 
bitmask_for_rejection_sampling(&self) -> Option<u8>130     fn bitmask_for_rejection_sampling(&self) -> Option<u8> {
131         self.0.curve_bitmask()
132     }
133 
public_key_validate(&self, key: &HpkePublicKey) -> Result<(), Self::Error>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 
secret_key_size(&self) -> usize150     fn secret_key_size(&self) -> usize {
151         self.0.secret_key_size()
152     }
153 }
154 
155 #[cfg(all(not(mls_build_async), test))]
156 mod 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]
dh()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]
dh_invalid_key()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]
to_public()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]
to_public_invalid_key()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]
generate()236     fn generate() {
237         let x25519 = Ecdh::new(CipherSuite::CURVE25519_AES128).unwrap();
238         assert!(x25519.generate().is_ok());
239     }
240 
241     #[test]
public_key_validate()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]
public_key_validate_invalid_key()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]
unsupported_cipher_suites()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 }
281