diff --git a/Cargo.toml b/Cargo.toml index 639523bf..b791bd55 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "orion" -version = "0.12.3" +version = "0.12.4" authors = ["brycx "] description = "Easy and usable rust crypto" keywords = [ "cryptography", "blake2", "aead", "sha2", "xchacha20_poly1305" ] @@ -34,7 +34,7 @@ no_std = [ "clear_on_drop/nightly", "subtle/nightly" ] [dev-dependencies] hex = "0.3.2" serde_json = "1.0.37" - +quickcheck = "0.8.0" [profile.dev] opt-level = 1 diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index e338930e..b9a4e9b4 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -9,9 +9,9 @@ publish = false cargo-fuzz = true [dependencies] -ring = "0.13.5" +ring = "0.14.3" sp800-185 = "0.2.0" -chacha = "0.2.0" +chacha = "0.3.0" sodiumoxide = { git = "https://github.com/sodiumoxide/sodiumoxide" } blake2-rfc = "0.2.18" diff --git a/fuzz/fuzz_targets/chacha20_poly1305_compare.rs b/fuzz/fuzz_targets/chacha20_poly1305_compare.rs index 13250ba4..1159ed37 100644 --- a/fuzz/fuzz_targets/chacha20_poly1305_compare.rs +++ b/fuzz/fuzz_targets/chacha20_poly1305_compare.rs @@ -35,6 +35,12 @@ fuzz_target!(|data: &[u8]| { let enc_key = ring::aead::SealingKey::new(&ring::aead::CHACHA20_POLY1305, &key).unwrap(); let dec_key = ring::aead::OpeningKey::new(&ring::aead::CHACHA20_POLY1305, &key).unwrap(); + + let ring_nonce_enc = ring::aead::Nonce::try_assume_unique_for_key(&nonce).unwrap(); + let ring_aad_enc = ring::aead::Aad::from(&aad); + + let ring_nonce_dec = ring::aead::Nonce::try_assume_unique_for_key(&nonce).unwrap(); + let ring_aad_dec = ring::aead::Aad::from(&aad); let mut ciphertext_with_tag_ring: Vec = vec![0u8; plaintext.len() + 16]; let mut plaintext_out_ring = Vec::new(); @@ -42,7 +48,7 @@ fuzz_target!(|data: &[u8]| { ciphertext_with_tag_ring[..plaintext.len()].copy_from_slice(&plaintext); let index = - ring::aead::seal_in_place(&enc_key, &nonce, &aad, &mut ciphertext_with_tag_ring, 16) + ring::aead::seal_in_place(&enc_key, ring_nonce_enc, ring_aad_enc, &mut ciphertext_with_tag_ring, 16) .unwrap(); assert_eq!( &ciphertext_with_tag_ring[..index].as_ref(), @@ -53,7 +59,7 @@ fuzz_target!(|data: &[u8]| { &ciphertext_with_tag_ring[index - 16..index].as_ref(), &ciphertext_with_tag_orion[plaintext.len()..].as_ref() ); - ring::aead::open_in_place(&dec_key, &nonce, &aad, 0, &mut ciphertext_with_tag_ring).unwrap(); + ring::aead::open_in_place(&dec_key, ring_nonce_dec, ring_aad_dec, 0, &mut ciphertext_with_tag_ring).unwrap(); plaintext_out_ring.extend_from_slice(&ciphertext_with_tag_ring); assert_eq!( &ciphertext_with_tag_ring[..plaintext.len()].as_ref(), diff --git a/fuzz/fuzz_targets/ring_compare.rs b/fuzz/fuzz_targets/ring_compare.rs index 65a26a9c..1a46014f 100644 --- a/fuzz/fuzz_targets/ring_compare.rs +++ b/fuzz/fuzz_targets/ring_compare.rs @@ -56,7 +56,7 @@ fn ro_pbkdf2(data: &[u8]) { pbkdf2::derive_key(&orion_password, &salt, iter, &mut dk_out_orion).unwrap(); ring_pbkdf2::derive( &digest::SHA512, - iter as u32, + std::num::NonZeroU32::new(iter as u32).unwrap(), &salt, &password, &mut dk_out_ring, @@ -65,7 +65,7 @@ fn ro_pbkdf2(data: &[u8]) { assert_eq!(&dk_out_ring, &dk_out_orion); assert!(ring_pbkdf2::verify( &digest::SHA512, - iter as u32, + std::num::NonZeroU32::new(iter as u32).unwrap(), &salt, &password, &dk_out_orion diff --git a/src/aead.rs b/src/aead.rs index e3430a77..9debdeae 100644 --- a/src/aead.rs +++ b/src/aead.rs @@ -130,85 +130,140 @@ pub fn open( Ok(dst_out) } -#[test] -fn auth_enc_encryption_decryption() { - let key = SecretKey::default(); - let plaintext = "Secret message".as_bytes().to_vec(); - - let dst_ciphertext = seal(&key, &plaintext).unwrap(); - assert!(dst_ciphertext.len() == plaintext.len() + (24 + 16)); - let dst_plaintext = open(&key, &dst_ciphertext).unwrap(); - assert!(dst_plaintext.len() == plaintext.len()); - assert_eq!(plaintext, dst_plaintext); -} +// Testing public functions in the module. +#[cfg(test)] +mod public { + use super::*; -#[test] -fn auth_enc_plaintext_empty_err() { - let key = SecretKey::default(); - let plaintext = "".as_bytes().to_vec(); + mod test_seal_open { + use super::*; + #[test] + fn test_auth_enc_encryption_decryption() { + let key = SecretKey::default(); + let plaintext = "Secret message".as_bytes().to_vec(); - assert!(seal(&key, &plaintext).is_err()); -} + let dst_ciphertext = seal(&key, &plaintext).unwrap(); + assert!(dst_ciphertext.len() == plaintext.len() + (24 + 16)); + let dst_plaintext = open(&key, &dst_ciphertext).unwrap(); + assert!(dst_plaintext.len() == plaintext.len()); + assert_eq!(plaintext, dst_plaintext); + } -#[test] -fn auth_enc_ciphertext_less_than_41_err() { - let key = SecretKey::default(); - let ciphertext = [0u8; 40]; + #[test] + fn test_auth_enc_plaintext_empty_err() { + let key = SecretKey::default(); + let plaintext = "".as_bytes().to_vec(); - assert!(open(&key, &ciphertext).is_err()); -} + assert!(seal(&key, &plaintext).is_err()); + } -#[test] -fn test_modified_nonce_err() { - let key = SecretKey::default(); - let plaintext = "Secret message".as_bytes().to_vec(); + #[test] + fn test_auth_enc_ciphertext_less_than_41_err() { + let key = SecretKey::default(); + let ciphertext = [0u8; 40]; - let mut dst_ciphertext = seal(&key, &plaintext).unwrap(); - // Modify nonce - dst_ciphertext[10] ^= 1; - assert!(open(&key, &dst_ciphertext).is_err()); -} + assert!(open(&key, &ciphertext).is_err()); + } -#[test] -fn test_modified_ciphertext_err() { - let key = SecretKey::default(); - let plaintext = "Secret message".as_bytes().to_vec(); + #[test] + fn test_modified_nonce_err() { + let key = SecretKey::default(); + let plaintext = "Secret message".as_bytes().to_vec(); - let mut dst_ciphertext = seal(&key, &plaintext).unwrap(); - // Modify ciphertext - dst_ciphertext[25] ^= 1; - assert!(open(&key, &dst_ciphertext).is_err()); -} + let mut dst_ciphertext = seal(&key, &plaintext).unwrap(); + // Modify nonce + dst_ciphertext[10] ^= 1; + assert!(open(&key, &dst_ciphertext).is_err()); + } -#[test] -fn test_modified_tag_err() { - let key = SecretKey::default(); - let plaintext = "Secret message".as_bytes().to_vec(); + #[test] + fn test_modified_ciphertext_err() { + let key = SecretKey::default(); + let plaintext = "Secret message".as_bytes().to_vec(); - let mut dst_ciphertext = seal(&key, &plaintext).unwrap(); - let dst_ciphertext_len = dst_ciphertext.len(); - // Modify tag - dst_ciphertext[dst_ciphertext_len - 6] ^= 1; - assert!(open(&key, &dst_ciphertext).is_err()); -} + let mut dst_ciphertext = seal(&key, &plaintext).unwrap(); + // Modify ciphertext + dst_ciphertext[25] ^= 1; + assert!(open(&key, &dst_ciphertext).is_err()); + } -#[test] -fn test_diff_secret_key_err() { - let key = SecretKey::default(); - let plaintext = "Secret message".as_bytes().to_vec(); + #[test] + fn test_modified_tag_err() { + let key = SecretKey::default(); + let plaintext = "Secret message".as_bytes().to_vec(); - let dst_ciphertext = seal(&key, &plaintext).unwrap(); - let bad_key = SecretKey::default(); - assert!(open(&bad_key, &dst_ciphertext).is_err()); -} + let mut dst_ciphertext = seal(&key, &plaintext).unwrap(); + let dst_ciphertext_len = dst_ciphertext.len(); + // Modify tag + dst_ciphertext[dst_ciphertext_len - 6] ^= 1; + assert!(open(&key, &dst_ciphertext).is_err()); + } + + #[test] + fn test_diff_secret_key_err() { + let key = SecretKey::default(); + let plaintext = "Secret message".as_bytes().to_vec(); -#[test] -fn test_secret_length_err() { - let key = SecretKey::generate(31).unwrap(); - let plaintext = "Secret message Secret message Secret message Secret message " - .as_bytes() - .to_vec(); + let dst_ciphertext = seal(&key, &plaintext).unwrap(); + let bad_key = SecretKey::default(); + assert!(open(&bad_key, &dst_ciphertext).is_err()); + } - assert!(seal(&key, &plaintext).is_err()); - assert!(open(&key, &plaintext).is_err()); + #[test] + fn test_secret_length_err() { + let key = SecretKey::generate(31).unwrap(); + let plaintext = "Secret message Secret message Secret message Secret message " + .as_bytes() + .to_vec(); + + assert!(seal(&key, &plaintext).is_err()); + assert!(open(&key, &plaintext).is_err()); + } + } + + // Proptests. Only exectued when NOT testing no_std. + #[cfg(feature = "safe_api")] + mod proptest { + use super::*; + + quickcheck! { + // Sealing input, and then opening should always yield the same input. + fn prop_seal_open_same_input(input: Vec) -> bool { + let pt = if input.is_empty() { + vec![1u8; 10] + } else { + input + }; + + let sk = SecretKey::default(); + + let ct = seal(&sk, &pt).unwrap(); + let pt_decrypted = open(&sk, &ct).unwrap(); + + (pt == pt_decrypted) + } + } + + quickcheck! { + // Sealing input, modifying the tag and then opening should + // always fail due to authentication. + fn prop_fail_on_diff_key(input: Vec) -> bool { + let pt = if input.is_empty() { + vec![1u8; 10] + } else { + input + }; + + let sk = SecretKey::default(); + let sk2 = SecretKey::default(); + + let ct = seal(&sk, &pt).unwrap(); + if open(&sk2, &ct).is_err() { + true + } else { + false + } + } + } + } } diff --git a/src/auth.rs b/src/auth.rs index 318a4fea..a8f3fbd7 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -91,31 +91,88 @@ pub fn authenticate_verify( Ok(true) } -#[test] -fn test_authenticate_verify_bad_key() { - let sec_key_correct = SecretKey::generate(64).unwrap(); - let sec_key_false = SecretKey::default(); - let msg = "what do ya want for nothing?".as_bytes().to_vec(); - - let hmac_bob = authenticate(&sec_key_correct, &msg).unwrap(); - - assert_eq!( - authenticate_verify(&hmac_bob, &sec_key_correct, &msg).unwrap(), - true - ); - assert!(authenticate_verify(&hmac_bob, &sec_key_false, &msg).is_err()); -} - -#[test] -fn test_authenticate_verify_bad_msg() { - let sec_key = SecretKey::generate(64).unwrap(); - let msg = "what do ya want for nothing?".as_bytes().to_vec(); - - let hmac_bob = authenticate(&sec_key, &msg).unwrap(); - - assert_eq!( - authenticate_verify(&hmac_bob, &sec_key, &msg).unwrap(), - true - ); - assert!(authenticate_verify(&hmac_bob, &sec_key, b"bad msg").is_err()); +// Testing public functions in the module. +#[cfg(test)] +mod public { + use super::*; + + mod test_auth_and_verify { + use super::*; + #[test] + fn test_authenticate_verify_bad_key() { + let sec_key_correct = SecretKey::generate(64).unwrap(); + let sec_key_false = SecretKey::default(); + let msg = "what do ya want for nothing?".as_bytes().to_vec(); + + let hmac_bob = authenticate(&sec_key_correct, &msg).unwrap(); + + assert_eq!( + authenticate_verify(&hmac_bob, &sec_key_correct, &msg).unwrap(), + true + ); + assert!(authenticate_verify(&hmac_bob, &sec_key_false, &msg).is_err()); + } + + #[test] + fn test_authenticate_verify_bad_msg() { + let sec_key = SecretKey::generate(64).unwrap(); + let msg = "what do ya want for nothing?".as_bytes().to_vec(); + + let hmac_bob = authenticate(&sec_key, &msg).unwrap(); + + assert_eq!( + authenticate_verify(&hmac_bob, &sec_key, &msg).unwrap(), + true + ); + assert!(authenticate_verify(&hmac_bob, &sec_key, b"bad msg").is_err()); + } + } + + // Proptests. Only exectued when NOT testing no_std. + #[cfg(feature = "safe_api")] + mod proptest { + use super::*; + + quickcheck! { + /// Authentication and verifing that authentication with the same parameters + /// should always be true. + fn prop_authenticate_verify(input: Vec) -> bool { + let sk = SecretKey::default(); + + let tag = authenticate(&sk, &input[..]).unwrap(); + authenticate_verify(&tag, &sk, &input[..]).unwrap() + } + } + + quickcheck! { + /// Authentication and verifing that authentication with a different key should + /// never be true. + fn prop_verify_fail_diff_key(input: Vec) -> bool { + let sk = SecretKey::default(); + let sk2 = SecretKey::default(); + + let tag = authenticate(&sk, &input[..]).unwrap(); + if authenticate_verify(&tag, &sk2, &input[..]).is_err() { + true + } else { + false + } + } + } + + quickcheck! { + /// Authentication and verifing that authentication with different input should + /// never be true. + fn prop_verify_fail_diff_input(input: Vec) -> bool { + let sk = SecretKey::default(); + + let tag = authenticate(&sk, &input[..]).unwrap(); + if authenticate_verify(&tag, &sk, b"Completely wrong input").is_err() { + true + } else { + false + } + } + } + } } diff --git a/src/hash.rs b/src/hash.rs index a9025503..967278d3 100644 --- a/src/hash.rs +++ b/src/hash.rs @@ -60,5 +60,34 @@ pub fn digest(data: &[u8]) -> Result { Ok(blake2b::Hasher::Blake2b256.digest(data)?) } -#[test] -fn basic_test() { let _digest = digest(b"Some data").unwrap(); } +// Testing public functions in the module. +#[cfg(test)] +mod public { + use super::*; + + mod test_digest { + use super::*; + #[test] + fn basic_test() { let _digest = digest(b"Some data").unwrap(); } + + // Proptests. Only exectued when NOT testing no_std. + #[cfg(feature = "safe_api")] + mod proptest { + use super::*; + + quickcheck! { + /// Hashing twice with same input should always produce same output. + fn prop_digest_same_result(input: Vec) -> bool { + (digest(&input[..]).unwrap() == digest(&input[..]).unwrap()) + } + } + + quickcheck! { + /// Hashing twice with different input should never produce same output. + fn prop_digest_diff_result(input: Vec) -> bool { + (digest(&input[..]).unwrap() != digest(b"Completely wrong input").unwrap()) + } + } + } + } +} diff --git a/src/hazardous/aead/chacha20poly1305.rs b/src/hazardous/aead/chacha20poly1305.rs index 3eff7e83..686c4308 100644 --- a/src/hazardous/aead/chacha20poly1305.rs +++ b/src/hazardous/aead/chacha20poly1305.rs @@ -138,16 +138,20 @@ fn padding(input: &[u8]) -> usize { #[must_use] #[inline] /// Process data to be authenticated using a `Poly1305` struct initialized with -/// a one-time-key. +/// a one-time-key. Up to `buf_in_len` data in `buf` get's authenticated. The +/// indexing is needed because authentication happens on different input lenghts +/// in seal()/open(). fn process_authentication( poly1305_state: &mut poly1305::Poly1305, ad: &[u8], buf: &[u8], buf_in_len: usize, ) -> Result<(), UnknownCryptoError> { - if buf_in_len > buf.len() { - return Err(UnknownCryptoError); - } + // If buf_in_len is 0, then NO ciphertext gets authenticated. + // Because of this, buf may never be empty either. + assert!(!buf.is_empty()); + assert!(buf_in_len > 0); + assert!(buf_in_len <= buf.len()); let mut padding_max = [0u8; 16]; @@ -252,242 +256,608 @@ pub fn open( Ok(()) } -#[test] -fn length_padding_tests() { - // Integral multiple of 16 - assert_eq!(padding(&[0u8; 16]), 0); - assert_eq!(padding(&[0u8; 15]), 1); - assert_eq!(padding(&[0u8; 32]), 0); - assert_eq!(padding(&[0u8; 30]), 2); -} +// Testing public functions in the module. +#[cfg(test)] +mod public { + use super::*; + // One function tested per submodule. + + mod test_seal { + use super::*; + + #[test] + /// Related bug: https://github.com/brycx/orion/issues/52 + fn test_dst_out_length() { + let mut dst_out_ct = [0u8; 80]; // 64 + Poly1305TagLen + let mut dst_out_ct_less = [0u8; 79]; // 64 + Poly1305TagLen - 1 + let mut dst_out_ct_more = [0u8; 81]; // 64 + Poly1305TagLen + 1 + let mut dst_out_ct_more_2 = [0u8; 64 + (POLY1305_BLOCKSIZE * 2)]; + + assert!(seal( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 12]).unwrap(), + &[0u8; 64], + None, + &mut dst_out_ct, + ) + .is_ok()); + + // Related bug: #52 + assert!(seal( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 12]).unwrap(), + &[0u8; 64], + None, + &mut dst_out_ct_more, + ) + .is_ok()); + + // Related bug: #52 + assert!(seal( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 12]).unwrap(), + &[0u8; 64], + None, + &mut dst_out_ct_more_2, + ) + .is_ok()); + + assert!(seal( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 12]).unwrap(), + &[0u8; 64], + None, + &mut dst_out_ct_less, + ) + .is_err()); + } + + #[test] + fn test_plaintext_length() { + let mut dst_out_ct_0 = [0u8; 16]; // 0 + Poly1305TagLen + let mut dst_out_ct_1 = [0u8; 17]; // 1 + Poly1305TagLen + let mut dst_out_ct_128 = [0u8; 144]; // 128 + Poly1305TagLen + + assert!(seal( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 12]).unwrap(), + &[0u8; 0], + None, + &mut dst_out_ct_0, + ) + .is_err()); + + assert!(seal( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 12]).unwrap(), + &[0u8; 1], + None, + &mut dst_out_ct_1, + ) + .is_ok()); + + assert!(seal( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 12]).unwrap(), + &[0u8; 128], + None, + &mut dst_out_ct_128, + ) + .is_ok()); + } + } -#[test] -fn test_auth_process_with_above_length_index() { - let poly1305_key = poly1305_key_gen(&[0u8; 32], &[0u8; 12]).unwrap(); - let mut poly1305_state = poly1305::init(&poly1305_key); + mod test_open { + use super::*; + + #[test] + fn test_ciphertext_with_tag_length() { + let mut dst_out_pt = [0u8; 64]; + + assert!(open( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 12]).unwrap(), + &[0u8; 0], + None, + &mut dst_out_pt, + ) + .is_err()); + + assert!(open( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 12]).unwrap(), + &[0u8; POLY1305_BLOCKSIZE], + None, + &mut dst_out_pt, + ) + .is_err()); + + assert!(open( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 12]).unwrap(), + &[0u8; POLY1305_BLOCKSIZE - 1], + None, + &mut dst_out_pt, + ) + .is_err()); + + let mut dst_out_ct = [0u8; 64 + 16]; + seal( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 12]).unwrap(), + &[0u8; POLY1305_BLOCKSIZE + 1], + None, + &mut dst_out_ct, + ) + .unwrap(); + + assert!(open( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 12]).unwrap(), + &dst_out_ct[..(POLY1305_BLOCKSIZE + 1) + 16], + None, + &mut dst_out_pt, + ) + .is_ok()); + } + + #[test] + fn test_dst_out_length() { + let mut dst_out_ct = [0u8; 64 + 16]; + seal( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 12]).unwrap(), + &[0u8; 64], + None, + &mut dst_out_ct, + ) + .unwrap(); + + let mut dst_out_pt = [0u8; 64]; + let mut dst_out_pt_0 = [0u8; 0]; + let mut dst_out_pt_less = [0u8; 63]; + let mut dst_out_pt_more = [0u8; 65]; + + assert!(open( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 12]).unwrap(), + &dst_out_ct, + None, + &mut dst_out_pt, + ) + .is_ok()); + + assert!(open( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 12]).unwrap(), + &dst_out_ct, + None, + &mut dst_out_pt_0, + ) + .is_err()); + + assert!(open( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 12]).unwrap(), + &dst_out_ct, + None, + &mut dst_out_pt_less, + ) + .is_err()); + + assert!(open( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 12]).unwrap(), + &dst_out_ct, + None, + &mut dst_out_pt_more, + ) + .is_ok()); + } + } - assert!(process_authentication(&mut poly1305_state, &[0u8; 0], &[0u8; 64], 65).is_err()); + // Proptests. Only exectued when NOT testing no_std. + #[cfg(feature = "safe_api")] + mod proptest { + use super::*; + + // Only return true if both a and b are true. + fn check_all_true(a: bool, b: bool) -> bool { (a == true) && (b == true) } + + quickcheck! { + // Sealing input, and then opening should always yield the same input. + fn prop_seal_open_same_input(input: Vec, ad: Vec) -> bool { + let pt = if input.is_empty() { + vec![1u8; 10] + } else { + input + }; + + let mut dst_out_ct_no_ad = vec![0u8; pt.len() + POLY1305_BLOCKSIZE]; + let mut dst_out_pt_no_ad = vec![0u8; pt.len()]; + + let mut dst_out_ct_with_ad = vec![0u8; pt.len() + POLY1305_BLOCKSIZE]; + let mut dst_out_pt_with_ad = vec![0u8; pt.len()]; + + seal( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 12]).unwrap(), + &pt[..], + None, + &mut dst_out_ct_no_ad, + ).unwrap(); + + open( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 12]).unwrap(), + &dst_out_ct_no_ad[..], + None, + &mut dst_out_pt_no_ad, + ).unwrap(); + + seal( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 12]).unwrap(), + &pt[..], + Some(&ad[..]), + &mut dst_out_ct_with_ad, + ).unwrap(); + + open( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 12]).unwrap(), + &dst_out_ct_with_ad[..], + Some(&ad[..]), + &mut dst_out_pt_with_ad, + ).unwrap(); + + check_all_true(dst_out_pt_no_ad == pt, dst_out_pt_with_ad == pt) + } + } + + quickcheck! { + // Sealing input, modifying the tag and then opening should + // always fail due to authentication. + fn prop_fail_on_bad_auth_tag(input: Vec, ad: Vec) -> bool { + let pt = if input.is_empty() { + vec![1u8; 10] + } else { + input + }; + + let mut dst_out_ct_no_ad = vec![0u8; pt.len() + POLY1305_BLOCKSIZE]; + let mut dst_out_pt_no_ad = vec![0u8; pt.len()]; + + let mut dst_out_ct_with_ad = vec![0u8; pt.len() + POLY1305_BLOCKSIZE]; + let mut dst_out_pt_with_ad = vec![0u8; pt.len()]; + + seal( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 12]).unwrap(), + &pt[..], + None, + &mut dst_out_ct_no_ad, + ).unwrap(); + + // Modify tags first byte + dst_out_ct_no_ad[pt.len() + 1] ^= 1; + + let res0 = if open( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 12]).unwrap(), + &dst_out_ct_no_ad[..], + None, + &mut dst_out_pt_no_ad, + ).is_err() { + true + } else { + false + }; + + seal( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 12]).unwrap(), + &pt[..], + Some(&ad[..]), + &mut dst_out_ct_with_ad, + ).unwrap(); + + // Modify tags first byte + dst_out_ct_with_ad[pt.len() + 1] ^= 1; + + let res1 = if open( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 12]).unwrap(), + &dst_out_ct_with_ad[..], + Some(&ad[..]), + &mut dst_out_pt_with_ad, + ).is_err() { + true + } else { + false + }; + + check_all_true(res0, res1) + } + } + + quickcheck! { + // Sealing input, modifying the ciphertext and then opening should + // always fail due to authentication. + fn prop_fail_on_bad_ciphertext(input: Vec, ad: Vec) -> bool { + let pt = if input.is_empty() { + vec![1u8; 10] + } else { + input + }; + + let mut dst_out_ct_no_ad = vec![0u8; pt.len() + POLY1305_BLOCKSIZE]; + let mut dst_out_pt_no_ad = vec![0u8; pt.len()]; + + let mut dst_out_ct_with_ad = vec![0u8; pt.len() + POLY1305_BLOCKSIZE]; + let mut dst_out_pt_with_ad = vec![0u8; pt.len()]; + + seal( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 12]).unwrap(), + &pt[..], + None, + &mut dst_out_ct_no_ad, + ).unwrap(); + + // Modify ciphertexts first byte + dst_out_ct_no_ad[0] ^= 1; + + let res0 = if open( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 12]).unwrap(), + &dst_out_ct_no_ad[..], + None, + &mut dst_out_pt_no_ad, + ).is_err() { + true + } else { + false + }; + + seal( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 12]).unwrap(), + &pt[..], + Some(&ad[..]), + &mut dst_out_ct_with_ad, + ).unwrap(); + + // Modify tags first byte + dst_out_ct_with_ad[0] ^= 1; + + let res1 = if open( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 12]).unwrap(), + &dst_out_ct_with_ad[..], + Some(&ad[..]), + &mut dst_out_pt_with_ad, + ).is_err() { + true + } else { + false + }; + + check_all_true(res0, res1) + } + } + } } -#[test] -fn test_auth_process_ok_index_length() { - let poly1305_key = poly1305_key_gen(&[0u8; 32], &[0u8; 12]).unwrap(); - let mut poly1305_state = poly1305::init(&poly1305_key); +// Testing private functions in the module. +#[cfg(test)] +mod private { + use super::*; + // One function tested per submodule. + + mod test_poly1305_key_gen { + use super::*; + use crate::hazardous::constants::{CHACHA_KEYSIZE, IETF_CHACHA_NONCESIZE}; + + #[test] + fn test_key_lengths() { + assert!( + poly1305_key_gen(&[0u8; CHACHA_KEYSIZE], &[0u8; IETF_CHACHA_NONCESIZE]).is_ok() + ); + assert!( + poly1305_key_gen(&[0u8; CHACHA_KEYSIZE - 1], &[0u8; IETF_CHACHA_NONCESIZE]) + .is_err() + ); + assert!( + poly1305_key_gen(&[0u8; CHACHA_KEYSIZE + 1], &[0u8; IETF_CHACHA_NONCESIZE]) + .is_err() + ); + assert!(poly1305_key_gen(&[0u8; 0], &[0u8; IETF_CHACHA_NONCESIZE]).is_err()); + } + + #[test] + fn test_nonce_lengths() { + assert!( + poly1305_key_gen(&[0u8; CHACHA_KEYSIZE], &[0u8; IETF_CHACHA_NONCESIZE]).is_ok() + ); + assert!( + poly1305_key_gen(&[0u8; CHACHA_KEYSIZE], &[0u8; IETF_CHACHA_NONCESIZE - 1]) + .is_err() + ); + assert!( + poly1305_key_gen(&[0u8; CHACHA_KEYSIZE], &[0u8; IETF_CHACHA_NONCESIZE + 1]) + .is_err() + ); + assert!(poly1305_key_gen(&[0u8; CHACHA_KEYSIZE], &[0u8; 0]).is_err()); + } + } - process_authentication(&mut poly1305_state, &[0u8; 0], &[0u8; 64], 64).unwrap(); + mod test_padding { + use super::*; + #[test] + fn test_length_padding() { + assert_eq!(padding(&[0u8; 0]), 0); + assert_eq!(padding(&[0u8; 1]), 15); + assert_eq!(padding(&[0u8; 2]), 14); + assert_eq!(padding(&[0u8; 3]), 13); + assert_eq!(padding(&[0u8; 4]), 12); + assert_eq!(padding(&[0u8; 5]), 11); + assert_eq!(padding(&[0u8; 6]), 10); + assert_eq!(padding(&[0u8; 7]), 9); + assert_eq!(padding(&[0u8; 8]), 8); + assert_eq!(padding(&[0u8; 9]), 7); + assert_eq!(padding(&[0u8; 10]), 6); + assert_eq!(padding(&[0u8; 11]), 5); + assert_eq!(padding(&[0u8; 12]), 4); + assert_eq!(padding(&[0u8; 13]), 3); + assert_eq!(padding(&[0u8; 14]), 2); + assert_eq!(padding(&[0u8; 15]), 1); + assert_eq!(padding(&[0u8; 16]), 0); + } + + // Proptests. Only exectued when NOT testing no_std. + #[cfg(feature = "safe_api")] + mod proptest { + use super::*; + + quickcheck! { + // The usize that padding() returns should always + // be what remains to make input a multiple of 16 in length. + fn prop_padding_result(input: Vec) -> bool { + let rem = padding(&input[..]); + + (((input.len() + rem) % 16) == 0) + } + } + + quickcheck! { + // padding() should never return a usize above 15. + // The usize must always be in range of 0..=15. + fn prop_result_never_above_15(input: Vec) -> bool { + let rem: usize = padding(&input[..]); + + (rem < 16) + } + } + } + } - process_authentication(&mut poly1305_state, &[0u8; 0], &[0u8; 64], 0).unwrap(); -} + mod test_process_authentication { + use super::*; -#[test] -fn test_nonce_sizes() { - assert!(Nonce::from_slice(&[0u8; 11]).is_err()); - assert!(Nonce::from_slice(&[0u8; 13]).is_err()); - assert!(Nonce::from_slice(&[0u8; 12]).is_ok()); -} + #[test] + #[should_panic] + fn test_panic_index_0() { + let poly1305_key = poly1305_key_gen(&[0u8; 32], &[0u8; 12]).unwrap(); + let mut poly1305_state = poly1305::init(&poly1305_key); -#[test] -fn test_modified_tag_error() { - let mut dst_out_ct = [0u8; 80]; // 64 + Poly1305TagLen - let mut dst_out_pt = [0u8; 64]; - - seal( - &SecretKey::from_slice(&[0u8; 32]).unwrap(), - &Nonce::from_slice(&[0u8; 12]).unwrap(), - &[0u8; 64], - None, - &mut dst_out_ct, - ) - .unwrap(); - // Modify the tags first byte - dst_out_ct[65] ^= 1; - assert!(open( - &SecretKey::from_slice(&[0u8; 32]).unwrap(), - &Nonce::from_slice(&[0u8; 12]).unwrap(), - &dst_out_ct, - None, - &mut dst_out_pt, - ) - .is_err()); -} + process_authentication(&mut poly1305_state, &[0u8; 0], &[0u8; 64], 0).unwrap(); + } -#[test] -fn test_bad_pt_ct_lengths() { - let mut dst_out_ct_1 = [0u8; 79]; // 64 + Poly1305TagLen = 80 - let mut dst_out_ct_2 = [0u8; 80]; // 64 + Poly1305TagLen = 80 - - let mut dst_out_pt_1 = [0u8; 63]; - let mut dst_out_pt_2 = [0u8; 64]; - - assert!(seal( - &SecretKey::from_slice(&[0u8; 32]).unwrap(), - &Nonce::from_slice(&[0u8; 12]).unwrap(), - &dst_out_pt_2, - Some(&[0u8; 5]), - &mut dst_out_ct_1, - ) - .is_err()); - - seal( - &SecretKey::from_slice(&[0u8; 32]).unwrap(), - &Nonce::from_slice(&[0u8; 12]).unwrap(), - &dst_out_pt_2, - Some(&[0u8; 5]), - &mut dst_out_ct_2, - ) - .unwrap(); - - assert!(open( - &SecretKey::from_slice(&[0u8; 32]).unwrap(), - &Nonce::from_slice(&[0u8; 12]).unwrap(), - &dst_out_ct_2, - Some(&[0u8; 5]), - &mut dst_out_pt_1, - ) - .is_err()); - - open( - &SecretKey::from_slice(&[0u8; 32]).unwrap(), - &Nonce::from_slice(&[0u8; 12]).unwrap(), - &dst_out_ct_2, - Some(&[0u8; 5]), - &mut dst_out_pt_2, - ) - .unwrap(); -} + #[test] + #[should_panic] + fn test_panic_empty_buf() { + let poly1305_key = poly1305_key_gen(&[0u8; 32], &[0u8; 12]).unwrap(); + let mut poly1305_state = poly1305::init(&poly1305_key); -#[test] -fn test_bad_ct_length_and_empty_out_decrypt() { - let dst_out_ct_1 = [0u8; POLY1305_BLOCKSIZE]; - let dst_out_ct_2 = [0u8; POLY1305_BLOCKSIZE - 1]; - let dst_out_ct_3 = [0u8; POLY1305_BLOCKSIZE + 1]; - - let mut dst_out_pt_1 = [0u8; 64]; - let mut dst_out_pt_2 = [0u8; 0]; - - assert!(open( - &SecretKey::from_slice(&[0u8; 32]).unwrap(), - &Nonce::from_slice(&[0u8; 12]).unwrap(), - &dst_out_ct_1, - None, - &mut dst_out_pt_1, - ) - .is_err()); - - assert!(open( - &SecretKey::from_slice(&[0u8; 32]).unwrap(), - &Nonce::from_slice(&[0u8; 12]).unwrap(), - &dst_out_ct_2, - None, - &mut dst_out_pt_1, - ) - .is_err()); - - assert!(open( - &SecretKey::from_slice(&[0u8; 32]).unwrap(), - &Nonce::from_slice(&[0u8; 12]).unwrap(), - &dst_out_ct_3, - None, - &mut dst_out_pt_2, - ) - .is_err()); -} + process_authentication(&mut poly1305_state, &[0u8; 0], &[0u8; 0], 64).unwrap(); + } -#[test] -fn rfc_8439_test_poly1305_key_gen_1() { - let key = [0u8; 32]; - let nonce = [ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - ]; - let expected = [ - 0x76, 0xb8, 0xe0, 0xad, 0xa0, 0xf1, 0x3d, 0x90, 0x40, 0x5d, 0x6a, 0xe5, 0x53, 0x86, 0xbd, - 0x28, 0xbd, 0xd2, 0x19, 0xb8, 0xa0, 0x8d, 0xed, 0x1a, 0xa8, 0x36, 0xef, 0xcc, 0x8b, 0x77, - 0x0d, 0xc7, - ]; - - assert_eq!( - poly1305_key_gen(&key, &nonce) - .unwrap() - .unprotected_as_bytes(), - expected.as_ref() - ); -} + #[test] + #[should_panic] + fn test_panic_above_length_index() { + let poly1305_key = poly1305_key_gen(&[0u8; 32], &[0u8; 12]).unwrap(); + let mut poly1305_state = poly1305::init(&poly1305_key); -#[test] -fn rfc_8439_test_poly1305_key_gen_2() { - let key = [ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x01, - ]; - let nonce = [ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, - ]; - let expected = [ - 0xec, 0xfa, 0x25, 0x4f, 0x84, 0x5f, 0x64, 0x74, 0x73, 0xd3, 0xcb, 0x14, 0x0d, 0xa9, 0xe8, - 0x76, 0x06, 0xcb, 0x33, 0x06, 0x6c, 0x44, 0x7b, 0x87, 0xbc, 0x26, 0x66, 0xdd, 0xe3, 0xfb, - 0xb7, 0x39, - ]; - - assert_eq!( - poly1305_key_gen(&key, &nonce) - .unwrap() - .unprotected_as_bytes(), - expected.as_ref() - ); -} + process_authentication(&mut poly1305_state, &[0u8; 0], &[0u8; 64], 65).unwrap(); + } + + #[test] + fn test_length_index() { + let poly1305_key = poly1305_key_gen(&[0u8; 32], &[0u8; 12]).unwrap(); + let mut poly1305_state = poly1305::init(&poly1305_key); + + assert!(process_authentication(&mut poly1305_state, &[0u8; 0], &[0u8; 64], 64).is_ok()); + + assert!(process_authentication(&mut poly1305_state, &[0u8; 0], &[0u8; 64], 63).is_ok()); + + assert!(process_authentication(&mut poly1305_state, &[0u8; 0], &[0u8; 64], 1).is_ok()); -#[test] -fn rfc_8439_test_poly1305_key_gen_3() { - let key = [ - 0x1c, 0x92, 0x40, 0xa5, 0xeb, 0x55, 0xd3, 0x8a, 0xf3, 0x33, 0x88, 0x86, 0x04, 0xf6, 0xb5, - 0xf0, 0x47, 0x39, 0x17, 0xc1, 0x40, 0x2b, 0x80, 0x09, 0x9d, 0xca, 0x5c, 0xbc, 0x20, 0x70, - 0x75, 0xc0, - ]; - let nonce = [ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, - ]; - let expected = [ - 0x96, 0x5e, 0x3b, 0xc6, 0xf9, 0xec, 0x7e, 0xd9, 0x56, 0x08, 0x08, 0xf4, 0xd2, 0x29, 0xf9, - 0x4b, 0x13, 0x7f, 0xf2, 0x75, 0xca, 0x9b, 0x3f, 0xcb, 0xdd, 0x59, 0xde, 0xaa, 0xd2, 0x33, - 0x10, 0xae, - ]; - - assert_eq!( - poly1305_key_gen(&key, &nonce) - .unwrap() - .unprotected_as_bytes(), - expected.as_ref() - ); + assert!(process_authentication(&mut poly1305_state, &[0u8; 0], &[0u8; 1], 1).is_ok()); + } + } } -#[test] -fn regression_detect_bigger_than_slice_bug() { - let pt = [0x5B; 79]; - - let mut dst_out_ct = [0u8; 79 + (POLY1305_BLOCKSIZE * 2)]; - - seal( - &SecretKey::from_slice(&[0u8; 32]).unwrap(), - &Nonce::from_slice(&[0u8; 12]).unwrap(), - &pt[..], - None, - &mut dst_out_ct, - ) - .unwrap(); - - // Verify that using a slice that is bigger than produces the exact same - // output as using a slice that is the exact required length - let mut dst_out_ct_2 = [0u8; 79 + POLY1305_BLOCKSIZE]; - - seal( - &SecretKey::from_slice(&[0u8; 32]).unwrap(), - &Nonce::from_slice(&[0u8; 12]).unwrap(), - &pt[..], - None, - &mut dst_out_ct_2, - ) - .unwrap(); - - assert!(dst_out_ct[..dst_out_ct_2.len()] == dst_out_ct_2[..]); +// Testing any test vectors that aren't put into library's /tests folder. +#[cfg(test)] +mod test_vectors { + use super::*; + + #[test] + fn rfc8439_poly1305_key_gen_1() { + let key = [0u8; 32]; + let nonce = [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + let expected = [ + 0x76, 0xb8, 0xe0, 0xad, 0xa0, 0xf1, 0x3d, 0x90, 0x40, 0x5d, 0x6a, 0xe5, 0x53, 0x86, + 0xbd, 0x28, 0xbd, 0xd2, 0x19, 0xb8, 0xa0, 0x8d, 0xed, 0x1a, 0xa8, 0x36, 0xef, 0xcc, + 0x8b, 0x77, 0x0d, 0xc7, + ]; + + assert_eq!( + poly1305_key_gen(&key, &nonce) + .unwrap() + .unprotected_as_bytes(), + expected.as_ref() + ); + } + + #[test] + fn rfc8439_poly1305_key_gen_2() { + let key = [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, + ]; + let nonce = [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + ]; + let expected = [ + 0xec, 0xfa, 0x25, 0x4f, 0x84, 0x5f, 0x64, 0x74, 0x73, 0xd3, 0xcb, 0x14, 0x0d, 0xa9, + 0xe8, 0x76, 0x06, 0xcb, 0x33, 0x06, 0x6c, 0x44, 0x7b, 0x87, 0xbc, 0x26, 0x66, 0xdd, + 0xe3, 0xfb, 0xb7, 0x39, + ]; + + assert_eq!( + poly1305_key_gen(&key, &nonce) + .unwrap() + .unprotected_as_bytes(), + expected.as_ref() + ); + } + + #[test] + fn rfc8439_poly1305_key_gen_3() { + let key = [ + 0x1c, 0x92, 0x40, 0xa5, 0xeb, 0x55, 0xd3, 0x8a, 0xf3, 0x33, 0x88, 0x86, 0x04, 0xf6, + 0xb5, 0xf0, 0x47, 0x39, 0x17, 0xc1, 0x40, 0x2b, 0x80, 0x09, 0x9d, 0xca, 0x5c, 0xbc, + 0x20, 0x70, 0x75, 0xc0, + ]; + let nonce = [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + ]; + let expected = [ + 0x96, 0x5e, 0x3b, 0xc6, 0xf9, 0xec, 0x7e, 0xd9, 0x56, 0x08, 0x08, 0xf4, 0xd2, 0x29, + 0xf9, 0x4b, 0x13, 0x7f, 0xf2, 0x75, 0xca, 0x9b, 0x3f, 0xcb, 0xdd, 0x59, 0xde, 0xaa, + 0xd2, 0x33, 0x10, 0xae, + ]; + + assert_eq!( + poly1305_key_gen(&key, &nonce) + .unwrap() + .unprotected_as_bytes(), + expected.as_ref() + ); + } } diff --git a/src/hazardous/aead/xchacha20poly1305.rs b/src/hazardous/aead/xchacha20poly1305.rs index c84156d3..ac27ee8f 100644 --- a/src/hazardous/aead/xchacha20poly1305.rs +++ b/src/hazardous/aead/xchacha20poly1305.rs @@ -147,58 +147,402 @@ pub fn open( Ok(()) } -#[test] -fn test_modified_tag_error() { - let mut dst_out_ct = [0u8; 80]; // 64 + Poly1305TagLen - let mut dst_out_pt = [0u8; 64]; - - seal( - &SecretKey::from_slice(&[0u8; 32]).unwrap(), - &Nonce::from_slice(&[0u8; 24]).unwrap(), - &[0u8; 64], - None, - &mut dst_out_ct, - ) - .unwrap(); - // Modify the tags first byte - dst_out_ct[65] ^= 1; - assert!(open( - &SecretKey::from_slice(&[0u8; 32]).unwrap(), - &Nonce::from_slice(&[0u8; 24]).unwrap(), - &dst_out_ct, - None, - &mut dst_out_pt, - ) - .is_err()); -} +// +// The tests below are the same tests as the ones in `chacha20poly1305` +// but with a bigger nonce. It's debatable whether this is needed, but right +// now I'm keeping them as they don't seem to bring any disadvantages. +// + +// Testing public functions in the module. +#[cfg(test)] +mod public { + use super::*; + use crate::hazardous::constants::POLY1305_BLOCKSIZE; + // One function tested per submodule. + + mod test_seal { + use super::*; + + #[test] + /// Related bug: https://github.com/brycx/orion/issues/52 + fn test_dst_out_length() { + let mut dst_out_ct = [0u8; 80]; // 64 + Poly1305TagLen + let mut dst_out_ct_less = [0u8; 79]; // 64 + Poly1305TagLen - 1 + let mut dst_out_ct_more = [0u8; 81]; // 64 + Poly1305TagLen + 1 + let mut dst_out_ct_more_2 = [0u8; 64 + (POLY1305_BLOCKSIZE * 2)]; + + assert!(seal( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 24]).unwrap(), + &[0u8; 64], + None, + &mut dst_out_ct, + ) + .is_ok()); + + // Related bug: #52 + assert!(seal( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 24]).unwrap(), + &[0u8; 64], + None, + &mut dst_out_ct_more, + ) + .is_ok()); + + // Related bug: #52 + assert!(seal( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 24]).unwrap(), + &[0u8; 64], + None, + &mut dst_out_ct_more_2, + ) + .is_ok()); + + assert!(seal( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 24]).unwrap(), + &[0u8; 64], + None, + &mut dst_out_ct_less, + ) + .is_err()); + } + + #[test] + fn test_plaintext_length() { + let mut dst_out_ct_0 = [0u8; 16]; // 0 + Poly1305TagLen + let mut dst_out_ct_1 = [0u8; 17]; // 1 + Poly1305TagLen + let mut dst_out_ct_128 = [0u8; 144]; // 128 + Poly1305TagLen + + assert!(seal( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 24]).unwrap(), + &[0u8; 0], + None, + &mut dst_out_ct_0, + ) + .is_err()); + + assert!(seal( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 24]).unwrap(), + &[0u8; 1], + None, + &mut dst_out_ct_1, + ) + .is_ok()); + + assert!(seal( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 24]).unwrap(), + &[0u8; 128], + None, + &mut dst_out_ct_128, + ) + .is_ok()); + } + } + + mod test_open { + use super::*; + + #[test] + fn test_ciphertext_with_tag_length() { + let mut dst_out_pt = [0u8; 64]; + + assert!(open( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 24]).unwrap(), + &[0u8; 0], + None, + &mut dst_out_pt, + ) + .is_err()); + + assert!(open( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 24]).unwrap(), + &[0u8; POLY1305_BLOCKSIZE], + None, + &mut dst_out_pt, + ) + .is_err()); + + assert!(open( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 24]).unwrap(), + &[0u8; POLY1305_BLOCKSIZE - 1], + None, + &mut dst_out_pt, + ) + .is_err()); + + let mut dst_out_ct = [0u8; 64 + 16]; + seal( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 24]).unwrap(), + &[0u8; POLY1305_BLOCKSIZE + 1], + None, + &mut dst_out_ct, + ) + .unwrap(); + + assert!(open( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 24]).unwrap(), + &dst_out_ct[..(POLY1305_BLOCKSIZE + 1) + 16], + None, + &mut dst_out_pt, + ) + .is_ok()); + } + + #[test] + fn test_dst_out_length() { + let mut dst_out_ct = [0u8; 64 + 16]; + seal( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 24]).unwrap(), + &[0u8; 64], + None, + &mut dst_out_ct, + ) + .unwrap(); + + let mut dst_out_pt = [0u8; 64]; + let mut dst_out_pt_0 = [0u8; 0]; + let mut dst_out_pt_less = [0u8; 63]; + let mut dst_out_pt_more = [0u8; 65]; + + assert!(open( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 24]).unwrap(), + &dst_out_ct, + None, + &mut dst_out_pt, + ) + .is_ok()); + + assert!(open( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 24]).unwrap(), + &dst_out_ct, + None, + &mut dst_out_pt_0, + ) + .is_err()); + + assert!(open( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 24]).unwrap(), + &dst_out_ct, + None, + &mut dst_out_pt_less, + ) + .is_err()); + + assert!(open( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 24]).unwrap(), + &dst_out_ct, + None, + &mut dst_out_pt_more, + ) + .is_ok()); + } + } + + // Proptests. Only exectued when NOT testing no_std. + #[cfg(feature = "safe_api")] + mod proptest { + use super::*; + + // Only return true if both a and b are true. + fn check_all_true(a: bool, b: bool) -> bool { (a == true) && (b == true) } + + quickcheck! { + // Sealing input, and then opening should always yield the same input. + fn prop_seal_open_same_input(input: Vec, ad: Vec) -> bool { + let pt = if input.is_empty() { + vec![1u8; 10] + } else { + input + }; + + let mut dst_out_ct_no_ad = vec![0u8; pt.len() + POLY1305_BLOCKSIZE]; + let mut dst_out_pt_no_ad = vec![0u8; pt.len()]; + + let mut dst_out_ct_with_ad = vec![0u8; pt.len() + POLY1305_BLOCKSIZE]; + let mut dst_out_pt_with_ad = vec![0u8; pt.len()]; + + seal( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 24]).unwrap(), + &pt[..], + None, + &mut dst_out_ct_no_ad, + ).unwrap(); + + open( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 24]).unwrap(), + &dst_out_ct_no_ad[..], + None, + &mut dst_out_pt_no_ad, + ).unwrap(); + + seal( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 24]).unwrap(), + &pt[..], + Some(&ad[..]), + &mut dst_out_ct_with_ad, + ).unwrap(); + + open( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 24]).unwrap(), + &dst_out_ct_with_ad[..], + Some(&ad[..]), + &mut dst_out_pt_with_ad, + ).unwrap(); + + check_all_true(dst_out_pt_no_ad == pt, dst_out_pt_with_ad == pt) + } + } + + quickcheck! { + // Sealing input, modifying the tag and then opening should + // always fail due to authentication. + fn prop_fail_on_bad_auth_tag(input: Vec, ad: Vec) -> bool { + let pt = if input.is_empty() { + vec![1u8; 10] + } else { + input + }; + + let mut dst_out_ct_no_ad = vec![0u8; pt.len() + POLY1305_BLOCKSIZE]; + let mut dst_out_pt_no_ad = vec![0u8; pt.len()]; + + let mut dst_out_ct_with_ad = vec![0u8; pt.len() + POLY1305_BLOCKSIZE]; + let mut dst_out_pt_with_ad = vec![0u8; pt.len()]; + + seal( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 24]).unwrap(), + &pt[..], + None, + &mut dst_out_ct_no_ad, + ).unwrap(); + + // Modify tags first byte + dst_out_ct_no_ad[pt.len() + 1] ^= 1; + + let res0 = if open( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 24]).unwrap(), + &dst_out_ct_no_ad[..], + None, + &mut dst_out_pt_no_ad, + ).is_err() { + true + } else { + false + }; + + seal( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 24]).unwrap(), + &pt[..], + Some(&ad[..]), + &mut dst_out_ct_with_ad, + ).unwrap(); + + // Modify tags first byte + dst_out_ct_with_ad[pt.len() + 1] ^= 1; + + let res1 = if open( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 24]).unwrap(), + &dst_out_ct_with_ad[..], + Some(&ad[..]), + &mut dst_out_pt_with_ad, + ).is_err() { + true + } else { + false + }; + + check_all_true(res0, res1) + } + } + + quickcheck! { + // Sealing input, modifying the ciphertext and then opening should + // always fail due to authentication. + fn prop_fail_on_bad_ciphertext(input: Vec, ad: Vec) -> bool { + let pt = if input.is_empty() { + vec![1u8; 10] + } else { + input + }; + + let mut dst_out_ct_no_ad = vec![0u8; pt.len() + POLY1305_BLOCKSIZE]; + let mut dst_out_pt_no_ad = vec![0u8; pt.len()]; + + let mut dst_out_ct_with_ad = vec![0u8; pt.len() + POLY1305_BLOCKSIZE]; + let mut dst_out_pt_with_ad = vec![0u8; pt.len()]; + + seal( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 24]).unwrap(), + &pt[..], + None, + &mut dst_out_ct_no_ad, + ).unwrap(); + + // Modify ciphertexts first byte + dst_out_ct_no_ad[0] ^= 1; + + let res0 = if open( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 24]).unwrap(), + &dst_out_ct_no_ad[..], + None, + &mut dst_out_pt_no_ad, + ).is_err() { + true + } else { + false + }; + + seal( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 24]).unwrap(), + &pt[..], + Some(&ad[..]), + &mut dst_out_ct_with_ad, + ).unwrap(); + + // Modify tags first byte + dst_out_ct_with_ad[0] ^= 1; + + let res1 = if open( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 24]).unwrap(), + &dst_out_ct_with_ad[..], + Some(&ad[..]), + &mut dst_out_pt_with_ad, + ).is_err() { + true + } else { + false + }; -#[test] -fn regression_detect_bigger_than_slice_bug() { - let pt = [0x5B; 79]; - - let mut dst_out_ct = [0u8; 79 + (16 * 2)]; - - seal( - &SecretKey::from_slice(&[0u8; 32]).unwrap(), - &Nonce::from_slice(&[0u8; 24]).unwrap(), - &pt[..], - None, - &mut dst_out_ct, - ) - .unwrap(); - - // Verify that using a slice that is bigger than produces the exact same - // output as using a slice that is the exact required length - let mut dst_out_ct_2 = [0u8; 79 + 16]; - - seal( - &SecretKey::from_slice(&[0u8; 32]).unwrap(), - &Nonce::from_slice(&[0u8; 24]).unwrap(), - &pt[..], - None, - &mut dst_out_ct_2, - ) - .unwrap(); - - assert!(dst_out_ct[..dst_out_ct_2.len()] == dst_out_ct_2[..]); + check_all_true(res0, res1) + } + } + } } diff --git a/src/hazardous/hash/blake2b.rs b/src/hazardous/hash/blake2b.rs index 675ef705..9d176a0b 100644 --- a/src/hazardous/hash/blake2b.rs +++ b/src/hazardous/hash/blake2b.rs @@ -205,9 +205,11 @@ impl Blake2b { #[inline(always)] /// Increment the internal states offset value `t`. fn increment_offset(&mut self, value: u64) { - self.t[0] += value; - if self.t[0] < value { - self.t[1] += 1; + let (res, was_overflow) = self.t[0].overflowing_add(value); + self.t[0] = res; + if was_overflow { + // If this panics size limit is reached. + self.t[1] = self.t[1].checked_add(1).unwrap(); } } @@ -458,218 +460,556 @@ pub fn verify( } } -#[test] -fn finalize_and_verify_true() { - let secret_key = SecretKey::from_slice("Jefe".as_bytes()).unwrap(); - let data = "what do ya want for nothing?".as_bytes(); +// Testing public functions in the module. +#[cfg(test)] +mod public { + use super::*; + + // One function tested per submodule. + + /// Compare two Blake2b state objects to check if their fields + /// are the same. + fn compare_blake2b_states(state_1: &Blake2b, state_2: &Blake2b) { + assert_eq!(state_1.init_state, state_2.init_state); + assert_eq!(state_1.internal_state, state_2.internal_state); + assert_eq!(state_1.buffer[..], state_2.buffer[..]); + assert_eq!(state_1.leftover, state_2.leftover); + assert_eq!(state_1.t, state_2.t); + assert_eq!(state_1.f, state_2.f); + assert_eq!(state_1.is_finalized, state_2.is_finalized); + assert_eq!(state_1.is_keyed, state_2.is_keyed); + assert_eq!(state_1.size, state_2.size); + } - let mut tag = init(Some(&secret_key), 64).unwrap(); - tag.update(data).unwrap(); + mod test_init { + use super::*; - assert_eq!( - verify( - &tag.finalize().unwrap(), - &SecretKey::from_slice("Jefe".as_bytes()).unwrap(), - 64, - data - ) - .unwrap(), - true - ); -} + /// Convenience testing function to avoid repetition when testing + /// init sizes with and without a secret key. Returns false if + /// incorrect Result is returned. + fn init_tester(sk: Option<&SecretKey>, size: usize) -> bool { + if size >= 1 && size <= BLAKE2B_OUTSIZE { + let res = if init(sk, size).is_ok() { true } else { false }; -#[test] -fn test_init_bad_sizes() { - assert!(init(None, 0).is_err()); - assert!(init(None, 65).is_err()); - assert!(init(None, 64).is_ok()); - assert!(init(None, 1).is_ok()); -} + return res; + } else { + let res = if init(sk, size).is_err() { true } else { false }; -#[test] -fn test_hasher_interface() { - let _digest_256 = Hasher::Blake2b256.digest(b"Test").unwrap(); - let _digest_384 = Hasher::Blake2b384.digest(b"Test").unwrap(); - let _digest_512 = Hasher::Blake2b512.digest(b"Test").unwrap(); + return res; + } + } - let _state_256 = Hasher::Blake2b256.init().unwrap(); - let _state_384 = Hasher::Blake2b384.init().unwrap(); - let _state_512 = Hasher::Blake2b512.init().unwrap(); -} + #[test] + fn test_init_size() { + assert!(init_tester(None, 0)); + assert!(init_tester(None, 65)); + assert!(init_tester(None, 64)); + assert!(init_tester(None, 1)); + + let sk = SecretKey::from_slice(&[0u8; 64]).unwrap(); + assert!(init_tester(Some(&sk), 0)); + assert!(init_tester(Some(&sk), 65)); + assert!(init_tester(Some(&sk), 64)); + assert!(init_tester(Some(&sk), 1)); + } -#[test] -fn double_finalize_err() { - let data = "what do ya want for nothing?".as_bytes(); + // Proptests. Only exectued when NOT testing no_std. + #[cfg(feature = "safe_api")] + mod proptest { + use super::*; + + quickcheck! { + /// Given a valid size parameter, init should always pass. If size + /// is invalid, then init should always fail. + fn prop_init_size_no_key(size: usize) -> bool { + init_tester(None, size) + } + } - let mut state = init(None, 64).unwrap(); - state.update(data).unwrap(); - let _ = state.finalize().unwrap(); - assert!(state.finalize().is_err()); -} + quickcheck! { + /// Given a valid size parameter, init should always pass. If size + /// is invalid, then init should always fail. + fn prop_init_size_key(size: usize) -> bool { + let sk = SecretKey::generate().unwrap(); + init_tester(Some(&sk), size) + } + } + } + } -#[test] -fn double_finalize_with_reset_ok_not_keyed() { - let data = "what do ya want for nothing?".as_bytes(); - - let mut state = init(None, 64).unwrap(); - state.update(data).unwrap(); - let one = state.finalize().unwrap(); - state.reset(None).unwrap(); - state.update(data).unwrap(); - let two = state.finalize().unwrap(); - assert_eq!(one.as_bytes(), two.as_bytes()); -} + mod test_verify { + use super::*; + + #[test] + fn finalize_and_verify_true() { + let secret_key = SecretKey::from_slice("Jefe".as_bytes()).unwrap(); + let data = "what do ya want for nothing?".as_bytes(); + + let mut tag = init(Some(&secret_key), 64).unwrap(); + tag.update(data).unwrap(); + + assert_eq!( + verify( + &tag.finalize().unwrap(), + &SecretKey::from_slice("Jefe".as_bytes()).unwrap(), + 64, + data + ) + .unwrap(), + true + ); + } -#[test] -fn double_finalize_with_reset_ok_keyed() { - let secret_key = SecretKey::from_slice(b"Testing").unwrap(); - let data = "what do ya want for nothing?".as_bytes(); - - let mut state = init(Some(&secret_key), 64).unwrap(); - state.update(data).unwrap(); - let one = state.finalize().unwrap(); - state.reset(Some(&secret_key)).unwrap(); - state.update(data).unwrap(); - let two = state.finalize().unwrap(); - assert_eq!(one.as_bytes(), two.as_bytes()); -} + // Proptests. Only exectued when NOT testing no_std. + #[cfg(feature = "safe_api")] + mod proptest { + use super::*; -#[test] -fn double_finalize_with_reset_no_update_ok() { - let data = "what do ya want for nothing?".as_bytes(); + quickcheck! { + /// When using the same parameters verify() should always yeild true. + fn prop_verify_same_params_true(data: Vec) -> bool { + let sk = SecretKey::generate().unwrap(); - let mut state = init(None, 64).unwrap(); - state.update(data).unwrap(); - let _ = state.finalize().unwrap(); - state.reset(None).unwrap(); - let _ = state.finalize().unwrap(); -} + let mut state = init(Some(&sk), 64).unwrap(); + state.update(&data[..]).unwrap(); + let tag = state.finalize().unwrap(); + // Failed verification on Err so res is not needed. + let _res = verify(&tag, &sk, 64, &data[..]).unwrap(); -#[test] -fn update_after_finalize_err() { - let data = "what do ya want for nothing?".as_bytes(); + true + } + } - let mut state = init(None, 64).unwrap(); - state.update(data).unwrap(); - let _ = state.finalize().unwrap(); - assert!(state.update(data).is_err()); -} + quickcheck! { + /// When using the same parameters verify() should always yeild true. + fn prop_verify_diff_key_false(data: Vec) -> bool { + let sk = SecretKey::generate().unwrap(); + let mut state = init(Some(&sk), 64).unwrap(); + state.update(&data[..]).unwrap(); + let tag = state.finalize().unwrap(); -#[test] -fn update_after_finalize_with_reset_ok() { - let data = "what do ya want for nothing?".as_bytes(); + let bad_sk = SecretKey::generate().unwrap(); - let mut state = init(None, 64).unwrap(); - state.update(data).unwrap(); - let _ = state.finalize().unwrap(); - state.reset(None).unwrap(); - state.update(data).unwrap(); -} + let res = if verify(&tag, &bad_sk, 64, &data[..]).is_err() { + true + } else { + false + }; + + res + } + } + } + } -#[test] -fn double_reset_ok() { - let data = "what do ya want for nothing?".as_bytes(); + mod test_hasher { + use super::*; - let mut state = init(None, 64).unwrap(); - state.update(data).unwrap(); - let _ = state.finalize().unwrap(); - state.reset(None).unwrap(); - state.reset(None).unwrap(); -} + #[test] + fn test_hasher_interface_no_panic_and_same_result() { + let digest_256 = Hasher::Blake2b256.digest(b"Test").unwrap(); + let digest_384 = Hasher::Blake2b384.digest(b"Test").unwrap(); + let digest_512 = Hasher::Blake2b512.digest(b"Test").unwrap(); -#[test] -fn err_on_keyed_switch_on_reset() { - let secret_key = SecretKey::from_slice(b"Testing").unwrap(); - let data = "what do ya want for nothing?".as_bytes(); + assert_eq!(digest_256, Hasher::Blake2b256.digest(b"Test").unwrap()); + assert_eq!(digest_384, Hasher::Blake2b384.digest(b"Test").unwrap()); + assert_eq!(digest_512, Hasher::Blake2b512.digest(b"Test").unwrap()); - let mut state = init(Some(&secret_key), 64).unwrap(); - state.update(data).unwrap(); - let _ = state.finalize().unwrap(); - assert!(state.reset(None).is_err()); + assert_ne!(digest_256, Hasher::Blake2b256.digest(b"Wrong").unwrap()); + assert_ne!(digest_384, Hasher::Blake2b384.digest(b"Wrong").unwrap()); + assert_ne!(digest_512, Hasher::Blake2b512.digest(b"Wrong").unwrap()); - let mut state_second = init(None, 64).unwrap(); - state_second.update(data).unwrap(); - let _ = state_second.finalize().unwrap(); - assert!(state_second.reset(Some(&secret_key)).is_err()); -} + let _state_256 = Hasher::Blake2b256.init().unwrap(); + let _state_384 = Hasher::Blake2b384.init().unwrap(); + let _state_512 = Hasher::Blake2b512.init().unwrap(); + } -#[test] -fn reset_after_update_correct_resets() { - let state_1 = init(None, 64).unwrap(); - - let mut state_2 = init(None, 64).unwrap(); - state_2.update(b"Tests").unwrap(); - state_2.reset(None).unwrap(); - - assert_eq!(state_1.init_state, state_2.init_state); - assert_eq!(state_1.internal_state, state_2.internal_state); - assert_eq!(state_1.buffer[..], state_2.buffer[..]); - assert_eq!(state_1.leftover, state_2.leftover); - assert_eq!(state_1.t, state_2.t); - assert_eq!(state_1.f, state_2.f); - assert_eq!(state_1.is_finalized, state_2.is_finalized); - assert_eq!(state_1.is_keyed, state_2.is_keyed); - assert_eq!(state_1.size, state_2.size); -} + // Proptests. Only exectued when NOT testing no_std. + #[cfg(feature = "safe_api")] + mod proptest { + use super::*; + + quickcheck! { + /// Given some data, .digest() should never fail in practice and should + /// produce the same output on a second call. + /// Only if data is unreasonably large. + fn prop_hasher_digest_no_panic_and_same_result(data: Vec) -> bool { + let d256 = Hasher::Blake2b256.digest(&data[..]).unwrap(); + let d384 = Hasher::Blake2b384.digest(&data[..]).unwrap(); + let d512 = Hasher::Blake2b512.digest(&data[..]).unwrap(); + + let d256_re = Hasher::Blake2b256.digest(&data[..]).unwrap(); + let d384_re = Hasher::Blake2b384.digest(&data[..]).unwrap(); + let d512_re = Hasher::Blake2b512.digest(&data[..]).unwrap(); + + (d256 == d256_re) && (d384 == d384_re) && (d512 == d512_re) + } + } -#[test] -fn reset_after_update_correct_resets_and_verify() { - // In non-keyed mode - let mut state_1 = init(None, 64).unwrap(); - state_1.update(b"Tests").unwrap(); - let d1 = state_1.finalize().unwrap(); - - let mut state_2 = init(None, 64).unwrap(); - state_2.update(b"Tests").unwrap(); - state_2.reset(None).unwrap(); - state_2.update(b"Tests").unwrap(); - let d2 = state_2.finalize().unwrap(); - - assert_eq!(d1, d2); - - // In keyed mode - let key = SecretKey::from_slice(&[0u8; 64]).unwrap(); - let mut state_1 = init(Some(&key), 64).unwrap(); - state_1.update(b"Tests").unwrap(); - let d1 = state_1.finalize().unwrap(); - - let mut state_2 = init(Some(&key), 64).unwrap(); - state_2.update(b"Tests").unwrap(); - state_2.reset(Some(&key)).unwrap(); - state_2.update(b"Tests").unwrap(); - let d2 = state_2.finalize().unwrap(); - - assert_eq!(d1, d2); -} + quickcheck! { + /// Given some data, .digest() should produce the same output as when + /// calling with streaming state. + fn prop_hasher_digest_256_same_as_streaming(data: Vec) -> bool { + let d256 = Hasher::Blake2b256.digest(&data[..]).unwrap();; -#[test] -#[cfg(feature = "safe_api")] -// Test for issues when incrementally processing data -// with leftover -fn test_streaming_consistency() { - for len in 0..BLAKE2B_BLOCKSIZE * 4 { - let data = vec![0u8; len]; - let mut state = init(None, 64).unwrap(); - let mut other_data: Vec = Vec::new(); + let mut state = init(None, 32).unwrap(); + state.update(&data[..]).unwrap(); + + (d256 == state.finalize().unwrap()) + } + } - other_data.extend_from_slice(&data); - state.update(&data).unwrap(); + quickcheck! { + /// Given some data, .digest() should produce the same output as when + /// calling with streaming state. + fn prop_hasher_digest_384_same_as_streaming(data: Vec) -> bool { + let d256 = Hasher::Blake2b384.digest(&data[..]).unwrap();; - if data.len() > BLAKE2B_BLOCKSIZE { - other_data.extend_from_slice(b""); - state.update(b"").unwrap(); + let mut state = init(None, 48).unwrap(); + state.update(&data[..]).unwrap(); + + (d256 == state.finalize().unwrap()) + } + } + + quickcheck! { + /// Given some data, .digest() should produce the same output as when + /// calling with streaming state. + fn prop_hasher_digest_512_same_as_streaming(data: Vec) -> bool { + let d256 = Hasher::Blake2b512.digest(&data[..]).unwrap();; + + let mut state = init(None, 64).unwrap(); + state.update(&data[..]).unwrap(); + + (d256 == state.finalize().unwrap()) + } + } + + quickcheck! { + /// Given two different data, .digest() should never produce the + /// same output.ValidationCryptoError + fn prop_hasher_digest_diff_input_diff_result(data: Vec) -> bool { + let d256 = Hasher::Blake2b256.digest(&data[..]).unwrap(); + let d384 = Hasher::Blake2b384.digest(&data[..]).unwrap(); + let d512 = Hasher::Blake2b512.digest(&data[..]).unwrap(); + + let d256_re = Hasher::Blake2b256.digest(b"Wrong data").unwrap(); + let d384_re = Hasher::Blake2b384.digest(b"Wrong data").unwrap(); + let d512_re = Hasher::Blake2b512.digest(b"Wrong data").unwrap(); + + (d256 != d256_re) && (d384 != d384_re) && (d512 != d512_re) + } + } + + quickcheck! { + /// .init() should never fail. + fn prop_hasher_init_no_panic() -> bool { + let _d256 = Hasher::Blake2b256.init().unwrap(); + let _d384 = Hasher::Blake2b384.init().unwrap(); + let _d512 = Hasher::Blake2b512.init().unwrap(); + + true + } + } } - if data.len() > BLAKE2B_BLOCKSIZE * 2 { - other_data.extend_from_slice(b"Extra"); - state.update(b"Extra").unwrap(); + } + + mod test_reset { + use super::*; + + #[test] + fn test_double_reset_ok() { + let mut state = init(None, 64).unwrap(); + state.update(b"Tests").unwrap(); + let _ = state.finalize().unwrap(); + state.reset(None).unwrap(); + state.reset(None).unwrap(); } - if data.len() > BLAKE2B_BLOCKSIZE * 3 { - other_data.extend_from_slice(&[0u8; 256]); - state.update(&[0u8; 256]).unwrap(); + + #[test] + fn test_switching_keyed_modes_fails() { + let secret_key = SecretKey::from_slice(b"Testing").unwrap(); + + let mut state = init(Some(&secret_key), 64).unwrap(); + state.update(b"Tests").unwrap(); + let _ = state.finalize().unwrap(); + assert!(state.reset(None).is_err()); + assert!(state.reset(Some(&secret_key)).is_ok()); + + let mut state_second = init(None, 64).unwrap(); + state_second.update(b"Tests").unwrap(); + let _ = state_second.finalize().unwrap(); + assert!(state_second.reset(Some(&secret_key)).is_err()); + assert!(state_second.reset(None).is_ok()); + } + } + + mod test_update { + use super::*; + + #[test] + /// Related bug: https://github.com/brycx/orion/issues/28 + fn test_update_after_finalize_fail() { + let mut state = init(None, 64).unwrap(); + state.update(b"Test").unwrap(); + let _ = state.finalize().unwrap(); + assert!(state.update(b"Test").is_err()); } - let digest_one_shot = Hasher::Blake2b512.digest(&other_data).unwrap(); + #[test] + fn test_update_after_finalize_with_reset() { + let mut state = init(None, 64).unwrap(); + state.update(b"Test").unwrap(); + let _ = state.finalize().unwrap(); + state.reset(None).unwrap(); + assert!(state.update(b"Test").is_ok()); + } + } + + mod test_finalize { + use super::*; + + #[test] + fn test_double_finalize_fail() { + let mut state = init(None, 64).unwrap(); + state.update(b"Test").unwrap(); + let _ = state.finalize().unwrap(); + assert!(state.finalize().is_err()); + } + + #[test] + fn test_finalize_after_reset() { + let mut state = init(None, 64).unwrap(); + state.update(b"Test").unwrap(); + let _ = state.finalize().unwrap(); + state.reset(None).unwrap(); + assert!(state.finalize().is_ok()); + } + + #[test] + fn test_double_finalize_with_reset_no_update() { + let mut state = init(None, 64).unwrap(); + state.update(b"Test").unwrap(); + let _ = state.finalize().unwrap(); + state.reset(None).unwrap(); + let _ = state.finalize().unwrap(); + } + } - assert!(state.finalize().unwrap().as_bytes() == digest_one_shot.as_bytes()); + mod test_streaming_interface { + use super::*; + + /// Related bug: https://github.com/brycx/orion/issues/46 + /// Testing different usage combinations of init(), update(), + /// finalize() and reset() produce the same Digest/Tag. + fn produces_same_hash(sk: Option<&SecretKey>, size: usize, data: &[u8]) { + // init(), update(), finalize() + let mut state_1 = init(sk, size).unwrap(); + state_1.update(data).unwrap(); + let res_1 = state_1.finalize().unwrap(); + + // init(), reset(), update(), finalize() + let mut state_2 = init(sk, size).unwrap(); + state_2.reset(sk).unwrap(); + state_2.update(data).unwrap(); + let res_2 = state_2.finalize().unwrap(); + + // init(), update(), reset(), update(), finalize() + let mut state_3 = init(sk, size).unwrap(); + state_3.update(data).unwrap(); + state_3.reset(sk).unwrap(); + state_3.update(data).unwrap(); + let res_3 = state_3.finalize().unwrap(); + + // init(), update(), finalize(), reset(), update(), finalize() + let mut state_4 = init(sk, size).unwrap(); + state_4.update(data).unwrap(); + let _ = state_4.finalize().unwrap(); + state_4.reset(sk).unwrap(); + state_4.update(data).unwrap(); + let res_4 = state_4.finalize().unwrap(); + + assert_eq!(res_1, res_2); + assert_eq!(res_2, res_3); + assert_eq!(res_3, res_4); + } + + /// Related bug: https://github.com/brycx/orion/issues/46 + /// Testing different usage combinations of init(), update(), + /// finalize() and reset() produce the same Digest/Tag. + fn produces_same_state(sk: Option<&SecretKey>, size: usize, data: &[u8]) { + // init() + let state_1 = init(sk, size).unwrap(); + + // init(), reset() + let mut state_2 = init(sk, size).unwrap(); + state_2.reset(sk).unwrap(); + + // init(), update(), reset() + let mut state_3 = init(sk, size).unwrap(); + state_3.update(data).unwrap(); + state_3.reset(sk).unwrap(); + + // init(), update(), finalize(), reset() + let mut state_4 = init(sk, size).unwrap(); + state_4.update(data).unwrap(); + let _ = state_4.finalize().unwrap(); + state_4.reset(sk).unwrap(); + + compare_blake2b_states(&state_1, &state_2); + compare_blake2b_states(&state_2, &state_3); + compare_blake2b_states(&state_3, &state_4); + } + + #[test] + /// Related bug: https://github.com/brycx/orion/issues/46 + fn test_produce_same_state() { + produces_same_state(None, 1, b"Tests"); + produces_same_state(None, 32, b"Tests"); + produces_same_state(None, 64, b"Tests"); + produces_same_state(None, 28, b"Tests"); + + let sk = SecretKey::from_slice(b"Testing").unwrap(); + produces_same_state(Some(&sk), 1, b"Tests"); + produces_same_state(Some(&sk), 32, b"Tests"); + produces_same_state(Some(&sk), 64, b"Tests"); + produces_same_state(Some(&sk), 28, b"Tests"); + } + + #[test] + /// Related bug: https://github.com/brycx/orion/issues/46 + fn test_produce_same_hash() { + produces_same_hash(None, 1, b"Tests"); + produces_same_hash(None, 32, b"Tests"); + produces_same_hash(None, 64, b"Tests"); + produces_same_hash(None, 28, b"Tests"); + + let sk = SecretKey::from_slice(b"Testing").unwrap(); + produces_same_hash(Some(&sk), 1, b"Tests"); + produces_same_hash(Some(&sk), 32, b"Tests"); + produces_same_hash(Some(&sk), 64, b"Tests"); + produces_same_hash(Some(&sk), 28, b"Tests"); + } + + #[test] + #[cfg(feature = "safe_api")] + // Test for issues when incrementally processing data + // with leftover + fn test_streaming_consistency() { + for len in 0..BLAKE2B_BLOCKSIZE * 4 { + let data = vec![0u8; len]; + let mut state = init(None, 64).unwrap(); + let mut other_data: Vec = Vec::new(); + + other_data.extend_from_slice(&data); + state.update(&data).unwrap(); + + if data.len() > BLAKE2B_BLOCKSIZE { + other_data.extend_from_slice(b""); + state.update(b"").unwrap(); + } + if data.len() > BLAKE2B_BLOCKSIZE * 2 { + other_data.extend_from_slice(b"Extra"); + state.update(b"Extra").unwrap(); + } + if data.len() > BLAKE2B_BLOCKSIZE * 3 { + other_data.extend_from_slice(&[0u8; 256]); + state.update(&[0u8; 256]).unwrap(); + } + + let digest_one_shot = Hasher::Blake2b512.digest(&other_data).unwrap(); + + assert!(state.finalize().unwrap().as_bytes() == digest_one_shot.as_bytes()); + } + } + // Proptests. Only exectued when NOT testing no_std. + #[cfg(feature = "safe_api")] + mod proptest { + use super::*; + + quickcheck! { + /// Related bug: https://github.com/brycx/orion/issues/46 + /// Test different streaming state usage patterns. + fn prop_same_hash_different_usage(data: Vec, size: usize) -> bool { + if size >= 1 && size <= BLAKE2B_OUTSIZE { + // Will panic on incorrect results. + produces_same_hash(None, size, &data[..]); + let sk = SecretKey::generate().unwrap(); + produces_same_hash(Some(&sk), size, &data[..]); + } + + true + } + } + + quickcheck! { + /// Related bug: https://github.com/brycx/orion/issues/46 + /// Test different streaming state usage patterns. + fn prop_same_state_different_usage(data: Vec, size: usize) -> bool { + if size >= 1 && size <= BLAKE2B_OUTSIZE { + // Will panic on incorrect results. + produces_same_state(None, size, &data[..]); + let sk = SecretKey::generate().unwrap(); + produces_same_state(Some(&sk), size, &data[..]); + } + + true + } + } + } + } +} + +// Testing private functions in the module. +#[cfg(test)] +mod private { + use super::*; + // One function tested per submodule. + + mod test_increment_offset { + use super::*; + + #[test] + fn test_offset_increase_values() { + let mut context = Blake2b { + init_state: [0u64; 8], + internal_state: IV, + buffer: [0u8; BLAKE2B_BLOCKSIZE], + leftover: 0, + t: [0u64; 2], + f: [0u64; 2], + is_finalized: false, + is_keyed: false, + size: 1, + }; + + context.increment_offset(1); + assert!(context.t == [1u64, 0u64]); + context.increment_offset(17); + assert!(context.t == [18u64, 0u64]); + context.increment_offset(12); + assert!(context.t == [30u64, 0u64]); + // Overflow + context.increment_offset(u64::max_value()); + assert!(context.t == [29u64, 1u64]); + } + + #[test] + #[should_panic] + fn test_panic_on_second_overflow() { + let mut context = Blake2b { + init_state: [0u64; 8], + internal_state: IV, + buffer: [0u8; BLAKE2B_BLOCKSIZE], + leftover: 0, + t: [1u64, u64::max_value()], + f: [0u64; 2], + is_finalized: false, + is_keyed: false, + size: 1, + }; + + context.increment_offset(u64::max_value()); + } } } diff --git a/src/hazardous/hash/sha512.rs b/src/hazardous/hash/sha512.rs index 370eba7c..3d59520b 100644 --- a/src/hazardous/hash/sha512.rs +++ b/src/hazardous/hash/sha512.rs @@ -250,10 +250,12 @@ impl Sha512 { // left-shift to get bit-sized representation of length // using .unwrap() because it should not panic in practice let len = length.checked_shl(3).unwrap(); - self.message_len[1] += len; + let (res, was_overflow) = self.message_len[1].overflowing_add(len); + self.message_len[1] = res; - if self.message_len[1] < len { - self.message_len[0] += 1; + if was_overflow { + // If this panics size limit is reached. + self.message_len[0] = self.message_len[0].checked_add(1).unwrap(); } } @@ -374,130 +376,294 @@ pub fn digest(data: &[u8]) -> Result { Ok(state.finalize()?) } -#[test] -fn double_finalize_err() { - let data = "what do ya want for nothing?".as_bytes(); - - let mut state = init(); - state.update(data).unwrap(); - let _ = state.finalize().unwrap(); - assert!(state.finalize().is_err()); +#[cfg(test)] +/// Compare two Sha512 state objects to check if their fields +/// are the same. +pub fn compare_sha512_states(state_1: &Sha512, state_2: &Sha512) { + assert_eq!(state_1.working_state, state_2.working_state); + assert_eq!(state_1.buffer[..], state_2.buffer[..]); + assert_eq!(state_1.leftover, state_2.leftover); + assert_eq!(state_1.message_len, state_2.message_len); + assert_eq!(state_1.is_finalized, state_2.is_finalized); } -#[test] -fn double_finalize_with_reset_ok_keyed() { - let data = "what do ya want for nothing?".as_bytes(); +// Testing public functions in the module. +#[cfg(test)] +mod public { + use super::*; - let mut state = init(); - state.update(data).unwrap(); - let one = state.finalize().unwrap(); - state.reset(); - state.update(data).unwrap(); - let two = state.finalize().unwrap(); - assert_eq!(one.as_bytes(), two.as_bytes()); -} + // One function tested per submodule. + mod test_reset { + use super::*; -#[test] -fn double_finalize_with_reset_no_update_ok() { - let data = "what do ya want for nothing?".as_bytes(); + #[test] + fn test_double_reset_ok() { + let data = "what do ya want for nothing?".as_bytes(); - let mut state = init(); - state.update(data).unwrap(); - let _ = state.finalize().unwrap(); - state.reset(); - let _ = state.finalize().unwrap(); -} + let mut state = init(); + state.update(data).unwrap(); + let _ = state.finalize().unwrap(); + state.reset(); + state.reset(); + } + } -#[test] -fn update_after_finalize_err() { - let data = "what do ya want for nothing?".as_bytes(); + mod test_update { + use super::*; - let mut state = init(); - state.update(data).unwrap(); - let _ = state.finalize().unwrap(); - assert!(state.update(data).is_err()); -} + #[test] + fn test_update_after_finalize_with_reset_ok() { + let data = "what do ya want for nothing?".as_bytes(); -#[test] -fn update_after_finalize_with_reset_ok() { - let data = "what do ya want for nothing?".as_bytes(); + let mut state = init(); + state.update(data).unwrap(); + let _ = state.finalize().unwrap(); + state.reset(); + state.update(data).unwrap(); + } - let mut state = init(); - state.update(data).unwrap(); - let _ = state.finalize().unwrap(); - state.reset(); - state.update(data).unwrap(); -} + #[test] + /// Related bug: https://github.com/brycx/orion/issues/28 + fn test_update_after_finalize_err() { + let data = "what do ya want for nothing?".as_bytes(); -#[test] -fn double_reset_ok() { - let data = "what do ya want for nothing?".as_bytes(); + let mut state = init(); + state.update(data).unwrap(); + let _ = state.finalize().unwrap(); + assert!(state.update(data).is_err()); + } + } - let mut state = init(); - state.update(data).unwrap(); - let _ = state.finalize().unwrap(); - state.reset(); - state.reset(); -} + mod test_finalize { + use super::*; -#[test] -fn reset_after_update_correct_resets() { - let state_1 = init(); + #[test] + fn test_double_finalize_with_reset_no_update_ok() { + let data = "what do ya want for nothing?".as_bytes(); - let mut state_2 = init(); - state_2.update(b"Tests").unwrap(); - state_2.reset(); + let mut state = init(); + state.update(data).unwrap(); + let _ = state.finalize().unwrap(); + state.reset(); + let _ = state.finalize().unwrap(); + } - assert_eq!(state_1.working_state, state_2.working_state); - assert_eq!(state_1.buffer[..], state_2.buffer[..]); - assert_eq!(state_1.leftover, state_2.leftover); - assert_eq!(state_1.message_len, state_2.message_len); - assert_eq!(state_1.is_finalized, state_2.is_finalized); -} + #[test] + fn test_double_finalize_with_reset_ok() { + let data = "what do ya want for nothing?".as_bytes(); + + let mut state = init(); + state.update(data).unwrap(); + let one = state.finalize().unwrap(); + state.reset(); + state.update(data).unwrap(); + let two = state.finalize().unwrap(); + assert_eq!(one.as_bytes(), two.as_bytes()); + } -#[test] -fn reset_after_update_correct_resets_and_verify() { - let mut state_1 = init(); - state_1.update(b"Tests").unwrap(); - let d1 = state_1.finalize().unwrap(); + #[test] + fn test_double_finalize_err() { + let data = "what do ya want for nothing?".as_bytes(); - let mut state_2 = init(); - state_2.update(b"Tests").unwrap(); - state_2.reset(); - state_2.update(b"Tests").unwrap(); - let d2 = state_2.finalize().unwrap(); + let mut state = init(); + state.update(data).unwrap(); + let _ = state.finalize().unwrap(); + assert!(state.finalize().is_err()); + } - assert_eq!(d1, d2); -} + } -#[test] -#[cfg(feature = "safe_api")] -// Test for issues when incrementally processing data -// with leftover -fn test_streaming_consistency() { - for len in 0..SHA2_BLOCKSIZE * 4 { - let data = vec![0u8; len]; - let mut state = init(); - let mut other_data: Vec = Vec::new(); - - other_data.extend_from_slice(&data); - state.update(&data).unwrap(); - - if data.len() > SHA2_BLOCKSIZE { - other_data.extend_from_slice(b""); - state.update(b"").unwrap(); + mod test_streaming_interface { + use super::*; + + /// Related bug: https://github.com/brycx/orion/issues/46 + /// Testing different usage combinations of init(), update(), + /// finalize() and reset() produce the same Digest. + fn produces_same_hash(data: &[u8]) { + // init(), update(), finalize() + let mut state_1 = init(); + state_1.update(data).unwrap(); + let res_1 = state_1.finalize().unwrap(); + + // init(), reset(), update(), finalize() + let mut state_2 = init(); + state_2.reset(); + state_2.update(data).unwrap(); + let res_2 = state_2.finalize().unwrap(); + + // init(), update(), reset(), update(), finalize() + let mut state_3 = init(); + state_3.update(data).unwrap(); + state_3.reset(); + state_3.update(data).unwrap(); + let res_3 = state_3.finalize().unwrap(); + + // init(), update(), finalize(), reset(), update(), finalize() + let mut state_4 = init(); + state_4.update(data).unwrap(); + let _ = state_4.finalize().unwrap(); + state_4.reset(); + state_4.update(data).unwrap(); + let res_4 = state_4.finalize().unwrap(); + + assert_eq!(res_1, res_2); + assert_eq!(res_2, res_3); + assert_eq!(res_3, res_4); } - if data.len() > SHA2_BLOCKSIZE * 2 { - other_data.extend_from_slice(b"Extra"); - state.update(b"Extra").unwrap(); + + /// Related bug: https://github.com/brycx/orion/issues/46 + /// Testing different usage combinations of init(), update(), + /// finalize() and reset() produce the same Digest. + fn produces_same_state(data: &[u8]) { + // init() + let state_1 = init(); + + // init(), reset() + let mut state_2 = init(); + state_2.reset(); + + // init(), update(), reset() + let mut state_3 = init(); + state_3.update(data).unwrap(); + state_3.reset(); + + // init(), update(), finalize(), reset() + let mut state_4 = init(); + state_4.update(data).unwrap(); + let _ = state_4.finalize().unwrap(); + state_4.reset(); + + compare_sha512_states(&state_1, &state_2); + compare_sha512_states(&state_2, &state_3); + compare_sha512_states(&state_3, &state_4); } - if data.len() > SHA2_BLOCKSIZE * 3 { - other_data.extend_from_slice(&[0u8; 256]); - state.update(&[0u8; 256]).unwrap(); + + #[test] + /// Related bug: https://github.com/brycx/orion/issues/46 + fn test_produce_same_state() { produces_same_state(b"Tests"); } + + #[test] + /// Related bug: https://github.com/brycx/orion/issues/46 + fn test_produce_same_hash() { produces_same_hash(b"Tests"); } + + #[test] + #[cfg(feature = "safe_api")] + // Test for issues when incrementally processing data + // with leftover + fn test_streaming_consistency() { + for len in 0..SHA2_BLOCKSIZE * 4 { + let data = vec![0u8; len]; + let mut state = init(); + let mut other_data: Vec = Vec::new(); + + other_data.extend_from_slice(&data); + state.update(&data).unwrap(); + + if data.len() > SHA2_BLOCKSIZE { + other_data.extend_from_slice(b""); + state.update(b"").unwrap(); + } + if data.len() > SHA2_BLOCKSIZE * 2 { + other_data.extend_from_slice(b"Extra"); + state.update(b"Extra").unwrap(); + } + if data.len() > SHA2_BLOCKSIZE * 3 { + other_data.extend_from_slice(&[0u8; 256]); + state.update(&[0u8; 256]).unwrap(); + } + + let digest_one_shot = digest(&other_data).unwrap(); + + assert!(state.finalize().unwrap().as_bytes() == digest_one_shot.as_bytes()); + } } + // Proptests. Only exectued when NOT testing no_std. + #[cfg(feature = "safe_api")] + mod proptest { + use super::*; + + quickcheck! { + /// Related bug: https://github.com/brycx/orion/issues/46 + /// Test different streaming state usage patterns. + fn prop_same_hash_different_usage(data: Vec) -> bool { + // Will panic on incorrect results. + produces_same_hash(&data[..]); + + true + } + } + + quickcheck! { + /// Related bug: https://github.com/brycx/orion/issues/46 + /// Test different streaming state usage patterns. + fn prop_same_state_different_usage(data: Vec) -> bool { + // Will panic on incorrect results. + produces_same_state(&data[..]); - let digest_one_shot = digest(&other_data).unwrap(); + true + } + } - assert!(state.finalize().unwrap().as_bytes() == digest_one_shot.as_bytes()); + quickcheck! { + /// Using the one-shot function should always produce the + /// same result as when using the streaming interface. + fn prop_digest_same_as_streaming(data: Vec) -> bool { + let mut state = init(); + state.update(&data[..]).unwrap(); + let stream = state.finalize().unwrap(); + let one_shot = digest(&data[..]).unwrap(); + + (one_shot == stream) + } + } + } + } +} + +// Testing private functions in the module. +#[cfg(test)] +mod private { + use super::*; + // One function tested per submodule. + + mod test_increment_mlen { + use super::*; + + #[test] + fn test_mlen_increase_values() { + let mut context = Sha512 { + working_state: H0, + buffer: [0u8; SHA2_BLOCKSIZE], + leftover: 0, + message_len: [0u64; 2], + is_finalized: false, + }; + + context.increment_mlen(1); + assert!(context.message_len == [0u64, 8u64]); + context.increment_mlen(17); + assert!(context.message_len == [0u64, 144u64]); + context.increment_mlen(12); + assert!(context.message_len == [0u64, 240u64]); + // Overflow + context.increment_mlen(u64::max_value()); + assert!(context.message_len == [1u64, 232u64]); + } + + #[test] + #[should_panic] + fn test_panic_on_second_overflow() { + let mut context = Sha512 { + working_state: H0, + buffer: [0u8; SHA2_BLOCKSIZE], + leftover: 0, + message_len: [u64::max_value(), u64::max_value() - 7], + is_finalized: false, + }; + // u64::max_value() - 7, to leave so that the length represented + // in bites should overflow by exactly one. + + context.increment_mlen(1); + } } } diff --git a/src/hazardous/kdf/hkdf.rs b/src/hazardous/kdf/hkdf.rs index 98058fb9..93c865d4 100644 --- a/src/hazardous/kdf/hkdf.rs +++ b/src/hazardous/kdf/hkdf.rs @@ -148,89 +148,119 @@ pub fn verify( } } +// Testing public functions in the module. #[cfg(test)] -mod test { - extern crate hex; - use self::hex::decode; - use crate::hazardous::kdf::hkdf::*; - - #[test] - fn hkdf_maximum_length_512() { - // Max allowed length here is 16320 - let mut okm_out = [0u8; 17000]; - let prk = extract("".as_bytes(), "".as_bytes()).unwrap(); - - assert!(expand(&prk, Some(b""), &mut okm_out).is_err()); - } +mod public { + use super::*; + + mod test_expand { + use super::*; + + #[test] + fn hkdf_maximum_length() { + // Max allowed length here is 16320 + let mut okm_out = [0u8; 17000]; + let prk = extract("".as_bytes(), "".as_bytes()).unwrap(); + + assert!(expand(&prk, Some(b""), &mut okm_out).is_err()); + } - #[test] - fn hkdf_zero_length() { - let mut okm_out = [0u8; 0]; - let prk = extract("".as_bytes(), "".as_bytes()).unwrap(); + #[test] + fn hkdf_zero_length() { + let mut okm_out = [0u8; 0]; + let prk = extract("".as_bytes(), "".as_bytes()).unwrap(); - assert!(expand(&prk, Some(b""), &mut okm_out).is_err()); + assert!(expand(&prk, Some(b""), &mut okm_out).is_err()); + } } - #[test] - fn hkdf_verify_true() { - let ikm = decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap(); - let salt = decode("000102030405060708090a0b0c").unwrap(); - let info = decode("f0f1f2f3f4f5f6f7f8f9").unwrap(); - let mut okm_out = [0u8; 42]; - - let expected_okm = decode( - "832390086cda71fb47625bb5ceb168e4c8e26a1a16ed34d9fc7fe92c1481579338da362cb8d9f925d7cb", - ) - .unwrap(); - - assert_eq!( - verify(&expected_okm, &salt, &ikm, Some(&info), &mut okm_out).unwrap(), - true - ); + #[cfg(feature = "safe_api")] + // Mark safe_api because currently it only contains proptests. + mod test_derive_key { + use super::*; + + // Proptests. Only exectued when NOT testing no_std. + #[cfg(feature = "safe_api")] + mod proptest { + use super::*; + + quickcheck! { + /// Using derive_key() should always yield the same result + /// as using extract and expand seperately. + fn prop_test_derive_key_same_seperate(salt: Vec, ikm: Vec, info: Vec, outsize: usize) -> bool { + + let outsize_checked = if outsize == 0 || outsize > 16320 { + 64 + } else { + outsize + }; + + let prk = extract(&salt[..], &ikm[..]).unwrap(); + let mut out = vec![0u8; outsize_checked]; + expand(&prk, Some(&info[..]), &mut out).unwrap(); + + let mut out_one_shot = vec![0u8; outsize_checked]; + derive_key(&salt[..], &ikm[..], Some(&info[..]), &mut out_one_shot).unwrap(); + + (out == out_one_shot) + } + } + } } - #[test] - fn hkdf_verify_wrong_salt() { - let salt = "salt".as_bytes(); - let ikm = decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap(); - let info = "".as_bytes(); - let mut okm_out = [0u8; 42]; + mod test_verify { + use super::*; - let expected_okm = decode( - "8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d9d201395faa4b61a96c8", - ) - .unwrap(); + #[test] + fn hkdf_verify_true() { + let ikm = b"0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"; + let salt = b"000102030405060708090a0b0c"; + let info = b"f0f1f2f3f4f5f6f7f8f9"; + let mut okm_out = [0u8; 42]; + let mut okm_out_verify = [0u8; 42]; - assert!(verify(&expected_okm, &salt, &ikm, Some(info), &mut okm_out).is_err()); - } + derive_key(salt, ikm, Some(info), &mut okm_out).unwrap(); - #[test] - fn hkdf_verify_wrong_ikm() { - let salt = "".as_bytes(); - let ikm = decode("0b").unwrap(); - let info = "".as_bytes(); - let mut okm_out = [0u8; 42]; + assert!(verify(&okm_out, salt, ikm, Some(info), &mut okm_out_verify).is_ok()); + } - let expected_okm = decode( - "8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d9d201395faa4b61a96c8", - ) - .unwrap(); + #[test] + fn hkdf_verify_wrong_salt() { + let ikm = b"0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"; + let salt = b"000102030405060708090a0b0c"; + let info = b"f0f1f2f3f4f5f6f7f8f9"; + let mut okm_out = [0u8; 42]; + let mut okm_out_verify = [0u8; 42]; - assert!(verify(&expected_okm, &salt, &ikm, Some(info), &mut okm_out).is_err()); - } + derive_key(salt, ikm, Some(info), &mut okm_out).unwrap(); + + assert!(verify(&okm_out, b"", ikm, Some(info), &mut okm_out_verify).is_err()); + } + + #[test] + fn hkdf_verify_wrong_ikm() { + let ikm = b"0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"; + let salt = b"000102030405060708090a0b0c"; + let info = b"f0f1f2f3f4f5f6f7f8f9"; + let mut okm_out = [0u8; 42]; + let mut okm_out_verify = [0u8; 42]; - #[test] - fn verify_diff_length() { - let salt = "".as_bytes(); - let ikm = decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap(); - let info = "".as_bytes(); - let mut okm_out = [0u8; 72]; + derive_key(salt, ikm, Some(info), &mut okm_out).unwrap(); - let expected_okm = decode( - "8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d9d201395faa4b61a96c8", - ) - .unwrap(); + assert!(verify(&okm_out, salt, b"", Some(info), &mut okm_out_verify).is_err()); + } + + #[test] + fn verify_diff_length() { + let ikm = b"0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"; + let salt = b"000102030405060708090a0b0c"; + let info = b"f0f1f2f3f4f5f6f7f8f9"; + let mut okm_out = [0u8; 42]; + let mut okm_out_verify = [0u8; 43]; - assert!(verify(&expected_okm, &salt, &ikm, Some(info), &mut okm_out).is_err()); + derive_key(salt, ikm, Some(info), &mut okm_out).unwrap(); + + assert!(verify(&okm_out, salt, ikm, Some(info), &mut okm_out_verify).is_err()); + } } } diff --git a/src/hazardous/kdf/pbkdf2.rs b/src/hazardous/kdf/pbkdf2.rs index f9b369e7..21553042 100644 --- a/src/hazardous/kdf/pbkdf2.rs +++ b/src/hazardous/kdf/pbkdf2.rs @@ -67,7 +67,6 @@ use crate::{ }, util, }; -use byteorder::{BigEndian, ByteOrder}; construct_hmac_key! { /// A type to represent the `Password` that PBKDF2 hashes. @@ -93,10 +92,8 @@ fn function_f( hmac: &mut hmac::Hmac, ) -> Result<(), UnknownCryptoError> { let mut u_step: HLenArray = [0u8; 64]; - // First 4 bytes used for index BE conversion - BigEndian::write_u32(&mut u_step[..4], index); hmac.update(salt)?; - hmac.update(&u_step[..4])?; + hmac.update(&index.to_be_bytes())?; u_step.copy_from_slice(&hmac.finalize()?.unprotected_as_bytes()); dk_block.copy_from_slice(&u_step[..block_len]); @@ -177,92 +174,109 @@ pub fn verify( } } +// Testing public functions in the module. #[cfg(test)] -mod test { +mod public { + use super::*; - extern crate hex; - use self::hex::decode; - use crate::hazardous::kdf::pbkdf2::*; + // One function tested per submodule. - #[test] - fn zero_iterations_err() { - let password = Password::from_slice("password".as_bytes()).unwrap(); - let salt = "salt".as_bytes(); - let iterations: usize = 0; - let mut okm_out = [0u8; 15]; + mod test_verify { + use super::*; - assert!(derive_key(&password, salt, iterations, &mut okm_out).is_err()); - } + #[test] + fn verify_true() { + let password = Password::from_slice("pass\0word".as_bytes()).unwrap(); + let salt = "sa\0lt".as_bytes(); + let iterations: usize = 4096; + let mut okm_out = [0u8; 16]; + let mut okm_out_verify = [0u8; 16]; - #[test] - fn zero_dklen_err() { - let password = Password::from_slice("password".as_bytes()).unwrap(); - let salt = "salt".as_bytes(); - let iterations: usize = 1; - let mut okm_out = [0u8; 0]; + derive_key(&password, &salt, iterations, &mut okm_out).unwrap(); - assert!(derive_key(&password, salt, iterations, &mut okm_out).is_err()); - } + assert!(verify(&okm_out, &password, salt, iterations, &mut okm_out_verify).is_ok()); + } - #[test] - fn verify_true() { - let password = Password::from_slice("pass\0word".as_bytes()).unwrap(); - let salt = "sa\0lt".as_bytes(); - let iterations: usize = 4096; - let mut okm_out = [0u8; 16]; + #[test] + fn verify_false_wrong_salt() { + let password = Password::from_slice("pass\0word".as_bytes()).unwrap(); + let salt = "sa\0lt".as_bytes(); + let iterations: usize = 4096; + let mut okm_out = [0u8; 16]; + let mut okm_out_verify = [0u8; 16]; - let expected_dk = decode("9d9e9c4cd21fe4be24d5b8244c759665").unwrap(); + derive_key(&password, &salt, iterations, &mut okm_out).unwrap(); - assert_eq!( - verify(&expected_dk, &password, salt, iterations, &mut okm_out).unwrap(), - true - ); - } + assert!(verify(&okm_out, &password, b"", iterations, &mut okm_out_verify).is_err()); + } + #[test] + fn verify_false_wrong_password() { + let password = Password::from_slice("pass\0word".as_bytes()).unwrap(); + let salt = "sa\0lt".as_bytes(); + let iterations: usize = 4096; + let mut okm_out = [0u8; 16]; + let mut okm_out_verify = [0u8; 16]; + + derive_key(&password, &salt, iterations, &mut okm_out).unwrap(); + + assert!(verify( + &okm_out, + &Password::from_slice(b"").unwrap(), + salt, + iterations, + &mut okm_out_verify + ) + .is_err()); + } - #[test] - fn verify_false_wrong_salt() { - let password = Password::from_slice("pass\0word".as_bytes()).unwrap(); - let salt = "".as_bytes(); - let iterations: usize = 4096; - let mut okm_out = [0u8; 16]; + #[test] + fn verify_diff_dklen_error() { + let password = Password::from_slice("pass\0word".as_bytes()).unwrap(); + let salt = "sa\0lt".as_bytes(); + let iterations: usize = 4096; + let mut okm_out = [0u8; 16]; + let mut okm_out_verify = [0u8; 32]; - let expected_dk = decode("9d9e9c4cd21fe4be24d5b8244c759665").unwrap(); + derive_key(&password, &salt, iterations, &mut okm_out).unwrap(); - assert!(verify(&expected_dk, &password, salt, iterations, &mut okm_out).is_err()); - } - #[test] - fn verify_false_wrong_password() { - let password = Password::from_slice("".as_bytes()).unwrap(); - let salt = "sa\0lt".as_bytes(); - let iterations: usize = 4096; - let mut okm_out = [0u8; 16]; + assert!(verify(&okm_out, &password, salt, iterations, &mut okm_out_verify).is_err()); + } - let expected_dk = decode("9d9e9c4cd21fe4be24d5b8244c759665").unwrap(); + #[test] + fn verify_diff_iter_error() { + let password = Password::from_slice("pass\0word".as_bytes()).unwrap(); + let salt = "sa\0lt".as_bytes(); + let iterations: usize = 4096; + let mut okm_out = [0u8; 16]; + let mut okm_out_verify = [0u8; 16]; - assert!(verify(&expected_dk, &password, salt, iterations, &mut okm_out).is_err()); - } + derive_key(&password, &salt, iterations, &mut okm_out).unwrap(); - #[test] - fn verify_diff_dklen_error() { - let password = Password::from_slice("pass\0word".as_bytes()).unwrap(); - let salt = "sa\0lt".as_bytes(); - let iterations: usize = 4096; - let mut okm_out = [0u8; 32]; + assert!(verify(&okm_out, &password, salt, 1024, &mut okm_out_verify).is_err()); + } + } - let expected_dk = decode("9d9e9c4cd21fe4be24d5b8244c759665").unwrap(); + mod test_derive_key { + use super::*; - assert!(verify(&expected_dk, &password, salt, iterations, &mut okm_out).is_err()); - } + #[test] + fn zero_iterations_err() { + let password = Password::from_slice("password".as_bytes()).unwrap(); + let salt = "salt".as_bytes(); + let iterations: usize = 0; + let mut okm_out = [0u8; 15]; - #[test] - fn verify_diff_iter_error() { - let password = Password::from_slice("pass\0word".as_bytes()).unwrap(); - let salt = "sa\0lt".as_bytes(); - let iterations: usize = 512; - let mut okm_out = [0u8; 16]; + assert!(derive_key(&password, salt, iterations, &mut okm_out).is_err()); + } - let expected_dk = decode("9d9e9c4cd21fe4be24d5b8244c759665").unwrap(); + #[test] + fn zero_dklen_err() { + let password = Password::from_slice("password".as_bytes()).unwrap(); + let salt = "salt".as_bytes(); + let iterations: usize = 1; + let mut okm_out = [0u8; 0]; - assert!(verify(&expected_dk, &password, salt, iterations, &mut okm_out).is_err()); + assert!(derive_key(&password, salt, iterations, &mut okm_out).is_err()); + } } } diff --git a/src/hazardous/mac/hmac.rs b/src/hazardous/mac/hmac.rs index 1fe782d2..9633917b 100644 --- a/src/hazardous/mac/hmac.rs +++ b/src/hazardous/mac/hmac.rs @@ -228,156 +228,327 @@ pub fn verify( } } -#[test] -fn finalize_and_verify_true() { - let secret_key = SecretKey::from_slice("Jefe".as_bytes()).unwrap(); - let data = "what do ya want for nothing?".as_bytes(); - - let mut tag = init(&secret_key); - tag.update(data).unwrap(); - - assert_eq!( - verify( - &tag.finalize().unwrap(), - &SecretKey::from_slice("Jefe".as_bytes()).unwrap(), - data - ) - .unwrap(), - true - ); -} +// Testing public functions in the module. +#[cfg(test)] +mod public { + use super::*; -#[test] -fn veriy_false_wrong_data() { - let secret_key = SecretKey::from_slice("Jefe".as_bytes()).unwrap(); - let data = "what do ya want for nothing?".as_bytes(); + use crate::hazardous::hash::sha512::compare_sha512_states; - let mut tag = init(&secret_key); - tag.update(data).unwrap(); + // One function tested per submodule. - assert!(verify( - &tag.finalize().unwrap(), - &SecretKey::from_slice("Jefe".as_bytes()).unwrap(), - "what do ya want for something?".as_bytes() - ) - .is_err()); -} + /// Compare two Sha512 state objects to check if their fields + /// are the same. + fn compare_hmac_states(state_1: &Hmac, state_2: &Hmac) { + compare_sha512_states(&state_1.opad_hasher, &state_1.opad_hasher); + compare_sha512_states(&state_1.ipad_hasher, &state_1.ipad_hasher); -#[test] -fn veriy_false_wrong_secret_key() { - let secret_key = SecretKey::from_slice("Jefe".as_bytes()).unwrap(); - let data = "what do ya want for nothing?".as_bytes(); + assert_eq!(state_1.ipad[..], state_2.ipad[..]); + assert_eq!(state_1.is_finalized, state_2.is_finalized); + } - let mut tag = init(&secret_key); - tag.update(data).unwrap(); + mod test_verify { + use super::*; + + #[test] + fn finalize_and_verify_true() { + let secret_key = SecretKey::from_slice("Jefe".as_bytes()).unwrap(); + let data = "what do ya want for nothing?".as_bytes(); + + let mut tag = init(&secret_key); + tag.update(data).unwrap(); + + assert_eq!( + verify( + &tag.finalize().unwrap(), + &SecretKey::from_slice("Jefe".as_bytes()).unwrap(), + data + ) + .unwrap(), + true + ); + } - assert!(verify( - &tag.finalize().unwrap(), - &SecretKey::from_slice("Jose".as_bytes()).unwrap(), - data - ) - .is_err()); -} + // Proptests. Only exectued when NOT testing no_std. + #[cfg(feature = "safe_api")] + mod proptest { + use super::*; + + quickcheck! { + /// When using the same parameters verify() should always yeild true. + fn prop_verify_same_params_true(data: Vec) -> bool { + let sk = SecretKey::generate().unwrap(); + + let mut state = init(&sk); + state.update(&data[..]).unwrap(); + let tag = state.finalize().unwrap(); + // Failed verification on Err so res is not needed. + let _res = verify(&tag, &sk, &data[..]).unwrap(); + + true + } + } + + quickcheck! { + /// When using the same parameters verify() should always yeild true. + fn prop_verify_diff_key_false(data: Vec) -> bool { + let sk = SecretKey::generate().unwrap(); + let mut state = init(&sk); + state.update(&data[..]).unwrap(); + let tag = state.finalize().unwrap(); + + let bad_sk = SecretKey::generate().unwrap(); + + let res = if verify(&tag, &bad_sk, &data[..]).is_err() { + true + } else { + false + }; + + res + } + } + } + } -#[test] -fn double_finalize_err() { - let secret_key = SecretKey::from_slice("Jefe".as_bytes()).unwrap(); - let data = "what do ya want for nothing?".as_bytes(); + mod test_reset { + use super::*; - let mut tag = init(&secret_key); - tag.update(data).unwrap(); - let _ = tag.finalize().unwrap(); - assert!(tag.finalize().is_err()); -} + #[test] + fn test_double_reset_ok() { + let sk = SecretKey::from_slice("Jefe".as_bytes()).unwrap(); + let data = "what do ya want for nothing?".as_bytes(); -#[test] -fn double_finalize_with_reset_ok() { - let secret_key = SecretKey::from_slice("Jefe".as_bytes()).unwrap(); - let data = "what do ya want for nothing?".as_bytes(); - - let mut tag = init(&secret_key); - tag.update(data).unwrap(); - let one = tag.finalize().unwrap(); - tag.reset(); - tag.update(data).unwrap(); - let two = tag.finalize().unwrap(); - assert_eq!(one.unprotected_as_bytes(), two.unprotected_as_bytes()); -} + let mut state = init(&sk); + state.update(data).unwrap(); + let _ = state.finalize().unwrap(); + state.reset(); + state.reset(); + } + } -#[test] -fn double_finalize_with_reset_no_update_ok() { - let secret_key = SecretKey::from_slice("Jefe".as_bytes()).unwrap(); - let data = "what do ya want for nothing?".as_bytes(); + mod test_update { + use super::*; - let mut tag = init(&secret_key); - tag.update(data).unwrap(); - let _ = tag.finalize().unwrap(); - tag.reset(); - let _ = tag.finalize().unwrap(); -} + #[test] + fn test_update_after_finalize_with_reset_ok() { + let sk = SecretKey::from_slice("Jefe".as_bytes()).unwrap(); + let data = "what do ya want for nothing?".as_bytes(); + + let mut state = init(&sk); + state.update(data).unwrap(); + let _ = state.finalize().unwrap(); + state.reset(); + state.update(data).unwrap(); + } -#[test] -fn update_after_finalize_err() { - let secret_key = SecretKey::from_slice("Jefe".as_bytes()).unwrap(); - let data = "what do ya want for nothing?".as_bytes(); + #[test] + /// Related bug: https://github.com/brycx/orion/issues/28 + fn test_update_after_finalize_err() { + let sk = SecretKey::from_slice("Jefe".as_bytes()).unwrap(); + let data = "what do ya want for nothing?".as_bytes(); - let mut tag = init(&secret_key); - tag.update(data).unwrap(); - let _ = tag.finalize().unwrap(); - assert!(tag.update(data).is_err()); -} + let mut state = init(&sk); + state.update(data).unwrap(); + let _ = state.finalize().unwrap(); + assert!(state.update(data).is_err()); + } + } -#[test] -fn update_after_finalize_with_reset_ok() { - let secret_key = SecretKey::from_slice("Jefe".as_bytes()).unwrap(); - let data = "what do ya want for nothing?".as_bytes(); + mod test_finalize { + use super::*; - let mut tag = init(&secret_key); - tag.update(data).unwrap(); - let _ = tag.finalize().unwrap(); - tag.reset(); - tag.update(data).unwrap(); -} + #[test] + fn test_double_finalize_with_reset_no_update_ok() { + let sk = SecretKey::from_slice("Jefe".as_bytes()).unwrap(); + let data = "what do ya want for nothing?".as_bytes(); -#[test] -fn double_reset_ok() { - let secret_key = SecretKey::from_slice("Jefe".as_bytes()).unwrap(); - let data = "what do ya want for nothing?".as_bytes(); + let mut state = init(&sk); + state.update(data).unwrap(); + let _ = state.finalize().unwrap(); + state.reset(); + let _ = state.finalize().unwrap(); + } - let mut tag = init(&secret_key); - tag.update(data).unwrap(); - let _ = tag.finalize().unwrap(); - tag.reset(); - tag.reset(); -} + #[test] + fn test_double_finalize_with_reset_ok() { + let sk = SecretKey::from_slice("Jefe".as_bytes()).unwrap(); + let data = "what do ya want for nothing?".as_bytes(); + + let mut state = init(&sk); + state.update(data).unwrap(); + let one = state.finalize().unwrap(); + state.reset(); + state.update(data).unwrap(); + let two = state.finalize().unwrap(); + assert_eq!(one, two); + } -#[test] -fn reset_after_update_correct_resets() { - let secret_key = SecretKey::from_slice("Jefe".as_bytes()).unwrap(); + #[test] + fn test_double_finalize_err() { + let sk = SecretKey::from_slice("Jefe".as_bytes()).unwrap(); + let data = "what do ya want for nothing?".as_bytes(); - let state_1 = init(&secret_key); + let mut state = init(&sk); + state.update(data).unwrap(); + let _ = state.finalize().unwrap(); + assert!(state.finalize().is_err()); + } - let mut state_2 = init(&secret_key); - state_2.update(b"Tests").unwrap(); - state_2.reset(); + } - assert_eq!(state_1.ipad[..], state_2.ipad[..]); - assert_eq!(state_1.is_finalized, state_2.is_finalized); -} + mod test_streaming_interface { + use super::*; + + /// Related bug: https://github.com/brycx/orion/issues/46 + /// Testing different usage combinations of init(), update(), + /// finalize() and reset() produce the same Digest. + fn produces_same_hash(sk: &SecretKey, data: &[u8]) { + // init(), update(), finalize() + let mut state_1 = init(&sk); + state_1.update(data).unwrap(); + let res_1 = state_1.finalize().unwrap(); + + // init(), reset(), update(), finalize() + let mut state_2 = init(&sk); + state_2.reset(); + state_2.update(data).unwrap(); + let res_2 = state_2.finalize().unwrap(); + + // init(), update(), reset(), update(), finalize() + let mut state_3 = init(&sk); + state_3.update(data).unwrap(); + state_3.reset(); + state_3.update(data).unwrap(); + let res_3 = state_3.finalize().unwrap(); + + // init(), update(), finalize(), reset(), update(), finalize() + let mut state_4 = init(&sk); + state_4.update(data).unwrap(); + let _ = state_4.finalize().unwrap(); + state_4.reset(); + state_4.update(data).unwrap(); + let res_4 = state_4.finalize().unwrap(); + + assert_eq!(res_1, res_2); + assert_eq!(res_2, res_3); + assert_eq!(res_3, res_4); + } -#[test] -fn reset_after_update_correct_resets_and_verify() { - let secret_key = SecretKey::from_slice("Jefe".as_bytes()).unwrap(); + /// Related bug: https://github.com/brycx/orion/issues/46 + /// Testing different usage combinations of init(), update(), + /// finalize() and reset() produce the same Digest. + fn produces_same_state(sk: &SecretKey, data: &[u8]) { + // init() + let state_1 = init(&sk); + + // init(), reset() + let mut state_2 = init(&sk); + state_2.reset(); + + // init(), update(), reset() + let mut state_3 = init(&sk); + state_3.update(data).unwrap(); + state_3.reset(); + + // init(), update(), finalize(), reset() + let mut state_4 = init(&sk); + state_4.update(data).unwrap(); + let _ = state_4.finalize().unwrap(); + state_4.reset(); + + compare_hmac_states(&state_1, &state_2); + compare_hmac_states(&state_2, &state_3); + compare_hmac_states(&state_3, &state_4); + } - let mut state_1 = init(&secret_key); - state_1.update(b"Tests").unwrap(); - let d1 = state_1.finalize().unwrap(); + #[test] + /// Related bug: https://github.com/brycx/orion/issues/46 + fn test_produce_same_state() { + let sk = SecretKey::from_slice("Jefe".as_bytes()).unwrap(); + produces_same_state(&sk, b"Tests"); + } - let mut state_2 = init(&secret_key); - state_2.update(b"Tests").unwrap(); - state_2.reset(); - state_2.update(b"Tests").unwrap(); - let d2 = state_2.finalize().unwrap(); + #[test] + /// Related bug: https://github.com/brycx/orion/issues/46 + fn test_produce_same_hash() { + let sk = SecretKey::from_slice("Jefe".as_bytes()).unwrap(); + produces_same_hash(&sk, b"Tests"); + } - assert_eq!(d1, d2); + #[test] + #[cfg(feature = "safe_api")] + // Test for issues when incrementally processing data. + fn test_streaming_consistency() { + for len in 0..SHA2_BLOCKSIZE * 4 { + let sk = SecretKey::from_slice("Jefe".as_bytes()).unwrap(); + let data = vec![0u8; len]; + let mut state = init(&sk); + let mut other_data: Vec = Vec::new(); + + other_data.extend_from_slice(&data); + state.update(&data).unwrap(); + + if data.len() > SHA2_BLOCKSIZE { + other_data.extend_from_slice(b""); + state.update(b"").unwrap(); + } + if data.len() > SHA2_BLOCKSIZE * 2 { + other_data.extend_from_slice(b"Extra"); + state.update(b"Extra").unwrap(); + } + if data.len() > SHA2_BLOCKSIZE * 3 { + other_data.extend_from_slice(&[0u8; 256]); + state.update(&[0u8; 256]).unwrap(); + } + + let digest_one_shot = hmac(&sk, &other_data).unwrap(); + + assert!(state.finalize().unwrap() == digest_one_shot); + } + } + // Proptests. Only exectued when NOT testing no_std. + #[cfg(feature = "safe_api")] + mod proptest { + use super::*; + + quickcheck! { + /// Related bug: https://github.com/brycx/orion/issues/46 + /// Test different streaming state usage patterns. + fn prop_same_hash_different_usage(data: Vec) -> bool { + let sk = SecretKey::generate().unwrap(); + // Will panic on incorrect results. + produces_same_hash(&sk, &data[..]); + + true + } + } + + quickcheck! { + /// Related bug: https://github.com/brycx/orion/issues/46 + /// Test different streaming state usage patterns. + fn prop_same_state_different_usage(data: Vec) -> bool { + let sk = SecretKey::generate().unwrap(); + // Will panic on incorrect results. + produces_same_state(&sk, &data[..]); + + true + } + } + + quickcheck! { + /// Using the one-shot function should always produce the + /// same result as when using the streaming interface. + fn prop_hmac_same_as_streaming(data: Vec) -> bool { + let sk = SecretKey::generate().unwrap(); + let mut state = init(&sk); + state.update(&data[..]).unwrap(); + let stream = state.finalize().unwrap(); + let one_shot = hmac(&sk, &data[..]).unwrap(); + + (one_shot == stream) + } + } + } + } } diff --git a/src/hazardous/mac/poly1305.rs b/src/hazardous/mac/poly1305.rs index 04ad5951..e1b4758c 100644 --- a/src/hazardous/mac/poly1305.rs +++ b/src/hazardous/mac/poly1305.rs @@ -412,177 +412,401 @@ pub fn verify( } } -#[test] -fn test_wrong_key_len() { - assert!(OneTimeKey::from_slice(&[0u8; 31]).is_err()); - assert!(OneTimeKey::from_slice(&[0u8; 33]).is_err()); - assert!(OneTimeKey::from_slice(&[0u8; 32]).is_ok()); -} +// Testing public functions in the module. +#[cfg(test)] +mod public { + use super::*; + + // One function tested per submodule. + + /// Compare two Poly1305 state objects to check if their fields + /// are the same. + fn compare_poly1305_states(state_1: &Poly1305, state_2: &Poly1305) { + assert_eq!(state_1.a, state_2.a); + assert_eq!(state_1.r, state_2.r); + assert_eq!(state_1.s, state_2.s); + assert_eq!(state_1.leftover, state_2.leftover); + assert_eq!(state_1.buffer[..], state_2.buffer[..]); + assert_eq!(state_1.is_finalized, state_2.is_finalized); + } -#[test] -fn test_poly1305_oneshot_ok() { - assert!(poly1305(&OneTimeKey::from_slice(&[0u8; 32]).unwrap(), &[0u8; 16]).is_ok()); -} + mod test_verify { + use super::*; + + #[test] + fn test_poly1305_verify_ok() { + let tag = poly1305(&OneTimeKey::from_slice(&[0u8; 32]).unwrap(), &[0u8; 16]).unwrap(); + verify( + &tag, + &OneTimeKey::from_slice(&[0u8; 32]).unwrap(), + &[0u8; 16], + ) + .unwrap(); + } -#[test] -fn test_poly1305_verify_ok() { - let tag = poly1305(&OneTimeKey::from_slice(&[0u8; 32]).unwrap(), &[0u8; 16]).unwrap(); - verify( - &tag, - &OneTimeKey::from_slice(&[0u8; 32]).unwrap(), - &[0u8; 16], - ) - .unwrap(); -} + #[test] + fn test_poly1305_verify_err() { + let mut tag = + poly1305(&OneTimeKey::from_slice(&[0u8; 32]).unwrap(), &[0u8; 16]).unwrap(); + tag.value[0] ^= 1; + assert!(verify( + &tag, + &OneTimeKey::from_slice(&[0u8; 32]).unwrap(), + &[0u8; 16], + ) + .is_err()); + } -#[test] -fn test_poly1305_verify_err() { - let mut tag = poly1305(&OneTimeKey::from_slice(&[0u8; 32]).unwrap(), &[0u8; 16]).unwrap(); - tag.value[0] ^= 1; - assert!(verify( - &tag, - &OneTimeKey::from_slice(&[0u8; 32]).unwrap(), - &[0u8; 16], - ) - .is_err()); -} + #[test] + fn finalize_and_verify_true() { + let secret_key = OneTimeKey::from_slice(&[0u8; 32]).unwrap(); + let data = "what do ya want for nothing?".as_bytes(); + + let mut tag = init(&secret_key); + tag.update(data).unwrap(); + + assert_eq!( + verify( + &tag.finalize().unwrap(), + &OneTimeKey::from_slice(&[0u8; 32]).unwrap(), + data + ) + .unwrap(), + true + ); + } -#[test] -fn test_bad_key_err_less() { - assert!(OneTimeKey::from_slice(&[0u8; 31]).is_err()); -} + // Proptests. Only exectued when NOT testing no_std. + #[cfg(feature = "safe_api")] + mod proptest { + use super::*; -#[test] -fn test_poly1305_oneshot_bad_key_err_greater() { - assert!(OneTimeKey::from_slice(&[0u8; 33]).is_err()); -} + quickcheck! { + /// When using the same parameters verify() should always yeild true. + fn prop_verify_same_params_true(data: Vec) -> bool { + let sk = OneTimeKey::generate().unwrap(); -#[test] -fn double_finalize_err() { - let mut poly1305_state = init(&OneTimeKey::from_slice(&[0u8; 32]).unwrap()); + let mut state = init(&sk); + state.update(&data[..]).unwrap(); + let tag = state.finalize().unwrap(); + // Failed verification on Err so res is not needed. + let _res = verify(&tag, &sk, &data[..]).unwrap(); - poly1305_state.update(&[0u8; 16]).unwrap(); - let _ = poly1305_state.finalize().unwrap(); - assert!(poly1305_state.finalize().is_err()); -} + true + } + } -#[test] -fn double_finalize_with_reset_ok() { - let mut poly1305_state = init(&OneTimeKey::from_slice(&[0u8; 32]).unwrap()); + quickcheck! { + /// When using the same parameters verify() should always yeild true. + fn prop_verify_diff_key_false(data: Vec) -> bool { + let sk = OneTimeKey::generate().unwrap(); + let mut state = init(&sk); + state.update(&data[..]).unwrap(); + let tag = state.finalize().unwrap(); - poly1305_state.update(&[0u8; 16]).unwrap(); - let one = poly1305_state.finalize().unwrap(); - poly1305_state.reset(); - poly1305_state.update(&[0u8; 16]).unwrap(); - let two = poly1305_state.finalize().unwrap(); - assert_eq!(one.unprotected_as_bytes(), two.unprotected_as_bytes()); -} + let bad_sk = OneTimeKey::generate().unwrap(); -#[test] -fn double_finalize_with_reset_no_update_ok() { - let mut poly1305_state = init(&OneTimeKey::from_slice(&[0u8; 32]).unwrap()); + let res = if verify(&tag, &bad_sk, &data[..]).is_err() { + true + } else { + false + }; - poly1305_state.update(&[0u8; 16]).unwrap(); - let _ = poly1305_state.finalize().unwrap(); - poly1305_state.reset(); - let _ = poly1305_state.finalize().unwrap(); -} + res + } + } + } + } -#[test] -fn update_after_finalize_err() { - let mut poly1305_state = init(&OneTimeKey::from_slice(&[0u8; 32]).unwrap()); + mod test_reset { + use super::*; - poly1305_state.update(&[0u8; 16]).unwrap(); - let _ = poly1305_state.finalize().unwrap(); - assert!(poly1305_state.update(&[0u8; 16]).is_err()); -} + #[test] + fn test_double_reset_ok() { + let sk = OneTimeKey::from_slice(&[0u8; 32]).unwrap(); + let data = "what do ya want for nothing?".as_bytes(); -#[test] -fn update_after_finalize_with_reset_ok() { - let mut poly1305_state = init(&OneTimeKey::from_slice(&[0u8; 32]).unwrap()); + let mut state = init(&sk); + state.update(data).unwrap(); + let _ = state.finalize().unwrap(); + state.reset(); + state.reset(); + } + } - poly1305_state.update(&[0u8; 16]).unwrap(); - let expected = poly1305_state.finalize().unwrap(); - poly1305_state.reset(); - poly1305_state.update(&[0u8; 16]).unwrap(); - assert!(&expected == &poly1305_state.finalize().unwrap()); -} + mod test_update { + use super::*; -#[test] -fn double_reset_ok() { - let mut poly1305_state = init(&OneTimeKey::from_slice(&[0u8; 32]).unwrap()); + #[test] + fn test_update_after_finalize_with_reset_ok() { + let sk = OneTimeKey::from_slice(&[0u8; 32]).unwrap(); + let data = "what do ya want for nothing?".as_bytes(); - poly1305_state.update(&[0u8; 16]).unwrap(); - let _ = poly1305_state.finalize().unwrap(); - poly1305_state.reset(); - poly1305_state.reset(); -} + let mut state = init(&sk); + state.update(data).unwrap(); + let _ = state.finalize().unwrap(); + state.reset(); + state.update(data).unwrap(); + } -#[test] -fn reset_after_update_correct_resets() { - let secret_key = OneTimeKey::from_slice(&[0u8; 32]).unwrap(); + #[test] + /// Related bug: https://github.com/brycx/orion/issues/28 + fn test_update_after_finalize_err() { + let sk = OneTimeKey::from_slice(&[0u8; 32]).unwrap(); + let data = "what do ya want for nothing?".as_bytes(); - let state_1 = init(&secret_key); + let mut state = init(&sk); + state.update(data).unwrap(); + let _ = state.finalize().unwrap(); + assert!(state.update(data).is_err()); + } + } - let mut state_2 = init(&secret_key); - state_2.update(b"Tests").unwrap(); - state_2.reset(); + mod test_finalize { + use super::*; - assert_eq!(state_1.a, state_2.a); - assert_eq!(state_1.r, state_2.r); - assert_eq!(state_1.s, state_2.s); - assert_eq!(state_1.leftover, state_2.leftover); - assert_eq!(state_1.buffer[..], state_2.buffer[..]); - assert_eq!(state_1.is_finalized, state_2.is_finalized); -} + #[test] + fn test_double_finalize_with_reset_no_update_ok() { + let sk = OneTimeKey::from_slice(&[0u8; 32]).unwrap(); + let data = "what do ya want for nothing?".as_bytes(); -#[test] -fn reset_after_update_correct_resets_and_verify() { - let secret_key = OneTimeKey::from_slice(&[0u8; 32]).unwrap(); + let mut state = init(&sk); + state.update(data).unwrap(); + let _ = state.finalize().unwrap(); + state.reset(); + let _ = state.finalize().unwrap(); + } - let mut state_1 = init(&secret_key); - state_1.update(b"Tests").unwrap(); - let d1 = state_1.finalize().unwrap(); + #[test] + fn test_double_finalize_with_reset_ok() { + let sk = OneTimeKey::from_slice(&[0u8; 32]).unwrap(); + let data = "what do ya want for nothing?".as_bytes(); + + let mut state = init(&sk); + state.update(data).unwrap(); + let one = state.finalize().unwrap(); + state.reset(); + state.update(data).unwrap(); + let two = state.finalize().unwrap(); + assert_eq!(one, two); + } - let mut state_2 = init(&secret_key); - state_2.update(b"Tests").unwrap(); - state_2.reset(); - state_2.update(b"Tests").unwrap(); - let d2 = state_2.finalize().unwrap(); + #[test] + fn test_double_finalize_err() { + let sk = OneTimeKey::from_slice(&[0u8; 32]).unwrap(); + let data = "what do ya want for nothing?".as_bytes(); - assert_eq!(d1, d2); -} + let mut state = init(&sk); + state.update(data).unwrap(); + let _ = state.finalize().unwrap(); + assert!(state.finalize().is_err()); + } -#[test] -#[cfg(feature = "safe_api")] -// Test for issues when incrementally processing data -// with leftover -fn test_streaming_consistency() { - let key = OneTimeKey::from_slice(&[0u8; 32]).unwrap(); + } - for len in 0..POLY1305_BLOCKSIZE * 4 { - let data = vec![0u8; len]; - let mut state = init(&key); - let mut other_data: Vec = Vec::new(); + mod test_streaming_interface { + use super::*; + + /// Related bug: https://github.com/brycx/orion/issues/46 + /// Testing different usage combinations of init(), update(), + /// finalize() and reset() produce the same Digest. + fn produces_same_hash(sk: &OneTimeKey, data: &[u8]) { + // init(), update(), finalize() + let mut state_1 = init(&sk); + state_1.update(data).unwrap(); + let res_1 = state_1.finalize().unwrap(); + + // init(), reset(), update(), finalize() + let mut state_2 = init(&sk); + state_2.reset(); + state_2.update(data).unwrap(); + let res_2 = state_2.finalize().unwrap(); + + // init(), update(), reset(), update(), finalize() + let mut state_3 = init(&sk); + state_3.update(data).unwrap(); + state_3.reset(); + state_3.update(data).unwrap(); + let res_3 = state_3.finalize().unwrap(); + + // init(), update(), finalize(), reset(), update(), finalize() + let mut state_4 = init(&sk); + state_4.update(data).unwrap(); + let _ = state_4.finalize().unwrap(); + state_4.reset(); + state_4.update(data).unwrap(); + let res_4 = state_4.finalize().unwrap(); + + assert_eq!(res_1, res_2); + assert_eq!(res_2, res_3); + assert_eq!(res_3, res_4); + } - other_data.extend_from_slice(&data); - state.update(&data).unwrap(); + /// Related bug: https://github.com/brycx/orion/issues/46 + /// Testing different usage combinations of init(), update(), + /// finalize() and reset() produce the same Digest. + fn produces_same_state(sk: &OneTimeKey, data: &[u8]) { + // init() + let state_1 = init(&sk); + + // init(), reset() + let mut state_2 = init(&sk); + state_2.reset(); + + // init(), update(), reset() + let mut state_3 = init(&sk); + state_3.update(data).unwrap(); + state_3.reset(); + + // init(), update(), finalize(), reset() + let mut state_4 = init(&sk); + state_4.update(data).unwrap(); + let _ = state_4.finalize().unwrap(); + state_4.reset(); + + compare_poly1305_states(&state_1, &state_2); + compare_poly1305_states(&state_2, &state_3); + compare_poly1305_states(&state_3, &state_4); + } - if data.len() > POLY1305_BLOCKSIZE { - other_data.extend_from_slice(b""); - state.update(b"").unwrap(); + #[test] + /// Related bug: https://github.com/brycx/orion/issues/46 + fn test_produce_same_state() { + let sk = OneTimeKey::from_slice(&[0u8; 32]).unwrap(); + produces_same_state(&sk, b"Tests"); } - if data.len() > POLY1305_BLOCKSIZE * 2 { - other_data.extend_from_slice(b"Extra"); - state.update(b"Extra").unwrap(); + + #[test] + /// Related bug: https://github.com/brycx/orion/issues/46 + fn test_produce_same_hash() { + let sk = OneTimeKey::from_slice(&[0u8; 32]).unwrap(); + produces_same_hash(&sk, b"Tests"); } - if data.len() > POLY1305_BLOCKSIZE * 3 { - other_data.extend_from_slice(&[0u8; 256]); - state.update(&[0u8; 256]).unwrap(); + + #[test] + #[cfg(feature = "safe_api")] + // Test for issues when incrementally processing data + // with leftover + fn test_streaming_consistency() { + for len in 0..POLY1305_BLOCKSIZE * 4 { + let key = OneTimeKey::from_slice(&[0u8; 32]).unwrap(); + let data = vec![0u8; len]; + let mut state = init(&key); + let mut other_data: Vec = Vec::new(); + + other_data.extend_from_slice(&data); + state.update(&data).unwrap(); + + if data.len() > POLY1305_BLOCKSIZE { + other_data.extend_from_slice(b""); + state.update(b"").unwrap(); + } + if data.len() > POLY1305_BLOCKSIZE * 2 { + other_data.extend_from_slice(b"Extra"); + state.update(b"Extra").unwrap(); + } + if data.len() > POLY1305_BLOCKSIZE * 3 { + other_data.extend_from_slice(&[0u8; 256]); + state.update(&[0u8; 256]).unwrap(); + } + + let digest_one_shot = poly1305(&key, &other_data).unwrap(); + + assert!(state.finalize().unwrap() == digest_one_shot); + } + } + // Proptests. Only exectued when NOT testing no_std. + #[cfg(feature = "safe_api")] + mod proptest { + use super::*; + + quickcheck! { + /// Related bug: https://github.com/brycx/orion/issues/46 + /// Test different streaming state usage patterns. + fn prop_same_tag_different_usage(data: Vec) -> bool { + let sk = OneTimeKey::generate().unwrap(); + // Will panic on incorrect results. + produces_same_hash(&sk, &data[..]); + + true + } + } + + quickcheck! { + /// Related bug: https://github.com/brycx/orion/issues/46 + /// Test different streaming state usage patterns. + fn prop_same_state_different_usage(data: Vec) -> bool { + let sk = OneTimeKey::generate().unwrap(); + // Will panic on incorrect results. + produces_same_state(&sk, &data[..]); + + true + } + } + + quickcheck! { + /// Using the one-shot function should always produce the + /// same result as when using the streaming interface. + fn prop_poly1305_same_as_streaming(data: Vec) -> bool { + let sk = OneTimeKey::generate().unwrap(); + let mut state = init(&sk); + state.update(&data[..]).unwrap(); + let stream = state.finalize().unwrap(); + let one_shot = poly1305(&sk, &data[..]).unwrap(); + + (one_shot == stream) + } + } } + } +} + +// Testing private functions in the module. +#[cfg(test)] +mod private { + use super::*; + + // One function tested per submodule. - let digest_one_shot = poly1305(&key, &other_data).unwrap(); + mod test_process_block { + use super::*; - assert!(state.finalize().unwrap() == digest_one_shot); + #[test] + fn test_process_block_len() { + let block_0 = [0u8; 0]; + let block_1 = [0u8; 15]; + let block_2 = [0u8; 17]; + let block_3 = [0u8; 16]; + + let sk = OneTimeKey::from_slice(&[0u8; 32]).unwrap(); + let mut state = init(&sk); + + assert!(state.process_block(&block_0).is_err()); + assert!(state.process_block(&block_1).is_err()); + assert!(state.process_block(&block_2).is_err()); + assert!(state.process_block(&block_3).is_ok()); + } + } + + mod test_process_end_of_stream { + use super::*; + + #[test] + fn test_process_no_panic() { + let block = [0u8; 16]; + let sk = OneTimeKey::from_slice(&[0u8; 32]).unwrap(); + let mut state = init(&sk); + // Should not panic + state.process_end_of_stream(); + state.reset(); + state.process_end_of_stream(); + + let mut state = init(&sk); + state.process_block(&block).unwrap(); + // Should not panic + state.process_end_of_stream(); + state.reset(); + state.process_end_of_stream(); + } } } diff --git a/src/hazardous/stream/chacha20.rs b/src/hazardous/stream/chacha20.rs index 8221dd55..f38ea426 100644 --- a/src/hazardous/stream/chacha20.rs +++ b/src/hazardous/stream/chacha20.rs @@ -398,654 +398,1038 @@ pub fn hchacha20( Ok(keystream_block) } -#[test] -fn test_process_block_wrong_combination_of_variant_and_nonce() { - let mut chacha_state_ietf = InternalState { - state: [0_u32; 16], - is_ietf: true, - }; - chacha_state_ietf - .init_state(&SecretKey::from_slice(&[0u8; 32]).unwrap(), &[0u8; 12]) - .unwrap(); +// Testing public functions in the module. +#[cfg(test)] +mod public { + use super::*; + // One function tested per submodule. + + // encrypt()/decrypt() are tested together here + // since decrypt() is just a wrapper around encrypt() + // and so only the decrypt() function is called + mod test_encrypt_decrypt { + use super::*; + #[test] + fn test_fail_on_initial_counter_overflow() { + let mut dst = [0u8; 65]; + + assert!(decrypt( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 12]).unwrap(), + u32::max_value(), + &[0u8; 65], + &mut dst, + ) + .is_err()); + } - let mut chacha_state_hchacha = InternalState { - state: [0_u32; 16], - is_ietf: false, - }; + #[test] + fn test_pass_on_one_iter_max_initial_counter() { + let mut dst = [0u8; 64]; + // Should pass because only one iteration is completed, so block_counter will + // not increase + assert!(decrypt( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 12]).unwrap(), + u32::max_value(), + &[0u8; 64], + &mut dst, + ) + .is_ok()); + } - chacha_state_hchacha - .init_state(&SecretKey::from_slice(&[0u8; 32]).unwrap(), &[0u8; 16]) - .unwrap(); + #[test] + fn test_fail_on_empty_plaintext() { + let mut dst = [0u8; 64]; + + assert!(decrypt( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 12]).unwrap(), + 0, + &[0u8; 0], + &mut dst, + ) + .is_err()); + } - assert!(chacha_state_hchacha.process_block(Some(1)).is_err()); - assert!(chacha_state_ietf.process_block(None).is_err()); - assert!(chacha_state_hchacha.process_block(None).is_ok()); - assert!(chacha_state_ietf.process_block(Some(1)).is_ok()); -} + #[test] + fn test_dst_out_length() { + let mut dst_small = [0u8; 64]; + + assert!(decrypt( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 12]).unwrap(), + 0, + &[0u8; 128], + &mut dst_small, + ) + .is_err()); + + let mut dst = [0u8; 64]; + + assert!(decrypt( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 12]).unwrap(), + 0, + &[0u8; 64], + &mut dst, + ) + .is_ok()); + + let mut dst_big = [0u8; 64]; + + assert!(decrypt( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 12]).unwrap(), + 0, + &[0u8; 32], + &mut dst_big, + ) + .is_ok()); + } -#[test] -fn test_serialize_block_wrong_combination_of_variant_and_dst() { - let mut chacha_state_ietf = InternalState { - state: [0_u32; 16], - is_ietf: true, - }; + // Proptests. Only exectued when NOT testing no_std. + #[cfg(feature = "safe_api")] + mod proptest { + use super::*; + + /// Given a input length `a` find out how many times + /// the initial counter on encrypt()/decrypt() would + /// increase. + fn counter_increase_times(a: f32) -> u32 { + // Otherwise a overvlowing subtration would happen + if a <= 64f32 { + return 0; + } + + let check_with_floor = (a / 64f32).floor(); + let actual = a / 64f32; + + assert!(actual >= check_with_floor); + // Subtract one because the first 64 in length + // the counter does not increase + if actual > check_with_floor { + (actual.ceil() as u32) - 1 + } else { + (actual as u32) - 1 + } + } + + quickcheck! { + // Encrypting input, and then decrypting should always yield the same input. + fn prop_encrypt_decrypt_same_input(input: Vec, block_counter: u32) -> bool { + let pt = if input.is_empty() { + vec![1u8; 10] + } else { + input + }; + + let mut dst_out_ct = vec![0u8; pt.len()]; + let mut dst_out_pt = vec![0u8; pt.len()]; + + // If `block_counter` is high enough check if it would overflow + if counter_increase_times(pt.len() as f32).checked_add(block_counter).is_none() { + // Overflow will occur and the operation should fail + let res = if encrypt( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 12]).unwrap(), + block_counter, + &pt[..], + &mut dst_out_ct, + ).is_err() { true } else { false }; + + return res; + } else { + + encrypt( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 12]).unwrap(), + block_counter, + &pt[..], + &mut dst_out_ct, + ).unwrap(); + + decrypt( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 12]).unwrap(), + block_counter, + &dst_out_ct[..], + &mut dst_out_pt, + ).unwrap(); + + return dst_out_pt == pt; + } + } + } + + 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) + } + } + } + } - chacha_state_ietf - .init_state(&SecretKey::from_slice(&[0u8; 32]).unwrap(), &[0u8; 12]) - .unwrap(); + mod test_keystream_block { + use super::*; + + #[test] + fn test_counter() { + // keystream_block never increases the provided counter + assert!(keystream_block( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 12]).unwrap(), + u32::max_value(), + ) + .is_ok()); + + assert!(keystream_block( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 12]).unwrap(), + 0, + ) + .is_ok()); + + assert!(keystream_block( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 12]).unwrap(), + 64, + ) + .is_ok()); + } - let mut chacha_state_hchacha = InternalState { - state: [0_u32; 16], - is_ietf: false, - }; + #[test] + fn test_diff_keys_diff_output() { + let keystream1 = keystream_block( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 12]).unwrap(), + 0, + ) + .unwrap(); + + let keystream2 = keystream_block( + &SecretKey::from_slice(&[1u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 12]).unwrap(), + 0, + ) + .unwrap(); + + assert!(keystream1[..] != keystream2[..]); + } - let mut hchacha_out = [0u8; HCHACHA_OUTSIZE]; - let mut ietf_out = [0u8; CHACHA_BLOCKSIZE]; + #[test] + fn test_diff_nonce_diff_output() { + let keystream1 = keystream_block( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 12]).unwrap(), + 0, + ) + .unwrap(); + + let keystream2 = keystream_block( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[1u8; 12]).unwrap(), + 0, + ) + .unwrap(); + + assert!(keystream1[..] != keystream2[..]); + } - chacha_state_hchacha - .init_state(&SecretKey::from_slice(&[0u8; 32]).unwrap(), &[0u8; 16]) - .unwrap(); + #[test] + fn test_diff_initial_counter_diff_output() { + let keystream1 = keystream_block( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 12]).unwrap(), + 0, + ) + .unwrap(); + + let keystream2 = keystream_block( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 12]).unwrap(), + 1, + ) + .unwrap(); + + assert!(keystream1[..] != keystream2[..]); + } - let ietf_src = chacha_state_ietf.process_block(Some(1)).unwrap(); - let hchacha_src = chacha_state_hchacha.process_block(None).unwrap(); - - assert!(chacha_state_hchacha - .serialize_block(&hchacha_src, &mut ietf_out) - .is_err()); - assert!(chacha_state_ietf - .serialize_block(&ietf_src, &mut hchacha_out) - .is_err()); - assert!(chacha_state_hchacha - .serialize_block(&hchacha_src, &mut hchacha_out) - .is_ok()); - assert!(chacha_state_ietf - .serialize_block(&ietf_src, &mut ietf_out) - .is_ok()); -} + // Proptests. Only exectued when NOT testing no_std. + #[cfg(feature = "safe_api")] + mod proptest { + use super::*; + + quickcheck! { + fn prop_same_params_same_output(counter: u32) -> bool { + let keystream1 = keystream_block( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 12]).unwrap(), + counter, + ).unwrap(); + + let keystream2 = keystream_block( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 12]).unwrap(), + counter, + ).unwrap(); + + (keystream1[..] == keystream2[..]) + } + } + } + } -#[test] -fn test_bad_key_nonce_size_init() { - let mut chacha_state = InternalState { - state: [0_u32; 16], - is_ietf: true, - }; + mod test_hchacha20 { + use super::*; - assert!(chacha_state - .init_state(&SecretKey::from_slice(&[0u8; 32]).unwrap(), &[0u8; 15]) - .is_err()); - assert!(chacha_state - .init_state(&SecretKey::from_slice(&[0u8; 32]).unwrap(), &[0u8; 10]) - .is_err()); - assert!(chacha_state - .init_state(&SecretKey::from_slice(&[0u8; 32]).unwrap(), &[0u8; 12]) - .is_ok()); - - let mut hchacha_state = InternalState { - state: [0_u32; 16], - is_ietf: false, - }; + #[test] + fn test_nonce_length() { + assert!(hchacha20(&SecretKey::from_slice(&[0u8; 32]).unwrap(), &[0u8; 16],).is_ok()); - assert!(hchacha_state - .init_state(&SecretKey::from_slice(&[0u8; 32]).unwrap(), &[0u8; 15]) - .is_err()); - assert!(hchacha_state - .init_state(&SecretKey::from_slice(&[0u8; 32]).unwrap(), &[0u8; 17]) - .is_err()); - assert!(hchacha_state - .init_state(&SecretKey::from_slice(&[0u8; 32]).unwrap(), &[0u8; 16]) - .is_ok()); -} + assert!(hchacha20(&SecretKey::from_slice(&[0u8; 32]).unwrap(), &[0u8; 17],).is_err()); -#[test] -fn test_nonce_sizes() { - assert!(&Nonce::from_slice(&[0u8; 10]).is_err()); - assert!(&Nonce::from_slice(&[0u8; 13]).is_err()); - assert!(&Nonce::from_slice(&[0u8; 12]).is_ok()); -} + assert!(hchacha20(&SecretKey::from_slice(&[0u8; 32]).unwrap(), &[0u8; 15],).is_err()); -#[test] -fn test_key_sizes() { - assert!(SecretKey::from_slice(&[0u8; 0]).is_err()); - assert!(SecretKey::from_slice(&[0u8; 1]).is_err()); - assert!(SecretKey::from_slice(&[0u8; 31]).is_err()); - assert!(SecretKey::from_slice(&[0u8; 64]).is_err()); - assert!(SecretKey::from_slice(&[0u8; 33]).is_err()); - assert!(SecretKey::from_slice(&[0u8; 32]).is_ok()); -} + assert!(hchacha20(&SecretKey::from_slice(&[0u8; 32]).unwrap(), &[0u8; 0],).is_err()); + } -#[test] -fn test_diff_ct_pt_len() { - let mut dst = [0u8; 64]; - - assert!(encrypt( - &SecretKey::from_slice(&[0u8; 32]).unwrap(), - &Nonce::from_slice(&[0u8; 12]).unwrap(), - 0, - &[0u8; 65], - &mut dst - ) - .is_err()); - assert!(encrypt( - &SecretKey::from_slice(&[0u8; 32]).unwrap(), - &Nonce::from_slice(&[0u8; 12]).unwrap(), - 0, - &[0u8; 63], - &mut dst - ) - .is_ok()); - assert!(encrypt( - &SecretKey::from_slice(&[0u8; 32]).unwrap(), - &Nonce::from_slice(&[0u8; 12]).unwrap(), - 0, - &[0u8; 64], - &mut dst - ) - .is_ok()); -} + #[test] + fn test_diff_keys_diff_output() { + let keystream1 = + hchacha20(&SecretKey::from_slice(&[0u8; 32]).unwrap(), &[0u8; 16]).unwrap(); -#[test] -fn test_err_on_diff_ct_pt_len_chacha_long() { - let mut dst = [0u8; 64]; - - assert!(encrypt( - &SecretKey::from_slice(&[0u8; 32]).unwrap(), - &Nonce::from_slice(&[0u8; 12]).unwrap(), - 0, - &[0u8; 128], - &mut dst, - ) - .is_err()); -} + let keystream2 = + hchacha20(&SecretKey::from_slice(&[1u8; 32]).unwrap(), &[0u8; 16]).unwrap(); -#[test] -fn test_err_on_diff_ct_pt_len_chacha_short() { - let mut dst = [0u8; 64]; - - assert!(encrypt( - &SecretKey::from_slice(&[0u8; 32]).unwrap(), - &Nonce::from_slice(&[0u8; 12]).unwrap(), - 0, - &[0u8; 0], - &mut dst, - ) - .is_err()); -} + assert!(keystream1 != keystream2); + } -#[test] -fn test_err_on_empty_pt() { - let mut dst = [0u8; 64]; - - assert!(encrypt( - &SecretKey::from_slice(&[0u8; 32]).unwrap(), - &Nonce::from_slice(&[0u8; 12]).unwrap(), - 0, - &[0u8; 0], - &mut dst, - ) - .is_err()); -} + #[test] + fn test_diff_nonce_diff_output() { + let keystream1 = + hchacha20(&SecretKey::from_slice(&[0u8; 32]).unwrap(), &[0u8; 16]).unwrap(); -#[test] -fn test_err_on_initial_counter_overflow() { - let mut dst = [0u8; 65]; - - assert!(encrypt( - &SecretKey::from_slice(&[0u8; 32]).unwrap(), - &Nonce::from_slice(&[0u8; 12]).unwrap(), - 4294967295, - &[0u8; 65], - &mut dst, - ) - .is_err()); -} + let keystream2 = + hchacha20(&SecretKey::from_slice(&[0u8; 32]).unwrap(), &[1u8; 16]).unwrap(); -#[test] -fn test_pass_on_one_iter_max_initial_counter() { - let mut dst = [0u8; 64]; - // Should pass because only one iteration is completed, so block_counter will - // not increase - encrypt( - &SecretKey::from_slice(&[0u8; 32]).unwrap(), - &Nonce::from_slice(&[0u8; 12]).unwrap(), - 4294967295, - &[0u8; 64], - &mut dst, - ) - .unwrap(); - // keystream_block never increases the provided counter - keystream_block( - &SecretKey::from_slice(&[0u8; 32]).unwrap(), - &Nonce::from_slice(&[0u8; 12]).unwrap(), - 4294967295, - ) - .unwrap(); + assert!(keystream1 != keystream2); + } + } } +// Testing private functions in the module. #[cfg(test)] -// Convenience function for testing. -fn init(key: &[u8], nonce: &[u8]) -> Result { - let mut chacha_state = InternalState { - state: [0_u32; 16], - is_ietf: true, - }; +mod private { + use super::*; + // One function tested per submodule. + + mod test_init_state { + use super::*; + + #[test] + fn test_nonce_length() { + let mut chacha_state = InternalState { + state: [0_u32; 16], + is_ietf: true, + }; + + assert!(chacha_state + .init_state(&SecretKey::from_slice(&[0u8; 32]).unwrap(), &[0u8; 15]) + .is_err()); + assert!(chacha_state + .init_state(&SecretKey::from_slice(&[0u8; 32]).unwrap(), &[0u8; 10]) + .is_err()); + assert!(chacha_state + .init_state(&SecretKey::from_slice(&[0u8; 32]).unwrap(), &[0u8; 12]) + .is_ok()); + + let mut hchacha_state = InternalState { + state: [0_u32; 16], + is_ietf: false, + }; + + assert!(hchacha_state + .init_state(&SecretKey::from_slice(&[0u8; 32]).unwrap(), &[0u8; 15]) + .is_err()); + assert!(hchacha_state + .init_state(&SecretKey::from_slice(&[0u8; 32]).unwrap(), &[0u8; 17]) + .is_err()); + assert!(hchacha_state + .init_state(&SecretKey::from_slice(&[0u8; 32]).unwrap(), &[0u8; 16]) + .is_ok()); + } - chacha_state - .init_state(&SecretKey::from_slice(key).unwrap(), nonce) - .unwrap(); + // Proptests. Only exectued when NOT testing no_std. + #[cfg(feature = "safe_api")] + mod proptest { + use super::*; + + quickcheck! { + // Always fail to intialize state while the nonce is not + // the correct length. If it is correct length, never panic. + fn prop_test_nonce_length_ietf(nonce: Vec) -> bool { + let mut chacha_state_ietf = InternalState { + state: [0_u32; 16], + is_ietf: true, + }; + + let sk = SecretKey::from_slice(&[0u8; 32]).unwrap(); + + if nonce.len() != IETF_CHACHA_NONCESIZE { + let res = if chacha_state_ietf + .init_state(&sk, &nonce[..]).is_err() { + true + } else { + false + }; + + return res; + } else { + let res = if chacha_state_ietf + .init_state(&sk, &nonce[..]).is_ok() { + true + } else { + false + }; + + return res; + } + } + } + + quickcheck! { + // Always fail to intialize state while the nonce is not + // the correct length. If it is correct length, never panic. + fn prop_test_nonce_length_hchacha(nonce: Vec) -> bool { + let mut chacha_state_hchacha = InternalState { + state: [0_u32; 16], + is_ietf: false, + }; + + let sk = SecretKey::from_slice(&[0u8; 32]).unwrap(); + + if nonce.len() != HCHACHA_NONCESIZE { + let res = if chacha_state_hchacha + .init_state(&sk, &nonce[..]).is_err() { + true + } else { + false + }; + + return res; + } else { + let res = if chacha_state_hchacha + .init_state(&sk, &nonce[..]).is_ok() { + true + } else { + false + }; + + return res; + } + } + } + } + } - Ok(chacha_state) -} + mod test_process_block { + use super::*; + #[test] + fn test_process_block_wrong_combination_of_variant_and_nonce() { + let mut chacha_state_ietf = InternalState { + state: [0_u32; 16], + is_ietf: true, + }; + chacha_state_ietf + .init_state(&SecretKey::from_slice(&[0u8; 32]).unwrap(), &[0u8; 12]) + .unwrap(); + + let mut chacha_state_hchacha = InternalState { + state: [0_u32; 16], + is_ietf: false, + }; + + chacha_state_hchacha + .init_state(&SecretKey::from_slice(&[0u8; 32]).unwrap(), &[0u8; 16]) + .unwrap(); + + assert!(chacha_state_hchacha.process_block(Some(1)).is_err()); + assert!(chacha_state_ietf.process_block(None).is_err()); + assert!(chacha_state_hchacha.process_block(None).is_ok()); + assert!(chacha_state_ietf.process_block(Some(1)).is_ok()); + } + } -#[test] -// From https://tools.ietf.org/html/rfc8439 -fn test_quarter_round_results() { - let mut chacha_state = InternalState { - state: [ - 0x11111111, 0x01020304, 0x9b8d6f43, 0x01234567, 0x11111111, 0x01020304, 0x9b8d6f43, - 0x01234567, 0x11111111, 0x01020304, 0x9b8d6f43, 0x01234567, 0x11111111, 0x01020304, - 0x9b8d6f43, 0x01234567, - ], - is_ietf: true, - }; - let expected: [u32; 4] = [0xea2a92f4, 0xcb1cf8ce, 0x4581472e, 0x5881c4bb]; - // Test all indexes - chacha_state.quarter_round(0, 1, 2, 3); - chacha_state.quarter_round(4, 5, 6, 7); - chacha_state.quarter_round(8, 9, 10, 11); - chacha_state.quarter_round(12, 13, 14, 15); - - assert_eq!(chacha_state.state[0..4], expected); - assert_eq!(chacha_state.state[4..8], expected); - assert_eq!(chacha_state.state[8..12], expected); - assert_eq!(chacha_state.state[12..16], expected); + mod test_serialize_block { + use super::*; + + #[test] + fn test_wrong_combination_of_variant_and_dst_out() { + let mut chacha_state_ietf = InternalState { + state: [0_u32; 16], + is_ietf: true, + }; + + chacha_state_ietf + .init_state(&SecretKey::from_slice(&[0u8; 32]).unwrap(), &[0u8; 12]) + .unwrap(); + + let mut chacha_state_hchacha = InternalState { + state: [0_u32; 16], + is_ietf: false, + }; + + chacha_state_hchacha + .init_state(&SecretKey::from_slice(&[0u8; 32]).unwrap(), &[0u8; 16]) + .unwrap(); + + let mut hchacha_out = [0u8; HCHACHA_OUTSIZE]; + let mut ietf_out = [0u8; CHACHA_BLOCKSIZE]; + + let ietf_src = chacha_state_ietf.process_block(Some(1)).unwrap(); + let hchacha_src = chacha_state_hchacha.process_block(None).unwrap(); + + assert!(chacha_state_hchacha + .serialize_block(&hchacha_src, &mut ietf_out) + .is_err()); + assert!(chacha_state_ietf + .serialize_block(&ietf_src, &mut hchacha_out) + .is_err()); + assert!(chacha_state_hchacha + .serialize_block(&hchacha_src, &mut hchacha_out) + .is_ok()); + assert!(chacha_state_ietf + .serialize_block(&ietf_src, &mut ietf_out) + .is_ok()); + } + } } -#[test] -// From https://tools.ietf.org/html/rfc8439 -fn test_quarter_round_results_on_indices() { - let mut chacha_state = InternalState { - state: [ - 0x879531e0, 0xc5ecf37d, 0x516461b1, 0xc9a62f8a, 0x44c20ef3, 0x3390af7f, 0xd9fc690b, - 0x2a5f714c, 0x53372767, 0xb00a5631, 0x974c541a, 0x359e9963, 0x5c971061, 0x3d631689, - 0x2098d9d6, 0x91dbd320, - ], - is_ietf: true, - }; - let expected: ChaChaState = [ - 0x879531e0, 0xc5ecf37d, 0xbdb886dc, 0xc9a62f8a, 0x44c20ef3, 0x3390af7f, 0xd9fc690b, - 0xcfacafd2, 0xe46bea80, 0xb00a5631, 0x974c541a, 0x359e9963, 0x5c971061, 0xccc07c79, - 0x2098d9d6, 0x91dbd320, - ]; - - chacha_state.quarter_round(2, 7, 8, 13); - assert_eq!(chacha_state.state[..], expected); -} +// Testing any test vectors that aren't put into library's /tests folder. +#[cfg(test)] +mod test_vectors { + use super::*; -#[test] -// From https://tools.ietf.org/html/rfc8439 -fn test_chacha20_block_results() { - let key = [ - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, - 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, - 0x1e, 0x1f, - ]; - let nonce = [ - 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x00, - ]; - let expected = [ - 0x10, 0xf1, 0xe7, 0xe4, 0xd1, 0x3b, 0x59, 0x15, 0x50, 0x0f, 0xdd, 0x1f, 0xa3, 0x20, 0x71, - 0xc4, 0xc7, 0xd1, 0xf4, 0xc7, 0x33, 0xc0, 0x68, 0x03, 0x04, 0x22, 0xaa, 0x9a, 0xc3, 0xd4, - 0x6c, 0x4e, 0xd2, 0x82, 0x64, 0x46, 0x07, 0x9f, 0xaa, 0x09, 0x14, 0xc2, 0xd7, 0x05, 0xd9, - 0x8b, 0x02, 0xa2, 0xb5, 0x12, 0x9c, 0xd1, 0xde, 0x16, 0x4e, 0xb9, 0xcb, 0xd0, 0x83, 0xe8, - 0xa2, 0x50, 0x3c, 0x4e, - ]; - // Test initial key-steup - let expected_init: ChaChaState = [ - 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574, 0x03020100, 0x07060504, 0x0b0a0908, - 0x0f0e0d0c, 0x13121110, 0x17161514, 0x1b1a1918, 0x1f1e1d1c, 0x00000001, 0x09000000, - 0x4a000000, 0x00000000, - ]; - // Test initial key-steup - let mut state = init(&key, &nonce).unwrap(); - state.state[12] = 1_u32; - assert_eq!(state.state[..], expected_init[..]); - - let keystream_block_from_state = state.process_block(Some(1)).unwrap(); - let mut ser_block = [0u8; 64]; - state - .serialize_block(&keystream_block_from_state, &mut ser_block) - .unwrap(); + // Convenience function for testing. + fn init(key: &[u8], nonce: &[u8]) -> Result { + let mut chacha_state = InternalState { + state: [0_u32; 16], + is_ietf: true, + }; - let keystream_block_only = keystream_block( - &SecretKey::from_slice(&key).unwrap(), - &Nonce::from_slice(&nonce).unwrap(), - 1, - ) - .unwrap(); + chacha_state + .init_state(&SecretKey::from_slice(key).unwrap(), nonce) + .unwrap(); - assert_eq!(ser_block[..], expected[..]); - assert_eq!(ser_block[..], keystream_block_only[..]); -} + Ok(chacha_state) + } -#[test] -// From https://tools.ietf.org/html/rfc8439 -fn chacha20_block_test_1() { - let key = [ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, - ]; - let nonce = [ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - ]; - let expected = [ - 0x76, 0xb8, 0xe0, 0xad, 0xa0, 0xf1, 0x3d, 0x90, 0x40, 0x5d, 0x6a, 0xe5, 0x53, 0x86, 0xbd, - 0x28, 0xbd, 0xd2, 0x19, 0xb8, 0xa0, 0x8d, 0xed, 0x1a, 0xa8, 0x36, 0xef, 0xcc, 0x8b, 0x77, - 0x0d, 0xc7, 0xda, 0x41, 0x59, 0x7c, 0x51, 0x57, 0x48, 0x8d, 0x77, 0x24, 0xe0, 0x3f, 0xb8, - 0xd8, 0x4a, 0x37, 0x6a, 0x43, 0xb8, 0xf4, 0x15, 0x18, 0xa1, 0x1c, 0xc3, 0x87, 0xb6, 0x69, - 0xb2, 0xee, 0x65, 0x86, - ]; - // Unserialized state - let expected_state: ChaChaState = [ - 0xade0b876, 0x903df1a0, 0xe56a5d40, 0x28bd8653, 0xb819d2bd, 0x1aed8da0, 0xccef36a8, - 0xc70d778b, 0x7c5941da, 0x8d485751, 0x3fe02477, 0x374ad8b8, 0xf4b8436a, 0x1ca11815, - 0x69b687c3, 0x8665eeb2, - ]; - - let mut state = init(&key, &nonce).unwrap(); - let keystream_block_from_state = state.process_block(Some(0)).unwrap(); - assert_eq!(keystream_block_from_state[..], expected_state[..]); - - let mut ser_block = [0u8; 64]; - state - .serialize_block(&keystream_block_from_state, &mut ser_block) - .unwrap(); + #[test] + fn rfc8439_quarter_round_results() { + let mut chacha_state = InternalState { + state: [ + 0x11111111, 0x01020304, 0x9b8d6f43, 0x01234567, 0x11111111, 0x01020304, 0x9b8d6f43, + 0x01234567, 0x11111111, 0x01020304, 0x9b8d6f43, 0x01234567, 0x11111111, 0x01020304, + 0x9b8d6f43, 0x01234567, + ], + is_ietf: true, + }; + let expected: [u32; 4] = [0xea2a92f4, 0xcb1cf8ce, 0x4581472e, 0x5881c4bb]; + + chacha_state.quarter_round(0, 1, 2, 3); + chacha_state.quarter_round(4, 5, 6, 7); + chacha_state.quarter_round(8, 9, 10, 11); + chacha_state.quarter_round(12, 13, 14, 15); + + assert_eq!(chacha_state.state[0..4], expected); + assert_eq!(chacha_state.state[4..8], expected); + assert_eq!(chacha_state.state[8..12], expected); + assert_eq!(chacha_state.state[12..16], expected); + } - let keystream_block_only = keystream_block( - &SecretKey::from_slice(&key).unwrap(), - &Nonce::from_slice(&nonce).unwrap(), - 0, - ) - .unwrap(); + #[test] + fn rfc8439_quarter_round_results_on_indices() { + let mut chacha_state = InternalState { + state: [ + 0x879531e0, 0xc5ecf37d, 0x516461b1, 0xc9a62f8a, 0x44c20ef3, 0x3390af7f, 0xd9fc690b, + 0x2a5f714c, 0x53372767, 0xb00a5631, 0x974c541a, 0x359e9963, 0x5c971061, 0x3d631689, + 0x2098d9d6, 0x91dbd320, + ], + is_ietf: true, + }; + let expected: ChaChaState = [ + 0x879531e0, 0xc5ecf37d, 0xbdb886dc, 0xc9a62f8a, 0x44c20ef3, 0x3390af7f, 0xd9fc690b, + 0xcfacafd2, 0xe46bea80, 0xb00a5631, 0x974c541a, 0x359e9963, 0x5c971061, 0xccc07c79, + 0x2098d9d6, 0x91dbd320, + ]; - assert_eq!(ser_block[..], expected[..]); - assert_eq!(ser_block[..], keystream_block_only[..]); -} + chacha_state.quarter_round(2, 7, 8, 13); + assert_eq!(chacha_state.state[..], expected); + } -#[test] -// From https://tools.ietf.org/html/rfc8439 -fn chacha20_block_test_2() { - let key = [ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, - ]; - let nonce = [ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - ]; - let expected = [ - 0x9f, 0x07, 0xe7, 0xbe, 0x55, 0x51, 0x38, 0x7a, 0x98, 0xba, 0x97, 0x7c, 0x73, 0x2d, 0x08, - 0x0d, 0xcb, 0x0f, 0x29, 0xa0, 0x48, 0xe3, 0x65, 0x69, 0x12, 0xc6, 0x53, 0x3e, 0x32, 0xee, - 0x7a, 0xed, 0x29, 0xb7, 0x21, 0x76, 0x9c, 0xe6, 0x4e, 0x43, 0xd5, 0x71, 0x33, 0xb0, 0x74, - 0xd8, 0x39, 0xd5, 0x31, 0xed, 0x1f, 0x28, 0x51, 0x0a, 0xfb, 0x45, 0xac, 0xe1, 0x0a, 0x1f, - 0x4b, 0x79, 0x4d, 0x6f, - ]; - // Unserialized state - let expected_state: ChaChaState = [ - 0xbee7079f, 0x7a385155, 0x7c97ba98, 0x0d082d73, 0xa0290fcb, 0x6965e348, 0x3e53c612, - 0xed7aee32, 0x7621b729, 0x434ee69c, 0xb03371d5, 0xd539d874, 0x281fed31, 0x45fb0a51, - 0x1f0ae1ac, 0x6f4d794b, - ]; - - let mut state = init(&key, &nonce).unwrap(); - let keystream_block_from_state = state.process_block(Some(1)).unwrap(); - assert_eq!(keystream_block_from_state[..], expected_state[..]); - - let mut ser_block = [0u8; 64]; - state - .serialize_block(&keystream_block_from_state, &mut ser_block) + #[test] + fn rfc8439_chacha20_block_results() { + let key = [ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, + 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, + 0x1c, 0x1d, 0x1e, 0x1f, + ]; + let nonce = [ + 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x00, + ]; + let expected = [ + 0x10, 0xf1, 0xe7, 0xe4, 0xd1, 0x3b, 0x59, 0x15, 0x50, 0x0f, 0xdd, 0x1f, 0xa3, 0x20, + 0x71, 0xc4, 0xc7, 0xd1, 0xf4, 0xc7, 0x33, 0xc0, 0x68, 0x03, 0x04, 0x22, 0xaa, 0x9a, + 0xc3, 0xd4, 0x6c, 0x4e, 0xd2, 0x82, 0x64, 0x46, 0x07, 0x9f, 0xaa, 0x09, 0x14, 0xc2, + 0xd7, 0x05, 0xd9, 0x8b, 0x02, 0xa2, 0xb5, 0x12, 0x9c, 0xd1, 0xde, 0x16, 0x4e, 0xb9, + 0xcb, 0xd0, 0x83, 0xe8, 0xa2, 0x50, 0x3c, 0x4e, + ]; + + let expected_init: ChaChaState = [ + 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574, 0x03020100, 0x07060504, 0x0b0a0908, + 0x0f0e0d0c, 0x13121110, 0x17161514, 0x1b1a1918, 0x1f1e1d1c, 0x00000001, 0x09000000, + 0x4a000000, 0x00000000, + ]; + // Test initial key-steup + let mut state = init(&key, &nonce).unwrap(); + state.state[12] = 1_u32; + assert_eq!(state.state[..], expected_init[..]); + + let keystream_block_from_state = state.process_block(Some(1)).unwrap(); + let mut ser_block = [0u8; 64]; + state + .serialize_block(&keystream_block_from_state, &mut ser_block) + .unwrap(); + + let keystream_block_only = keystream_block( + &SecretKey::from_slice(&key).unwrap(), + &Nonce::from_slice(&nonce).unwrap(), + 1, + ) .unwrap(); - let keystream_block_only = keystream_block( - &SecretKey::from_slice(&key).unwrap(), - &Nonce::from_slice(&nonce).unwrap(), - 1, - ) - .unwrap(); - - assert_eq!(ser_block[..], expected[..]); - assert_eq!(ser_block[..], keystream_block_only[..]); -} + assert_eq!(ser_block[..], expected[..]); + assert_eq!(ser_block[..], keystream_block_only[..]); + } -#[test] -// From https://tools.ietf.org/html/rfc8439 -fn chacha20_block_test_3() { - let key = [ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x01, - ]; - let nonce = [ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - ]; - let expected = [ - 0x3a, 0xeb, 0x52, 0x24, 0xec, 0xf8, 0x49, 0x92, 0x9b, 0x9d, 0x82, 0x8d, 0xb1, 0xce, 0xd4, - 0xdd, 0x83, 0x20, 0x25, 0xe8, 0x01, 0x8b, 0x81, 0x60, 0xb8, 0x22, 0x84, 0xf3, 0xc9, 0x49, - 0xaa, 0x5a, 0x8e, 0xca, 0x00, 0xbb, 0xb4, 0xa7, 0x3b, 0xda, 0xd1, 0x92, 0xb5, 0xc4, 0x2f, - 0x73, 0xf2, 0xfd, 0x4e, 0x27, 0x36, 0x44, 0xc8, 0xb3, 0x61, 0x25, 0xa6, 0x4a, 0xdd, 0xeb, - 0x00, 0x6c, 0x13, 0xa0, - ]; - // Unserialized state - let expected_state: ChaChaState = [ - 0x2452eb3a, 0x9249f8ec, 0x8d829d9b, 0xddd4ceb1, 0xe8252083, 0x60818b01, 0xf38422b8, - 0x5aaa49c9, 0xbb00ca8e, 0xda3ba7b4, 0xc4b592d1, 0xfdf2732f, 0x4436274e, 0x2561b3c8, - 0xebdd4aa6, 0xa0136c00, - ]; - - let mut state = init(&key, &nonce).unwrap(); - let keystream_block_from_state = state.process_block(Some(1)).unwrap(); - assert_eq!(keystream_block_from_state[..], expected_state[..]); - - let mut ser_block = [0u8; 64]; - state - .serialize_block(&keystream_block_from_state, &mut ser_block) + #[test] + fn rfc8439_chacha20_block_test_1() { + let key = [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + ]; + let nonce = [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + let expected = [ + 0x76, 0xb8, 0xe0, 0xad, 0xa0, 0xf1, 0x3d, 0x90, 0x40, 0x5d, 0x6a, 0xe5, 0x53, 0x86, + 0xbd, 0x28, 0xbd, 0xd2, 0x19, 0xb8, 0xa0, 0x8d, 0xed, 0x1a, 0xa8, 0x36, 0xef, 0xcc, + 0x8b, 0x77, 0x0d, 0xc7, 0xda, 0x41, 0x59, 0x7c, 0x51, 0x57, 0x48, 0x8d, 0x77, 0x24, + 0xe0, 0x3f, 0xb8, 0xd8, 0x4a, 0x37, 0x6a, 0x43, 0xb8, 0xf4, 0x15, 0x18, 0xa1, 0x1c, + 0xc3, 0x87, 0xb6, 0x69, 0xb2, 0xee, 0x65, 0x86, + ]; + // Unserialized state + let expected_state: ChaChaState = [ + 0xade0b876, 0x903df1a0, 0xe56a5d40, 0x28bd8653, 0xb819d2bd, 0x1aed8da0, 0xccef36a8, + 0xc70d778b, 0x7c5941da, 0x8d485751, 0x3fe02477, 0x374ad8b8, 0xf4b8436a, 0x1ca11815, + 0x69b687c3, 0x8665eeb2, + ]; + + let mut state = init(&key, &nonce).unwrap(); + let keystream_block_from_state = state.process_block(Some(0)).unwrap(); + assert_eq!(keystream_block_from_state[..], expected_state[..]); + + let mut ser_block = [0u8; 64]; + state + .serialize_block(&keystream_block_from_state, &mut ser_block) + .unwrap(); + + let keystream_block_only = keystream_block( + &SecretKey::from_slice(&key).unwrap(), + &Nonce::from_slice(&nonce).unwrap(), + 0, + ) .unwrap(); - let keystream_block_only = keystream_block( - &SecretKey::from_slice(&key).unwrap(), - &Nonce::from_slice(&nonce).unwrap(), - 1, - ) - .unwrap(); - - assert_eq!(ser_block[..], expected[..]); - assert_eq!(ser_block[..], keystream_block_only[..]); -} + assert_eq!(ser_block[..], expected[..]); + assert_eq!(ser_block[..], keystream_block_only[..]); + } -#[test] -// From https://tools.ietf.org/html/rfc8439 -fn chacha20_block_test_4() { - let key = [ - 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, - ]; - let nonce = [ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - ]; - let expected = [ - 0x72, 0xd5, 0x4d, 0xfb, 0xf1, 0x2e, 0xc4, 0x4b, 0x36, 0x26, 0x92, 0xdf, 0x94, 0x13, 0x7f, - 0x32, 0x8f, 0xea, 0x8d, 0xa7, 0x39, 0x90, 0x26, 0x5e, 0xc1, 0xbb, 0xbe, 0xa1, 0xae, 0x9a, - 0xf0, 0xca, 0x13, 0xb2, 0x5a, 0xa2, 0x6c, 0xb4, 0xa6, 0x48, 0xcb, 0x9b, 0x9d, 0x1b, 0xe6, - 0x5b, 0x2c, 0x09, 0x24, 0xa6, 0x6c, 0x54, 0xd5, 0x45, 0xec, 0x1b, 0x73, 0x74, 0xf4, 0x87, - 0x2e, 0x99, 0xf0, 0x96, - ]; - // Unserialized state - let expected_state: ChaChaState = [ - 0xfb4dd572, 0x4bc42ef1, 0xdf922636, 0x327f1394, 0xa78dea8f, 0x5e269039, 0xa1bebbc1, - 0xcaf09aae, 0xa25ab213, 0x48a6b46c, 0x1b9d9bcb, 0x092c5be6, 0x546ca624, 0x1bec45d5, - 0x87f47473, 0x96f0992e, - ]; - - let mut state = init(&key, &nonce).unwrap(); - let keystream_block_from_state = state.process_block(Some(2)).unwrap(); - assert_eq!(keystream_block_from_state[..], expected_state[..]); - - let mut ser_block = [0u8; 64]; - state - .serialize_block(&keystream_block_from_state, &mut ser_block) + #[test] + fn rfc8439_chacha20_block_test_2() { + let key = [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + ]; + let nonce = [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + let expected = [ + 0x9f, 0x07, 0xe7, 0xbe, 0x55, 0x51, 0x38, 0x7a, 0x98, 0xba, 0x97, 0x7c, 0x73, 0x2d, + 0x08, 0x0d, 0xcb, 0x0f, 0x29, 0xa0, 0x48, 0xe3, 0x65, 0x69, 0x12, 0xc6, 0x53, 0x3e, + 0x32, 0xee, 0x7a, 0xed, 0x29, 0xb7, 0x21, 0x76, 0x9c, 0xe6, 0x4e, 0x43, 0xd5, 0x71, + 0x33, 0xb0, 0x74, 0xd8, 0x39, 0xd5, 0x31, 0xed, 0x1f, 0x28, 0x51, 0x0a, 0xfb, 0x45, + 0xac, 0xe1, 0x0a, 0x1f, 0x4b, 0x79, 0x4d, 0x6f, + ]; + // Unserialized state + let expected_state: ChaChaState = [ + 0xbee7079f, 0x7a385155, 0x7c97ba98, 0x0d082d73, 0xa0290fcb, 0x6965e348, 0x3e53c612, + 0xed7aee32, 0x7621b729, 0x434ee69c, 0xb03371d5, 0xd539d874, 0x281fed31, 0x45fb0a51, + 0x1f0ae1ac, 0x6f4d794b, + ]; + + let mut state = init(&key, &nonce).unwrap(); + let keystream_block_from_state = state.process_block(Some(1)).unwrap(); + assert_eq!(keystream_block_from_state[..], expected_state[..]); + + let mut ser_block = [0u8; 64]; + state + .serialize_block(&keystream_block_from_state, &mut ser_block) + .unwrap(); + + let keystream_block_only = keystream_block( + &SecretKey::from_slice(&key).unwrap(), + &Nonce::from_slice(&nonce).unwrap(), + 1, + ) .unwrap(); - let keystream_block_only = keystream_block( - &SecretKey::from_slice(&key).unwrap(), - &Nonce::from_slice(&nonce).unwrap(), - 2, - ) - .unwrap(); - - assert_eq!(ser_block[..], expected[..]); - assert_eq!(ser_block[..], keystream_block_only[..]); -} + assert_eq!(ser_block[..], expected[..]); + assert_eq!(ser_block[..], keystream_block_only[..]); + } -#[test] -// From https://tools.ietf.org/html/rfc8439 -fn chacha20_block_test_5() { - let key = [ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, - ]; - let nonce = [ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, - ]; - let expected = [ - 0xc2, 0xc6, 0x4d, 0x37, 0x8c, 0xd5, 0x36, 0x37, 0x4a, 0xe2, 0x04, 0xb9, 0xef, 0x93, 0x3f, - 0xcd, 0x1a, 0x8b, 0x22, 0x88, 0xb3, 0xdf, 0xa4, 0x96, 0x72, 0xab, 0x76, 0x5b, 0x54, 0xee, - 0x27, 0xc7, 0x8a, 0x97, 0x0e, 0x0e, 0x95, 0x5c, 0x14, 0xf3, 0xa8, 0x8e, 0x74, 0x1b, 0x97, - 0xc2, 0x86, 0xf7, 0x5f, 0x8f, 0xc2, 0x99, 0xe8, 0x14, 0x83, 0x62, 0xfa, 0x19, 0x8a, 0x39, - 0x53, 0x1b, 0xed, 0x6d, - ]; - // Unserialized state - let expected_state: ChaChaState = [ - 0x374dc6c2, 0x3736d58c, 0xb904e24a, 0xcd3f93ef, 0x88228b1a, 0x96a4dfb3, 0x5b76ab72, - 0xc727ee54, 0x0e0e978a, 0xf3145c95, 0x1b748ea8, 0xf786c297, 0x99c28f5f, 0x628314e8, - 0x398a19fa, 0x6ded1b53, - ]; - - let mut state = init(&key, &nonce).unwrap(); - let keystream_block_from_state = state.process_block(Some(0)).unwrap(); - assert_eq!(keystream_block_from_state[..], expected_state[..]); - - let mut ser_block = [0u8; 64]; - state - .serialize_block(&keystream_block_from_state, &mut ser_block) + #[test] + fn rfc8439_chacha20_block_test_3() { + let key = [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, + ]; + let nonce = [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + let expected = [ + 0x3a, 0xeb, 0x52, 0x24, 0xec, 0xf8, 0x49, 0x92, 0x9b, 0x9d, 0x82, 0x8d, 0xb1, 0xce, + 0xd4, 0xdd, 0x83, 0x20, 0x25, 0xe8, 0x01, 0x8b, 0x81, 0x60, 0xb8, 0x22, 0x84, 0xf3, + 0xc9, 0x49, 0xaa, 0x5a, 0x8e, 0xca, 0x00, 0xbb, 0xb4, 0xa7, 0x3b, 0xda, 0xd1, 0x92, + 0xb5, 0xc4, 0x2f, 0x73, 0xf2, 0xfd, 0x4e, 0x27, 0x36, 0x44, 0xc8, 0xb3, 0x61, 0x25, + 0xa6, 0x4a, 0xdd, 0xeb, 0x00, 0x6c, 0x13, 0xa0, + ]; + // Unserialized state + let expected_state: ChaChaState = [ + 0x2452eb3a, 0x9249f8ec, 0x8d829d9b, 0xddd4ceb1, 0xe8252083, 0x60818b01, 0xf38422b8, + 0x5aaa49c9, 0xbb00ca8e, 0xda3ba7b4, 0xc4b592d1, 0xfdf2732f, 0x4436274e, 0x2561b3c8, + 0xebdd4aa6, 0xa0136c00, + ]; + + let mut state = init(&key, &nonce).unwrap(); + let keystream_block_from_state = state.process_block(Some(1)).unwrap(); + assert_eq!(keystream_block_from_state[..], expected_state[..]); + + let mut ser_block = [0u8; 64]; + state + .serialize_block(&keystream_block_from_state, &mut ser_block) + .unwrap(); + + let keystream_block_only = keystream_block( + &SecretKey::from_slice(&key).unwrap(), + &Nonce::from_slice(&nonce).unwrap(), + 1, + ) .unwrap(); - let keystream_block_only = keystream_block( - &SecretKey::from_slice(&key).unwrap(), - &Nonce::from_slice(&nonce).unwrap(), - 0, - ) - .unwrap(); - - assert_eq!(ser_block[..], expected[..]); - assert_eq!(ser_block[..], keystream_block_only[..]); -} - -#[test] -// From https://tools.ietf.org/html/rfc8439 -fn test_key_schedule() { - let key = [ - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, - 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, - 0x1e, 0x1f, - ]; - let nonce = [ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x00, - ]; - // First block setup expected - let first_state: ChaChaState = [ - 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574, 0x03020100, 0x07060504, 0x0b0a0908, - 0x0f0e0d0c, 0x13121110, 0x17161514, 0x1b1a1918, 0x1f1e1d1c, 0x00000001, 0x00000000, - 0x4a000000, 0x00000000, - ]; - // Second block setup expected - let second_state: ChaChaState = [ - 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574, 0x03020100, 0x07060504, 0x0b0a0908, - 0x0f0e0d0c, 0x13121110, 0x17161514, 0x1b1a1918, 0x1f1e1d1c, 0x00000002, 0x00000000, - 0x4a000000, 0x00000000, - ]; - - // First block operation expected - let first_block: ChaChaState = [ - 0xf3514f22, 0xe1d91b40, 0x6f27de2f, 0xed1d63b8, 0x821f138c, 0xe2062c3d, 0xecca4f7e, - 0x78cff39e, 0xa30a3b8a, 0x920a6072, 0xcd7479b5, 0x34932bed, 0x40ba4c79, 0xcd343ec6, - 0x4c2c21ea, 0xb7417df0, - ]; - // Second block operation expected - let second_block: ChaChaState = [ - 0x9f74a669, 0x410f633f, 0x28feca22, 0x7ec44dec, 0x6d34d426, 0x738cb970, 0x3ac5e9f3, - 0x45590cc4, 0xda6e8b39, 0x892c831a, 0xcdea67c1, 0x2b7e1d90, 0x037463f3, 0xa11a2073, - 0xe8bcfb88, 0xedc49139, - ]; - - // Expected keystream - let expected_keystream = [ - 0x22, 0x4f, 0x51, 0xf3, 0x40, 0x1b, 0xd9, 0xe1, 0x2f, 0xde, 0x27, 0x6f, 0xb8, 0x63, 0x1d, - 0xed, 0x8c, 0x13, 0x1f, 0x82, 0x3d, 0x2c, 0x06, 0xe2, 0x7e, 0x4f, 0xca, 0xec, 0x9e, 0xf3, - 0xcf, 0x78, 0x8a, 0x3b, 0x0a, 0xa3, 0x72, 0x60, 0x0a, 0x92, 0xb5, 0x79, 0x74, 0xcd, 0xed, - 0x2b, 0x93, 0x34, 0x79, 0x4c, 0xba, 0x40, 0xc6, 0x3e, 0x34, 0xcd, 0xea, 0x21, 0x2c, 0x4c, - 0xf0, 0x7d, 0x41, 0xb7, 0x69, 0xa6, 0x74, 0x9f, 0x3f, 0x63, 0x0f, 0x41, 0x22, 0xca, 0xfe, - 0x28, 0xec, 0x4d, 0xc4, 0x7e, 0x26, 0xd4, 0x34, 0x6d, 0x70, 0xb9, 0x8c, 0x73, 0xf3, 0xe9, - 0xc5, 0x3a, 0xc4, 0x0c, 0x59, 0x45, 0x39, 0x8b, 0x6e, 0xda, 0x1a, 0x83, 0x2c, 0x89, 0xc1, - 0x67, 0xea, 0xcd, 0x90, 0x1d, 0x7e, 0x2b, 0xf3, 0x63, - ]; - - let mut state = init(&key, &nonce).unwrap(); - // Block call with initial counter - let first_block_state = state.process_block(Some(1)).unwrap(); - assert_eq!(first_block_state, first_block); - // Test first internal state - assert_eq!(first_state, state.state); - - // Next iteration call, increase counter - let second_block_state = state.process_block(Some(1 + 1)).unwrap(); - assert_eq!(second_block_state, second_block); - // Test second internal state - assert_eq!(second_state, state.state); - - let mut actual_keystream = [0u8; 128]; - // Append first keystream block - state - .serialize_block(&first_block_state, &mut actual_keystream[..64]) - .unwrap(); - state - .serialize_block(&second_block_state, &mut actual_keystream[64..]) - .unwrap(); - assert_eq!( - actual_keystream[..expected_keystream.len()].as_ref(), - expected_keystream.as_ref() - ); + assert_eq!(ser_block[..], expected[..]); + assert_eq!(ser_block[..], keystream_block_only[..]); + } - actual_keystream[..64].copy_from_slice( - &keystream_block( + #[test] + fn rfc8439_chacha20_block_test_4() { + let key = [ + 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + ]; + let nonce = [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + let expected = [ + 0x72, 0xd5, 0x4d, 0xfb, 0xf1, 0x2e, 0xc4, 0x4b, 0x36, 0x26, 0x92, 0xdf, 0x94, 0x13, + 0x7f, 0x32, 0x8f, 0xea, 0x8d, 0xa7, 0x39, 0x90, 0x26, 0x5e, 0xc1, 0xbb, 0xbe, 0xa1, + 0xae, 0x9a, 0xf0, 0xca, 0x13, 0xb2, 0x5a, 0xa2, 0x6c, 0xb4, 0xa6, 0x48, 0xcb, 0x9b, + 0x9d, 0x1b, 0xe6, 0x5b, 0x2c, 0x09, 0x24, 0xa6, 0x6c, 0x54, 0xd5, 0x45, 0xec, 0x1b, + 0x73, 0x74, 0xf4, 0x87, 0x2e, 0x99, 0xf0, 0x96, + ]; + // Unserialized state + let expected_state: ChaChaState = [ + 0xfb4dd572, 0x4bc42ef1, 0xdf922636, 0x327f1394, 0xa78dea8f, 0x5e269039, 0xa1bebbc1, + 0xcaf09aae, 0xa25ab213, 0x48a6b46c, 0x1b9d9bcb, 0x092c5be6, 0x546ca624, 0x1bec45d5, + 0x87f47473, 0x96f0992e, + ]; + + let mut state = init(&key, &nonce).unwrap(); + let keystream_block_from_state = state.process_block(Some(2)).unwrap(); + assert_eq!(keystream_block_from_state[..], expected_state[..]); + + let mut ser_block = [0u8; 64]; + state + .serialize_block(&keystream_block_from_state, &mut ser_block) + .unwrap(); + + let keystream_block_only = keystream_block( &SecretKey::from_slice(&key).unwrap(), &Nonce::from_slice(&nonce).unwrap(), - 1, + 2, ) - .unwrap(), - ); - actual_keystream[64..].copy_from_slice( - &keystream_block( + .unwrap(); + + assert_eq!(ser_block[..], expected[..]); + assert_eq!(ser_block[..], keystream_block_only[..]); + } + + #[test] + fn rfc8439_chacha20_block_test_5() { + let key = [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + ]; + let nonce = [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + ]; + let expected = [ + 0xc2, 0xc6, 0x4d, 0x37, 0x8c, 0xd5, 0x36, 0x37, 0x4a, 0xe2, 0x04, 0xb9, 0xef, 0x93, + 0x3f, 0xcd, 0x1a, 0x8b, 0x22, 0x88, 0xb3, 0xdf, 0xa4, 0x96, 0x72, 0xab, 0x76, 0x5b, + 0x54, 0xee, 0x27, 0xc7, 0x8a, 0x97, 0x0e, 0x0e, 0x95, 0x5c, 0x14, 0xf3, 0xa8, 0x8e, + 0x74, 0x1b, 0x97, 0xc2, 0x86, 0xf7, 0x5f, 0x8f, 0xc2, 0x99, 0xe8, 0x14, 0x83, 0x62, + 0xfa, 0x19, 0x8a, 0x39, 0x53, 0x1b, 0xed, 0x6d, + ]; + // Unserialized state + let expected_state: ChaChaState = [ + 0x374dc6c2, 0x3736d58c, 0xb904e24a, 0xcd3f93ef, 0x88228b1a, 0x96a4dfb3, 0x5b76ab72, + 0xc727ee54, 0x0e0e978a, 0xf3145c95, 0x1b748ea8, 0xf786c297, 0x99c28f5f, 0x628314e8, + 0x398a19fa, 0x6ded1b53, + ]; + + let mut state = init(&key, &nonce).unwrap(); + let keystream_block_from_state = state.process_block(Some(0)).unwrap(); + assert_eq!(keystream_block_from_state[..], expected_state[..]); + + let mut ser_block = [0u8; 64]; + state + .serialize_block(&keystream_block_from_state, &mut ser_block) + .unwrap(); + + let keystream_block_only = keystream_block( &SecretKey::from_slice(&key).unwrap(), &Nonce::from_slice(&nonce).unwrap(), - 1 + 1, + 0, ) - .unwrap(), - ); + .unwrap(); - assert_eq!( - actual_keystream[..expected_keystream.len()].as_ref(), - expected_keystream.as_ref() - ); + assert_eq!(ser_block[..], expected[..]); + assert_eq!(ser_block[..], keystream_block_only[..]); + } + + #[test] + fn rfc8439_key_schedule() { + let key = [ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, + 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, + 0x1c, 0x1d, 0x1e, 0x1f, + ]; + let nonce = [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x00, + ]; + // First block setup expected + let first_state: ChaChaState = [ + 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574, 0x03020100, 0x07060504, 0x0b0a0908, + 0x0f0e0d0c, 0x13121110, 0x17161514, 0x1b1a1918, 0x1f1e1d1c, 0x00000001, 0x00000000, + 0x4a000000, 0x00000000, + ]; + // Second block setup expected + let second_state: ChaChaState = [ + 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574, 0x03020100, 0x07060504, 0x0b0a0908, + 0x0f0e0d0c, 0x13121110, 0x17161514, 0x1b1a1918, 0x1f1e1d1c, 0x00000002, 0x00000000, + 0x4a000000, 0x00000000, + ]; + + // First block operation expected + let first_block: ChaChaState = [ + 0xf3514f22, 0xe1d91b40, 0x6f27de2f, 0xed1d63b8, 0x821f138c, 0xe2062c3d, 0xecca4f7e, + 0x78cff39e, 0xa30a3b8a, 0x920a6072, 0xcd7479b5, 0x34932bed, 0x40ba4c79, 0xcd343ec6, + 0x4c2c21ea, 0xb7417df0, + ]; + // Second block operation expected + let second_block: ChaChaState = [ + 0x9f74a669, 0x410f633f, 0x28feca22, 0x7ec44dec, 0x6d34d426, 0x738cb970, 0x3ac5e9f3, + 0x45590cc4, 0xda6e8b39, 0x892c831a, 0xcdea67c1, 0x2b7e1d90, 0x037463f3, 0xa11a2073, + 0xe8bcfb88, 0xedc49139, + ]; + + // Expected keystream + let expected_keystream = [ + 0x22, 0x4f, 0x51, 0xf3, 0x40, 0x1b, 0xd9, 0xe1, 0x2f, 0xde, 0x27, 0x6f, 0xb8, 0x63, + 0x1d, 0xed, 0x8c, 0x13, 0x1f, 0x82, 0x3d, 0x2c, 0x06, 0xe2, 0x7e, 0x4f, 0xca, 0xec, + 0x9e, 0xf3, 0xcf, 0x78, 0x8a, 0x3b, 0x0a, 0xa3, 0x72, 0x60, 0x0a, 0x92, 0xb5, 0x79, + 0x74, 0xcd, 0xed, 0x2b, 0x93, 0x34, 0x79, 0x4c, 0xba, 0x40, 0xc6, 0x3e, 0x34, 0xcd, + 0xea, 0x21, 0x2c, 0x4c, 0xf0, 0x7d, 0x41, 0xb7, 0x69, 0xa6, 0x74, 0x9f, 0x3f, 0x63, + 0x0f, 0x41, 0x22, 0xca, 0xfe, 0x28, 0xec, 0x4d, 0xc4, 0x7e, 0x26, 0xd4, 0x34, 0x6d, + 0x70, 0xb9, 0x8c, 0x73, 0xf3, 0xe9, 0xc5, 0x3a, 0xc4, 0x0c, 0x59, 0x45, 0x39, 0x8b, + 0x6e, 0xda, 0x1a, 0x83, 0x2c, 0x89, 0xc1, 0x67, 0xea, 0xcd, 0x90, 0x1d, 0x7e, 0x2b, + 0xf3, 0x63, + ]; + + let mut state = init(&key, &nonce).unwrap(); + // Block call with initial counter + let first_block_state = state.process_block(Some(1)).unwrap(); + assert_eq!(first_block_state, first_block); + // Test first internal state + assert_eq!(first_state, state.state); + + // Next iteration call, increase counter + let second_block_state = state.process_block(Some(1 + 1)).unwrap(); + assert_eq!(second_block_state, second_block); + // Test second internal state + assert_eq!(second_state, state.state); + + let mut actual_keystream = [0u8; 128]; + // Append first keystream block + state + .serialize_block(&first_block_state, &mut actual_keystream[..64]) + .unwrap(); + state + .serialize_block(&second_block_state, &mut actual_keystream[64..]) + .unwrap(); + assert_eq!( + actual_keystream[..expected_keystream.len()].as_ref(), + expected_keystream.as_ref() + ); + + actual_keystream[..64].copy_from_slice( + &keystream_block( + &SecretKey::from_slice(&key).unwrap(), + &Nonce::from_slice(&nonce).unwrap(), + 1, + ) + .unwrap(), + ); + actual_keystream[64..].copy_from_slice( + &keystream_block( + &SecretKey::from_slice(&key).unwrap(), + &Nonce::from_slice(&nonce).unwrap(), + 1 + 1, + ) + .unwrap(), + ); + + assert_eq!( + actual_keystream[..expected_keystream.len()].as_ref(), + expected_keystream.as_ref() + ); + } } diff --git a/src/hazardous/stream/xchacha20.rs b/src/hazardous/stream/xchacha20.rs index 604034c6..07ba7bd4 100644 --- a/src/hazardous/stream/xchacha20.rs +++ b/src/hazardous/stream/xchacha20.rs @@ -182,3 +182,284 @@ fn test_pass_on_one_iter_max_initial_counter() { ) .unwrap(); } + +// +// The tests below are the same tests as the ones in `chacha20` +// but with a bigger nonce. It's debatable whether this is needed, but right +// now I'm keeping them as they don't seem to bring any disadvantages. +// + +// Testing public functions in the module. +#[cfg(test)] +mod public { + use super::*; + // One function tested per submodule. + + // encrypt()/decrypt() are tested together here + // since decrypt() is just a wrapper around encrypt() + // and so only the decrypt() function is called + mod test_encrypt_decrypt { + use super::*; + #[test] + fn test_fail_on_initial_counter_overflow() { + let mut dst = [0u8; 65]; + + assert!(decrypt( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 24]).unwrap(), + u32::max_value(), + &[0u8; 65], + &mut dst, + ) + .is_err()); + } + + #[test] + fn test_pass_on_one_iter_max_initial_counter() { + let mut dst = [0u8; 64]; + // Should pass because only one iteration is completed, so block_counter will + // not increase + assert!(decrypt( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 24]).unwrap(), + u32::max_value(), + &[0u8; 64], + &mut dst, + ) + .is_ok()); + } + + #[test] + fn test_fail_on_empty_plaintext() { + let mut dst = [0u8; 64]; + + assert!(decrypt( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 24]).unwrap(), + 0, + &[0u8; 0], + &mut dst, + ) + .is_err()); + } + + #[test] + fn test_dst_out_length() { + let mut dst_small = [0u8; 64]; + + assert!(decrypt( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 24]).unwrap(), + 0, + &[0u8; 128], + &mut dst_small, + ) + .is_err()); + + let mut dst = [0u8; 64]; + + assert!(decrypt( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 24]).unwrap(), + 0, + &[0u8; 64], + &mut dst, + ) + .is_ok()); + + let mut dst_big = [0u8; 64]; + + assert!(decrypt( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 24]).unwrap(), + 0, + &[0u8; 32], + &mut dst_big, + ) + .is_ok()); + } + + // Proptests. Only exectued when NOT testing no_std. + #[cfg(feature = "safe_api")] + mod proptest { + use super::*; + + /// Given a input length `a` find out how many times + /// the initial counter on encrypt()/decrypt() would + /// increase. + fn counter_increase_times(a: f32) -> u32 { + // Otherwise a overvlowing subtration would happen + if a <= 64f32 { + return 0; + } + + let check_with_floor = (a / 64f32).floor(); + let actual = a / 64f32; + + assert!(actual >= check_with_floor); + // Subtract one because the first 64 in length + // the counter does not increase + if actual > check_with_floor { + (actual.ceil() as u32) - 1 + } else { + (actual as u32) - 1 + } + } + + quickcheck! { + // Encrypting input, and then decrypting should always yield the same input. + fn prop_encrypt_decrypt_same_input(input: Vec, block_counter: u32) -> bool { + let pt = if input.is_empty() { + vec![1u8; 10] + } else { + input + }; + + let mut dst_out_ct = vec![0u8; pt.len()]; + let mut dst_out_pt = vec![0u8; pt.len()]; + + // If `block_counter` is high enough check if it would overflow + if counter_increase_times(pt.len() as f32).checked_add(block_counter).is_none() { + // Overflow will occur and the operation should fail + let res = if encrypt( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 24]).unwrap(), + block_counter, + &pt[..], + &mut dst_out_ct, + ).is_err() { true } else { false }; + + return res; + } else { + + encrypt( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 24]).unwrap(), + block_counter, + &pt[..], + &mut dst_out_ct, + ).unwrap(); + + decrypt( + &SecretKey::from_slice(&[0u8; 32]).unwrap(), + &Nonce::from_slice(&[0u8; 24]).unwrap(), + block_counter, + &dst_out_ct[..], + &mut dst_out_pt, + ).unwrap(); + + return dst_out_pt == pt; + } + } + } + + 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/hazardous/xof/cshake.rs b/src/hazardous/xof/cshake.rs index 82629289..e75eb7ed 100644 --- a/src/hazardous/xof/cshake.rs +++ b/src/hazardous/xof/cshake.rs @@ -75,7 +75,6 @@ extern crate core; use self::core::mem; use crate::errors::{FinalizationCryptoError, UnknownCryptoError}; -use byteorder::{BigEndian, ByteOrder}; use tiny_keccak::Keccak; #[must_use] @@ -99,7 +98,15 @@ impl core::fmt::Debug for CShake { impl CShake { /// Initial setup with encoding of `custom` and `name`. - fn setup(&mut self, custom: &[u8], name: &[u8]) { + fn setup(&mut self, custom: &[u8], name: &[u8]) -> Result<(), UnknownCryptoError> { + if (name.is_empty()) && (custom.is_empty()) { + return Err(UnknownCryptoError); + } + + if name.len() > 65536 || custom.len() > 65536 { + return Err(UnknownCryptoError); + } + // Only append the left encoded rate, not the rate itself as with `name` and // `custom` let (encoded, offset) = left_encode(136_u64); @@ -117,6 +124,8 @@ impl CShake { // Pad with zeroes before calling pad() in finalize() self.hasher.fill_block(); self.setup_hasher = self.hasher.clone(); + + Ok(()) } /// Reset to `init()` state. @@ -167,12 +176,6 @@ pub fn init(custom: &[u8], name: Option<&[u8]>) -> Result *n_val, None => &[0u8; 0], }; - if (name_val.is_empty()) && (custom.is_empty()) { - return Err(UnknownCryptoError); - } - if name_val.len() > 65536 || custom.len() > 65536 { - return Err(UnknownCryptoError); - } // 136 is the rate of Keccak512 let mut hash = CShake { @@ -181,7 +184,7 @@ pub fn init(custom: &[u8], name: Option<&[u8]>) -> Result ([u8; 9], usize) { 8 } else { let mut tmp: usize = 0; - BigEndian::write_u64(&mut input[1..], x); + input[1..].copy_from_slice(&x.to_be_bytes()); for idx in &input { if *idx != 0 { break; @@ -210,313 +213,419 @@ fn left_encode(x: u64) -> ([u8; 9], usize) { (input, offset) } -#[test] -fn test_left_encode() { - let (test_1, offset_1) = left_encode(32); - let (test_2, offset_2) = left_encode(255); - let (test_3, offset_3) = left_encode(0); - let (test_4, offset_4) = left_encode(64); - let (test_5, offset_5) = left_encode(u64::max_value()); - - assert_eq!(&test_1[(offset_1 - 1)..], &[1, 32]); - assert_eq!(&test_2[(offset_2 - 1)..], &[1, 255]); - assert_eq!(&test_3[(offset_3 - 1)..], &[1, 0]); - assert_eq!(&test_4[(offset_4 - 1)..], &[1, 64]); - assert_eq!( - &test_5[(offset_5 - 1)..], - &[8, 255, 255, 255, 255, 255, 255, 255, 255] - ); -} +// Testing public functions in the module. +#[cfg(test)] +mod public { + use super::*; + + mod test_endianness_issue { + use super::*; + // See: https://github.com/brycx/orion/issues/15 + #[test] + #[cfg(target_endian = "little")] + fn non_8_div_len() { + let input = b"\x00\x01\x02\x03"; + let custom = b"Email Signature"; + let mut out = [0u8; 17]; + + let mut cshake = init(custom, None).unwrap(); + cshake.update(input).unwrap(); + cshake.finalize(&mut out).unwrap(); + + let expected = b"\xD0\x08\x82\x8E\x2B\x80\xAC\x9D\x22\x18\xFF\xEE\x1D\x07\x0C\x48\xB8\ + \xE4\xC8\x7B\xFF\x32\xC9\x69\x9D\x5B\x68\x96\xEE\xE0\xED\xD1\x64\x02\ + \x0E\x2B\xE0\x56\x08\x58\xD9\xC0\x0C\x03\x7E\x34\xA9\x69\x37\xC5\x61\ + \xA7\x4C\x41\x2B\xB4\xC7\x46\x46\x95\x27\x28\x1C\x8C"; -#[test] -fn err_on_empty_name_custom() { - let custom = b""; - let name = b""; + assert_eq!(expected[..17].len(), out.len()); + assert_eq!(out, &expected[..17]); + } - assert!(init(custom, Some(name)).is_err()); -} + // See: https://github.com/brycx/orion/issues/15 + #[test] + #[cfg(target_endian = "little")] + fn result_ok() { + let input = b"\x00\x01\x02\x03"; + let custom = b"Email Signature"; + let mut out = [0u8; 64]; -#[test] -fn empty_custom_ok() { - let custom = b""; - let name = b"Email signature"; + let mut cshake = init(custom, None).unwrap(); + cshake.update(input).unwrap(); + cshake.finalize(&mut out).unwrap(); - assert!(init(custom, Some(name)).is_ok()); -} + let expected = b"\xD0\x08\x82\x8E\x2B\x80\xAC\x9D\x22\x18\xFF\xEE\x1D\x07\x0C\x48\xB8\ + \xE4\xC8\x7B\xFF\x32\xC9\x69\x9D\x5B\x68\x96\xEE\xE0\xED\xD1\x64\x02\ + \x0E\x2B\xE0\x56\x08\x58\xD9\xC0\x0C\x03\x7E\x34\xA9\x69\x37\xC5\x61\ + \xA7\x4C\x41\x2B\xB4\xC7\x46\x46\x95\x27\x28\x1C\x8C"; -#[test] -fn empty_input_ok() { - let custom = b"Custom String"; - let name = b"Email signature"; + assert_eq!(out.as_ref(), expected.as_ref()); + } - assert!(init(custom, Some(name)).is_ok()); -} + // See: https://github.com/brycx/orion/issues/15 + // Detecting test-case that if tiny-keccak is fixed then this should panic + #[test] + #[cfg(target_endian = "big")] + fn result_ok_assume_wrong_on_big_endian() { + let input = b"\x00\x01\x02\x03"; + let custom = b"Email Signature"; + let mut out = [0u8; 64]; -#[test] -fn err_on_zero_length() { - let input = b"\x00\x01\x02\x03"; - let custom = b""; - let name = b"Email signature"; - let mut out = [0u8; 0]; + let mut cshake = init(custom, None).unwrap(); + cshake.update(input).unwrap(); + cshake.finalize(&mut out).unwrap(); - let mut hash = init(custom, Some(name)).unwrap(); - hash.update(input).unwrap(); - assert!(hash.finalize(&mut out).is_err()); -} + let expected = b"\xD0\x08\x82\x8E\x2B\x80\xAC\x9D\x22\x18\xFF\xEE\x1D\x07\x0C\x48\xB8\ + \xE4\xC8\x7B\xFF\x32\xC9\x69\x9D\x5B\x68\x96\xEE\xE0\xED\xD1\x64\x02\ + \x0E\x2B\xE0\x56\x08\x58\xD9\xC0\x0C\x03\x7E\x34\xA9\x69\x37\xC5\x61\ + \xA7\x4C\x41\x2B\xB4\xC7\x46\x46\x95\x27\x28\x1C\x8C"; -#[test] -fn err_on_above_max_length() { - let input = b"\x00\x01\x02\x03"; - let custom = b""; - let name = b"Email signature"; - let mut out = [0u8; 65537]; + assert_ne!(out.as_ref(), expected.as_ref()); + } - let mut hash = init(custom, Some(name)).unwrap(); - hash.update(input).unwrap(); - assert!(hash.finalize(&mut out).is_err()); -} + // See: https://github.com/brycx/orion/issues/15 + #[test] + #[cfg(target_endian = "little")] + fn verify_err() { + // `name` and `custom` values have been switched here compared to the previous + // one + let input = b"\x00\x01\x02\x03"; + let custom = b""; + let name = b"Email Signature"; + let mut out = [0u8; 64]; + + let mut cshake = init(custom, Some(name)).unwrap(); + cshake.update(input).unwrap(); + cshake.finalize(&mut out).unwrap(); + + let expected = b"\xD0\x08\x82\x8E\x2B\x80\xAC\x9D\x22\x18\xFF\xEE\x1D\x07\x0C\x48\xB8\ + \xE4\xC8\x7B\xFF\x32\xC9\x69\x9D\x5B\x68\x96\xEE\xE0\xED\xD1\x64\x02\ + \x0E\x2B\xE0\x56\x08\x58\xD9\xC0\x0C\x03\x7E\x34\xA9\x69\x37\xC5\x61\ + \xA7\x4C\x41\x2B\xB4\xC7\x46\x46\x95\x27\x28\x1C\x8C"; -#[test] -fn err_on_name_max_length() { - let custom = b""; - let name = [0u8; 65537]; + assert_ne!(out.as_ref(), expected.as_ref()); + } + } - assert!(init(custom, Some(&name)).is_err()); -} + mod test_init { + use super::*; -#[test] -fn err_on_n_c_max_length() { - let custom = [0u8; 65537]; - let name = [0u8; 65537]; + #[test] + fn err_on_empty_name_custom() { + let custom = b""; + let name = b""; - assert!(init(&custom, Some(&name)).is_err()); -} + assert!(init(custom, Some(name)).is_err()); + } -#[test] -fn err_on_custom_max_length() { - let custom = [0u8; 65537]; - let name = [0u8; 0]; + #[test] + fn empty_custom_ok() { + let custom = b""; + let name = b"Email signature"; - assert!(init(&custom, Some(&name)).is_err()); - assert!(init(&custom, None).is_err()); -} + assert!(init(custom, Some(name)).is_ok()); + } -// See: https://github.com/brycx/orion/issues/15 -#[test] -#[cfg(target_endian = "little")] -fn non_8_div_len() { - let input = b"\x00\x01\x02\x03"; - let custom = b"Email Signature"; - let mut out = [0u8; 17]; + #[test] + fn empty_input_ok() { + let custom = b"Custom String"; + let name = b"Email signature"; - let mut cshake = init(custom, None).unwrap(); - cshake.update(input).unwrap(); - cshake.finalize(&mut out).unwrap(); + assert!(init(custom, Some(name)).is_ok()); + } - let expected = b"\xD0\x08\x82\x8E\x2B\x80\xAC\x9D\x22\x18\xFF\xEE\x1D\x07\x0C\x48\xB8\ - \xE4\xC8\x7B\xFF\x32\xC9\x69\x9D\x5B\x68\x96\xEE\xE0\xED\xD1\x64\x02\ - \x0E\x2B\xE0\x56\x08\x58\xD9\xC0\x0C\x03\x7E\x34\xA9\x69\x37\xC5\x61\ - \xA7\x4C\x41\x2B\xB4\xC7\x46\x46\x95\x27\x28\x1C\x8C"; + #[test] + fn err_on_name_max_length() { + let custom = b""; + let name = [0u8; 65537]; - assert_eq!(expected[..17].len(), out.len()); - assert_eq!(out, &expected[..17]); -} + assert!(init(custom, Some(&name)).is_err()); + } -// See: https://github.com/brycx/orion/issues/15 -#[test] -#[cfg(target_endian = "little")] -fn result_ok() { - let input = b"\x00\x01\x02\x03"; - let custom = b"Email Signature"; - let mut out = [0u8; 64]; + #[test] + fn err_on_n_c_max_length() { + let custom = [0u8; 65537]; + let name = [0u8; 65537]; - let mut cshake = init(custom, None).unwrap(); - cshake.update(input).unwrap(); - cshake.finalize(&mut out).unwrap(); + assert!(init(&custom, Some(&name)).is_err()); + } - let expected = b"\xD0\x08\x82\x8E\x2B\x80\xAC\x9D\x22\x18\xFF\xEE\x1D\x07\x0C\x48\xB8\ - \xE4\xC8\x7B\xFF\x32\xC9\x69\x9D\x5B\x68\x96\xEE\xE0\xED\xD1\x64\x02\ - \x0E\x2B\xE0\x56\x08\x58\xD9\xC0\x0C\x03\x7E\x34\xA9\x69\x37\xC5\x61\ - \xA7\x4C\x41\x2B\xB4\xC7\x46\x46\x95\x27\x28\x1C\x8C"; + #[test] + fn err_on_custom_max_length() { + let custom = [0u8; 65537]; + let name = [0u8; 0]; - assert_eq!(out.as_ref(), expected.as_ref()); -} + assert!(init(&custom, Some(&name)).is_err()); + assert!(init(&custom, None).is_err()); + } + } -// See: https://github.com/brycx/orion/issues/15 -// Detecting test-case that if tiny-keccak is fixed then this should panic -#[test] -#[cfg(target_endian = "big")] -fn result_ok_assume_wrong_on_big_endian() { - let input = b"\x00\x01\x02\x03"; - let custom = b"Email Signature"; - let mut out = [0u8; 64]; + mod test_reset { + use super::*; + + #[test] + fn double_reset_ok() { + let input = b"\x00\x01\x02\x03"; + let custom = b""; + let name = b"Email Signature"; + let mut out = [0u8; 64]; + + let mut cshake = init(custom, Some(name)).unwrap(); + cshake.update(input).unwrap(); + cshake.finalize(&mut out).unwrap(); + cshake.reset(); + cshake.reset(); + } + } - let mut cshake = init(custom, None).unwrap(); - cshake.update(input).unwrap(); - cshake.finalize(&mut out).unwrap(); + mod test_update { + use super::*; + + #[test] + /// Related bug: https://github.com/brycx/orion/issues/28 + fn update_after_finalize_err() { + let input = b"\x00\x01\x02\x03"; + let custom = b""; + let name = b"Email Signature"; + let mut out = [0u8; 64]; + + let mut cshake = init(custom, Some(name)).unwrap(); + cshake.update(input).unwrap(); + cshake.finalize(&mut out).unwrap(); + assert!(cshake.update(input).is_err()); + } - let expected = b"\xD0\x08\x82\x8E\x2B\x80\xAC\x9D\x22\x18\xFF\xEE\x1D\x07\x0C\x48\xB8\ - \xE4\xC8\x7B\xFF\x32\xC9\x69\x9D\x5B\x68\x96\xEE\xE0\xED\xD1\x64\x02\ - \x0E\x2B\xE0\x56\x08\x58\xD9\xC0\x0C\x03\x7E\x34\xA9\x69\x37\xC5\x61\ - \xA7\x4C\x41\x2B\xB4\xC7\x46\x46\x95\x27\x28\x1C\x8C"; + #[test] + fn update_after_finalize_with_reset_ok() { + let input = b"\x00\x01\x02\x03"; + let custom = b""; + let name = b"Email Signature"; + let mut out = [0u8; 64]; + let mut out_check = [0u8; 64]; + + let mut cshake = init(custom, Some(name)).unwrap(); + cshake.update(input).unwrap(); + cshake.finalize(&mut out).unwrap(); + cshake.reset(); + cshake.update(input).unwrap(); + cshake.finalize(&mut out_check).unwrap(); + + assert_eq!(out.as_ref(), out_check.as_ref()); + } + } - assert_ne!(out.as_ref(), expected.as_ref()); -} + mod test_finalize { + use super::*; -// See: https://github.com/brycx/orion/issues/15 -#[test] -#[cfg(target_endian = "little")] -fn verify_err() { - // `name` and `custom` values have been switched here compared to the previous - // one - let input = b"\x00\x01\x02\x03"; - let custom = b""; - let name = b"Email Signature"; - let mut out = [0u8; 64]; - - let mut cshake = init(custom, Some(name)).unwrap(); - cshake.update(input).unwrap(); - cshake.finalize(&mut out).unwrap(); - - let expected = b"\xD0\x08\x82\x8E\x2B\x80\xAC\x9D\x22\x18\xFF\xEE\x1D\x07\x0C\x48\xB8\ - \xE4\xC8\x7B\xFF\x32\xC9\x69\x9D\x5B\x68\x96\xEE\xE0\xED\xD1\x64\x02\ - \x0E\x2B\xE0\x56\x08\x58\xD9\xC0\x0C\x03\x7E\x34\xA9\x69\x37\xC5\x61\ - \xA7\x4C\x41\x2B\xB4\xC7\x46\x46\x95\x27\x28\x1C\x8C"; + #[test] + fn err_on_zero_length() { + let input = b"\x00\x01\x02\x03"; + let custom = b""; + let name = b"Email signature"; + let mut out = [0u8; 0]; - assert_ne!(out.as_ref(), expected.as_ref()); -} + let mut hash = init(custom, Some(name)).unwrap(); + hash.update(input).unwrap(); + assert!(hash.finalize(&mut out).is_err()); + } -#[test] -fn double_finalize_err() { - let input = b"\x00\x01\x02\x03"; - let custom = b""; - let name = b"Email Signature"; - let mut out = [0u8; 64]; - - let mut cshake = init(custom, Some(name)).unwrap(); - cshake.update(input).unwrap(); - cshake.finalize(&mut out).unwrap(); - assert!(cshake.finalize(&mut out).is_err()); -} + #[test] + fn err_on_above_max_length() { + let input = b"\x00\x01\x02\x03"; + let custom = b""; + let name = b"Email signature"; + let mut out = [0u8; 65537]; -#[test] -fn double_finalize_with_reset_ok() { - let input = b"\x00\x01\x02\x03"; - let custom = b""; - let name = b"Email Signature"; - let mut out = [0u8; 64]; - - let mut cshake = init(custom, Some(name)).unwrap(); - cshake.update(input).unwrap(); - cshake.finalize(&mut out).unwrap(); - cshake.reset(); - cshake.update(input).unwrap(); - cshake.finalize(&mut out).unwrap(); -} + let mut hash = init(custom, Some(name)).unwrap(); + hash.update(input).unwrap(); + assert!(hash.finalize(&mut out).is_err()); + } -#[test] -fn double_finalize_with_reset_no_update_ok() { - let input = b"\x00\x01\x02\x03"; - let custom = b""; - let name = b"Email Signature"; - let mut out = [0u8; 64]; - - let mut cshake = init(custom, Some(name)).unwrap(); - cshake.update(input).unwrap(); - cshake.finalize(&mut out).unwrap(); - cshake.reset(); - cshake.finalize(&mut out).unwrap(); -} + #[test] + fn double_finalize_err() { + let input = b"\x00\x01\x02\x03"; + let custom = b""; + let name = b"Email Signature"; + let mut out = [0u8; 64]; + + let mut cshake = init(custom, Some(name)).unwrap(); + cshake.update(input).unwrap(); + cshake.finalize(&mut out).unwrap(); + assert!(cshake.finalize(&mut out).is_err()); + } -#[test] -fn update_after_finalize_err() { - let input = b"\x00\x01\x02\x03"; - let custom = b""; - let name = b"Email Signature"; - let mut out = [0u8; 64]; - - let mut cshake = init(custom, Some(name)).unwrap(); - cshake.update(input).unwrap(); - cshake.finalize(&mut out).unwrap(); - assert!(cshake.update(input).is_err()); -} + #[test] + fn double_finalize_with_reset_ok() { + let input = b"\x00\x01\x02\x03"; + let custom = b""; + let name = b"Email Signature"; + let mut out = [0u8; 64]; + + let mut cshake = init(custom, Some(name)).unwrap(); + cshake.update(input).unwrap(); + cshake.finalize(&mut out).unwrap(); + cshake.reset(); + cshake.update(input).unwrap(); + cshake.finalize(&mut out).unwrap(); + } -#[test] -fn update_after_finalize_with_reset_ok() { - let input = b"\x00\x01\x02\x03"; - let custom = b""; - let name = b"Email Signature"; - let mut out = [0u8; 64]; - let mut out_check = [0u8; 64]; - - let mut cshake = init(custom, Some(name)).unwrap(); - cshake.update(input).unwrap(); - cshake.finalize(&mut out).unwrap(); - cshake.reset(); - cshake.update(input).unwrap(); - cshake.finalize(&mut out_check).unwrap(); - - assert_eq!(out.as_ref(), out_check.as_ref()); -} + #[test] + fn double_finalize_with_reset_no_update_ok() { + let input = b"\x00\x01\x02\x03"; + let custom = b""; + let name = b"Email Signature"; + let mut out = [0u8; 64]; + + let mut cshake = init(custom, Some(name)).unwrap(); + cshake.update(input).unwrap(); + cshake.finalize(&mut out).unwrap(); + cshake.reset(); + cshake.finalize(&mut out).unwrap(); + } + + } + + #[cfg(feature = "safe_api")] + // Mark safe_api because currently it only contains proptests and tests that + // need vec![]. + mod test_streaming_interface { + use super::*; + + #[cfg(feature = "safe_api")] + /// Related bug: https://github.com/brycx/orion/issues/46 + /// Testing different usage combinations of init(), update(), + /// finalize() and reset() produce the same output. + /// Cannot be on no_std due to use of Vec. + fn produces_same_out(input: &[u8], custom: &[u8], name: &[u8], outsize: usize) { + let custom_checked = if custom.is_empty() && name.is_empty() { + b"Hello world" + } else { + custom + }; + + let outsize_checked = if outsize == 0 || outsize > 65536 { + 64 + } else { + outsize + }; + + // init(), update(), finalize() + let mut state_1 = init(custom_checked, Some(name)).unwrap(); + state_1.update(input).unwrap(); + let mut res_1 = vec![0u8; outsize_checked]; + state_1.finalize(&mut res_1).unwrap(); + + // init(), reset(), update(), finalize() + let mut state_2 = init(custom_checked, Some(name)).unwrap(); + state_2.reset(); + state_2.update(input).unwrap(); + let mut res_2 = vec![0u8; outsize_checked]; + state_2.finalize(&mut res_2).unwrap(); + + // init(), update(), reset(), update(), finalize() + let mut state_3 = init(custom_checked, Some(name)).unwrap(); + state_3.update(input).unwrap(); + state_3.reset(); + state_3.update(input).unwrap(); + let mut res_3 = vec![0u8; outsize_checked]; + state_3.finalize(&mut res_3).unwrap(); + + // init(), update(), finalize(), reset(), update(), finalize() + let mut state_4 = init(custom_checked, Some(name)).unwrap(); + state_4.update(input).unwrap(); + let mut res_4 = vec![0u8; outsize_checked]; + state_4.finalize(&mut res_4).unwrap(); + state_4.reset(); + state_4.update(input).unwrap(); + state_4.finalize(&mut res_4).unwrap(); + + assert_eq!(res_1, res_2); + assert_eq!(res_2, res_3); + assert_eq!(res_3, res_4); + } -#[test] -fn double_reset_ok() { - let input = b"\x00\x01\x02\x03"; - let custom = b""; - let name = b"Email Signature"; - let mut out = [0u8; 64]; - - let mut cshake = init(custom, Some(name)).unwrap(); - cshake.update(input).unwrap(); - cshake.finalize(&mut out).unwrap(); - cshake.reset(); - cshake.reset(); + #[test] + #[cfg(feature = "safe_api")] + // Test for issues when incrementally processing data. + fn test_streaming_consistency() { + for len in 0..128 * 4 { + let input = vec![0u8; len]; + let custom = b"Email Signature"; + let mut out = vec![0u8; len + 1]; + + let mut state = init(custom, None).unwrap(); + let mut other_data: Vec = Vec::new(); + + other_data.extend_from_slice(&input); + state.update(&input).unwrap(); + + if input.len() > 128 { + other_data.extend_from_slice(b""); + state.update(b"").unwrap(); + } + if input.len() > 128 * 2 { + other_data.extend_from_slice(b"Extra"); + state.update(b"Extra").unwrap(); + } + if input.len() > 128 * 3 { + other_data.extend_from_slice(&[0u8; 256]); + state.update(&[0u8; 256]).unwrap(); + } + + state.finalize(&mut out).unwrap(); + + let mut non_incremental = init(custom, None).unwrap(); + non_incremental.update(&other_data[..]).unwrap(); + let mut out_non_incremental = vec![0u8; len + 1]; + non_incremental.finalize(&mut out_non_incremental).unwrap(); + + assert_eq!(out, out_non_incremental); + } + } + // Proptests. Only exectued when NOT testing no_std. + #[cfg(feature = "safe_api")] + mod proptest { + use super::*; + + quickcheck! { + /// Related bug: https://github.com/brycx/orion/issues/46 + /// Test different streaming state usage patterns. + fn prop_same_result_different_usage(input: Vec, custom: Vec, name: Vec, outsize: usize) -> bool { + // Will panic on incorrect results. + produces_same_out(&input[..], &custom[..], &name[..], outsize); + + true + } + } + } + } } -#[test] -fn reset_after_update_correct_resets() { - let input = b"\x00\x01\x02\x03"; - let custom = b"Hello world"; - let name = b"Email Signature"; - let mut out = [0u8; 64]; - let mut out2 = [0u8; 64]; - - // With optional name paramter and non-empty custom - let mut cshake = init(custom, Some(name)).unwrap(); - cshake.update(input).unwrap(); - cshake.finalize(&mut out).unwrap(); - - let mut cshake2 = init(custom, Some(name)).unwrap(); - cshake2.update(input).unwrap(); - cshake2.reset(); - cshake2.update(input).unwrap(); - cshake2.finalize(&mut out2).unwrap(); - - assert!(out[..] == out2[..]); - - // With optional name paramter and empty custom - let mut cshake = init(b"", Some(name)).unwrap(); - cshake.update(input).unwrap(); - cshake.finalize(&mut out).unwrap(); - - let mut cshake2 = init(b"", Some(name)).unwrap(); - cshake2.update(input).unwrap(); - cshake2.reset(); - cshake2.update(input).unwrap(); - cshake2.finalize(&mut out2).unwrap(); - - assert!(out[..] == out2[..]); - - // Without optional name parameter - let mut cshake = init(custom, None).unwrap(); - cshake.update(input).unwrap(); - cshake.finalize(&mut out).unwrap(); - - let mut cshake2 = init(custom, None).unwrap(); - cshake2.update(input).unwrap(); - cshake2.reset(); - cshake2.update(input).unwrap(); - cshake2.finalize(&mut out2).unwrap(); - - assert!(out[..] == out2[..]); +// Testing private functions in the module. +#[cfg(test)] +mod private { + use super::*; + + // One function tested per submodule. + + mod test_left_encode { + use super::*; + + #[test] + fn test_left_encode() { + let (test_1, offset_1) = left_encode(32); + let (test_2, offset_2) = left_encode(255); + let (test_3, offset_3) = left_encode(0); + let (test_4, offset_4) = left_encode(64); + let (test_5, offset_5) = left_encode(u64::max_value()); + + assert_eq!(&test_1[(offset_1 - 1)..], &[1, 32]); + assert_eq!(&test_2[(offset_2 - 1)..], &[1, 255]); + assert_eq!(&test_3[(offset_3 - 1)..], &[1, 0]); + assert_eq!(&test_4[(offset_4 - 1)..], &[1, 64]); + assert_eq!( + &test_5[(offset_5 - 1)..], + &[8, 255, 255, 255, 255, 255, 255, 255, 255] + ); + } + } } diff --git a/src/kdf.rs b/src/kdf.rs index 99784a2b..205902aa 100644 --- a/src/kdf.rs +++ b/src/kdf.rs @@ -129,32 +129,130 @@ pub fn derive_key_verify( Ok(is_good) } -#[test] -fn derive_key_and_verify() { - let password = Password::from_slice(&[0u8; 64]).unwrap(); - let salt = Salt::from_slice(&[0u8; 64]).unwrap(); +// Testing public functions in the module. +#[cfg(test)] +mod public { + use super::*; - let dk = derive_key(&password, &salt, 100, 64).unwrap(); + mod test_derive_key_and_verify { + use super::*; + #[test] + fn test_derive_key_and_verify() { + let password = Password::from_slice(&[0u8; 64]).unwrap(); + let salt = Salt::from_slice(&[0u8; 64]).unwrap(); - assert!(derive_key_verify(&dk, &password, &salt, 100).unwrap()); -} + let dk = derive_key(&password, &salt, 100, 64).unwrap(); -#[test] -fn derive_key_and_verify_err() { - let password = Password::from_slice(&[0u8; 64]).unwrap(); - let salt = Salt::from_slice(&[0u8; 64]).unwrap(); + assert!(derive_key_verify(&dk, &password, &salt, 100).unwrap()); + } - let dk = derive_key(&password, &salt, 100, 64).unwrap(); + #[test] + fn test_derive_key_and_verify_err() { + let password = Password::from_slice(&[0u8; 64]).unwrap(); + let salt = Salt::from_slice(&[0u8; 64]).unwrap(); - assert!(derive_key_verify(&dk, &password, &salt, 50).is_err()); -} + let dk = derive_key(&password, &salt, 100, 64).unwrap(); + + assert!(derive_key_verify(&dk, &password, &salt, 50).is_err()); + } + + #[test] + fn test_derive_key_bad_length() { + let password = Password::from_slice(&[0u8; 64]).unwrap(); + let salt = Salt::from_slice(&[0u8; 64]).unwrap(); + + assert!(derive_key(&password, &salt, 100, 0).is_err()); + assert!(derive_key(&password, &salt, 100, 1).is_ok()); + assert!(derive_key(&password, &salt, 100, usize::max_value()).is_err()); + } + } + + // Proptests. Only exectued when NOT testing no_std. + #[cfg(feature = "safe_api")] + mod proptest { + use super::*; + + quickcheck! { + /// Deriving a key and verifying with the same parameters should always be true. + fn prop_derive_key_verify(input: Vec, size: usize) -> bool { + let passin = if input.is_empty() { + vec![1u8; 10] + } else { + input + }; + + let size_checked = if size == 0 { + 32 + } else { + size + }; -#[test] -fn derive_key_bad_length() { - let password = Password::from_slice(&[0u8; 64]).unwrap(); - let salt = Salt::from_slice(&[0u8; 64]).unwrap(); + let pass = Password::from_slice(&passin[..]).unwrap(); + let salt = Salt::default(); + let derived_key = derive_key(&pass, &salt, 100, size_checked).unwrap(); - assert!(derive_key(&password, &salt, 100, 0).is_err()); - assert!(derive_key(&password, &salt, 100, 1).is_ok()); - assert!(derive_key(&password, &salt, 100, usize::max_value()).is_err()); + if derive_key_verify(&derived_key, &pass, &salt, 100).is_ok() { + true + } else { + false + } + } + } + + quickcheck! { + /// Deriving a key and verifying with a different password should always be false. + fn prop_derive_key_verify_false_bad_password(input: Vec, size: usize) -> bool { + let passin = if input.is_empty() { + vec![1u8; 10] + } else { + input + }; + + let size_checked = if size == 0 { + 32 + } else { + size + }; + + let pass = Password::from_slice(&passin[..]).unwrap(); + let salt = Salt::default(); + let derived_key = derive_key(&pass, &salt, 100, size_checked).unwrap(); + let bad_pass = Password::generate(32).unwrap(); + + if derive_key_verify(&derived_key, &bad_pass, &salt, 100).is_err() { + true + } else { + false + } + } + } + + quickcheck! { + /// Deriving a key and verifying with a different salt should always be false. + fn prop_derive_key_verify_false_bad_salt(input: Vec, size: usize) -> bool { + let passin = if input.is_empty() { + vec![1u8; 10] + } else { + input + }; + + let size_checked = if size == 0 { + 32 + } else { + size + }; + + let pass = Password::from_slice(&passin[..]).unwrap(); + let salt = Salt::default(); + let derived_key = derive_key(&pass, &salt, 100, size_checked).unwrap(); + let bad_salt = Salt::default(); + + if derive_key_verify(&derived_key, &pass, &bad_salt, 100).is_err() { + true + } else { + false + } + } + } + } } diff --git a/src/lib.rs b/src/lib.rs index 703a5be2..c0502e70 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -56,6 +56,11 @@ extern crate rand_os; extern crate subtle; extern crate tiny_keccak; +#[cfg(test)] +#[cfg(feature = "safe_api")] +#[macro_use] +extern crate quickcheck; + #[macro_use] mod typedefs; diff --git a/src/pwhash.rs b/src/pwhash.rs index 5e313148..1156a83f 100644 --- a/src/pwhash.rs +++ b/src/pwhash.rs @@ -124,57 +124,114 @@ pub fn hash_password_verify( Ok(is_good) } -#[test] -fn pbkdf2_verify() { - let password = Password::from_slice(&[0u8; 64]).unwrap(); - - let pbkdf2_dk = hash_password(&password, 100).unwrap(); - - assert_eq!( - hash_password_verify(&pbkdf2_dk, &password, 100).unwrap(), - true - ); -} - -#[test] -fn pbkdf2_verify_err_modified_salt() { - let password = Password::from_slice(&[0u8; 64]).unwrap(); - - let pbkdf2_dk = hash_password(&password, 100).unwrap(); - let mut pwd_mod = pbkdf2_dk.unprotected_as_bytes().to_vec(); - pwd_mod[0..32].copy_from_slice(&[0u8; 32]); - let modified = PasswordHash::from_slice(&pwd_mod).unwrap(); - - assert!(hash_password_verify(&modified, &password, 100).is_err()); -} - -#[test] -fn pbkdf2_verify_err_modified_password() { - let password = Password::from_slice(&[0u8; 64]).unwrap(); - - let pbkdf2_dk = hash_password(&password, 100).unwrap(); - let mut pwd_mod = pbkdf2_dk.unprotected_as_bytes().to_vec(); - pwd_mod[120..128].copy_from_slice(&[0u8; 8]); - let modified = PasswordHash::from_slice(&pwd_mod).unwrap(); - - assert!(hash_password_verify(&modified, &password, 100).is_err()); -} - -#[test] -fn pbkdf2_verify_err_modified_salt_and_password() { - let password = Password::from_slice(&[0u8; 64]).unwrap(); - - let pbkdf2_dk = hash_password(&password, 100).unwrap(); - let mut pwd_mod = pbkdf2_dk.unprotected_as_bytes().to_vec(); - pwd_mod[64..96].copy_from_slice(&[0u8; 32]); - let modified = PasswordHash::from_slice(&pwd_mod).unwrap(); - - assert!(hash_password_verify(&modified, &password, 100).is_err()); -} - -#[test] -fn pbkdf2_zero_iterations() { - let password = Password::from_slice(&[0u8; 64]).unwrap(); - - assert!(hash_password(&password, 0).is_err()); +// Testing public functions in the module. +#[cfg(test)] +mod public { + use super::*; + + mod test_pwhash_and_verify { + use super::*; + + #[test] + fn test_pbkdf2_verify() { + let password = Password::from_slice(&[0u8; 64]).unwrap(); + + let pbkdf2_dk = hash_password(&password, 100).unwrap(); + + assert_eq!( + hash_password_verify(&pbkdf2_dk, &password, 100).unwrap(), + true + ); + } + + #[test] + fn test_pbkdf2_verify_err_modified_salt() { + let password = Password::from_slice(&[0u8; 64]).unwrap(); + + let pbkdf2_dk = hash_password(&password, 100).unwrap(); + let mut pwd_mod = pbkdf2_dk.unprotected_as_bytes().to_vec(); + pwd_mod[0..32].copy_from_slice(&[0u8; 32]); + let modified = PasswordHash::from_slice(&pwd_mod).unwrap(); + + assert!(hash_password_verify(&modified, &password, 100).is_err()); + } + + #[test] + fn test_pbkdf2_verify_err_modified_password() { + let password = Password::from_slice(&[0u8; 64]).unwrap(); + + let pbkdf2_dk = hash_password(&password, 100).unwrap(); + let mut pwd_mod = pbkdf2_dk.unprotected_as_bytes().to_vec(); + pwd_mod[120..128].copy_from_slice(&[0u8; 8]); + let modified = PasswordHash::from_slice(&pwd_mod).unwrap(); + + assert!(hash_password_verify(&modified, &password, 100).is_err()); + } + + #[test] + fn test_pbkdf2_verify_err_modified_salt_and_password() { + let password = Password::from_slice(&[0u8; 64]).unwrap(); + + let pbkdf2_dk = hash_password(&password, 100).unwrap(); + let mut pwd_mod = pbkdf2_dk.unprotected_as_bytes().to_vec(); + pwd_mod[64..96].copy_from_slice(&[0u8; 32]); + let modified = PasswordHash::from_slice(&pwd_mod).unwrap(); + + assert!(hash_password_verify(&modified, &password, 100).is_err()); + } + + #[test] + fn test_pbkdf2_zero_iterations() { + let password = Password::from_slice(&[0u8; 64]).unwrap(); + + assert!(hash_password(&password, 0).is_err()); + } + } + + // Proptests. Only exectued when NOT testing no_std. + #[cfg(feature = "safe_api")] + mod proptest { + use super::*; + + quickcheck! { + /// Hashing and verifying the same password should always be true. + fn prop_pwhash_verify(input: Vec) -> bool { + let passin = if input.is_empty() { + vec![1u8; 10] + } else { + input + }; + + let pass = Password::from_slice(&passin[..]).unwrap(); + let pass_hash = hash_password(&pass, 100).unwrap(); + + if hash_password_verify(&pass_hash, &pass, 100).is_ok() { + true + } else { + false + } + } + } + + quickcheck! { + /// Hashing and verifying different passwords should always be false. + fn prop_pwhash_verify_false(input: Vec) -> bool { + let passin = if input.is_empty() { + vec![1u8; 10] + } else { + input + }; + + let pass = Password::from_slice(&passin[..]).unwrap(); + let pass_hash = hash_password(&pass, 100).unwrap(); + let bad_pass = Password::generate(32).unwrap(); + + if hash_password_verify(&pass_hash, &bad_pass, 100).is_err() { + true + } else { + false + } + } + } + } } diff --git a/src/typedefs.rs b/src/typedefs.rs index 53afc624..291fef87 100644 --- a/src/typedefs.rs +++ b/src/typedefs.rs @@ -42,7 +42,7 @@ macro_rules! impl_default_trait (($name:ident, $size:expr) => ( /// Macro that implements the `PartialEq` trait on a object called `$name` that /// also implements `unprotected_as_bytes()`. This `PartialEq` will perform in /// constant time. -macro_rules! impl_partialeq_trait (($name:ident) => ( +macro_rules! impl_ct_partialeq_trait (($name:ident) => ( impl PartialEq for $name { fn eq(&self, other: &$name) -> bool { use subtle::ConstantTimeEq; @@ -53,10 +53,21 @@ macro_rules! impl_partialeq_trait (($name:ident) => ( } )); +/// Macro that implements the `PartialEq` trait on a object called `$name` that +/// also implements `as_bytes()`. This `PartialEq` will NOT perform in +/// constant time. +macro_rules! impl_normal_partialeq_trait (($name:ident) => ( + impl PartialEq for $name { + fn eq(&self, other: &$name) -> bool { + (&self.as_bytes() == &other.as_bytes()) + } + } +)); + /// Macro that implements the `Debug` trait on a object called `$name`. /// This `Debug` will omit any fields of object `$name` to avoid them being /// written to logs. -macro_rules! impl_debug_trait (($name:ident) => ( +macro_rules! impl_omitted_debug_trait (($name:ident) => ( impl core::fmt::Debug for $name { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { write!(f, "{} {{***OMITTED***}}", stringify!($name)) @@ -64,6 +75,17 @@ macro_rules! impl_debug_trait (($name:ident) => ( } )); +/// Macro that implements the `Debug` trait on a object called `$name`. +/// This `Debug` will omit any fields of object `$name` to avoid them being +/// written to logs. +macro_rules! impl_normal_debug_trait (($name:ident) => ( + impl core::fmt::Debug for $name { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "{} {:?}", stringify!($name), &self.value[..]) + } + } +)); + /// Macro that implements the `Drop` trait on a object called `$name` which as a /// field `value`. This `Drop` will zero out the field `value` when the objects /// destructor is called. WARNING: This requires value to be an array as @@ -111,6 +133,22 @@ macro_rules! func_from_slice (($name:ident, $size:expr) => ( } )); +#[cfg(feature = "safe_api")] +/// Macro to implement a `from_slice()` function. Returns `UnknownCryptoError` +/// if the slice is not of length `$size`. +macro_rules! func_from_slice_variable_size (($name:ident) => ( + #[must_use] + #[cfg(feature = "safe_api")] + /// Make an object from a given byte slice. + pub fn from_slice(slice: &[u8]) -> Result<$name, UnknownCryptoError> { + if slice.is_empty() { + return Err(UnknownCryptoError); + } + + Ok($name { value: Vec::from(slice) }) + } +)); + /// Macro to implement a `unprotected_as_bytes()` function for objects that /// implement extra protections. Typically used on objects that implement /// `Drop`, `Debug` and/or `PartialEq`. @@ -157,6 +195,26 @@ macro_rules! func_generate (($name:ident, $size:expr) => ( } )); +#[cfg(feature = "safe_api")] +/// Macro to implement a `generate()` function for objects that benefit from +/// having a CSPRNG available to generate data of a variable length. +macro_rules! func_generate_variable_size (($name:ident) => ( + #[must_use] + #[cfg(feature = "safe_api")] + /// Randomly generate using a CSPRNG. Not available in `no_std` context. + pub fn generate(length: usize) -> Result<$name, UnknownCryptoError> { + use crate::util; + if length < 1 || length >= (u32::max_value() as usize) { + return Err(UnknownCryptoError); + } + + let mut value = vec![0u8; length]; + util::secure_rand_bytes(&mut value)?; + + Ok($name { value: value }) + } +)); + /// Macro to construct a type containing sensitive data, using a fixed-size /// array. macro_rules! construct_secret_key { @@ -170,9 +228,9 @@ macro_rules! construct_secret_key { /// that the type implements. pub struct $name { value: [u8; $size] } - impl_debug_trait!($name); + impl_omitted_debug_trait!($name); impl_drop_stack_trait!($name); - impl_partialeq_trait!($name); + impl_ct_partialeq_trait!($name); impl $name { func_from_slice!($name, $size); @@ -183,16 +241,44 @@ macro_rules! construct_secret_key { #[test] fn test_key_size() { - // We don't test above $size here in case it's passed as a `max_value()` assert!($name::from_slice(&[0u8; $size]).is_ok()); - assert!($name::from_slice(&[0u8; $size - $size]).is_err()); + assert!($name::from_slice(&[0u8; 0]).is_err()); assert!($name::from_slice(&[0u8; $size - 1]).is_err()); + assert!($name::from_slice(&[0u8; $size + 1]).is_err()); } #[test] fn test_unprotected_as_bytes_secret_key() { let test = $name::from_slice(&[0u8; $size]).unwrap(); + assert!(test.unprotected_as_bytes() == &[0u8; $size]); assert!(test.unprotected_as_bytes().len() == $size); } + + #[test] + fn test_get_length_secret_key() { + let test = $name::from_slice(&[0u8; $size]).unwrap(); + assert!(test.unprotected_as_bytes().len() == test.get_length()); + assert!($size == test.get_length()); + } + + #[test] + #[cfg(feature = "safe_api")] + fn test_generate_secret_key() { + let test_zero = $name::from_slice(&[0u8; $size]).unwrap(); + // A random one should never be all 0's. + let test_rand = $name::generate().unwrap(); + assert!(test_zero != test_rand); + // A random generated one should always be $size in length. + assert!(test_rand.get_length() == $size); + } + + #[test] + #[cfg(feature = "safe_api")] + // format! is only available with std + fn test_omitted_debug_secret_key() { + let secret = format!("{:?}", [0u8; $size].as_ref()); + let test_debug_contents = format!("{:?}", $name::from_slice(&[0u8; $size]).unwrap()); + assert_eq!(test_debug_contents.contains(&secret), false); + } ); } @@ -204,29 +290,33 @@ macro_rules! construct_nonce_no_generator { $(#[$meta])* pub struct $name { value: [u8; $size] } + impl_normal_debug_trait!($name); + impl_normal_partialeq_trait!($name); + impl $name { func_from_slice!($name, $size); func_as_bytes!(); func_get_length!(); } - impl core::fmt::Debug for $name { - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - write!(f, "{} {:?}", stringify!($name), &self.value) - } - } - #[test] fn test_nonce_size() { - // We don't test above $size here in case it's passed as a `max_value()` assert!($name::from_slice(&[0u8; $size]).is_ok()); assert!($name::from_slice(&[0u8; $size - $size]).is_err()); - assert!($name::from_slice(&[0u8; $size - 1]).is_err()); + assert!($name::from_slice(&[0u8; $size + 1]).is_err()); } #[test] fn test_as_bytes_nonce_no_gen() { let test = $name::from_slice(&[0u8; $size]).unwrap(); assert!(test.as_bytes().len() == $size); + assert!(test.as_bytes() == &[0u8; $size]); + } + + #[test] + fn test_get_length_nonce_no_gen() { + let test = $name::from_slice(&[0u8; $size]).unwrap(); + assert!(test.as_bytes().len() == test.get_length()); + assert!($size == test.get_length()); } ); } @@ -239,6 +329,9 @@ macro_rules! construct_nonce_with_generator { $(#[$meta])* pub struct $name { value: [u8; $size] } + impl_normal_debug_trait!($name); + impl_normal_partialeq_trait!($name); + impl $name { func_from_slice!($name, $size); func_as_bytes!(); @@ -246,23 +339,35 @@ macro_rules! construct_nonce_with_generator { func_get_length!(); } - impl core::fmt::Debug for $name { - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - write!(f, "{} {:?}", stringify!($name), &self.value) - } - } - #[test] fn test_nonce_size() { - // We don't test above $size here in case it's passed as a `max_value()` assert!($name::from_slice(&[0u8; $size]).is_ok()); assert!($name::from_slice(&[0u8; $size - $size]).is_err()); - assert!($name::from_slice(&[0u8; $size - 1]).is_err()); + assert!($name::from_slice(&[0u8; $size + 1]).is_err()); } #[test] fn test_as_bytes_nonce_with_gen() { let test = $name::from_slice(&[0u8; $size]).unwrap(); assert!(test.as_bytes().len() == $size); + assert!(test.as_bytes() == &[0u8; $size]); + } + + #[test] + fn test_get_length_nonce_with_gen() { + let test = $name::from_slice(&[0u8; $size]).unwrap(); + assert!(test.as_bytes().len() == test.get_length()); + assert!($size == test.get_length()); + } + + #[test] + #[cfg(feature = "safe_api")] + fn test_generate_nonce() { + let test_zero = $name::from_slice(&[0u8; $size]).unwrap(); + // A random one should never be all 0's. + let test_rand = $name::generate().unwrap(); + assert!(test_zero != test_rand); + // A random generated one should always be $size in length. + assert!(test_rand.get_length() == $size); } ); } @@ -280,7 +385,8 @@ macro_rules! construct_tag { /// that the type implements. pub struct $name { value: [u8; $size] } - impl_partialeq_trait!($name); + impl_omitted_debug_trait!($name); + impl_ct_partialeq_trait!($name); impl $name { func_from_slice!($name, $size); @@ -288,23 +394,33 @@ macro_rules! construct_tag { func_get_length!(); } - impl core::fmt::Debug for $name { - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - write!(f, "{} {:?}", stringify!($name), &self.value[..]) - } - } - #[test] fn test_tag_size() { - // We don't test above $size here in case it's passed as a `max_value()` assert!($name::from_slice(&[0u8; $size]).is_ok()); assert!($name::from_slice(&[0u8; $size - $size]).is_err()); - assert!($name::from_slice(&[0u8; $size - 1]).is_err()); + assert!($name::from_slice(&[0u8; $size + 1]).is_err()); } #[test] fn test_unprotected_as_bytes_tag() { let test = $name::from_slice(&[0u8; $size]).unwrap(); assert!(test.unprotected_as_bytes().len() == $size); + assert!(test.unprotected_as_bytes() == [0u8; $size].as_ref()); + } + + #[test] + fn test_get_length_tag() { + let test = $name::from_slice(&[0u8; $size]).unwrap(); + assert!(test.unprotected_as_bytes().len() == test.get_length()); + assert!($size == test.get_length()); + } + + #[test] + #[cfg(feature = "safe_api")] + // format! is only available with std + fn test_omitted_debug_tag() { + let secret = format!("{:?}", [0u8; $size].as_ref()); + let test_debug_contents = format!("{:?}", $name::from_slice(&[0u8; $size]).unwrap()); + assert_eq!(test_debug_contents.contains(&secret), false); } ); } @@ -322,9 +438,9 @@ macro_rules! construct_hmac_key { /// that the type implements. pub struct $name { value: [u8; $size] } - impl_debug_trait!($name); + impl_omitted_debug_trait!($name); impl_drop_stack_trait!($name); - impl_partialeq_trait!($name); + impl_ct_partialeq_trait!($name); impl $name { #[must_use] @@ -353,15 +469,43 @@ macro_rules! construct_hmac_key { #[test] fn test_key_size() { - // We don't test above $size here in case it's passed as a `max_value()` - let _ = $name::from_slice(&[0u8; $size]).unwrap(); - let _ = $name::from_slice(&[0u8; $size - $size]).unwrap(); - let _ = $name::from_slice(&[0u8; $size - 1]).unwrap(); + assert!($name::from_slice(&[0u8; $size]).is_ok()); + assert!($name::from_slice(&[0u8; $size - $size]).is_ok()); + assert!($name::from_slice(&[0u8; $size + 1]).is_ok()); } + #[test] fn test_unprotected_as_bytes_hmac_key() { let test = $name::from_slice(&[0u8; $size]).unwrap(); assert!(test.unprotected_as_bytes().len() == $size); + assert!(test.unprotected_as_bytes() == [0u8; $size].as_ref()); + } + + #[test] + fn test_get_length_hmac_key() { + let test = $name::from_slice(&[0u8; $size]).unwrap(); + assert!(test.unprotected_as_bytes().len() == test.get_length()); + assert!($size == test.get_length()); + } + + #[test] + #[cfg(feature = "safe_api")] + fn test_generate_hmac() { + let test_zero = $name::from_slice(&[0u8; $size]).unwrap(); + // A random one should never be all 0's. + let test_rand = $name::generate().unwrap(); + assert!(test_zero != test_rand); + // A random generated one should always be $size in length. + assert!(test_rand.get_length() == $size); + } + + #[test] + #[cfg(feature = "safe_api")] + // format! is only available with std + fn test_omitted_debug_hmac_key() { + let secret = format!("{:?}", [0u8; $size].as_ref()); + let test_debug_contents = format!("{:?}", $name::from_slice(&[0u8; $size]).unwrap()); + assert_eq!(test_debug_contents.contains(&secret), false); } ); } @@ -383,9 +527,9 @@ macro_rules! construct_blake2b_key { original_size: usize, } - impl_debug_trait!($name); + impl_omitted_debug_trait!($name); impl_drop_stack_trait!($name); - impl_partialeq_trait!($name); + impl_ct_partialeq_trait!($name); impl $name { #[must_use] @@ -405,7 +549,6 @@ macro_rules! construct_blake2b_key { }) } - #[must_use] /// Get the original size of the key, before padding. pub fn get_original_length(&self) -> usize { self.original_size @@ -432,15 +575,66 @@ macro_rules! construct_blake2b_key { #[test] fn test_blake2b_key_size() { - // We don't test above $size here in case it's passed as a `max_value()` - let _ = $name::from_slice(&[0u8; 64]).unwrap(); - let _ = $name::from_slice(&[0u8; 64 - 63]).unwrap(); - let _ = $name::from_slice(&[0u8; 64 - 1]).unwrap(); + assert!($name::from_slice(&[0u8; 64]).is_ok()); + assert!($name::from_slice(&[0u8; 63]).is_ok()); + assert!($name::from_slice(&[0u8; 65]).is_err()); + assert!($name::from_slice(&[0u8; 0]).is_err()); } #[test] fn test_unprotected_as_bytes_blake2b_key() { + // Verify against $size because that's how the key is padded. let test = $name::from_slice(&[0u8; 64]).unwrap(); - assert!(test.unprotected_as_bytes().len() == $size); + assert!(test.unprotected_as_bytes() == [0u8; $size].as_ref()); + + let test = $name::from_slice(&[0u8; 32]).unwrap(); + assert!(test.unprotected_as_bytes() == [0u8; $size].as_ref()); + + let test = $name::from_slice(&[0u8; 1]).unwrap(); + assert!(test.unprotected_as_bytes() == [0u8; $size].as_ref()); + } + + #[test] + fn test_get_length_blake2b_key() { + let test = $name::from_slice(&[0u8; 64]).unwrap(); + assert!(test.unprotected_as_bytes().len() == test.get_length()); + assert!($size == test.get_length()); + } + + #[test] + fn test_get_original_length_blake2b_key() { + let test = $name::from_slice(&[0u8; 64]).unwrap(); + assert!(test.unprotected_as_bytes().len() != test.get_original_length()); + assert!(64 == test.get_original_length()); + } + + #[test] + fn test_blake2b_key_zero_padding() { + let test = $name::from_slice(&[1u8; 64]).unwrap(); + assert!([0u8; 64].as_ref() == &test.unprotected_as_bytes()[64..]); + } + + #[test] + #[cfg(feature = "safe_api")] + fn test_generate_blake2b() { + let test_zero = $name::from_slice(&[0u8; 64]).unwrap(); + // A random one should never be all 0's. + let test_rand = $name::generate().unwrap(); + assert!(test_zero != test_rand); + assert_ne!( + test_zero.unprotected_as_bytes(), + &test_rand.unprotected_as_bytes()[..test_zero.get_original_length()] + ); + // A random generated one should always be 64 in length for Blake2b. + assert!(test_rand.get_original_length() == 64); + } + + #[test] + #[cfg(feature = "safe_api")] + // format! is only available with std + fn test_omitted_debug_blake2b_key() { + let secret = format!("{:?}", [0u8; 64].as_ref()); + let test_debug_contents = format!("{:?}", $name::from_slice(&[0u8; 64]).unwrap()); + assert_eq!(test_debug_contents.contains(&secret), false); } ); } @@ -489,6 +683,11 @@ macro_rules! construct_digest { digest_size: slice.len(), }) } + + /// Return the length of the object. + pub fn get_length(&self) -> usize { + self.digest_size + } } impl core::fmt::Debug for $name { @@ -498,16 +697,29 @@ macro_rules! construct_digest { } #[test] - fn test_blake2b_mac_size() { - // We don't test above $size here in case it's passed as a `max_value()` - let _ = $name::from_slice(&[0u8; 64]).unwrap(); - let _ = $name::from_slice(&[0u8; 64 - 63]).unwrap(); - let _ = $name::from_slice(&[0u8; 64 - 1]).unwrap(); + fn test_blake2b_digest_size() { + assert!($name::from_slice(&[0u8; 64]).is_ok()); + assert!($name::from_slice(&[0u8; 63]).is_ok()); + assert!($name::from_slice(&[0u8; 65]).is_err()); + assert!($name::from_slice(&[0u8; 0]).is_err()); } #[test] - fn test_unprotected_as_bytes_blake2b_mac() { + fn test_as_bytes_blake2b_digest() { let test = $name::from_slice(&[0u8; 64]).unwrap(); - assert!(test.as_bytes().len() == 64); + assert!(test.as_bytes() == [0u8; 64].as_ref()); + + let test = $name::from_slice(&[0u8; 32]).unwrap(); + assert!(test.as_bytes() == [0u8; 32].as_ref()); + + let test = $name::from_slice(&[0u8; 1]).unwrap(); + assert!(test.as_bytes() == [0u8; 1].as_ref()); + } + + #[test] + fn test_get_length_blake2b_digest() { + let test = $name::from_slice(&[0u8; 32]).unwrap(); + assert!(test.as_bytes().len() == test.get_length()); + assert!(32 == test.get_length()); } ); } @@ -527,58 +739,56 @@ macro_rules! construct_secret_key_variable_size { /// that the type implements. pub struct $name { value: Vec } - impl_debug_trait!($name); + impl_omitted_debug_trait!($name); impl_drop_heap_trait!($name); - impl_partialeq_trait!($name); + impl_ct_partialeq_trait!($name); impl_default_trait!($name, $size); impl $name { - #[must_use] - #[cfg(feature = "safe_api")] - /// Make an object from a given byte slice. - pub fn from_slice(slice: &[u8]) -> Result<$name, UnknownCryptoError> { - if slice.is_empty() { - return Err(UnknownCryptoError); - } - - Ok($name { value: Vec::from(slice) }) - } - + func_from_slice_variable_size!($name); func_unprotected_as_bytes!(); func_get_length!(); - #[must_use] - #[cfg(feature = "safe_api")] - /// Randomly generate using a CSPRNG. Not available in `no_std` context. - pub fn generate(length: usize) -> Result<$name, UnknownCryptoError> { - use crate::util; - if length < 1 || length >= (u32::max_value() as usize) { - return Err(UnknownCryptoError); - } - - let mut value = vec![0u8; length]; - util::secure_rand_bytes(&mut value)?; - - Ok($name { value: value }) - } + func_generate_variable_size!($name); } #[test] fn test_from_slice_key() { - let _ = $name::from_slice(&[0u8; 256]).unwrap(); - let _ = $name::from_slice(&[0u8; 512]).unwrap(); + assert!($name::from_slice(&[0u8; 512]).is_ok()); + assert!($name::from_slice(&[0u8; 256]).is_ok()); + assert!($name::from_slice(&[0u8; 1]).is_ok()); assert!($name::from_slice(&[0u8; 0]).is_err()); } + #[test] fn test_unprotected_as_bytes_derived_key() { let test = $name::from_slice(&[0u8; 256]).unwrap(); assert!(test.unprotected_as_bytes().len() == 256); + assert!(test.unprotected_as_bytes() == [0u8; 256].as_ref()); } + #[test] - fn test_generate_key() { + #[cfg(feature = "safe_api")] + fn test_generate_secret_key() { assert!($name::generate(0).is_err()); assert!($name::generate(usize::max_value()).is_err()); assert!($name::generate(1).is_ok()); assert!($name::generate(64).is_ok()); + + let test_zero = $name::from_slice(&[0u8; 128]).unwrap(); + // A random one should never be all 0's. + let test_rand = $name::generate(128).unwrap(); + assert!(test_zero != test_rand); + // A random generated one should always be $size in length. + assert!(test_rand.get_length() == 128); + } + + #[test] + #[cfg(feature = "safe_api")] + // format! is only available with std + fn test_omitted_debug_secret_key() { + let secret = format!("{:?}", [0u8; $size].as_ref()); + let test_debug_contents = format!("{:?}", $name::from_slice(&[0u8; $size]).unwrap()); + assert_eq!(test_debug_contents.contains(&secret), false); } ); } @@ -595,61 +805,46 @@ macro_rules! construct_salt_variable_size { /// pub struct $name { value: Vec } + impl_normal_debug_trait!($name); impl_default_trait!($name, $size); + impl_normal_partialeq_trait!($name); impl $name { - #[must_use] - #[cfg(feature = "safe_api")] - /// Make an object from a given byte slice. - pub fn from_slice(slice: &[u8]) -> Result<$name, UnknownCryptoError> { - if slice.is_empty() { - return Err(UnknownCryptoError); - } - - Ok($name { value: Vec::from(slice) }) - } - + func_from_slice_variable_size!($name); func_as_bytes!(); func_get_length!(); - #[must_use] - #[cfg(feature = "safe_api")] - /// Randomly generate using a CSPRNG. Not available in `no_std` context. - pub fn generate(length: usize) -> Result<$name, UnknownCryptoError> { - use crate::util; - if length < 1 || length >= (u32::max_value() as usize) { - return Err(UnknownCryptoError); - } - - let mut value = vec![0u8; length]; - util::secure_rand_bytes(&mut value)?; - - Ok($name { value: value }) - } - } - - impl core::fmt::Debug for $name { - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - write!(f, "{} {:?}", stringify!($name), &self.value[..]) - } + func_generate_variable_size!($name); } #[test] fn test_form_slice_salt() { - let _ = $name::from_slice(&[0u8; 256]).unwrap(); - let _ = $name::from_slice(&[0u8; 512]).unwrap(); + assert!($name::from_slice(&[0u8; 512]).is_ok()); + assert!($name::from_slice(&[0u8; 256]).is_ok()); + assert!($name::from_slice(&[0u8; 1]).is_ok()); assert!($name::from_slice(&[0u8; 0]).is_err()); } + #[test] fn test_as_bytes_salt() { let test = $name::from_slice(&[0u8; 256]).unwrap(); assert!(test.as_bytes().len() == 256); + assert!(test.as_bytes() == [0u8; 256].as_ref()); } + #[test] + #[cfg(feature = "safe_api")] fn test_generate_salt() { assert!($name::generate(0).is_err()); assert!($name::generate(usize::max_value()).is_err()); assert!($name::generate(1).is_ok()); assert!($name::generate(64).is_ok()); + + let test_zero = $name::from_slice(&[0u8; 128]).unwrap(); + // A random one should never be all 0's. + let test_rand = $name::generate(128).unwrap(); + assert!(test_zero != test_rand); + // A random generated one should always be $size in length. + assert!(test_rand.get_length() == 128); } ); } @@ -668,36 +863,54 @@ macro_rules! construct_password_variable_size { /// that the type implements. pub struct $name { value: Vec } - impl_debug_trait!($name); + impl_omitted_debug_trait!($name); impl_drop_heap_trait!($name); - impl_partialeq_trait!($name); + impl_ct_partialeq_trait!($name); impl $name { - #[must_use] - #[cfg(feature = "safe_api")] - /// Make an object from a given byte slice. - pub fn from_slice(slice: &[u8]) -> Result<$name, UnknownCryptoError> { - if slice.is_empty() { - return Err(UnknownCryptoError); - } - - Ok($name { value: Vec::from(slice) }) - } - + func_from_slice_variable_size!($name); func_unprotected_as_bytes!(); func_get_length!(); + func_generate_variable_size!($name); } #[test] fn test_form_slice_password() { - let _ = $name::from_slice(&[0u8; 256]).unwrap(); - let _ = $name::from_slice(&[0u8; 512]).unwrap(); + assert!($name::from_slice(&[0u8; 512]).is_ok()); + assert!($name::from_slice(&[0u8; 256]).is_ok()); + assert!($name::from_slice(&[0u8; 1]).is_ok()); assert!($name::from_slice(&[0u8; 0]).is_err()); } #[test] fn test_unprotected_as_bytes_password() { let test = $name::from_slice(&[0u8; 256]).unwrap(); assert!(test.unprotected_as_bytes().len() == 256); + assert!(test.unprotected_as_bytes() == [0u8; 256].as_ref()); + } + + #[test] + #[cfg(feature = "safe_api")] + fn test_generate_password() { + assert!($name::generate(0).is_err()); + assert!($name::generate(usize::max_value()).is_err()); + assert!($name::generate(1).is_ok()); + assert!($name::generate(64).is_ok()); + + let test_zero = $name::from_slice(&[0u8; 128]).unwrap(); + // A random one should never be all 0's. + let test_rand = $name::generate(128).unwrap(); + assert!(test_zero != test_rand); + // A random generated one should always be $size in length. + assert!(test_rand.get_length() == 128); + } + + #[test] + #[cfg(feature = "safe_api")] + // format! is only available with std + fn test_omitted_debug_password() { + let secret = format!("{:?}", [0u8; 64].as_ref()); + let test_debug_contents = format!("{:?}", $name::from_slice(&[0u8; 64]).unwrap()); + assert_eq!(test_debug_contents.contains(&secret), false); } ); }