#![cfg(feature = "std")]
use dstu_core::crypto_stream::{decrypt, encrypt, Key, StreamError};
use dstu_core::hazmat::strumok::Strumok256;
use proptest::prelude::*;
const IV_LEN: usize = 32;
#[test]
fn round_trip() {
let key = Key::generate().expect("OS CSPRNG available in test environment");
let plaintext = b"hello dstu";
let sealed = encrypt(&key, plaintext).expect("OS CSPRNG available in test environment");
let opened = decrypt(&key, &sealed).expect("input long enough to contain an IV");
assert_eq!(opened, plaintext);
}
#[test]
fn zero_length_plaintext_round_trips() {
let key = Key::generate().expect("OS CSPRNG available in test environment");
let sealed = encrypt(&key, &[]).expect("OS CSPRNG available in test environment");
assert_eq!(sealed.len(), IV_LEN);
let opened = decrypt(&key, &sealed).expect("input long enough to contain an IV");
assert_eq!(opened, Vec::<u8>::new());
}
#[test]
fn large_message_round_trips() {
let key = Key::generate().expect("OS CSPRNG available in test environment");
let plaintext = vec![0x42u8; 4096];
let sealed = encrypt(&key, &plaintext).expect("OS CSPRNG available in test environment");
let opened = decrypt(&key, &sealed).expect("input long enough to contain an IV");
assert_eq!(opened, plaintext);
}
#[test]
fn two_calls_use_different_ivs() {
let key = Key::generate().expect("OS CSPRNG available in test environment");
let a = encrypt(&key, b"same plaintext").expect("OS CSPRNG available in test environment");
let b = encrypt(&key, b"same plaintext").expect("OS CSPRNG available in test environment");
assert_ne!(
&a[..IV_LEN],
&b[..IV_LEN],
"a fresh random IV must be drawn per call"
);
assert_ne!(a, b);
}
#[test]
fn truncated_input_is_rejected_not_a_panic() {
let key = Key::generate().expect("OS CSPRNG available in test environment");
for len in [0usize, 1, 20, IV_LEN - 1] {
let short = vec![0u8; len];
let err = decrypt(&key, &short).expect_err("input shorter than an IV must be rejected");
assert!(matches!(err, StreamError::Truncated), "len = {len}");
}
}
#[test]
fn wrong_key_produces_different_plaintext_not_an_error() {
let key = Key::generate().expect("OS CSPRNG available in test environment");
let other = Key::generate().expect("OS CSPRNG available in test environment");
let plaintext = b"secret message";
let sealed = encrypt(&key, plaintext).expect("OS CSPRNG available in test environment");
let opened = decrypt(&other, &sealed).expect("decrypt never fails on a wrong key");
assert_ne!(opened, plaintext);
}
#[test]
fn tampered_ciphertext_does_not_error_but_produces_garbage() {
let key = Key::generate().expect("OS CSPRNG available in test environment");
let plaintext = b"secret message..";
let mut sealed = encrypt(&key, plaintext).expect("OS CSPRNG available in test environment");
sealed[IV_LEN] ^= 0xFF;
let opened = decrypt(&key, &sealed).expect("decrypt never fails on tampered ciphertext");
assert_ne!(opened, plaintext);
}
#[test]
fn wire_format_is_iv_then_ciphertext() {
let key = Key::generate().expect("OS CSPRNG available in test environment");
let plaintext = b"pin the layout";
let sealed = encrypt(&key, plaintext).expect("OS CSPRNG available in test environment");
let mut iv = [0u8; IV_LEN];
iv.copy_from_slice(&sealed[..IV_LEN]);
let mut buf = plaintext.to_vec();
let mut cipher = Strumok256::new(key.as_bytes(), &iv);
cipher.apply_keystream(&mut buf);
assert_eq!(
&sealed[IV_LEN..],
buf.as_slice(),
"ciphertext must match a direct hazmat call with the same key/IV"
);
}
proptest! {
#[test]
fn round_trip_property(plaintext in proptest::collection::vec(any::<u8>(), 0..=2048)) {
let key = Key::generate().expect("OS CSPRNG available in test environment");
let sealed = encrypt(&key, &plaintext).expect("OS CSPRNG available in test environment");
let opened = decrypt(&key, &sealed).expect("input long enough to contain an IV");
prop_assert_eq!(opened, plaintext);
}
}