Skip to content

Commit

Permalink
add more tests
Browse files Browse the repository at this point in the history
  • Loading branch information
deepu105 committed Jan 16, 2024
1 parent ebbbcee commit cbba5c0
Show file tree
Hide file tree
Showing 4 changed files with 223 additions and 71 deletions.
39 changes: 31 additions & 8 deletions src/app/jwt_decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,12 +123,12 @@ impl TokenOutput {
}

#[derive(Debug, Clone)]
struct DecodeArgs {
pub(super) struct DecodeArgs {
/// The JWT to decode.
pub jwt: String,
/// Display unix timestamps as ISO 8601 UTC dates
pub time_format_utc: bool,
/// The secret to validate the JWT with. Prefix with @ to read from a file or b64: to use base-64 encoded bytes
/// The secret to validate the JWT with.
pub secret: String,
/// Ignore token expiration date (`exp` claim) during validation
pub ignore_exp: bool,
Expand Down Expand Up @@ -185,7 +185,7 @@ pub fn print_decoded_token(token: &TokenData<Payload>, json: bool) {
}

/// returns the base64 decoded values and signature verified result
fn decode_token(
pub(super) fn decode_token(
arguments: &DecodeArgs,
) -> (JWTResult<TokenData<Payload>>, JWTResult<TokenData<Payload>>) {
let header = match decode_header(&arguments.jwt) {
Expand Down Expand Up @@ -309,7 +309,7 @@ mod tests {
use super::*;

#[test]
fn test_decode_hs256_token_with_valid_jwt_and_secret() {
fn test_decode_hmac_token_with_valid_jwt_and_secret() {
let args = DecodeArgs {
jwt: String::from("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"),
secret: String::from("your-256-bit-secret"),
Expand All @@ -334,7 +334,7 @@ mod tests {
}

#[test]
fn test_decode_hs256_token_with_valid_jwt_and_b64secret() {
fn test_decode_hmac_token_with_valid_jwt_and_b64secret() {
let args = DecodeArgs {
jwt: String::from("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.DCwemWTIxJURgfU0rFIIo20__ZAhQbl3ZpQ44nf6Mqs"),
secret: String::from("b64:eW91ci0yNTYtYml0LXNlY3JldAo="),
Expand All @@ -359,7 +359,7 @@ mod tests {
}

#[test]
fn test_decode_rs256_token_with_valid_jwt() {
fn test_decode_rsa_token_with_valid_jwt_and_invalid_signature() {
let args = DecodeArgs {
jwt: String::from("eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IkRGbzcxemxOdV9vLTkxOFJIN0lIVyJ9.eyJodHRwczovL3d3dy5qaGlwc3Rlci50ZWNoL3JvbGVzIjpbIkFkbWluaXN0cmF0b3IiLCJST0xFX0FETUlOIiwiUk9MRV9VU0VSIl0sImlzcyI6Imh0dHBzOi8vZGV2LTA2YnpzMWN1LnVzLmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHw2MWJjYmM3NmY2NGQ0YTAwNzJhZjhhMWQiLCJhdWQiOlsiaHR0cHM6Ly9kZXYtMDZienMxY3UudXMuYXV0aDAuY29tL2FwaS92Mi8iLCJodHRwczovL2Rldi0wNmJ6czFjdS51cy5hdXRoMC5jb20vdXNlcmluZm8iXSwiaWF0IjoxNzA1MDAyMDQxLCJleHAiOjE3MDUwODg0NDEsImF6cCI6IjFmbTdJMUdHRXRNZlRabW5vdFV1azVVT3gyWm10NnR0Iiwic2NvcGUiOiJvcGVuaWQifQ.eWdbVEolnmqqyx_Z5rR-09H3kg06EaokYoAAdrqLmB6FHwZbbyZrPaHImmEnY8BSRM42FpE9NZehqVAeQ5VQhOVdMMklCQSA5h13oQbKn6ciuc9Etyq2jg4sk2lOEkSmw4e_hWUGjkXnzP_J84o9-2qpN7VKNTGEvtk3mdQYXxwoeD8RvQjYJq6LsKIKA0biEyGWZxIpK1LCAFH1dmo5ZMpTeNGIwnUBdOxkL4jbKe26e9t7TDO0EtFjXmq-C218bbr1AgFN2eyj6n-3kNy9XfRcnfIlyXWJ0ZvcDVa9UoaTGP9Wdo0Ze3q2IrcgYrP7zTeZia5O2tejkaNknKNnwA"),
secret: "".into(),
Expand All @@ -385,7 +385,30 @@ mod tests {
}

#[test]
fn test_decode_es384_token_with_valid_jwt_and_secret_pem() {
fn test_decode_rsa_pss_token_with_valid_jwt_and_secret() {
let args = DecodeArgs {
jwt: String::from("eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpYXQiOjE1MTYyMzkwMjIsIm5hbWUiOiJKb2huIERvZSIsInN1YiI6IjEyMzQ1Njc4OTAifQ.a6yeSQkIfGD1Va9TgdImZUZ1AKO0OgP15ZFV4JPpZy8TpeByQpqUA3r2kJHNeUlETyEeYMKsDbZI5dYOEa_ZfF9xY6eslV1xmawOPkJYzf8IK3Lb42GEykn9qBWSvHzh5xFs2U1dYjJ9GW7bqhyPVaRVRKh1EBw8AbXmEYT42xSDnzkVUHhPpGM8_2anJNXvnexCQKlVRVVzZC04eHNsRIl5_n50irg7bQCO4z24kwViMTuCQTalV9LXCfdxp7_3Pp4Av_iJtkKHDXWs9GrrD6ttq1J6jOXDSbxn42XrPlxirr0pNtdvbk58W2LqYz4_G9q0HTRz_WO3FmaSxIxyqQ"),
secret: "@./test_data/test_rsa_public_key.pem".into(),
time_format_utc: false,
ignore_exp: true,
};

let (decode_only, verified_token_data) = decode_token(&args);

assert!(decode_only.is_ok());
assert!(verified_token_data.is_ok());

let decode_only_token = decode_only.unwrap();

assert_eq!(decode_only_token.header.alg, Algorithm::RS256);
assert_eq!(
format!("{:?}", decode_only_token.claims.0.get("name").unwrap()),
"String(\"John Doe\")"
);
}

#[test]
fn test_decode_ecdsa_token_with_valid_jwt_and_secret_pem() {
let secret_file_name = "./test_data/test_ecdsa_public_key.pem";

let args = DecodeArgs {
Expand All @@ -412,7 +435,7 @@ mod tests {
}

#[test]
fn test_decode_rs256_token_with_valid_jwt_and_secret_der() {
fn test_decode_rsa_token_with_valid_jwt_and_secret_der() {
let secret_file_name = "./test_data/test_rsa_public_key.der";

let args = DecodeArgs {
Expand Down
254 changes: 191 additions & 63 deletions src/app/jwt_encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,61 +52,59 @@ impl Encoder<'_> {
}
}

#[derive(Debug)]
struct EncodeArgs {
pub header: String,
/// claims
pub payload: String,
/// The secret to sign the JWT with.
pub secret: String,
}

pub fn encode_jwt_token(app: &mut App) {
let header = app.data.encoder.header.input.lines().join("\n");
if header.is_empty() {
app.handle_error(String::from("Header should not be empty").into());
return;
let out = encode_token(&EncodeArgs {
header: app.data.encoder.header.input.lines().join("\n"),
payload: app.data.encoder.payload.input.lines().join("\n"),
secret: app.data.encoder.secret.input.value().to_string(),
});

match out {
Ok(token) => {
if token != app.data.encoder.encoded.get_txt() {
app.data.encoder.encoded = ScrollableTxt::new(token);
app.data.encoder.signature_verified = true;
}
app.data.error = String::new();
}
Err(e) => {
app.handle_error(e);
}
}
}

fn encode_token(args: &EncodeArgs) -> JWTResult<String> {
if args.header.is_empty() {
return Err(String::from("Header should not be empty").into());
}
let payload = app.data.encoder.payload.input.lines().join("\n");
if payload.is_empty() {
app.handle_error(String::from("Payload should not be empty").into());
return;
if args.payload.is_empty() {
return Err(String::from("Payload should not be empty").into());
}
let header: Result<Header, serde_json::Error> = serde_json::from_str(&header);
let header: Result<Header, serde_json::Error> = serde_json::from_str(&args.header);
match header {
Ok(header) => {
let alg = header.alg;

let payload: Result<Payload, serde_json::Error> = serde_json::from_str(&payload);
let payload: Result<Payload, serde_json::Error> = serde_json::from_str(&args.payload);
match payload {
Ok(payload) => {
let secret = app.data.encoder.secret.input.value();
let encoding_key = encoding_key_from_secret(&alg, secret);
match encoding_key {
Ok(encoding_key) => {
let token = jsonwebtoken::encode(&header, &payload, &encoding_key);
match token {
Ok(token) => {
if token != app.data.encoder.encoded.get_txt() {
app.data.encoder.encoded = ScrollableTxt::new(token);
app.data.encoder.signature_verified = true;
}
}
Err(e) => {
app.handle_error(e.into());
return;
}
}
}
Err(e) => {
app.handle_error(e);
return;
}
}
}
Err(e) => {
app.handle_error(format!("Error parsing payload: {:}", e).into());
return;
let encoding_key = encoding_key_from_secret(&alg, &args.secret)?;
Ok(jsonwebtoken::encode(&header, &payload, &encoding_key)?)
}
Err(e) => Err(format!("Error parsing payload: {:}", e).into()),
}
}
Err(e) => {
app.handle_error(format!("Error parsing header: {:}", e).into());
return;
}
Err(e) => Err(format!("Error parsing header: {:}", e).into()),
}
app.data.error = String::new();
}

pub fn encoding_key_from_secret(alg: &Algorithm, secret_string: &str) -> JWTResult<EncodingKey> {
Expand Down Expand Up @@ -154,14 +152,13 @@ pub fn encoding_key_from_secret(alg: &Algorithm, secret_string: &str) -> JWTResu

#[cfg(test)]
mod tests {
use jsonwebtoken::{DecodingKey, Validation};
use tui_textarea::TextArea;

use super::*;
use crate::app::{utils::slurp_file, utils::strip_leading_symbol};
use crate::app::jwt_decoder::{decode_token, DecodeArgs};

#[test]
fn test_encode_jwt_token_with_valid_payload_and_defaults() {
fn test_encode_hmac_jwt_token_with_valid_payload_and_defaults() {
let mut app = App::new(250, None, "secrets".into());

app.data.encoder.payload.input = vec![
Expand All @@ -181,10 +178,21 @@ mod tests {
.encoded
.get_txt(), "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1MTYyMzkwMjIsIm5hbWUiOiJKb2huIERvZSIsInN1YiI6IjEyMzQ1Njc4OTAifQ.TggX4VlPVD-2G5eUT5AhzepyMCx_nuzfiQ_YkdXsMKI");
assert!(app.data.encoder.signature_verified);

let args = DecodeArgs {
jwt: app.data.encoder.encoded.get_txt(),
secret: String::from("secrets"),
time_format_utc: false,
ignore_exp: true,
};

let decoded = decode_token(&args).1;

assert!(decoded.is_ok())
}

#[test]
fn test_encode_jwt_token_with_valid_payload_and_header_rs256() {
fn test_encode_rsa_jwt_token_with_valid_payload_and_header() {
let mut app = App::new(250, None, "".into());

let header = vec!["{", r#" "alg": "RS256","#, r#" "typ": "JWT""#, "}"];
Expand All @@ -211,23 +219,143 @@ mod tests {
assert!(app.data.encoder.signature_verified);

// decode the key and verify
let mut secret_validator = Validation::new(Algorithm::RS256);
secret_validator.leeway = 1000;
secret_validator
.required_spec_claims
.retain(|claim| claim != "exp");
secret_validator.validate_exp = false;

let secret_string = "@./test_data/test_rsa_public_key.pem";

let secret = slurp_file(strip_leading_symbol(secret_string)).unwrap();

let decoded = jsonwebtoken::decode::<Payload>(
&app.data.encoder.encoded.get_txt(),
&DecodingKey::from_rsa_pem(&secret).unwrap(),
&secret_validator,
)
.unwrap();
let args = DecodeArgs {
jwt: app.data.encoder.encoded.get_txt(),
secret: String::from("@./test_data/test_rsa_public_key.pem"),
time_format_utc: false,
ignore_exp: true,
};

let decoded = decode_token(&args).1.unwrap();

assert_eq!(
decoded.header,
serde_json::from_str(header.join("\n").as_str()).unwrap()
);
assert_eq!(
decoded.claims,
serde_json::from_str(claims.join("\n").as_str()).unwrap()
);
}

#[test]
fn test_encode_rsa_pss_jwt_token_with_valid_payload_and_header() {
let mut app = App::new(250, None, "".into());

let header = vec!["{", r#" "alg": "PS256","#, r#" "typ": "JWT""#, "}"];
app.data.encoder.header.input = header.clone().into();

let claims = vec![
"{",
r#" "sub": "1234567890","#,
r#" "name": "John Doe","#,
r#" "iat": 1516239022"#,
"}",
];
app.data.encoder.payload.input = claims.clone().into();

app.data.encoder.secret.input = "@./test_data/test_rsa_private_key.der".into();

encode_jwt_token(&mut app);
assert_eq!(app.data.error, "");
assert!(!app.data.encoder.encoded.get_txt().is_empty());
assert!(app.data.encoder.signature_verified);

// decode the key and verify
let args = DecodeArgs {
jwt: app.data.encoder.encoded.get_txt(),
secret: String::from("@./test_data/test_rsa_public_key.der"),
time_format_utc: false,
ignore_exp: true,
};

let decoded = decode_token(&args).1.unwrap();

assert_eq!(
decoded.header,
serde_json::from_str(header.join("\n").as_str()).unwrap()
);
assert_eq!(
decoded.claims,
serde_json::from_str(claims.join("\n").as_str()).unwrap()
);
}

#[test]
fn test_encode_ecdsa_jwt_token_with_valid_payload_and_header() {
let mut app = App::new(250, None, "".into());

let header = vec!["{", r#" "alg": "ES256","#, r#" "typ": "JWT""#, "}"];
app.data.encoder.header.input = header.clone().into();

let claims = vec![
"{",
r#" "sub": "1234567890","#,
r#" "name": "John Doe","#,
r#" "iat": 1516239022"#,
"}",
];
app.data.encoder.payload.input = claims.clone().into();

app.data.encoder.secret.input = "@./test_data/test_ecdsa_private_key.pk8".into();

encode_jwt_token(&mut app);
assert_eq!(app.data.error, "");
assert!(!app.data.encoder.encoded.get_txt().is_empty());
assert!(app.data.encoder.signature_verified);

// decode the key and verify
let args = DecodeArgs {
jwt: app.data.encoder.encoded.get_txt(),
secret: String::from("@./test_data/test_ecdsa_public_key.pk8"),
time_format_utc: false,
ignore_exp: true,
};

let decoded = decode_token(&args).1.unwrap();

assert_eq!(
decoded.header,
serde_json::from_str(header.join("\n").as_str()).unwrap()
);
assert_eq!(
decoded.claims,
serde_json::from_str(claims.join("\n").as_str()).unwrap()
);
}

#[test]
fn test_encode_eddsa_jwt_token_with_valid_payload_and_header() {
let mut app = App::new(250, None, "".into());

let header = vec!["{", r#" "alg": "EdDSA","#, r#" "typ": "JWT""#, "}"];
app.data.encoder.header.input = header.clone().into();

let claims = vec![
"{",
r#" "sub": "1234567890","#,
r#" "name": "John Doe","#,
r#" "iat": 1516239022"#,
"}",
];
app.data.encoder.payload.input = claims.clone().into();

app.data.encoder.secret.input = "@./test_data/test_eddsa_private_key.pem".into();

encode_jwt_token(&mut app);
assert_eq!(app.data.error, "");
assert!(!app.data.encoder.encoded.get_txt().is_empty());
assert!(app.data.encoder.signature_verified);

// decode the key and verify
let args = DecodeArgs {
jwt: app.data.encoder.encoded.get_txt(),
secret: String::from("@./test_data/test_eddsa_public_key.pem"),
time_format_utc: false,
ignore_exp: true,
};

let decoded = decode_token(&args).1.unwrap();

assert_eq!(
decoded.header,
Expand Down
Binary file added test_data/test_ecdsa_private_key.pk8
Binary file not shown.
1 change: 1 addition & 0 deletions test_data/test_ecdsa_public_key.pk8
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ò@�O�%�I�_����!I�AM ���L���y�5+\�I�����w�[��a �ԫxG2�GU�
Expand Down

0 comments on commit cbba5c0

Please sign in to comment.