tekul / jose-jwt

Haskell implementation of JOSE/JWT standards
BSD 3-Clause "New" or "Revised" License
35 stars 22 forks source link

Jose.Jwk Haddocks and Questions #10

Open jasonzoladz opened 8 years ago

jasonzoladz commented 8 years ago

First, thanks very much for building this library. I'm not in a position to roll-my-own-crypto, so it's practical libraries like this one that keep me free of Scala's rough, wtf-is-this embrace.

Second, please create haddocks for Jose.Jwk. Lack of docs for Jose.Jwk definitely impeded my progress with the library.

Third, as a sanity check, is this proper usage of the lib? Further, do you have concerns with your library being used in production?

import qualified Data.Aeson.Encode     as A
import           Data.ByteString.Lazy  (toStrict)
import           Data.Either
import           Data.Time.Clock
import           Data.Time.Clock.POSIX
import           Jose.Jwa
import           Jose.Jwk
import           Jose.Jws
import           Jose.Jwt

-- Get a Jwk
privateJwk :: IO Jwk
privateJwk = do
  (_, privKey) <- generateRsaKeyPair 256 (KeyId "mykey") Sig (Just (Signed RS256))
  return privKey

-- Make some claims
makeJwtClaims :: IO JwtClaims
makeJwtClaims = do
  currentUTC <- getCurrentTime
  let laterDate =  IntDate $ utcTimeToPOSIXSeconds $ addUTCTime (60 * 60 * 24 * 14) currentUTC
  return $
    JwtClaims (Just "issuer")
              (Just "sub")
              (Just ["aud1", "aud2"])
              (Just laterDate)
              Nothing
              Nothing
              (Just "jti")

-- Create a Payload
makePayload :: JwtClaims -> Payload
makePayload claims = Claims $ toStrict $ A.encode claims

-- Test Encode and Decode a Jwt
encodeDecodePrint :: IO ()
encodeDecodePrint = do
  jwk    <- privateJwk
  claims <- makeJwtClaims
  let encAlg  = JwsEncoding RS256
      payload = makePayload claims
  eitherJwt <- encode [jwk] encAlg payload
  case eitherJwt of
    Right jwt -> do
      eitherContent <- decode [jwk] (Just encAlg) (unJwt jwt)
      either (\_ -> print "Decode Failure")
             (\(Jws (_, bs)) -> print bs)
             eitherContent
    _ -> print "Encode failure"

Finally, if possible, please post details on how to send donations to support your work on this. I'd gladly contribute.

Cheers, Jason

tekul commented 8 years ago

Hi. Thanks for your feedback! I will try to add some more documentation in general to clarify some of the things that your questions raise. Most of the library evolved to support my own use cases on another project, so it's definitely not comprehensive and some things are only implemented as far as I needed them to be. The APIs could also probably be improved.

It's true that there isn't any Haddock for the JWK module. This is mostly because the code (like the spec) is rather clunky to work with directly and I don't really like it as a public API. It is mainly there because you often have to work with JWKs when using the various specs, so it is useful to be able to deal with existing JSON encoded keys. In this case you are only really using the FromJSON instances. It is also useful to be able to export JSON JWKs, but the current module is quite limited in terms of how much of the spec if covers.

In any case, you don't need to use JWKs to create tokens and you don't need to use Jwt.encode and Jwt.decode. You get more type safety from working directly with the lower level APIs. For example, you could use the Jws module directly in your example above, which makes the code simpler. You don't have to use the JwtClaims type to create the content. That is really intended as a convenience for checking certain named claims (sub, iss etc.).

import Jose.Jws
import Jose.Jwa
import Crypto.PubKey.RSA

...

encodeDecodePrint :: IO ()
encodeDecodePrint = do
    (kPub, kPr) <- generate 512 65537
    eitherJwt <- rsaEncode RSA256 kPr "your content as a bytestring"
    case eitherJwt of
        Right jwt -> do
            eitherContent <- rsaDecode kPub (unJwt jwt)
            either (\_ -> print "Decode Failure")
                (\(Jws (_, bs)) -> print bs)
                eitherContent
    _ -> print "Encode failure"   

In some use cases (like mine) you may need to support multiple key and algorithm options, and have different keys for different purposes. I configure a server with a set of JWKs and also with the algorithms used, but obviously if the keys don't match the algorithms, you will get a runtime error.

JWTs are pretty general purpose, so "proper usage" of the library is hard to define. The functionality is really all in encode/decode pairs, so it's quite simple to use and I can't really think of a wrong way to use it. However, I'm talking purely about the library functionality here, not about how you choose to use JWTs, where there is obviously more scope for error, depending on what they're being used for.

Regarding use in production, I don't have any specific concerns. I'm reasonably confident that the code works the way it's intended to, in terms of producing and decoding valid JWTs. Decoding invalid content is something I would like to investigate more. There are no guarantees, and you should do plenty of tests and make your own decision, based on what you are building. Anything that applies to the underlying libraries (such as cryptonite) and Haskell crypto in general applies here.

I'm not looking for donations at the moment, but thanks for your generous offer. I'll let you know if times get tough ;-).

jasonzoladz commented 8 years ago

Thank you for clarifying this stuff. Really helpful. I appreciate it.

As for donations, I just thought you might want to do some mountain biking: http://blog.mohiva.com/2016/01/new-mtb-for-spring-campaign.html :)

tekul commented 8 years ago

The weather here is kind of lousy for biking. I might start an "emigrate to Spain" fund. I don't think my workload here justifies it yet though. I'll leave this open for the time being as a reminder to make some doc alterations.