Skip to main content

bssl_crypto/
aead.rs

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
47use crate::{with_output_array, with_output_vec, with_output_vec_fallible, FfiMutSlice, FfiSlice};
48use alloc::vec::Vec;
49
50/// The error type returned when a fallible, in-place operation fails.
51#[derive(Debug)]
52pub struct InvalidCiphertext;
53
54/// Authenticated Encryption with Associated Data (AEAD) algorithm trait.
55pub 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.
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.
73    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`.
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`.
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.
94pub struct Aes128Gcm(EvpAead<16, 12, 16>);
95aead_algo!(Aes128Gcm, EVP_aead_aes_128_gcm, 16, 12, 16);
96
97/// AES-256 in Galois Counter Mode.
98pub struct Aes256Gcm(EvpAead<32, 12, 16>);
99aead_algo!(Aes256Gcm, EVP_aead_aes_256_gcm, 32, 12, 16);
100
101/// AES-128 in GCM-SIV mode (which is different from SIV mode!).
102pub struct Aes128GcmSiv(EvpAead<16, 12, 16>);
103aead_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!).
106pub struct Aes256GcmSiv(EvpAead<32, 12, 16>);
107aead_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>.
110pub struct Chacha20Poly1305(EvpAead<32, 12, 16>);
111aead_algo!(Chacha20Poly1305, EVP_aead_chacha20_poly1305, 32, 12, 16);
112
113/// Chacha20Poly1305 with an extended nonce that makes random generation of nonces safe.
114pub struct XChacha20Poly1305(EvpAead<32, 24, 16>);
115aead_algo!(XChacha20Poly1305, EVP_aead_xchacha20_poly1305, 32, 24, 16);
116
117/// An internal struct that implements AEAD operations given an `EVP_AEAD`.
118struct 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)]
123impl<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.
127    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
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
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
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
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
271impl<const KEY_LEN: usize, const NONCE_LEN: usize, const TAG_LEN: usize> Drop
272    for EvpAead<KEY_LEN, NONCE_LEN, TAG_LEN>
273{
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)]
282mod test {
283    use super::*;
284    use crate::test_helpers::{decode_hex, decode_hex_into_vec};
285
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]
323    fn aes_128_gcm_invariants() {
324        check_aead_invariants(Aes128Gcm::new(&[0u8; 16]));
325    }
326
327    #[test]
328    fn aes_256_gcm_invariants() {
329        check_aead_invariants(Aes256Gcm::new(&[0u8; 32]));
330    }
331
332    #[test]
333    fn aes_128_gcm_siv_invariants() {
334        check_aead_invariants(Aes128GcmSiv::new(&[0u8; 16]));
335    }
336
337    #[test]
338    fn aes_256_gcm_siv_invariants() {
339        check_aead_invariants(Aes256GcmSiv::new(&[0u8; 32]));
340    }
341
342    #[test]
343    fn chacha20_poly1305_invariants() {
344        check_aead_invariants(Chacha20Poly1305::new(&[0u8; 32]));
345    }
346
347    #[test]
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
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]
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]
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]
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]
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}