frasertweedale / hs-jose

Haskell JOSE and JWT library
http://hackage.haskell.org/package/jose
Apache License 2.0
122 stars 46 forks source link

Verifying tokens from Google's SecureTokens API #63

Closed donatello closed 6 years ago

donatello commented 6 years ago

I am trying to verify tokens signed by Google's SecureToken service - https://developers.google.com/identity/toolkit/securetoken

Google is using the RS256 algo, and the public key is one of the keys provided at https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com

So to verify the tokens generated by Google, we need to parse and convert the keys provided at the above URL, and check if any of them successfully verify the token.

To parse each public key, I am using:

fromCertRaw :: ByteString -> Either Text X509.Certificate
fromCertRaw s = do
    pems <- fmapL toS $ Pem.pemParseBS s
    pem <- note "No pem found" $ headMay pems
    signedExactCert <- fmapL toS $ X509.decodeSignedCertificate $
                       Pem.pemContent pem
    let sCert = X509.getSigned signedExactCert
        cert = X509.signedObject sCert
    return cert

To convert the Certificate value to a JWK value:

certToJwk :: X509.Certificate -> Either Text JWT.JWK
certToJwk cert = do
    let X509.PubKeyRSA (PublicKey size n e) = X509.certPubKey cert
        jwk = JWK.fromKeyMaterial $ JWK.RSAKeyMaterial $
              JWK.RSAKeyParameters (JTypes.SizedBase64Integer size n)
              (JTypes.Base64Integer e) Nothing
        jwk' = jwk & JWK.jwkKeyOps .~ Just [JWK.Verify]
    return jwk'

I got signature verification working with something like this:

verifyIt :: ByteString -> JWT.JWK -> Either JWT.Error ByteString
verifyIt token key = do
    jws <- decodeCompact (toS token) :: (Either JWT.Error
                                         (JWS.CompactJWS JWS.JWSHeader))
    JWS.verifyJWS' key jws

Is this the right way to perform the verification?

If you like I could send part of this as a PR as it could help solve part of https://github.com/frasertweedale/hs-jose/issues/62 - but I would need some help as I am not too familiar with lenses.

frasertweedale commented 6 years ago

@donatello your approach is fine. One improvement would be to construct a JWKSet of all the keys and then perform validation once. (This lets you avoid explicitly checking each JWK to find one that verifies the token).

w.r.t. #62, the main part I'd like to adopt is certToJwk. Feel free to create a PR. Otherwise I'll tackle this soon. I'd like the type to be:

fromX509Certificate
  :: X509.Certificate
  -> Maybe JWK

where the Nothing result absorbs all of the unsupported key types (c.f. your current implementation which has an irrefutable pattern match for an RSA public key). I'd also like the jwkX5c field to be set. And finally, you should be able to use the existing fromRSA function to do some of they heavy lifting.

frasertweedale commented 6 years ago

Closing this. #62 will take care of JWK construction from X.509 cert. Thanks for your contribution @donatello.