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