Skip to content

Commit 02b3ebf

Browse files
committed
Add verify command
This commit introduces a verify command that enforces signature validation. The decode command now does not attempt to validate the token signature and does not expect the parameters `--secret`, `--ignore_exp` and `--alg` any more. This commit also includes some additional code cleanups regarding exit codes.
1 parent 084ffe4 commit 02b3ebf

File tree

2 files changed

+182
-166
lines changed

2 files changed

+182
-166
lines changed

src/main.rs

Lines changed: 88 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,26 @@ fn config_options<'a, 'b>() -> App<'a, 'b> {
239239
),
240240
).subcommand(
241241
SubCommand::with_name("decode")
242-
.about("Decode a JWT")
242+
.about("Decode a JWT (no signature check is performed)")
243+
.arg(
244+
Arg::with_name("jwt")
245+
.help("the jwt to decode")
246+
.index(1)
247+
.required(true),
248+
).arg(
249+
Arg::with_name("iso_dates")
250+
.help("display unix timestamps as ISO 8601 dates")
251+
.takes_value(false)
252+
.long("iso8601")
253+
).arg(
254+
Arg::with_name("json")
255+
.help("render decoded JWT as JSON")
256+
.long("json")
257+
.short("j"),
258+
),
259+
).subcommand(
260+
SubCommand::with_name("verify")
261+
.about("Decode a JWT and validate its signature")
243262
.arg(
244263
Arg::with_name("jwt")
245264
.help("the jwt to decode")
@@ -264,7 +283,7 @@ fn config_options<'a, 'b>() -> App<'a, 'b> {
264283
.takes_value(true)
265284
.long("secret")
266285
.short("S")
267-
.default_value(""),
286+
.required(true),
268287
).arg(
269288
Arg::with_name("json")
270289
.help("render decoded JWT as JSON")
@@ -458,20 +477,7 @@ fn encode_token(matches: &ArgMatches) -> JWTResult<String> {
458477
.and_then(|secret| encode(&header, &claims, &secret))
459478
}
460479

461-
fn decode_token(
462-
matches: &ArgMatches,
463-
) -> (
464-
Option<JWTResult<TokenData<Payload>>>,
465-
JWTResult<TokenData<Payload>>,
466-
OutputFormat,
467-
) {
468-
let algorithm = translate_algorithm(SupportedAlgorithms::from_string(
469-
matches.value_of("algorithm").unwrap(),
470-
));
471-
let secret = match matches.value_of("secret").map(|s| (s, !s.is_empty())) {
472-
Some((secret, true)) => Some(decoding_key_from_secret(&algorithm, &secret)),
473-
_ => None,
474-
};
480+
fn decode_token(matches: &ArgMatches) -> (String, JWTResult<TokenData<Payload>>, OutputFormat) {
475481
let jwt = matches
476482
.value_of("jwt")
477483
.map(|value| {
@@ -491,13 +497,6 @@ fn decode_token(
491497
.trim()
492498
.to_owned();
493499

494-
let secret_validator = Validation {
495-
leeway: 1000,
496-
algorithms: vec![algorithm],
497-
validate_exp: !matches.is_present("ignore_exp"),
498-
..Default::default()
499-
};
500-
501500
let token_data = dangerous_insecure_decode::<Payload>(&jwt).map(|mut token| {
502501
if matches.is_present("iso_dates") {
503502
token.claims.convert_timestamps();
@@ -507,14 +506,7 @@ fn decode_token(
507506
});
508507

509508
(
510-
match secret {
511-
Some(secret_key) => Some(decode::<Payload>(
512-
&jwt,
513-
&secret_key.unwrap(),
514-
&secret_validator,
515-
)),
516-
None => None, // unable to safely decode token => validated_token is set to None
517-
},
509+
jwt,
518510
token_data,
519511
if matches.is_present("json") {
520512
OutputFormat::Json
@@ -524,6 +516,24 @@ fn decode_token(
524516
)
525517
}
526518

519+
fn verify_token(jwt: &String, matches: &ArgMatches) -> JWTResult<TokenData<Payload>> {
520+
let algorithm = translate_algorithm(SupportedAlgorithms::from_string(
521+
matches.value_of("algorithm").unwrap(),
522+
));
523+
524+
let secret_validator = Validation {
525+
leeway: 1000,
526+
algorithms: vec![algorithm],
527+
validate_exp: !matches.is_present("ignore_exp"),
528+
..Default::default()
529+
};
530+
531+
let secret = matches.value_of("secret").unwrap();
532+
let secret_key = decoding_key_from_secret(&algorithm, &secret).unwrap();
533+
534+
decode::<Payload>(jwt, &secret_key, &secret_validator)
535+
}
536+
527537
fn print_encoded_token(token: JWTResult<String>) {
528538
match token {
529539
Ok(jwt) => {
@@ -532,26 +542,24 @@ fn print_encoded_token(token: JWTResult<String>) {
532542
} else {
533543
print!("{}", jwt);
534544
}
535-
exit(0);
536545
}
537546
Err(err) => {
538547
bunt::eprintln!("{$red+bold}Something went awry creating the jwt{/$}\n");
539548
eprintln!("{}", err);
540-
exit(1);
541549
}
542550
}
543551
}
544552

545553
fn print_decoded_token(
546554
validated_token: Option<JWTResult<TokenData<Payload>>>,
547555
token_data: JWTResult<TokenData<Payload>>,
548-
options_algorithm: Algorithm,
556+
options_algorithm: Option<Algorithm>,
549557
format: OutputFormat,
550558
) {
551559
match validated_token {
552560
Some(Err(ref err)) => match err.kind() {
553561
ErrorKind::InvalidToken => {
554-
bunt::println!("{$red+bold}The JWT provided is invalid{/$}")
562+
bunt::eprintln!("{$red+bold}The JWT provided is invalid{/$}")
555563
}
556564
ErrorKind::InvalidSignature => {
557565
bunt::eprintln!("{$red+bold}The JWT provided has an invalid signature{/$}")
@@ -566,7 +574,7 @@ fn print_decoded_token(
566574
bunt::eprintln!("{$red+bold}The token has expired (or the `exp` claim is not set). This error can be ignored via the `--ignore-exp` parameter.{/$}")
567575
}
568576
ErrorKind::InvalidIssuer => {
569-
bunt::println!("{$red+bold}The token issuer is invalid{/$}")
577+
bunt::eprintln!("{$red+bold}The token issuer is invalid{/$}")
570578
}
571579
ErrorKind::InvalidAudience => {
572580
bunt::eprintln!("{$red+bold}The token audience doesn't match the subject{/$}")
@@ -582,15 +590,27 @@ fn print_decoded_token(
582590
Ok(ref token) => token.header.alg,
583591
Err(_) => panic!("Error: Invalid token data."),
584592
};
585-
bunt::eprintln!("{$red+bold}Error: Invalid Signature! The JWT provided has a different signing algorithm ({:?}) than the one selected for validation ({:?}){/$}",jwt_algorithm, options_algorithm)
593+
bunt::eprintln!("{$red+bold}Error: Invalid Signature! The JWT provided has a different signing algorithm ({:?}) than the one selected for validation ({:?}){/$}",jwt_algorithm, options_algorithm.unwrap())
586594
}
587595
_ => bunt::eprintln!(
588596
"{$red+bold}The JWT provided is invalid because {:?}{/$}",
589597
err
590598
),
591599
},
592600
Some(Ok(_)) => bunt::eprintln!("{$green+bold}Success! JWT signature is valid!{/$}"),
593-
None => bunt::eprintln!("{$red+bold}Warning! JWT signature has not been validated!{/$}"),
601+
None => {
602+
// the signature could not be verified
603+
match token_data {
604+
Err(ref err) => match err.kind() {
605+
ErrorKind::InvalidToken => bunt::eprintln!("{$red+bold}Error: The token could not be decoded (invalid token structure).{/$}"),
606+
ErrorKind::Base64(_) => bunt::eprintln!("{$red+bold}Error: The token could not be decoded (invalid Base64 encoding).{/$}"),
607+
ErrorKind::Json(_) => bunt::eprintln!("{$red+bold}Error: The token could not be decoded (error while decoding json).{/$}"),
608+
ErrorKind::Utf8(_) => bunt::eprintln!("{$red+bold}Error: The token could not be decoded (error while decoding UTF8 string).{/$}"),
609+
_ => bunt::eprintln!("{$red+bold}Error: Unexpected error while decoding the token!{/$}"),
610+
}
611+
Ok(_) => bunt::eprintln!("{$red+bold}Warning! JWT signature has not been validated!{/$}"),
612+
}
613+
}
594614
}
595615

596616
match (format, token_data) {
@@ -603,14 +623,8 @@ fn print_decoded_token(
603623
bunt::println!("{$bold}Token claims\n------------{/$}");
604624
println!("{}", to_string_pretty(&token.claims).unwrap());
605625
}
606-
(_, Err(_)) => exit(1),
626+
(_, Err(_)) => {}
607627
}
608-
609-
exit(match validated_token {
610-
Some(Ok(_)) => 0, // successful signature check
611-
Some(Err(_)) => 1, // token validation error
612-
None => 2, // no signature check performed
613-
})
614628
}
615629

616630
fn main() {
@@ -621,17 +635,43 @@ fn main() {
621635
warn_unsupported(&encode_matches);
622636

623637
let token = encode_token(&encode_matches);
638+
let return_code: i32 = match &token {
639+
Ok(_) => 0, // token encoded sucessfully
640+
Err(_) => 1, // token could not be encoded
641+
};
624642

625643
print_encoded_token(token);
644+
exit(return_code)
626645
}
627646
("decode", Some(decode_matches)) => {
628-
let (validated_token, token_data, format) = decode_token(&decode_matches);
629-
647+
let (_, token_data, format) = decode_token(&decode_matches);
648+
let return_code: i32 = match &token_data {
649+
Ok(_) => 0, // token decoded sucessfully
650+
Err(_) => 1, // token could not be decoded
651+
};
652+
653+
print_decoded_token(None, token_data, None, format);
654+
exit(return_code)
655+
}
656+
("verify", Some(decode_matches)) => {
657+
let (jwt, token_data, format) = decode_token(&decode_matches);
658+
let validated_token = verify_token(&jwt, &decode_matches);
659+
let return_code: i32 = match validated_token {
660+
Ok(_) => 0, // successful signature check
661+
Err(_) => 1, // unsuccessful signature check
662+
};
630663
let options_algorithm = translate_algorithm(SupportedAlgorithms::from_string(
631664
decode_matches.value_of("algorithm").unwrap(),
632665
));
633666

634-
print_decoded_token(validated_token, token_data, options_algorithm, format);
667+
print_decoded_token(
668+
Some(validated_token),
669+
token_data,
670+
Some(options_algorithm),
671+
format,
672+
);
673+
674+
exit(return_code)
635675
}
636676
_ => (),
637677
}

0 commit comments

Comments
 (0)