-
Notifications
You must be signed in to change notification settings - Fork 35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How to use a jwk for decoding a jwt? #96
Comments
Sorry I haven't touched this library for a while. There is currently no code to construct a key usable by |
I tried that, but openssl seems to have a different type for BigNums, and it seems there is no way to convert between the two :| |
I see. A thought: is there any way to convert the BigNums into little/big endian byte arrays and then have the other library reconstruct it from the byte array with the appropriate endianness? |
Yeah, it seems to support |
I see where you're going, but it doesn't seem to be working: let jwks: JWKSet<Empty> = serde_json::from_str(include_str!("jwks.json"))?;
let jwk = jwks.find("42").unwrap();
let jwt: JWT<Empty, Empty> = JWT::new_encoded(include_str!("access.jwt"));
let key_parameters = if let AlgorithmParameters::RSA(ref kp) = jwk.algorithm {
Some(kp)
} else { None }.unwrap();
let n_be = key_parameters.n.to_bytes_be();
let e_be = key_parameters.e.to_bytes_be();
let n_bn = openssl::bn::BigNum::from_slice(&n_be).unwrap();
let e_bn = openssl::bn::BigNum::from_slice(&e_be).unwrap();
let pubkey = openssl::rsa::Rsa::from_public_components(n_bn, e_bn).unwrap();
let der = pubkey.public_key_to_der().unwrap();
let secret = Secret::PublicKey(der);
let algorithm = SignatureAlgorithm::RS256;
let decoded = jwt.decode(&secret, algorithm);
Ok(format!("jwk = {:?} \n\n\n jwt = {:?}\n\n\n decoded= {:?}", jwk, jwt, decoded)) This returns |
Turns out should've used The code below works: let jwks: JWKSet<Empty> = serde_json::from_str(include_str!("jwks.json"))?;
let jwk = jwks.find("42").unwrap();
let key_parameters = if let AlgorithmParameters::RSA(ref kp) = jwk.algorithm {
Some(kp)
} else { None }.unwrap();
let n_be = key_parameters.n.to_bytes_be();
let e_be = key_parameters.e.to_bytes_be();
let n_bn = openssl::bn::BigNum::from_slice(&n_be).unwrap();
let e_bn = openssl::bn::BigNum::from_slice(&e_be).unwrap();
let pubkey = openssl::rsa::Rsa::from_public_components(n_bn, e_bn).unwrap();
let der: Vec<u8> = pubkey.public_key_to_der_pkcs1().unwrap();
let secret = Secret::PublicKey(der);
let algorithm = SignatureAlgorithm::RS256;
let jwt: JWT<MyClaims, Empty> = JWT::new_encoded(include_str!("access.jwt"));
let decoded = jwt.decode(&secret, algorithm);
println!("decoded= {:?}", decoded)); |
Thanks for figuring this out. I might add this in the future to the documentation or add some optional functions to do this (behind a feature gate probably). |
Well this is mostly a hack... I hope your implementation will not go back-and-forth to openssl to do it, but i'm looking forward to it - it seems like a logical thing to do - use a jwk to get the public key to verify the signature of the jwt :) |
Ok, so, its actually a bit easier than above. I ended up having to use the fork here: I loaded the JWK's from Amazon Cognito in this case, code to extract the custom claims is pretty short: use biscuit::{
jwa::SignatureAlgorithm,
jwk::{AlgorithmParameters, JWKSet},
jws::{Header, Secret},
Empty, JWT,
};
use reqwest;
#[derive(Debug)]
struct AuthError {}
pub struct CognitoAuth {
key_set: JWKSet<Empty>,
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct CognitoClaims {
#[serde(rename = "cognito:username")]
pub username: String,
#[serde(rename = "custom:sub-exp")]
pub sub_exp: String,
}
impl CognitoAuth {
/// Create a new CognitoAuth object
///
/// Blocking call that will panic if unable to load the initial JWK's from AWS.
pub fn new(region: &str, user_pool_id: &str) -> Self {
let url = format!(
"https://cognito-idp.{}.amazonaws.com/{}/.well-known/jwks.json",
region, user_pool_id
);
let data: JWKSet<Empty> = reqwest::get(&url)
.expect("Unable to fetch JWK's")
.json()
.expect("Unable to load JWK response into JSON");
CognitoAuth { key_set: data }
}
/// Extract an auth token and return a Cognito User object
pub fn extract_auth_header(&self, token: &str) -> Result<CognitoClaims, AuthError> {
// First extract without verifying the header to locate the key-id (kid)
let token = JWT::<CognitoClaims, Empty>::new_encoded(token);
let header: Header<Empty> = token.unverified_header().map_err(|_| AuthError {})?;
let key_id = header.registered.key_id.ok_or(AuthError {})?;
let key = self.key_set.find(&key_id).ok_or(AuthError {})?;
// Now that we have the key, construct our RSA public key secret
let secret = match key.algorithm {
AlgorithmParameters::RSA(ref rsa_key) => Secret::Pkcs {
n: rsa_key.n.clone(),
e: rsa_key.e.clone(),
},
_ => return Err(AuthError {}),
};
// Not fully verify and extract the token with verification
let token = token
.into_decoded(&secret, SignatureAlgorithm::RS256)
.map_err(|_| AuthError {})?;
let user = token.header().map_err(|_| AuthError {})?;
let payload = token.payload().map_err(|_| AuthError {})?;
Ok(payload.private.clone())
}
} It'd be great to see the branch PR merged in and some examples polished up since I'd imagine using a JWK like this is a fairly common desire. |
I would like to take what was implemented in #91 and tweak the design a bit. Give me a bit of time and I'll open a PR with that. |
I've merged #100 with the code from #91. Also added a convenience method to construct the I've released this as Not fully satisfied with how this is done. Ideally I would like to be rid of Closing this for now. If you think this is unresolved, please reopen or raise a new issue. |
Hello,
Can you please provide an example on how to use a jwk to decode an RS256 jwt?
What I got so far:
The text was updated successfully, but these errors were encountered: