diff --git a/crates/idfoto-core/tests/format_v2.rs b/crates/idfoto-core/tests/format_v2.rs new file mode 100644 index 0000000..97210a7 --- /dev/null +++ b/crates/idfoto-core/tests/format_v2.rs @@ -0,0 +1,54 @@ +//! Format v2 invariants: VERSION_BYTE = 0x02, v1 blobs are rejected with +//! UnsupportedFormatVersion, length-prefix construction guarantees domain +//! separation. + +use idfoto_core::{ + IdfotoError, + crypto::{KdfParams, VERSION_BYTE}, + decrypt, derive_master_key, encrypt, +}; +use zeroize::Zeroizing; + +fn fast_params() -> KdfParams { KdfParams { argon2_m: 256, argon2_t: 1, argon2_p: 1 } } + +#[test] +fn version_byte_is_2() { + assert_eq!(VERSION_BYTE, 0x02); +} + +#[test] +fn fresh_ciphertext_starts_with_0x02() { + let key = Zeroizing::new([0u8; 32]); + // encrypt(key: &[u8; 32], plaintext: &[u8]) + let ct = encrypt(&*key, b"hello").unwrap(); + assert_eq!(ct[0], 0x02); +} + +#[test] +fn v1_blob_is_rejected_with_unsupported_format_version() { + // v1 layout: [0x01][24 nonce bytes][16 tag bytes] + let mut blob = vec![0x01u8]; + blob.extend_from_slice(&[0u8; 24 + 16]); + let key = Zeroizing::new([0u8; 32]); + // decrypt(key: &[u8; 32], data: &[u8]) + let err = decrypt(&*key, &blob); + match err { + Err(IdfotoError::UnsupportedFormatVersion { found, expected }) => { + assert_eq!(found, 0x01); + assert_eq!(expected, 0x02); + } + other => panic!("expected UnsupportedFormatVersion, got {other:?}"), + } +} + +#[test] +fn length_prefix_distinguishes_concat_collisions() { + let salt = [0u8; 32]; + let img = [0x44u8; 32]; + let p1 = b"abc"; + let p2 = b"abcD"; // Pre-length-prefix, ("abc", [0x44, ...]) and ("abcD", ...) + // could be made to collide. With length-prefix they cannot. + let k1 = derive_master_key(p1, &img, &salt, &fast_params()).unwrap(); + let k2 = derive_master_key(p2, &img, &salt, &fast_params()).unwrap(); + assert_ne!(*k1, *k2); +}