style(rust): Format all Rust code with rustfmt
Change-Id: Iab7e00cc26a4f9727d3ab98691ef379921a33052 Reviewed-on: https://cl.tvl.fyi/c/depot/+/5240 Tested-by: BuildkiteCI Reviewed-by: kanepyork <rikingcoding@gmail.com> Reviewed-by: Profpatsch <mail@profpatsch.de> Reviewed-by: grfn <grfn@gws.fyi> Reviewed-by: tazjin <tazjin@tvl.su>
This commit is contained in:
parent
3318982f81
commit
3d8ee62087
42 changed files with 1253 additions and 876 deletions
|
|
@ -67,23 +67,24 @@
|
|||
//!
|
||||
//! [JWKS]: https://tools.ietf.org/html/rfc7517
|
||||
|
||||
#[macro_use] extern crate serde_derive;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
extern crate base64;
|
||||
extern crate openssl;
|
||||
extern crate serde;
|
||||
extern crate serde_json;
|
||||
|
||||
use base64::{URL_SAFE_NO_PAD, Config, DecodeError};
|
||||
use base64::{Config, DecodeError, URL_SAFE_NO_PAD};
|
||||
use openssl::bn::BigNum;
|
||||
use openssl::error::ErrorStack;
|
||||
use openssl::hash::MessageDigest;
|
||||
use openssl::pkey::{Public, PKey};
|
||||
use openssl::pkey::{PKey, Public};
|
||||
use openssl::rsa::Rsa;
|
||||
use openssl::sign::Verifier;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde_json::Value;
|
||||
use std::time::{UNIX_EPOCH, Duration, SystemTime};
|
||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
|
@ -101,12 +102,16 @@ fn jwt_forgiving() -> Config {
|
|||
/// JWT algorithm used. The only supported algorithm is currently
|
||||
/// RS256.
|
||||
#[derive(Clone, Deserialize, Debug)]
|
||||
enum KeyAlgorithm { RS256 }
|
||||
enum KeyAlgorithm {
|
||||
RS256,
|
||||
}
|
||||
|
||||
/// Type of key contained in a JWT. The only supported key type is
|
||||
/// currently RSA.
|
||||
#[derive(Clone, Deserialize, Debug)]
|
||||
enum KeyType { RSA }
|
||||
enum KeyType {
|
||||
RSA,
|
||||
}
|
||||
|
||||
/// Representation of a single JSON Web Key. See [RFC
|
||||
/// 7517](https://tools.ietf.org/html/rfc7517#section-4).
|
||||
|
|
@ -146,7 +151,7 @@ impl JWKS {
|
|||
|
||||
/// Representation of an undecoded JSON Web Token. See [RFC
|
||||
/// 7519](https://tools.ietf.org/html/rfc7519).
|
||||
struct JWT<'a> (&'a str);
|
||||
struct JWT<'a>(&'a str);
|
||||
|
||||
/// Representation of a decoded and validated JSON Web Token.
|
||||
///
|
||||
|
|
@ -217,15 +222,21 @@ pub enum ValidationError {
|
|||
type JWTResult<T> = Result<T, ValidationError>;
|
||||
|
||||
impl From<ErrorStack> for ValidationError {
|
||||
fn from(err: ErrorStack) -> Self { ValidationError::OpenSSL(err) }
|
||||
fn from(err: ErrorStack) -> Self {
|
||||
ValidationError::OpenSSL(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<serde_json::Error> for ValidationError {
|
||||
fn from(err: serde_json::Error) -> Self { ValidationError::JSON(err) }
|
||||
fn from(err: serde_json::Error) -> Self {
|
||||
ValidationError::JSON(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DecodeError> for ValidationError {
|
||||
fn from(err: DecodeError) -> Self { ValidationError::InvalidBase64(err) }
|
||||
fn from(err: DecodeError) -> Self {
|
||||
ValidationError::InvalidBase64(err)
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to extract the `kid`-claim out of a JWT's header claims.
|
||||
|
|
@ -266,9 +277,7 @@ pub fn token_kid(token: &str) -> JWTResult<Option<String>> {
|
|||
///
|
||||
/// It is the user's task to ensure that the correct JWK is passed in
|
||||
/// for validation.
|
||||
pub fn validate(token: &str,
|
||||
jwk: &JWK,
|
||||
validations: Vec<Validation>) -> JWTResult<ValidJWT> {
|
||||
pub fn validate(token: &str, jwk: &JWK, validations: Vec<Validation>) -> JWTResult<ValidJWT> {
|
||||
let jwt = JWT(token);
|
||||
let public_key = public_key_from_jwk(&jwk)?;
|
||||
validate_jwt_signature(&jwt, public_key)?;
|
||||
|
|
@ -279,7 +288,7 @@ pub fn validate(token: &str,
|
|||
if parts.len() != 3 {
|
||||
// This is unlikely considering that validation has already
|
||||
// been performed at this point, but better safe than sorry.
|
||||
return Err(ValidationError::InvalidComponents)
|
||||
return Err(ValidationError::InvalidComponents);
|
||||
}
|
||||
|
||||
// Perform claim validations before constructing the valid token:
|
||||
|
|
@ -351,7 +360,7 @@ fn validate_jwt_signature(jwt: &JWT, key: Rsa<Public>) -> JWTResult<()> {
|
|||
verifier.update(data.as_bytes())?;
|
||||
|
||||
match verifier.verify(&sig)? {
|
||||
true => Ok(()),
|
||||
true => Ok(()),
|
||||
false => Err(ValidationError::InvalidSignature),
|
||||
}
|
||||
}
|
||||
|
|
@ -362,7 +371,7 @@ fn validate_jwt_signature(jwt: &JWT, key: Rsa<Public>) -> JWTResult<()> {
|
|||
#[serde(untagged)]
|
||||
enum Audience {
|
||||
Single(String),
|
||||
Multi(Vec<String>)
|
||||
Multi(Vec<String>),
|
||||
}
|
||||
|
||||
/// Internal helper struct for claims that are relevant for claim
|
||||
|
|
@ -376,15 +385,14 @@ struct PartialClaims {
|
|||
}
|
||||
|
||||
/// Apply a single validation to the claim set of a token.
|
||||
fn apply_validation(claims: &PartialClaims,
|
||||
validation: Validation) -> Result<(), &'static str> {
|
||||
fn apply_validation(claims: &PartialClaims, validation: Validation) -> Result<(), &'static str> {
|
||||
match validation {
|
||||
// Validate that an 'iss' claim is present and matches the
|
||||
// supplied value.
|
||||
Validation::Issuer(iss) => {
|
||||
match claims.iss {
|
||||
None => Err("'iss' claim is missing"),
|
||||
Some(ref claim) => if *claim == iss {
|
||||
Validation::Issuer(iss) => match claims.iss {
|
||||
None => Err("'iss' claim is missing"),
|
||||
Some(ref claim) => {
|
||||
if *claim == iss {
|
||||
Ok(())
|
||||
} else {
|
||||
Err("'iss' claim does not match")
|
||||
|
|
@ -394,15 +402,17 @@ fn apply_validation(claims: &PartialClaims,
|
|||
|
||||
// Validate that an 'aud' claim is present and matches the
|
||||
// supplied value.
|
||||
Validation::Audience(aud) => {
|
||||
match claims.aud {
|
||||
None => Err("'aud' claim is missing"),
|
||||
Some(Audience::Single(ref claim)) => if *claim == aud {
|
||||
Validation::Audience(aud) => match claims.aud {
|
||||
None => Err("'aud' claim is missing"),
|
||||
Some(Audience::Single(ref claim)) => {
|
||||
if *claim == aud {
|
||||
Ok(())
|
||||
} else {
|
||||
Err("'aud' claim does not match")
|
||||
},
|
||||
Some(Audience::Multi(ref claims)) => if claims.contains(&aud) {
|
||||
}
|
||||
}
|
||||
Some(Audience::Multi(ref claims)) => {
|
||||
if claims.contains(&aud) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err("'aud' claim does not match")
|
||||
|
|
@ -447,12 +457,12 @@ fn apply_validation(claims: &PartialClaims,
|
|||
}
|
||||
|
||||
/// Apply all requested validations to a partial claim set.
|
||||
fn validate_claims(claims: PartialClaims,
|
||||
validations: Vec<Validation>) -> JWTResult<()> {
|
||||
let validation_errors: Vec<_> = validations.into_iter()
|
||||
fn validate_claims(claims: PartialClaims, validations: Vec<Validation>) -> JWTResult<()> {
|
||||
let validation_errors: Vec<_> = validations
|
||||
.into_iter()
|
||||
.map(|v| apply_validation(&claims, v))
|
||||
.filter_map(|result| match result {
|
||||
Ok(_) => None,
|
||||
Ok(_) => None,
|
||||
Err(err) => Some(err),
|
||||
})
|
||||
.collect();
|
||||
|
|
|
|||
|
|
@ -21,14 +21,19 @@ fn test_fragment_decoding() {
|
|||
let bignum = decode_fragment(fragment).expect("Failed to decode fragment");
|
||||
|
||||
let expected = "19947781743618558124649689124245117083485690334420160711273532766920651190711502679542723943527557680293732686428091794139998732541701457212387600480039297092835433997837314251024513773285252960725418984381935183495143908023024822433135775773958512751261112853383693442999603704969543668619221464654540065497665889289271044207667765128672709218996183649696030570183970367596949687544839066873508106034650634722970893169823917299050098551447676778961773465887890052852528696684907153295689693676910831376066659456592813140662563597179711588277621736656871685099184755908108451080261403193680966083938080206832839445289";
|
||||
assert_eq!(expected, format!("{}", bignum), "Decoded fragment should match ");
|
||||
assert_eq!(
|
||||
expected,
|
||||
format!("{}", bignum),
|
||||
"Decoded fragment should match "
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_find_jwks() {
|
||||
let json = "{\"keys\":[{\"kty\":\"RSA\",\"alg\":\"RS256\",\"use\":\"sig\",\"kid\":\"mUjI\\/rIMLLtung35BKZfdbrqtlEAAYJ4JX\\/SKvnLxJc=\",\"n\":\"ngRRjNbXgPW29oNtF0JgsyyfTwPyEL0u_X16s453X2AOc33XGFxVKLEQ7R_TiMenaKcr-tPifYqgps_deyi0XOr4I3SOdOMtAVKDZJCANe--CANOHZb-meIfjKhCHisvT90fm5Apd6qPRVsXsZ7A8pmClZHKM5fwZUkBv8NsPLm2Xy2sGOZIiwP_7z8m3j0abUzniPQsx2b3xcWimB9vRtshFHN1KgPUf1ALQ5xzLfJnlFkCxC7kmOxKC7_NpQ4kJR_DKzKFV_r3HxTqf-jddHcXIrrMcLQXCSyeLQtLaz7whQ4F-EfL42z4XgwPr4ji3sct2gWL13EqlbE5DDxLKQ\",\"e\":\"GK7oLCDbNPAF59LhvyseqcG04hDnPs58qGYolr_HHmaR4lulWJ90ozx6e4Ut363yKG2p9vwvivR5UIC-aLPtqT2qr-OtjhBFzUFVaMGZ6mPCvMKk0AgMYdOHvWTgBSqQtNJTvl1yYLnhcWyoE2fLQhoEbY9qUyCBCEOScXOZRDpnmBtz5I8q5yYMV6a920J24T_IYbxHgkGcEU2SGg-b1cOMD7Rja7vCfV---CQ2pR4leQ0jufzudDoe7z3mziJm-Ihcdrz2Ujy5kPEMdz6R55prJ-ENKrkD_X4u5aSlSRaetwmHS3oAVkjr1JwUNbqnpM-kOqieqHEp8LUmez-Znw\"}]}";
|
||||
let jwks: JWKS = serde_json::from_str(json).expect("Failed to decode JWKS");
|
||||
let jwk = jwks.find("mUjI/rIMLLtung35BKZfdbrqtlEAAYJ4JX/SKvnLxJc=")
|
||||
let jwk = jwks
|
||||
.find("mUjI/rIMLLtung35BKZfdbrqtlEAAYJ4JX/SKvnLxJc=")
|
||||
.expect("Failed to find required JWK");
|
||||
|
||||
public_key_from_jwk(&jwk).expect("Failed to construct public key from JWK");
|
||||
|
|
@ -39,18 +44,21 @@ fn test_token_kid() {
|
|||
let jwt = "eyJraWQiOiI4ckRxOFB3MEZaY2FvWFdURVZRbzcrVGYyWXpTTDFmQnhOS1BDZWJhYWk0PSIsImFsZyI6IlJTMjU2IiwidHlwIjoiSldUIn0.eyJpc3MiOiJhdXRoLnRlc3QuYXByaWxhLm5vIiwiaWF0IjoxNTM2MDUwNjkzLCJleHAiOjE1MzYwNTQyOTMsInN1YiI6IjQyIiwiZXh0Ijoic21va2V0ZXN0IiwicHJ2IjoiYXJpc3RpIiwic2NwIjoicHJvY2VzcyJ9.gOLsv98109qLkmRK6Dn7WWRHLW7o8W78WZcWvFZoxPLzVO0qvRXXRLYc9h5chpfvcWreLZ4f1cOdvxv31_qnCRSQQPOeQ7r7hj_sPEDzhKjk-q2aoNHaGGJg1vabI--9EFkFsGQfoS7UbMMssS44dgR68XEnKtjn0Vys-Vzbvz_CBSCH6yQhRLik2SU2jR2L7BoFvh4LGZ6EKoQWzm8Z-CHXLGLUs4Hp5aPhF46dGzgAzwlPFW4t9G4DciX1uB4vv1XnfTc5wqJch6ltjKMde1GZwLR757a8dJSBcmGWze3UNE2YH_VLD7NCwH2kkqr3gh8rn7lWKG4AUIYPxsw9CB";
|
||||
|
||||
let kid = token_kid(&jwt).expect("Failed to extract token KID");
|
||||
assert_eq!(Some("8rDq8Pw0FZcaoXWTEVQo7+Tf2YzSL1fBxNKPCebaai4=".into()),
|
||||
kid, "Extracted KID did not match expected KID");
|
||||
assert_eq!(
|
||||
Some("8rDq8Pw0FZcaoXWTEVQo7+Tf2YzSL1fBxNKPCebaai4=".into()),
|
||||
kid,
|
||||
"Extracted KID did not match expected KID"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_jwt() {
|
||||
let jwks_json = "{\"keys\":[{\"kty\":\"RSA\",\"alg\":\"RS256\",\"use\":\"sig\",\"kid\":\"8rDq8Pw0FZcaoXWTEVQo7+Tf2YzSL1fBxNKPCebaai4=\",\"n\":\"l4UTgk1zr-8C8utt0E57DtBV6qqAPWzVRrIuQS2j0_hp2CviaNl5XzGRDnB8gwk0Hx95YOhJupAe6RNq5ok3fDdxL7DLvppJNRLz3Ag9CsmDLcbXgNEQys33fBJaPw1v3GcaFC4tisU5p-o1f5RfWwvwdBtdBfGiwT1GRvbc5sFx6M4iYjg9uv1lNKW60PqSJW4iDYrfqzZmB0zF1SJ0BL_rnQZ1Wi_UkFmNe9arM8W9tI9T3Ie59HITFuyVSTCt6qQEtSfa1e5PiBaVuV3qoFI2jPBiVZQ6LPGBWEDyz4QtrHLdECPPoTF30NN6TSVwwlRbCuUUrdNdXdjYe2dMFQ\",\"e\":\"DhaD5zC7mzaDvHO192wKT_9sfsVmdy8w8T8C9VG17_b1jG2srd3cmc6Ycw-0blDf53Wrpi9-KGZXKHX6_uIuJK249WhkP7N1SHrTJxO0sUJ8AhK482PLF09Qtu6cUfJqY1X1y1S2vACJZItU4Vjr3YAfiVGQXeA8frAf7Sm4O1CBStCyg6yCcIbGojII0jfh2vSB-GD9ok1F69Nmk-R-bClyqMCV_Oq-5a0gqClVS8pDyGYMgKTww2RHgZaFSUcG13KeLMQsG2UOB2OjSC8FkOXK00NBlAjU3d0Vv-IamaLIszO7FQBY3Oh0uxNOvIE9ofQyCOpB-xIK6V9CTTphxw\"}]}";
|
||||
|
||||
let jwks: JWKS = serde_json::from_str(jwks_json)
|
||||
.expect("Failed to decode JWKS");
|
||||
let jwks: JWKS = serde_json::from_str(jwks_json).expect("Failed to decode JWKS");
|
||||
|
||||
let jwk = jwks.find("8rDq8Pw0FZcaoXWTEVQo7+Tf2YzSL1fBxNKPCebaai4=")
|
||||
let jwk = jwks
|
||||
.find("8rDq8Pw0FZcaoXWTEVQo7+Tf2YzSL1fBxNKPCebaai4=")
|
||||
.expect("Failed to find required JWK");
|
||||
|
||||
let pkey = public_key_from_jwk(&jwk).expect("Failed to construct public key");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue