use std::fmt::{self, Display};
use serde::{Deserialize, Deserializer, Serialize};
use crate::error::{Error, IncompatibleKeyError, InvalidKeyError, Result};
use crate::jwk::{
Algorithm, Key, KeyOperation, KeyType, KeyUse, is_operation_compatible_with_algorithm,
};
mod cache;
#[cfg(all(feature = "cloudflare", target_arch = "wasm32"))]
pub mod cloudflare;
mod store;
#[cfg(all(feature = "moka", not(target_arch = "wasm32")))]
pub use cache::moka::{DEFAULT_MOKA_CACHE_TTL, MokaKeyCache};
pub use cache::{CachedKeyStore, KeyCache};
#[cfg(feature = "http")]
pub use store::http::HttpKeyStore;
#[cfg(all(feature = "http", not(target_arch = "wasm32")))]
pub use store::http::DEFAULT_TIMEOUT;
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum SelectionError {
EmptyVerifyAllowlist,
UnknownAlgorithm,
UnknownOperation,
OperationAlgorithmMismatch {
operation: KeyOperation,
algorithm: Algorithm,
},
AlgorithmNotAllowed,
AlgorithmMismatch {
requested: Algorithm,
declared: Algorithm,
},
IntentMismatch,
InvalidKey(InvalidKeyError),
IncompatibleKeyType,
KeySuitabilityFailed(IncompatibleKeyError),
AmbiguousSelection {
count: usize,
},
NoMatchingKey,
}
impl Display for SelectionError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use crate::error::sanitize_for_display;
match self {
SelectionError::EmptyVerifyAllowlist => {
write!(f, "verification allowlist is empty")
}
SelectionError::UnknownAlgorithm => write!(f, "unknown or unsupported algorithm"),
SelectionError::UnknownOperation => write!(f, "unknown or unsupported operation"),
SelectionError::OperationAlgorithmMismatch {
operation,
algorithm,
} => {
let operation_display = match operation {
KeyOperation::Unknown(value) => {
format!("unknown({})", sanitize_for_display(value))
}
_ => operation.to_string(),
};
let algorithm_display = match algorithm {
Algorithm::Unknown(value) => {
format!("unknown({})", sanitize_for_display(value))
}
_ => algorithm.to_string(),
};
write!(
f,
"operation/algorithm mismatch: operation {} is not valid for algorithm {}",
operation_display, algorithm_display
)
}
SelectionError::AlgorithmNotAllowed => {
write!(f, "algorithm is not allowed for verification")
}
SelectionError::AlgorithmMismatch {
requested,
declared,
} => {
let requested_display = requested.to_string();
let declared_display = match declared {
Algorithm::Unknown(value) => {
format!("unknown({})", sanitize_for_display(value))
}
_ => declared.to_string(),
};
write!(
f,
"algorithm mismatch: requested {}, key declares {}",
requested_display, declared_display
)
}
SelectionError::IntentMismatch => {
write!(f, "key metadata does not permit requested operation")
}
SelectionError::InvalidKey(e) => {
write!(f, "key is invalid: {}", e)
}
SelectionError::IncompatibleKeyType => {
write!(f, "key type/curve is incompatible with requested algorithm")
}
SelectionError::KeySuitabilityFailed(e) => {
write!(f, "key suitability check failed: {}", e)
}
SelectionError::AmbiguousSelection { count } => {
write!(f, "selection is ambiguous: {} matching keys", count)
}
SelectionError::NoMatchingKey => write!(f, "no matching key found"),
}
}
}
impl std::error::Error for SelectionError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
SelectionError::InvalidKey(e) => Some(e),
SelectionError::KeySuitabilityFailed(e) => Some(e),
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub struct KeyMatcher<'a> {
op: KeyOperation,
alg: Algorithm,
kid: Option<&'a str>,
}
impl<'a> KeyMatcher<'a> {
#[must_use]
pub fn new(op: KeyOperation, alg: Algorithm) -> Self {
Self { op, alg, kid: None }
}
#[must_use]
pub fn with_kid(mut self, kid: &'a str) -> Self {
self.kid = Some(kid);
self
}
}
#[derive(Debug, Clone, Default)]
#[non_exhaustive]
pub struct KeyFilter<'a> {
pub op: Option<KeyOperation>,
pub alg: Option<Algorithm>,
pub kid: Option<&'a str>,
pub kty: Option<KeyType>,
pub key_use: Option<KeyUse>,
}
impl<'a> KeyFilter<'a> {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn for_alg(alg: Algorithm) -> Self {
Self::new().with_alg(alg)
}
#[must_use]
pub fn for_use(key_use: KeyUse) -> Self {
Self::new().with_key_use(key_use)
}
#[must_use]
pub fn for_kty(kty: KeyType) -> Self {
Self::new().with_kty(kty)
}
#[must_use]
pub fn for_op(op: KeyOperation) -> Self {
Self::new().with_op(op)
}
#[must_use]
pub fn for_use_alg(key_use: KeyUse, alg: Algorithm) -> Self {
Self::new().with_key_use(key_use).with_alg(alg)
}
#[must_use]
pub fn for_op_alg(op: KeyOperation, alg: Algorithm) -> Self {
Self::new().with_op(op).with_alg(alg)
}
#[must_use]
pub fn with_op(mut self, op: KeyOperation) -> Self {
self.op = Some(op);
self
}
#[must_use]
pub fn with_alg(mut self, alg: Algorithm) -> Self {
self.alg = Some(alg);
self
}
#[must_use]
pub fn with_kid(mut self, kid: &'a str) -> Self {
self.kid = Some(kid);
self
}
#[must_use]
pub fn with_kty(mut self, kty: KeyType) -> Self {
self.kty = Some(kty);
self
}
#[must_use]
pub fn with_key_use(mut self, key_use: KeyUse) -> Self {
self.key_use = Some(key_use);
self
}
}
#[derive(Debug, Clone)]
pub struct KeySelector<'a> {
keyset: &'a KeySet,
allowed_verify_algs: Vec<Algorithm>,
}
impl<'a> KeySelector<'a> {
pub fn select(&self, matcher: KeyMatcher<'_>) -> std::result::Result<&'a Key, SelectionError> {
if matcher.alg.is_unknown() {
return Err(SelectionError::UnknownAlgorithm);
}
if matcher.op.is_unknown() {
return Err(SelectionError::UnknownOperation);
}
if !is_operation_compatible_with_algorithm(&matcher.op, &matcher.alg) {
return Err(SelectionError::OperationAlgorithmMismatch {
operation: matcher.op,
algorithm: matcher.alg,
});
}
if matcher.op == KeyOperation::Verify {
if self.allowed_verify_algs.is_empty() {
return Err(SelectionError::EmptyVerifyAllowlist);
}
if !self.allowed_verify_algs.contains(&matcher.alg) {
return Err(SelectionError::AlgorithmNotAllowed);
}
}
let mut candidates = Vec::new();
let mut incompatible_for_known_kid = false;
let mut saw_alg_mismatch: Option<(Algorithm, Algorithm)> = None;
let mut saw_intent_mismatch = false;
let mut saw_invalid_key: Option<InvalidKeyError> = None;
let mut saw_suitability_error: Option<IncompatibleKeyError> = None;
for key in self.keyset.keys.iter() {
if let Some(kid) = matcher.kid
&& key.kid() != Some(kid)
{
continue;
}
if let Some(declared_alg) = key.alg()
&& declared_alg != &matcher.alg
{
if matcher.kid.is_some() && saw_alg_mismatch.is_none() {
saw_alg_mismatch = Some((matcher.alg.clone(), declared_alg.clone()));
}
continue;
}
if !key.is_algorithm_compatible(&matcher.alg) {
if matcher.kid.is_some() {
incompatible_for_known_kid = true;
}
continue;
}
if let Err(err) = key.check_operation_intent(std::slice::from_ref(&matcher.op)) {
if matcher.kid.is_some() {
match err {
Error::IncompatibleKey(IncompatibleKeyError::OperationNotPermitted {
..
}) => saw_intent_mismatch = true,
Error::InvalidKey(invalid) => {
if saw_invalid_key.is_none() {
saw_invalid_key = Some(invalid);
}
}
Error::IncompatibleKey(_) => saw_intent_mismatch = true,
_ => incompatible_for_known_kid = true,
}
}
continue;
}
if let Err(err) = key.validate_certificate_metadata() {
if matcher.kid.is_some() {
match err {
Error::InvalidKey(invalid) => {
if saw_invalid_key.is_none() {
saw_invalid_key = Some(invalid);
}
}
_ => incompatible_for_known_kid = true,
}
}
continue;
}
if let Err(e) = key.check_algorithm_suitability(&matcher.alg) {
if matcher.kid.is_some() {
match e {
Error::InvalidKey(invalid) => {
if saw_invalid_key.is_none() {
saw_invalid_key = Some(invalid);
}
}
Error::IncompatibleKey(suitability) => {
if saw_suitability_error.is_none() {
saw_suitability_error = Some(suitability);
}
}
_ => incompatible_for_known_kid = true,
}
}
continue;
}
if let Err(e) = key.check_operation_capability(std::slice::from_ref(&matcher.op)) {
if matcher.kid.is_some() {
match e {
Error::IncompatibleKey(suitability) => {
if saw_suitability_error.is_none() {
saw_suitability_error = Some(suitability);
}
}
Error::InvalidKey(_) => incompatible_for_known_kid = true,
_ => incompatible_for_known_kid = true,
}
}
continue;
}
candidates.push(key);
}
if candidates.is_empty() {
if let Some((requested, declared)) = saw_alg_mismatch {
return Err(SelectionError::AlgorithmMismatch {
requested,
declared,
});
}
if saw_intent_mismatch {
return Err(SelectionError::IntentMismatch);
}
if let Some(invalid) = saw_invalid_key {
return Err(SelectionError::InvalidKey(invalid));
}
if let Some(suitability) = saw_suitability_error {
return Err(SelectionError::KeySuitabilityFailed(suitability));
}
if incompatible_for_known_kid {
return Err(SelectionError::IncompatibleKeyType);
}
return Err(SelectionError::NoMatchingKey);
}
if candidates.len() > 1 {
return Err(SelectionError::AmbiguousSelection {
count: candidates.len(),
});
}
Ok(candidates[0])
}
}
#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
pub trait KeyStore {
async fn get_keyset(&self) -> Result<KeySet>;
async fn get_key(&self, kid: &str) -> Result<Option<Key>> {
Ok(self.get_keyset().await?.get_by_kid(kid).cloned())
}
}
#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
impl KeyStore for KeySet {
async fn get_keyset(&self) -> Result<KeySet> {
Ok(self.clone())
}
async fn get_key(&self, kid: &str) -> Result<Option<Key>> {
Ok(self.get_by_kid(kid).cloned())
}
}
#[derive(Debug, Clone, Serialize, Default)]
pub struct KeySet {
keys: Vec<Key>,
}
impl<'de> Deserialize<'de> for KeySet {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct RawJwkSet {
keys: Vec<serde_json::Value>,
}
let raw = RawJwkSet::deserialize(deserializer)?;
let mut keys = Vec::with_capacity(raw.keys.len());
for value in raw.keys {
if let Ok(key) = serde_json::from_value::<Key>(value)
&& key.validate().is_ok()
{
keys.push(key);
}
}
Ok(KeySet { keys })
}
}
impl KeySet {
pub fn new() -> Self {
Self { keys: Vec::new() }
}
#[must_use]
pub fn from_keys_lossy(keys: Vec<Key>) -> Self {
Self {
keys: keys.into_iter().filter(|k| k.validate().is_ok()).collect(),
}
}
pub fn keys(&self) -> &[Key] {
&self.keys
}
pub fn len(&self) -> usize {
self.keys.len()
}
pub fn is_empty(&self) -> bool {
self.keys.is_empty()
}
pub fn add_key(&mut self, key: Key) -> Result<()> {
key.validate()?;
self.keys.push(key);
Ok(())
}
pub fn remove_by_kid(&mut self, kid: &str) -> Option<Key> {
if let Some(pos) = self.keys.iter().position(|k| k.kid() == Some(kid)) {
Some(self.keys.remove(pos))
} else {
None
}
}
pub fn get_by_kid(&self, kid: &str) -> Option<&Key> {
self.keys.iter().find(|k| k.kid() == Some(kid))
}
pub fn signing_keys(&self) -> impl Iterator<Item = &Key> {
self.keys.iter().filter(|k| is_signing_key(k))
}
pub fn encryption_keys(&self) -> impl Iterator<Item = &Key> {
self.keys.iter().filter(|k| is_encryption_key(k))
}
pub fn first_signing_key(&self) -> Option<&Key> {
self.signing_keys().next()
}
pub fn first(&self) -> Option<&Key> {
self.keys.first()
}
pub fn iter(&self) -> impl Iterator<Item = &Key> {
self.keys.iter()
}
pub fn validate(&self) -> Result<()> {
for key in &self.keys {
key.validate()?;
}
Ok(())
}
pub fn get_by_thumbprint(&self, thumbprint: &str) -> Option<&Key> {
self.keys.iter().find(|k| k.thumbprint() == thumbprint)
}
pub fn find<'a, 'f>(&'a self, filter: KeyFilter<'f>) -> impl Iterator<Item = &'a Key> + 'a {
let KeyFilter {
op,
alg,
kid,
kty,
key_use,
} = filter;
let kid = kid.map(ToOwned::to_owned);
self.keys.iter().filter(move |k| {
if let Some(kid) = kid.as_deref()
&& k.kid() != Some(kid)
{
return false;
}
if let Some(kty) = kty
&& k.kty() != kty
{
return false;
}
if let Some(alg) = &alg
&& k.alg() != Some(alg)
{
return false;
}
if let Some(key_use) = &key_use
&& k.key_use() != Some(key_use)
{
return false;
}
if let Some(op) = &op {
if let Some(key_ops) = k.key_ops() {
if !key_ops.contains(op) {
return false;
}
} else if let Some(key_use) = k.key_use() {
let allowed_by_use = match op {
KeyOperation::Sign | KeyOperation::Verify => key_use == &KeyUse::Signature,
KeyOperation::Encrypt
| KeyOperation::Decrypt
| KeyOperation::WrapKey
| KeyOperation::UnwrapKey
| KeyOperation::DeriveKey
| KeyOperation::DeriveBits => key_use == &KeyUse::Encryption,
KeyOperation::Unknown(_) => true,
};
if !allowed_by_use {
return false;
}
}
}
true
})
}
pub fn selector(&self, allowed_verify_algs: &[Algorithm]) -> KeySelector<'_> {
KeySelector {
keyset: self,
allowed_verify_algs: allowed_verify_algs.to_vec(),
}
}
}
impl IntoIterator for KeySet {
type Item = Key;
type IntoIter = std::vec::IntoIter<Key>;
fn into_iter(self) -> Self::IntoIter {
self.keys.into_iter()
}
}
impl<'a> IntoIterator for &'a KeySet {
type Item = &'a Key;
type IntoIter = std::slice::Iter<'a, Key>;
fn into_iter(self) -> Self::IntoIter {
self.keys.iter()
}
}
impl std::ops::Index<usize> for KeySet {
type Output = Key;
fn index(&self, index: usize) -> &Self::Output {
&self.keys[index]
}
}
fn is_signing_key(key: &Key) -> bool {
if let Some(ops) = key.key_ops() {
ops.contains(&KeyOperation::Sign) || ops.contains(&KeyOperation::Verify)
} else {
key.key_use().is_none() || key.key_use() == Some(&KeyUse::Signature)
}
}
fn is_encryption_key(key: &Key) -> bool {
if let Some(ops) = key.key_ops() {
ops.contains(&KeyOperation::Encrypt)
|| ops.contains(&KeyOperation::Decrypt)
|| ops.contains(&KeyOperation::WrapKey)
|| ops.contains(&KeyOperation::UnwrapKey)
|| ops.contains(&KeyOperation::DeriveKey)
|| ops.contains(&KeyOperation::DeriveBits)
} else {
key.key_use() == Some(&KeyUse::Encryption)
}
}
#[cfg(test)]
mod tests {
use super::*;
const SAMPLE_JWKS: &str = r#"{
"keys": [
{
"kty": "RSA",
"kid": "rsa-key-1",
"use": "sig",
"alg": "RS256",
"n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
"e": "AQAB"
},
{
"kty": "EC",
"kid": "ec-key-1",
"use": "sig",
"alg": "ES256",
"crv": "P-256",
"x": "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
"y": "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM"
},
{
"kty": "RSA",
"kid": "rsa-enc-1",
"use": "enc",
"n": "sXchDaQebSXKcvL0vwlG",
"e": "AQAB"
}
]
}"#;
#[test]
fn test_parse_jwks() {
let jwks: KeySet = serde_json::from_str(SAMPLE_JWKS).unwrap();
assert_eq!(jwks.len(), 3);
}
#[test]
fn test_parse_skips_semantically_invalid_key() {
let json = r#"{
"keys": [
{"kty": "EC", "crv": "P-256", "x": "AQ", "y": "AQ", "kid": "bad"},
{"kty": "oct", "k": "AQAB", "kid": "good"}
]
}"#;
let jwks: KeySet = serde_json::from_str(json).unwrap();
assert_eq!(jwks.len(), 1);
assert!(jwks.get_by_kid("bad").is_none());
assert!(jwks.get_by_kid("good").is_some());
}
#[test]
fn test_parse_skips_unknown_kty() {
let json = r#"{
"keys": [
{"kty": "UNKNOWN", "kid": "unknown"},
{"kty": "oct", "k": "AQAB", "kid": "good"}
]
}"#;
let jwks: KeySet = serde_json::from_str(json).unwrap();
assert_eq!(jwks.len(), 1);
assert!(jwks.get_by_kid("unknown").is_none());
assert!(jwks.get_by_kid("good").is_some());
}
#[test]
fn test_get_by_kid() {
let jwks: KeySet = serde_json::from_str(SAMPLE_JWKS).unwrap();
assert!(jwks.get_by_kid("rsa-key-1").is_some());
assert!(jwks.get_by_kid("rsa-enc-1").is_some());
assert!(jwks.get_by_kid("ec-key-1").is_some());
assert!(jwks.get_by_kid("unknown").is_none());
}
#[test]
fn test_find_with_filter() {
let jwks: KeySet = serde_json::from_str(SAMPLE_JWKS).unwrap();
let by_alg = KeyFilter::new().with_alg(Algorithm::Rs256);
assert_eq!(jwks.find(by_alg).count(), 1);
let by_kty = KeyFilter::new().with_kty(KeyType::Rsa);
assert_eq!(jwks.find(by_kty).count(), 2);
let by_use = KeyFilter::new().with_key_use(KeyUse::Encryption);
assert_eq!(jwks.find(by_use).count(), 1);
let by_op_use = KeyFilter::new().with_op(KeyOperation::Sign);
assert_eq!(jwks.find(by_op_use).count(), 2);
let by_unknown_op = KeyFilter::new().with_op(KeyOperation::Unknown("custom".to_string()));
assert_eq!(jwks.find(by_unknown_op).count(), 3);
let json = r#"{"keys": [
{"kty": "RSA", "kid": "sign", "n": "AQAB", "e": "AQAB", "key_ops": ["sign"]},
{"kty": "RSA", "kid": "enc", "n": "AQAB", "e": "AQAB", "key_ops": ["encrypt"]}
]}"#;
let with_key_ops: KeySet = serde_json::from_str(json).unwrap();
let by_op_key_ops = KeyFilter::new().with_op(KeyOperation::Sign);
assert_eq!(with_key_ops.find(by_op_key_ops).count(), 1);
}
#[test]
fn test_find_with_shorthand_constructors() {
let jwks: KeySet = serde_json::from_str(SAMPLE_JWKS).unwrap();
assert_eq!(jwks.find(KeyFilter::for_alg(Algorithm::Rs256)).count(), 1);
assert_eq!(jwks.find(KeyFilter::for_kty(KeyType::Rsa)).count(), 2);
assert_eq!(jwks.find(KeyFilter::for_use(KeyUse::Signature)).count(), 2);
assert_eq!(jwks.find(KeyFilter::for_op(KeyOperation::Sign)).count(), 2);
assert_eq!(
jwks.find(KeyFilter::for_use_alg(KeyUse::Signature, Algorithm::Rs256))
.count(),
1
);
assert_eq!(
jwks.find(KeyFilter::for_op_alg(KeyOperation::Sign, Algorithm::Rs256))
.count(),
1
);
}
#[test]
fn test_selector_verify_empty_allowlist() {
let jwks: KeySet = serde_json::from_str(SAMPLE_JWKS).unwrap();
let selector = jwks.selector(&[]);
let err = selector
.select(KeyMatcher::new(KeyOperation::Verify, Algorithm::Rs256))
.unwrap_err();
assert!(matches!(err, SelectionError::EmptyVerifyAllowlist));
}
#[test]
fn test_selector_verify_algorithm_not_allowed() {
let jwks: KeySet = serde_json::from_str(SAMPLE_JWKS).unwrap();
let selector = jwks.selector(&[Algorithm::Es256]);
let err = selector
.select(KeyMatcher::new(KeyOperation::Verify, Algorithm::Rs256))
.unwrap_err();
assert!(matches!(err, SelectionError::AlgorithmNotAllowed));
}
#[test]
fn test_selector_verify_selects_single_key() {
let jwks: KeySet = serde_json::from_str(SAMPLE_JWKS).unwrap();
let selector = jwks.selector(&[Algorithm::Rs256]);
let key = selector
.select(KeyMatcher::new(KeyOperation::Verify, Algorithm::Rs256).with_kid("rsa-key-1"))
.unwrap();
assert_eq!(key.kid(), Some("rsa-key-1"));
}
#[test]
fn test_selector_ambiguous_selection() {
let json = r#"{"keys": [
{"kty": "EC", "kid": "ec-1", "use": "sig", "alg": "ES256", "crv": "P-256", "x": "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", "y": "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM"},
{"kty": "EC", "kid": "ec-2", "use": "sig", "alg": "ES256", "crv": "P-256", "x": "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", "y": "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM"}
]}"#;
let jwks: KeySet = serde_json::from_str(json).unwrap();
let selector = jwks.selector(&[Algorithm::Es256]);
let err = selector
.select(KeyMatcher::new(KeyOperation::Verify, Algorithm::Es256))
.unwrap_err();
assert!(matches!(
err,
SelectionError::AmbiguousSelection { count: 2 }
));
}
#[test]
fn test_selector_algorithm_mismatch_for_known_kid() {
let json = r#"{"keys": [
{"kty": "RSA", "kid": "rsa", "alg": "RS256", "use": "sig", "n": "AQAB", "e": "AQAB"}
]}"#;
let jwks: KeySet = serde_json::from_str(json).unwrap();
let selector = jwks.selector(&[Algorithm::Es256]);
let err = selector
.select(KeyMatcher::new(KeyOperation::Sign, Algorithm::Es256).with_kid("rsa"))
.unwrap_err();
assert!(matches!(
err,
SelectionError::AlgorithmMismatch {
requested: Algorithm::Es256,
declared: Algorithm::Rs256
}
));
}
#[test]
fn test_selector_unknown_algorithm_rejected() {
let jwks: KeySet = serde_json::from_str(SAMPLE_JWKS).unwrap();
let selector = jwks.selector(&[]);
let err = selector
.select(KeyMatcher::new(
KeyOperation::Sign,
Algorithm::Unknown("CUSTOM".to_string()),
))
.unwrap_err();
assert!(matches!(err, SelectionError::UnknownAlgorithm));
}
#[test]
fn test_selector_unknown_operation_rejected() {
let jwks: KeySet = serde_json::from_str(SAMPLE_JWKS).unwrap();
let selector = jwks.selector(&[]);
let err = selector
.select(KeyMatcher::new(
KeyOperation::Unknown("custom-op".to_string()),
Algorithm::Rs256,
))
.unwrap_err();
assert!(matches!(err, SelectionError::UnknownOperation));
}
#[test]
fn test_selector_operation_algorithm_mismatch_rejected() {
let jwks: KeySet = serde_json::from_str(SAMPLE_JWKS).unwrap();
let selector = jwks.selector(&[]);
let err = selector
.select(KeyMatcher::new(KeyOperation::Encrypt, Algorithm::Rs256))
.unwrap_err();
assert!(matches!(
err,
SelectionError::OperationAlgorithmMismatch {
operation: KeyOperation::Encrypt,
algorithm: Algorithm::Rs256
}
));
}
#[test]
fn test_selector_incompatible_key_type_for_known_kid() {
let json = r#"{"keys": [
{"kty": "oct", "kid": "oct-1", "k": "AQAB"}
]}"#;
let jwks: KeySet = serde_json::from_str(json).unwrap();
let selector = jwks.selector(&[]);
let err = selector
.select(KeyMatcher::new(KeyOperation::Sign, Algorithm::Rs256).with_kid("oct-1"))
.unwrap_err();
assert!(matches!(err, SelectionError::IncompatibleKeyType));
}
#[test]
fn test_selector_key_validation_failed_for_known_kid() {
let json = r#"{"keys": [
{"kty": "RSA", "kid": "weak-rsa", "use": "sig", "n": "AQAB", "e": "AQAB"}
]}"#;
let jwks: KeySet = serde_json::from_str(json).unwrap();
let selector = jwks.selector(&[]);
let err = selector
.select(KeyMatcher::new(KeyOperation::Sign, Algorithm::Rs256).with_kid("weak-rsa"))
.unwrap_err();
assert!(matches!(err, SelectionError::KeySuitabilityFailed(_)));
}
#[test]
fn test_selector_rejects_structurally_invalid_key_added_programmatically() {
use crate::encoding::Base64UrlBytes;
use crate::{EcCurve, EcParams, KeyParams};
let bad_ec = Key::new(KeyParams::Ec(EcParams::new_public(
EcCurve::P256,
Base64UrlBytes::new(vec![1, 2, 3, 4]), Base64UrlBytes::new(vec![0; 32]), )))
.with_kid("bad-ec");
let mut jwks = KeySet::new();
jwks.keys.push(bad_ec); assert_eq!(jwks.len(), 1);
let selector = jwks.selector(&[Algorithm::Es256]);
let err = selector
.select(KeyMatcher::new(KeyOperation::Verify, Algorithm::Es256).with_kid("bad-ec"))
.unwrap_err();
assert!(matches!(err, SelectionError::InvalidKey(_)));
}
#[test]
fn test_selector_key_suitability_failed_hs512_for_known_kid() {
let json = r#"{"keys": [
{"kty": "oct", "kid": "weak-hs", "use": "sig", "alg": "HS512", "k": "AQAB"}
]}"#;
let jwks: KeySet = serde_json::from_str(json).unwrap();
let selector = jwks.selector(&[Algorithm::Hs512]);
let err = selector
.select(KeyMatcher::new(KeyOperation::Verify, Algorithm::Hs512).with_kid("weak-hs"))
.unwrap_err();
assert!(matches!(err, SelectionError::KeySuitabilityFailed(_)));
}
#[test]
fn test_selector_intent_mismatch_for_known_kid() {
let json = r#"{"keys": [
{"kty": "RSA", "kid": "enc-rsa", "use": "enc", "n": "AQAB", "e": "AQAB"}
]}"#;
let jwks: KeySet = serde_json::from_str(json).unwrap();
let selector = jwks.selector(&[]);
let err = selector
.select(KeyMatcher::new(KeyOperation::Sign, Algorithm::Rs256).with_kid("enc-rsa"))
.unwrap_err();
assert!(matches!(err, SelectionError::IntentMismatch));
}
#[test]
fn test_selector_invalid_key_for_known_kid() {
let bad_key = Key::new(crate::KeyParams::Rsa(crate::RsaParams::new_public(
crate::encoding::Base64UrlBytes::new(vec![1, 2, 3]),
crate::encoding::Base64UrlBytes::new(vec![1, 0, 1]),
)))
.with_kid("dup-ops")
.with_alg(Algorithm::Rs256)
.with_key_ops([KeyOperation::Verify, KeyOperation::Verify]);
let mut jwks = KeySet::new();
jwks.keys.push(bad_key); let selector = jwks.selector(&[Algorithm::Rs256]);
let err = selector
.select(KeyMatcher::new(KeyOperation::Verify, Algorithm::Rs256).with_kid("dup-ops"))
.unwrap_err();
assert!(matches!(err, SelectionError::InvalidKey(_)));
}
#[test]
fn test_selector_invalid_certificate_metadata_for_known_kid() {
let bad_key = Key::new(crate::KeyParams::Rsa(crate::RsaParams::new_public(
crate::encoding::Base64UrlBytes::new(vec![1; 256]),
crate::encoding::Base64UrlBytes::new(vec![1, 0, 1]),
)))
.with_kid("bad-x5u")
.with_alg(Algorithm::Rs256)
.with_x5u("http://example.com/cert.pem");
let mut jwks = KeySet::new();
jwks.keys.push(bad_key); let selector = jwks.selector(&[]);
let err = selector
.select(KeyMatcher::new(KeyOperation::Sign, Algorithm::Rs256).with_kid("bad-x5u"))
.unwrap_err();
assert!(matches!(err, SelectionError::InvalidKey(_)));
}
#[test]
fn test_selector_intent_mismatch_sign_only_key_for_verify() {
let json = r#"{"keys": [
{"kty": "EC", "kid": "sign-only", "key_ops": ["sign"], "alg": "ES256", "crv": "P-256", "x": "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", "y": "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", "d": "870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE"}
]}"#;
let jwks: KeySet = serde_json::from_str(json).unwrap();
let selector = jwks.selector(&[Algorithm::Es256]);
let err = selector
.select(KeyMatcher::new(KeyOperation::Verify, Algorithm::Es256).with_kid("sign-only"))
.unwrap_err();
assert!(matches!(err, SelectionError::IntentMismatch));
}
#[test]
fn test_selector_intent_mismatch_verify_only_key_for_sign() {
let json = r#"{"keys": [
{"kty": "EC", "kid": "verify-only", "key_ops": ["verify"], "alg": "ES256", "crv": "P-256", "x": "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", "y": "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM"}
]}"#;
let jwks: KeySet = serde_json::from_str(json).unwrap();
let selector = jwks.selector(&[]);
let err = selector
.select(KeyMatcher::new(KeyOperation::Sign, Algorithm::Es256).with_kid("verify-only"))
.unwrap_err();
assert!(matches!(err, SelectionError::IntentMismatch));
}
#[test]
fn test_selector_no_kid_all_candidates_invalid_returns_no_match() {
let json = r#"{"keys": [
{"kty": "RSA", "kid": "weak-1", "use": "sig", "n": "AQAB", "e": "AQAB"},
{"kty": "RSA", "kid": "weak-2", "use": "sig", "n": "AQAB", "e": "AQAB"}
]}"#;
let jwks: KeySet = serde_json::from_str(json).unwrap();
let selector = jwks.selector(&[]);
let err = selector
.select(KeyMatcher::new(KeyOperation::Sign, Algorithm::Rs256))
.unwrap_err();
assert!(matches!(err, SelectionError::NoMatchingKey));
}
#[test]
fn test_selector_error_precedence_alg_mismatch_over_intent() {
let json = r#"{"keys": [
{"kty": "RSA", "kid": "dup", "alg": "ES256", "use": "enc", "n": "AQAB", "e": "AQAB"}
]}"#;
let jwks: KeySet = serde_json::from_str(json).unwrap();
let selector = jwks.selector(&[]);
let err = selector
.select(KeyMatcher::new(KeyOperation::Sign, Algorithm::Rs256).with_kid("dup"))
.unwrap_err();
assert!(matches!(
err,
SelectionError::AlgorithmMismatch {
requested: Algorithm::Rs256,
declared: Algorithm::Es256
}
));
}
#[test]
fn test_selector_error_precedence_intent_over_validation() {
let json = r#"{"keys": [
{"kty": "RSA", "kid": "dup", "use": "enc", "n": "AQAB", "e": "AQAB"},
{"kty": "RSA", "kid": "dup", "use": "sig", "n": "AQAB", "e": "AQAB"}
]}"#;
let jwks: KeySet = serde_json::from_str(json).unwrap();
let selector = jwks.selector(&[]);
let err = selector
.select(KeyMatcher::new(KeyOperation::Sign, Algorithm::Rs256).with_kid("dup"))
.unwrap_err();
assert!(matches!(err, SelectionError::IntentMismatch));
}
#[test]
fn test_selector_error_precedence_intent_over_incompatible() {
let json = r#"{"keys": [
{"kty": "oct", "kid": "dup", "k": "AQAB"},
{"kty": "RSA", "kid": "dup", "use": "enc", "n": "AQAB", "e": "AQAB"}
]}"#;
let jwks: KeySet = serde_json::from_str(json).unwrap();
let selector = jwks.selector(&[]);
let err = selector
.select(KeyMatcher::new(KeyOperation::Sign, Algorithm::Rs256).with_kid("dup"))
.unwrap_err();
assert!(matches!(err, SelectionError::IntentMismatch));
}
#[test]
fn test_selector_error_precedence_validation_over_incompatible() {
let json = r#"{"keys": [
{"kty": "oct", "kid": "dup", "k": "AQAB"},
{"kty": "RSA", "kid": "dup", "use": "sig", "n": "AQAB", "e": "AQAB"}
]}"#;
let jwks: KeySet = serde_json::from_str(json).unwrap();
let selector = jwks.selector(&[]);
let err = selector
.select(KeyMatcher::new(KeyOperation::Sign, Algorithm::Rs256).with_kid("dup"))
.unwrap_err();
assert!(matches!(err, SelectionError::KeySuitabilityFailed(_)));
}
#[test]
fn test_selector_verify_selects_single_key_without_kid() {
let jwks: KeySet = serde_json::from_str(SAMPLE_JWKS).unwrap();
let selector = jwks.selector(&[Algorithm::Es256]);
let key = selector
.select(KeyMatcher::new(KeyOperation::Verify, Algorithm::Es256))
.unwrap();
assert_eq!(key.kid(), Some("ec-key-1"));
}
#[test]
fn test_selector_no_kid_all_declared_algs_mismatch_returns_no_match() {
let json = r#"{"keys": [
{"kty": "RSA", "kid": "r1", "alg": "RS256", "use": "sig", "n": "AQAB", "e": "AQAB"},
{"kty": "EC", "kid": "e1", "alg": "ES256", "use": "sig", "crv": "P-256", "x": "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", "y": "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM"}
]}"#;
let jwks: KeySet = serde_json::from_str(json).unwrap();
let selector = jwks.selector(&[]);
let err = selector
.select(KeyMatcher::new(KeyOperation::Sign, Algorithm::Ps256))
.unwrap_err();
assert!(matches!(err, SelectionError::NoMatchingKey));
}
#[test]
fn test_selector_okp_verify_success() {
let json = r#"{"keys": [
{"kty": "OKP", "kid": "ed-key", "use": "sig", "alg": "Ed25519", "crv": "Ed25519", "x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"}
]}"#;
let jwks: KeySet = serde_json::from_str(json).unwrap();
let selector = jwks.selector(&[Algorithm::Ed25519]);
let key = selector
.select(KeyMatcher::new(KeyOperation::Verify, Algorithm::Ed25519).with_kid("ed-key"))
.unwrap();
assert_eq!(key.kid(), Some("ed-key"));
}
#[test]
fn test_selector_okp_sign_success_with_private_key() {
let json = r#"{"keys": [
{"kty": "OKP", "kid": "ed-sign", "use": "sig", "alg": "Ed25519", "crv": "Ed25519", "x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo", "d": "nWGxne_9Wm8tRcf0UjvXw9vQ3j8n0i4Q4fQx5t6k7mA"}
]}"#;
let jwks: KeySet = serde_json::from_str(json).unwrap();
let selector = jwks.selector(&[]);
let key = selector
.select(KeyMatcher::new(KeyOperation::Sign, Algorithm::Ed25519).with_kid("ed-sign"))
.unwrap();
assert_eq!(key.kid(), Some("ed-sign"));
}
#[test]
fn test_selector_okp_incompatible_with_ec_algorithm() {
let json = r#"{"keys": [
{"kty": "OKP", "kid": "ed-key", "use": "sig", "alg": "Ed25519", "crv": "Ed25519", "x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"}
]}"#;
let jwks: KeySet = serde_json::from_str(json).unwrap();
let selector = jwks.selector(&[]);
let err = selector
.select(KeyMatcher::new(KeyOperation::Sign, Algorithm::Es256).with_kid("ed-key"))
.unwrap_err();
assert!(matches!(
err,
SelectionError::AlgorithmMismatch {
requested: Algorithm::Es256,
declared: Algorithm::Ed25519
}
));
}
#[test]
fn test_selector_sign_selects_single_key() {
let json = r#"{"keys": [
{"kty": "EC", "kid": "ec-sign", "use": "sig", "alg": "ES256", "crv": "P-256", "x": "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", "y": "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", "d": "870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE"}
]}"#;
let jwks: KeySet = serde_json::from_str(json).unwrap();
let selector = jwks.selector(&[]);
let key = selector
.select(KeyMatcher::new(KeyOperation::Sign, Algorithm::Es256).with_kid("ec-sign"))
.unwrap();
assert_eq!(key.kid(), Some("ec-sign"));
}
#[test]
fn test_selector_rejects_public_key_for_sign_with_known_kid() {
let json = r#"{"keys": [
{"kty": "RSA", "kid": "rsa-pub", "use": "sig", "alg": "RS256", "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw", "e": "AQAB"}
]}"#;
let jwks: KeySet = serde_json::from_str(json).unwrap();
let selector = jwks.selector(&[]);
let err = selector
.select(KeyMatcher::new(KeyOperation::Sign, Algorithm::Rs256).with_kid("rsa-pub"))
.unwrap_err();
assert!(matches!(err, SelectionError::KeySuitabilityFailed(_)));
}
#[test]
fn test_selector_rejects_public_ec_key_for_sign_with_known_kid() {
let json = r#"{"keys": [
{"kty": "EC", "kid": "ec-pub", "use": "sig", "alg": "ES256", "crv": "P-256", "x": "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", "y": "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM"}
]}"#;
let jwks: KeySet = serde_json::from_str(json).unwrap();
let selector = jwks.selector(&[]);
let err = selector
.select(KeyMatcher::new(KeyOperation::Sign, Algorithm::Es256).with_kid("ec-pub"))
.unwrap_err();
assert!(matches!(err, SelectionError::KeySuitabilityFailed(_)));
}
#[test]
fn test_selector_no_kid_public_signing_candidates_return_no_match() {
let json = r#"{"keys": [
{"kty": "RSA", "kid": "rsa-pub-1", "use": "sig", "alg": "RS256", "n": "AQAB", "e": "AQAB"},
{"kty": "RSA", "kid": "rsa-pub-2", "use": "sig", "alg": "RS256", "n": "AQAB", "e": "AQAB"}
]}"#;
let jwks: KeySet = serde_json::from_str(json).unwrap();
let selector = jwks.selector(&[]);
let err = selector
.select(KeyMatcher::new(KeyOperation::Sign, Algorithm::Rs256))
.unwrap_err();
assert!(matches!(err, SelectionError::NoMatchingKey));
}
#[test]
fn test_selector_empty_verify_allowlist_does_not_block_signing() {
let json = r#"{"keys": [
{"kty": "EC", "kid": "ec-key-1", "use": "sig", "alg": "ES256", "crv": "P-256", "x": "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", "y": "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", "d": "870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE"}
]}"#;
let jwks: KeySet = serde_json::from_str(json).unwrap();
let selector = jwks.selector(&[]);
let key = selector
.select(KeyMatcher::new(KeyOperation::Sign, Algorithm::Es256).with_kid("ec-key-1"))
.unwrap();
assert_eq!(key.kid(), Some("ec-key-1"));
}
#[test]
fn test_find_with_filter_op_and_use_combination() {
let jwks: KeySet = serde_json::from_str(SAMPLE_JWKS).unwrap();
let compatible = KeyFilter::new()
.with_op(KeyOperation::Sign)
.with_key_use(KeyUse::Signature);
assert_eq!(jwks.find(compatible).count(), 2);
let conflicting = KeyFilter::new()
.with_op(KeyOperation::Sign)
.with_key_use(KeyUse::Encryption);
assert_eq!(jwks.find(conflicting).count(), 0);
}
#[test]
fn test_find_with_filter_op_passthrough_without_metadata() {
let json = r#"{"keys": [
{"kty": "RSA", "kid": "meta-less", "n": "AQAB", "e": "AQAB"},
{"kty": "RSA", "kid": "sig-use", "use": "sig", "n": "AQAB", "e": "AQAB"}
]}"#;
let jwks: KeySet = serde_json::from_str(json).unwrap();
let by_sign = KeyFilter::new().with_op(KeyOperation::Sign);
assert_eq!(jwks.find(by_sign).count(), 2);
}
#[test]
fn test_find_with_filter_builder_api() {
let jwks: KeySet = serde_json::from_str(SAMPLE_JWKS).unwrap();
let filter = KeyFilter::new()
.with_kty(KeyType::Rsa)
.with_alg(Algorithm::Rs256)
.with_kid("rsa-key-1");
let keys: Vec<_> = jwks.find(filter).collect();
assert_eq!(keys.len(), 1);
assert_eq!(keys[0].kid(), Some("rsa-key-1"));
}
#[test]
fn test_selector_no_matching_key_for_empty_keyset() {
let jwks = KeySet::new();
let selector = jwks.selector(&[]);
let err = selector
.select(KeyMatcher::new(KeyOperation::Sign, Algorithm::Rs256))
.unwrap_err();
assert!(matches!(err, SelectionError::NoMatchingKey));
}
#[test]
fn test_selector_no_matching_key_for_unknown_kid() {
let jwks: KeySet = serde_json::from_str(SAMPLE_JWKS).unwrap();
let selector = jwks.selector(&[]);
let err = selector
.select(KeyMatcher::new(KeyOperation::Sign, Algorithm::Rs256).with_kid("ghost"))
.unwrap_err();
assert!(matches!(err, SelectionError::NoMatchingKey));
}
#[test]
fn test_selection_error_display_messages() {
assert_eq!(
SelectionError::EmptyVerifyAllowlist.to_string(),
"verification allowlist is empty"
);
assert_eq!(
SelectionError::UnknownAlgorithm.to_string(),
"unknown or unsupported algorithm"
);
assert_eq!(
SelectionError::UnknownOperation.to_string(),
"unknown or unsupported operation"
);
assert_eq!(
SelectionError::OperationAlgorithmMismatch {
operation: KeyOperation::Encrypt,
algorithm: Algorithm::Rs256,
}
.to_string(),
"operation/algorithm mismatch: operation encrypt is not valid for algorithm RS256"
);
assert_eq!(
SelectionError::AlgorithmNotAllowed.to_string(),
"algorithm is not allowed for verification"
);
assert_eq!(
SelectionError::IntentMismatch.to_string(),
"key metadata does not permit requested operation"
);
assert_eq!(
SelectionError::InvalidKey(InvalidKeyError::InconsistentParameters(
"duplicate key_ops".to_string()
))
.to_string(),
"key is invalid: inconsistent key parameters: duplicate key_ops"
);
assert_eq!(
SelectionError::IncompatibleKeyType.to_string(),
"key type/curve is incompatible with requested algorithm"
);
let mismatch = SelectionError::AlgorithmMismatch {
requested: Algorithm::Rs256,
declared: Algorithm::Es256,
};
assert_eq!(
mismatch.to_string(),
"algorithm mismatch: requested RS256, key declares ES256"
);
let ambiguous = SelectionError::AmbiguousSelection { count: 2 };
assert_eq!(
ambiguous.to_string(),
"selection is ambiguous: 2 matching keys"
);
let suitability =
SelectionError::KeySuitabilityFailed(IncompatibleKeyError::InsufficientKeyStrength {
minimum_bits: 256,
actual_bits: 128,
context: "HS256",
});
assert_eq!(
suitability.to_string(),
"key suitability check failed: insufficient key strength for HS256: need 256 bits, got 128"
);
assert_eq!(
SelectionError::NoMatchingKey.to_string(),
"no matching key found"
);
}
#[test]
fn test_find_by_alg() {
let jwks: KeySet = serde_json::from_str(SAMPLE_JWKS).unwrap();
assert_eq!(
jwks.find(KeyFilter::new().with_alg(Algorithm::Rs256))
.count(),
1
);
assert_eq!(
jwks.find(KeyFilter::new().with_alg(Algorithm::Es256))
.count(),
1
);
}
#[test]
fn test_find_by_use() {
let jwks: KeySet = serde_json::from_str(SAMPLE_JWKS).unwrap();
assert_eq!(
jwks.find(KeyFilter::new().with_key_use(KeyUse::Signature))
.count(),
2
);
assert_eq!(
jwks.find(KeyFilter::new().with_key_use(KeyUse::Encryption))
.count(),
1
);
}
#[test]
fn test_signing_keys() {
let jwks: KeySet = serde_json::from_str(SAMPLE_JWKS).unwrap();
assert_eq!(jwks.signing_keys().count(), 2);
}
#[test]
fn test_encryption_keys() {
let jwks: KeySet = serde_json::from_str(SAMPLE_JWKS).unwrap();
assert_eq!(jwks.encryption_keys().count(), 1);
}
#[test]
fn test_encryption_keys_include_derive_ops() {
let json = r#"{"keys": [
{"kty": "RSA", "kid": "derive-key", "key_ops": ["deriveKey"], "n": "AQAB", "e": "AQAB"},
{"kty": "RSA", "kid": "derive-bits", "key_ops": ["deriveBits"], "n": "AQAB", "e": "AQAB"},
{"kty": "RSA", "kid": "verify-only", "key_ops": ["verify"], "n": "AQAB", "e": "AQAB"}
]}"#;
let jwks: KeySet = serde_json::from_str(json).unwrap();
let kids: Vec<_> = jwks.encryption_keys().filter_map(Key::kid).collect();
assert_eq!(kids.len(), 2);
assert!(kids.contains(&"derive-key"));
assert!(kids.contains(&"derive-bits"));
}
#[test]
fn test_first_signing_key() {
let jwks: KeySet = serde_json::from_str(SAMPLE_JWKS).unwrap();
let first = jwks.first_signing_key().unwrap();
assert_eq!(first.kid(), Some("rsa-key-1"));
}
#[test]
fn test_find_first_by_alg() {
let jwks: KeySet = serde_json::from_str(SAMPLE_JWKS).unwrap();
let key = jwks
.find(KeyFilter::new().with_alg(Algorithm::Rs256))
.next();
assert!(key.is_some());
assert_eq!(key.unwrap().kid(), Some("rsa-key-1"));
let key = jwks
.find(KeyFilter::new().with_alg(Algorithm::Es256))
.next();
assert!(key.is_some());
assert_eq!(key.unwrap().kid(), Some("ec-key-1"));
let missing = jwks
.find(KeyFilter::new().with_alg(Algorithm::Ps512))
.next();
assert!(missing.is_none());
}
#[test]
fn test_signing_keys_includes_verify_key_ops() {
let json = r#"{"keys": [
{"kty": "RSA", "kid": "verify-key", "key_ops": ["verify"], "n": "AQAB", "e": "AQAB"}
]}"#;
let jwks: KeySet = serde_json::from_str(json).unwrap();
assert_eq!(jwks.signing_keys().count(), 1);
}
#[test]
fn test_signing_keys_respects_key_ops_sign() {
let json = r#"{"keys": [
{"kty": "RSA", "kid": "sign-key", "key_ops": ["sign"], "n": "AQAB", "e": "AQAB"}
]}"#;
let jwks: KeySet = serde_json::from_str(json).unwrap();
assert_eq!(jwks.signing_keys().count(), 1);
}
#[test]
fn test_signing_keys_excludes_encrypt_key_ops() {
let json = r#"{"keys": [
{"kty": "RSA", "kid": "enc-key", "key_ops": ["encrypt"], "n": "AQAB", "e": "AQAB"}
]}"#;
let jwks: KeySet = serde_json::from_str(json).unwrap();
assert_eq!(jwks.signing_keys().count(), 0);
}
#[test]
fn test_rfc9864_alg_lookup_behavior() {
let json = r#"{"keys": [
{"kty": "OKP", "kid": "ed25519-key", "use": "sig", "alg": "Ed25519", "crv": "Ed25519", "x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo", "d": "nWGxne_9Wm8tRcf0UjvXw9vQ3j8n0i4Q4fQx5t6k7mA"},
{"kty": "OKP", "kid": "legacy-eddsa", "use": "sig", "alg": "EdDSA", "crv": "Ed25519", "x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo", "d": "nWGxne_9Wm8tRcf0UjvXw9vQ3j8n0i4Q4fQx5t6k7mA"}
]}"#;
let jwks: KeySet = serde_json::from_str(json).unwrap();
assert_eq!(
jwks.find(KeyFilter::new().with_alg(Algorithm::Ed25519))
.count(),
1
);
assert_eq!(
jwks.find(KeyFilter::new().with_alg(Algorithm::EdDsa))
.count(),
1
);
assert_eq!(
jwks.selector(&[])
.select(
KeyMatcher::new(KeyOperation::Sign, Algorithm::Ed25519).with_kid("ed25519-key")
)
.unwrap()
.kid(),
Some("ed25519-key")
);
assert_eq!(
jwks.selector(&[])
.select(
KeyMatcher::new(KeyOperation::Sign, Algorithm::EdDsa).with_kid("legacy-eddsa")
)
.unwrap()
.kid(),
Some("legacy-eddsa")
);
}
#[test]
fn test_empty_jwks() {
let jwks = KeySet::new();
assert!(jwks.is_empty());
assert_eq!(jwks.len(), 0);
assert!(jwks.first().is_none());
assert!(jwks.first_signing_key().is_none());
}
#[test]
fn test_serde_roundtrip() {
let original: KeySet = serde_json::from_str(SAMPLE_JWKS).unwrap();
let json = serde_json::to_string(&original).unwrap();
let parsed: KeySet = serde_json::from_str(&json).unwrap();
assert_eq!(original.len(), parsed.len());
assert_eq!(original.keys(), parsed.keys());
}
#[test]
fn test_iterator() {
let jwks: KeySet = serde_json::from_str(SAMPLE_JWKS).unwrap();
let count = jwks.iter().count();
assert_eq!(count, 3);
let kids: Vec<_> = jwks.iter().filter_map(Key::kid).collect();
assert!(kids.contains(&"rsa-key-1"));
}
#[test]
fn test_index() {
let jwks: KeySet = serde_json::from_str(SAMPLE_JWKS).unwrap();
let first = &jwks[0];
assert_eq!(first.kid(), Some("rsa-key-1"));
}
#[test]
fn test_add_key() {
let mut jwks = KeySet::new();
assert!(jwks.is_empty());
let key: Key = serde_json::from_str(r#"{"kty":"oct","kid":"k1","k":"AQAB"}"#).unwrap();
jwks.add_key(key).unwrap();
assert_eq!(jwks.len(), 1);
assert!(jwks.get_by_kid("k1").is_some());
}
#[test]
fn test_from_keys_lossy_empty_vec_returns_empty_keyset() {
let jwks = KeySet::from_keys_lossy(Vec::new());
assert!(jwks.is_empty());
}
#[test]
fn test_from_keys_lossy_all_valid_preserves_all_and_order() {
let key1: Key = serde_json::from_str(r#"{"kty":"oct","kid":"k1","k":"AQAB"}"#).unwrap();
let key2: Key = serde_json::from_str(r#"{"kty":"oct","kid":"k2","k":"AQID"}"#).unwrap();
let jwks = KeySet::from_keys_lossy(vec![key1, key2]);
assert_eq!(jwks.len(), 2);
let kids: Vec<_> = jwks.iter().filter_map(Key::kid).collect();
assert_eq!(kids, vec!["k1", "k2"]);
}
#[test]
fn test_from_keys_lossy_mixed_drops_invalid_preserves_valid_order() {
let valid1: Key =
serde_json::from_str(r#"{"kty":"oct","kid":"valid-1","k":"AQAB"}"#).unwrap();
let valid2: Key =
serde_json::from_str(r#"{"kty":"oct","kid":"valid-2","k":"AQID"}"#).unwrap();
let invalid: Key =
serde_json::from_str(r#"{"kty":"oct","kid":"invalid","k":"AQAE"}"#).unwrap();
let invalid = invalid
.with_use(KeyUse::Signature)
.with_key_ops([KeyOperation::Encrypt]);
let jwks = KeySet::from_keys_lossy(vec![valid1, invalid, valid2]);
assert_eq!(jwks.len(), 2);
assert!(jwks.get_by_kid("invalid").is_none());
let kids: Vec<_> = jwks.iter().filter_map(Key::kid).collect();
assert_eq!(kids, vec!["valid-1", "valid-2"]);
}
#[test]
fn test_from_keys_lossy_all_invalid_returns_empty_keyset() {
let invalid1: Key =
serde_json::from_str(r#"{"kty":"oct","kid":"bad-1","k":"AQAE"}"#).unwrap();
let invalid1 = invalid1
.with_use(KeyUse::Signature)
.with_key_ops([KeyOperation::Encrypt]);
let invalid2: Key =
serde_json::from_str(r#"{"kty":"oct","kid":"bad-2","k":"AQAF"}"#).unwrap();
let invalid2 = invalid2
.with_use(KeyUse::Encryption)
.with_key_ops([KeyOperation::Sign]);
let jwks = KeySet::from_keys_lossy(vec![invalid1, invalid2]);
assert!(jwks.is_empty());
}
#[test]
fn test_remove_by_kid() {
let mut jwks: KeySet = serde_json::from_str(SAMPLE_JWKS).unwrap();
assert_eq!(jwks.len(), 3);
let removed = jwks.remove_by_kid("ec-key-1");
assert!(removed.is_some());
assert_eq!(removed.unwrap().kid(), Some("ec-key-1"));
assert_eq!(jwks.len(), 2);
assert!(jwks.get_by_kid("ec-key-1").is_none());
assert!(jwks.remove_by_kid("nonexistent").is_none());
assert_eq!(jwks.len(), 2);
}
#[test]
fn test_find_by_kty() {
let jwks: KeySet = serde_json::from_str(SAMPLE_JWKS).unwrap();
assert_eq!(
jwks.find(KeyFilter::new().with_kty(KeyType::Rsa)).count(),
2
);
assert_eq!(jwks.find(KeyFilter::new().with_kty(KeyType::Ec)).count(), 1);
assert_eq!(
jwks.find(KeyFilter::new().with_kty(KeyType::Okp)).count(),
0
);
assert_eq!(
jwks.find(KeyFilter::new().with_kty(KeyType::Symmetric))
.count(),
0
);
}
#[test]
fn test_get_by_thumbprint_finds_matching_key() {
let jwks: KeySet = serde_json::from_str(SAMPLE_JWKS).unwrap();
let rsa_key = jwks.get_by_kid("rsa-key-1").unwrap();
let thumbprint = rsa_key.thumbprint();
let found = jwks.get_by_thumbprint(&thumbprint);
assert!(found.is_some());
assert_eq!(found.unwrap().kid(), Some("rsa-key-1"));
}
#[test]
fn test_get_by_thumbprint_returns_none_for_unknown_value() {
let jwks: KeySet = serde_json::from_str(SAMPLE_JWKS).unwrap();
assert!(
jwks.get_by_thumbprint("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
.is_none()
);
}
#[test]
fn test_get_by_thumbprint_matches_first_key_when_duplicates_present() {
let key_json = r#"{"kty":"RSA","n":"AQAB","e":"AQAB"}"#;
let key: Key = serde_json::from_str(key_json).unwrap();
let duplicate: Key = serde_json::from_str(key_json).unwrap();
let thumbprint = key.thumbprint();
let mut jwks = KeySet::new();
jwks.add_key(key).unwrap();
jwks.add_key(duplicate).unwrap();
let found = jwks.get_by_thumbprint(&thumbprint).unwrap();
assert_eq!(found.thumbprint(), thumbprint);
}
#[cfg(not(target_arch = "wasm32"))]
#[tokio::test]
async fn test_jwkset_implements_store() {
let json = r#"{"keys": [{"kty": "oct", "kid": "test-key", "k": "AQAB"}]}"#;
let store: KeySet = serde_json::from_str(json).unwrap();
let key = store.get_key("test-key").await.unwrap();
assert!(key.is_some());
assert_eq!(key.unwrap().kid(), Some("test-key"));
let missing = store.get_key("nonexistent").await.unwrap();
assert!(missing.is_none());
let keyset = store.get_keyset().await.unwrap();
assert_eq!(keyset.len(), 1);
}
}