use std::collections::HashSet;
use hyperion_vault_core::crypto::{
self, generate_dek, generate_nonce, open, open_envelope, seal, seal_envelope, LocalKeyWrapper,
DEK_LEN, NONCE_LEN,
};
use hyperion_vault_core::types::aad_for;
#[test]
fn seal_open_round_trips() {
let dek = generate_dek();
let nonce = generate_nonce();
let aad = aad_for("db/password", 1);
let plaintext = b"super-secret-value";
let ct = seal(&dek, &nonce, &aad, plaintext).unwrap();
assert_ne!(
ct.as_slice(),
plaintext,
"ciphertext must not equal plaintext"
);
let pt = open(&dek, &nonce, &aad, &ct).unwrap();
assert_eq!(pt.as_slice(), plaintext.as_slice());
}
#[test]
fn ciphertext_tamper_is_rejected() {
let dek = generate_dek();
let nonce = generate_nonce();
let aad = aad_for("name", 1);
let mut ct = seal(&dek, &nonce, &aad, b"value").unwrap();
ct[0] ^= 0x01;
assert!(
open(&dek, &nonce, &aad, &ct).is_err(),
"tampered ciphertext must fail AEAD"
);
}
#[test]
fn tag_truncation_is_rejected() {
let dek = generate_dek();
let nonce = generate_nonce();
let aad = aad_for("name", 1);
let mut ct = seal(&dek, &nonce, &aad, b"value").unwrap();
ct.pop();
assert!(open(&dek, &nonce, &aad, &ct).is_err());
}
#[test]
fn nonce_tamper_is_rejected() {
let dek = generate_dek();
let nonce = generate_nonce();
let aad = aad_for("name", 1);
let ct = seal(&dek, &nonce, &aad, b"value").unwrap();
let mut bad_nonce = nonce;
bad_nonce[0] ^= 0x01;
assert!(open(&dek, &bad_nonce, &aad, &ct).is_err());
}
#[test]
fn wrong_key_is_rejected() {
let dek = generate_dek();
let other = generate_dek();
let nonce = generate_nonce();
let aad = aad_for("name", 1);
let ct = seal(&dek, &nonce, &aad, b"value").unwrap();
assert!(open(&other, &nonce, &aad, &ct).is_err());
}
#[test]
fn aad_binds_ciphertext_to_name_and_version() {
let dek = generate_dek();
let nonce = generate_nonce();
let ct = seal(&dek, &nonce, &aad_for("secretA", 1), b"value").unwrap();
assert!(
open(&dek, &nonce, &aad_for("secretB", 1), &ct).is_err(),
"ciphertext must not decrypt under a different secret name"
);
assert!(
open(&dek, &nonce, &aad_for("secretA", 2), &ct).is_err(),
"ciphertext must not decrypt under a different version"
);
assert!(open(&dek, &nonce, &aad_for("secretA", 1), &ct).is_ok());
}
#[test]
fn nonces_do_not_repeat() {
let mut seen: HashSet<[u8; NONCE_LEN]> = HashSet::new();
for _ in 0..100_000 {
assert!(seen.insert(generate_nonce()), "nonce collision generated");
}
}
#[test]
fn data_keys_are_unique_and_high_entropy() {
let mut seen: HashSet<[u8; DEK_LEN]> = HashSet::new();
for _ in 0..50_000 {
let dek = generate_dek();
assert!(seen.insert(*dek), "DEK collision generated");
}
}
#[test]
fn envelope_round_trips_through_wrapper() {
let wrapper = LocalKeyWrapper::random();
let aad = aad_for("api/key", 3);
let env = seal_envelope(&wrapper, &aad, b"top-secret").unwrap();
assert_ne!(env.ciphertext.as_slice(), b"top-secret");
assert!(!env.wrapped_dek.is_empty());
assert_eq!(
open_envelope(&wrapper, &env, &aad).unwrap().as_slice(),
b"top-secret".as_slice()
);
}
#[test]
fn envelope_wrapped_dek_is_randomized_per_call() {
let wrapper = LocalKeyWrapper::random();
let a = seal_envelope(&wrapper, b"aad", b"value").unwrap();
let b = seal_envelope(&wrapper, b"aad", b"value").unwrap();
assert_ne!(
a.wrapped_dek, b.wrapped_dek,
"DEK wrapping must be nondeterministic"
);
assert_ne!(
a.ciphertext, b.ciphertext,
"ciphertext must be nondeterministic"
);
}
#[test]
fn envelope_rejects_wrong_master_key() {
let aad = aad_for("x", 1);
let env = seal_envelope(&LocalKeyWrapper::random(), &aad, b"value").unwrap();
let other = LocalKeyWrapper::random();
assert!(open_envelope(&other, &env, &aad).is_err());
}
#[test]
fn envelope_rejects_tampered_wrapped_dek() {
let wrapper = LocalKeyWrapper::random();
let aad = aad_for("x", 1);
let mut env = seal_envelope(&wrapper, &aad, b"value").unwrap();
let last = env.wrapped_dek.len() - 1;
env.wrapped_dek[last] ^= 0x01;
assert!(open_envelope(&wrapper, &env, &aad).is_err());
}
#[test]
fn dek_from_slice_enforces_length() {
assert!(crypto::dek_from_slice(&[0u8; DEK_LEN]).is_ok());
assert!(crypto::dek_from_slice(&[0u8; DEK_LEN - 1]).is_err());
assert!(crypto::dek_from_slice(&[0u8; DEK_LEN + 1]).is_err());
}