xref: /aosp_15_r20/system/security/mls/mls-rs-crypto-boringssl/src/aead.rs (revision e1997b9af69e3155ead6e072d106a0077849ffba)
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