use proptest::prelude::*;
use uselesskey_core::{Factory, Seed};
#[cfg(feature = "rsa")]
mod rsa_props {
use super::*;
use jsonwebtoken::{Algorithm, Header, Validation, decode, encode};
use serde::{Deserialize, Serialize};
use uselesskey_jsonwebtoken::JwtKeyExt;
use uselesskey_rsa::{RsaFactoryExt, RsaSpec};
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct Claims {
sub: String,
exp: usize,
}
proptest! {
#![proptest_config(ProptestConfig { cases: 5, ..ProptestConfig::default() })]
#[test]
fn rsa_jwt_roundtrip(sub in "[a-z]{1,20}") {
let fx = Factory::random();
let kp = fx.rsa("prop-jwt-rsa", RsaSpec::rs256());
let claims = Claims { sub: sub.clone(), exp: 2_000_000_000 };
let token = encode(&Header::new(Algorithm::RS256), &claims, &kp.encoding_key()).unwrap();
let decoded = decode::<Claims>(&token, &kp.decoding_key(), &Validation::new(Algorithm::RS256)).unwrap();
prop_assert_eq!(decoded.claims.sub, sub);
}
#[test]
fn rsa_deterministic(seed in any::<[u8; 32]>()) {
let fx1 = Factory::deterministic(Seed::new(seed));
let fx2 = Factory::deterministic(Seed::new(seed));
let kp1 = fx1.rsa("prop-det-rsa", RsaSpec::rs256());
let kp2 = fx2.rsa("prop-det-rsa", RsaSpec::rs256());
let claims = Claims { sub: "det".into(), exp: 2_000_000_000 };
let token = encode(&Header::new(Algorithm::RS256), &claims, &kp1.encoding_key()).unwrap();
let decoded = decode::<Claims>(&token, &kp2.decoding_key(), &Validation::new(Algorithm::RS256)).unwrap();
prop_assert_eq!(decoded.claims.sub, "det");
}
#[test]
fn rsa_different_seeds_distinct(
seed_a in any::<[u8; 32]>(),
seed_b in any::<[u8; 32]>(),
) {
prop_assume!(seed_a != seed_b);
let fx_a = Factory::deterministic(Seed::new(seed_a));
let fx_b = Factory::deterministic(Seed::new(seed_b));
let kp_a = fx_a.rsa("prop-rsa", RsaSpec::rs256());
let kp_b = fx_b.rsa("prop-rsa", RsaSpec::rs256());
let claims = Claims { sub: "x".into(), exp: 2_000_000_000 };
let token = encode(&Header::new(Algorithm::RS256), &claims, &kp_a.encoding_key()).unwrap();
let result = decode::<Claims>(&token, &kp_b.decoding_key(), &Validation::new(Algorithm::RS256));
prop_assert!(result.is_err(), "Different seeds should not cross-verify");
}
}
}
#[cfg(feature = "ecdsa")]
mod ecdsa_props {
use super::*;
use jsonwebtoken::{Algorithm, Header, Validation, decode, encode};
use serde::{Deserialize, Serialize};
use uselesskey_ecdsa::{EcdsaFactoryExt, EcdsaSpec};
use uselesskey_jsonwebtoken::JwtKeyExt;
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct Claims {
sub: String,
exp: usize,
}
proptest! {
#![proptest_config(ProptestConfig { cases: 16, ..ProptestConfig::default() })]
#[test]
fn es256_jwt_roundtrip(sub in "[a-z]{1,20}") {
let fx = Factory::random();
let kp = fx.ecdsa("prop-jwt-ec256", EcdsaSpec::es256());
let claims = Claims { sub: sub.clone(), exp: 2_000_000_000 };
let token = encode(&Header::new(Algorithm::ES256), &claims, &kp.encoding_key()).unwrap();
let decoded = decode::<Claims>(&token, &kp.decoding_key(), &Validation::new(Algorithm::ES256)).unwrap();
prop_assert_eq!(decoded.claims.sub, sub);
}
#[test]
fn es384_jwt_roundtrip(sub in "[a-z]{1,20}") {
let fx = Factory::random();
let kp = fx.ecdsa("prop-jwt-ec384", EcdsaSpec::es384());
let claims = Claims { sub: sub.clone(), exp: 2_000_000_000 };
let token = encode(&Header::new(Algorithm::ES384), &claims, &kp.encoding_key()).unwrap();
let decoded = decode::<Claims>(&token, &kp.decoding_key(), &Validation::new(Algorithm::ES384)).unwrap();
prop_assert_eq!(decoded.claims.sub, sub);
}
#[test]
fn ecdsa_deterministic(seed in any::<[u8; 32]>()) {
let fx1 = Factory::deterministic(Seed::new(seed));
let fx2 = Factory::deterministic(Seed::new(seed));
let kp1 = fx1.ecdsa("prop-det-ec", EcdsaSpec::es256());
let kp2 = fx2.ecdsa("prop-det-ec", EcdsaSpec::es256());
prop_assert_eq!(
kp1.private_key_pkcs8_der(),
kp2.private_key_pkcs8_der(),
"Deterministic ECDSA keys should be identical"
);
}
#[test]
fn ecdsa_different_seeds_distinct(
seed_a in any::<[u8; 32]>(),
seed_b in any::<[u8; 32]>(),
) {
prop_assume!(seed_a != seed_b);
let fx_a = Factory::deterministic(Seed::new(seed_a));
let fx_b = Factory::deterministic(Seed::new(seed_b));
let kp_a = fx_a.ecdsa("prop-ec", EcdsaSpec::es256());
let kp_b = fx_b.ecdsa("prop-ec", EcdsaSpec::es256());
prop_assert_ne!(
kp_a.private_key_pkcs8_der(),
kp_b.private_key_pkcs8_der(),
"Different seeds should produce different ECDSA keys"
);
}
}
}
#[cfg(feature = "ed25519")]
mod ed25519_props {
use super::*;
use jsonwebtoken::{Algorithm, Header, Validation, decode, encode};
use serde::{Deserialize, Serialize};
use uselesskey_ed25519::{Ed25519FactoryExt, Ed25519Spec};
use uselesskey_jsonwebtoken::JwtKeyExt;
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct Claims {
sub: String,
exp: usize,
}
proptest! {
#![proptest_config(ProptestConfig { cases: 32, ..ProptestConfig::default() })]
#[test]
fn ed25519_jwt_roundtrip(sub in "[a-z]{1,20}") {
let fx = Factory::random();
let kp = fx.ed25519("prop-jwt-ed", Ed25519Spec::new());
let claims = Claims { sub: sub.clone(), exp: 2_000_000_000 };
let token = encode(&Header::new(Algorithm::EdDSA), &claims, &kp.encoding_key()).unwrap();
let decoded = decode::<Claims>(&token, &kp.decoding_key(), &Validation::new(Algorithm::EdDSA)).unwrap();
prop_assert_eq!(decoded.claims.sub, sub);
}
#[test]
fn ed25519_deterministic(seed in any::<[u8; 32]>()) {
let fx1 = Factory::deterministic(Seed::new(seed));
let fx2 = Factory::deterministic(Seed::new(seed));
let kp1 = fx1.ed25519("prop-det-ed", Ed25519Spec::new());
let kp2 = fx2.ed25519("prop-det-ed", Ed25519Spec::new());
prop_assert_eq!(
kp1.private_key_pkcs8_der(),
kp2.private_key_pkcs8_der(),
"Deterministic Ed25519 keys should be identical"
);
}
#[test]
fn ed25519_different_seeds_distinct(
seed_a in any::<[u8; 32]>(),
seed_b in any::<[u8; 32]>(),
) {
prop_assume!(seed_a != seed_b);
let fx_a = Factory::deterministic(Seed::new(seed_a));
let fx_b = Factory::deterministic(Seed::new(seed_b));
let kp_a = fx_a.ed25519("prop-ed", Ed25519Spec::new());
let kp_b = fx_b.ed25519("prop-ed", Ed25519Spec::new());
prop_assert_ne!(
kp_a.private_key_pkcs8_der(),
kp_b.private_key_pkcs8_der(),
"Different seeds should produce different Ed25519 keys"
);
}
}
}
#[cfg(feature = "hmac")]
mod hmac_props {
use super::*;
use jsonwebtoken::{Algorithm, Header, Validation, decode, encode};
use serde::{Deserialize, Serialize};
use uselesskey_hmac::{HmacFactoryExt, HmacSpec};
use uselesskey_jsonwebtoken::JwtKeyExt;
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct Claims {
sub: String,
exp: usize,
}
proptest! {
#![proptest_config(ProptestConfig { cases: 32, ..ProptestConfig::default() })]
#[test]
fn hs256_jwt_roundtrip(sub in "[a-z]{1,20}") {
let fx = Factory::random();
let secret = fx.hmac("prop-jwt-hs256", HmacSpec::hs256());
let claims = Claims { sub: sub.clone(), exp: 2_000_000_000 };
let token = encode(&Header::new(Algorithm::HS256), &claims, &secret.encoding_key()).unwrap();
let decoded = decode::<Claims>(&token, &secret.decoding_key(), &Validation::new(Algorithm::HS256)).unwrap();
prop_assert_eq!(decoded.claims.sub, sub);
}
#[test]
fn hs384_jwt_roundtrip(sub in "[a-z]{1,20}") {
let fx = Factory::random();
let secret = fx.hmac("prop-jwt-hs384", HmacSpec::hs384());
let claims = Claims { sub: sub.clone(), exp: 2_000_000_000 };
let token = encode(&Header::new(Algorithm::HS384), &claims, &secret.encoding_key()).unwrap();
let decoded = decode::<Claims>(&token, &secret.decoding_key(), &Validation::new(Algorithm::HS384)).unwrap();
prop_assert_eq!(decoded.claims.sub, sub);
}
#[test]
fn hs512_jwt_roundtrip(sub in "[a-z]{1,20}") {
let fx = Factory::random();
let secret = fx.hmac("prop-jwt-hs512", HmacSpec::hs512());
let claims = Claims { sub: sub.clone(), exp: 2_000_000_000 };
let token = encode(&Header::new(Algorithm::HS512), &claims, &secret.encoding_key()).unwrap();
let decoded = decode::<Claims>(&token, &secret.decoding_key(), &Validation::new(Algorithm::HS512)).unwrap();
prop_assert_eq!(decoded.claims.sub, sub);
}
#[test]
fn hmac_deterministic(seed in any::<[u8; 32]>()) {
let fx1 = Factory::deterministic(Seed::new(seed));
let fx2 = Factory::deterministic(Seed::new(seed));
let s1 = fx1.hmac("prop-det-hmac", HmacSpec::hs256());
let s2 = fx2.hmac("prop-det-hmac", HmacSpec::hs256());
prop_assert_eq!(
s1.secret_bytes(),
s2.secret_bytes(),
"Deterministic HMAC secrets should be identical"
);
}
#[test]
fn hmac_different_seeds_distinct(
seed_a in any::<[u8; 32]>(),
seed_b in any::<[u8; 32]>(),
) {
prop_assume!(seed_a != seed_b);
let fx_a = Factory::deterministic(Seed::new(seed_a));
let fx_b = Factory::deterministic(Seed::new(seed_b));
let s_a = fx_a.hmac("prop-hmac", HmacSpec::hs256());
let s_b = fx_b.hmac("prop-hmac", HmacSpec::hs256());
prop_assert_ne!(
s_a.secret_bytes(),
s_b.secret_bytes(),
"Different seeds should produce different HMAC secrets"
);
}
}
}