//! The code must reproduce the published test vectors byte for byte, and the vectors must decode. use std::path::PathBuf; use kv_crypto::{aead::SymKey, seal::EncKeypair, secret_key::SecretKey}; use kv_proto::Envelope; use serde_json::Value; use uuid::Uuid; #[path = "../vectors/gen.rs"] mod gen; fn dir() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/vectors") } fn load(name: &str) -> Value { let text = std::fs::read_to_string(dir().join(format!("{name}.json"))).expect("vector file (run gen-vectors)"); serde_json::from_str(&text).unwrap() } #[test] fn code_reproduces_vector_files() { let spec = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../../kestrove-spec/vectors"); for (name, doc) in gen::generate() { let rendered = gen::render(&doc); let on_disk = std::fs::read_to_string(dir().join(format!("{name}.json"))).unwrap(); assert_eq!(rendered, on_disk, "{name}.json differs from what the code produces"); // When the spec repo is checked out next to this one, it must carry the same files. if let Ok(published) = std::fs::read_to_string(spec.join(format!("{name}.json"))) { assert_eq!(published, on_disk, "kestrove-spec/vectors/{name}.json is stale"); } } assert_eq!(gen::FILES.len(), gen::generate().len()); } #[test] fn envelopes_open() { for v in load("envelope")["vectors"].as_array().unwrap() { let key: [u8; 32] = gen::unhex(v["key"].as_str().unwrap()).try_into().unwrap(); let key = SymKey::from_bytes(Uuid::parse_str(v["kid"].as_str().unwrap()).unwrap(), key); let env: Envelope = serde_json::from_value(v["envelope"].clone()).unwrap(); let pt = key.open(&env, &gen::unhex(v["aad"].as_str().unwrap())).unwrap(); assert_eq!(gen::hex(&pt), v["plaintext"].as_str().unwrap()); assert_eq!(gen::hex(&env.ct), v["ciphertext"].as_str().unwrap()); } } #[test] fn hpke_opens() { for v in load("hpke")["vectors"].as_array().unwrap() { let kp = EncKeypair::from_secret_bytes( Uuid::parse_str(v["recipient_kid"].as_str().unwrap()).unwrap(), &gen::unhex(v["recipient_sk"].as_str().unwrap()), ) .unwrap(); assert_eq!(gen::hex(&kp.public_bytes()), v["recipient_pk"].as_str().unwrap()); let env: Envelope = serde_json::from_value(v["envelope"].clone()).unwrap(); let pt = kp.open(&env, &gen::unhex(v["aad"].as_str().unwrap())).unwrap(); assert_eq!(gen::hex(&pt), v["plaintext"].as_str().unwrap()); } } #[test] fn secret_keys_parse() { let doc = load("secret_key_format"); for v in doc["valid"].as_array().unwrap() { let k = SecretKey::parse(v["display"].as_str().unwrap()).unwrap(); assert_eq!(gen::hex(k.expose()), v["raw"].as_str().unwrap()); } for v in doc["alternate_inputs"].as_array().unwrap() { let k = SecretKey::parse(v["input"].as_str().unwrap()).unwrap(); assert_eq!(gen::hex(k.expose()), v["raw"].as_str().unwrap()); } for v in doc["invalid"].as_array().unwrap() { assert!(SecretKey::parse(v["input"].as_str().unwrap()).is_err(), "{v}"); } } #[test] fn rfc6238_codes_are_the_published_ones() { // Spot-check against the RFC 6238 Appendix B table itself, not only our own output. let doc = load("totp"); let find = |t: u64, alg: &str| { doc["vectors"] .as_array() .unwrap() .iter() .find(|v| v["time"] == t && v["uri"].as_str().unwrap().contains(&format!("algorithm={alg}&"))) .unwrap()["code"] .as_str() .unwrap() .to_string() }; assert_eq!(find(59, "SHA1"), "94287082"); assert_eq!(find(59, "SHA256"), "46119246"); assert_eq!(find(59, "SHA512"), "90693936"); assert_eq!(find(20_000_000_000, "SHA1"), "65353130"); assert_eq!(find(20_000_000_000, "SHA512"), "47863826"); }