From fda1c1492ca45489576aa509af74713e54a2855b Mon Sep 17 00:00:00 2001 From: Johannes Date: Sun, 7 Jun 2020 15:04:53 +0200 Subject: [PATCH] Add random secret-key/nonce generation to stream cipher and AEAD tests (#123) * Add ability to randomly generate SecretKey and Nonce in (StreamCipher/Aead)TestRunner This must be implemented seperately from the test runner, and is therefor a pub function. The reason for this is, that the trait that let's us randomly generate these types, should only be available when testing. But since StreamCipherTestRunner is used in intergation tests (/tests), compiling it conditionally with cfg(test) would still make it inaccessible in /tests. Using cfg(debug_assertions) is not desired either, because we want to test in both release and debug mode. * (x)chacha20: Use new testing function to replace old quickcheck tests * (x)chacha20poly1305: Add tests for randomly generated secret-key/nonce. See previous commit for details * Tests: Fixed input length of 16 for new sk/n combination test. With some very small lengths, 'collisions' would occur. --- src/hazardous/aead/chacha20poly1305.rs | 2 +- src/hazardous/aead/xchacha20poly1305.rs | 2 +- src/hazardous/stream/chacha20.rs | 123 +++---------------- src/hazardous/stream/xchacha20.rs | 115 ++--------------- src/test_framework/aead_interface.rs | 43 +++++++ src/test_framework/streamcipher_interface.rs | 52 ++++++++ 6 files changed, 119 insertions(+), 218 deletions(-) diff --git a/src/hazardous/aead/chacha20poly1305.rs b/src/hazardous/aead/chacha20poly1305.rs index e12069e7..5814dc48 100644 --- a/src/hazardous/aead/chacha20poly1305.rs +++ b/src/hazardous/aead/chacha20poly1305.rs @@ -242,7 +242,7 @@ mod public { let secret_key = SecretKey::generate(); let nonce = Nonce::from_slice(&[0u8; chacha20::IETF_CHACHA_NONCESIZE]).unwrap(); AeadTestRunner(seal, open, secret_key, nonce, &input, None, POLY1305_OUTSIZE, &ad); - + test_diff_params_err(&seal, &open, &input, POLY1305_OUTSIZE); true } } diff --git a/src/hazardous/aead/xchacha20poly1305.rs b/src/hazardous/aead/xchacha20poly1305.rs index 97af91b6..5a4860fc 100644 --- a/src/hazardous/aead/xchacha20poly1305.rs +++ b/src/hazardous/aead/xchacha20poly1305.rs @@ -136,7 +136,7 @@ mod public { let secret_key = SecretKey::generate(); let nonce = Nonce::generate(); AeadTestRunner(seal, open, secret_key, nonce, &input, None, POLY1305_OUTSIZE, &ad); - + test_diff_params_err(&seal, &open, &input, POLY1305_OUTSIZE); true } } diff --git a/src/hazardous/stream/chacha20.rs b/src/hazardous/stream/chacha20.rs index 72f62029..333ade38 100644 --- a/src/hazardous/stream/chacha20.rs +++ b/src/hazardous/stream/chacha20.rs @@ -386,6 +386,20 @@ mod public { use super::*; use crate::test_framework::streamcipher_interface::*; + impl TestingRandom for SecretKey { + fn gen() -> Self { + Self::generate() + } + } + + impl TestingRandom for Nonce { + fn gen() -> Self { + let mut n = [0u8; IETF_CHACHA_NONCESIZE]; + crate::util::secure_rand_bytes(&mut n).unwrap(); + Self::from_slice(&n).unwrap() + } + } + // Proptests. Only executed when NOT testing no_std. mod proptest { use super::*; @@ -395,118 +409,11 @@ mod public { let secret_key = SecretKey::generate(); let nonce = Nonce::from_slice(&[0u8; IETF_CHACHA_NONCESIZE]).unwrap(); StreamCipherTestRunner(encrypt, decrypt, secret_key, nonce, counter, &input, None); + test_diff_params_diff_output(&encrypt, &decrypt); true } } - - quickcheck! { - // Encrypting and decrypting using two different secret keys and the same nonce - // should never yield the same input. - fn prop_encrypt_decrypt_diff_keys_diff_input(input: Vec) -> bool { - let pt = if input.is_empty() { - vec![1u8; 10] - } else { - input - }; - - let sk1 = SecretKey::from_slice(&[0u8; 32]).unwrap(); - let sk2 = SecretKey::from_slice(&[1u8; 32]).unwrap(); - - let mut dst_out_ct = vec![0u8; pt.len()]; - let mut dst_out_pt = vec![0u8; pt.len()]; - - encrypt( - &sk1, - &Nonce::from_slice(&[0u8; 12]).unwrap(), - 0, - &pt[..], - &mut dst_out_ct, - ).unwrap(); - - decrypt( - &sk2, - &Nonce::from_slice(&[0u8; 12]).unwrap(), - 0, - &dst_out_ct[..], - &mut dst_out_pt, - ).unwrap(); - - dst_out_pt != pt - } - } - - quickcheck! { - // Encrypting and decrypting using two different nonces and the same secret key - // should never yield the same input. - fn prop_encrypt_decrypt_diff_nonces_diff_input(input: Vec) -> bool { - let pt = if input.is_empty() { - vec![1u8; 10] - } else { - input - }; - - let n1 = Nonce::from_slice(&[0u8; 12]).unwrap(); - let n2 = Nonce::from_slice(&[1u8; 12]).unwrap(); - - let mut dst_out_ct = vec![0u8; pt.len()]; - let mut dst_out_pt = vec![0u8; pt.len()]; - - encrypt( - &SecretKey::from_slice(&[0u8; 32]).unwrap(), - &n1, - 0, - &pt[..], - &mut dst_out_ct, - ).unwrap(); - - decrypt( - &SecretKey::from_slice(&[0u8; 32]).unwrap(), - &n2, - 0, - &dst_out_ct[..], - &mut dst_out_pt, - ).unwrap(); - - dst_out_pt != pt - } - } - - quickcheck! { - // Encrypting and decrypting using two different initial counters - // should never yield the same input. - fn prop_encrypt_decrypt_diff_init_counter_diff_input(input: Vec) -> bool { - let pt = if input.is_empty() { - vec![1u8; 10] - } else { - input - }; - - let init_counter1 = 32; - let init_counter2 = 64; - - let mut dst_out_ct = vec![0u8; pt.len()]; - let mut dst_out_pt = vec![0u8; pt.len()]; - - encrypt( - &SecretKey::from_slice(&[0u8; 32]).unwrap(), - &Nonce::from_slice(&[0u8; 12]).unwrap(), - init_counter1, - &pt[..], - &mut dst_out_ct, - ).unwrap(); - - decrypt( - &SecretKey::from_slice(&[0u8; 32]).unwrap(), - &Nonce::from_slice(&[0u8; 12]).unwrap(), - init_counter2, - &dst_out_ct[..], - &mut dst_out_pt, - ).unwrap(); - - dst_out_pt != pt - } - } } } diff --git a/src/hazardous/stream/xchacha20.rs b/src/hazardous/stream/xchacha20.rs index bf6d4a4b..ebf1f2ba 100644 --- a/src/hazardous/stream/xchacha20.rs +++ b/src/hazardous/stream/xchacha20.rs @@ -152,6 +152,12 @@ mod public { use super::*; use crate::test_framework::streamcipher_interface::*; + impl TestingRandom for Nonce { + fn gen() -> Self { + Self::generate() + } + } + // Proptests. Only executed when NOT testing no_std. #[cfg(feature = "safe_api")] mod proptest { @@ -162,118 +168,11 @@ mod public { let secret_key = SecretKey::generate(); let nonce = Nonce::generate(); StreamCipherTestRunner(encrypt, decrypt, secret_key, nonce, counter, &input, None); + test_diff_params_diff_output(&encrypt, &decrypt); true } } - - quickcheck! { - // Encrypting and decrypting using two different secret keys and the same nonce - // should never yield the same input. - fn prop_encrypt_decrypt_diff_keys_diff_input(input: Vec) -> bool { - let pt = if input.is_empty() { - vec![1u8; 10] - } else { - input - }; - - let sk1 = SecretKey::from_slice(&[0u8; 32]).unwrap(); - let sk2 = SecretKey::from_slice(&[1u8; 32]).unwrap(); - - let mut dst_out_ct = vec![0u8; pt.len()]; - let mut dst_out_pt = vec![0u8; pt.len()]; - - encrypt( - &sk1, - &Nonce::from_slice(&[0u8; 24]).unwrap(), - 0, - &pt[..], - &mut dst_out_ct, - ).unwrap(); - - decrypt( - &sk2, - &Nonce::from_slice(&[0u8; 24]).unwrap(), - 0, - &dst_out_ct[..], - &mut dst_out_pt, - ).unwrap(); - - dst_out_pt != pt - } - } - - quickcheck! { - // Encrypting and decrypting using two different nonces and the same secret key - // should never yield the same input. - fn prop_encrypt_decrypt_diff_nonces_diff_input(input: Vec) -> bool { - let pt = if input.is_empty() { - vec![1u8; 10] - } else { - input - }; - - let n1 = Nonce::from_slice(&[0u8; 24]).unwrap(); - let n2 = Nonce::from_slice(&[1u8; 24]).unwrap(); - - let mut dst_out_ct = vec![0u8; pt.len()]; - let mut dst_out_pt = vec![0u8; pt.len()]; - - encrypt( - &SecretKey::from_slice(&[0u8; 32]).unwrap(), - &n1, - 0, - &pt[..], - &mut dst_out_ct, - ).unwrap(); - - decrypt( - &SecretKey::from_slice(&[0u8; 32]).unwrap(), - &n2, - 0, - &dst_out_ct[..], - &mut dst_out_pt, - ).unwrap(); - - dst_out_pt != pt - } - } - - quickcheck! { - // Encrypting and decrypting using two different initial counters - // should never yield the same input. - fn prop_encrypt_decrypt_diff_init_counter_diff_input(input: Vec) -> bool { - let pt = if input.is_empty() { - vec![1u8; 10] - } else { - input - }; - - let init_counter1 = 32; - let init_counter2 = 64; - - let mut dst_out_ct = vec![0u8; pt.len()]; - let mut dst_out_pt = vec![0u8; pt.len()]; - - encrypt( - &SecretKey::from_slice(&[0u8; 32]).unwrap(), - &Nonce::from_slice(&[0u8; 24]).unwrap(), - init_counter1, - &pt[..], - &mut dst_out_ct, - ).unwrap(); - - decrypt( - &SecretKey::from_slice(&[0u8; 32]).unwrap(), - &Nonce::from_slice(&[0u8; 24]).unwrap(), - init_counter2, - &dst_out_ct[..], - &mut dst_out_pt, - ).unwrap(); - - dst_out_pt != pt - } - } } } } diff --git a/src/test_framework/aead_interface.rs b/src/test_framework/aead_interface.rs index d2ca760a..f772acd6 100644 --- a/src/test_framework/aead_interface.rs +++ b/src/test_framework/aead_interface.rs @@ -24,6 +24,10 @@ #[cfg(feature = "safe_api")] use crate::errors::UnknownCryptoError; +#[cfg(test)] +#[cfg(feature = "safe_api")] +use crate::test_framework::streamcipher_interface::TestingRandom; + #[allow(clippy::too_many_arguments)] #[cfg(feature = "safe_api")] /// Test runner for AEADs. @@ -369,3 +373,42 @@ fn none_or_empty_some_aad_same_result( .is_ok()); assert!(opener(&key, &nonce, &dst_out_ct_some_empty, None, &mut dst_out_pt).is_ok()); } + +#[cfg(test)] +#[cfg(feature = "safe_api")] +/// Test that sealing and opening with different secret-key/nonce yields an error. +pub fn test_diff_params_err( + sealer: &Sealer, + opener: &Opener, + input: &[u8], + tag_size: usize, +) where + Key: TestingRandom + PartialEq, + Nonce: TestingRandom + PartialEq, + Sealer: Fn(&Key, &Nonce, &[u8], Option<&[u8]>, &mut [u8]) -> Result<(), UnknownCryptoError>, + Opener: Fn(&Key, &Nonce, &[u8], Option<&[u8]>, &mut [u8]) -> Result<(), UnknownCryptoError>, +{ + let mut input = input; + if input.is_empty() { + input = &[0u8; 1]; + } + + let sk1 = Key::gen(); + let sk2 = Key::gen(); + assert!(sk1 != sk2); + + let n1 = Nonce::gen(); + let n2 = Nonce::gen(); + assert!(n1 != n2); + + let mut dst_out_ct = vec![0u8; input.len() + tag_size]; + let mut dst_out_pt = vec![0u8; input.len()]; + + // Different secret key + sealer(&sk1, &n1, input, None, &mut dst_out_ct).unwrap(); + assert!(opener(&sk2, &n1, &dst_out_ct, None, &mut dst_out_pt).is_err()); + + // Different nonce + sealer(&sk1, &n1, input, None, &mut dst_out_ct).unwrap(); + assert!(opener(&sk1, &n2, &dst_out_ct, None, &mut dst_out_pt).is_err()); +} diff --git a/src/test_framework/streamcipher_interface.rs b/src/test_framework/streamcipher_interface.rs index d107bba7..c03a4b70 100644 --- a/src/test_framework/streamcipher_interface.rs +++ b/src/test_framework/streamcipher_interface.rs @@ -25,6 +25,13 @@ #[cfg(feature = "safe_api")] use crate::errors::UnknownCryptoError; +#[cfg(test)] +#[cfg(feature = "safe_api")] +pub trait TestingRandom { + /// Randomly generate self. + fn gen() -> Self; +} + #[cfg(feature = "safe_api")] /// Test runner for stream ciphers. pub fn StreamCipherTestRunner( @@ -249,3 +256,48 @@ fn initial_counter_max_ok( ) .is_ok()); } + +#[cfg(test)] +#[cfg(feature = "safe_api")] +/// Test that encrypting using different secret-key/nonce/initial-counter combinations yields different +/// ciphertexts. +pub fn test_diff_params_diff_output( + encryptor: &Encryptor, + decryptor: &Decryptor, +) where + Key: TestingRandom + PartialEq, + Nonce: TestingRandom + PartialEq, + Encryptor: Fn(&Key, &Nonce, u32, &[u8], &mut [u8]) -> Result<(), UnknownCryptoError>, + Decryptor: Fn(&Key, &Nonce, u32, &[u8], &mut [u8]) -> Result<(), UnknownCryptoError>, +{ + let input = &[0u8; 16]; + + let sk1 = Key::gen(); + let sk2 = Key::gen(); + assert!(sk1 != sk2); + + let n1 = Nonce::gen(); + let n2 = Nonce::gen(); + assert!(n1 != n2); + + let c1 = 0u32; + let c2 = 1u32; + + let mut dst_out_ct = vec![0u8; input.len()]; + let mut dst_out_pt = vec![0u8; input.len()]; + + // Different secret key + encryptor(&sk1, &n1, c1, input, &mut dst_out_ct).unwrap(); + decryptor(&sk2, &n1, c1, &dst_out_ct, &mut dst_out_pt).unwrap(); + assert_ne!(&dst_out_pt[..], input); + + // Different nonce + encryptor(&sk1, &n1, c1, input, &mut dst_out_ct).unwrap(); + decryptor(&sk1, &n2, c1, &dst_out_ct, &mut dst_out_pt).unwrap(); + assert_ne!(&dst_out_pt[..], input); + + // Different initial counter + encryptor(&sk1, &n1, c1, input, &mut dst_out_ct).unwrap(); + decryptor(&sk1, &n1, c2, &dst_out_ct, &mut dst_out_pt).unwrap(); + assert_ne!(&dst_out_pt[..], input); +}