//! Property-based tests for Encrypted. #![cfg(feature = "chacha20poly1305")] use bolero::check; use evidence::{ codec::Identity, encrypted::{Encrypted, EncryptedUnchecked}, encryption::chacha20poly1305::ChaCha20Poly1305, }; /// Helper to create a key from seed bytes. fn key_from_seed(seed: [u8; 32]) -> chacha20poly1305::Key { chacha20poly1305::Key::from(seed) } #[test] #[allow(clippy::expect_used)] // test should panic if decryption fails fn encrypted_decrypt_roundtrip() { check!() .with_type::<([u8; 32], Vec)>() .for_each(|(seed, plaintext)| { let key = key_from_seed(*seed); let encrypted: Encrypted, ChaCha20Poly1305, Identity> = Encrypted::encrypt(&key, plaintext); let decrypted = encrypted .try_decrypt(&key) .expect("decryption should succeed for just-encrypted payload"); assert_eq!( &decrypted, plaintext, "decrypted payload must match original" ); }); } #[test] fn encrypted_different_nonces() { check!() .with_type::<([u8; 32], Vec)>() .for_each(|(seed, plaintext)| { let key = key_from_seed(*seed); let encrypted1: Encrypted, ChaCha20Poly1305, Identity> = Encrypted::encrypt(&key, plaintext); let encrypted2: Encrypted, ChaCha20Poly1305, Identity> = Encrypted::encrypt(&key, plaintext); assert_ne!( encrypted1.nonce(), encrypted2.nonce(), "different encryptions should use different nonces" ); }); } #[test] fn wrong_key_fails_decryption() { check!() .with_type::<([u8; 32], [u8; 32], Vec)>() .filter(|(a, b, _)| a != b) .for_each(|(seed_correct, seed_wrong, plaintext)| { let correct_key = key_from_seed(*seed_correct); let wrong_key = key_from_seed(*seed_wrong); let encrypted: Encrypted, ChaCha20Poly1305, Identity> = Encrypted::encrypt(&correct_key, plaintext); assert!( encrypted.try_decrypt(&wrong_key).is_err(), "decryption must fail with wrong key" ); }); } #[test] #[allow(clippy::indexing_slicing)] // ciphertext is non-empty (filter guarantees) fn tampered_ciphertext_fails_decryption() { check!() .with_type::<([u8; 32], Vec)>() .cloned() .filter(|(_, plaintext)| !plaintext.is_empty()) .for_each(|(seed, plaintext)| { let key = key_from_seed(seed); let encrypted: Encrypted, ChaCha20Poly1305, Identity> = Encrypted::encrypt(&key, &plaintext); let mut tampered_ciphertext = encrypted.ciphertext().to_vec(); tampered_ciphertext[0] = tampered_ciphertext[0].wrapping_add(1); let tampered: Encrypted, ChaCha20Poly1305, Identity> = Encrypted::from_unchecked_parts(tampered_ciphertext, *encrypted.nonce()); assert!( tampered.try_decrypt(&key).is_err(), "decryption must fail with tampered ciphertext" ); }); } #[test] fn deterministic_with_same_nonce() { check!() .with_type::<([u8; 32], [u8; 12], Vec)>() .for_each(|(seed, nonce_bytes, plaintext)| { let key = key_from_seed(*seed); let nonce = hybrid_array::Array::from(*nonce_bytes); let encrypted1: Encrypted, ChaCha20Poly1305, Identity> = Encrypted::encrypt_with_nonce(&key, &nonce, plaintext); let encrypted2: Encrypted, ChaCha20Poly1305, Identity> = Encrypted::encrypt_with_nonce(&key, &nonce, plaintext); assert_eq!( encrypted1.ciphertext(), encrypted2.ciphertext(), "same key + nonce + plaintext must produce same ciphertext" ); }); }