use jwt_compact::alg::{Hs256, Hs256Key, Hs384, Hs384Key, Hs512, Hs512Key};
use jwt_compact::AlgorithmExt;
use crate::model::claims::TryFromTrustedJWTCompactClaims;
use crate::{
error::jwt_error::JWTError,
model::{
claims::JWTClaims, signing_algorithm::SigningKeyLength,
untrusted_token::untrusted_jwt_compact::UntrustedJWT,
},
};
use super::signature_validator::SignatureValidation;
pub struct HmacSignatureValidator {
key_length: SigningKeyLength,
secret: String,
}
impl HmacSignatureValidator {
pub fn new(key_length: SigningKeyLength, secret: String) -> Self {
Self { key_length, secret }
}
}
impl SignatureValidation for HmacSignatureValidator {
fn validate(&self, token: String) -> Result<JWTClaims, JWTError> {
UntrustedJWT::new(token.as_str())
.and_then(|untrusted| {
match self.key_length {
SigningKeyLength::Len256 => {
let key = Hs256Key::new(self.secret.clone());
Hs256.validator(&key).validate(&untrusted.into())
}
SigningKeyLength::Len384 => {
let key = Hs384Key::new(self.secret.clone());
Hs384.validator(&key).validate(&untrusted.into())
}
SigningKeyLength::Len512 => {
let key = Hs512Key::new(self.secret.clone());
Hs512.validator(&key).validate(&untrusted.into())
}
}
.map_err(JWTError::from)
})
.and_then(|claims| JWTClaims::try_from_trusted_jwt_compact_claims(claims, token))
}
}
#[cfg(test)]
mod tests {
use super::HmacSignatureValidator;
const HMAC_SECRET_256: &str = "dURKmcK2ENvNnfO1p1DPDmb8UfTGImHP";
const HMAC_SECRET_384: &str = "dURKmcK2ENvNnfO1p1DPDmb8UfTGImHPdURKmcK2ENvNnfO1";
const HMAC_SECRET_512: &str =
"dURKmcK2ENvNnfO1p1DPDmb8UfTGImHPdURKmcK2ENvNnfO1p1DPDmb8UfTGImHP";
fn hs256_validator() -> HmacSignatureValidator {
HmacSignatureValidator::new(
crate::model::signing_algorithm::SigningKeyLength::Len256,
String::from(HMAC_SECRET_256),
)
}
fn hs384_validator() -> HmacSignatureValidator {
HmacSignatureValidator::new(
crate::model::signing_algorithm::SigningKeyLength::Len384,
String::from(HMAC_SECRET_384),
)
}
fn hs512_validator() -> HmacSignatureValidator {
HmacSignatureValidator::new(
crate::model::signing_algorithm::SigningKeyLength::Len512,
String::from(HMAC_SECRET_512),
)
}
mod hmac_validator_256 {
use crate::error::jwt_error::JWTErrorEnumKind;
use crate::validator::hmac_validator::SignatureValidation;
use crate::validator::signature_validator::tests::assert_error_kind;
use test_case::test_case;
use super::hs256_validator;
extern crate test_case;
#[test]
fn validation_fails_when_token_not_sent() {
let token = "";
let validation: Result<_, crate::error::jwt_error::JWTError> =
hs256_validator().validate(token.to_string());
assert_error_kind(&validation, JWTErrorEnumKind::TokenParseFailed);
}
#[test]
fn unparseable_token_is_rejected() {
let token = "WhatAmI?AnInvalidToken!";
let validation = hs256_validator().validate(token.to_string());
assert_error_kind(&validation, JWTErrorEnumKind::TokenParseFailed);
}
#[test]
fn validation_fails_on_invalid_signature() {
let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.bmI9mERm8_qnYPgBMh-IaQdA5KBdRPOSvazkGB-7ElI";
let validation = hs256_validator().validate(token.to_string());
assert_error_kind(&validation, JWTErrorEnumKind::ValidationFailed);
}
#[test]
fn validation_fails_on_missing_signature() {
let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.";
let validation = hs256_validator().validate(token.to_string());
assert_error_kind(&validation, JWTErrorEnumKind::ValidationFailed);
}
#[test]
fn verify_valid_token_is_correctly_parsed() {
let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjgyMDA1ODI2NH0.mu54d8dajyU_eMa4VCE4CcG5HzN7nNflxbWUw7JO0V0";
let validation = hs256_validator().validate(token.to_string());
let jwt_claims = validation.unwrap();
assert_eq!(jwt_claims.get_header("alg").unwrap(), "HS256");
assert_eq!(
jwt_claims.expiration().unwrap().to_string(),
"1995-12-27 09:57:44 UTC"
);
}
#[test_case("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.5bW99CUMyOZjGkG1niOmlBINR8Vzp9x6tO_4rja9naw"; "verifyValidToken 256")]
#[test_case("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjgyMDA1ODI2NH0.mu54d8dajyU_eMa4VCE4CcG5HzN7nNflxbWUw7JO0V0"; "exceptionNotThrownOnExpiredTokenWithNoClaimValidation 256")]
#[test_case("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.5bW99CUMyOZjGkG1niOmlBINR8Vzp9x6tO_4rja9naw"; "exceptionNotThrownOnMissingClaim 256")]
#[test_case("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjMyODAyMzMyMDl9.gmXcQZjAjUu5yDDF8Tz6PHgkjyaDezrT2-fRDQ0AGKA"; "verifyNotExpiredToken 256")]
#[test_case("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjgyMDA1ODMyMn0.QJoKD7HaF7nOiTZO6esguJ7tF7MCRiDAmf553OtAxBA"; "exceptionNotThrownOnExpiredToken 256")]
#[test_case("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYmYiOjgyMDA3MjU2Mn0.7C6AhlGvT0KrmKcV4LnKPmMB_pq0aBfdG7r2TkbViLA"; "verifyTokenWithNbf 256")]
#[test_case("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYmYiOjMyODAyOTA0MzR9.Joc61cnZDakVYIjaOARLWLlrM0qgG4O0OlaJp3Q58g4"; "exceptionNotThrownNotYetActiveToken 256")]
#[test_case("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJmb28ifQ.fBfbNCCLjjrRf8dg3htXM-r0hh4m7jLCvLAzRsgqBCg"; "verifyTokenWithAud 256")]
#[test_case("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOlsiZXRjIiwiZm9vIiwiYmxhaCJdfQ.IRYninPbdgWb1i_9FhRSobjjVHPkCg4nH_QH1MqfhHo"; "verifyTokenWithAudArray 256")]
#[test_case("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOlsiZXRjIiwiZm9vIGJhciIsImJsYWgiXX0.2diRbOvDICKy14bWgeqnzVTpe_ig3bTTx6FSw9jwi6I"; "verifyTokenWithAudiencesWithSpaces 256")]
#[test_case("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOlsiZXRjIiwiRm9vIiwiYmxhaCJdfQ.esjtb7g7x7p4dbEkF7dh7PRhR___dWW8llq5Hm6lGQE"; "verifyTokenWithAudiencesCaseSensitive 256")]
#[test_case("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOlsiZXRjIiwiYmxhaCJdfQ.mweyE1Ip21sSrcDkr_IndOOW26UygQ3eicSYXyXTcto"; "exceptionNotThrownWhenInvalidAudToken 256")]
#[test_case("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJmb28iLCJuYmYiOjgyMDA3Mjk0MSwibmFtZSI6IkpvaG4gRG9lIiwic2NvcGVzIjpbInJlYWQiLCJ3cml0ZSJdLCJleHAiOjMyODAyOTE3Njd9.dSZOHQCS7RGt6nQKjMnfQg7V8UxLAPZ1QzwcZ1Aaz6s"; "checkAllValidations 256")]
fn verify_valid_token_256(token: &str) {
let validation = hs256_validator().validate(token.to_string());
assert!(validation.is_ok());
}
}
mod hmac_validator_384 {
use crate::validator::hmac_validator::SignatureValidation;
use crate::{
error::jwt_error::JWTErrorEnumKind,
validator::signature_validator::tests::assert_error_kind,
};
use test_case::test_case;
use super::hs384_validator;
extern crate test_case;
#[test]
fn validation_fails_when_token_not_sent() {
let token = "";
let validation = hs384_validator().validate(token.to_string());
assert_error_kind(&validation, JWTErrorEnumKind::TokenParseFailed);
}
#[test]
fn unparseable_token_is_rejected() {
let token = "WhatAmI?AnInvalidToken!";
let validation = hs384_validator().validate(token.to_string());
assert_error_kind(&validation, JWTErrorEnumKind::TokenParseFailed);
}
#[test]
fn validation_fails_on_invalid_signature() {
let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzM4NCJ9.e30.XYsOC2_46dliJ2gL5h6KC_h1-LBSzJ1PyYL4cK5vON-SGSGHDEl8z6ikWaH9s4Ne";
let validation = hs384_validator().validate(token.to_string());
assert_error_kind(&validation, JWTErrorEnumKind::ValidationFailed);
}
#[test]
fn validation_fails_on_missing_signature() {
let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzM4NCJ9.e30.";
let validation = hs384_validator().validate(token.to_string());
assert_error_kind(&validation, JWTErrorEnumKind::ValidationFailed);
}
#[test_case("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzM4NCJ9.e30.ss6xb6Vbjrutq2OjcNI2uxwLhL1zkMzDb4ff0vY8sLoa2eOJioVwBm4EM1ALtye6"; "verifyValidToken 384")]
#[test_case("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzM4NCJ9.eyJleHAiOjgyMDA5NTAyMn0.gcG76syx-Eq8p4Mq6c-LIoMJ6pA1371YVmAeADgKGAbLwON-rrTQVmqQpvrqABY3"; "exceptionNotThrownOnExpiredTokenWithNoClaimValidation 384")]
#[test_case("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzM4NCJ9.e30.ss6xb6Vbjrutq2OjcNI2uxwLhL1zkMzDb4ff0vY8sLoa2eOJioVwBm4EM1ALtye6"; "exceptionNotThrownOnMissingClaim 384")]
#[test_case("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzM4NCJ9.eyJleHAiOjMyODAzODA5MjR9.bApP3Q27KFePahE4LTvZPQTW__qwbc99MlKklGE_XeXJQrq6SV6UwlDdoAaVIyoB"; "verifyNotExpiredToken 384")]
#[test_case("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzM4NCJ9.eyJleHAiOjgyMDA5NTI1Nn0.ZEAPO_dzAjmx504Fa3TBKGJd9nJbSFdwUwu6al8YgeNb65ug6epHDa1Mm5cdC4tk"; "exceptionNotThrownOnExpiredToken 384")]
#[test_case("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzM4NCJ9.eyJuYmYiOjgyMDA5NTI5Nn0.qTfbKz9-PJNDiZh5-RHkMFSEDiZf49zNawIhu-tXapMY9SROARtVyikQ-vYerN1p"; "verifyTokenWithNbf 384")]
#[test_case("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzM4NCJ9.eyJuYmYiOjMyODAzODEyODd9.raA8Qj2pdQC-PNG_efkOdQIII4nW1Vcos0vLxvBhJD7631_9ztOBA6Q3Y_qQO0L5"; "exceptionNotThrownNotYetActiveToken 384")]
#[test_case("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzM4NCJ9.eyJhdWQiOiJmb28ifQ.ZibYb11Fq-PgHQWDwmn9lcNU4Zl_AeDK7_KJ9h56CfULhnCCNju7wmnVM9N-oylT"; "verifyTokenWithAud 384")]
#[test_case("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzM4NCJ9.eyJhdWQiOlsiZXRjIiwiZm9vIiwiYmxhaCJdfQ.WmLAwgP1NJMa_mbxF3N3ijCejFFbpQHXcDfEDa_oBYQyDOHrGCT2C9awEHkYefZ-"; "verifyTokenWithAudArray 384")]
#[test_case("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzM4NCJ9.eyJhdWQiOlsiZXRjIiwiZm9vIGJhciIsImJsYWgiXX0.vKw-gGIIHTrM27j6AZ1964KuhzWZdX2c5hl21ElpGK8SATk0_JBV6Fo4f37H2W9b"; "verifyTokenWithAudiencesWithSpaces 384")]
#[test_case("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzM4NCJ9.eyJhdWQiOlsiZXRjIiwiRm9vIiwiYmxhaCJdfQ.eO2q__MG-dJ4g9ms7dAD5RtnGIQ5_keoOhnn44jBVOErIhGFMOgDwoY2Two9ySKM"; "verifyTokenWithAudiencesCaseSensitive 384")]
#[test_case("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzM4NCJ9.eyJhdWQiOlsiZXRjIiwiYmxhaCJdfQ.R9b-YQ__m-qCZwb2ibo6dpkg-pLBTs_p8UJW-4kp-ROsp4DHcbNISYQXZZyLOx_y"; "exceptionNotThrownWhenInvalidAudToken 384")]
#[test_case("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzM4NCJ9.eyJhdWQiOiJmb28iLCJuYmYiOjgyMDA5NTQ1OSwibmFtZSI6IkpvaG4gRG9lIiwic2NvcGVzIjpbInJlYWQiLCJ3cml0ZSJdLCJleHAiOjMyODAzODE4Mzd9.ba_JRLG82Pmj7u7ybfhnFHGwn75HemL4YuMRC5ug3llo82gkykooPZlZniAhaxl6"; "checkAllValidations 384")]
fn verify_valid_token_384(token: &str) {
let validation = hs384_validator().validate(token.to_string());
assert!(validation.is_ok());
}
}
mod hmac_validator_512 {
use super::hs512_validator;
use crate::validator::hmac_validator::SignatureValidation;
use crate::{
error::jwt_error::JWTErrorEnumKind,
validator::signature_validator::tests::assert_error_kind,
};
use test_case::test_case;
extern crate test_case;
#[test]
fn validation_fails_when_token_not_sent() {
let token = "";
let validation = hs512_validator().validate(token.to_string());
assert_error_kind(&validation, JWTErrorEnumKind::TokenParseFailed);
}
#[test]
fn unparseable_token_is_rejected() {
let token = "WhatAmI?AnInvalidToken!";
let validation = hs512_validator().validate(token.to_string());
assert_error_kind(&validation, JWTErrorEnumKind::TokenParseFailed);
}
#[test]
fn validation_fails_on_invalid_signature() {
let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.e30.Ba3m9DcgHHeLKAh7KthRASIDNm7G_nZDkVJ_gh1wXQ3IJFS5moG_qohHl8ZoJI5WZOSSklnFWzEYPTbFtgeUzg";
let validation = hs512_validator().validate(token.to_string());
assert_error_kind(&validation, JWTErrorEnumKind::ValidationFailed);
}
#[test]
fn validation_fails_on_missing_signature() {
let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.e30.";
let validation = hs512_validator().validate(token.to_string());
assert_error_kind(&validation, JWTErrorEnumKind::ValidationFailed);
}
#[test_case("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.e30.BW5kqJDEnSVJeUK0xxK2eu6ZmRUI-NjRF1i9D3p1Pq2GiKxCiLBhISXGJ081TH5yDgSAOvuXn4Ie0UeEqa6aEA"; "verifyValidToken 512")]
#[test_case("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJleHAiOjgyMDA5NTgzMX0.p8Tqf9PFUxLWfFgOw7HfJNgItHQa0qfyn-bAESAwZZR4Mr-8yRk72dc83iz1remfeVHLD65lWZRUPjAzcvV-iQ"; "exceptionNotThrownOnExpiredTokenWithNoClaimValidation 512")]
#[test_case("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.e30.BW5kqJDEnSVJeUK0xxK2eu6ZmRUI-NjRF1i9D3p1Pq2GiKxCiLBhISXGJ081TH5yDgSAOvuXn4Ie0UeEqa6aEA"; "exceptionNotThrownOnMissingClaim 512")]
#[test_case("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJleHAiOjMyODAzODMzMjd9.0BalxeKKvKSn4296jc-PZtuAUGMvPz567Mrz8FhPq__Tpgpvh_KtbVmZJv73-R6uWqElgU7TP--iLOiXhK6WpA"; "verifyNotExpiredToken 512")]
#[test_case("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJleHAiOjgyMDA5NTgzMX0.p8Tqf9PFUxLWfFgOw7HfJNgItHQa0qfyn-bAESAwZZR4Mr-8yRk72dc83iz1remfeVHLD65lWZRUPjAzcvV-iQ"; "exceptionNotThrownOnExpiredToken 512")]
#[test_case("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJuYmYiOjgyMDA5NTgzMX0.UVZnwEmojvsdGYu1mUPMvke4pah7w0sVmFwPCQjgOziJT_OCf80XwjJe9Paja7cmI1pwNj29Jhm7bNGHEPF9nQ"; "verifyTokenWithNbf 512")]
#[test_case("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJuYmYiOjMyODAzODMzMjd9.kOPL23VA1adljaBIcxcFze3sETlKUT6Bxdwkkef6RJMi7bJJZs7WXXskDBem_Bb4HNAesJq_OEN0K0uOXpWCBw"; "exceptionNotThrownNotYetActiveToken 512")]
#[test_case("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJhdWQiOiJmb28ifQ.2KV55R0_PnHQt3Ua2BRg1xC8Tz_dz87jt7Jn-_ya4EmCQeJzVrGnhiQFn3pW9990loBt_sg64ssWF2LpLpmkfg"; "verifyTokenWithAud 512")]
#[test_case("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJhdWQiOlsiZXRjIiwiZm9vIiwiYmxhaCJdfQ.hejTNLlKpqka_dX7dDNsm2ht7ehEsFPnSi85jAJFuT7PBNjkyAOA6ydBfDCrWoKJvoCpxtvpf-g3H0c0zbBixg"; "verifyTokenWithAudArray 512")]
#[test_case("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJhdWQiOlsiZXRjIiwiZm9vIGJhciIsImJsYWgiXX0.kE5CaSAY7Xw_YKJ3hx288_pchLRqMb9x5-auybhmJK6IJ-abEOwxusxV7zgTX0UDx_Z8Xz99Rg29rSI4IikAQA"; "verifyTokenWithAudiencesWithSpaces 512")]
#[test_case("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJhdWQiOlsiZXRjIiwiRm9vIiwiYmxhaCJdfQ.wjqG6WEvzJlyNvsdX2pitffMC4C2fL5wuwPNAGbYITtIDveeaBB2RZr0az08gw8wQWLnj_4zDnAt0tvrL-9ObA"; "verifyTokenWithAudiencesCaseSensitive 512")]
#[test_case("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJhdWQiOlsiZXRjIiwiYmxhaCJdfQ.2Fkk6wdr0FPmG0dyJHC6hF81xf3R-VKBVPJgPfc_fcLXrRlafMo-ClZXtPV5FXmeArkEuQ8wt6p7rGoLiRC1-g"; "exceptionNotThrownWhenInvalidAudToken 512")]
#[test_case("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJhdWQiOiJmb28iLCJuYmYiOjgyMDA5NTgzMSwibmFtZSI6IkpvaG4gRG9lIiwic2NvcGVzIjpbInJlYWQiLCJ3cml0ZSJdLCJleHAiOjMyODAzODMzMjd9.TFaQJ46jOGIP8wBD2EaMIsheC-nQgTlj0a5IwUZQtW6h_C6xVjxPI0lr_9H73gF1S2-Ksf8q3OC6BaTFag6SXg"; "checkAllValidations 512")]
fn verify_valid_token_512(token: &str) {
let validation = hs512_validator().validate(token.to_string());
assert!(validation.is_ok());
}
}
}