From 484d2093febf7335562d0ee1dd0c5c2aa01c37a0 Mon Sep 17 00:00:00 2001 From: "Sebastian H. Gabrielli" Date: Sat, 30 Dec 2023 23:14:53 +0100 Subject: [PATCH] Serparate JWT contents into separate file The JWT validation functions and structs have been moved to it's own file. --- src/jwt_validation.rs | 225 +++++++++++++++++++++++++++++++++++++++++ src/main.rs | 227 +----------------------------------------- 2 files changed, 227 insertions(+), 225 deletions(-) create mode 100644 src/jwt_validation.rs diff --git a/src/jwt_validation.rs b/src/jwt_validation.rs new file mode 100644 index 0000000..238ff14 --- /dev/null +++ b/src/jwt_validation.rs @@ -0,0 +1,225 @@ +use jsonwebtoken::{decode, decode_header, Algorithm, DecodingKey, Validation, TokenData}; +use reqwest; +use serde::Deserialize; +use std::collections::HashMap; + +// Define a struct for the claims you expect in your token +#[derive(Debug, Deserialize)] +pub struct MyClaims { + pub sub: String, + pub exp: usize, + pub aud: String, + pub iss: String, + pub preferred_username: Option, +} + +#[derive(Deserialize)] +struct AuthorizationWellKnown { + issuer: String, + jwks_uri: String, +} + +#[derive(Deserialize)] +struct JwksContent { + kid: String, + x5c: Vec, +} + +#[derive(Deserialize)] +struct Jwks { + keys: Vec, +} + +pub struct JwtInfo { + pub jwks_uri: String, + pub audience: Vec, + pub issuer: Vec, + pub public_keys: HashMap, +} + +#[derive(Debug)] +pub enum MyCustomErrorType { + NetworkError, + JwtError, + JsonParseError, +} + +pub fn validate_jwt(token: &str, jwt_info: &mut JwtInfo) -> Result { + // Decode the header to give info about the crypto + let jwt_header = decode_header(token)?; + + // Create a new validation + let mut validation = Validation::new(jwt_header.alg); + // Set the expected audience and issuer + validation.set_audience(&jwt_info.audience); + validation.set_issuer(&jwt_info.issuer); + + // Extract the JWT kid + let kid: String; + match jwt_header.kid { + Some(fetched_kid) => kid = fetched_kid, + None => { + eprintln!("Unable to extract KID from jwt header"); + return Err(jsonwebtoken::errors::ErrorKind::InvalidToken.into()); + } + } + + // Fetch the corresponding public key + let public_key_pem: &String; + match jwt_info.public_keys.get(&kid) { + Some(key) => public_key_pem = key, + None => { + // If the key doesn't exist look up the keys again + match fetch_jwt_certificates(jwt_info) { + Some(key_map) => jwt_info.public_keys = key_map, + None => { + eprintln!("Failed to fetch jwt pem certificates"); + } + } + + // Try to get the keys once more + match jwt_info.public_keys.get(&kid) { + Some(key) => public_key_pem = key, + None => { + eprintln!("Failed to fetch find matching certificates for given KID. {}", kid); + return Err(jsonwebtoken::errors::ErrorKind::InvalidToken.into()); + } + } + } + } + + // Decode the JWT token + let token_data: TokenData; + match jwt_header.alg { + Algorithm::RS256 => { + token_data = decode::( + token, + &DecodingKey::from_rsa_pem(public_key_pem.as_bytes())?, + &validation, + )?; + }, + Algorithm::ES256 => { + token_data = decode::( + token, + &DecodingKey::from_ec_pem(public_key_pem.as_bytes())?, + &validation, + )?; + }, + _ => { + eprintln!("JWT Public key algoritm not handled"); + return Err(jsonwebtoken::errors::ErrorKind::InvalidAlgorithm.into()); + } + } + + Ok(token_data.claims) +} + +fn fetch_jwt_certificates(jwt_info: &JwtInfo) -> Option> { + // Fetch the JWKS endpoint + let jwks_body: String; + match reqwest::blocking::get(&jwt_info.jwks_uri) { + Ok(response) => { + match response.text() { + Ok(text) => jwks_body = text, + Err(e) => { + eprintln!("Failed to extract text from response body with error:\n{}", e); + return None; + } + } + }, + Err(e) => { + eprintln!("Failed to get the jwks_uri with error:\n{}", e); + return None; + } + } + + // Parse the data into the struct + let jwks_data: Jwks; + match serde_json::from_str(&jwks_body) { + Ok(jwks) => jwks_data = jwks, + Err(e) => { + eprintln!("Failed to parse fetched jwks body to Jwks struct with error:\n{}", e); + return None; + } + } + + // Create the output hashmap + let mut output_map: HashMap = HashMap::new(); + + // Go through each pair of keys and add them to the output jwt info + for key in jwks_data.keys { + // Extract the x5c key data + let x5c = key.x5c.get(0)?; + + // Add the PEM info in to the x5c + let pem_data = format!("-----BEGIN CERTIFICATE-----\n{}\n-----END CERTIFICATE-----", x5c); + + // Add the resulting key to the hashmap + output_map.insert(key.kid, pem_data); + } + + // Check that we got any keys + if output_map.is_empty() { + eprintln!("Failed to fetch any public keys"); + return None; + } + + Some(output_map) +} + +pub fn fetch_jwt_info(well_known_uri: &str, expected_issuer: Vec) -> Result { + // Fetch the info from the well known endpoint + let well_known_body; + match reqwest::blocking::get(well_known_uri) { + Ok(response) => { + match response.text() { + Ok(text) => well_known_body = text, + Err(e) => { + eprintln!("Failed to extract text from response body with error:\n{}", e); + return Err(MyCustomErrorType::NetworkError); + } + } + }, + Err(e) => { + eprintln!("Failed to get the well known with error:\n{}", e); + return Err(MyCustomErrorType::NetworkError); + } + } + + // Parse the data into the well known struct + let well_known_data: AuthorizationWellKnown; + match serde_json::from_str(&well_known_body) { + Ok(data) => well_known_data = data, + Err(e) => { + eprintln!("Failed to parse well known data into struct with err:\n{}", e); + return Err(MyCustomErrorType::JsonParseError); + } + } + + // Validate the issuer + if !expected_issuer.contains(&well_known_data.issuer) { + eprintln!( + "Expected issuer does not contain fetched issuer.\n{} ∉ {:?}", + well_known_data.issuer, expected_issuer + ); + return Err(MyCustomErrorType::JwtError); + } + + // Create a JwtInfo variable + let mut jwt_info: JwtInfo = JwtInfo { + jwks_uri: well_known_data.jwks_uri, + audience: Vec::new(), + issuer: expected_issuer, + public_keys: HashMap::new(), + }; + + // Fetch the valid public keys + match fetch_jwt_certificates(&jwt_info) { + Some(map) => jwt_info.public_keys = map, + None => { + return Err(MyCustomErrorType::JwtError); + } + } + + Ok(jwt_info) +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index e7b775f..06dbfd8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,228 +1,5 @@ -use jsonwebtoken::{decode, decode_header, Algorithm, DecodingKey, Validation, TokenData}; -use reqwest; -use serde::Deserialize; -use std::collections::HashMap; - -// Define a struct for the claims you expect in your token -#[derive(Debug, Deserialize)] -struct MyClaims { - sub: String, - exp: usize, - aud: String, - iss: String, - preferred_username: Option, -} - -#[derive(Deserialize)] -struct AuthorizationWellKnown { - issuer: String, - jwks_uri: String, -} - -#[derive(Deserialize)] -struct JwksContent { - kid: String, - x5c: Vec, -} - -#[derive(Deserialize)] -struct Jwks { - keys: Vec, -} - -struct JwtInfo { - jwks_uri: String, - audience: Vec, - issuer: Vec, - public_keys: HashMap, -} - -#[derive(Debug)] -enum MyCustomErrorType { - NetworkError, - JwtError, - JsonParseError, -} - -fn validate_jwt(token: &str, jwt_info: &mut JwtInfo) -> Result { - // Decode the header to give info about the crypto - let jwt_header = decode_header(token)?; - - // Create a new validation - let mut validation = Validation::new(jwt_header.alg); - // Set the expected audience and issuer - validation.set_audience(&jwt_info.audience); - validation.set_issuer(&jwt_info.issuer); - - // Extract the JWT kid - let kid: String; - match jwt_header.kid { - Some(fetched_kid) => kid = fetched_kid, - None => { - eprintln!("Unable to extract KID from jwt header"); - return Err(jsonwebtoken::errors::ErrorKind::InvalidToken.into()); - } - } - - // Fetch the corresponding public key - let public_key_pem: &String; - match jwt_info.public_keys.get(&kid) { - Some(key) => public_key_pem = key, - None => { - // If the key doesn't exist look up the keys again - match fetch_jwt_certificates(jwt_info) { - Some(key_map) => jwt_info.public_keys = key_map, - None => { - eprintln!("Failed to fetch jwt pem certificates"); - } - } - - // Try to get the keys once more - match jwt_info.public_keys.get(&kid) { - Some(key) => public_key_pem = key, - None => { - eprintln!("Failed to fetch find matching certificates for given KID. {}", kid); - return Err(jsonwebtoken::errors::ErrorKind::InvalidToken.into()); - } - } - } - } - - // Decode the JWT token - let token_data: TokenData; - match jwt_header.alg { - Algorithm::RS256 => { - token_data = decode::( - token, - &DecodingKey::from_rsa_pem(public_key_pem.as_bytes())?, - &validation, - )?; - }, - Algorithm::ES256 => { - token_data = decode::( - token, - &DecodingKey::from_ec_pem(public_key_pem.as_bytes())?, - &validation, - )?; - }, - _ => { - eprintln!("JWT Public key algoritm not handled"); - return Err(jsonwebtoken::errors::ErrorKind::InvalidAlgorithm.into()); - } - } - - Ok(token_data.claims) -} - -fn fetch_jwt_certificates(jwt_info: &JwtInfo) -> Option> { - // Fetch the JWKS endpoint - let jwks_body: String; - match reqwest::blocking::get(&jwt_info.jwks_uri) { - Ok(response) => { - match response.text() { - Ok(text) => jwks_body = text, - Err(e) => { - eprintln!("Failed to extract text from response body with error:\n{}", e); - return None; - } - } - }, - Err(e) => { - eprintln!("Failed to get the jwks_uri with error:\n{}", e); - return None; - } - } - - // Parse the data into the struct - let jwks_data: Jwks; - match serde_json::from_str(&jwks_body) { - Ok(jwks) => jwks_data = jwks, - Err(e) => { - eprintln!("Failed to parse fetched jwks body to Jwks struct with error:\n{}", e); - return None; - } - } - - // Create the output hashmap - let mut output_map: HashMap = HashMap::new(); - - // Go through each pair of keys and add them to the output jwt info - for key in jwks_data.keys { - // Extract the x5c key data - let x5c = key.x5c.get(0)?; - - // Add the PEM info in to the x5c - let pem_data = format!("-----BEGIN CERTIFICATE-----\n{}\n-----END CERTIFICATE-----", x5c); - - // Add the resulting key to the hashmap - output_map.insert(key.kid, pem_data); - } - - // Check that we got any keys - if output_map.is_empty() { - eprintln!("Failed to fetch any public keys"); - return None; - } - - Some(output_map) -} - -fn fetch_jwt_info(well_known_uri: &str, expected_issuer: Vec) -> Result { - // Fetch the info from the well known endpoint - let well_known_body; - match reqwest::blocking::get(well_known_uri) { - Ok(response) => { - match response.text() { - Ok(text) => well_known_body = text, - Err(e) => { - eprintln!("Failed to extract text from response body with error:\n{}", e); - return Err(MyCustomErrorType::NetworkError); - } - } - }, - Err(e) => { - eprintln!("Failed to get the well known with error:\n{}", e); - return Err(MyCustomErrorType::NetworkError); - } - } - - // Parse the data into the well known struct - let well_known_data: AuthorizationWellKnown; - match serde_json::from_str(&well_known_body) { - Ok(data) => well_known_data = data, - Err(e) => { - eprintln!("Failed to parse well known data into struct with err:\n{}", e); - return Err(MyCustomErrorType::JsonParseError); - } - } - - // Validate the issuer - if !expected_issuer.contains(&well_known_data.issuer) { - eprintln!( - "Expected issuer does not contain fetched issuer.\n{} ∉ {:?}", - well_known_data.issuer, expected_issuer - ); - return Err(MyCustomErrorType::JwtError); - } - - // Create a JwtInfo variable - let mut jwt_info: JwtInfo = JwtInfo { - jwks_uri: well_known_data.jwks_uri, - audience: Vec::new(), - issuer: expected_issuer, - public_keys: HashMap::new(), - }; - - // Fetch the valid public keys - match fetch_jwt_certificates(&jwt_info) { - Some(map) => jwt_info.public_keys = map, - None => { - return Err(MyCustomErrorType::JwtError); - } - } - - Ok(jwt_info) -} +mod jwt_validation; +use jwt_validation::*; fn main() { let token = "eyJhbGciOiJFUzI1NiIsImtpZCI6IjVkM2JkMDcxOGQ4ZWM3NWQ3ZDg1MjlmNDQwMzRiYTc1IiwidHlwIjoiSldUIn0.eyJpc3MiOiJodHRwczovL3Nzby5naXRnYWxzLmNvbS9hcHBsaWNhdGlvbi9vL3NlYnRlc3QvIiwic3ViIjoiZjJiNzIwOGY2MTcwYWI0NWNlZGM1OGUzMTM0NGNjNGY3MGQzZWRjMjhkYWZkMmJlNDZkNzIxMzM1ZDQxZDk2NCIsImF1ZCI6IkNMYUxyOHNpa0VpTjdOQ3JQTWhqaGJ0TFpnblpKNkpaVnpQZFZONVAiLCJleHAiOjE3MDM5OTE2NTUsImlhdCI6MTcwMzk3MzY1NSwiYXV0aF90aW1lIjoxNzAzODU3NzMwLCJhY3IiOiJnb2F1dGhlbnRpay5pby9wcm92aWRlcnMvb2F1dGgyL2RlZmF1bHQiLCJlbWFpbCI6IiIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJuYW1lIjoiSW5zb21uaWEiLCJnaXZlbl9uYW1lIjoiSW5zb21uaWEiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJpbnNvbW5pYS10ZXN0Iiwibmlja25hbWUiOiJpbnNvbW5pYS10ZXN0IiwiZ3JvdXBzIjpbXSwiYXpwIjoiQ0xhTHI4c2lrRWlON05DclBNaGpoYnRMWmduWko2SlpWelBkVk41UCIsInVpZCI6ImpjQVNabUttM1NFRW9ZeTNPVXpsWTZQcHpveXdZdm93bDRCbWtBUDUifQ.HM7W64in-cLdpgsNotegL9eTyjXfsr36uO5hsagQnUpc2X5vlCzeLSbRZrbduLR_W0k3vb1wZCOA1cWeSDOBfA";