Fetch and validate based on well known URI
This commit is contained in:
parent
08d7983f5b
commit
08de228a3e
45
.vscode/launch.json
vendored
Normal file
45
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "lldb",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Debug executable 'jwt-decode-test'",
|
||||||
|
"cargo": {
|
||||||
|
"args": [
|
||||||
|
"build",
|
||||||
|
"--bin=jwt-decode-test",
|
||||||
|
"--package=jwt-decode-test"
|
||||||
|
],
|
||||||
|
"filter": {
|
||||||
|
"name": "jwt-decode-test",
|
||||||
|
"kind": "bin"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"args": [],
|
||||||
|
"cwd": "${workspaceFolder}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "lldb",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Debug unit tests in executable 'jwt-decode-test'",
|
||||||
|
"cargo": {
|
||||||
|
"args": [
|
||||||
|
"test",
|
||||||
|
"--no-run",
|
||||||
|
"--bin=jwt-decode-test",
|
||||||
|
"--package=jwt-decode-test"
|
||||||
|
],
|
||||||
|
"filter": {
|
||||||
|
"name": "jwt-decode-test",
|
||||||
|
"kind": "bin"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"args": [],
|
||||||
|
"cwd": "${workspaceFolder}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
863
Cargo.lock
generated
863
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -8,5 +8,6 @@ edition = "2021"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
jsonwebtoken = "9.2.0"
|
jsonwebtoken = "9.2.0"
|
||||||
pem = "3.0.3"
|
pem = "3.0.3"
|
||||||
|
reqwest = {version="0.11.23", features = ["blocking"]}
|
||||||
serde = {version = "1.0", features = ["derive"] }
|
serde = {version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0.108"
|
serde_json = "1.0.108"
|
||||||
|
|||||||
167
src/main.rs
167
src/main.rs
@ -1,5 +1,7 @@
|
|||||||
use jsonwebtoken::{decode, DecodingKey, Validation, Algorithm, errors::Result};
|
use jsonwebtoken::{decode, decode_header, errors::Result, Algorithm, DecodingKey, Validation, TokenData};
|
||||||
use serde::{Deserialize};
|
use reqwest;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
// Define a struct for the claims you expect in your token
|
// Define a struct for the claims you expect in your token
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
@ -8,37 +10,164 @@ struct MyClaims {
|
|||||||
exp: usize,
|
exp: usize,
|
||||||
aud: String,
|
aud: String,
|
||||||
iss: String,
|
iss: String,
|
||||||
preferred_username: Option<String>
|
preferred_username: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct AuthorizationWellKnown {
|
||||||
|
issuer: String,
|
||||||
|
jwks_uri: String,
|
||||||
|
authorization_endpoint: String,
|
||||||
|
token_endpoint: String,
|
||||||
|
userinfo_endpoint: String,
|
||||||
|
end_session_endpoint: String,
|
||||||
|
introspection_endpoint: String,
|
||||||
|
revocation_endpoint: String,
|
||||||
|
device_authorization_endpoint: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct JwksContent {
|
||||||
|
kid: String,
|
||||||
|
x5c: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
fn validate_jwt(token: &str, public_key_pem: String) -> Result<MyClaims> {
|
#[derive(Deserialize)]
|
||||||
let mut validation = Validation::new(Algorithm::RS256);
|
struct Jwks {
|
||||||
validation.set_audience(&["CLaLr8sikEiN7NCrPMhjhbtLZgnZJ6JZVzPdVN5P"]);
|
keys: Vec<JwksContent>,
|
||||||
validation.set_issuer(&["https://sso.gitgals.com/application/o/sebtest/"]);
|
}
|
||||||
|
|
||||||
let token_data = decode::<MyClaims>(
|
struct JwtInfo {
|
||||||
token,
|
jwks_uri: String,
|
||||||
&DecodingKey::from_rsa_pem(public_key_pem.as_bytes()).unwrap(),
|
audience: Vec<String>,
|
||||||
&validation,
|
issuer: Vec<String>,
|
||||||
)?;
|
public_keys: HashMap<String, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_jwt(token: &str, jwt_info: &JwtInfo) -> Result<MyClaims> {
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// Fetch the JWT kid
|
||||||
|
let kid = jwt_header.kid.unwrap();
|
||||||
|
// Fetch the corresponding public key
|
||||||
|
let public_key_pem = jwt_info.public_keys.get(&kid).unwrap();
|
||||||
|
|
||||||
|
// 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()).unwrap(),
|
||||||
|
&validation,
|
||||||
|
)?;
|
||||||
|
},
|
||||||
|
Algorithm::ES256 => {
|
||||||
|
token_data = decode::<MyClaims>(
|
||||||
|
token,
|
||||||
|
&DecodingKey::from_ec_pem(public_key_pem.as_bytes()).unwrap(),
|
||||||
|
&validation,
|
||||||
|
)?;
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
eprintln!("JWT Public key algoritm not handled");
|
||||||
|
return Err(jsonwebtoken::errors::ErrorKind::InvalidAlgorithm.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(token_data.claims)
|
Ok(token_data.claims)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn fetch_jwt_certificates(jwt_info: &JwtInfo) -> Option<HashMap<String, String>> {
|
||||||
|
// Fetch the JWKS endpoint
|
||||||
|
let jwks_body = reqwest::blocking::get(&jwt_info.jwks_uri)
|
||||||
|
.unwrap()
|
||||||
|
.text()
|
||||||
|
.unwrap();
|
||||||
|
// Parse the data into the struct
|
||||||
|
let jwks_data: Jwks = serde_json::from_str(&jwks_body).unwrap();
|
||||||
|
|
||||||
|
// 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).unwrap();
|
||||||
|
|
||||||
|
// Append 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<String>) -> Result<JwtInfo> {
|
||||||
|
// Fetch the info from the well known endpoint
|
||||||
|
let well_known_body = reqwest::blocking::get(well_known_uri)
|
||||||
|
.unwrap()
|
||||||
|
.text()
|
||||||
|
.unwrap();
|
||||||
|
// Parse the data into the well known struct
|
||||||
|
let well_known_data: AuthorizationWellKnown = serde_json::from_str(&well_known_body).unwrap();
|
||||||
|
|
||||||
|
// 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
|
||||||
|
);
|
||||||
|
// TODO: Return Err properly
|
||||||
|
//Err("Invalid issuer");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 => {
|
||||||
|
// TODO: Return err properly
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(jwt_info)
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let token = "eyJhbGciOiJSUzI1NiIsImtpZCI6IjExOTMxYjliMjVhZjJmNjYyZjQ4NjNkYjAwZTJhMjg5IiwidHlwIjoiSldUIn0.eyJpc3MiOiJodHRwczovL3Nzby5naXRnYWxzLmNvbS9hcHBsaWNhdGlvbi9vL3NlYnRlc3QvIiwic3ViIjoiZjJiNzIwOGY2MTcwYWI0NWNlZGM1OGUzMTM0NGNjNGY3MGQzZWRjMjhkYWZkMmJlNDZkNzIxMzM1ZDQxZDk2NCIsImF1ZCI6IkNMYUxyOHNpa0VpTjdOQ3JQTWhqaGJ0TFpnblpKNkpaVnpQZFZONVAiLCJleHAiOjE3MDM5NTgxMTgsImlhdCI6MTcwMzk0MDExOCwiYXV0aF90aW1lIjoxNzAzODU3NzMwLCJhY3IiOiJnb2F1dGhlbnRpay5pby9wcm92aWRlcnMvb2F1dGgyL2RlZmF1bHQiLCJlbWFpbCI6IiIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJuYW1lIjoiSW5zb21uaWEiLCJnaXZlbl9uYW1lIjoiSW5zb21uaWEiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJpbnNvbW5pYS10ZXN0Iiwibmlja25hbWUiOiJpbnNvbW5pYS10ZXN0IiwiZ3JvdXBzIjpbXSwiYXpwIjoiQ0xhTHI4c2lrRWlON05DclBNaGpoYnRMWmduWko2SlpWelBkVk41UCIsInVpZCI6IjFpRnoySmVvV0IyMFlBOWpzcEZSdDJLTUVsQXJhZ0NtRnJwVUJhVE0ifQ.QWW2A4NGE3SGVLZsAYW0mVpopud3Pd-fua2F__0g7BHO7B6IDCSQGM786cOgk4E07Eq2dP8m2xM4O2Ok18ayyek2gYbg4uCHzZv3F1097njPspRfrJZH-MpExobV_6oBtAeAuQKopaSfGIPQE4cWJh32TKZFGHRPEMBz8W7GTPqDZcnzbu8zGHzb7tXhEUybjQFRKWU8jRIu8AiVDC8jyyqzTGJClVVd_gNUHY1vDB86lcJQHhJw2sEY9LjMv3O_ok9nJ-abzb2bJK133C1zWw9whrYOKeYCBNDC37I03m9eMnTi1YPPzi1woak_qC2JFcmCrcCbalrrruHdDwyWtyabH9s_YueZAEGyKZvLSLadx05xEAQ-aUHkKqr6hIYzPBw9vCmfhJ-UouRjFFP9FCxkNKTZ00_QUY-LQoYizTd0jENmjLZCRf8xo4iVmL5RtVwtJNoL8g_Mdpyn9JfKnPrhRQE3SqMVYQbF43XNInmVCP-acrezvOG5nwHZ4lWNgsjhYguhRM4eTx-B7IYx6x2Kqpbnj6Qte19FHfTS7cClGZ0eRpV55mUohSxdvEYGWsB-F_rprV4vxdcmw7kEpk8wtt6Nb4xX-NmTHiFUkKw2sio3wg-u5EziHhTqHZaldSTefnweoBJ1PB439Xww5a-x6xN6mE8DkEcc3xfAuXM";
|
let token = "eyJhbGciOiJFUzI1NiIsImtpZCI6IjVkM2JkMDcxOGQ4ZWM3NWQ3ZDg1MjlmNDQwMzRiYTc1IiwidHlwIjoiSldUIn0.eyJpc3MiOiJodHRwczovL3Nzby5naXRnYWxzLmNvbS9hcHBsaWNhdGlvbi9vL3NlYnRlc3QvIiwic3ViIjoiZjJiNzIwOGY2MTcwYWI0NWNlZGM1OGUzMTM0NGNjNGY3MGQzZWRjMjhkYWZkMmJlNDZkNzIxMzM1ZDQxZDk2NCIsImF1ZCI6IkNMYUxyOHNpa0VpTjdOQ3JQTWhqaGJ0TFpnblpKNkpaVnpQZFZONVAiLCJleHAiOjE3MDM5NjUxNjIsImlhdCI6MTcwMzk0NzE2MiwiYXV0aF90aW1lIjoxNzAzODU3NzMwLCJhY3IiOiJnb2F1dGhlbnRpay5pby9wcm92aWRlcnMvb2F1dGgyL2RlZmF1bHQiLCJlbWFpbCI6IiIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJuYW1lIjoiSW5zb21uaWEiLCJnaXZlbl9uYW1lIjoiSW5zb21uaWEiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJpbnNvbW5pYS10ZXN0Iiwibmlja25hbWUiOiJpbnNvbW5pYS10ZXN0IiwiZ3JvdXBzIjpbXSwiYXpwIjoiQ0xhTHI4c2lrRWlON05DclBNaGpoYnRMWmduWko2SlpWelBkVk41UCIsInVpZCI6IkdPQTNRdTBIOW5TUUx4WHJZVXJ4RHUzTTVkVDhmNTZIQ0l5QlhZdnYifQ.AuVjuJXApMq1vPS48gK5htGSv8KzcCQZlerc82adiCNVb789w2lBoiLjbKotHvAPQOLTQ3qWv2yHNPgBE3dhVA";
|
||||||
let well_known_uri = "https://sso.gitgals.com/application/o/sebtest/.well-known/openid-configuration";
|
let well_known_uri = "https://sso.gitgals.com/application/o/sebtest/.well-known/openid-configuration";
|
||||||
|
|
||||||
// Extract the x5c field from the JWKS
|
let mut jwt_info = fetch_jwt_info(well_known_uri, vec!("https://sso.gitgals.com/application/o/sebtest/".into())).unwrap();
|
||||||
let x5c = "MIIFVDCCAzygAwIBAgIRAN2g0n7Cqki0kP4aDM1ON50wDQYJKoZIhvcNAQELBQAwHjEcMBoGA1UEAwwTYXV0aGVudGlrIDIwMjMuMTAuNDAeFw0yMzEyMjcxNDEwNDlaFw0yNDEyMjcxNDEwNDlaMFYxKjAoBgNVBAMMIWF1dGhlbnRpayBTZWxmLXNpZ25lZCBDZXJ0aWZpY2F0ZTESMBAGA1UECgwJYXV0aGVudGlrMRQwEgYDVQQLDAtTZWxmLXNpZ25lZDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAM2BP7UtRaTlM/KvE7E2S5GPdV88f/IUqxANoWS+JnWFSpRUXkkCorEUWo7LTrhNaaQni3FIdo2m2ouF9/+NlHj2038l1pNj/FPiZe8EyRbriQe/maKejq02CNW2rmD4cjWMK4h228o8QqeNSFxG11DEku4mN6Kz6X8Mi9iQWwTx0h8cl1Rxo/S6iBK9DZA1005DSkMskXGsN172O1cHZaG55WhmL6lKOcvvKyGwbccrrl9AufBz1SCoFZ/ERN/D5yRwehwjChP1mm3G4jzhiK/8LkmmnnfzNB01WoxZFzL9a+BXjEWWFyfRm+1rwEhrZ9DFqmPKA43XuPeRVijr3pZGUxH45WqSoXDv0luLLi0dTOUvgDETtj5ugVd+0Qi2cZX0W5Sh8ecEK6FwICRVYoRcd5fv4MfUwMlqT9S4+16B0TrEWkc6brfg9V3xGgNFRG3axCd68LAzP7ezPVGheubdNNnA/4HplxsFH8pjoyRiORSiK77IlLQ/MhngpJ9ExW/8xRkbl9o5bSAYTqtPPIOsSciMAlsipSLx+eUE4/VDcfhuaGf7Z2FSHfHvETwFhr+CDfaDHdgNbk2+ZXWWIqFbLxevrbHwj9YMtIuxagGrNTaAx1ZEizf9tZco5JDDsywFGvcL7oaHo4zx579gGGPgoWvHvzLejjg4hJL74vIzAgMBAAGjVTBTMFEGA1UdEQEB/wRHMEWCQ1JNa3diMFFMeDI5eXlManRvT0Zod1NPU1dEQVJhR3NjWmNvMk96NnMuc2VsZi1zaWduZWQuZ29hdXRoZW50aWsuaW8wDQYJKoZIhvcNAQELBQADggIBAKKSS0rA4gjn/g+LvSSoSr8H8PErLYQyHHTP4VfRXvk/o0drM2YJTrH53biHxXL92Ej9SRXKrrGcQlkVrucB79w6WHQUUmIVmm/CvYFbEKk7j2IbfR9CzKMMGzY7jrtf9YFyPPVQK5tIwA8uCaqXBu0zz5KSHTrXZLXXVV/xoikBTf1s5GN9MrnDK442xzf5cRBzQjpsa750MM2o5vEfRf2n9+HOuesAC56EaMiVQFGKELsx9Gmrjgley1X72H/5qgkLZb1o5+m4+9BAM8xSaowxC/DmH5ROryB8ctx7BGVZX4IEwHE9Q1G/6bfIeLr6bU/P+AgSzJv5Qan3e1jzmqB/2rmVxECfLEy5Pxmsa1FZNjleFK+8jj4qSKF+jiEMnmtv6V18EvIj5m4YcOjJpPjfq2saw0u9mP45wFERuEGpb7tWBEVLj+HirrmounCvaMr+m++oxaWaT3ECCwN8Sw5EtfXRZovzbW52Qj3HK0f8ip+EzurnWO+EJt6xBXgR0eRzNKSxPpGpZLuF8KUGLwOubMsDIO/+jRy3se/9jG9b9dz2rjgIxSYgnvSl55vqw0WMp6qoCqnXBB8+50Sn4Znc+/nApExFAgY7T5cE/q17CaryfmF2nkqANkYeCcduZ1qtSPnEsyxTaL9aerxyi9GMpfvriPdhNlwc1cos9CV3";
|
jwt_info.audience = vec!("CLaLr8sikEiN7NCrPMhjhbtLZgnZJ6JZVzPdVN5P".to_string());
|
||||||
|
|
||||||
// Convert to PEM format
|
|
||||||
let public_key_pem = format!("-----BEGIN CERTIFICATE-----\n{}\n-----END CERTIFICATE-----", x5c);
|
|
||||||
|
|
||||||
let result: MyClaims;
|
let result: MyClaims;
|
||||||
match validate_jwt(token, public_key_pem) {
|
match validate_jwt(token, &jwt_info) {
|
||||||
Ok(claims) => result = claims,
|
Ok(claims) => result = claims,
|
||||||
Err(err) => panic!("Error validating token: {:?}", err),
|
Err(err) => panic!("Error validating token: {:?}", err),
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user