Skip to main content

bssl_crypto/
aead.rs

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