mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-06 16:02:23 +00:00
fix: tolerate encryption keys in a guest JWKS set
jsonwebtoken 8.3 models a JWK's `alg` as its signing `Algorithm` enum, so a set carrying an encryption key (`alg: "RSA-OAEP"`, absent from that enum) alongside a signing key failed whole-set deserialization and the whole JWKS was rejected, which real issuers publish. Parse each key on its own and skip one that does not model as a JWT key, keeping the usable signing keys. Unit-tested with a mixed set. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VF3v6LA9399gNphmZaHYG3
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
e2c6e659ea
commit
1845a9c10f
@@ -9,7 +9,7 @@ use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use jsonwebtoken::{
|
||||
jwk::{AlgorithmParameters, Jwk, JwkSet, PublicKeyUse},
|
||||
jwk::{AlgorithmParameters, Jwk, PublicKeyUse},
|
||||
Algorithm, DecodingKey, Validation,
|
||||
};
|
||||
use quick_cache::sync::Cache;
|
||||
@@ -249,11 +249,23 @@ pub async fn fetch_jwks(url: &str) -> Result<HashMap<String, Jwk>> {
|
||||
}
|
||||
body.extend_from_slice(&chunk);
|
||||
}
|
||||
let set: JwkSet = serde_json::from_slice(&body)
|
||||
.map_err(|e| Error::BadRequest(format!("JWKS is not a JSON Web Key Set: {e}")))?;
|
||||
let keys: HashMap<String, Jwk> = set
|
||||
.keys
|
||||
.into_iter()
|
||||
parse_jwks_keys(&body)
|
||||
}
|
||||
|
||||
/// The usable signing keys in a JWKS body, by `kid`. Each key is parsed on its own and
|
||||
/// one that does not model as a JWT key is skipped, not fatal: a set may legitimately
|
||||
/// carry an encryption key (say `alg: "RSA-OAEP"`, which is not in jsonwebtoken's signing
|
||||
/// `Algorithm` enum and would fail whole-set deserialization) beside its signing keys.
|
||||
fn parse_jwks_keys(body: &[u8]) -> Result<HashMap<String, Jwk>> {
|
||||
let set: serde_json::Value = serde_json::from_slice(body)
|
||||
.map_err(|e| Error::BadRequest(format!("JWKS is not JSON: {e}")))?;
|
||||
let entries = set
|
||||
.get("keys")
|
||||
.and_then(|k| k.as_array())
|
||||
.ok_or_else(|| Error::BadRequest("JWKS has no `keys` array".to_string()))?;
|
||||
let keys: HashMap<String, Jwk> = entries
|
||||
.iter()
|
||||
.filter_map(|entry| serde_json::from_value::<Jwk>(entry.clone()).ok())
|
||||
.filter(|jwk| jwk_algorithms(jwk).is_some())
|
||||
.filter_map(|jwk| jwk.common.key_id.clone().map(|kid| (kid, jwk)))
|
||||
.collect();
|
||||
@@ -444,6 +456,22 @@ mod tests {
|
||||
assert_eq!(jwk_algorithms(&k), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_mixed_use_jwks_keeps_only_the_signing_keys() {
|
||||
// An encryption key (RSA-OAEP is not in jsonwebtoken's signing Algorithm enum)
|
||||
// beside a signing key must not fail the whole set.
|
||||
let body = serde_json::json!({
|
||||
"keys": [
|
||||
{"kty":"RSA","alg":"RSA-OAEP","kid":"enc","use":"enc","n":"aa","e":"AQAB"},
|
||||
{"kty":"EC","crv":"P-256","kid":"sig","x":"aa","y":"bb"}
|
||||
]
|
||||
})
|
||||
.to_string();
|
||||
let keys = parse_jwks_keys(body.as_bytes()).expect("the signing key survives");
|
||||
assert!(keys.contains_key("sig"));
|
||||
assert!(!keys.contains_key("enc"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn key_ops_without_verify_is_refused() {
|
||||
let enc = jwk(serde_json::json!({"kty":"RSA","key_ops":["encrypt"],"n":"aa","e":"AQAB"}));
|
||||
|
||||
Reference in New Issue
Block a user