crystal-community/jwt

Support for JWKS?

carcinocron opened this issue · 9 comments

How would you verify and read a JWT signed by a JWKS? Here are some reference libs from other languages:

https://github.com/auth0/node-jwks-rsa
https://github.com/nov/json-jwt/wiki#decode--verify

It's outlined in the readme: https://github.com/crystal-community/jwt#usage
Just change the algorithm in the example to RS256

So the issue is that i dont see how we can assemble the jkws pem key via the methods available in openssl_ext. I currently have not been able to do it at least.

I believe the request is to support verifying a JWT token via the JWKS method. i.e.:

jwt_token = "aaaa.bbbb.cccc"
info = JSON.parse(Halite.get("https://oauth.com/.well-known/openid-configuration").body)
keys = JSON.parse(Halite.get(info["jwks_uri"].as_s).body)
payload, header = JWT.decode(jwt_token, keys, verify: true, validate: true)

More links on the JWKS auth data:

https://8gwifi.org/jwkconvertfunctions.jsp
https://developer.byu.edu/docs/consume-api/use-api/implement-openid-connect/jwks-public-key-documentation

@kalinon if I'm reading this correctly there is a desire to have a helper class that can construct and decode the JWKS structure into something a little more useful?

plus, potentially a HTTP wrapper to help with making requests using those keys?

@kalinon if I'm reading this correctly there is a desire to have a helper class that can construct and decode the JWKS structure into something a little more useful?

plus, potentially a HTTP wrapper to help with making requests using those keys?

Perhaps the HTTP wrapper may be out of scope on this request, at a minimum i think we need to be able to pass a JWKS object to JWT.decode or perhaps enhance openssl_ext to allow the creation of RSA public keys from the JWKS information.

I admit i am not an expert in libcrypto or openssl, but it seems being able to set the n, e and other variables is required? I did see empty specs in the openssl_ext for this functionality: https://github.com/randomstate/openssl_ext/blob/master/spec/rsa_spec.cr#L140

The general workflow of JWKS for RS256 is:

  1. reach out to the jwks_uri to get key information
  2. transform the key information into a public key
  3. use public key to verify token

Number 2 is not currently possible via the jwt or openssl_ext libs as far as i can tell.

Any chance of this being added? It will help usecases like integrating google/apple sign ins into our apps or using AWS services using the authenticated api.

For eg.
the google-sign-in's key is at;

pp! pub_key_jwk = HTTP::Client.get "https://www.googleapis.com/oauth2/v3/certs"
pp! pub_key_pem = HTTP::Client.get "https://www.googleapis.com/oauth2/v1/certs"
and I would want to decode like this;
payload, header = JWT.decode(token, pub_key_pem , JWT::Algorithm::HS256)

So after some headache, I have a very simple and reductive JWK to JWT which uses use mod and exp.

require "base64"
 
pp! jwk = {
  "n": "3aOynmXd2aSH0ZOd0TIYd5RRaNXhLW306dlYw26nMp6QPGaJuOeMhTO3BO8Zt_ncRs4gdry4mEaUOetCKTUOyCCpIM2JAn0laN_iHfGKTYsNkjr16FiHWYJmvNJ1Q1-XXjWqNNKMFIKHKtMrsP2XPVD6ufp-lNQmt4Dl0g0qXJ4_Y_CKuP-uSlFWZuJ_0_2ukUgevvKtOZNcbth0iOiFalBRDr-2i1eNSJWOknEphy7GRs-JGPboTdHC7A3b-0dVFGMEMJFhxcEJHJgLCsQGdYdkphLJ5f21gCNdhp3g16H3Cqts2KTXgO4Rr8uhwZx5yiUjTuizD9wc7uDso4UJ7Q",
  "use": "sig",
  "kty": "RSA",
  "kid": "b6f8d55da534ea91cb2cb00e1af4e8e0cdeca93d",
  "alg": "RS256",
  "e": "AQAB"
}
 
mod_hex = Base64.decode(jwk["n"]).hexstring
exp_hex = Base64.decode(jwk["e"]).hexstring
 
pub_key_hex = "30820122300D06092A864886F70D01010105000382010F003082010A0282010100" + mod_hex + "0203" + exp_hex
 
pp! pub_key = Base64.encode(String.new(pub_key_hex.hexbytes))

This only uses

parameter n: Base64 URL encoded string representing the modulus of the RSA Key.
parameter e: Base64 URL encoded string representing the public exponent of the RSA Key.

out of all possible jwk fields

parameter d: Base64 URL encoded string representing the private exponent of the RSA Key.
parameter p: Base64 URL encoded string representing the secret prime factor of the RSA Key.
parameter q: Base64 URL encoded string representing the secret prime factor of the RSA Key.
parameter dp: Base64 URL encoded string representing the first factor CRT exponent of the RSA Key. d mod (p-1)
parameter dq: Base64 URL encoded string representing the second factor CRT exponentof the RSA Key. d mod (q-1)
parameter qi: Base64 URL encoded string representing the first CRT coefficient of the RSA Key. q^-1 mod p

I made a lib for this here: https://github.com/place-labs/jwks

I also wouldn't mind merging this into this repo.