1 // Copyright 2023 The BoringSSL Authors
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 //     https://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 //! AEAD couples confidentiality and integrity in a single primitive. AEAD
18 //! algorithms take a key and then can seal and open individual messages. Each
19 //! message has a unique, per-message nonce and, optionally, additional data
20 //! which is authenticated but not included in the ciphertext.
21 //!
22 //! No two distinct plaintexts must ever be sealed using the same (key, nonce)
23 //! pair. It is up to the user of these algorithms to ensure this. For example,
24 //! when encrypting a stream of messages (e.g. over a TCP socket) a message
25 //! counter can provide distinct nonces as long as the key is randomly generated
26 //! for the specific connection and is distinct in each direction.
27 //!
28 //! To implement that example:
29 //!
30 //! ```
31 //! use bssl_crypto::aead::{Aead, Aes256Gcm};
32 //!
33 //! let key = bssl_crypto::rand_array();
34 //! let aead = Aes256Gcm::new(&key);
35 //!
36 //! let mut message_counter: u64 = 0;
37 //! let mut nonce = bssl_crypto::rand_array();
38 //! nonce[4..].copy_from_slice(message_counter.to_be_bytes().as_slice());
39 //! message_counter += 1;
40 //! let plaintext = b"message";
41 //! let ciphertext = aead.seal(&nonce, plaintext, b"");
42 //!
43 //! let decrypted = aead.open(&nonce, ciphertext.as_slice(), b"");
44 //! assert_eq!(plaintext, decrypted.unwrap().as_slice());
45 //! ```
46 
47 use crate::{with_output_array, with_output_vec, with_output_vec_fallible, FfiMutSlice, FfiSlice};
48 use alloc::vec::Vec;
49 
50 /// The error type returned when a fallible, in-place operation fails.
51 #[derive(Debug)]
52 pub struct InvalidCiphertext;
53 
54 /// Authenticated Encryption with Associated Data (AEAD) algorithm trait.
55 pub trait Aead {
56     /// The type of tags produced by this AEAD. Generally a u8 array of fixed
57     /// length.
58     type Tag: AsRef<[u8]>;
59 
60     /// The type of nonces used by this AEAD. Generally a u8 array of fixed
61     /// length.
62     type Nonce: AsRef<[u8]>;
63 
64     /// Encrypt and authenticate `plaintext`, and authenticate `ad`, returning
65     /// the result as a freshly allocated [`Vec`]. The `nonce` must never
66     /// be used in any sealing operation with the same key, ever again.
seal(&self, nonce: &Self::Nonce, plaintext: &[u8], ad: &[u8]) -> Vec<u8>67     fn seal(&self, nonce: &Self::Nonce, plaintext: &[u8], ad: &[u8]) -> Vec<u8>;
68 
69     /// Encrypt and authenticate `plaintext`, and authenticate `ad`, writing
70     /// the ciphertext over `plaintext` and additionally returning the calculated
71     /// tag, which is usually appended to the ciphertext. The `nonce` must never
72     /// be used in any sealing operation with the same key, ever again.
seal_in_place(&self, nonce: &Self::Nonce, plaintext: &mut [u8], ad: &[u8]) -> Self::Tag73     fn seal_in_place(&self, nonce: &Self::Nonce, plaintext: &mut [u8], ad: &[u8]) -> Self::Tag;
74 
75     /// Authenticate `ciphertext` and `ad` and, if valid, decrypt `ciphertext`,
76     /// returning the original plaintext in a newly allocated [`Vec`]. The `nonce`
77     /// must be the same value as given to the sealing operation that produced
78     /// `ciphertext`.
open(&self, nonce: &Self::Nonce, ciphertext: &[u8], ad: &[u8]) -> Option<Vec<u8>>79     fn open(&self, nonce: &Self::Nonce, ciphertext: &[u8], ad: &[u8]) -> Option<Vec<u8>>;
80 
81     /// Authenticate `ciphertext` and `ad` using `tag` and, if valid, decrypt
82     /// `ciphertext` in place. The `nonce` must be the same value as given to
83     /// the sealing operation that produced `ciphertext`.
open_in_place( &self, nonce: &Self::Nonce, ciphertext: &mut [u8], tag: &Self::Tag, ad: &[u8], ) -> Result<(), InvalidCiphertext>84     fn open_in_place(
85         &self,
86         nonce: &Self::Nonce,
87         ciphertext: &mut [u8],
88         tag: &Self::Tag,
89         ad: &[u8],
90     ) -> Result<(), InvalidCiphertext>;
91 }
92 
93 /// AES-128 in Galois Counter Mode.
94 pub struct Aes128Gcm(EvpAead<16, 12, 16>);
95 aead_algo!(Aes128Gcm, EVP_aead_aes_128_gcm, 16, 12, 16);
96 
97 /// AES-256 in Galois Counter Mode.
98 pub struct Aes256Gcm(EvpAead<32, 12, 16>);
99 aead_algo!(Aes256Gcm, EVP_aead_aes_256_gcm, 32, 12, 16);
100 
101 /// AES-128 in GCM-SIV mode (which is different from SIV mode!).
102 pub struct Aes128GcmSiv(EvpAead<16, 12, 16>);
103 aead_algo!(Aes128GcmSiv, EVP_aead_aes_128_gcm_siv, 16, 12, 16);
104 
105 /// AES-256 in GCM-SIV mode (which is different from SIV mode!).
106 pub struct Aes256GcmSiv(EvpAead<32, 12, 16>);
107 aead_algo!(Aes256GcmSiv, EVP_aead_aes_256_gcm_siv, 32, 12, 16);
108 
109 /// The AEAD built from ChaCha20 and Poly1305 as described in <https://datatracker.ietf.org/doc/html/rfc8439>.
110 pub struct Chacha20Poly1305(EvpAead<32, 12, 16>);
111 aead_algo!(Chacha20Poly1305, EVP_aead_chacha20_poly1305, 32, 12, 16);
112 
113 /// Chacha20Poly1305 with an extended nonce that makes random generation of nonces safe.
114 pub struct XChacha20Poly1305(EvpAead<32, 24, 16>);
115 aead_algo!(XChacha20Poly1305, EVP_aead_xchacha20_poly1305, 32, 24, 16);
116 
117 /// An internal struct that implements AEAD operations given an `EVP_AEAD`.
118 struct EvpAead<const KEY_LEN: usize, const NONCE_LEN: usize, const TAG_LEN: usize>(
119     *mut bssl_sys::EVP_AEAD_CTX,
120 );
121 
122 #[allow(clippy::unwrap_used)]
123 impl<const KEY_LEN: usize, const NONCE_LEN: usize, const TAG_LEN: usize>
124     EvpAead<KEY_LEN, NONCE_LEN, TAG_LEN>
125 {
126     // Tagged unsafe because `evp_aead` must be valid.
new(key: &[u8; KEY_LEN], evp_aead: *const bssl_sys::EVP_AEAD) -> Self127     unsafe fn new(key: &[u8; KEY_LEN], evp_aead: *const bssl_sys::EVP_AEAD) -> Self {
128         // `evp_aead` is assumed to be valid. The function will validate
129         // the other lengths and return NULL on error. In that case we
130         // crash the address space because that should never happen.
131         let ptr =
132             unsafe { bssl_sys::EVP_AEAD_CTX_new(evp_aead, key.as_ffi_ptr(), key.len(), TAG_LEN) };
133         assert!(!ptr.is_null());
134         Self(ptr)
135     }
136 
seal(&self, nonce: &[u8; NONCE_LEN], plaintext: &[u8], ad: &[u8]) -> Vec<u8>137     fn seal(&self, nonce: &[u8; NONCE_LEN], plaintext: &[u8], ad: &[u8]) -> Vec<u8> {
138         let max_output = plaintext.len() + TAG_LEN;
139         unsafe {
140             with_output_vec(max_output, |out_buf| {
141                 let mut out_len = 0usize;
142                 // Safety: the input buffers are all valid, with corresponding
143                 // ptr and length. The output buffer has at least `max_output`
144                 // bytes of space and that maximum is passed to
145                 // `EVP_AEAD_CTX_seal` as a limit.
146                 let result = bssl_sys::EVP_AEAD_CTX_seal(
147                     self.0,
148                     out_buf,
149                     &mut out_len,
150                     max_output,
151                     nonce.as_ffi_ptr(),
152                     nonce.len(),
153                     plaintext.as_ffi_ptr(),
154                     plaintext.len(),
155                     ad.as_ffi_ptr(),
156                     ad.len(),
157                 );
158                 // Sealing never fails unless there's a programmer error.
159                 assert_eq!(result, 1);
160                 // For the implemented AEADs, we should always have calculated
161                 // the overhead exactly.
162                 assert_eq!(out_len, max_output);
163                 // Safety: `out_len` bytes have been written to.
164                 out_len
165             })
166         }
167     }
168 
seal_in_place( &self, nonce: &[u8; NONCE_LEN], plaintext: &mut [u8], ad: &[u8], ) -> [u8; TAG_LEN]169     fn seal_in_place(
170         &self,
171         nonce: &[u8; NONCE_LEN],
172         plaintext: &mut [u8],
173         ad: &[u8],
174     ) -> [u8; TAG_LEN] {
175         // Safety: the buffers are all valid, with corresponding ptr and length.
176         // `tag_len` is passed at the maximum size of `tag` and `out_tag_len`
177         // is checked to ensure that the whole output was written to.
178         unsafe {
179             with_output_array(|tag, tag_len| {
180                 let mut out_tag_len = 0usize;
181                 let result = bssl_sys::EVP_AEAD_CTX_seal_scatter(
182                     self.0,
183                     plaintext.as_mut_ffi_ptr(),
184                     tag,
185                     &mut out_tag_len,
186                     tag_len,
187                     nonce.as_ffi_ptr(),
188                     nonce.len(),
189                     plaintext.as_ffi_ptr(),
190                     plaintext.len(),
191                     /*extra_in=*/ core::ptr::null(),
192                     /*extra_in_len=*/ 0,
193                     ad.as_ffi_ptr(),
194                     ad.len(),
195                 );
196                 // Failure indicates that one of the configured lengths was wrong.
197                 // Crashing is a good answer in that case.
198                 assert_eq!(result, 1);
199                 // The whole output must have been written to.
200                 assert_eq!(out_tag_len, TAG_LEN);
201             })
202         }
203     }
204 
open(&self, nonce: &[u8; NONCE_LEN], ciphertext: &[u8], ad: &[u8]) -> Option<Vec<u8>>205     fn open(&self, nonce: &[u8; NONCE_LEN], ciphertext: &[u8], ad: &[u8]) -> Option<Vec<u8>> {
206         if ciphertext.len() < TAG_LEN {
207             return None;
208         }
209         let max_output = ciphertext.len() - TAG_LEN;
210 
211         unsafe {
212             with_output_vec_fallible(max_output, |out_buf| {
213                 let mut out_len = 0usize;
214                 // Safety: the input buffers are all valid, with corresponding
215                 // ptr and length. The output buffer has at least `max_output`
216                 // bytes of space and that maximum is passed to
217                 // `EVP_AEAD_CTX_open` as a limit.
218                 let result = bssl_sys::EVP_AEAD_CTX_open(
219                     self.0,
220                     out_buf,
221                     &mut out_len,
222                     max_output,
223                     nonce.as_ffi_ptr(),
224                     nonce.len(),
225                     ciphertext.as_ffi_ptr(),
226                     ciphertext.len(),
227                     ad.as_ffi_ptr(),
228                     ad.len(),
229                 );
230                 if result == 1 {
231                     // Safety: `out_len` bytes have been written to.
232                     Some(out_len)
233                 } else {
234                     None
235                 }
236             })
237         }
238     }
239 
open_in_place( &self, nonce: &[u8; NONCE_LEN], ciphertext: &mut [u8], tag: &[u8; TAG_LEN], ad: &[u8], ) -> Result<(), InvalidCiphertext>240     fn open_in_place(
241         &self,
242         nonce: &[u8; NONCE_LEN],
243         ciphertext: &mut [u8],
244         tag: &[u8; TAG_LEN],
245         ad: &[u8],
246     ) -> Result<(), InvalidCiphertext> {
247         // Safety:
248         // - The buffers are all valid, with corresponding ptr and length
249         let result = unsafe {
250             bssl_sys::EVP_AEAD_CTX_open_gather(
251                 self.0,
252                 ciphertext.as_mut_ffi_ptr(),
253                 nonce.as_ffi_ptr(),
254                 nonce.len(),
255                 ciphertext.as_ffi_ptr(),
256                 ciphertext.len(),
257                 tag.as_ffi_ptr(),
258                 tag.len(),
259                 ad.as_ffi_ptr(),
260                 ad.len(),
261             )
262         };
263         if result == 1 {
264             Ok(())
265         } else {
266             Err(InvalidCiphertext)
267         }
268     }
269 }
270 
271 impl<const KEY_LEN: usize, const NONCE_LEN: usize, const TAG_LEN: usize> Drop
272     for EvpAead<KEY_LEN, NONCE_LEN, TAG_LEN>
273 {
drop(&mut self)274     fn drop(&mut self) {
275         // Safety: `self.0` was initialized by `EVP_AEAD_CTX_init` because all
276         // paths to create an `EvpAead` do so.
277         unsafe { bssl_sys::EVP_AEAD_CTX_free(self.0) }
278     }
279 }
280 
281 #[cfg(test)]
282 mod test {
283     use super::*;
284     use crate::test_helpers::{decode_hex, decode_hex_into_vec};
285 
check_aead_invariants< const NONCE_LEN: usize, const TAG_LEN: usize, A: Aead<Nonce = [u8; NONCE_LEN], Tag = [u8; TAG_LEN]>, >( aead: A, )286     fn check_aead_invariants<
287         const NONCE_LEN: usize,
288         const TAG_LEN: usize,
289         A: Aead<Nonce = [u8; NONCE_LEN], Tag = [u8; TAG_LEN]>,
290     >(
291         aead: A,
292     ) {
293         let plaintext = b"plaintext";
294         let ad = b"additional data";
295         let nonce: A::Nonce = [0u8; NONCE_LEN];
296 
297         let mut ciphertext = aead.seal(&nonce, plaintext, ad);
298         let plaintext2 = aead
299             .open(&nonce, ciphertext.as_slice(), ad)
300             .expect("should decrypt");
301         assert_eq!(plaintext, plaintext2.as_slice());
302 
303         ciphertext[0] ^= 1;
304         assert!(aead.open(&nonce, ciphertext.as_slice(), ad).is_none());
305         ciphertext[0] ^= 1;
306 
307         let (ciphertext_in_place, tag_slice) =
308             ciphertext.as_mut_slice().split_at_mut(plaintext.len());
309         let tag: [u8; TAG_LEN] = tag_slice.try_into().unwrap();
310         aead.open_in_place(&nonce, ciphertext_in_place, &tag, ad)
311             .expect("should decrypt");
312         assert_eq!(plaintext, ciphertext_in_place);
313 
314         let tag = aead.seal_in_place(&nonce, ciphertext_in_place, ad);
315         aead.open_in_place(&nonce, ciphertext_in_place, &tag, ad)
316             .expect("should decrypt");
317         assert_eq!(plaintext, ciphertext_in_place);
318 
319         assert!(aead.open(&nonce, b"tooshort", b"").is_none());
320     }
321 
322     #[test]
aes_128_gcm_invariants()323     fn aes_128_gcm_invariants() {
324         check_aead_invariants(Aes128Gcm::new(&[0u8; 16]));
325     }
326 
327     #[test]
aes_256_gcm_invariants()328     fn aes_256_gcm_invariants() {
329         check_aead_invariants(Aes256Gcm::new(&[0u8; 32]));
330     }
331 
332     #[test]
aes_128_gcm_siv_invariants()333     fn aes_128_gcm_siv_invariants() {
334         check_aead_invariants(Aes128GcmSiv::new(&[0u8; 16]));
335     }
336 
337     #[test]
aes_256_gcm_siv_invariants()338     fn aes_256_gcm_siv_invariants() {
339         check_aead_invariants(Aes256GcmSiv::new(&[0u8; 32]));
340     }
341 
342     #[test]
chacha20_poly1305_invariants()343     fn chacha20_poly1305_invariants() {
344         check_aead_invariants(Chacha20Poly1305::new(&[0u8; 32]));
345     }
346 
347     #[test]
xchacha20_poly1305_invariants()348     fn xchacha20_poly1305_invariants() {
349         check_aead_invariants(XChacha20Poly1305::new(&[0u8; 32]));
350     }
351 
352     struct TestCase<const KEY_LEN: usize, const NONCE_LEN: usize> {
353         key: [u8; KEY_LEN],
354         nonce: [u8; NONCE_LEN],
355         msg: Vec<u8>,
356         ad: Vec<u8>,
357         ciphertext: Vec<u8>,
358     }
359 
check_test_cases< const KEY_LEN: usize, const NONCE_LEN: usize, const TAG_LEN: usize, F: Fn(&[u8; KEY_LEN]) -> Box<dyn Aead<Nonce = [u8; NONCE_LEN], Tag = [u8; TAG_LEN]>>, >( new_func: F, test_cases: &[TestCase<KEY_LEN, NONCE_LEN>], )360     fn check_test_cases<
361         const KEY_LEN: usize,
362         const NONCE_LEN: usize,
363         const TAG_LEN: usize,
364         F: Fn(&[u8; KEY_LEN]) -> Box<dyn Aead<Nonce = [u8; NONCE_LEN], Tag = [u8; TAG_LEN]>>,
365     >(
366         new_func: F,
367         test_cases: &[TestCase<KEY_LEN, NONCE_LEN>],
368     ) {
369         for (test_num, test) in test_cases.iter().enumerate() {
370             let ctx = new_func(&test.key);
371             let ciphertext = ctx.seal(&test.nonce, test.msg.as_slice(), test.ad.as_slice());
372             assert_eq!(ciphertext, test.ciphertext, "Failed on test #{}", test_num);
373 
374             let plaintext = ctx
375                 .open(&test.nonce, ciphertext.as_slice(), test.ad.as_slice())
376                 .unwrap();
377             assert_eq!(plaintext, test.msg, "Decrypt failed on test #{}", test_num);
378         }
379     }
380 
381     #[test]
aes_128_gcm_siv()382     fn aes_128_gcm_siv() {
383         let test_cases: &[TestCase<16, 12>] = &[
384             TestCase {
385                 // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_siv_test.json
386                 // TC1 - Empty Message
387                 key: decode_hex("01000000000000000000000000000000"),
388                 nonce: decode_hex("030000000000000000000000"),
389                 msg: Vec::new(),
390                 ad: Vec::new(),
391                 ciphertext: decode_hex_into_vec("dc20e2d83f25705bb49e439eca56de25"),
392             },
393             TestCase {
394                 // TC2
395                 key: decode_hex("01000000000000000000000000000000"),
396                 nonce: decode_hex("030000000000000000000000"),
397                 msg: decode_hex_into_vec("0100000000000000"),
398                 ad: Vec::new(),
399                 ciphertext: decode_hex_into_vec("b5d839330ac7b786578782fff6013b815b287c22493a364c"),
400             },
401             TestCase {
402                 // TC14
403                 key: decode_hex("01000000000000000000000000000000"),
404                 nonce: decode_hex("030000000000000000000000"),
405                 msg: decode_hex_into_vec("02000000"),
406                 ad: decode_hex_into_vec("010000000000000000000000"),
407                 ciphertext: decode_hex_into_vec("a8fe3e8707eb1f84fb28f8cb73de8e99e2f48a14"),
408             },
409         ];
410 
411         check_test_cases(|key| Box::new(Aes128GcmSiv::new(key)), test_cases);
412     }
413 
414     #[test]
aes_256_gcm_siv()415     fn aes_256_gcm_siv() {
416         let test_cases: &[TestCase<32, 12>] = &[
417             TestCase {
418                 // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_siv_test.json
419                 // TC77
420                 key: decode_hex("0100000000000000000000000000000000000000000000000000000000000000"),
421                 nonce: decode_hex("030000000000000000000000"),
422                 msg: decode_hex_into_vec("0100000000000000"),
423                 ad: Vec::new(),
424                 ciphertext: decode_hex_into_vec("c2ef328e5c71c83b843122130f7364b761e0b97427e3df28"),
425             },
426             TestCase {
427                 // TC78
428                 key: decode_hex("0100000000000000000000000000000000000000000000000000000000000000"),
429                 nonce: decode_hex("030000000000000000000000"),
430                 msg: decode_hex_into_vec("010000000000000000000000"),
431                 ad: Vec::new(),
432                 ciphertext: decode_hex_into_vec(
433                     "9aab2aeb3faa0a34aea8e2b18ca50da9ae6559e48fd10f6e5c9ca17e",
434                 ),
435             },
436             TestCase {
437                 // TC89 contains associated data
438                 key: decode_hex("0100000000000000000000000000000000000000000000000000000000000000"),
439                 nonce: decode_hex("030000000000000000000000"),
440                 msg: decode_hex_into_vec("02000000"),
441                 ad: decode_hex_into_vec("010000000000000000000000"),
442                 ciphertext: decode_hex_into_vec("22b3f4cd1835e517741dfddccfa07fa4661b74cf"),
443             },
444         ];
445 
446         check_test_cases(|key| Box::new(Aes256GcmSiv::new(key)), test_cases);
447     }
448 
449     #[test]
aes_128_gcm()450     fn aes_128_gcm() {
451         let test_cases: &[TestCase<16, 12>] = &[
452             TestCase {
453                 // TC 1 from crypto/cipher/test/aes_128_gcm_tests.txt
454                 key: decode_hex("d480429666d48b400633921c5407d1d1"),
455                 nonce: decode_hex("3388c676dc754acfa66e172a"),
456                 msg: Vec::new(),
457                 ad: Vec::new(),
458                 ciphertext: decode_hex_into_vec("7d7daf44850921a34e636b01adeb104f"),
459             },
460             TestCase {
461                 // TC2
462                 key: decode_hex("3881e7be1bb3bbcaff20bdb78e5d1b67"),
463                 nonce: decode_hex("dcf5b7ae2d7552e2297fcfa9"),
464                 msg: decode_hex_into_vec("0a2714aa7d"),
465                 ad: decode_hex_into_vec("c60c64bbf7"),
466                 ciphertext: decode_hex_into_vec("5626f96ecbff4c4f1d92b0abb1d0820833d9eb83c7"),
467             },
468         ];
469 
470         check_test_cases(|key| Box::new(Aes128Gcm::new(key)), test_cases);
471     }
472 
473     #[test]
aes_256_gcm()474     fn aes_256_gcm() {
475         let test_cases: &[TestCase<32, 12>] = &[
476             TestCase {
477                 // TC 1 from crypto/cipher/test/aes_128_gcm_tests.txt
478                 key: decode_hex("e5ac4a32c67e425ac4b143c83c6f161312a97d88d634afdf9f4da5bd35223f01"),
479                 nonce: decode_hex("5bf11a0951f0bfc7ea5c9e58"),
480                 msg: Vec::new(),
481                 ad: Vec::new(),
482                 ciphertext: decode_hex_into_vec("d7cba289d6d19a5af45dc13857016bac"),
483             },
484             TestCase {
485                 // TC2
486                 key: decode_hex("73ad7bbbbc640c845a150f67d058b279849370cd2c1f3c67c4dd6c869213e13a"),
487                 nonce: decode_hex("a330a184fc245812f4820caa"),
488                 msg: decode_hex_into_vec("f0535fe211"),
489                 ad: decode_hex_into_vec("e91428be04"),
490                 ciphertext: decode_hex_into_vec("e9b8a896da9115ed79f26a030c14947b3e454db9e7"),
491             },
492         ];
493 
494         check_test_cases(|key| Box::new(Aes256Gcm::new(key)), test_cases);
495     }
496 }
497