Skip to content

Commit 115eb64

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 674d389 commit 115eb64

File tree

2 files changed

+196
-176
lines changed

2 files changed

+196
-176
lines changed

src/main.rs

Lines changed: 88 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,26 @@ fn config_options<'a, 'b>() -> App<'a, 'b> {
245245
),
246246
).subcommand(
247247
SubCommand::with_name("decode")
248-
.about("Decode a JWT")
248+
.about("Decode a JWT (no signature check is performed)")
249+
.arg(
250+
Arg::with_name("jwt")
251+
.help("the jwt to decode")
252+
.index(1)
253+
.required(true),
254+
).arg(
255+
Arg::with_name("iso_dates")
256+
.help("display unix timestamps as ISO 8601 dates")
257+
.takes_value(false)
258+
.long("iso8601")
259+
).arg(
260+
Arg::with_name("json")
261+
.help("render decoded JWT as JSON")
262+
.long("json")
263+
.short("j"),
264+
),
265+
).subcommand(
266+
SubCommand::with_name("verify")
267+
.about("Decode a JWT and validate its signature")
249268
.arg(
250269
Arg::with_name("jwt")
251270
.help("the jwt to decode")
@@ -270,7 +289,7 @@ fn config_options<'a, 'b>() -> App<'a, 'b> {
270289
.takes_value(true)
271290
.long("secret")
272291
.short("S")
273-
.default_value(""),
292+
.required(true),
274293
).arg(
275294
Arg::with_name("json")
276295
.help("render decoded JWT as JSON")
@@ -473,20 +492,7 @@ fn encode_token(matches: &ArgMatches) -> JWTResult<String> {
473492
.and_then(|secret| encode(&header, &claims, &secret))
474493
}
475494

476-
fn decode_token(
477-
matches: &ArgMatches,
478-
) -> (
479-
Option<JWTResult<TokenData<Payload>>>,
480-
JWTResult<TokenData<Payload>>,
481-
OutputFormat,
482-
) {
483-
let algorithm = translate_algorithm(SupportedAlgorithms::from_string(
484-
matches.value_of("algorithm").unwrap(),
485-
));
486-
let secret = match matches.value_of("secret").map(|s| (s, !s.is_empty())) {
487-
Some((secret, true)) => Some(decoding_key_from_secret(&algorithm, secret)),
488-
_ => None,
489-
};
495+
fn decode_token(matches: &ArgMatches) -> (String, JWTResult<TokenData<Payload>>, OutputFormat) {
490496
let jwt = matches
491497
.value_of("jwt")
492498
.map(|value| {
@@ -506,13 +512,6 @@ fn decode_token(
506512
.trim()
507513
.to_owned();
508514

509-
let secret_validator = Validation {
510-
leeway: 1000,
511-
algorithms: vec![algorithm],
512-
validate_exp: !matches.is_present("ignore_exp"),
513-
..Default::default()
514-
};
515-
516515
let token_data = dangerous_insecure_decode::<Payload>(&jwt).map(|mut token| {
517516
if matches.is_present("iso_dates") {
518517
token.claims.convert_timestamps();
@@ -522,14 +521,7 @@ fn decode_token(
522521
});
523522

524523
(
525-
match secret {
526-
Some(secret_key) => Some(decode::<Payload>(
527-
&jwt,
528-
&secret_key.unwrap(),
529-
&secret_validator,
530-
)),
531-
None => None, // unable to safely decode token => validated_token is set to None
532-
},
524+
jwt,
533525
token_data,
534526
if matches.is_present("json") {
535527
OutputFormat::Json
@@ -539,6 +531,24 @@ fn decode_token(
539531
)
540532
}
541533

534+
fn verify_token(jwt: &String, matches: &ArgMatches) -> JWTResult<TokenData<Payload>> {
535+
let algorithm = translate_algorithm(SupportedAlgorithms::from_string(
536+
matches.value_of("algorithm").unwrap(),
537+
));
538+
539+
let secret_validator = Validation {
540+
leeway: 1000,
541+
algorithms: vec![algorithm],
542+
validate_exp: !matches.is_present("ignore_exp"),
543+
..Default::default()
544+
};
545+
546+
let secret = matches.value_of("secret").unwrap();
547+
let secret_key = decoding_key_from_secret(&algorithm, &secret).unwrap();
548+
549+
decode::<Payload>(jwt, &secret_key, &secret_validator)
550+
}
551+
542552
fn print_encoded_token(token: JWTResult<String>) {
543553
match token {
544554
Ok(jwt) => {
@@ -547,26 +557,24 @@ fn print_encoded_token(token: JWTResult<String>) {
547557
} else {
548558
print!("{}", jwt);
549559
}
550-
exit(0);
551560
}
552561
Err(err) => {
553562
bunt::eprintln!("{$red+bold}Something went awry creating the jwt{/$}\n");
554563
eprintln!("{}", err);
555-
exit(1);
556564
}
557565
}
558566
}
559567

560568
fn print_decoded_token(
561569
validated_token: Option<JWTResult<TokenData<Payload>>>,
562570
token_data: JWTResult<TokenData<Payload>>,
563-
options_algorithm: Algorithm,
571+
options_algorithm: Option<Algorithm>,
564572
format: OutputFormat,
565573
) {
566574
match validated_token {
567575
Some(Err(ref err)) => match err.kind() {
568576
ErrorKind::InvalidToken => {
569-
bunt::println!("{$red+bold}The JWT provided is invalid{/$}")
577+
bunt::eprintln!("{$red+bold}The JWT provided is invalid{/$}")
570578
}
571579
ErrorKind::InvalidSignature => {
572580
bunt::eprintln!("{$red+bold}The JWT provided has an invalid signature{/$}")
@@ -581,7 +589,7 @@ fn print_decoded_token(
581589
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.{/$}")
582590
}
583591
ErrorKind::InvalidIssuer => {
584-
bunt::println!("{$red+bold}The token issuer is invalid{/$}")
592+
bunt::eprintln!("{$red+bold}The token issuer is invalid{/$}")
585593
}
586594
ErrorKind::InvalidAudience => {
587595
bunt::eprintln!("{$red+bold}The token audience doesn't match the subject{/$}")
@@ -597,15 +605,27 @@ fn print_decoded_token(
597605
Ok(ref token) => token.header.alg,
598606
Err(_) => panic!("Error: Invalid token data."),
599607
};
600-
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)
608+
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())
601609
}
602610
_ => bunt::eprintln!(
603611
"{$red+bold}The JWT provided is invalid because {:?}{/$}",
604612
err
605613
),
606614
},
607615
Some(Ok(_)) => bunt::eprintln!("{$green+bold}Success! JWT signature is valid!{/$}"),
608-
None => bunt::eprintln!("{$red+bold}Warning! JWT signature has not been validated!{/$}"),
616+
None => {
617+
// the signature could not be verified
618+
match token_data {
619+
Err(ref err) => match err.kind() {
620+
ErrorKind::InvalidToken => bunt::eprintln!("{$red+bold}Error: The token could not be decoded (invalid token structure).{/$}"),
621+
ErrorKind::Base64(_) => bunt::eprintln!("{$red+bold}Error: The token could not be decoded (invalid Base64 encoding).{/$}"),
622+
ErrorKind::Json(_) => bunt::eprintln!("{$red+bold}Error: The token could not be decoded (error while decoding json).{/$}"),
623+
ErrorKind::Utf8(_) => bunt::eprintln!("{$red+bold}Error: The token could not be decoded (error while decoding UTF8 string).{/$}"),
624+
_ => bunt::eprintln!("{$red+bold}Error: Unexpected error while decoding the token!{/$}"),
625+
}
626+
Ok(_) => bunt::eprintln!("{$red+bold}Warning! JWT signature has not been validated!{/$}"),
627+
}
628+
}
609629
}
610630

611631
match (format, token_data) {
@@ -618,14 +638,8 @@ fn print_decoded_token(
618638
bunt::println!("{$bold}Token claims\n------------{/$}");
619639
println!("{}", to_string_pretty(&token.claims).unwrap());
620640
}
621-
(_, Err(_)) => exit(1),
641+
(_, Err(_)) => {}
622642
}
623-
624-
exit(match validated_token {
625-
Some(Ok(_)) => 0, // successful signature check
626-
Some(Err(_)) => 1, // token validation error
627-
None => 2, // no signature check performed
628-
})
629643
}
630644

631645
fn main() {
@@ -636,17 +650,43 @@ fn main() {
636650
warn_unsupported(encode_matches);
637651

638652
let token = encode_token(encode_matches);
653+
let return_code: i32 = match &token {
654+
Ok(_) => 0, // token encoded sucessfully
655+
Err(_) => 1, // token could not be encoded
656+
};
639657

640658
print_encoded_token(token);
659+
exit(return_code)
641660
}
642661
("decode", Some(decode_matches)) => {
643-
let (validated_token, token_data, format) = decode_token(decode_matches);
644-
662+
let (_, token_data, format) = decode_token(&decode_matches);
663+
let return_code: i32 = match &token_data {
664+
Ok(_) => 0, // token decoded sucessfully
665+
Err(_) => 1, // token could not be decoded
666+
};
667+
668+
print_decoded_token(None, token_data, None, format);
669+
exit(return_code)
670+
}
671+
("verify", Some(decode_matches)) => {
672+
let (jwt, token_data, format) = decode_token(&decode_matches);
673+
let validated_token = verify_token(&jwt, &decode_matches);
674+
let return_code: i32 = match validated_token {
675+
Ok(_) => 0, // successful signature check
676+
Err(_) => 1, // unsuccessful signature check
677+
};
645678
let options_algorithm = translate_algorithm(SupportedAlgorithms::from_string(
646679
decode_matches.value_of("algorithm").unwrap(),
647680
));
648681

649-
print_decoded_token(validated_token, token_data, options_algorithm, format);
682+
print_decoded_token(
683+
Some(validated_token),
684+
token_data,
685+
Some(options_algorithm),
686+
format,
687+
);
688+
689+
exit(return_code)
650690
}
651691
_ => (),
652692
}

0 commit comments

Comments
 (0)