blob: eaa33a92d426c5a058a973a8effa4d3445579a26 [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//! Authenticated encryption with additional data.
16
17use bssl_crypto::aead::{Aead, Aes128Gcm, Aes256Gcm, Chacha20Poly1305};
18use mls_rs_core::crypto::CipherSuite;
19use mls_rs_core::error::IntoAnyError;
20use mls_rs_crypto_traits::{AeadId, AeadType, AES_TAG_LEN};
21
22use core::array::TryFromSliceError;
23use thiserror::Error;
24
25/// Errors returned from AEAD.
26#[derive(Debug, Error)]
27pub 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
66impl IntoAnyError for AeadError {
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)]
74pub struct AeadWrapper(AeadId);
75
76impl AeadWrapper {
77 /// Creates a new AeadWrapper.
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)]
86impl AeadType for AeadWrapper {
87 type Error = AeadError;
88
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
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)]
177 fn key_size(&self) -> usize {
178 self.0.key_size()
179 }
180
181 fn nonce_size(&self) -> usize {
182 self.0.nonce_size()
183 }
184
185 fn aead_id(&self) -> u16 {
186 self.0 as u16
187 }
188}
189
190#[cfg(all(not(mls_build_async), test))]
191mod 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
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]
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]
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]
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]
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]
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}