Add jwt_validation.rs from JWT validation test repo
This commit is contained in:
parent
c61692cc32
commit
40714e4215
193
Cargo.lock
generated
193
Cargo.lock
generated
@ -763,8 +763,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"wasi",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -932,6 +934,19 @@ dependencies = [
|
||||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-tls"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"hyper",
|
||||
"native-tls",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.58"
|
||||
@ -993,6 +1008,12 @@ version = "0.1.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb"
|
||||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
|
||||
|
||||
[[package]]
|
||||
name = "is-terminal"
|
||||
version = "0.4.9"
|
||||
@ -1028,6 +1049,21 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jsonwebtoken"
|
||||
version = "9.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c7ea04a7c5c055c175f189b6dc6ba036fd62306b58c66c9f6389036c503a3f4"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"js-sys",
|
||||
"pem",
|
||||
"ring",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"simple_asn1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
@ -1442,6 +1478,16 @@ dependencies = [
|
||||
"syn 2.0.42",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pem"
|
||||
version = "3.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b8fcc794035347fb64beda2d3b462595dd2753e3f268d89c5aae77e8cf2c310"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pem-rfc7468"
|
||||
version = "0.7.0"
|
||||
@ -1710,6 +1756,58 @@ dependencies = [
|
||||
"bytecheck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.11.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"bytes",
|
||||
"encoding_rs",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"hyper-tls",
|
||||
"ipnet",
|
||||
"js-sys",
|
||||
"log",
|
||||
"mime",
|
||||
"native-tls",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"system-configuration",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tower-service",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"winreg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"getrandom",
|
||||
"libc",
|
||||
"spin 0.9.8",
|
||||
"untrusted",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rkyv"
|
||||
version = "0.7.43"
|
||||
@ -1782,6 +1880,8 @@ name = "rocket-test"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"jsonwebtoken",
|
||||
"reqwest",
|
||||
"rocket",
|
||||
"rocket_cors",
|
||||
"sea-orm",
|
||||
@ -2094,6 +2194,18 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_urlencoded"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1"
|
||||
version = "0.10.6"
|
||||
@ -2150,6 +2262,18 @@ version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a"
|
||||
|
||||
[[package]]
|
||||
name = "simple_asn1"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"num-traits",
|
||||
"thiserror",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.9"
|
||||
@ -2509,6 +2633,27 @@ dependencies = [
|
||||
"syn 2.0.42",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-configuration"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"core-foundation",
|
||||
"system-configuration-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-configuration-sys"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tap"
|
||||
version = "1.0.1"
|
||||
@ -2631,6 +2776,16 @@ dependencies = [
|
||||
"syn 2.0.42",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-native-tls"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
|
||||
dependencies = [
|
||||
"native-tls",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-stream"
|
||||
version = "0.1.14"
|
||||
@ -2858,6 +3013,12 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.5.0"
|
||||
@ -2942,6 +3103,18 @@ dependencies = [
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.4.39"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.89"
|
||||
@ -2971,6 +3144,16 @@ version = "0.2.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f"
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.66"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "whoami"
|
||||
version = "1.4.1"
|
||||
@ -3158,6 +3341,16 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.50.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wyz"
|
||||
version = "0.5.1"
|
||||
|
||||
@ -11,3 +11,5 @@ serde = { version = "1.0", features = ["derive"] }
|
||||
sea-orm = { version = "^0.12.0", features = [ "sqlx-sqlite", "runtime-tokio-native-tls", "macros", "mock" ] }
|
||||
futures = "0.3.28"
|
||||
rocket_cors = "0.6.0"
|
||||
jsonwebtoken = "9.2.0"
|
||||
reqwest = "0.11.23"
|
||||
|
||||
1
src/authentication.rs
Normal file
1
src/authentication.rs
Normal file
@ -0,0 +1 @@
|
||||
//use jwt_validation::*;
|
||||
225
src/jwt_validation.rs
Normal file
225
src/jwt_validation.rs
Normal file
@ -0,0 +1,225 @@
|
||||
use jsonwebtoken::{decode, decode_header, Algorithm, DecodingKey, Validation, TokenData};
|
||||
use reqwest;
|
||||
use rocket::serde::{Deserialize, json::serde_json};
|
||||
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<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct AuthorizationWellKnown {
|
||||
issuer: String,
|
||||
jwks_uri: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct JwksContent {
|
||||
kid: String,
|
||||
x5c: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Jwks {
|
||||
keys: Vec<JwksContent>,
|
||||
}
|
||||
|
||||
pub struct JwtInfo {
|
||||
pub jwks_uri: String,
|
||||
pub audience: Vec<String>,
|
||||
pub issuer: Vec<String>,
|
||||
pub public_keys: HashMap<String, String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum MyCustomErrorType {
|
||||
NetworkError,
|
||||
JwtError,
|
||||
JsonParseError,
|
||||
}
|
||||
|
||||
pub async fn validate_jwt(token: &str, jwt_info: &mut JwtInfo) -> Result<MyClaims, jsonwebtoken::errors::Error> {
|
||||
// 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).await {
|
||||
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<MyClaims>;
|
||||
match jwt_header.alg {
|
||||
Algorithm::RS256 => {
|
||||
token_data = decode::<MyClaims>(
|
||||
token,
|
||||
&DecodingKey::from_rsa_pem(public_key_pem.as_bytes())?,
|
||||
&validation,
|
||||
)?;
|
||||
},
|
||||
Algorithm::ES256 => {
|
||||
token_data = decode::<MyClaims>(
|
||||
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)
|
||||
}
|
||||
|
||||
async fn fetch_jwt_certificates(jwt_info: &JwtInfo) -> Option<HashMap<String, String>> {
|
||||
// Fetch the JWKS endpoint
|
||||
let jwks_body: String;
|
||||
match reqwest::get(&jwt_info.jwks_uri).await {
|
||||
Ok(response) => {
|
||||
match response.text().await {
|
||||
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<String, String> = 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 async fn fetch_jwt_info(well_known_uri: &str, expected_issuer: Vec<String>) -> Result<JwtInfo, MyCustomErrorType> {
|
||||
// Fetch the info from the well known endpoint
|
||||
let well_known_body;
|
||||
match reqwest::get(well_known_uri).await {
|
||||
Ok(response) => {
|
||||
match response.text().await {
|
||||
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).await {
|
||||
Some(map) => jwt_info.public_keys = map,
|
||||
None => {
|
||||
return Err(MyCustomErrorType::JwtError);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(jwt_info)
|
||||
}
|
||||
@ -15,6 +15,10 @@ use webserver_member::*;
|
||||
// Handle CORS
|
||||
use rocket_cors::{AllowedOrigins, CorsOptions};
|
||||
|
||||
// Handle authentication
|
||||
mod jwt_validation;
|
||||
use jwt_validation::*;
|
||||
|
||||
// Serve the very exiting main page
|
||||
#[get("/")]
|
||||
fn index() -> &'static str {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user