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