frasertweedale / hs-jose

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

Can't decode JWKSet if thumbprint is not 20 bytes (incorrect number of bytes) #54

Closed Leonti closed 6 years ago

Leonti commented 6 years ago

Hi! While trying to decode JWKSet from Auth0 I stumbled on an issue when decoding fails because x5t is more than 20 bytes (I'm getting Left "Error in $.keys[0].x5t: incorrect number of bytes"). Here is the value:

"x5t":"RTBFQjE3MEU0QjQ2M0FCNkYxRTEwMUIwNTJFOUY1NDgyMjgzRTI1NQ"

The decoded value for this is:

E0EB170E4B463AB6F1E101B052E9F5482283E255

It has 40 characters in hex, so I think it will be 20 bytes when in binary.

This is the JWK set coming from https://auth0.com

The full set if needed:

{  
   "keys":[  
      {  
         "alg":"RS256",
         "kty":"RSA",
         "use":"sig",
         "x5c":[  
            "MIIDATCCAemgAwIBAgIJFZEwRAGW4NzQMA0GCSqGSIb3DQEBCwUAMB4xHDAaBgNVBAMTE2xlb250aS5hdS5hdXRoMC5jb20wHhcNMTcwNzA5MDk1OTU3WhcNMzEwMzE4MDk1OTU3WjAeMRwwGgYDVQQDExNsZW9udGkuYXUuYXV0aDAuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApSUFVpIv+kiRsIw6M9R2JqXFofwHM7JXo022PkDFiIsgu1UtKV4ubLssA3hrmJAx1n+jSENifzMEl6ppyh4cmnTbV5XVKNjUm8D7+DsKYebTaf5tbvTTCiil7t4YMWcwIbHudF6j6NFDxXy3c2A0oCOA7+edMOVXXKAbE6/QyE+Z59pa2PkEFWQbE13R4GXewHqtOmuzBj0bKN9mj37tPAaAluizhrE60sK97Xn49wIizunJlquUJycoF093adz0nfJRk/scchID0Dg+MgDNePLUtfCEkGMYDPgwepDUfsOaQDNlssxRGxaFgrK3U6ASMSLkoqP2gCcX9t+uWbO/9QIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBT8lWNl0h7CwDPWEN/WVzh55xwYBjAOBgNVHQ8BAf8EBAMCAoQwDQYJKoZIhvcNAQELBQADggEBAFWJRIuxuPAfX8chXZ2ZEXK+70DGMbw42XWZxNCnhAaPWftPcybvws24LWdxVrYCOngTIQo5ezOsELVjn6mO8TDHvp5L8oeGLCLyTHe4sTw+7rKjV9wyS5DH6xH/40h8CuUEyXghiMAE6r5weCcUQ9r21VpNvLdK+O9LSwnBUCcjOqM4RFazbeVqVMjGWOCP/j9nHpPW5/18YHTTUV5pIrS/STzlGaQ/oeu7GImQSea/Zxsgmw+bkSyT/p5eyDOrlMcc3fwB1LJB72oA3hYj44ndj9gel8dQzWwNb3YKeDbDDUHjGyEfcW+D9HYDAtmgRedOxAt4YGVa7abjwUvfWos="
         ],
         "n":"pSUFVpIv-kiRsIw6M9R2JqXFofwHM7JXo022PkDFiIsgu1UtKV4ubLssA3hrmJAx1n-jSENifzMEl6ppyh4cmnTbV5XVKNjUm8D7-DsKYebTaf5tbvTTCiil7t4YMWcwIbHudF6j6NFDxXy3c2A0oCOA7-edMOVXXKAbE6_QyE-Z59pa2PkEFWQbE13R4GXewHqtOmuzBj0bKN9mj37tPAaAluizhrE60sK97Xn49wIizunJlquUJycoF093adz0nfJRk_scchID0Dg-MgDNePLUtfCEkGMYDPgwepDUfsOaQDNlssxRGxaFgrK3U6ASMSLkoqP2gCcX9t-uWbO_9Q",
         "e":"AQAB",
         "kid":"RTBFQjE3MEU0QjQ2M0FCNkYxRTEwMUIwNTJFOUY1NDgyMjgzRTI1NQ",
         "x5t":"RTBFQjE3MEU0QjQ2M0FCNkYxRTEwMUIwNTJFOUY1NDgyMjgzRTI1NQ"
      }
   ]
}

Cheers, Leonti

frasertweedale commented 6 years ago

This is a bug in auth0. Per https://tools.ietf.org/html/rfc7517#section-4.8:

4.8.  "x5t" (X.509 Certificate SHA-1 Thumbprint) Parameter

   The "x5t" (X.509 certificate SHA-1 thumbprint) parameter is a
   base64url-encoded SHA-1 thumbprint (a.k.a. digest) of the DER
   encoding of an X.509 certificate [RFC5280].

Therefore the unencoded datum must be 20-bytes in size.

frasertweedale commented 6 years ago

Thanks for your report, but you should raise the issue with auth0. Good luck!

Leonti commented 6 years ago

Thanks! I asked the question on their support forum :) I doubt they will change their implementation, but I'd like to know the explanation why they implemented it this way.

wraithm commented 6 years ago

@Leonti Did you ever end up getting anywhere with auth0? I just ran into the same issue. What did you end up doing?

Leonti commented 6 years ago

@WraithM Unfortunately there is no progress from their side: https://community.auth0.com/questions/7227/certificate-thumbprint-is-longer-than-20-bytes

This what I ended up doing unfortunately:

jwksFix :: ByteString -> ByteString  
jwksFix = replace (L.toStrict "x5t") (L.toStrict "x5t_unused") . L.toStrict

It basically removes thumbprint field so it's not decoded anymore At least it still verifies certificate and audience, good enough for my use case.

frasertweedale commented 6 years ago

Fair enough. Be aware that if the x5t parameter appears in the JWS Protected Header (including any JWS containing the x5t parameter that uses the Compact Serialization) this approach will not work.

Another option is to actually decode the hex-encoded thumbprint and re-encode it properly. But again that will only work when it is in the unprotected header.

wraithm commented 6 years ago

I wrote the x5t re-encoding code. @frasertweedale, is this what you were roughly thinking?:

import           Control.Monad
import           Data.Aeson
import           Data.HashMap.Strict              as H
import           Data.Text.Encoding
import           Data.ByteString.Lazy             (ByteString)
import qualified Data.ByteString.Base16           as B16
import qualified Data.ByteString.Base64.URL       as B64
import qualified Crypto.JOSE.JWK                  as JWK

auth0TokenFix :: ByteString -> Maybe JWK.JWKSet
auth0TokenFix jwksBs = do
    jwks <- mapM (modifyx5tField <=< fromObject)
        =<< fromArray =<< H.lookup "keys"
        =<< fromObject =<< decode jwksBs
    decode . encode $ object [ "keys" .= jwks ]
  where
    x5tField = "x5t"
    modifyx5tField o = flip (H.insert x5tField) o . String . b64HexToB64 <$> (fromJSONString =<< H.lookup x5tField o)
    b64HexToB64 = decodeUtf8 . B64.encode . fst . B16.decode . B64.decodeLenient . encodeUtf8

    fromJSONString (String s) = Just s
    fromJSONString _          = Nothing
    fromArray (Array xs) = Just xs
    fromArray _          = Nothing
    fromObject (Object o) = Just o
    fromObject _          = Nothing
frasertweedale commented 6 years ago

Yes, pretty much along those lines.

tmcgilchrist commented 5 years ago

Is it worth linking that snippet along side the auth0 being broken comment?

frasertweedale commented 5 years ago

@tmcgilchrist good idea; done: https://github.com/frasertweedale/hs-jose#interoperability-issues.

ocramz commented 4 years ago

IIUC this has been fixed upstream with signing key rotation :

https://community.auth0.com/t/jwk-certificate-thumbprint-is-invalid/16070/22

frasertweedale commented 4 years ago

Wow! Only took 2.5 years... Thanks for the update @ocramz.