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 //! Authenticated encryption with additional data. 16 17 use bssl_crypto::aead::{Aead, Aes128Gcm, Aes256Gcm, Chacha20Poly1305}; 18 use mls_rs_core::crypto::CipherSuite; 19 use mls_rs_core::error::IntoAnyError; 20 use mls_rs_crypto_traits::{AeadId, AeadType, AES_TAG_LEN}; 21 22 use core::array::TryFromSliceError; 23 use thiserror::Error; 24 25 /// Errors returned from AEAD. 26 #[derive(Debug, Error)] 27 pub enum AeadError { 28 /// Error returned when conversion from slice to array fails. 29 #[error(transparent)] 30 TryFromSliceError(#[from] TryFromSliceError), 31 /// Error returned when the ciphertext is invalid. 32 #[error("AEAD ciphertext was invalid")] 33 InvalidCiphertext, 34 /// Error returned when the ciphertext length is too short. 35 #[error("AEAD ciphertext of length {len}, expected length at least {min_len}")] 36 TooShortCiphertext { 37 /// Invalid ciphertext length. 38 len: usize, 39 /// Minimum ciphertext length. 40 min_len: usize, 41 }, 42 /// Error returned when the plaintext is empty. 43 #[error("message cannot be empty")] 44 EmptyPlaintext, 45 /// Error returned when the key length is invalid. 46 #[error("AEAD key of invalid length {len}, expected length {expected_len}")] 47 InvalidKeyLen { 48 /// Invalid key length. 49 len: usize, 50 /// Expected key length. 51 expected_len: usize, 52 }, 53 /// Error returned when the nonce size is invalid. 54 #[error("AEAD nonce of invalid length {len}, expected length {expected_len}")] 55 InvalidNonceLen { 56 /// Invalid nonce length. 57 len: usize, 58 /// Expected nonce length. 59 expected_len: usize, 60 }, 61 /// Error returned when unsupported cipher suite is requested. 62 #[error("unsupported cipher suite")] 63 UnsupportedCipherSuite, 64 } 65 66 impl IntoAnyError for AeadError { into_dyn_error(self) -> Result<Box<dyn std::error::Error + Send + Sync>, Self>67 fn into_dyn_error(self) -> Result<Box<dyn std::error::Error + Send + Sync>, Self> { 68 Ok(self.into()) 69 } 70 } 71 72 /// AeadType implementation backed by BoringSSL. 73 #[derive(Clone)] 74 pub struct AeadWrapper(AeadId); 75 76 impl AeadWrapper { 77 /// Creates a new AeadWrapper. new(cipher_suite: CipherSuite) -> Option<Self>78 pub fn new(cipher_suite: CipherSuite) -> Option<Self> { 79 AeadId::new(cipher_suite).map(Self) 80 } 81 } 82 83 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)] 84 #[cfg_attr(all(target_arch = "wasm32", mls_build_async), maybe_async::must_be_async(?Send))] 85 #[cfg_attr(all(not(target_arch = "wasm32"), mls_build_async), maybe_async::must_be_async)] 86 impl AeadType for AeadWrapper { 87 type Error = AeadError; 88 seal<'a>( &self, key: &[u8], data: &[u8], aad: Option<&'a [u8]>, nonce: &[u8], ) -> Result<Vec<u8>, AeadError>89 async fn seal<'a>( 90 &self, 91 key: &[u8], 92 data: &[u8], 93 aad: Option<&'a [u8]>, 94 nonce: &[u8], 95 ) -> Result<Vec<u8>, AeadError> { 96 if data.is_empty() { 97 return Err(AeadError::EmptyPlaintext); 98 } 99 if key.len() != self.key_size() { 100 return Err(AeadError::InvalidKeyLen { len: key.len(), expected_len: self.key_size() }); 101 } 102 if nonce.len() != self.nonce_size() { 103 return Err(AeadError::InvalidNonceLen { 104 len: nonce.len(), 105 expected_len: self.nonce_size(), 106 }); 107 } 108 109 let nonce_array = nonce[..self.nonce_size()].try_into()?; 110 111 match self.0 { 112 AeadId::Aes128Gcm => { 113 let cipher = Aes128Gcm::new(key[..self.key_size()].try_into()?); 114 Ok(cipher.seal(nonce_array, data, aad.unwrap_or_default())) 115 } 116 AeadId::Aes256Gcm => { 117 let cipher = Aes256Gcm::new(key[..self.key_size()].try_into()?); 118 Ok(cipher.seal(nonce_array, data, aad.unwrap_or_default())) 119 } 120 AeadId::Chacha20Poly1305 => { 121 let cipher = Chacha20Poly1305::new(key[..self.key_size()].try_into()?); 122 Ok(cipher.seal(nonce_array, data, aad.unwrap_or_default())) 123 } 124 _ => Err(AeadError::UnsupportedCipherSuite), 125 } 126 } 127 open<'a>( &self, key: &[u8], ciphertext: &[u8], aad: Option<&'a [u8]>, nonce: &[u8], ) -> Result<Vec<u8>, AeadError>128 async fn open<'a>( 129 &self, 130 key: &[u8], 131 ciphertext: &[u8], 132 aad: Option<&'a [u8]>, 133 nonce: &[u8], 134 ) -> Result<Vec<u8>, AeadError> { 135 if ciphertext.len() < AES_TAG_LEN { 136 return Err(AeadError::TooShortCiphertext { 137 len: ciphertext.len(), 138 min_len: AES_TAG_LEN, 139 }); 140 } 141 if key.len() != self.key_size() { 142 return Err(AeadError::InvalidKeyLen { len: key.len(), expected_len: self.key_size() }); 143 } 144 if nonce.len() != self.nonce_size() { 145 return Err(AeadError::InvalidNonceLen { 146 len: nonce.len(), 147 expected_len: self.nonce_size(), 148 }); 149 } 150 151 let nonce_array = nonce[..self.nonce_size()].try_into()?; 152 153 match self.0 { 154 AeadId::Aes128Gcm => { 155 let cipher = Aes128Gcm::new(key[..self.key_size()].try_into()?); 156 cipher 157 .open(nonce_array, ciphertext, aad.unwrap_or_default()) 158 .ok_or(AeadError::InvalidCiphertext) 159 } 160 AeadId::Aes256Gcm => { 161 let cipher = Aes256Gcm::new(key[..self.key_size()].try_into()?); 162 cipher 163 .open(nonce_array, ciphertext, aad.unwrap_or_default()) 164 .ok_or(AeadError::InvalidCiphertext) 165 } 166 AeadId::Chacha20Poly1305 => { 167 let cipher = Chacha20Poly1305::new(key[..self.key_size()].try_into()?); 168 cipher 169 .open(nonce_array, ciphertext, aad.unwrap_or_default()) 170 .ok_or(AeadError::InvalidCiphertext) 171 } 172 _ => Err(AeadError::UnsupportedCipherSuite), 173 } 174 } 175 176 #[inline(always)] key_size(&self) -> usize177 fn key_size(&self) -> usize { 178 self.0.key_size() 179 } 180 nonce_size(&self) -> usize181 fn nonce_size(&self) -> usize { 182 self.0.nonce_size() 183 } 184 aead_id(&self) -> u16185 fn aead_id(&self) -> u16 { 186 self.0 as u16 187 } 188 } 189 190 #[cfg(all(not(mls_build_async), test))] 191 mod test { 192 use super::{AeadError, AeadWrapper}; 193 use assert_matches::assert_matches; 194 use mls_rs_core::crypto::CipherSuite; 195 use mls_rs_crypto_traits::{AeadType, AES_TAG_LEN}; 196 get_aeads() -> Vec<AeadWrapper>197 fn get_aeads() -> Vec<AeadWrapper> { 198 [ 199 CipherSuite::CURVE25519_AES128, 200 CipherSuite::CURVE25519_CHACHA, 201 CipherSuite::CURVE448_AES256, 202 ] 203 .into_iter() 204 .map(|suite| AeadWrapper::new(suite).unwrap()) 205 .collect() 206 } 207 208 #[test] seal_and_open()209 fn seal_and_open() { 210 for aead in get_aeads() { 211 let key = vec![42u8; aead.key_size()]; 212 let nonce = vec![42u8; aead.nonce_size()]; 213 let plaintext = b"message"; 214 215 let ciphertext = aead.seal(&key, plaintext, None, &nonce).unwrap(); 216 assert_eq!( 217 plaintext, 218 aead.open(&key, ciphertext.as_slice(), None, &nonce).unwrap().as_slice(), 219 "open failed for AEAD with ID {}", 220 aead.aead_id(), 221 ); 222 } 223 } 224 225 #[test] seal_and_open_with_invalid_key()226 fn seal_and_open_with_invalid_key() { 227 for aead in get_aeads() { 228 let data = b"top secret data that's long enough"; 229 let nonce = vec![42u8; aead.nonce_size()]; 230 231 let key_short = vec![42u8; aead.key_size() - 1]; 232 assert_matches!( 233 aead.seal(&key_short, data, None, &nonce), 234 Err(AeadError::InvalidKeyLen { .. }), 235 "seal with short key should fail for AEAD with ID {}", 236 aead.aead_id(), 237 ); 238 assert_matches!( 239 aead.open(&key_short, data, None, &nonce), 240 Err(AeadError::InvalidKeyLen { .. }), 241 "open with short key should fail for AEAD with ID {}", 242 aead.aead_id(), 243 ); 244 245 let key_long = vec![42u8; aead.key_size() + 1]; 246 assert_matches!( 247 aead.seal(&key_long, data, None, &nonce), 248 Err(AeadError::InvalidKeyLen { .. }), 249 "seal with long key should fail for AEAD with ID {}", 250 aead.aead_id(), 251 ); 252 assert_matches!( 253 aead.open(&key_long, data, None, &nonce), 254 Err(AeadError::InvalidKeyLen { .. }), 255 "open with long key should fail for AEAD with ID {}", 256 aead.aead_id(), 257 ); 258 } 259 } 260 261 #[test] invalid_ciphertext()262 fn invalid_ciphertext() { 263 for aead in get_aeads() { 264 let key = vec![42u8; aead.key_size()]; 265 let nonce = vec![42u8; aead.nonce_size()]; 266 267 let ciphertext_short = [0u8; AES_TAG_LEN - 1]; 268 assert_matches!( 269 aead.open(&key, &ciphertext_short, None, &nonce), 270 Err(AeadError::TooShortCiphertext { .. }), 271 "open with short ciphertext should fail for AEAD with ID {}", 272 aead.aead_id(), 273 ); 274 } 275 } 276 277 #[test] associated_data_mismatch()278 fn associated_data_mismatch() { 279 for aead in get_aeads() { 280 let key = vec![42u8; aead.key_size()]; 281 let nonce = vec![42u8; aead.nonce_size()]; 282 283 let ciphertext = aead.seal(&key, b"message", Some(b"foo"), &nonce).unwrap(); 284 assert_matches!( 285 aead.open(&key, &ciphertext, Some(b"bar"), &nonce), 286 Err(AeadError::InvalidCiphertext), 287 "open with incorrect associated data should fail for AEAD with ID {}", 288 aead.aead_id(), 289 ); 290 assert_matches!( 291 aead.open(&key, &ciphertext, None, &nonce), 292 Err(AeadError::InvalidCiphertext), 293 "open with incorrect associated data should fail for AEAD with ID {}", 294 aead.aead_id(), 295 ); 296 } 297 } 298 299 #[test] invalid_nonce()300 fn invalid_nonce() { 301 for aead in get_aeads() { 302 let key = vec![42u8; aead.key_size()]; 303 let data = b"top secret data that's long enough"; 304 305 let nonce_short = vec![42u8; aead.nonce_size() - 1]; 306 assert_matches!( 307 aead.seal(&key, data, None, &nonce_short), 308 Err(AeadError::InvalidNonceLen { .. }), 309 "seal with short nonce should fail for AEAD with ID {}", 310 aead.aead_id(), 311 ); 312 assert_matches!( 313 aead.open(&key, data, None, &nonce_short), 314 Err(AeadError::InvalidNonceLen { .. }), 315 "open with short nonce should fail for AEAD with ID {}", 316 aead.aead_id(), 317 ); 318 319 let nonce_long = vec![42u8; aead.nonce_size() + 1]; 320 assert_matches!( 321 aead.seal(&key, data, None, &nonce_long), 322 Err(AeadError::InvalidNonceLen { .. }), 323 "seal with long nonce should fail for AEAD with ID {}", 324 aead.aead_id(), 325 ); 326 assert_matches!( 327 aead.open(&key, data, None, &nonce_long), 328 Err(AeadError::InvalidNonceLen { .. }), 329 "open with long nonce should fail for AEAD with ID {}", 330 aead.aead_id(), 331 ); 332 } 333 } 334 } 335