packages feed

jwt 0.10.1 → 0.11.0

raw patch · 7 files changed

+144/−68 lines, 7 filesdep +cryptostoredep ~doctest

Dependencies added: cryptostore

Dependency ranges changed: doctest

Files

CHANGELOG.md view
@@ -1,3 +1,10 @@+# 2021-12-11 0.11.0++* Added support for RSA256 Public Key verification. This in turn means that some +methods are available to verify that are not available to encode; causing a +breaking API change. The `toVerify` method and the new `EncodeSigner` and `VerifySigner` +are the major changes.+ # 2021-12-02 0.10.1  * Add support for Aeson 2.x
README.md view
@@ -13,4 +13,4 @@ See the [Web.JWT module](http://hackage.haskell.org/package/jwt/docs/Web-JWT.html) documentation to get started.  [![Build-Status](https://travis-ci.org/juretta/haskell-jwt.svg?branch=master)](https://travis-ci.org/juretta/haskell-jwt)+Status](https://img.shields.io/bitbucket/pipelines/puffnfresh/haskell-jwt/master)](https://bitbucket.org/puffnfresh/haskell-jwt/addon/pipelines/home)
doctests.hs view
@@ -1,4 +1,4 @@ import Test.DocTest  main :: IO ()-main = doctest ["-isrc", "src/Web"]+main = doctest ["-isrc", "src"]
jwt.cabal view
@@ -2,7 +2,7 @@ -- documentation, see http://haskell.org/cabal/users-guide/  name:                jwt-version:             0.10.1+version:             0.11.0 synopsis:            JSON Web Token (JWT) decoding and encoding license:             MIT license-file:        LICENSE@@ -34,6 +34,7 @@   other-modules:       Data.Text.Extended, Data.ByteString.Extended   build-depends:       base >= 4.8 && < 5                      , cryptonite               >= 0.6+                     , cryptostore              >= 0.2                      , memory                   >= 0.8                      , bytestring               >= 0.10                      , text                     >= 0.11@@ -81,6 +82,7 @@                      , HUnit                      , QuickCheck >= 2.4.0.1                      , cryptonite+                     , cryptostore                      , memory                      , bytestring               >= 0.10                      , text                     >= 0.11@@ -105,4 +107,4 @@   ghc-options:         -threaded   build-depends:       base < 5 && >= 4.8                      , jwt-                     , doctest     >= 0.9.11+                     , doctest     >= 0.20
src/Web/JWT.hs view
@@ -42,6 +42,8 @@     , tokenIssuer     , hmacSecret     , readRsaSecret+    , readRsaPublicKey+    , toVerify     -- ** JWT structure     , claims     , header@@ -58,7 +60,8 @@     , UnverifiedJWT     , VerifiedJWT     , Signature-    , Signer(..)+    , VerifySigner(..)+    , EncodeSigner(..)     , JWT     , Algorithm(..)     , JWTClaimsSet(..)@@ -84,8 +87,9 @@ import           Control.Monad import           Crypto.Hash.Algorithms import           Crypto.MAC.HMAC-import           Crypto.PubKey.RSA          (PrivateKey)-import           Crypto.PubKey.RSA.PKCS15   (sign)+import           Crypto.PubKey.RSA          (PrivateKey, PublicKey)+import qualified Crypto.PubKey.RSA.PKCS15   as RSA+import           Crypto.Store.X509          (readPubKeyFileFromMemory) import           Data.ByteArray.Encoding import           Data.Aeson                 hiding (decode, encode) import qualified Data.Aeson                 as JSON@@ -94,7 +98,7 @@ import           Data.Scientific import qualified Data.Semigroup             as Semigroup import           Data.Time.Clock            (NominalDiffTime)-import           Data.X509                  (PrivKey (PrivKeyRSA))+import           Data.X509                  (PrivKey (PrivKeyRSA), PubKey (PubKeyRSA)) import           Data.X509.Memory           (readKeyFileFromMemory) import qualified Network.URI                as URI import           Prelude                    hiding (exp)@@ -106,18 +110,16 @@ import qualified Data.HashMap.Strict        as KeyMap #endif --- $setup--- The code examples in this module require GHC's `OverloadedStrings`--- extension:------ >>> :set -XOverloadedStrings- {-# DEPRECATED JWTHeader "Use JOSEHeader instead. JWTHeader will be removed in 1.0" #-} type JWTHeader = JOSEHeader -data Signer = HMACSecret BS.ByteString-            | RSAPrivateKey PrivateKey+data VerifySigner = VerifyHMACSecret BS.ByteString+                  | VerifyRSAPrivateKey PrivateKey+                  | VerifyRSAPublicKey PublicKey +data EncodeSigner = EncodeHMACSecret BS.ByteString+                  | EncodeRSAPrivateKey PrivateKey+ newtype Signature = Signature T.Text deriving (Show)  instance Eq Signature where@@ -257,22 +259,22 @@  -- | Encode a claims set using the given secret -----  @+--  >>> :{ --  let --      cs = mempty { -- mempty returns a default JWTClaimsSet---         iss = stringOrURI "Foo"---       , unregisteredClaims = Map.fromList [("http://example.com/is_root", (Bool True))]+--         iss = stringOrURI . T.pack $ "Foo"+--       , unregisteredClaims = ClaimsMap $ Map.fromList [(T.pack "http://example.com/is_root", (Bool True))] --      }---      key = hmacSecret "secret-key"+--      key = hmacSecret . T.pack $ "secret-key" --  in encodeSigned key mempty cs--- @--- > "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJodHRwOi8vZXhhbXBsZS5jb20vaXNfcm9vdCI6dHJ1ZSwiaXNzIjoiRm9vIn0.vHQHuG3ujbnBUmEp-fSUtYxk27rLiP2hrNhxpyWhb2E"-encodeSigned :: Signer -> JOSEHeader -> JWTClaimsSet -> T.Text+--  :}+--  "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJodHRwOi8vZXhhbXBsZS5jb20vaXNfcm9vdCI6dHJ1ZSwiaXNzIjoiRm9vIn0.vHQHuG3ujbnBUmEp-fSUtYxk27rLiP2hrNhxpyWhb2E"+encodeSigned :: EncodeSigner -> JOSEHeader -> JWTClaimsSet -> T.Text encodeSigned signer header' claims' = dotted [header'', claim, signature']     where claim     = encodeJWT claims'           algo      = case signer of-                        HMACSecret _    -> HS256-                        RSAPrivateKey _ -> RS256+                        EncodeHMACSecret _    -> HS256+                        EncodeRSAPrivateKey _ -> RS256            header''  = encodeJWT header' {                         typ = Just "JWT"@@ -282,16 +284,15 @@  -- | Encode a claims set without signing it -----  @---  let---      cs = mempty { -- mempty returns a default JWTClaimsSet---      iss = stringOrURI "Foo"---    , iat = numericDate 1394700934---    , unregisteredClaims = Map.fromList [("http://example.com/is_root", (Bool True))]---  }+--  >>> :{+--  let cs = mempty+--            { iss = stringOrURI . Data.Text.pack $ "Foo"+--            , iat = numericDate 1394700934+--            , unregisteredClaims = ClaimsMap $ Data.Map.fromList [(Data.Text.pack "http://example.com/is_root", (Bool True))]+--            } --  in encodeUnsigned cs mempty---  @--- > "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjEzOTQ3MDA5MzQsImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlLCJpc3MiOiJGb28ifQ."+--  :}+--  "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjEzOTQ3MDA5MzQsImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlLCJpc3MiOiJGb28ifQ." encodeUnsigned :: JWTClaimsSet -> JOSEHeader -> T.Text encodeUnsigned claims' header' = dotted [header'', claim, ""]     where claim     = encodeJWT claims'@@ -307,7 +308,7 @@ -- -- >>> :{ --  let---      input = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzb21lIjoicGF5bG9hZCJ9.Joh1R2dYzkRvDkqv3sygm5YyK8Gi4ShZqbhK2gxcs2U" :: T.Text+--      input = Data.Text.pack "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzb21lIjoicGF5bG9hZCJ9.Joh1R2dYzkRvDkqv3sygm5YyK8Gi4ShZqbhK2gxcs2U" --      mJwt = decode input --  in fmap header mJwt -- :}@@ -317,7 +318,7 @@ -- -- >>> :{ --  let---      input = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzb21lIjoicGF5bG9hZCJ9.Joh1R2dYzkRvDkqv3sygm5YyK8Gi4ShZqbhK2gxcs2U" :: T.Text+--      input = T.pack "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzb21lIjoicGF5bG9hZCJ9.Joh1R2dYzkRvDkqv3sygm5YyK8Gi4ShZqbhK2gxcs2U" --      mJwt = decode input --  in fmap claims mJwt -- :}@@ -346,17 +347,16 @@ -- -- >>> :{ --  let---      input = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzb21lIjoicGF5bG9hZCJ9.Joh1R2dYzkRvDkqv3sygm5YyK8Gi4ShZqbhK2gxcs2U" :: T.Text+--      input = T.pack "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzb21lIjoicGF5bG9hZCJ9.Joh1R2dYzkRvDkqv3sygm5YyK8Gi4ShZqbhK2gxcs2U" --      mUnverifiedJwt = decode input---      mVerifiedJwt = verify (hmacSecret "secret") =<< mUnverifiedJwt+--      mVerifiedJwt = verify (toVerify . hmacSecret . T.pack $ "secret") =<< mUnverifiedJwt --  in signature =<< mVerifiedJwt -- :} -- Just (Signature "Joh1R2dYzkRvDkqv3sygm5YyK8Gi4ShZqbhK2gxcs2U")-verify :: Signer -> JWT UnverifiedJWT -> Maybe (JWT VerifiedJWT)+verify :: VerifySigner -> JWT UnverifiedJWT -> Maybe (JWT VerifiedJWT) verify signer (Unverified header' claims' unverifiedSignature originalClaim) = do-   let calculatedSignature = Signature $ calculateDigest signer originalClaim-   guard (unverifiedSignature == calculatedSignature)-   pure $ Verified header' claims' calculatedSignature+   guard (verifyDigest signer unverifiedSignature originalClaim)+   pure $ Verified header' claims' unverifiedSignature  -- | Decode a claims set and verify that the signature matches by using the supplied secret. -- The algorithm is based on the supplied header value.@@ -366,12 +366,12 @@ -- -- >>> :{ --  let---      input = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzb21lIjoicGF5bG9hZCJ9.Joh1R2dYzkRvDkqv3sygm5YyK8Gi4ShZqbhK2gxcs2U" :: T.Text---      mJwt = decodeAndVerifySignature (hmacSecret "secret") input+--      input = T.pack "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzb21lIjoicGF5bG9hZCJ9.Joh1R2dYzkRvDkqv3sygm5YyK8Gi4ShZqbhK2gxcs2U"+--      mJwt = decodeAndVerifySignature (toVerify . hmacSecret . T.pack $ "secret") input --  in signature =<< mJwt -- :} -- Just (Signature "Joh1R2dYzkRvDkqv3sygm5YyK8Gi4ShZqbhK2gxcs2U")-decodeAndVerifySignature :: Signer -> T.Text -> Maybe (JWT VerifiedJWT)+decodeAndVerifySignature :: VerifySigner -> T.Text -> Maybe (JWT VerifiedJWT) decodeAndVerifySignature signer input = verify signer =<< decode input  -- | Try to extract the value for the issue claim field 'iss' from the web token in JSON form@@ -380,14 +380,20 @@  -- | Create a Secret using the given key. -- Consider using `HMACSecret` instead if your key is not already a "Data.Text".-hmacSecret :: T.Text -> Signer-hmacSecret = HMACSecret . TE.encodeUtf8+hmacSecret :: T.Text -> EncodeSigner+hmacSecret = EncodeHMACSecret . TE.encodeUtf8 +-- | Converts an EncodeSigner into a VerifySigner+-- If you can encode then you can always verify; but the reverse is not always true.+toVerify :: EncodeSigner -> VerifySigner+toVerify (EncodeHMACSecret s) = VerifyHMACSecret s+toVerify (EncodeRSAPrivateKey pk) = VerifyRSAPrivateKey pk+ -- | Create an RSAPrivateKey from PEM contents -- -- Please, consider using 'readRsaSecret' instead.-rsaKeySecret :: String -> IO (Maybe Signer)-rsaKeySecret = pure . fmap RSAPrivateKey . readRsaSecret . C8.pack+rsaKeySecret :: String -> IO (Maybe EncodeSigner)+rsaKeySecret = pure . fmap EncodeRSAPrivateKey . readRsaSecret . C8.pack  -- | Create an RSA 'PrivateKey' from PEM contents --@@ -433,6 +439,30 @@         [(PrivKeyRSA k)] -> Just k         _                -> Nothing ++-- | Create an RSA 'PublicKey' from PEM contents+--+-- > readRsaPublicKey <$> BS.readFile "foo.pub"+-- >>> :{+--   fromJust . readRsaPublicKey . Data.ByteString.Char8.pack $ Data.List.unlines+--       [ "-----BEGIN PUBLIC KEY-----"+--       , "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA12d4M6f3QQ9E52fVjoJ7"+--       , "HorKvi1A83f4YL4e7TU0Lj/73+afrRBtnAdl8dIrnYHLWRdL9T4+yw7+AimQgj1R"+--       , "zZO5FQN/qVxygkPeMKAZ53nObi2NyBbQYmRrBjx7rOz7UddI5qo/ApTWNrSBjDKK"+--       , "1splbuO2BoTrsHlsSoJDWps/5SwpEF4GGkn5c4nZRnpnayUZqolp+HwDK2Dys9MO"+--       , "GEIsUil1+k/76T96pBnPf6mf3X0IacTCNjJcztSaHCPWre1q45miQUGVlTmhfg/6"+--       , "L8xmNRxz4BZdv8Nv6STfRTsn6PqiaabD0vITVsF1AapdHohmPMwe+lG5ebUJEh8p"+--       , "HQIDAQAB"+--       , "-----END PUBLIC KEY-----"+--       ]+-- :}+-- PublicKey {public_size = 256, public_n = 27192258298637073499814714121384917708820189127612408586659742012541332375187297990620494295383503839337630959589643433993051132285579261506578281787130221431792495554016841577295914249477128682873612830754668313951998800261326356221445367133271958375798088350587817966390021082924122322621635687775325677158394714044356852489350530339926527334843762933075425870780010358838296108179073735084189560997222261973170469403371017667139302904425235800700389626242339763391588052694912470921008842459564204534000688115202764921141372629345213727775126077560633656612484128950350759146471467728292335666402631045889956718877, public_e = 65537}+readRsaPublicKey :: BS.ByteString -> Maybe PublicKey+readRsaPublicKey bs =+    case readPubKeyFileFromMemory bs of+          [(PubKeyRSA k)] -> Just k+          _                -> Nothing+ -- | Convert the `NominalDiffTime` into an IntDate. Returns a Nothing if the -- argument is invalid (e.g. the NominalDiffTime must be convertible into a -- positive Integer representing the seconds since epoch).@@ -485,20 +515,30 @@  -- ================================================================================= -calculateDigest :: Signer -> T.Text -> T.Text-calculateDigest (HMACSecret key) msg =+calculateDigest :: EncodeSigner -> T.Text -> T.Text+calculateDigest (EncodeHMACSecret key) msg =     TE.decodeUtf8 $ convertToBase Base64URLUnpadded (hmac key (TE.encodeUtf8 msg) :: HMAC SHA256) -calculateDigest (RSAPrivateKey key) msg = TE.decodeUtf8+calculateDigest (EncodeRSAPrivateKey key) msg = TE.decodeUtf8     $ convertToBase Base64URLUnpadded     $ sign'     $ TE.encodeUtf8 msg   where     sign' :: BS.ByteString -> BS.ByteString-    sign' bs = case sign Nothing (Just SHA256) key bs of+    sign' bs = case RSA.sign Nothing (Just SHA256) key bs of         Right sig -> sig         Left  _   -> error "impossible"  -- This function can only fail with @SignatureTooLong@,                                          -- which is impossible because we use a hash.++verifyDigest :: VerifySigner -> Signature -> T.Text -> Bool+verifyDigest (VerifyHMACSecret key) unverifiedSig msg = unverifiedSig == Signature (calculateDigest (EncodeHMACSecret key) msg)+verifyDigest (VerifyRSAPrivateKey pk) unverifiedSig msg = unverifiedSig == Signature (calculateDigest (EncodeRSAPrivateKey pk) msg)+verifyDigest (VerifyRSAPublicKey pk) (Signature base64Sig) msg =+  let+    decodedSig =+      convertFromBase Base64URLUnpadded (TE.encodeUtf8 base64Sig)+  in+    either (pure False) (RSA.verify (Just SHA256) pk (TE.encodeUtf8 msg)) decodedSig  -- ================================================================================= 
stack.yaml view
@@ -3,3 +3,5 @@ extra-deps:   - aeson-2.0.0.0   - lens-aeson-1.1.2+  - cryptostore-0.2.1.0+  - doctest-0.20.0
tests/src/Web/JWTTests.hs view
@@ -12,16 +12,19 @@ import           Test.Tasty.TH import           Test.Tasty.HUnit import           Test.Tasty.QuickCheck-import qualified Test.QuickCheck as QC+import qualified Test.QuickCheck       as QC import qualified Data.Map              as Map import qualified Data.Text             as T import qualified Data.Text.Lazy        as TL-import qualified Data.ByteString as BS+import qualified Data.ByteString       as BS import           Data.Aeson.Types import           Data.Maybe import           Data.String (fromString) import           Data.Time import           Web.JWT+import qualified Crypto.PubKey.RSA as RSA+import qualified Crypto.Random.Types as CT+import qualified Data.ByteArray as BA  defaultTestGroup :: TestTree defaultTestGroup = $(testGroupGenerator)@@ -57,13 +60,13 @@ case_verify = do     -- Generated with ruby-jwt     let input = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzb21lIjoicGF5bG9hZCJ9.Joh1R2dYzkRvDkqv3sygm5YyK8Gi4ShZqbhK2gxcs2U"-        mVerified = verify (hmacSecret "secret") =<< decode input+        mVerified = verify (toVerify . hmacSecret $ "secret") =<< decode input     True @=? isJust mVerified  case_decodeAndVerifyJWT = do     -- Generated with ruby-jwt     let input = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzb21lIjoicGF5bG9hZCJ9.Joh1R2dYzkRvDkqv3sygm5YyK8Gi4ShZqbhK2gxcs2U"-        mJwt = decodeAndVerifySignature (hmacSecret "secret") input+        mJwt = decodeAndVerifySignature (toVerify . hmacSecret $ "secret") input     True @=? isJust mJwt     let (Just verified) = mJwt     Just HS256 @=? alg (header verified)@@ -89,13 +92,13 @@             }     -}     let input = "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpc3MiOiJodHRwczovL2p3dC1pZHAuZXhhbXBsZS5jb20iLCJzdWIiOiJtYWlsdG86bWlrZUBleGFtcGxlLmNvbSIsIm5iZiI6MTQyNTk4MDc1NSwiZXhwIjoxNDI1OTg0MzU1LCJpYXQiOjE0MjU5ODA3NTUsImp0aSI6ImlkMTIzNDU2IiwidHlwIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9yZWdpc3RlciJ9."-        mJwt = decodeAndVerifySignature (hmacSecret "secretkey") input+        mJwt = decodeAndVerifySignature (toVerify . hmacSecret $ "secretkey") input     False @=? isJust mJwt  case_decodeAndVerifyJWTFailing = do     -- Generated with ruby-jwt, modified to be invalid     let input = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzb21lIjoicGF5bG9hZCJ9.Joh1R2dYzkRvDkqv3sygm5YyK8Gi4ShZqbhK2gxcs2u"-        mJwt = decodeAndVerifySignature (hmacSecret "secret") input+        mJwt = decodeAndVerifySignature (toVerify . hmacSecret $ "secret") input     False @=? isJust mJwt  case_decodeInvalidInput = do@@ -105,7 +108,7 @@  case_decodeAndVerifySignatureInvalidInput = do     let inputs = ["", "a.", "a.b"]-        result = map (decodeAndVerifySignature (hmacSecret "secret")) inputs+        result = map (decodeAndVerifySignature (toVerify . hmacSecret $ "secret")) inputs     True @=? all isNothing result  case_encodeJWTNoMac = do@@ -162,8 +165,8 @@       , iat = numericDate now       , unregisteredClaims = ClaimsMap $ Map.fromList [("http://example.com/is_root", Bool True)]     }-    let secret' = hmacSecret "secret"-        jwt = decodeAndVerifySignature secret' $ encodeSigned secret' mempty cs+    let secret' =  hmacSecret "secret"+        jwt = decodeAndVerifySignature (toVerify secret') $ encodeSigned secret' mempty cs     Just cs @=? fmap claims jwt  case_encodeDecodeJWTClaimsSetWithSingleAud = do@@ -174,7 +177,7 @@           , iat = numericDate now         }     let secret' = hmacSecret "secret"-        jwt = decodeAndVerifySignature secret' $ encodeSigned secret' mempty cs+        jwt = decodeAndVerifySignature (toVerify secret') $ encodeSigned secret' mempty cs     Just cs @=? fmap claims jwt  case_encodeDecodeJWTClaimsSetWithMultipleAud = do@@ -185,7 +188,7 @@           , iat = numericDate now         }     let secret' = hmacSecret "secret"-        jwt = decodeAndVerifySignature secret' $ encodeSigned secret' mempty cs+        jwt = decodeAndVerifySignature (toVerify secret') $ encodeSigned secret' mempty cs     Just cs @=? fmap claims jwt  case_encodeDecodeJWTClaimsSetBinarySecret = do@@ -195,8 +198,8 @@           , iat = numericDate now         }     secretKey <- BS.readFile "tests/jwt.secret.1"-    let secret' = HMACSecret secretKey-        jwt = decodeAndVerifySignature secret' $ encodeSigned secret' mempty cs+    let secret' = EncodeHMACSecret secretKey+        jwt = decodeAndVerifySignature (toVerify secret') $ encodeSigned secret' mempty cs     Just cs @=? fmap claims jwt  prop_stringOrURIProp = f@@ -218,15 +221,37 @@  prop_encode_decode_binary_secret = f     where f :: BS.ByteString -> JWTClaimsSet -> Bool-          f binary claims' = let Just unverified = (decode $ encodeSigned (HMACSecret binary) mempty claims')+          f binary claims' = let Just unverified = (decode $ encodeSigned (EncodeHMACSecret binary) mempty claims')                           in claims unverified == claims'  prop_encode_decode_verify_signature = f     where f :: T.Text -> JWTClaimsSet -> Bool           f key' claims' = let key = hmacSecret key'-                               Just verified = (decodeAndVerifySignature key $ encodeSigned key mempty claims')+                               Just verified = (decodeAndVerifySignature (toVerify key) $ encodeSigned key mempty claims')                            in claims verified == claims' +-- Generating a keypair takes over a second. Let's only do this a few times.+prop_rsa_verify_with_public_key = withMaxSuccess 20 f+    where f :: Keypair -> JWTClaimsSet -> Bool+          f kp claims' = let encodeSigner = EncodeRSAPrivateKey . kpPrivate $ kp+                             verifySigner = VerifyRSAPublicKey . kpPublic $ kp+                             signedToken = encodeSigned encodeSigner mempty claims'+                         in isJust $ decodeAndVerifySignature verifySigner signedToken++data Keypair = Keypair+    { kpPrivate :: RSA.PrivateKey+    , kpPublic :: RSA.PublicKey+    } deriving (Show)++instance Arbitrary (Keypair) where+    arbitrary = do+        (pubKey, privateKey) <- RSA.generate 256 3+        return $ Keypair { kpPrivate = privateKey, kpPublic = pubKey }++instance CT.MonadRandom Gen where+    getRandomBytes size = do+        bytes <- vector size+        return . BA.pack $ bytes  instance Arbitrary JWTClaimsSet where     arbitrary = JWTClaimsSet <$> arbitrary