use std::fs; use libtest_mimic::{Arguments, Failed, Trial}; use pasta_tokens::purpose::local::{EncryptedToken, LocalVersion, SymmetricKey, UnencryptedToken}; use pasta_tokens::purpose::public::{ PublicKey, PublicVersion, SecretKey, SignedToken, UnsignedToken, }; use pasta_tokens::version::{V3, V4}; use serde::{ de::{DeserializeOwned, Visitor}, Deserialize, }; use signature::rand_core::impls::{next_u32_via_fill, next_u64_via_fill}; fn main() { let mut args = Arguments::from_args(); args.test_threads = Some(1); let mut tests = vec![]; PasetoTest::add_all_tests(&mut tests); libtest_mimic::run(&args, tests).exit(); } fn read_test(v: &str) -> TestFile { let path = format!("tests/test-vectors/{v}"); let file = fs::read_to_string(path).unwrap(); serde_json::from_str(&file).unwrap() } #[derive(Deserialize)] struct TestFile { tests: Vec>, } #[derive(Deserialize)] struct Test { name: String, #[serde(flatten)] test_data: T, } #[derive(Deserialize, Debug)] #[serde(rename_all = "kebab-case")] struct PasetoTest { token: String, footer: String, implicit_assertion: String, #[serde(flatten)] purpose: PasetoPurpose, #[serde(flatten)] result: TestResult, } impl PasetoTest { fn add_all_tests(tests: &mut Vec) { Self::add_tests::(tests); Self::add_tests::(tests); } fn add_tests(tests: &mut Vec) where PublicKey: ParseKey, SecretKey: ParseKey, SymmetricKey: ParseKey, { let test_file: TestFile = read_test(&format!("{}.json", V::PASETO_HEADER)); for test in test_file.tests { tests.push(Trial::test(test.name, || test.test_data.test::())); } } fn test(self) -> Result<(), Failed> where PublicKey: ParseKey, SecretKey: ParseKey, SymmetricKey: ParseKey, { match self { PasetoTest { token, footer, implicit_assertion, purpose: PasetoPurpose::Local { key, .. }, result: TestResult::Failure { .. }, } => { let key = SymmetricKey::::from_key(&key); let Ok(token): Result>, _> = token.parse() else { return Ok(()); }; assert_eq!(token.unverified_footer(), footer.as_bytes()); match token.decrypt_with_assertions::( &key, implicit_assertion.as_bytes(), ) { Ok(_) => Err("decrypting token should fail".into()), Err(_) => Ok(()), } } PasetoTest { token: token_str, footer, implicit_assertion, purpose: PasetoPurpose::Local { nonce, key }, result: TestResult::Success { payload, .. }, } => { let key = SymmetricKey::::from_key(&key); let token: EncryptedToken> = token_str.parse().unwrap(); assert_eq!(token.unverified_footer(), footer.as_bytes()); let decrypted_token = token .decrypt_with_assertions::( &key, implicit_assertion.as_bytes(), ) .unwrap(); let payload: serde_json::Value = serde_json::from_str(&payload).unwrap(); assert_eq!(decrypted_token.message, payload); let nonce: [u8; 32] = hex::decode(nonce).unwrap().try_into().unwrap(); let token = UnencryptedToken::new(decrypted_token.message) .with_footer(decrypted_token.footer) .encrypt_with_assertions_and_rng( &key, implicit_assertion.as_bytes(), FakeRng { bytes: nonce, start: 0, }, ) .unwrap(); assert_eq!(token.to_string(), token_str); Ok(()) } PasetoTest { token, footer, implicit_assertion, purpose: PasetoPurpose::Public { public_key, .. }, result: TestResult::Failure { .. }, } => { let public_key = PublicKey::::from_key(&public_key); let Ok(token): Result>, _> = token.parse() else { return Ok(()); }; assert_eq!(token.unverified_footer(), footer.as_bytes()); match token.verify_with_assertions::( &public_key, implicit_assertion.as_bytes(), ) { Ok(_) => Err("verifying token should fail".into()), Err(_) => Ok(()), } } PasetoTest { token: token_str, footer, implicit_assertion, purpose: PasetoPurpose::Public { public_key, secret_key, }, result: TestResult::Success { payload, .. }, } => { let public_key = PublicKey::::from_key(&public_key); let secret_key = SecretKey::::from_key(&secret_key); let token: SignedToken> = token_str.parse().unwrap(); assert_eq!(token.unverified_footer(), footer.as_bytes()); let token = token .verify_with_assertions::( &public_key, implicit_assertion.as_bytes(), ) .unwrap(); let payload: serde_json::Value = serde_json::from_str(&payload).unwrap(); assert_eq!(token.message, payload); let token = UnsignedToken::::new(token.message) .with_footer(token.footer) .sign_with_assertions(&secret_key, implicit_assertion.as_bytes()) .unwrap(); assert_eq!(token.to_string(), token_str); Ok(()) } } } } #[derive(Deserialize, Debug)] #[serde(untagged)] enum PasetoPurpose { #[serde(rename_all = "kebab-case")] Local { nonce: String, key: String }, #[serde(rename_all = "kebab-case")] Public { public_key: String, secret_key: String, }, } #[derive(Debug)] struct Bool; impl<'a, const B: bool> Deserialize<'a> for Bool { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'a>, { struct BoolVisitor; impl<'a, const B: bool> Visitor<'a> for BoolVisitor { type Value = Bool; fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { write!(formatter, "{B}") } fn visit_bool(self, v: bool) -> Result where E: serde::de::Error, { (v == B) .then_some(Bool) .ok_or_else(|| E::custom(format!("expected {B}, got {v}"))) } } deserializer.deserialize_bool(BoolVisitor) } } #[derive(Deserialize, Debug)] #[serde(untagged)] enum TestResult { #[serde(rename_all = "kebab-case")] Success { #[allow(dead_code)] expect_fail: Bool, payload: String, }, #[serde(rename_all = "kebab-case")] Failure { #[allow(dead_code)] expect_fail: Bool, #[allow(dead_code)] payload: (), }, } trait ParseKey { fn from_key(s: &str) -> Self; } impl ParseKey for SymmetricKey { fn from_key(s: &str) -> Self { let b = hex::decode(s).unwrap(); Self::from_bytes(b.try_into().unwrap()) } } impl ParseKey for SymmetricKey { fn from_key(s: &str) -> Self { let b = hex::decode(s).unwrap(); Self::from_bytes(b.try_into().unwrap()) } } impl ParseKey for SecretKey { fn from_key(s: &str) -> Self { let b = hex::decode(s).unwrap(); Self::from_bytes(&b).unwrap() } } impl ParseKey for SecretKey { fn from_key(s: &str) -> Self { let b = hex::decode(s).unwrap(); Self::from_keypair_bytes(&b).unwrap() } } impl ParseKey for PublicKey { fn from_key(s: &str) -> Self { let b = hex::decode(s).unwrap(); Self::from_sec1_bytes(&b).unwrap() } } impl ParseKey for PublicKey { fn from_key(s: &str) -> Self { let b = hex::decode(s).unwrap(); Self::from_public_key(&b).unwrap() } } #[derive(Clone, Debug)] /// a consistent rng store struct FakeRng { pub bytes: [u8; N], pub start: usize, } impl rand::RngCore for FakeRng { fn next_u32(&mut self) -> u32 { next_u32_via_fill(self) } fn next_u64(&mut self) -> u64 { next_u64_via_fill(self) } fn fill_bytes(&mut self, dest: &mut [u8]) { let remaining = N - self.start; let requested = dest.len(); if requested > remaining { panic!("not enough entropy"); } dest.copy_from_slice(&self.bytes[self.start..self.start + requested]); self.start += requested; } fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> { self.fill_bytes(dest); Ok(()) } } // not really impl rand::CryptoRng for FakeRng {}