1use anyhow::{anyhow, Result};
2use base64::Engine;
3use jsonwebtoken::errors::ErrorKind;
4use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Header, Validation};
5use serde_json::Value;
6use std::collections::HashMap;
7use std::error::Error;
8use std::fmt;
9use zeroize::Zeroize;
10
11use crate::utils::compression;
12
13#[derive(Debug, Clone)]
15pub enum JwtError {
16 InvalidSignature,
18 ExpiredSignature,
20 ImmatureSignature,
22 InvalidAlgorithm,
24 Other(String),
26}
27
28impl fmt::Display for JwtError {
29 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30 match self {
31 JwtError::InvalidSignature => write!(f, "Invalid signature"),
32 JwtError::ExpiredSignature => write!(f, "Expired signature"),
33 JwtError::ImmatureSignature => write!(f, "Immature signature"),
34 JwtError::InvalidAlgorithm => write!(f, "Invalid algorithm"),
35 JwtError::Other(msg) => write!(f, "JWT error: {msg}"),
36 }
37 }
38}
39
40impl Error for JwtError {}
41
42impl From<ErrorKind> for JwtError {
43 fn from(kind: ErrorKind) -> Self {
44 match kind {
45 ErrorKind::InvalidSignature => JwtError::InvalidSignature,
46 ErrorKind::ExpiredSignature => JwtError::ExpiredSignature,
47 ErrorKind::ImmatureSignature => JwtError::ImmatureSignature,
48 ErrorKind::InvalidAlgorithm => JwtError::InvalidAlgorithm,
49 _ => JwtError::Other(format!("{kind:?}")),
50 }
51 }
52}
53
54#[derive(Debug, Clone)]
58pub struct DecodedToken {
59 pub header: HashMap<String, Value>,
60 pub claims: Value,
61 pub algorithm: Algorithm,
62}
63
64#[derive(Debug, Clone)]
66pub struct DecodedJweToken {
67 pub header: HashMap<String, Value>,
68 pub encrypted_key: String,
69 pub iv: String,
70 pub ciphertext: String,
71 pub tag: String,
72 pub algorithm: String,
73 pub encryption: String,
74}
75
76#[derive(Debug, Clone, PartialEq)]
78pub enum TokenType {
79 Jwt,
80 Jwe,
81 Unknown,
82}
83
84pub enum KeyData<'a> {
86 Secret(&'a str),
88 PrivateKeyPem(&'a str),
90 #[allow(dead_code)]
92 PrivateKeyDer(&'a [u8]),
93 None,
95}
96
97pub struct EncodeOptions<'a> {
99 pub algorithm: &'a str,
101 pub key_data: KeyData<'a>,
103 pub header_params: Option<HashMap<&'a str, &'a str>>,
105 pub compress_payload: bool,
107}
108
109impl<'a> Default for EncodeOptions<'a> {
110 fn default() -> Self {
111 Self {
112 algorithm: "HS256",
113 key_data: KeyData::Secret(""),
114 header_params: None,
115 compress_payload: false,
116 }
117 }
118}
119
120#[allow(dead_code)]
122pub fn encode(claims: &Value, secret: &str, alg_str: &str) -> Result<String> {
123 let options = EncodeOptions {
124 algorithm: alg_str,
125 key_data: KeyData::Secret(secret),
126 header_params: None,
127 compress_payload: false,
128 };
129
130 encode_with_options(claims, &options)
131}
132
133pub fn encode_with_options(claims: &Value, options: &EncodeOptions) -> Result<String> {
135 use std::collections::BTreeMap;
136
137 let algorithm = match options.algorithm.to_uppercase().as_str() {
139 "HS256" => Algorithm::HS256,
140 "HS384" => Algorithm::HS384,
141 "HS512" => Algorithm::HS512,
142 "RS256" => Algorithm::RS256,
143 "RS384" => Algorithm::RS384,
144 "RS512" => Algorithm::RS512,
145 "ES256" => Algorithm::ES256,
146 "ES384" => Algorithm::ES384,
147 "ES512" => {
148 return encode_with_josekit_jwt(claims, options, "ES512");
150 }
151 "PS256" => Algorithm::PS256,
152 "PS384" => Algorithm::PS384,
153 "PS512" => Algorithm::PS512,
154 "EDDSA" => Algorithm::EdDSA,
155 "NONE" => Algorithm::HS256, _ => {
157 return Err(anyhow!(
158 "Unsupported algorithm '{}'. Supported algorithms: HS256, HS384, HS512, RS256, RS384, RS512, ES256, ES384, ES512, PS256, PS384, PS512, EdDSA, none",
159 options.algorithm
160 ))
161 }
162 };
163
164 if options.compress_payload {
166 return encode_compressed_jwt(claims, options, algorithm);
167 }
168
169 let mut header = Header::new(algorithm);
171 if let Some(params) = &options.header_params {
172 for (key, value) in params {
173 match *key {
175 "typ" => header.typ = Some(value.to_string()),
176 "cty" => header.cty = Some(value.to_string()),
177 _ => { }
178 }
179 }
180 }
181
182 if options.algorithm.to_uppercase() == "NONE" {
184 let mut header_map = BTreeMap::new();
186 header_map.insert("alg".to_string(), Value::String("none".to_string()));
187 header_map.insert("typ".to_string(), Value::String("JWT".to_string()));
188
189 if let Some(params) = &options.header_params {
191 for (key, value) in params {
192 header_map.insert(key.to_string(), Value::String(value.to_string()));
193 }
194 }
195
196 let header_json = serde_json::to_string(&header_map)?;
197 let claims_json = serde_json::to_string(claims)?;
198
199 let encoded_header =
200 base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(header_json.as_bytes());
201 let encoded_claims =
202 base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(claims_json.as_bytes());
203
204 return Ok(format!("{encoded_header}.{encoded_claims}.''"));
205 }
206
207 let encoding_key = match &options.key_data {
209 KeyData::Secret(secret) => match algorithm {
210 Algorithm::HS256 | Algorithm::HS384 | Algorithm::HS512 => {
211 EncodingKey::from_secret(secret.as_bytes())
212 }
213 _ => {
214 return Err(anyhow!(
215 "Secret key provided but algorithm {:?} is not an HMAC algorithm. Use HS256, HS384, or HS512 for secret keys",
216 algorithm
217 ))
218 }
219 },
220 KeyData::PrivateKeyPem(pem) => match algorithm {
221 Algorithm::RS256
222 | Algorithm::RS384
223 | Algorithm::RS512
224 | Algorithm::PS256
225 | Algorithm::PS384
226 | Algorithm::PS512 => EncodingKey::from_rsa_pem(pem.as_bytes())?,
227 Algorithm::ES256 | Algorithm::ES384 => EncodingKey::from_ec_pem(pem.as_bytes())?,
228 Algorithm::EdDSA => EncodingKey::from_ed_pem(pem.as_bytes())?,
229 _ => {
230 return Err(anyhow!(
231 "Algorithm {:?} not compatible with PEM key",
232 algorithm
233 ))
234 }
235 },
236 KeyData::PrivateKeyDer(der) => match algorithm {
237 Algorithm::RS256
238 | Algorithm::RS384
239 | Algorithm::RS512
240 | Algorithm::PS256
241 | Algorithm::PS384
242 | Algorithm::PS512 => EncodingKey::from_rsa_der(der),
243 Algorithm::ES256 | Algorithm::ES384 => EncodingKey::from_ec_der(der),
244 Algorithm::EdDSA => EncodingKey::from_ed_der(der),
245 _ => {
246 return Err(anyhow!(
247 "Algorithm {:?} not compatible with DER key",
248 algorithm
249 ))
250 }
251 },
252 KeyData::None => {
253 return Err(anyhow!(
254 "No key or secret provided for algorithm {:?}. Please provide a secret (for HMAC) or private key (for RSA/ECDSA/EdDSA)",
255 algorithm
256 ))
257 }
258 };
259
260 let token = jsonwebtoken::encode(&header, claims, &encoding_key)?;
262 Ok(token)
263}
264
265fn encode_compressed_jwt(
267 claims: &Value,
268 options: &EncodeOptions,
269 algorithm: Algorithm,
270) -> Result<String> {
271 use std::collections::BTreeMap;
272
273 let mut header_map = BTreeMap::new();
275 header_map.insert(
276 "alg".to_string(),
277 Value::String(options.algorithm.to_string()),
278 );
279 header_map.insert("typ".to_string(), Value::String("JWT".to_string()));
280 header_map.insert("zip".to_string(), Value::String("DEF".to_string()));
281
282 if let Some(params) = &options.header_params {
284 for (key, value) in params {
285 if *key != "zip" {
286 header_map.insert(key.to_string(), Value::String(value.to_string()));
288 }
289 }
290 }
291
292 let claims_json = serde_json::to_string(claims)?;
294 let compressed_payload = compression::compress_deflate(claims_json.as_bytes())?;
295
296 let header_json = serde_json::to_string(&header_map)?;
298 let encoded_header =
299 base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(header_json.as_bytes());
300 let encoded_payload =
301 base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(&compressed_payload);
302
303 if options.algorithm.to_uppercase() == "NONE" {
305 return Ok(format!("{encoded_header}.{encoded_payload}.''"));
306 }
307
308 let message = format!("{encoded_header}.{encoded_payload}");
310
311 let mut signature = match &options.key_data {
313 KeyData::Secret(secret) => match algorithm {
314 Algorithm::HS256 => {
315 let mut mac = hmac_sha256::HMAC::mac(message.as_bytes(), secret.as_bytes());
316 let sig = mac.to_vec();
317 mac.zeroize();
318 sig
319 }
320 Algorithm::HS384 | Algorithm::HS512 => {
321 return Err(anyhow!("HS384/HS512 with compression not yet supported"));
324 }
325 _ => return Err(anyhow!("HMAC algorithms require a secret key")),
326 },
327 _ => {
328 return Err(anyhow!(
329 "Only HMAC-SHA256 is currently supported for compressed JWTs"
330 ))
331 }
332 };
333
334 let encoded_signature = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(&signature);
336 signature.zeroize();
337
338 Ok(format!(
339 "{encoded_header}.{encoded_payload}.{encoded_signature}"
340 ))
341}
342
343pub fn detect_token_type(token: &str) -> TokenType {
345 let parts: Vec<&str> = token.split('.').collect();
346 match parts.len() {
347 3 => TokenType::Jwt,
348 5 => TokenType::Jwe,
349 _ => TokenType::Unknown,
350 }
351}
352
353pub fn decode_jwe(token: &str) -> Result<DecodedJweToken> {
355 let parts: Vec<&str> = token.split('.').collect();
357 if parts.len() != 5 {
358 return Err(anyhow!(
359 "Invalid JWE token format: expected 5 parts, got {}",
360 parts.len()
361 ));
362 }
363
364 let header_b64 = parts[0];
366 let header_bytes = base64::engine::general_purpose::URL_SAFE_NO_PAD
367 .decode(header_b64)
368 .map_err(|_| anyhow!("Invalid JWE header encoding"))?;
369 let header_str = String::from_utf8(header_bytes)?;
370 let header: HashMap<String, Value> = serde_json::from_str(&header_str)?;
371
372 let alg_value = header
374 .get("alg")
375 .ok_or_else(|| anyhow!("Missing 'alg' in JWE header"))?;
376 let algorithm = alg_value
377 .as_str()
378 .ok_or_else(|| anyhow!("'alg' is not a string"))?
379 .to_string();
380
381 let enc_value = header
382 .get("enc")
383 .ok_or_else(|| anyhow!("Missing 'enc' in JWE header"))?;
384 let encryption = enc_value
385 .as_str()
386 .ok_or_else(|| anyhow!("'enc' is not a string"))?
387 .to_string();
388
389 Ok(DecodedJweToken {
390 header,
391 encrypted_key: parts[1].to_string(),
392 iv: parts[2].to_string(),
393 ciphertext: parts[3].to_string(),
394 tag: parts[4].to_string(),
395 algorithm,
396 encryption,
397 })
398}
399
400pub fn decode(token: &str) -> Result<DecodedToken> {
402 let parts: Vec<&str> = token.split('.').collect();
404 if parts.len() < 2 {
405 return Err(anyhow!(
406 "Invalid JWT token format: expected at least 2 parts (header.payload), found {} part(s)",
407 parts.len()
408 ));
409 }
410
411 let header_b64 = parts[0];
413 let header_bytes = base64::engine::general_purpose::URL_SAFE_NO_PAD
414 .decode(header_b64)
415 .map_err(|_| anyhow!("Invalid header encoding"))?;
416 let header_str = String::from_utf8(header_bytes)?;
417 let header: HashMap<String, Value> = serde_json::from_str(&header_str)?;
418
419 let alg_value = header
421 .get("alg")
422 .ok_or_else(|| anyhow!("Missing 'alg' in header"))?;
423 let alg_str = alg_value
424 .as_str()
425 .ok_or_else(|| anyhow!("'alg' is not a string"))?;
426
427 let algorithm = match alg_str.to_uppercase().as_str() {
429 "HS256" => Algorithm::HS256,
430 "HS384" => Algorithm::HS384,
431 "HS512" => Algorithm::HS512,
432 "RS256" => Algorithm::RS256,
433 "RS384" => Algorithm::RS384,
434 "RS512" => Algorithm::RS512,
435 "ES256" => Algorithm::ES256,
436 "ES384" => Algorithm::ES384,
437 "PS256" => Algorithm::PS256,
438 "PS384" => Algorithm::PS384,
439 "PS512" => Algorithm::PS512,
440 "EDDSA" => Algorithm::EdDSA,
441 "NONE" => Algorithm::HS256, _ => return Err(anyhow!("Unsupported algorithm: {}", alg_str)),
443 };
444
445 let payload_b64 = parts[1];
447 let payload_bytes = base64::engine::general_purpose::URL_SAFE_NO_PAD
448 .decode(payload_b64)
449 .map_err(|_| anyhow!("Invalid payload encoding"))?;
450
451 let is_compressed = header
453 .get("zip")
454 .and_then(|v| v.as_str())
455 .map(|s| s.to_uppercase() == "DEF")
456 .unwrap_or(false);
457
458 let payload_str = if is_compressed {
459 let decompressed_bytes = compression::decompress_deflate(&payload_bytes)
461 .map_err(|e| anyhow!("Failed to decompress payload: {}", e))?;
462 String::from_utf8(decompressed_bytes)?
463 } else {
464 String::from_utf8(payload_bytes)?
465 };
466
467 let claims: Value = serde_json::from_str(&payload_str)?;
468
469 Ok(DecodedToken {
470 header,
471 claims,
472 algorithm,
473 })
474}
475
476pub enum VerifyKeyData<'a> {
478 Secret(&'a str),
480 #[allow(dead_code)]
482 PublicKeyPem(&'a str),
483 #[allow(dead_code)]
485 PublicKeyDer(&'a [u8]),
486}
487
488pub struct VerifyOptions<'a> {
490 pub key_data: VerifyKeyData<'a>,
492 pub validate_exp: bool,
494 pub validate_nbf: bool,
496 pub leeway: u64,
498}
499
500impl<'a> Default for VerifyOptions<'a> {
501 fn default() -> Self {
502 Self {
503 key_data: VerifyKeyData::Secret(""),
504 validate_exp: false,
505 validate_nbf: false,
506 leeway: 0,
507 }
508 }
509}
510
511pub fn verify(token: &str, secret: &str) -> Result<bool> {
513 let options = VerifyOptions {
514 key_data: VerifyKeyData::Secret(secret),
515 ..Default::default()
516 };
517
518 verify_with_options(token, &options)
519}
520
521pub struct Hs256Verifier {
527 signing_input: Vec<u8>,
528 expected_sig: Vec<u8>,
529}
530
531impl Hs256Verifier {
532 pub fn verify(&self, secret: &[u8]) -> bool {
534 let mut calculated = hmac_sha256::HMAC::mac(&self.signing_input, secret);
535 let matches = calculated.as_slice() == self.expected_sig.as_slice();
536 calculated.zeroize();
537 matches
538 }
539}
540
541pub fn prepare_hs256_verifier(token: &str) -> Result<Hs256Verifier> {
546 let decoded = decode(token)?;
547 if decoded.algorithm != Algorithm::HS256 {
548 return Err(anyhow!("token is not HS256"));
549 }
550 let mut parts = token.splitn(3, '.');
551 let header_b64 = parts
552 .next()
553 .ok_or_else(|| anyhow!("Invalid token format"))?;
554 let payload_b64 = parts
555 .next()
556 .ok_or_else(|| anyhow!("Invalid token format"))?;
557 let signature_b64 = parts
558 .next()
559 .ok_or_else(|| anyhow!("Invalid token format"))?;
560 if signature_b64.is_empty() {
561 return Err(anyhow!("Empty signature"));
562 }
563 let mut signing_input = Vec::with_capacity(header_b64.len() + 1 + payload_b64.len());
564 signing_input.extend_from_slice(header_b64.as_bytes());
565 signing_input.push(b'.');
566 signing_input.extend_from_slice(payload_b64.as_bytes());
567 let expected_sig = base64::engine::general_purpose::URL_SAFE_NO_PAD
568 .decode(signature_b64)
569 .map_err(|_| anyhow!("Invalid signature encoding"))?;
570 Ok(Hs256Verifier {
571 signing_input,
572 expected_sig,
573 })
574}
575
576fn create_validation(algorithm: Algorithm, options: &VerifyOptions) -> Validation {
578 let mut validation = Validation::new(algorithm);
579 validation.validate_exp = options.validate_exp;
580 validation.validate_nbf = options.validate_nbf;
581 validation.leeway = options.leeway;
582 validation
583}
584
585fn handle_verification_result(
587 result: std::result::Result<jsonwebtoken::TokenData<Value>, jsonwebtoken::errors::Error>,
588) -> Result<bool> {
589 match result {
590 Ok(_) => Ok(true),
591 Err(e) => {
592 let jwt_error = JwtError::from(e.kind().clone());
593 if matches!(
594 jwt_error,
595 JwtError::ExpiredSignature | JwtError::ImmatureSignature
596 ) {
597 Err(anyhow::anyhow!(jwt_error))
598 } else {
599 Ok(false)
600 }
601 }
602 }
603}
604
605pub fn verify_with_options(token: &str, options: &VerifyOptions) -> Result<bool> {
607 let decoded_token = decode(token)?;
609
610 if let Some(alg) = decoded_token.header.get("alg") {
612 if let Some(alg_str) = alg.as_str() {
613 if alg_str.to_uppercase() == "NONE" {
614 return Ok(true);
616 }
617 }
618 }
619
620 let parts: Vec<&str> = token.split('.').collect();
622 if parts.len() < 3 {
623 return Err(anyhow!("Invalid token format for verification"));
624 }
625
626 let message = format!("{}.{}", parts[0], parts[1]);
628 let signature_b64 = parts[2];
629
630 if signature_b64.is_empty() {
632 return Ok(false);
633 }
634
635 let signature = base64::engine::general_purpose::URL_SAFE_NO_PAD
637 .decode(signature_b64)
638 .map_err(|_| anyhow!("Invalid signature encoding"))?;
639
640 match &options.key_data {
642 VerifyKeyData::Secret(secret) => {
643 match decoded_token.algorithm {
644 Algorithm::HS256 => {
645 let mut calculated_sig =
647 hmac_sha256::HMAC::mac(message.as_bytes(), secret.as_bytes());
648 let sig_matches = signature == calculated_sig.as_slice();
649 calculated_sig.zeroize();
650 if !sig_matches {
651 return Ok(false); }
653
654 if options.validate_exp || options.validate_nbf {
656 let validation = create_validation(Algorithm::HS256, options);
657 let decoding_key = DecodingKey::from_secret(secret.as_bytes());
658 match jsonwebtoken::decode::<Value>(token, &decoding_key, &validation) {
659 Ok(_) => Ok(true), Err(e) => Err(anyhow::anyhow!(JwtError::from(e.kind().clone()))), }
662 } else {
663 Ok(true) }
665 }
666 Algorithm::HS384 | Algorithm::HS512 => {
667 let decoding_key = DecodingKey::from_secret(secret.as_bytes());
671 let validation = create_validation(decoded_token.algorithm, options);
672 let result = jsonwebtoken::decode::<Value>(token, &decoding_key, &validation);
673 handle_verification_result(result)
674 }
675 _ => Err(anyhow!(
676 "Secret key provided but token uses algorithm {:?}. Secret keys can only verify HMAC algorithms (HS256, HS384, HS512)",
677 decoded_token.algorithm
678 )),
679 }
680 }
681 VerifyKeyData::PublicKeyPem(pem) => {
682 verify_with_public_key_pem(token, pem, decoded_token.algorithm, options)
683 }
684 VerifyKeyData::PublicKeyDer(der) => {
685 verify_with_public_key_der(token, der, decoded_token.algorithm, options)
686 }
687 }
688}
689
690fn verify_with_public_key_pem(
692 token: &str,
693 pem: &str,
694 algorithm: Algorithm,
695 options: &VerifyOptions,
696) -> Result<bool> {
697 let decoding_key = match algorithm {
698 Algorithm::RS256
699 | Algorithm::RS384
700 | Algorithm::RS512
701 | Algorithm::PS256
702 | Algorithm::PS384
703 | Algorithm::PS512 => DecodingKey::from_rsa_pem(pem.as_bytes())?,
704 Algorithm::ES256 | Algorithm::ES384 => DecodingKey::from_ec_pem(pem.as_bytes())?,
705 Algorithm::EdDSA => DecodingKey::from_ed_pem(pem.as_bytes())?,
706 _ => {
707 return Err(anyhow!(
708 "Public key provided but algorithm is {:?}",
709 algorithm
710 ))
711 }
712 };
713
714 let validation = create_validation(algorithm, options);
715 let result = jsonwebtoken::decode::<Value>(token, &decoding_key, &validation);
716 handle_verification_result(result)
717}
718
719fn verify_with_public_key_der(
721 token: &str,
722 der: &[u8],
723 algorithm: Algorithm,
724 options: &VerifyOptions,
725) -> Result<bool> {
726 let decoding_key = match algorithm {
727 Algorithm::RS256
728 | Algorithm::RS384
729 | Algorithm::RS512
730 | Algorithm::PS256
731 | Algorithm::PS384
732 | Algorithm::PS512 => DecodingKey::from_rsa_der(der),
733 Algorithm::ES256 | Algorithm::ES384 => DecodingKey::from_ec_der(der),
734 Algorithm::EdDSA => DecodingKey::from_ed_der(der),
735 _ => {
736 return Err(anyhow!(
737 "Public key provided but algorithm is {:?}",
738 algorithm
739 ))
740 }
741 };
742
743 let validation = create_validation(algorithm, options);
744 let result = jsonwebtoken::decode::<Value>(token, &decoding_key, &validation);
745 handle_verification_result(result)
746}
747
748pub fn decrypt_jwe(token: &str, key: &str) -> Result<String> {
750 use aes_gcm::aead::{Aead, KeyInit, Payload};
751 use aes_gcm::{Aes128Gcm, Aes256Gcm};
752
753 let decoded = decode_jwe(token)?;
755
756 if decoded.algorithm != "dir" {
758 return Err(anyhow!(
759 "Only 'dir' (direct encryption) is currently supported for JWE cracking"
760 ));
761 }
762
763 let iv_bytes = base64::engine::general_purpose::URL_SAFE_NO_PAD
765 .decode(&decoded.iv)
766 .map_err(|_| anyhow!("Invalid IV"))?;
767
768 let ciphertext_bytes = base64::engine::general_purpose::URL_SAFE_NO_PAD
769 .decode(&decoded.ciphertext)
770 .map_err(|_| anyhow!("Invalid ciphertext"))?;
771
772 let tag_bytes = base64::engine::general_purpose::URL_SAFE_NO_PAD
773 .decode(&decoded.tag)
774 .map_err(|_| anyhow!("Invalid authentication tag"))?;
775
776 let mut ciphertext_with_tag = ciphertext_bytes.clone();
778 ciphertext_with_tag.extend_from_slice(&tag_bytes);
779
780 let parts: Vec<&str> = token.split('.').collect();
782 let aad = parts[0].as_bytes();
783
784 let key_bytes = key.as_bytes();
786
787 match decoded.encryption.as_str() {
788 "A128GCM" => {
789 if key_bytes.len() != 16 {
790 return Err(anyhow!(
791 "A128GCM requires a 16-byte key, got {}",
792 key_bytes.len()
793 ));
794 }
795 if iv_bytes.len() != 12 {
798 return Err(anyhow!(
799 "Invalid IV length: AES-GCM requires a 12-byte nonce, got {}",
800 iv_bytes.len()
801 ));
802 }
803 let mut key_128: [u8; 16] = key_bytes
804 .try_into()
805 .map_err(|_| anyhow!("Failed to convert key to 16-byte array"))?;
806
807 let cipher = Aes128Gcm::new(&key_128.into());
808 key_128.zeroize();
809 let payload = Payload {
810 msg: &ciphertext_with_tag,
811 aad,
812 };
813
814 cipher
815 .decrypt((&iv_bytes[..]).into(), payload)
816 .map_err(|_| anyhow!("Decryption failed - incorrect key"))
817 .and_then(|plaintext| {
818 String::from_utf8(plaintext)
819 .map_err(|_| anyhow!("Decrypted payload is not valid UTF-8"))
820 })
821 }
822 "A192GCM" => {
823 Err(anyhow!(
825 "A192GCM is not supported. Use A128GCM or A256GCM instead."
826 ))
827 }
828 "A256GCM" => {
829 if key_bytes.len() != 32 {
830 return Err(anyhow!(
831 "A256GCM requires a 32-byte key, got {}",
832 key_bytes.len()
833 ));
834 }
835 if iv_bytes.len() != 12 {
838 return Err(anyhow!(
839 "Invalid IV length: AES-GCM requires a 12-byte nonce, got {}",
840 iv_bytes.len()
841 ));
842 }
843 let mut key_256: [u8; 32] = key_bytes
844 .try_into()
845 .map_err(|_| anyhow!("Failed to convert key to 32-byte array"))?;
846
847 let cipher = Aes256Gcm::new(&key_256.into());
848 key_256.zeroize();
849 let payload = Payload {
850 msg: &ciphertext_with_tag,
851 aad,
852 };
853
854 cipher
855 .decrypt((&iv_bytes[..]).into(), payload)
856 .map_err(|_| anyhow!("Decryption failed - incorrect key"))
857 .and_then(|plaintext| {
858 String::from_utf8(plaintext)
859 .map_err(|_| anyhow!("Decrypted payload is not valid UTF-8"))
860 })
861 }
862 _ => Err(anyhow!(
863 "Unsupported encryption algorithm: {}. Supported: A128GCM, A256GCM",
864 decoded.encryption
865 )),
866 }
867}
868
869pub fn detect_jwe_misconfigurations(decoded: &DecodedJweToken) -> Vec<String> {
871 let mut issues = Vec::new();
872
873 if decoded.algorithm == "none" {
875 issues.push("⚠️ Algorithm set to 'none' - encryption bypass possible".to_string());
876 }
877
878 if decoded.algorithm == "dir" && decoded.encrypted_key.is_empty() {
880 issues.push("ℹ️ Direct encryption mode - vulnerable to key brute force".to_string());
881 }
882
883 match decoded.encryption.as_str() {
885 "A128GCM" => issues.push("⚠️ 128-bit encryption - consider using A256GCM".to_string()),
886 "A128CBC-HS256" => issues
887 .push("⚠️ CBC mode - potentially vulnerable to padding oracle attacks".to_string()),
888 "A192CBC-HS384" => issues
889 .push("⚠️ CBC mode - potentially vulnerable to padding oracle attacks".to_string()),
890 "A256CBC-HS512" => issues
891 .push("⚠️ CBC mode - potentially vulnerable to padding oracle attacks".to_string()),
892 _ => {}
893 }
894
895 if let Some(zip_value) = decoded.header.get("zip") {
897 if zip_value.as_str() == Some("DEF") {
898 issues.push(
899 "⚠️ Compression enabled - may be vulnerable to CRIME-like attacks".to_string(),
900 );
901 }
902 }
903
904 if !decoded.iv.is_empty() {
906 match base64::engine::general_purpose::URL_SAFE_NO_PAD.decode(&decoded.iv) {
907 Ok(iv_bytes) => {
908 if iv_bytes.len() < 12 {
909 issues
910 .push("⚠️ IV too short - should be at least 96 bits for GCM".to_string());
911 }
912 if iv_bytes.len() >= 2 && iv_bytes.windows(2).all(|w| w[0] == w[1]) {
914 issues.push("⚠️ IV appears to be a test/dummy value".to_string());
915 }
916 }
917 Err(_) => issues.push("⚠️ IV encoding is invalid".to_string()),
918 }
919 }
920
921 if !decoded.tag.is_empty() {
923 match base64::engine::general_purpose::URL_SAFE_NO_PAD.decode(&decoded.tag) {
924 Ok(tag_bytes) => {
925 if tag_bytes.len() < 16 {
926 issues.push("⚠️ Authentication tag too short".to_string());
927 }
928 if tag_bytes.len() >= 2 && tag_bytes.windows(2).all(|w| w[0] == w[1]) {
930 issues.push(
931 "⚠️ Authentication tag appears to be a test/dummy value".to_string(),
932 );
933 }
934 }
935 Err(_) => issues.push("⚠️ Authentication tag encoding is invalid".to_string()),
936 }
937 }
938
939 issues
940}
941
942fn encode_with_josekit_jwt(
944 claims: &Value,
945 options: &EncodeOptions,
946 alg_str: &str,
947) -> Result<String> {
948 use josekit::jwk::alg::ec::{EcCurve, EcKeyPair};
949 use josekit::jws::{JwsHeader, ES512};
950
951 let mut header = JwsHeader::new();
953 header.set_algorithm(alg_str);
954 header.set_token_type("JWT");
955
956 if let Some(params) = &options.header_params {
958 for (key, value) in params {
959 header.set_claim(key, Some(serde_json::Value::String(value.to_string())))?;
960 }
961 }
962
963 let payload = serde_json::to_vec(claims)?;
965
966 let signer = match &options.key_data {
968 KeyData::PrivateKeyPem(pem_str) => {
969 let key_pair = EcKeyPair::from_pem(pem_str, Some(EcCurve::P521))?;
971 let jwk = key_pair.to_jwk_key_pair();
972 ES512.signer_from_jwk(&jwk)?
973 }
974 KeyData::PrivateKeyDer(der_bytes) => {
975 let jwk = EcKeyPair::from_der(der_bytes, Some(EcCurve::P521))?;
977 ES512.signer_from_jwk(&jwk.to_jwk_key_pair())?
978 }
979 KeyData::Secret(_) => {
980 return Err(anyhow!(
981 "ES512 requires an EC private key (PEM or DER), not a secret"
982 ));
983 }
984 KeyData::None => {
985 return Err(anyhow!("ES512 requires an EC private key"));
986 }
987 };
988
989 let jwt = josekit::jws::serialize_compact(&payload, &header, &*signer)?;
991 Ok(jwt)
992}
993
994#[deprecated(note = "Use encode_jwe instead for real encryption")]
996pub fn encode_jwe_demo(payload: &str, _recipient_key: &str) -> Result<String> {
997 let header_json = serde_json::json!({
1001 "alg": "dir",
1002 "enc": "A256GCM"
1003 });
1004
1005 let header_str = serde_json::to_string(&header_json)?;
1006 let encoded_header =
1007 base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(header_str.as_bytes());
1008
1009 let encrypted_key = ""; let iv = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(b"dummy_iv_123456");
1012 let ciphertext = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(payload.as_bytes());
1013 let tag = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(b"dummy_tag");
1014
1015 Ok(format!(
1017 "{}.{}.{}.{}.{}",
1018 encoded_header, encrypted_key, iv, ciphertext, tag
1019 ))
1020}
1021
1022pub enum JweKeyManagement<'a> {
1027 Direct(&'a str),
1029 RsaOaep(&'a str),
1031 RsaOaep256(&'a str),
1033 EcdhEs(&'a str),
1035 EcdhEsA128kw(&'a str),
1037 EcdhEsA256kw(&'a str),
1039 A128kw(&'a str), A256kw(&'a str), }
1044
1045pub enum JweContentEncryption {
1047 A128GCM,
1049 A256GCM,
1051}
1052
1053pub fn encode_jwe(
1055 payload: &str,
1056 key_mgmt: JweKeyManagement,
1057 content_enc: JweContentEncryption,
1058) -> Result<String> {
1059 use josekit::jwe::{serialize_compact, JweHeader};
1060
1061 let mut header = JweHeader::new();
1063
1064 let (alg_name, encrypter): (&str, Box<dyn josekit::jwe::JweEncrypter>) = match key_mgmt {
1066 JweKeyManagement::Direct(key) => {
1067 use josekit::jwe::Dir;
1068 header.set_content_encryption(match content_enc {
1069 JweContentEncryption::A128GCM => "A128GCM",
1070 JweContentEncryption::A256GCM => "A256GCM",
1071 });
1072 let encrypter = Dir.encrypter_from_bytes(key.as_bytes())?;
1073 ("dir", Box::new(encrypter))
1074 }
1075 JweKeyManagement::RsaOaep(pem) => {
1076 use josekit::jwe::RSA_OAEP;
1077 header.set_content_encryption(match content_enc {
1078 JweContentEncryption::A128GCM => "A128GCM",
1079 JweContentEncryption::A256GCM => "A256GCM",
1080 });
1081 let encrypter = RSA_OAEP.encrypter_from_pem(pem)?;
1082 ("RSA-OAEP", Box::new(encrypter))
1083 }
1084 JweKeyManagement::RsaOaep256(pem) => {
1085 use josekit::jwe::RSA_OAEP_256;
1086 header.set_content_encryption(match content_enc {
1087 JweContentEncryption::A128GCM => "A128GCM",
1088 JweContentEncryption::A256GCM => "A256GCM",
1089 });
1090 let encrypter = RSA_OAEP_256.encrypter_from_pem(pem)?;
1091 ("RSA-OAEP-256", Box::new(encrypter))
1092 }
1093 JweKeyManagement::EcdhEs(pem) => {
1094 use josekit::jwe::ECDH_ES;
1095 header.set_content_encryption(match content_enc {
1096 JweContentEncryption::A128GCM => "A128GCM",
1097 JweContentEncryption::A256GCM => "A256GCM",
1098 });
1099 let encrypter = ECDH_ES.encrypter_from_pem(pem)?;
1100 ("ECDH-ES", Box::new(encrypter))
1101 }
1102 JweKeyManagement::EcdhEsA128kw(pem) => {
1103 use josekit::jwe::ECDH_ES_A128KW;
1104 header.set_content_encryption(match content_enc {
1105 JweContentEncryption::A128GCM => "A128GCM",
1106 JweContentEncryption::A256GCM => "A256GCM",
1107 });
1108 let encrypter = ECDH_ES_A128KW.encrypter_from_pem(pem)?;
1109 ("ECDH-ES+A128KW", Box::new(encrypter))
1110 }
1111 JweKeyManagement::EcdhEsA256kw(pem) => {
1112 use josekit::jwe::ECDH_ES_A256KW;
1113 header.set_content_encryption(match content_enc {
1114 JweContentEncryption::A128GCM => "A128GCM",
1115 JweContentEncryption::A256GCM => "A256GCM",
1116 });
1117 let encrypter = ECDH_ES_A256KW.encrypter_from_pem(pem)?;
1118 ("ECDH-ES+A256KW", Box::new(encrypter))
1119 }
1120 JweKeyManagement::A128kw(key) => {
1121 use josekit::jwe::A128KW;
1122 header.set_content_encryption(match content_enc {
1123 JweContentEncryption::A128GCM => "A128GCM",
1124 JweContentEncryption::A256GCM => "A256GCM",
1125 });
1126 let encrypter = A128KW.encrypter_from_bytes(key.as_bytes())?;
1127 ("A128KW", Box::new(encrypter))
1128 }
1129 JweKeyManagement::A256kw(key) => {
1130 use josekit::jwe::A256KW;
1131 header.set_content_encryption(match content_enc {
1132 JweContentEncryption::A128GCM => "A128GCM",
1133 JweContentEncryption::A256GCM => "A256GCM",
1134 });
1135 let encrypter = A256KW.encrypter_from_bytes(key.as_bytes())?;
1136 ("A256KW", Box::new(encrypter))
1137 }
1138 };
1139
1140 header.set_algorithm(alg_name);
1141
1142 let jwe = serialize_compact(payload.as_bytes(), &header, &*encrypter)?;
1144 Ok(jwe)
1145}
1146
1147pub fn decrypt_jwe_with_josekit(token: &str, key_mgmt: JweKeyManagement) -> Result<String> {
1149 use josekit::jwe::deserialize_compact;
1150
1151 let decrypter: Box<dyn josekit::jwe::JweDecrypter> = match key_mgmt {
1153 JweKeyManagement::Direct(key) => {
1154 use josekit::jwe::Dir;
1155 Box::new(Dir.decrypter_from_bytes(key.as_bytes())?)
1156 }
1157 JweKeyManagement::RsaOaep(pem) => {
1158 use josekit::jwe::RSA_OAEP;
1159 Box::new(RSA_OAEP.decrypter_from_pem(pem)?)
1160 }
1161 JweKeyManagement::RsaOaep256(pem) => {
1162 use josekit::jwe::RSA_OAEP_256;
1163 Box::new(RSA_OAEP_256.decrypter_from_pem(pem)?)
1164 }
1165 JweKeyManagement::EcdhEs(pem) => {
1166 use josekit::jwe::ECDH_ES;
1167 Box::new(ECDH_ES.decrypter_from_pem(pem)?)
1168 }
1169 JweKeyManagement::EcdhEsA128kw(pem) => {
1170 use josekit::jwe::ECDH_ES_A128KW;
1171 Box::new(ECDH_ES_A128KW.decrypter_from_pem(pem)?)
1172 }
1173 JweKeyManagement::EcdhEsA256kw(pem) => {
1174 use josekit::jwe::ECDH_ES_A256KW;
1175 Box::new(ECDH_ES_A256KW.decrypter_from_pem(pem)?)
1176 }
1177 JweKeyManagement::A128kw(key) => {
1178 use josekit::jwe::A128KW;
1179 Box::new(A128KW.decrypter_from_bytes(key.as_bytes())?)
1180 }
1181 JweKeyManagement::A256kw(key) => {
1182 use josekit::jwe::A256KW;
1183 Box::new(A256KW.decrypter_from_bytes(key.as_bytes())?)
1184 }
1185 };
1186
1187 let (payload, _header) = deserialize_compact(token, &*decrypter)?;
1189 let plaintext = String::from_utf8(payload)?;
1190 Ok(plaintext)
1191}
1192
1193#[cfg(test)]
1194mod tests {
1195 use super::*;
1196 use base64::Engine; use chrono::{Duration, Utc};
1198 use serde_json::json;
1199 use std::collections::HashMap;
1200 use std::fs;
1201
1202 const RSA_PRIVATE_KEY_PEM_PATH: &str = "src/jwt/test_rsa_private.pem";
1204 const RSA_PUBLIC_KEY_PEM_PATH: &str = "src/jwt/test_rsa_public.pem"; const EC_PRIVATE_KEY_PEM_PATH: &str = "src/jwt/test_ec_private.pem";
1206 const ED25519_PRIVATE_KEY_PEM_PATH: &str = "src/jwt/test_ed25519_private.pem";
1207
1208 #[test]
1209 fn test_encode_hs256() {
1210 let claims = json!({"user": "test"});
1211 let options = EncodeOptions {
1212 algorithm: "HS256",
1213 key_data: KeyData::Secret("test_secret"),
1214 header_params: None,
1215 compress_payload: false,
1216 };
1217 let result = encode_with_options(&claims, &options);
1218 assert!(result.is_ok());
1219
1220 let token_str = result.unwrap();
1222 let decoded_result = decode(&token_str);
1223 assert!(decoded_result.is_ok());
1224 let decoded_token = decoded_result.unwrap();
1225
1226 assert_eq!(
1227 decoded_token.header.get("alg").unwrap().as_str().unwrap(),
1228 "HS256"
1229 );
1230 assert_eq!(decoded_token.claims, claims);
1231 }
1232
1233 #[test]
1234 fn test_encode_rs256() {
1235 let rsa_private_key = fs::read_to_string(RSA_PRIVATE_KEY_PEM_PATH)
1236 .expect("Should have been able to read the RSA private key file");
1237
1238 let claims = json!({"user": "test_rs256"});
1239 let options = EncodeOptions {
1240 algorithm: "RS256",
1241 key_data: KeyData::PrivateKeyPem(&rsa_private_key),
1242 header_params: None,
1243 compress_payload: false,
1244 };
1245 let result = encode_with_options(&claims, &options);
1246 assert!(result.is_err());
1248
1249 }
1257
1258 #[test]
1259 fn test_encode_es256() {
1260 let ec_private_key = fs::read_to_string(EC_PRIVATE_KEY_PEM_PATH)
1261 .expect("Should have been able to read the EC private key file");
1262
1263 let claims = json!({"user": "test_es256"});
1264 let options = EncodeOptions {
1265 algorithm: "ES256",
1266 key_data: KeyData::PrivateKeyPem(&ec_private_key),
1267 header_params: None,
1268 compress_payload: false,
1269 };
1270 let result = encode_with_options(&claims, &options);
1271 assert!(result.is_err());
1273
1274 }
1282
1283 #[test]
1284 fn test_encode_eddsa() {
1285 let ed25519_private_key = fs::read_to_string(ED25519_PRIVATE_KEY_PEM_PATH)
1286 .expect("Should have been able to read the Ed25519 private key file");
1287
1288 let claims = json!({"user": "test_eddsa"});
1289 let options = EncodeOptions {
1290 algorithm: "EdDSA",
1291 key_data: KeyData::PrivateKeyPem(&ed25519_private_key),
1292 header_params: None,
1293 compress_payload: false,
1294 };
1295 let result = encode_with_options(&claims, &options);
1296 assert!(result.is_err());
1298
1299 }
1307
1308 #[test]
1309 fn test_encode_none_algorithm() {
1310 let claims = json!({"user": "test_none"});
1311 let options = EncodeOptions {
1312 algorithm: "none",
1313 key_data: KeyData::None, header_params: None,
1315 compress_payload: false,
1316 };
1317 let result = encode_with_options(&claims, &options);
1318 assert!(result.is_ok());
1319
1320 let token_str = result.unwrap();
1321 let parts: Vec<&str> = token_str.split('.').collect();
1322 assert_eq!(parts.len(), 3, "Token should have three parts");
1323 assert_eq!(
1324 parts[2], "''",
1325 "Signature part should be empty for 'none' algorithm"
1326 ); let header_b64 = parts[0];
1329 let header_bytes_result =
1330 base64::engine::general_purpose::URL_SAFE_NO_PAD.decode(header_b64);
1331 assert!(
1332 header_bytes_result.is_ok(),
1333 "Header should be valid Base64Url"
1334 );
1335 let header_bytes = header_bytes_result.unwrap();
1336
1337 let header_str_result = String::from_utf8(header_bytes);
1338 assert!(header_str_result.is_ok(), "Header should be valid UTF-8");
1339 let header_str = header_str_result.unwrap();
1340
1341 let header_json_result: Result<Value, _> = serde_json::from_str(&header_str);
1342 assert!(header_json_result.is_ok(), "Header should be valid JSON");
1343 let header_json = header_json_result.unwrap();
1344
1345 assert_eq!(header_json.get("alg").unwrap().as_str().unwrap(), "none");
1346 }
1347
1348 #[test]
1349 fn test_encode_with_header_params() {
1350 let claims = json!({"user": "test_header_params"});
1351 let mut header_params = HashMap::new();
1352 header_params.insert("kid", "test_key_id");
1353 header_params.insert("custom_param", "custom_value");
1354
1355 let options = EncodeOptions {
1356 algorithm: "HS256",
1357 key_data: KeyData::Secret("test_secret_for_header_params"),
1358 header_params: Some(header_params),
1359 compress_payload: false,
1360 };
1361 let result = encode_with_options(&claims, &options);
1362 assert!(result.is_ok());
1363
1364 let token_str = result.unwrap();
1365 let decoded_result = decode(&token_str);
1366 assert!(decoded_result.is_ok());
1367 let decoded_token = decoded_result.unwrap();
1368
1369 assert_eq!(
1370 decoded_token.header.get("alg").unwrap().as_str().unwrap(),
1371 "HS256"
1372 );
1373 let mut header_params_for_cty = HashMap::new();
1453 header_params_for_cty.insert("cty", "test_content_type");
1454
1455 let options_cty = EncodeOptions {
1456 algorithm: "HS256",
1457 key_data: KeyData::Secret("test_secret_for_cty"),
1458 header_params: Some(header_params_for_cty),
1459 compress_payload: false,
1460 };
1461 let result_cty = encode_with_options(&claims, &options_cty);
1462 assert!(result_cty.is_ok(), "Encoding with cty should succeed");
1463 let token_cty_str = result_cty.unwrap();
1464 let decoded_cty_result = decode(&token_cty_str);
1465 assert!(
1466 decoded_cty_result.is_ok(),
1467 "Decoding cty token should succeed"
1468 );
1469 let decoded_cty_token = decoded_cty_result.unwrap();
1470 assert_eq!(
1471 decoded_cty_token
1472 .header
1473 .get("cty")
1474 .unwrap()
1475 .as_str()
1476 .unwrap(),
1477 "test_content_type"
1478 );
1479
1480 let mut header_params_for_none = HashMap::new();
1490 header_params_for_none.insert("kid", "test_key_id_for_none");
1491 header_params_for_none.insert("custom_field", "custom_value_for_none");
1492
1493 let options_none_custom = EncodeOptions {
1494 algorithm: "none",
1495 key_data: KeyData::None,
1496 header_params: Some(header_params_for_none),
1497 compress_payload: false,
1498 };
1499 let result_none_custom = encode_with_options(&claims, &options_none_custom);
1500 assert!(
1501 result_none_custom.is_ok(),
1502 "Encoding with none and custom params should succeed"
1503 );
1504 let token_none_custom_str = result_none_custom.unwrap();
1505
1506 let parts_none_custom: Vec<&str> = token_none_custom_str.split('.').collect();
1507 assert_eq!(parts_none_custom.len(), 3);
1508 let header_none_custom_b64 = parts_none_custom[0];
1509 let header_none_custom_bytes = base64::engine::general_purpose::URL_SAFE_NO_PAD
1510 .decode(header_none_custom_b64)
1511 .unwrap();
1512 let header_none_custom_str = String::from_utf8(header_none_custom_bytes).unwrap();
1513 let header_none_custom_json: Value = serde_json::from_str(&header_none_custom_str).unwrap();
1514
1515 assert_eq!(
1516 header_none_custom_json
1517 .get("alg")
1518 .unwrap()
1519 .as_str()
1520 .unwrap(),
1521 "none"
1522 );
1523 assert_eq!(
1524 header_none_custom_json
1525 .get("kid")
1526 .unwrap()
1527 .as_str()
1528 .unwrap(),
1529 "test_key_id_for_none"
1530 );
1531 assert_eq!(
1532 header_none_custom_json
1533 .get("custom_field")
1534 .unwrap()
1535 .as_str()
1536 .unwrap(),
1537 "custom_value_for_none"
1538 );
1539 }
1540
1541 #[test]
1542 fn test_decode_valid_hs256_token() {
1543 let claims = json!({"user": "test_decode_valid"});
1544 let options = EncodeOptions {
1545 algorithm: "HS256",
1546 key_data: KeyData::Secret("test_secret_for_decode"),
1547 header_params: None,
1548 compress_payload: false,
1549 };
1550 let encode_result = encode_with_options(&claims, &options);
1551 assert!(
1552 encode_result.is_ok(),
1553 "Token encoding failed for decode test"
1554 );
1555 let token_str = encode_result.unwrap();
1556
1557 let decode_result = decode(&token_str);
1558 assert!(
1559 decode_result.is_ok(),
1560 "Decoding valid token failed. Error: {:?}",
1561 decode_result.err()
1562 );
1563 let decoded_token = decode_result.unwrap();
1564
1565 assert_eq!(
1566 decoded_token.header.get("alg").unwrap().as_str().unwrap(),
1567 "HS256"
1568 );
1569 assert_eq!(decoded_token.claims, claims);
1570 assert_eq!(decoded_token.algorithm, Algorithm::HS256);
1571 }
1572
1573 #[test]
1574 fn test_decode_token_invalid_header_base64() {
1575 let token_str = "!!!!.eyJ1c2VyIjoidGVzdCJ9."; let decode_result = decode(token_str);
1577 assert!(decode_result.is_err());
1578 let err = decode_result.err().unwrap();
1579 assert!(
1580 err.to_string().contains("Invalid header encoding"),
1581 "Unexpected error message: {err}"
1582 );
1583 }
1584
1585 #[test]
1586 fn test_decode_token_invalid_payload_base64() {
1587 let header = json!({"alg": "HS256", "typ": "JWT"});
1589 let encoded_header =
1590 base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(header.to_string().as_bytes());
1591 let token_str = format!("{encoded_header}.!!!!."); let decode_result = decode(&token_str);
1594 assert!(decode_result.is_err());
1595 let err = decode_result.err().unwrap();
1596 assert!(
1597 err.to_string().contains("Invalid payload encoding"),
1598 "Unexpected error message: {err}"
1599 );
1600 }
1601
1602 #[test]
1603 fn test_decode_token_missing_alg_in_header() {
1604 let header_no_alg = json!({"typ": "JWT"});
1605 let encoded_header_no_alg = base64::engine::general_purpose::URL_SAFE_NO_PAD
1606 .encode(header_no_alg.to_string().as_bytes());
1607 let payload = json!({"user": "test"});
1608 let encoded_payload =
1609 base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(payload.to_string().as_bytes());
1610 let token_str = format!("{encoded_header_no_alg}.{encoded_payload}.");
1611
1612 let decode_result = decode(&token_str);
1613 assert!(decode_result.is_err());
1614 let err = decode_result.err().unwrap();
1615 assert!(
1616 err.to_string().contains("Missing 'alg' in header"),
1617 "Unexpected error message: {err}"
1618 );
1619 }
1620
1621 #[test]
1622 fn test_decode_token_alg_not_a_string() {
1623 let header_alg_not_string = json!({"alg": 123, "typ": "JWT"});
1624 let encoded_header_alg_not_string = base64::engine::general_purpose::URL_SAFE_NO_PAD
1625 .encode(header_alg_not_string.to_string().as_bytes());
1626 let payload = json!({"user": "test"});
1627 let encoded_payload =
1628 base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(payload.to_string().as_bytes());
1629 let token_str = format!("{encoded_header_alg_not_string}.{encoded_payload}.");
1630
1631 let decode_result = decode(&token_str);
1632 assert!(decode_result.is_err());
1633 let err = decode_result.err().unwrap();
1634 assert!(
1635 err.to_string().contains("'alg' is not a string"),
1636 "Unexpected error message: {err}"
1637 );
1638 }
1639
1640 #[test]
1641 fn test_decode_invalid_token_format_not_enough_parts() {
1642 let token_str = "invalidtoken";
1643 let decode_result = decode(token_str);
1644 assert!(decode_result.is_err());
1645 let err = decode_result.err().unwrap();
1646 assert!(
1647 err.to_string().contains("Invalid JWT token format")
1648 && err.to_string().contains("expected at least 2 parts"),
1649 "Unexpected error message: {err}"
1650 );
1651
1652 let token_str_one_dot = "only.onepart";
1653 let decode_result_one_dot = decode(token_str_one_dot);
1654 assert!(decode_result_one_dot.is_err(), "Expected error for 'only.onepart' due to invalid header content (not base64url or not utf8 after decode)");
1658 if let Some(err) = decode_result_one_dot.err() {
1661 assert!(
1662 err.to_string().contains("Invalid header encoding")
1663 || err.to_string().contains("invalid utf-8"),
1664 "Unexpected error message for 'only.onepart': {err}"
1665 );
1666 }
1667 }
1668
1669 #[test]
1670 fn test_verify_hs256_token_correct_secret() {
1671 let claims = json!({"user": "test_verify_correct"});
1672 let options_encode = EncodeOptions {
1673 algorithm: "HS256",
1674 key_data: KeyData::Secret("correct_secret"),
1675 header_params: None,
1676 compress_payload: false,
1677 };
1678 let token_str = encode_with_options(&claims, &options_encode)
1679 .expect("Token encoding failed for verify test");
1680
1681 let options_verify = VerifyOptions {
1682 key_data: VerifyKeyData::Secret("correct_secret"),
1683 ..Default::default()
1684 };
1685 let result = verify_with_options(&token_str, &options_verify);
1686 assert!(
1687 result.is_ok(),
1688 "Verification failed for correct secret: {:?}",
1689 result.err()
1690 );
1691 assert!(
1692 result.unwrap(),
1693 "Verification returned false for correct secret"
1694 );
1695 }
1696
1697 #[test]
1698 fn test_verify_hs256_token_incorrect_secret() {
1699 let claims = json!({"user": "test_verify_incorrect"});
1700 let options_encode = EncodeOptions {
1701 algorithm: "HS256",
1702 key_data: KeyData::Secret("correct_secret"),
1703 header_params: None,
1704 compress_payload: false,
1705 };
1706 let token_str = encode_with_options(&claims, &options_encode)
1707 .expect("Token encoding failed for verify incorrect secret test");
1708
1709 let options_verify = VerifyOptions {
1710 key_data: VerifyKeyData::Secret("incorrect_secret"),
1711 ..Default::default()
1712 };
1713 let result = verify_with_options(&token_str, &options_verify);
1714 assert!(result.is_ok(), "Verification with incorrect secret should not error initially unless key format is wrong, but expect Ok(false). Error: {:?}", result.err());
1715 assert!(
1716 !result.unwrap(),
1717 "Verification returned true for incorrect secret"
1718 );
1719 }
1720
1721 #[test]
1722 fn test_prepare_hs256_verifier_matches_verify() {
1723 let claims = json!({"user": "fast_path"});
1724 let options_encode = EncodeOptions {
1725 algorithm: "HS256",
1726 key_data: KeyData::Secret("s3cret"),
1727 header_params: None,
1728 compress_payload: false,
1729 };
1730 let token = encode_with_options(&claims, &options_encode).expect("encode");
1731
1732 let v = prepare_hs256_verifier(&token).expect("prepare");
1733 assert!(v.verify(b"s3cret"));
1734 assert!(!v.verify(b"wrong"));
1735 assert!(!v.verify(b""));
1736 }
1737
1738 #[test]
1739 fn test_prepare_hs256_verifier_rejects_non_hs256() {
1740 let header = json!({"alg": "HS384", "typ": "JWT"});
1742 let claims = json!({"u": 1});
1743 let h = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(header.to_string());
1744 let c = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(claims.to_string());
1745 let sig = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode("x");
1746 let token = format!("{h}.{c}.{sig}");
1747 assert!(prepare_hs256_verifier(&token).is_err());
1748 }
1749
1750 #[test]
1751 fn test_prepare_hs256_verifier_rejects_empty_signature() {
1752 let header = json!({"alg": "HS256", "typ": "JWT"});
1753 let claims = json!({"u": 1});
1754 let h = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(header.to_string());
1755 let c = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(claims.to_string());
1756 let token = format!("{h}.{c}.");
1757 assert!(prepare_hs256_verifier(&token).is_err());
1758 }
1759
1760 #[test]
1761 fn test_prepare_hs256_verifier_rejects_malformed_token() {
1762 assert!(prepare_hs256_verifier("not.a-jwt").is_err());
1764 assert!(prepare_hs256_verifier("aaa.bbb.ccc").is_err());
1766 }
1767
1768 #[test]
1769 fn test_prepare_hs256_verifier_rejects_invalid_signature_base64() {
1770 let header = json!({"alg": "HS256", "typ": "JWT"});
1771 let claims = json!({"u": 1});
1772 let h = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(header.to_string());
1773 let c = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(claims.to_string());
1774 let token = format!("{h}.{c}.!!!not-base64!!!");
1776 assert!(prepare_hs256_verifier(&token).is_err());
1777 }
1778
1779 #[test]
1780 fn test_verify_rs256_token_correct_key() {
1781 let header = json!({"alg": "RS256", "typ": "JWT"});
1788 let claims = json!({"user": "test_rs256_verify"});
1789 let encoded_header =
1790 base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(header.to_string());
1791 let encoded_claims =
1792 base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(claims.to_string());
1793 let fake_signature =
1794 base64::engine::general_purpose::URL_SAFE_NO_PAD.encode("fake_signature");
1795 let token_str = format!("{encoded_header}.{encoded_claims}.{fake_signature}");
1796
1797 let public_key_pem_string = fs::read_to_string(RSA_PUBLIC_KEY_PEM_PATH)
1799 .unwrap_or_else(|_| String::from("-----BEGIN PUBLIC KEY-----\nTHIS IS A SHORT PLACEHOLDER PUBLIC KEY.\nWILL BE REPLACED LATER IF NEEDED.\n-----END PUBLIC KEY-----"));
1800 let options_verify = VerifyOptions {
1803 key_data: VerifyKeyData::PublicKeyPem(&public_key_pem_string),
1804 validate_exp: false,
1805 validate_nbf: false,
1806 leeway: 0,
1807 };
1808
1809 let result = verify_with_options(&token_str, &options_verify);
1810 assert!(
1814 result.is_err(),
1815 "Verification should fail with placeholder RSA public key. Result was: {result:?}"
1816 );
1817 }
1818
1819 #[test]
1820 fn test_verify_none_algorithm_token() {
1821 let claims = json!({"user": "test_none_verify"});
1822 let options_encode = EncodeOptions {
1823 algorithm: "none",
1824 key_data: KeyData::None,
1825 header_params: None,
1826 compress_payload: false,
1827 };
1828 let token_str = encode_with_options(&claims, &options_encode)
1829 .expect("Encoding 'none' algorithm token failed");
1830
1831 let options_verify = VerifyOptions {
1832 key_data: VerifyKeyData::Secret("any_secret_is_ignored_for_none"),
1834 ..Default::default()
1835 };
1836 let result = verify_with_options(&token_str, &options_verify);
1837 assert!(
1838 result.is_ok(),
1839 "Verification of 'none' token erred: {:?}",
1840 result.err()
1841 );
1842 assert!(
1843 result.unwrap(),
1844 "Verification of 'none' token returned false"
1845 );
1846 }
1847
1848 #[test]
1849 fn test_verify_token_with_exp_validation_valid() {
1850 let current_time = Utc::now();
1851 let claims = json!({
1852 "user": "test_exp_valid",
1853 "exp": (current_time + Duration::seconds(3600)).timestamp()
1854 });
1855 let options_encode = EncodeOptions {
1856 algorithm: "HS256",
1857 key_data: KeyData::Secret("secret_exp_valid"),
1858 header_params: None,
1859 compress_payload: false,
1860 };
1861 let token_str = encode_with_options(&claims, &options_encode)
1862 .expect("Token encoding for exp valid test failed");
1863
1864 let options_verify = VerifyOptions {
1865 key_data: VerifyKeyData::Secret("secret_exp_valid"),
1866 validate_exp: true,
1867 ..Default::default()
1868 };
1869 let result = verify_with_options(&token_str, &options_verify);
1870 assert!(
1871 result.is_ok(),
1872 "Verification of valid exp token erred: {:?}",
1873 result.err()
1874 );
1875 assert!(
1876 result.unwrap(),
1877 "Verification of valid exp token returned false"
1878 );
1879 }
1880
1881 #[test]
1882 fn test_verify_token_with_exp_validation_expired() {
1883 let current_time = Utc::now();
1884 let claims = json!({
1885 "user": "test_exp_expired",
1886 "exp": (current_time - Duration::seconds(3600)).timestamp()
1887 });
1888 let options_encode = EncodeOptions {
1889 algorithm: "HS256",
1890 key_data: KeyData::Secret("secret_exp_expired"),
1891 header_params: None,
1892 compress_payload: false,
1893 };
1894 let token_str = encode_with_options(&claims, &options_encode)
1895 .expect("Token encoding for exp expired test failed");
1896
1897 let options_verify = VerifyOptions {
1898 key_data: VerifyKeyData::Secret("secret_exp_expired"),
1899 validate_exp: true,
1900 ..Default::default()
1901 };
1902 let result = verify_with_options(&token_str, &options_verify);
1903 assert!(
1905 result.is_err(),
1906 "Verification of expired token should return an error. Result: {result:?}"
1907 );
1908 }
1910
1911 #[test]
1912 fn test_verify_token_with_nbf_validation_valid() {
1913 let current_time = Utc::now();
1914 let claims = json!({
1915 "user": "test_nbf_valid",
1916 "nbf": (current_time - Duration::seconds(3600)).timestamp(),
1917 "exp": (current_time + Duration::seconds(3600)).timestamp() });
1919 let options_encode = EncodeOptions {
1920 algorithm: "HS256",
1921 key_data: KeyData::Secret("secret_nbf_valid"),
1922 header_params: None,
1923 compress_payload: false,
1924 };
1925 let token_str = encode_with_options(&claims, &options_encode)
1926 .expect("Token encoding for nbf valid test failed");
1927
1928 let options_verify = VerifyOptions {
1929 key_data: VerifyKeyData::Secret("secret_nbf_valid"),
1930 validate_nbf: true,
1931 validate_exp: false, ..Default::default()
1933 };
1934 let result = verify_with_options(&token_str, &options_verify);
1935 assert!(
1936 result.is_ok(),
1937 "Verification of valid nbf token erred: {:?}",
1938 result.err()
1939 );
1940 assert!(
1941 result.unwrap(),
1942 "Verification of valid nbf token returned false"
1943 );
1944 }
1945
1946 #[test]
1947 fn test_verify_token_with_nbf_validation_not_yet_valid() {
1948 let current_time = Utc::now();
1949 let claims = json!({
1950 "user": "test_nbf_not_yet_valid",
1951 "nbf": (current_time + Duration::seconds(3600)).timestamp(),
1952 "exp": (current_time + Duration::seconds(7200)).timestamp() });
1954 let options_encode = EncodeOptions {
1955 algorithm: "HS256",
1956 key_data: KeyData::Secret("secret_nbf_not_yet_valid"),
1957 header_params: None,
1958 compress_payload: false,
1959 };
1960 let token_str = encode_with_options(&claims, &options_encode)
1961 .expect("Token encoding for nbf not yet valid test failed");
1962
1963 let options_verify = VerifyOptions {
1964 key_data: VerifyKeyData::Secret("secret_nbf_not_yet_valid"),
1965 validate_nbf: true,
1966 validate_exp: false, ..Default::default()
1968 };
1969 let result = verify_with_options(&token_str, &options_verify);
1970 assert!(
1972 result.is_err(),
1973 "Verification of not-yet-valid nbf token should return an error. Result: {result:?}"
1974 );
1975 }
1977
1978 #[test]
1979 fn test_verify_es256_token_pathway() {
1980 let header = json!({"alg": "ES256", "typ": "JWT"});
1985 let claims = json!({"user": "test_es256_verify"});
1986 let encoded_header =
1987 base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(header.to_string());
1988 let encoded_claims =
1989 base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(claims.to_string());
1990 let fake_signature =
1991 base64::engine::general_purpose::URL_SAFE_NO_PAD.encode("fake_es256_signature");
1992 let token_str = format!("{encoded_header}.{encoded_claims}.{fake_signature}");
1993
1994 let public_key_pem_string = fs::read_to_string("src/jwt/test_ec_public.pem")
1995 .expect("Should have created/found test_ec_public.pem");
1996
1997 let options_verify = VerifyOptions {
1998 key_data: VerifyKeyData::PublicKeyPem(&public_key_pem_string),
1999 validate_exp: false,
2000 validate_nbf: false,
2001 leeway: 0,
2002 };
2003
2004 let result = verify_with_options(&token_str, &options_verify);
2005 assert!(
2006 result.is_err(),
2007 "Verification should return Err with a placeholder EC public key. Result was: {result:?}"
2008 );
2009 }
2011
2012 #[test]
2013 fn test_verify_es256_token_invalid_key_format() {
2014 let token_str = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoidGVzdCJ9.c2lnbmF0dXJl"; let invalid_pem = "this is not a valid pem";
2016
2017 let options_verify = VerifyOptions {
2018 key_data: VerifyKeyData::PublicKeyPem(invalid_pem),
2019 ..Default::default()
2020 };
2021 let result = verify_with_options(token_str, &options_verify);
2022 assert!(
2023 result.is_err(),
2024 "Verification should fail with an invalid EC key format"
2025 );
2026 }
2027
2028 #[test]
2029 fn test_encode_with_compression() {
2030 let claims = json!({"sub": "test", "name": "Test User", "description": "This is a test payload for compression testing"});
2031 let options = EncodeOptions {
2032 algorithm: "none",
2033 key_data: KeyData::None,
2034 header_params: None,
2035 compress_payload: true,
2036 };
2037 let result = encode_with_options(&claims, &options);
2038 assert!(result.is_ok(), "Encoding with compression should succeed");
2039
2040 let token = result.unwrap();
2041 let decoded = decode(&token).expect("Decoding compressed token should succeed");
2042
2043 assert_eq!(decoded.header.get("zip").unwrap().as_str().unwrap(), "DEF");
2045
2046 assert_eq!(decoded.claims, claims);
2048 }
2049
2050 #[test]
2051 fn test_encode_with_compression_hs256() {
2052 let claims =
2053 json!({"sub": "test", "name": "Test User", "data": "Some test data for compression"});
2054 let options = EncodeOptions {
2055 algorithm: "HS256",
2056 key_data: KeyData::Secret("test_secret"),
2057 header_params: None,
2058 compress_payload: true,
2059 };
2060 let result = encode_with_options(&claims, &options);
2061 assert!(
2062 result.is_ok(),
2063 "Encoding HS256 with compression should succeed"
2064 );
2065
2066 let token = result.unwrap();
2067 let decoded = decode(&token).expect("Decoding compressed HS256 token should succeed");
2068
2069 assert_eq!(decoded.header.get("zip").unwrap().as_str().unwrap(), "DEF");
2071 assert_eq!(
2072 decoded.header.get("alg").unwrap().as_str().unwrap(),
2073 "HS256"
2074 );
2075
2076 assert_eq!(decoded.claims, claims);
2078 }
2079
2080 #[test]
2081 fn test_decode_compressed_token() {
2082 let claims = json!({"user": "testuser", "role": "admin", "permissions": ["read", "write", "delete"]});
2084 let options = EncodeOptions {
2085 algorithm: "none",
2086 key_data: KeyData::None,
2087 header_params: None,
2088 compress_payload: true,
2089 };
2090 let token =
2091 encode_with_options(&claims, &options).expect("Failed to create compressed token");
2092
2093 let decoded = decode(&token).expect("Failed to decode compressed token");
2095
2096 assert_eq!(decoded.claims, claims);
2097 assert_eq!(decoded.header.get("zip").unwrap().as_str().unwrap(), "DEF");
2098 }
2099
2100 #[test]
2101 fn test_compression_preserves_signature_verification() {
2102 let claims = json!({"sub": "test", "exp": (chrono::Utc::now() + chrono::Duration::hours(1)).timestamp()});
2103 let secret = "test_secret_for_compression";
2104
2105 let options = EncodeOptions {
2107 algorithm: "HS256",
2108 key_data: KeyData::Secret(secret),
2109 header_params: None,
2110 compress_payload: true,
2111 };
2112 let token =
2113 encode_with_options(&claims, &options).expect("Failed to create compressed token");
2114
2115 let verify_options = VerifyOptions {
2117 key_data: VerifyKeyData::Secret(secret),
2118 validate_exp: false,
2119 validate_nbf: false,
2120 leeway: 0,
2121 };
2122 let verification_result = verify_with_options(&token, &verify_options);
2123 assert!(
2124 verification_result.is_ok(),
2125 "Verification should succeed for compressed token"
2126 );
2127 assert!(
2128 verification_result.unwrap(),
2129 "Compressed token should be valid"
2130 );
2131 }
2132
2133 #[test]
2134 fn test_decrypt_jwe_rejects_malformed_iv_length() {
2135 use base64::Engine;
2136 let b64 = |b: &[u8]| base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(b);
2137 let header = b64(br#"{"alg":"dir","enc":"A256GCM"}"#);
2138 let iv = b64(&[0u8; 8]);
2141 let ciphertext = b64(&[0u8; 16]);
2142 let tag = b64(&[0u8; 16]);
2143 let token = format!("{header}..{iv}.{ciphertext}.{tag}");
2145 let key = "01234567890123456789012345678901"; let result = decrypt_jwe(&token, key);
2148 assert!(
2149 result.is_err(),
2150 "decrypt_jwe must reject a non-12-byte IV instead of panicking"
2151 );
2152 let msg = result.unwrap_err().to_string();
2153 assert!(
2154 msg.contains("Invalid IV length"),
2155 "unexpected error message: {msg}"
2156 );
2157 }
2158
2159 #[test]
2160 fn test_verify_hs384_expired_token_propagates_error() {
2161 let secret = "test_secret";
2162 let exp = (Utc::now() - Duration::hours(1)).timestamp();
2163 let claims = json!({ "sub": "u", "exp": exp });
2164 let options = EncodeOptions {
2165 algorithm: "HS384",
2166 key_data: KeyData::Secret(secret),
2167 header_params: None,
2168 compress_payload: false,
2169 };
2170 let token = encode_with_options(&claims, &options).expect("Failed to encode HS384 token");
2171
2172 let verify_options = VerifyOptions {
2173 key_data: VerifyKeyData::Secret(secret),
2174 validate_exp: true,
2175 validate_nbf: false,
2176 leeway: 0,
2177 };
2178 let result = verify_with_options(&token, &verify_options);
2181 assert!(
2182 result.is_err(),
2183 "expired HS384 token should propagate an error"
2184 );
2185 let msg = result.unwrap_err().to_string().to_lowercase();
2186 assert!(
2187 msg.contains("expired"),
2188 "error should mention expiration: {msg}"
2189 );
2190 }
2191
2192 #[test]
2193 fn test_detect_token_type_jwt() {
2194 let jwt_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0In0.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ";
2195 assert_eq!(detect_token_type(jwt_token), TokenType::Jwt);
2196 }
2197
2198 #[test]
2199 fn test_detect_token_type_jwe() {
2200 let jwe_token = "eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIn0..ZHVtbXlfaXZfMTIzNDU2.eyJzdWIiOiJ0ZXN0In0.ZHVtbXlfdGFn";
2201 assert_eq!(detect_token_type(jwe_token), TokenType::Jwe);
2202 }
2203
2204 #[test]
2205 fn test_detect_token_type_unknown() {
2206 let invalid_token = "invalid.token.format.with.too.many.parts.here";
2207 assert_eq!(detect_token_type(invalid_token), TokenType::Unknown);
2208 }
2209
2210 #[test]
2211 fn test_decode_jwe_basic() {
2212 let jwe_token = "eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIn0..ZHVtbXlfaXZfMTIzNDU2.eyJzdWIiOiJ0ZXN0In0.ZHVtbXlfdGFn";
2213 let result = decode_jwe(jwe_token);
2214 assert!(result.is_ok(), "JWE decoding should succeed");
2215
2216 let decoded = result.unwrap();
2217 assert_eq!(decoded.algorithm, "dir");
2218 assert_eq!(decoded.encryption, "A256GCM");
2219 assert!(decoded.encrypted_key.is_empty());
2220 assert!(!decoded.iv.is_empty());
2221 assert!(!decoded.ciphertext.is_empty());
2222 assert!(!decoded.tag.is_empty());
2223 }
2224
2225 #[test]
2226 fn test_decode_jwe_invalid_format() {
2227 let invalid_jwe = "invalid.jwt.token"; let result = decode_jwe(invalid_jwe);
2229 assert!(
2230 result.is_err(),
2231 "JWE decoding should fail for invalid format"
2232 );
2233 }
2234
2235 #[test]
2236 #[allow(deprecated)]
2237 fn test_encode_jwe_demo() {
2238 let payload = r#"{"sub":"test","name":"JWE User"}"#;
2239 let result = encode_jwe_demo(payload, "test_key");
2240 assert!(result.is_ok(), "JWE encoding demo should succeed");
2241
2242 let jwe_token = result.unwrap();
2243 let parts: Vec<&str> = jwe_token.split('.').collect();
2244 assert_eq!(parts.len(), 5, "JWE token should have 5 parts");
2245
2246 let decode_result = decode_jwe(&jwe_token);
2248 assert!(
2249 decode_result.is_ok(),
2250 "Generated JWE token should be decodable"
2251 );
2252 }
2253
2254 #[test]
2255 fn test_jwt_error_display() {
2256 assert_eq!(JwtError::InvalidSignature.to_string(), "Invalid signature");
2257 assert_eq!(JwtError::ExpiredSignature.to_string(), "Expired signature");
2258 assert_eq!(
2259 JwtError::ImmatureSignature.to_string(),
2260 "Immature signature"
2261 );
2262 assert_eq!(JwtError::InvalidAlgorithm.to_string(), "Invalid algorithm");
2263 assert_eq!(
2264 JwtError::Other("test error".to_string()).to_string(),
2265 "JWT error: test error"
2266 );
2267 }
2268
2269 #[test]
2271 fn test_encode_es512() {
2272 let ec_private_key = include_str!("test_ec_p521_private.pem");
2274
2275 let claims = json!({"sub": "test_es512", "iat": 1234567890});
2276 let options = EncodeOptions {
2277 algorithm: "ES512",
2278 key_data: KeyData::PrivateKeyPem(ec_private_key),
2279 header_params: None,
2280 compress_payload: false,
2281 };
2282
2283 let result = encode_with_options(&claims, &options);
2284 assert!(result.is_ok(), "ES512 encoding should succeed");
2285
2286 let token = result.unwrap();
2287 let parts: Vec<&str> = token.split('.').collect();
2288 assert_eq!(parts.len(), 3, "JWT should have 3 parts");
2289
2290 let header_bytes = base64::engine::general_purpose::URL_SAFE_NO_PAD
2292 .decode(parts[0])
2293 .expect("Header should decode");
2294 let header_json: serde_json::Value =
2295 serde_json::from_slice(&header_bytes).expect("Header should be valid JSON");
2296 assert_eq!(
2297 header_json["alg"].as_str().unwrap(),
2298 "ES512",
2299 "Algorithm should be ES512"
2300 );
2301 }
2302
2303 #[test]
2305 fn test_jwe_direct_encryption_round_trip() {
2306 let payload = "Test payload for direct encryption";
2307 let key = "01234567890123456789012345678901"; let jwe_token = encode_jwe(
2311 payload,
2312 JweKeyManagement::Direct(key),
2313 JweContentEncryption::A256GCM,
2314 )
2315 .expect("Direct JWE encoding should succeed");
2316
2317 let parts: Vec<&str> = jwe_token.split('.').collect();
2319 assert_eq!(parts.len(), 5, "JWE should have 5 parts");
2320
2321 let decrypted = decrypt_jwe_with_josekit(&jwe_token, JweKeyManagement::Direct(key))
2323 .expect("Direct JWE decryption should succeed");
2324
2325 assert_eq!(decrypted, payload, "Round-trip should preserve payload");
2326 }
2327
2328 #[test]
2329 fn test_jwe_rsa_oaep_round_trip() {
2330 let rsa_private_key = include_str!("test_rsa_2048_private.pem");
2331 let rsa_public_key = include_str!("test_rsa_2048_public.pem");
2332
2333 let payload = "Test payload for RSA-OAEP encryption";
2334
2335 let jwe_token = encode_jwe(
2337 payload,
2338 JweKeyManagement::RsaOaep(rsa_public_key),
2339 JweContentEncryption::A256GCM,
2340 )
2341 .expect("RSA-OAEP JWE encoding should succeed");
2342
2343 let parts: Vec<&str> = jwe_token.split('.').collect();
2345 assert_eq!(parts.len(), 5, "JWE should have 5 parts");
2346
2347 let decrypted =
2349 decrypt_jwe_with_josekit(&jwe_token, JweKeyManagement::RsaOaep(rsa_private_key))
2350 .expect("RSA-OAEP JWE decryption should succeed");
2351
2352 assert_eq!(decrypted, payload, "Round-trip should preserve payload");
2353 }
2354
2355 #[test]
2356 fn test_jwe_rsa_oaep_256_round_trip() {
2357 let rsa_private_key = include_str!("test_rsa_2048_private.pem");
2358 let rsa_public_key = include_str!("test_rsa_2048_public.pem");
2359
2360 let payload = "Test payload for RSA-OAEP-256 encryption";
2361
2362 let jwe_token = encode_jwe(
2364 payload,
2365 JweKeyManagement::RsaOaep256(rsa_public_key),
2366 JweContentEncryption::A128GCM,
2367 )
2368 .expect("RSA-OAEP-256 JWE encoding should succeed");
2369
2370 let decrypted =
2372 decrypt_jwe_with_josekit(&jwe_token, JweKeyManagement::RsaOaep256(rsa_private_key))
2373 .expect("RSA-OAEP-256 JWE decryption should succeed");
2374
2375 assert_eq!(decrypted, payload, "Round-trip should preserve payload");
2376 }
2377
2378 #[test]
2379 fn test_jwe_ecdh_es_round_trip() {
2380 let ec_private_key = include_str!("test_ec_p256_private.pem");
2381 let ec_public_key = include_str!("test_ec_p256_public.pem");
2382
2383 let payload = "Test payload for ECDH-ES encryption";
2384
2385 let jwe_token = encode_jwe(
2387 payload,
2388 JweKeyManagement::EcdhEs(ec_public_key),
2389 JweContentEncryption::A256GCM,
2390 )
2391 .expect("ECDH-ES JWE encoding should succeed");
2392
2393 let decrypted =
2395 decrypt_jwe_with_josekit(&jwe_token, JweKeyManagement::EcdhEs(ec_private_key))
2396 .expect("ECDH-ES JWE decryption should succeed");
2397
2398 assert_eq!(decrypted, payload, "Round-trip should preserve payload");
2399 }
2400
2401 #[test]
2402 fn test_jwe_aes_key_wrap_round_trip() {
2403 let payload = "Test payload for AES key wrap";
2404 let key_128 = "0123456789012345"; let key_256 = "01234567890123456789012345678901"; let jwe_token = encode_jwe(
2409 payload,
2410 JweKeyManagement::A128kw(key_128),
2411 JweContentEncryption::A128GCM,
2412 )
2413 .expect("A128KW JWE encoding should succeed");
2414
2415 let decrypted = decrypt_jwe_with_josekit(&jwe_token, JweKeyManagement::A128kw(key_128))
2416 .expect("A128KW JWE decryption should succeed");
2417
2418 assert_eq!(
2419 decrypted, payload,
2420 "A128KW round-trip should preserve payload"
2421 );
2422
2423 let jwe_token = encode_jwe(
2425 payload,
2426 JweKeyManagement::A256kw(key_256),
2427 JweContentEncryption::A256GCM,
2428 )
2429 .expect("A256KW JWE encoding should succeed");
2430
2431 let decrypted = decrypt_jwe_with_josekit(&jwe_token, JweKeyManagement::A256kw(key_256))
2432 .expect("A256KW JWE decryption should succeed");
2433
2434 assert_eq!(
2435 decrypted, payload,
2436 "A256KW round-trip should preserve payload"
2437 );
2438 }
2439
2440 #[test]
2441 fn test_jwe_ecdh_es_a128kw_round_trip() {
2442 let ec_private_key = include_str!("test_ec_p256_private.pem");
2443 let ec_public_key = include_str!("test_ec_p256_public.pem");
2444
2445 let payload = "Test payload for ECDH-ES+A128KW encryption";
2446
2447 let jwe_token = encode_jwe(
2449 payload,
2450 JweKeyManagement::EcdhEsA128kw(ec_public_key),
2451 JweContentEncryption::A128GCM,
2452 )
2453 .expect("ECDH-ES+A128KW JWE encoding should succeed");
2454
2455 let decrypted =
2457 decrypt_jwe_with_josekit(&jwe_token, JweKeyManagement::EcdhEsA128kw(ec_private_key))
2458 .expect("ECDH-ES+A128KW JWE decryption should succeed");
2459
2460 assert_eq!(decrypted, payload, "Round-trip should preserve payload");
2461 }
2462
2463 #[test]
2464 fn test_jwe_ecdh_es_a256kw_round_trip() {
2465 let ec_private_key = include_str!("test_ec_p256_private.pem");
2466 let ec_public_key = include_str!("test_ec_p256_public.pem");
2467
2468 let payload = "Test payload for ECDH-ES+A256KW encryption";
2469
2470 let jwe_token = encode_jwe(
2472 payload,
2473 JweKeyManagement::EcdhEsA256kw(ec_public_key),
2474 JweContentEncryption::A256GCM,
2475 )
2476 .expect("ECDH-ES+A256KW JWE encoding should succeed");
2477
2478 let decrypted =
2480 decrypt_jwe_with_josekit(&jwe_token, JweKeyManagement::EcdhEsA256kw(ec_private_key))
2481 .expect("ECDH-ES+A256KW JWE decryption should succeed");
2482
2483 assert_eq!(decrypted, payload, "Round-trip should preserve payload");
2484 }
2485}