use base64::Engine;
use jwt_verify::{CognitoJwtVerifier, JwtError, JwtVerifier, VerifierConfig};
use std::time::{SystemTime, UNIX_EPOCH};
fn create_test_token(header: &str, payload: &str, signature: &str) -> String {
format!("{}.{}.{}", header, payload, signature)
}
fn base64_encode(data: &str) -> String {
base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(data)
}
fn current_timestamp() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
}
fn create_test_id_token(issuer: &str, kid: &str, exp_offset: i64) -> String {
let now = current_timestamp();
let exp = now + exp_offset as u64;
let header = base64_encode(&format!(r#"{{"alg":"RS256","kid":"{}","typ":"JWT"}}"#, kid));
let payload = base64_encode(&format!(
r#"{{
"sub": "test-user-id",
"iss": "{}",
"client_id": "test-client-id",
"token_use": "id",
"auth_time": {},
"exp": {},
"iat": {},
"jti": "test-jwt-id",
"email": "test@example.com",
"email_verified": true,
"cognito:username": "testuser",
"aud": "test-client-id"
}}"#,
issuer,
now - 300,
exp,
now
));
let signature = "test-signature";
create_test_token(&header, &payload, signature)
}
fn create_tampered_token(issuer: &str, kid: &str, tamper_type: &str) -> String {
let now = current_timestamp();
let exp = now + 3600;
let header = base64_encode(&format!(r#"{{"alg":"RS256","kid":"{}","typ":"JWT"}}"#, kid));
let payload = match tamper_type {
"modified_sub" => base64_encode(&format!(
r#"{{
"sub": "hacker-user-id",
"iss": "{}",
"client_id": "test-client-id",
"token_use": "id",
"auth_time": {},
"exp": {},
"iat": {},
"jti": "test-jwt-id",
"email": "test@example.com"
}}"#,
issuer,
now - 300,
exp,
now
)),
"elevated_permissions" => base64_encode(&format!(
r#"{{
"sub": "test-user-id",
"iss": "{}",
"client_id": "test-client-id",
"token_use": "id",
"auth_time": {},
"exp": {},
"iat": {},
"jti": "test-jwt-id",
"email": "test@example.com",
"cognito:groups": ["admin", "superuser"]
}}"#,
issuer,
now - 300,
exp,
now
)),
"modified_iat" => base64_encode(&format!(
r#"{{
"sub": "test-user-id",
"iss": "{}",
"client_id": "test-client-id",
"token_use": "id",
"auth_time": {},
"exp": {},
"iat": {},
"jti": "test-jwt-id",
"email": "test@example.com"
}}"#,
issuer,
now - 300,
exp,
now - 86400 )),
_ => base64_encode(&format!(
r#"{{
"sub": "test-user-id",
"iss": "{}",
"client_id": "test-client-id",
"token_use": "id",
"auth_time": {},
"exp": {},
"iat": {},
"jti": "test-jwt-id",
"email": "test@example.com"
}}"#,
issuer,
now - 300,
exp,
now
)),
};
let signature = "test-signature";
create_test_token(&header, &payload, signature)
}
fn create_none_algorithm_token(issuer: &str) -> String {
let now = current_timestamp();
let exp = now + 3600;
let header = base64_encode(r#"{"alg":"none","typ":"JWT"}"#);
let payload = base64_encode(&format!(
r#"{{
"sub": "test-user-id",
"iss": "{}",
"client_id": "test-client-id",
"token_use": "id",
"auth_time": {},
"exp": {},
"iat": {},
"jti": "test-jwt-id",
"email": "test@example.com",
"cognito:groups": ["admin"]
}}"#,
issuer,
now - 300,
exp,
now
));
let signature = "";
create_test_token(&header, &payload, signature)
}
fn create_invalid_signature_token(issuer: &str, kid: &str) -> String {
let now = current_timestamp();
let exp = now + 3600;
let header = base64_encode(&format!(r#"{{"alg":"RS256","kid":"{}","typ":"JWT"}}"#, kid));
let payload = base64_encode(&format!(
r#"{{
"sub": "test-user-id",
"iss": "{}",
"client_id": "test-client-id",
"token_use": "id",
"auth_time": {},
"exp": {},
"iat": {},
"jti": "test-jwt-id",
"email": "test@example.com"
}}"#,
issuer,
now - 300,
exp,
now
));
let signature = "invalid-signature";
create_test_token(&header, &payload, signature)
}
fn create_expired_token(issuer: &str, kid: &str, expired_seconds_ago: u64) -> String {
let now = current_timestamp();
let exp = now - expired_seconds_ago;
let header = base64_encode(&format!(r#"{{"alg":"RS256","kid":"{}","typ":"JWT"}}"#, kid));
let payload = base64_encode(&format!(
r#"{{
"sub": "test-user-id",
"iss": "{}",
"client_id": "test-client-id",
"token_use": "id",
"auth_time": {},
"exp": {},
"iat": {},
"jti": "test-jwt-id",
"email": "test@example.com"
}}"#,
issuer,
now - expired_seconds_ago - 3600, exp,
now - expired_seconds_ago - 3600 ));
let signature = "test-signature";
create_test_token(&header, &payload, signature)
}
fn create_wrong_issuer_token(kid: &str) -> String {
let now = current_timestamp();
let exp = now + 3600;
let header = base64_encode(&format!(r#"{{"alg":"RS256","kid":"{}","typ":"JWT"}}"#, kid));
let payload = base64_encode(&format!(
r#"{{
"sub": "test-user-id",
"iss": "https://wrong-issuer.com",
"client_id": "test-client-id",
"token_use": "id",
"auth_time": {},
"exp": {},
"iat": {},
"jti": "test-jwt-id",
"email": "test@example.com"
}}"#,
now - 300,
exp,
now
));
let signature = "test-signature";
create_test_token(&header, &payload, signature)
}
fn create_future_iat_token(issuer: &str, kid: &str, seconds_in_future: u64) -> String {
let now = current_timestamp();
let future_time = now + seconds_in_future;
let exp = future_time + 3600;
let header = base64_encode(&format!(r#"{{"alg":"RS256","kid":"{}","typ":"JWT"}}"#, kid));
let payload = base64_encode(&format!(
r#"{{
"sub": "test-user-id",
"iss": "{}",
"client_id": "test-client-id",
"token_use": "id",
"auth_time": {},
"exp": {},
"iat": {},
"jti": "test-jwt-id",
"email": "test@example.com"
}}"#,
issuer,
future_time,
exp,
future_time
));
let signature = "test-signature";
create_test_token(&header, &payload, signature)
}
#[tokio::test]
async fn test_tampered_tokens() {
let config = VerifierConfig::new(
"us-east-1",
"us-east-1_example",
&["test-client-id".to_string()],
None,
)
.unwrap();
let verifier = CognitoJwtVerifier::new(vec![config]).unwrap();
let issuer = "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_example";
let tampered_sub_token = create_tampered_token(issuer, "test-key-1", "modified_sub");
let result = verifier.verify_id_token(&tampered_sub_token).await;
assert!(result.is_err());
let elevated_perms_token = create_tampered_token(issuer, "test-key-1", "elevated_permissions");
let result = verifier.verify_id_token(&elevated_perms_token).await;
assert!(result.is_err());
let modified_iat_token = create_tampered_token(issuer, "test-key-1", "modified_iat");
let result = verifier.verify_id_token(&modified_iat_token).await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_none_algorithm_attack() {
let config = VerifierConfig::new(
"us-east-1",
"us-east-1_example",
&["test-client-id".to_string()],
None,
)
.unwrap();
let verifier = CognitoJwtVerifier::new(vec![config]).unwrap();
let issuer = "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_example";
let none_alg_token = create_none_algorithm_token(issuer);
let result = verifier.verify_id_token(&none_alg_token).await;
assert!(result.is_err());
match result.unwrap_err() {
JwtError::InvalidClaim { claim, .. } => {
assert_eq!(claim, "alg");
},
JwtError::ParseError { .. } => {
},
err => panic!("Expected InvalidClaim or ParseError, got: {:?}", err),
}
}
#[tokio::test]
async fn test_invalid_signature() {
let config = VerifierConfig::new(
"us-east-1",
"us-east-1_example",
&["test-client-id".to_string()],
None,
)
.unwrap();
let verifier = CognitoJwtVerifier::new(vec![config]).unwrap();
let issuer = "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_example";
let invalid_sig_token = create_invalid_signature_token(issuer, "test-key-1");
let result = verifier.verify_id_token(&invalid_sig_token).await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_expired_tokens() {
let config = VerifierConfig::new(
"us-east-1",
"us-east-1_example",
&["test-client-id".to_string()],
None,
)
.unwrap();
let verifier = CognitoJwtVerifier::new(vec![config]).unwrap();
let issuer = "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_example";
let expired_token = create_expired_token(issuer, "test-key-1", 3600);
let result = verifier.verify_id_token(&expired_token).await;
assert!(result.is_err());
let just_expired_token = create_expired_token(issuer, "test-key-1", 1);
let result = verifier.verify_id_token(&just_expired_token).await;
assert!(result.is_err());
let long_expired_token = create_expired_token(issuer, "test-key-1", 31536000); let result = verifier.verify_id_token(&long_expired_token).await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_wrong_issuer() {
let config = VerifierConfig::new(
"us-east-1",
"us-east-1_example",
&["test-client-id".to_string()],
None,
)
.unwrap();
let verifier = CognitoJwtVerifier::new(vec![config]).unwrap();
let wrong_issuer_token = create_wrong_issuer_token("test-key-1");
let result = verifier.verify_id_token(&wrong_issuer_token).await;
assert!(result.is_err());
match result.unwrap_err() {
JwtError::ConfigurationError { parameter, .. } => {
assert_eq!(parameter, Some("issuer".to_string()));
},
err => panic!("Expected ConfigurationError, got: {:?}", err),
}
}
#[tokio::test]
async fn test_future_issued_at() {
let config = VerifierConfig::new(
"us-east-1",
"us-east-1_example",
&["test-client-id".to_string()],
None,
)
.unwrap();
let verifier = CognitoJwtVerifier::new(vec![config]).unwrap();
let issuer = "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_example";
let future_token = create_future_iat_token(issuer, "test-key-1", 3600);
let result = verifier.verify_id_token(&future_token).await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_cross_pool_token_rejection() {
let config1 = VerifierConfig::new(
"us-east-1",
"us-east-1_pool1",
&["client-id-1".to_string()],
None,
)
.unwrap();
let config2 = VerifierConfig::new(
"us-west-2",
"us-west-2_pool2",
&["client-id-2".to_string()],
None,
)
.unwrap();
let verifier = CognitoJwtVerifier::new(vec![config1, config2]).unwrap();
let issuer1 = "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_pool1";
let issuer2 = "https://cognito-idp.us-west-2.amazonaws.com/us-west-2_pool2";
let token1 = create_test_id_token(issuer1, "test-key-1", 3600);
let token2 = create_test_id_token(issuer2, "test-key-1", 3600);
let modified_token1 = token1.replace(issuer1, issuer2);
let result = verifier.verify_id_token(&modified_token1).await;
assert!(result.is_err());
let modified_token2 = token2.replace(issuer2, issuer1);
let result = verifier.verify_id_token(&modified_token2).await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_token_replay_detection() {
let config = VerifierConfig::new(
"us-east-1",
"us-east-1_example",
&["test-client-id".to_string()],
None,
)
.unwrap();
let verifier = CognitoJwtVerifier::new(vec![config]).unwrap();
let issuer = "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_example";
let token = create_test_id_token(issuer, "test-key-1", 3600);
let result1 = verifier.verify_id_token(&token).await;
assert!(result1.is_err());
let result2 = verifier.verify_id_token(&token).await;
assert!(result2.is_err());
}