diff --git a/src/main.rs b/src/main.rs index ad5409c..56bfbf0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -465,7 +465,7 @@ fn encode_token(matches: &ArgMatches) -> JWTResult { fn decode_token( matches: &ArgMatches, ) -> ( - JWTResult>, + Option>>, JWTResult>, OutputFormat, ) { @@ -506,8 +506,12 @@ fn decode_token( ( match secret { - Some(secret_key) => decode::(&jwt, &secret_key.unwrap(), &secret_validator), - None => dangerous_insecure_decode::(&jwt), + Some(secret_key) => Some(decode::( + &jwt, + &secret_key.unwrap(), + &secret_validator, + )), + None => None, // unable to safely decode token => validated_token is set to None }, token_data, if matches.is_present("json") { @@ -540,8 +544,9 @@ fn print_encoded_token(token: JWTResult) { } fn print_decoded_token( - validated_token: JWTResult>, + validated_token: Option>>, token_data: JWTResult>, + options_algorithm: Algorithm, format: OutputFormat, ) { let should_validate_exp = if let Ok(token) = &token_data { @@ -550,62 +555,89 @@ fn print_decoded_token( false }; - if let Err(err) = &validated_token { - match err.kind() { + match validated_token { + Some(Err(ref err)) => match err.kind() { ErrorKind::InvalidToken => { - println!("{}", Red.bold().paint("The JWT provided is invalid")) + println!( + "{}", + Red.bold().paint("Error: The JWT provided is invalid.") + ) } ErrorKind::InvalidSignature => eprintln!( "{}", Red.bold() - .paint("The JWT provided has an invalid signature",) + .paint("Error: The JWT provided has an invalid signature",) ), ErrorKind::InvalidRsaKey => eprintln!( "{}", Red.bold() - .paint("The secret provided isn't a valid RSA key",) + .paint("Error: The secret provided isn't a valid RSA key.",) ), ErrorKind::InvalidEcdsaKey => eprintln!( "{}", Red.bold() - .paint("The secret provided isn't a valid ECDSA key",) + .paint("Error: The secret provided isn't a valid ECDSA key.",) ), ErrorKind::ExpiredSignature => { if should_validate_exp { - println!("{}", Red.bold().paint("The token has expired")) + println!("{}", Red.bold().paint("Error: The token has expired.")) + } else { + println!( + "{}", + Red.bold().paint( + "Warning: The `exp` claim is not set. Skipping token expiration check." + ) + ) } } ErrorKind::InvalidIssuer => { - println!("{}", Red.bold().paint("The token issuer is invalid")) + println!( + "{}", + Red.bold().paint("Error: The token issuer is invalid.") + ) } ErrorKind::InvalidAudience => eprintln!( "{}", Red.bold() - .paint("The token audience doesn't match the subject",) + .paint("Error: The token audience doesn't match the subject.",) ), ErrorKind::InvalidSubject => eprintln!( "{}", Red.bold() - .paint("The token subject doesn't match the audience",) + .paint("Error: The token subject doesn't match the audience.",) ), ErrorKind::ImmatureSignature => eprintln!( "{}", Red.bold() - .paint("The `nbf` claim is in the future which isn't allowed",) + .paint("Error: The `nbf` claim is in the future which isn't allowed.",) ), - ErrorKind::InvalidAlgorithm => eprintln!( - "{}", - Red.bold().paint( - "The JWT provided has a different signing algorithm than the one you \ - provided", + ErrorKind::InvalidAlgorithm => { + let jwt_algorithm = match token_data { + Ok(ref token) => token.header.alg, + Err(_) => panic!("Error: Invalid token data."), + }; + eprintln!( + "{}", + Red.bold().paint(format!( + "Error: Invalid signature! The JWT provided has a different signing \ + algorithm ({:?}) than the one selected for validation ({:?}).", + jwt_algorithm, options_algorithm + )) ) - ), + } _ => eprintln!( "{} {:?}", - Red.bold().paint("The JWT provided is invalid because"), + Red.bold() + .paint("Error: The JWT provided is invalid because"), err ), - }; + }, + Some(Ok(_)) => eprintln!("{}", Green.bold().paint("Success! JWT signature is valid!")), + None => eprintln!( + "{}", + Red.bold() + .paint("Warning! JWT signature has not been validated!") + ), } match (format, token_data) { @@ -622,11 +654,12 @@ fn print_decoded_token( } exit(match validated_token { - Err(err) => match (err.kind(), should_validate_exp) { - (ErrorKind::ExpiredSignature, false) => 0, - _ => 1, + Some(Err(err)) => match (err.kind(), should_validate_exp) { + (ErrorKind::ExpiredSignature, false) => 0, // signature expired, but expiration time should be ignored + _ => 1, // token validation error }, - Ok(_) => 0, + Some(Ok(_)) => 0, // successful signature check + None => 2, // no signature check performed }) } @@ -644,7 +677,11 @@ fn main() { ("decode", Some(decode_matches)) => { let (validated_token, token_data, format) = decode_token(&decode_matches); - print_decoded_token(validated_token, token_data, format); + let options_algorithm = translate_algorithm(SupportedAlgorithms::from_string( + decode_matches.value_of("algorithm").unwrap(), + )); + + print_decoded_token(validated_token, token_data, options_algorithm, format); } _ => (), } diff --git a/tests/jwt-cli.rs b/tests/jwt-cli.rs index 8f9cf88..8b3fac0 100644 --- a/tests/jwt-cli.rs +++ b/tests/jwt-cli.rs @@ -228,9 +228,9 @@ mod tests { let decode_matches = decode_matcher.subcommand_matches("decode").unwrap(); let (decoded_token, _, _) = decode_token(&decode_matches); - assert!(decoded_token.is_ok()); + assert!(decoded_token.as_ref().unwrap().is_ok()); - let TokenData { claims, header } = decoded_token.unwrap(); + let TokenData { claims, header } = decoded_token.unwrap().unwrap(); assert_eq!(header.alg, Algorithm::HS256); assert_eq!(header.kid, Some("1234".to_string())); @@ -262,9 +262,9 @@ mod tests { let decode_matches = decode_matcher.subcommand_matches("decode").unwrap(); let (decoded_token, _, _) = decode_token(&decode_matches); - assert!(decoded_token.is_ok()); + assert!(decoded_token.as_ref().unwrap().is_ok()); - let TokenData { claims, header: _ } = decoded_token.unwrap(); + let TokenData { claims, header: _ } = decoded_token.unwrap().unwrap(); let iat = from_value::(claims.0["iat"].clone()); assert!(iat.is_ok()); @@ -284,7 +284,7 @@ mod tests { let decode_matches = decode_matcher.subcommand_matches("decode").unwrap(); let (decoded_token, token_data, _) = decode_token(&decode_matches); - assert!(decoded_token.is_err()); + assert!(decoded_token.as_ref().unwrap().is_err()); let TokenData { claims, header: _ } = token_data.unwrap(); @@ -304,9 +304,9 @@ mod tests { let decode_matches = decode_matcher.subcommand_matches("decode").unwrap(); let (decoded_token, _, _) = decode_token(&decode_matches); - assert!(decoded_token.is_ok()); + assert!(decoded_token.as_ref().unwrap().is_ok()); - let TokenData { claims, header: _ } = decoded_token.unwrap(); + let TokenData { claims, header: _ } = decoded_token.unwrap().unwrap(); let exp = from_value::(claims.0["exp"].clone()); assert!(exp.is_ok()); @@ -333,9 +333,9 @@ mod tests { let decode_matches = decode_matcher.subcommand_matches("decode").unwrap(); let (decoded_token, _, _) = decode_token(&decode_matches); - assert!(decoded_token.is_ok()); + assert!(decoded_token.as_ref().unwrap().is_ok()); - let TokenData { claims, header: _ } = decoded_token.unwrap(); + let TokenData { claims, header: _ } = decoded_token.unwrap().unwrap(); assert!(claims.0.get("iat").is_none()); } @@ -361,9 +361,9 @@ mod tests { let decode_matches = decode_matcher.subcommand_matches("decode").unwrap(); let (decoded_token, _, _) = decode_token(&decode_matches); - assert!(decoded_token.is_ok()); + assert!(decoded_token.as_ref().unwrap().is_ok()); - let TokenData { claims, header: _ } = decoded_token.unwrap(); + let TokenData { claims, header: _ } = decoded_token.unwrap().unwrap(); let exp_claim = from_value::(claims.0["exp"].clone()); assert!(exp_claim.is_ok()); @@ -390,9 +390,9 @@ mod tests { let decode_matches = decode_matcher.subcommand_matches("decode").unwrap(); let (decoded_token, _, _) = decode_token(&decode_matches); - assert!(decoded_token.is_ok()); + assert!(decoded_token.as_ref().unwrap().is_ok()); - let TokenData { claims, header: _ } = decoded_token.unwrap(); + let TokenData { claims, header: _ } = decoded_token.unwrap().unwrap(); let exp_claim = from_value::(claims.0["exp"].clone()); let iat_claim = from_value::(claims.0["iat"].clone()); @@ -426,9 +426,9 @@ mod tests { let decode_matches = decode_matcher.subcommand_matches("decode").unwrap(); let (decoded_token, _, _) = decode_token(&decode_matches); - assert!(decoded_token.is_ok()); + assert!(decoded_token.as_ref().unwrap().is_ok()); - let TokenData { claims, header: _ } = decoded_token.unwrap(); + let TokenData { claims, header: _ } = decoded_token.unwrap().unwrap(); let nbf_claim = from_value::(claims.0["nbf"].clone()); let iat_claim = from_value::(claims.0["iat"].clone()); @@ -457,7 +457,7 @@ mod tests { let decode_matches = matches.subcommand_matches("decode").unwrap(); let (result, _, _) = decode_token(&decode_matches); - assert!(result.is_ok()); + assert!(result.unwrap().is_ok()); } #[test] @@ -471,9 +471,10 @@ mod tests { ]) .unwrap(); let decode_matches = matches.subcommand_matches("decode").unwrap(); - let (result, _, format) = decode_token(&decode_matches); + let (validated_token, token_data, format) = decode_token(&decode_matches); - assert!(result.is_ok()); + assert!(validated_token.is_none()); // no signature validation + assert!(token_data.is_ok()); assert!(format == OutputFormat::Json); } @@ -493,7 +494,7 @@ mod tests { let decode_matches = matches.subcommand_matches("decode").unwrap(); let (result, _, _) = decode_token(&decode_matches); - assert!(result.is_err()); + assert!(result.unwrap().is_err()); } #[test] @@ -508,9 +509,10 @@ mod tests { ]) .unwrap(); let decode_matches = matches.subcommand_matches("decode").unwrap(); - let (result, _, _) = decode_token(&decode_matches); + let (validated_token, token_data, _) = decode_token(&decode_matches); - assert!(result.is_ok()); + assert!(validated_token.is_none()); // no signature validation + assert!(token_data.is_ok()); } #[test] @@ -523,9 +525,10 @@ mod tests { ]) .unwrap(); let decode_matches = matches.subcommand_matches("decode").unwrap(); - let (result, _, _) = decode_token(&decode_matches); + let (validated_token, token_data, _) = decode_token(&decode_matches); - assert!(result.is_ok()); + assert!(validated_token.is_none()); // no signature validation + assert!(token_data.is_ok()); } #[test] @@ -538,9 +541,10 @@ mod tests { ]) .unwrap(); let decode_matches = matches.subcommand_matches("decode").unwrap(); - let (result, _, _) = decode_token(&decode_matches); + let (validated_token, token_data, _) = decode_token(&decode_matches); - assert!(result.is_ok()); + assert!(validated_token.is_none()); // no signature validation + assert!(token_data.is_ok()); } #[test] @@ -553,9 +557,10 @@ mod tests { ]) .unwrap(); let decode_matches = matches.subcommand_matches("decode").unwrap(); - let (result, _, _) = decode_token(&decode_matches); + let (validated_token, token_data, _) = decode_token(&decode_matches); - assert!(result.is_ok()); + assert!(validated_token.is_none()); // no signature validation + assert!(token_data.is_ok()); } #[test] @@ -589,7 +594,7 @@ mod tests { let decode_matches = decode_matcher.subcommand_matches("decode").unwrap(); let (result, _, _) = decode_token(&decode_matches); - assert!(result.is_ok()); + assert!(result.unwrap().is_ok()); } #[test] @@ -623,9 +628,7 @@ mod tests { let decode_matches = decode_matcher.subcommand_matches("decode").unwrap(); let (result, _, _) = decode_token(&decode_matches); - dbg!(&result); - - assert!(result.is_ok()); + assert!(result.unwrap().is_ok()); } #[test] @@ -659,7 +662,7 @@ mod tests { let decode_matches = decode_matcher.subcommand_matches("decode").unwrap(); let (decoded_token, token_data, _) = decode_token(&decode_matches); - assert!(decoded_token.is_ok()); + assert!(decoded_token.as_ref().unwrap().is_ok()); let TokenData { claims, header: _ } = token_data.unwrap();