frasertweedale / hs-jose

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

Implement JWK Thumbprint (RFC 7638) #37

Closed frasertweedale closed 7 years ago

frasertweedale commented 7 years ago

https://tools.ietf.org/html/rfc7638

sophie-h commented 7 years ago

Stuff that I know. Maybe this simplifies the implementation:

Currently I'm using this implementation in my ACME library:

import Crypto.Hash.SHA256 (hash)

jwkThumbprint :: KeyMaterial -> String
jwkThumbprint = sha256 . L.unpack . encodingToLazyByteString . pairs . toEnc
  where
    toEnc (ECKeyMaterial ECKeyParameters {..}) =
      "crv" .= ecCrv <> "kty" .= ecKty <> "x" .= ecX <> "y" .= ecY
    toEnc (RSAKeyMaterial keyParam) =
      "e" .= (keyParam ^?! rsaE) <> "kty" .= (keyParam ^?! rsaKty) <> "n" .=
      (keyParam ^?! rsaN)
    toEnc (OctKeyMaterial OctKeyParameters {..}) = undefined

sha256 :: String -> String
sha256 x = filter (/= '=') $ B.unpack $ Base64.encode (hash $ B.pack x)

Looks pretty messy. Would be great to have this in jose! (Also, converting something to the JWS Base64 format and the SHA256 function would be great library features!)

I'm testing this against the RFC example (only RSA). The test looks something like this

      let file = $(embedStringFile "test/rfc7638-jwk.json") :: L.ByteString
          jwk = fromJust (decode file) :: KeyMaterial
      in jwkThumbprint jwk @?= "NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs"
frasertweedale commented 7 years ago

@sophie-h thanks for the info! Your approach is sound; I'll add this soon (within the next week or so), and include it in the next release (maybe a bit after I add the new function, unless more fixes/features are needed).

For the base64 stuff, I think it would be best to add it base64-bytestring (or, if they that project is not receptive, a small, separate library).

I don't see a need to export a SHA-256 function from jose (once JWK Thumbprint is implemented).

Thanks again for your input; glad to hear someone is working on a Haskell ACME implementation and finding my library useful.

frasertweedale commented 7 years ago

@sophie-h hi again. Would you mind reviewing f7cca79ed0d3a87332767363368344ad466ba700 to determine if it meets your needs?

Note that I re-export Crypto.Hash via Crypto.JOSE.JWK so it is easy to select the desired digest algorithm.

sophie-h commented 7 years ago

Great! Without jose it would be really painful to work with ACME. Implementation looks good to me!

Maybe I overlooked something, but currently I don't see a way to avoid the filter (/= '='). If I need the thumbprint inside of JWS I can use Base64Octets/Base64SHA256, but ACME also needs the thumbprint as Base64url without = as DNS record etc. Maybe, the easiest way would be to add a function that converts Base64Octets/Base64SHA256 to String or Bytestring. base64-bytestring does not remove the =, so imho the logic of this format belongs to JWS.

Also, at some point Base64SHA256 fooled me. I thought that my Bytestring would be hashed when converted to JSON. Don't know if it's worth documenting that the constructor takes as hashed value.

frasertweedale commented 7 years ago

@sophie-h how do you like this prism? https://github.com/frasertweedale/hs-jose/commit/48efff65638a49db7f416dea6198e627f1399371

Update: added a new subcommand to the example program that computes the fingerprint and uses the prism to print the base64url encoding of it: https://github.com/frasertweedale/hs-jose/commit/8a600ba8482bb97e5211df00ed24d95e33b0268a

sophie-h commented 7 years ago

Looks good and very flexible.

frasertweedale commented 7 years ago

Fixed in f7cca79.