1 /* Copyright (c) 2023, Google Inc. 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 48 use crate::{with_output_array, with_output_vec, with_output_vec_fallible, FfiMutSlice, FfiSlice}; 49 use alloc::vec::Vec; 50 51 /// The error type returned when a fallible, in-place operation fails. 52 #[derive(Debug)] 53 pub struct InvalidCiphertext; 54 55 /// Authenticated Encryption with Associated Data (AEAD) algorithm trait. 56 pub 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. seal(&self, nonce: &Self::Nonce, plaintext: &[u8], ad: &[u8]) -> Vec<u8>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. seal_in_place(&self, nonce: &Self::Nonce, plaintext: &mut [u8], ad: &[u8]) -> Self::Tag74 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`. open(&self, nonce: &Self::Nonce, ciphertext: &[u8], ad: &[u8]) -> Option<Vec<u8>>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`. open_in_place( &self, nonce: &Self::Nonce, ciphertext: &mut [u8], tag: &Self::Tag, ad: &[u8], ) -> Result<(), InvalidCiphertext>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. 95 pub struct Aes128Gcm(EvpAead<16, 12, 16>); 96 aead_algo!(Aes128Gcm, EVP_aead_aes_128_gcm, 16, 12, 16); 97 98 /// AES-256 in Galois Counter Mode. 99 pub struct Aes256Gcm(EvpAead<32, 12, 16>); 100 aead_algo!(Aes256Gcm, EVP_aead_aes_256_gcm, 32, 12, 16); 101 102 /// AES-128 in GCM-SIV mode (which is different from SIV mode!). 103 pub struct Aes128GcmSiv(EvpAead<16, 12, 16>); 104 aead_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!). 107 pub struct Aes256GcmSiv(EvpAead<32, 12, 16>); 108 aead_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>. 111 pub struct Chacha20Poly1305(EvpAead<32, 12, 16>); 112 aead_algo!(Chacha20Poly1305, EVP_aead_chacha20_poly1305, 32, 12, 16); 113 114 /// Chacha20Poly1305 with an extended nonce that makes random generation of nonces safe. 115 pub struct XChacha20Poly1305(EvpAead<32, 24, 16>); 116 aead_algo!(XChacha20Poly1305, EVP_aead_xchacha20_poly1305, 32, 24, 16); 117 118 /// An internal struct that implements AEAD operations given an `EVP_AEAD`. 119 struct 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)] 124 impl<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. new(key: &[u8; KEY_LEN], evp_aead: *const bssl_sys::EVP_AEAD) -> Self128 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 seal(&self, nonce: &[u8; NONCE_LEN], plaintext: &[u8], ad: &[u8]) -> Vec<u8>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 seal_in_place( &self, nonce: &[u8; NONCE_LEN], plaintext: &mut [u8], ad: &[u8], ) -> [u8; TAG_LEN]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 open(&self, nonce: &[u8; NONCE_LEN], ciphertext: &[u8], ad: &[u8]) -> Option<Vec<u8>>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 open_in_place( &self, nonce: &[u8; NONCE_LEN], ciphertext: &mut [u8], tag: &[u8; TAG_LEN], ad: &[u8], ) -> Result<(), InvalidCiphertext>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 272 impl<const KEY_LEN: usize, const NONCE_LEN: usize, const TAG_LEN: usize> Drop 273 for EvpAead<KEY_LEN, NONCE_LEN, TAG_LEN> 274 { drop(&mut self)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)] 283 mod test { 284 use super::*; 285 use crate::test_helpers::{decode_hex, decode_hex_into_vec}; 286 check_aead_invariants< const NONCE_LEN: usize, const TAG_LEN: usize, A: Aead<Nonce = [u8; NONCE_LEN], Tag = [u8; TAG_LEN]>, >( aead: A, )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] aes_128_gcm_invariants()324 fn aes_128_gcm_invariants() { 325 check_aead_invariants(Aes128Gcm::new(&[0u8; 16])); 326 } 327 328 #[test] aes_256_gcm_invariants()329 fn aes_256_gcm_invariants() { 330 check_aead_invariants(Aes256Gcm::new(&[0u8; 32])); 331 } 332 333 #[test] aes_128_gcm_siv_invariants()334 fn aes_128_gcm_siv_invariants() { 335 check_aead_invariants(Aes128GcmSiv::new(&[0u8; 16])); 336 } 337 338 #[test] aes_256_gcm_siv_invariants()339 fn aes_256_gcm_siv_invariants() { 340 check_aead_invariants(Aes256GcmSiv::new(&[0u8; 32])); 341 } 342 343 #[test] chacha20_poly1305_invariants()344 fn chacha20_poly1305_invariants() { 345 check_aead_invariants(Chacha20Poly1305::new(&[0u8; 32])); 346 } 347 348 #[test] xchacha20_poly1305_invariants()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 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>], )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] aes_128_gcm_siv()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] aes_256_gcm_siv()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] aes_128_gcm()451 fn aes_128_gcm() { 452 let test_cases: &[TestCase<16, 12>] = &[ 453 TestCase { 454 // TC 1 from crypto/cipher_extra/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] aes_256_gcm()475 fn aes_256_gcm() { 476 let test_cases: &[TestCase<32, 12>] = &[ 477 TestCase { 478 // TC 1 from crypto/cipher_extra/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 } 498