packages feed

jose-jwt 0.5 → 0.6

raw patch · 9 files changed

+148/−57 lines, 9 filesdep +old-localedep ~aesondep ~timePVP ok

version bump matches the API change (PVP)

Dependencies added: old-locale

Dependency ranges changed: aeson, time

API changes (from Hackage documentation)

- Jose.Internal.Crypto: decryptPayload :: Enc -> ByteString -> ByteString -> ByteString -> ByteString -> ByteString -> Either JwtError ByteString
+ Jose.Internal.Crypto: decryptPayload :: MonadError JwtError m => Enc -> ByteString -> ByteString -> ByteString -> ByteString -> ByteString -> m ByteString
- Jose.Internal.Crypto: generateCmkAndIV :: CPRG g => g -> Enc -> (ByteString, ByteString, g)
+ Jose.Internal.Crypto: generateCmkAndIV :: CPRG g => g -> Enc -> ((ByteString, ByteString), g)

Files

CHANGELOG.md view
@@ -1,3 +1,9 @@+0.6+---++* Change KeyId type to allow use of a UTCTime string for the identifier.+* Internal crypto fixes to prevent exceptions from external libraries.+ 0.5 --- 
Jose/Internal/Crypto.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE OverloadedStrings, FlexibleContexts #-} {-# OPTIONS_HADDOCK prune #-}  -- | Internal functions for encrypting and signing / decrypting@@ -22,6 +22,7 @@  import           Control.Applicative import           Crypto.Cipher.Types (AuthTag(..))+import           Control.Monad.Error import           Crypto.Number.Serialize (os2ip) import qualified Crypto.PubKey.ECC.ECDSA as ECDSA import qualified Crypto.PubKey.RSA as RSA@@ -124,8 +125,8 @@ generateCmkAndIV :: CPRG g                  => g   -- ^ The random number generator                  -> Enc -- ^ The encryption algorithm to be used-                 -> (B.ByteString, B.ByteString, g) -- ^ The key, IV and generator-generateCmkAndIV g e = (cmk, iv, g'')+                 -> ((B.ByteString, B.ByteString), g) -- ^ The key, IV and generator+generateCmkAndIV g e = ((cmk, iv), g'')   where     (cmk, g') = cprgGenerate (keySize e) g     (iv, g'') = cprgGenerate (ivSize e) g'  -- iv for aes gcm or cbc@@ -162,7 +163,7 @@            -> RSA.PrivateKey                -- ^ The decryption key            -> B.ByteString                  -- ^ The encrypted content            -> Either JwtError B.ByteString  -- ^ The decrypted key-rsaDecrypt blinder a rsaKey jweKey = either (const $ Left BadCrypto) Right $ decrypt rsaKey jweKey+rsaDecrypt blinder a rsaKey jweKey = either (const $ throwError BadCrypto) return $ decrypt rsaKey jweKey   where     decrypt = case a of         RSA1_5   -> PKCS15.decrypt blinder@@ -171,16 +172,15 @@ oaepParams :: OAEP.OAEPParams oaepParams = OAEP.defaultOAEPParams (hashFunction hashDescrSHA1) --- TODO: Need to check key length and IV are is valid for enc.- -- | Decrypt an AES encrypted message.-decryptPayload :: Enc        -- ^ Encryption algorithm+decryptPayload :: MonadError JwtError m+               => Enc        -- ^ Encryption algorithm                -> ByteString -- ^ Content management key                -> ByteString -- ^ IV                -> ByteString -- ^ Additional authentication data                -> ByteString -- ^ The integrity protection value to be checked                -> ByteString -- ^ The encrypted JWT payload-               -> Either JwtError ByteString+               -> m ByteString decryptPayload e cek iv aad sig ct = do     (plaintext, tag) <- case e of         A128GCM        -> decryptedGCM@@ -189,11 +189,12 @@         A256CBC_HS512  -> decryptedCBC 32 hashDescrSHA512     if tag == AuthTag sig       then return plaintext-      else Left BadSignature+      else throwError BadSignature   where-    decryptedGCM = Right $ AES.decryptGCM (AES.initAES cek) iv aad ct+    decryptedGCM = return $ AES.decryptGCM (AES.initAES cek) iv aad ct      decryptedCBC l h = do+      unless (B.length ct `mod` 16 == 0) $ throwError BadCrypto       let (macKey, encKey) = B.splitAt (B.length cek `div` 2) cek       let al = fromIntegral (B.length aad) * 8 :: Word64       plaintext <- unpad $ AES.decryptCBC (AES.initAES encKey) iv ct@@ -222,11 +223,11 @@ authTag :: Int -> HashDescr -> ByteString -> ByteString -> AuthTag authTag l h k m = AuthTag $ B.take l $ hmac (hashFunction h) 64 k m -unpad :: ByteString -> Either JwtError ByteString+unpad :: MonadError JwtError m => ByteString -> m ByteString unpad bs-    | padLen > 16 || padLen /= B.length padding = Left BadCrypto-    | B.any (/= padByte) padding = Left BadCrypto-    | otherwise = Right pt+    | padLen > 16 || padLen /= B.length padding = throwError BadCrypto+    | B.any (/= padByte) padding = throwError BadCrypto+    | otherwise = return pt   where     len     = B.length bs     padByte = B.last bs
Jose/Jwe.hs view
@@ -23,7 +23,9 @@  import Control.Arrow (first) import Crypto.Cipher.Types (AuthTag(..))+import Control.Error import Crypto.PubKey.RSA (PrivateKey(..), PublicKey(..), generateBlinder, private_pub)+import Control.Monad.State.Strict import Crypto.Random.API (CPRG) import Data.ByteString (ByteString) import qualified Data.ByteString as B@@ -75,39 +77,43 @@     a   = jweAlg h     e   = jweEnc h     hdr = encodeHeader h-    (cmk, iv, rng') = generateCmkAndIV rng e+    ((cmk, iv), rng') = generateCmkAndIV rng e     (jweKey, rng'') = rsaEncrypt rng' a pubKey cmk     aad = B64.encode hdr     (ct, AuthTag sig) = encryptPayload e cmk iv aad claims     jwe = B.intercalate "." $ map B64.encode [hdr, jweKey, iv, ct, sig] - -- | Decrypts a JWE. rsaDecode :: CPRG g           => g           -> PrivateKey               -- ^ Decryption key           -> ByteString               -- ^ The encoded JWE           -> (Either JwtError Jwe, g) -- ^ The decoded JWT, unless an error occurs-rsaDecode rng pk jwt = (decode blinder, rng')-  where-    (blinder, rng') = generateBlinder rng (public_n $ private_pub pk)+rsaDecode rng pk jwt = flip runState rng $ runEitherT $ do+    blinder <- state $ \g -> generateBlinder g (public_n $ private_pub pk) -    decode b = do-        checkDots-        let components = BC.split '.' jwt-        let aad = head components-        [h, ek, iv, payload, sig] <- mapM B64.decode components-        hdr <- case parseHeader h of-            Right (JweH jweHdr) -> return jweHdr-            Right (JwsH _)      -> Left (BadHeader "Header is for a JWS")-            Right UnsecuredH    -> Left (BadHeader "Header is for an unsecured JWT")-            Left e              -> Left e-        let alg = jweAlg hdr-        cek    <- rsaDecrypt (Just b) alg pk ek-        claims <- decryptPayload (jweEnc hdr) cek iv aad sig payload-        return (hdr, claims)+    checkDots+    let components = BC.split '.' jwt+    let aad = head components+    [h, ek, providedIv, payload, sig] <- mapM B64.decode components+    hdr <- case parseHeader h of+        Right (JweH jweHdr) -> return jweHdr+        Right (JwsH _)      -> left (BadHeader "Header is for a JWS")+        Right UnsecuredH    -> left (BadHeader "Header is for an unsecured JWT")+        Left e              -> left e+    let alg = jweAlg hdr+        enc = jweEnc hdr+    (dummyCek, dummyIv) <- state $ \g -> generateCmkAndIV g enc+    let decryptedCek = either (const dummyCek) id $ rsaDecrypt (Just blinder) alg pk ek+        cek = if B.length decryptedCek == B.length dummyCek+                then decryptedCek+                else dummyCek+        iv  = if B.length providedIv == B.length dummyIv+                 then providedIv+                 else dummyIv+    claims <- decryptPayload enc cek iv aad sig payload+    return (hdr, claims) -    checkDots = case BC.count '.' jwt of-                    4 -> Right ()-                    _ -> Left $ BadDots 4+  where+    checkDots = unless (BC.count '.' jwt == 4) $ left (BadDots 4) 
Jose/Jwk.hs view
@@ -7,14 +7,20 @@     , KeyId     , Jwk (..)     , JwkSet (..)+    , isPublic+    , isPrivate+    , jwkId+    , jwkUse     , canDecodeJws     , canDecodeJwe     , canEncodeJws     , canEncodeJwe+    , generateRsaKeyPair     ) where  import           Control.Applicative (pure)+import           Crypto.Random (CPRG) import qualified Crypto.PubKey.RSA as RSA import qualified Crypto.PubKey.ECC.ECDSA as ECDSA import qualified Crypto.Types.PubKey.ECC as ECC@@ -55,6 +61,27 @@     { keys :: [Jwk]     } deriving (Show, Eq, Generic) +generateRsaKeyPair :: (CPRG g)+    => g+    -> Int+    -> KeyId+    -> KeyUse+    -> Maybe Alg+    -> ((Jwk, Jwk), g)+generateRsaKeyPair rng nBytes id' kuse kalg =+    let ((kPub, kPr), rng') = RSA.generate rng nBytes 65537+    in  ((RsaPublicJwk kPub (Just id') (Just kuse) kalg, RsaPrivateJwk kPr (Just id') (Just kuse) kalg), rng')++isPublic :: Jwk -> Bool+isPublic RsaPublicJwk {} = True+isPublic EcPublicJwk  {} = True+isPublic _ = False++isPrivate :: Jwk -> Bool+isPrivate RsaPrivateJwk {} = True+isPrivate EcPrivateJwk  {} = True+isPrivate _ = False+ canDecodeJws :: JwsHeader -> Jwk -> Bool canDecodeJws hdr jwk = jwkUse jwk /= Just Enc &&     keyIdCompatible (jwsKid hdr) jwk &&@@ -292,7 +319,7 @@     , y   :: Maybe JwkBytes     , use :: Maybe KeyUse     , alg :: Maybe Alg-    , kid :: Maybe Text+    , kid :: Maybe KeyId     , x5u :: Maybe Text     , x5c :: Maybe [Text]     , x5t :: Maybe Text
Jose/Jwt.hs view
@@ -17,7 +17,7 @@ -- >>> let (Right (Jwt jwtEncoded), g')  = encode g [jwk] (JwsEncoding RS256) (Claims "public claims") -- >>> let (Right jwtDecoded, g'') = Jose.Jwt.decode g' [jwk] (Just (JwsEncoding RS256)) jwtEncoded -- >>> jwtDecoded--- Jws (JwsHeader {jwsAlg = RS256, jwsTyp = Nothing, jwsCty = Nothing, jwsKid = Just "mykey"},"public claims")+-- Jws (JwsHeader {jwsAlg = RS256, jwsTyp = Nothing, jwsCty = Nothing, jwsKid = Just (KeyId "mykey")},"public claims")  module Jose.Jwt     ( module Jose.Types
Jose/Types.hs view
@@ -14,7 +14,7 @@     , JwtError (..)     , IntDate (..)     , Payload (..)-    , KeyId+    , KeyId (..)     , parseHeader     , encodeHeader     , defJwsHdr@@ -30,12 +30,15 @@ import qualified Data.ByteString.Lazy as BL import qualified Data.HashMap.Strict as H import Data.Int (Int64)+import Data.Time.Clock (UTCTime) import Data.Time.Clock.POSIX+import Data.Time.Format import Data.Text (Text) import qualified Data.Text as T import qualified Data.Text.Encoding as TE import Data.Vector (singleton) import GHC.Generics+import System.Locale (defaultTimeLocale)  import Jose.Jwa (JweAlg(..), JwsAlg (..), Enc(..)) @@ -69,8 +72,22 @@                | UnsecuredH                  deriving (Show) -type KeyId   = Text+data KeyId+    = KeyId    Text+    | UTCKeyId UTCTime+      deriving (Eq, Show, Ord) +instance ToJSON KeyId+  where+    toJSON (KeyId t)    = toJSON t+    toJSON (UTCKeyId t) = toJSON t++instance FromJSON KeyId+  where+    parseJSON = withText "KeyId" $ \t ->+        case parseTime defaultTimeLocale "%FT%T%QZ" (T.unpack t) of+            Just d -> pure (UTCKeyId d)+            _      -> pure (KeyId t)  -- | Header content for a JWS. data JwsHeader = JwsHeader {
jose-jwt.cabal view
@@ -1,5 +1,5 @@ Name:               jose-jwt-Version:            0.5+Version:            0.6 Synopsis:           JSON Object Signing and Encryption Library Homepage:           http://github.com/tekul/jose-jwt Bug-Reports:        http://github.com/tekul/jose-jwt/issues@@ -59,8 +59,9 @@                     , crypto-random >= 0.0.7                     , crypto-numbers >= 0.2                     , cipher-aes >= 0.2.6-                    , errors >= 1.4-                    , aeson >= 0.7+                    , errors >= 1.4 && < 2.0+                    , aeson >= 0.8.1.0+                    , old-locale                     , text  >= 0.11                     , time  >= 1.4                     , unordered-containers >= 0.2
tests/Tests/JweSpec.hs view
@@ -9,6 +9,7 @@ import Data.Either.Combinators import Data.Word (Word8) import qualified Data.ByteString as B+import qualified Data.ByteString.Char8 as BC import Test.Hspec import Test.HUnit hiding (Test) import Test.QuickCheck@@ -38,7 +39,7 @@        it "generates the expected IV and CMK from the RNG" $ do         let g = RNG $ B.append a1cek a1iv-        generateCmkAndIV g A256GCM @?= (a1cek, a1iv, RNG "")+        generateCmkAndIV g A256GCM @?= ((a1cek, a1iv), RNG "")        it "generates the expected RSA-encrypted content key" $ do         let g = RNG a1oaepSeed@@ -66,6 +67,22 @@             Just  k2 = decodeStrict' a2jwk         fst (decode blinderRNG [k2, k1] (Just $ JweEncoding RSA_OAEP A256GCM) a1) @?= (Right $ Jwe (a1Header, a1Payload)) +      it "a truncated CEK returns BadSignature" $ do+        let [hdr, _, iv, payload, tag] = BC.split '.' a1+            (newEk, _) = rsaEncrypt blinderRNG RSA_OAEP a1PubKey (B.tail a1cek)+        fst (Jwe.rsaDecode blinderRNG a1PrivKey (B.intercalate "." [hdr, B64.encode newEk, iv, payload, tag])) @?= Left BadSignature++      it "a truncated payload returns BadSignature" $ do+        let [hdr, ek, iv, payload, tag] = BC.split '.' a1+            Right ct = B64.decode payload+        fst (Jwe.rsaDecode blinderRNG a1PrivKey (B.intercalate "." [hdr, ek, iv, B64.encode (B.tail ct), tag])) @?= Left BadSignature++      it "a truncated IV returns BadSignature" $ do+        let (fore, aft) = BC.breakSubstring (B64.encode a1iv) a1+            newIv = B64.encode (B.tail a1iv)+        fst (Jwe.rsaDecode blinderRNG a1PrivKey (B.concat [fore, newIv, aft])) @?= Left BadSignature++     context "when using JWE Appendix 2 data" $ do       let a2Header = defJweHdr {jweAlg = RSA1_5, jweEnc = A128CBC_HS256}       let aad = B64.encode . encodeHeader $ a2Header@@ -86,6 +103,21 @@        it "decodes the JWT to the expected header and payload" $         fst (Jwe.rsaDecode blinderRNG a2PrivKey a2) @?= Right (a2Header, a2Payload)++      it "a truncated CEK returns BadCrypto" $ do+        let [hdr, _, iv, payload, tag] = BC.split '.' a2+            (newEk, _) = rsaEncrypt blinderRNG RSA1_5 a2PubKey (B.tail a2cek)+        fst (Jwe.rsaDecode blinderRNG a2PrivKey (B.intercalate "." [hdr, B64.encode newEk, iv, payload, tag])) @?= Left BadCrypto++      it "a truncated payload returns BadCrypto" $ do+        let [hdr, ek, iv, payload, tag] = BC.split '.' a2+            Right ct = B64.decode payload+        fst (Jwe.rsaDecode blinderRNG a2PrivKey (B.intercalate "." [hdr, ek, iv, B64.encode (B.tail ct), tag])) @?= Left BadCrypto++      it "a truncated IV returns BadSignature" $ do+        let (fore, aft) = BC.breakSubstring (B64.encode a2iv) a2+            newIv = B64.encode (B.tail a2iv)+        fst (Jwe.rsaDecode blinderRNG a2PrivKey (B.concat [fore, newIv, aft])) @?= Left BadSignature      context "when used with quickcheck" $ do       it "padded msg is always a multiple of 16" $ property $
tests/Tests/JwkSpec.hs view
@@ -11,7 +11,7 @@ import Crypto.Types.PubKey.ECDSA import Crypto.Types.PubKey.ECC -import Jose.Jwt (defJwsHdr, JwsHeader(..))+import Jose.Jwt (defJwsHdr, JwsHeader(..), KeyId(..)) import Jose.Jwk import Jose.Jwa @@ -26,9 +26,9 @@         , {"use": "enc", "crv": "P-256", "kty": "EC", "y": "zcOqE_LYsPTf7a9FOFpJiwK2ZQuUmoNLdsY7BRTICN0", "x": "6eXHDpNoiUaAR5Cle6rfmrVgksSagyi8fzvLF1kedKc", "kid": "a3"}         , {"kty": "oct", "k":"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow", "kid":"HMAC key used in JWS A.1 example"}         , {"kty":"EC", "crv":"P-256", "x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", "y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", "use":"enc", "kid":"1"}-        , {"kty":"RSA", "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw", "e":"AQAB", "alg":"RS256", "kid":"2011-04-29"}+        , {"kty":"RSA", "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw", "e":"AQAB", "alg":"RS256", "kid":"2015-05-16T18:00:14.259Z"}         , {"kty":"EC", "crv":"P-256", "x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", "y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", "d":"870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE", "use":"enc", "kid":"1"}-        , {"kty":"RSA", "n":"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw", "e":"AQAB", "d":"X4cTteJY_gn4FYPsXB8rdXix5vwsg1FLN5E3EaG6RJoVH-HLLKD9M7dx5oo7GURknchnrRweUkC7hT5fJLM0WbFAKNLWY2vv7B6NqXSzUvxT0_YSfqijwp3RTzlBaCxWp4doFk5N2o8Gy_nHNKroADIkJ46pRUohsXywbReAdYaMwFs9tv8d_cPVY3i07a3t8MN6TNwm0dSawm9v47UiCl3Sk5ZiG7xojPLu4sbg1U2jx4IBTNBznbJSzFHK66jT8bgkuqsk0GjskDJk19Z4qwjwbsnn4j2WBii3RL-Us2lGVkY8fkFzme1z0HbIkfz0Y6mqnOYtqc0X4jfcKoAC8Q", "p":"83i-7IvMGXoMXCskv73TKr8637FiO7Z27zv8oj6pbWUQyLPQBQxtPVnwD20R-60eTDmD2ujnMt5PoqMrm8RfmNhVWDtjjMmCMjOpSXicFHj7XOuVIYQyqVWlWEh6dN36GVZYk93N8Bc9vY41xy8B9RzzOGVQzXvNEvn7O0nVbfs", "q":"3dfOR9cuYq-0S-mkFLzgItgMEfFzB2q3hWehMuG0oCuqnb3vobLyumqjVZQO1dIrdwgTnCdpYzBcOfW5r370AFXjiWft_NGEiovonizhKpo9VVS78TzFgxkIdrecRezsZ-1kYd_s1qDbxtkDEgfAITAG9LUnADun4vIcb6yelxk", "dp":"G4sPXkc6Ya9y8oJW9_ILj4xuppu0lzi_H7VTkS8xj5SdX3coE0oimYwxIi2emTAue0UOa5dpgFGyBJ4c8tQ2VF402XRugKDTP8akYhFo5tAA77Qe_NmtuYZc3C3m3I24G2GvR5sSDxUyAN2zq8Lfn9EUms6rY3Ob8YeiKkTiBj0", "dq":"s9lAH9fggBsoFR8Oac2R_E2gw282rT2kGOAhvIllETE1efrA6huUUvMfBcMpn8lqeW6vzznYY5SSQF7pMdC_agI3nG8Ibp1BUb0JUiraRNqUfLhcQb_d9GF4Dh7e74WbRsobRonujTYN1xCaP6TO61jvWrX-L18txXw494Q_cgk", "qi":"GyM_p6JrXySiz1toFgKbWV-JdI3jQ4ypu9rbMWx3rQJBfmt0FoYzgUIZEVFEcOqwemRN81zoDAaa-Bk0KWNGDjJHZDdDmFhW3AN7lI-puxk_mHZGJ11rxyR8O55XLSe3SPmRfKwZI6yU24ZxvQKFYItdldUKGzO6Ia6zTKhAVRU", "alg":"RS256", "kid":"2011-04-29"}+        , {"kty":"RSA", "n":"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw", "e":"AQAB", "d":"X4cTteJY_gn4FYPsXB8rdXix5vwsg1FLN5E3EaG6RJoVH-HLLKD9M7dx5oo7GURknchnrRweUkC7hT5fJLM0WbFAKNLWY2vv7B6NqXSzUvxT0_YSfqijwp3RTzlBaCxWp4doFk5N2o8Gy_nHNKroADIkJ46pRUohsXywbReAdYaMwFs9tv8d_cPVY3i07a3t8MN6TNwm0dSawm9v47UiCl3Sk5ZiG7xojPLu4sbg1U2jx4IBTNBznbJSzFHK66jT8bgkuqsk0GjskDJk19Z4qwjwbsnn4j2WBii3RL-Us2lGVkY8fkFzme1z0HbIkfz0Y6mqnOYtqc0X4jfcKoAC8Q", "p":"83i-7IvMGXoMXCskv73TKr8637FiO7Z27zv8oj6pbWUQyLPQBQxtPVnwD20R-60eTDmD2ujnMt5PoqMrm8RfmNhVWDtjjMmCMjOpSXicFHj7XOuVIYQyqVWlWEh6dN36GVZYk93N8Bc9vY41xy8B9RzzOGVQzXvNEvn7O0nVbfs", "q":"3dfOR9cuYq-0S-mkFLzgItgMEfFzB2q3hWehMuG0oCuqnb3vobLyumqjVZQO1dIrdwgTnCdpYzBcOfW5r370AFXjiWft_NGEiovonizhKpo9VVS78TzFgxkIdrecRezsZ-1kYd_s1qDbxtkDEgfAITAG9LUnADun4vIcb6yelxk", "dp":"G4sPXkc6Ya9y8oJW9_ILj4xuppu0lzi_H7VTkS8xj5SdX3coE0oimYwxIi2emTAue0UOa5dpgFGyBJ4c8tQ2VF402XRugKDTP8akYhFo5tAA77Qe_NmtuYZc3C3m3I24G2GvR5sSDxUyAN2zq8Lfn9EUms6rY3Ob8YeiKkTiBj0", "dq":"s9lAH9fggBsoFR8Oac2R_E2gw282rT2kGOAhvIllETE1efrA6huUUvMfBcMpn8lqeW6vzznYY5SSQF7pMdC_agI3nG8Ibp1BUb0JUiraRNqUfLhcQb_d9GF4Dh7e74WbRsobRonujTYN1xCaP6TO61jvWrX-L18txXw494Q_cgk", "qi":"GyM_p6JrXySiz1toFgKbWV-JdI3jQ4ypu9rbMWx3rQJBfmt0FoYzgUIZEVFEcOqwemRN81zoDAaa-Bk0KWNGDjJHZDdDmFhW3AN7lI-puxk_mHZGJ11rxyR8O55XLSe3SPmRfKwZI6yU24ZxvQKFYItdldUKGzO6Ia6zTKhAVRU", "alg":"RS256", "kid":"2015-05-16T18:00:14.259Z"}         ]    } |]@@ -37,7 +37,7 @@ spec :: Spec spec = do     let Success s@(JwkSet _) = fromJSON (Object keySet) :: Result JwkSet-    describe "JWK encoding and decoding" $ do+    describe "JWK encoding and decoding" $         it "decodes and encodes an entire key set successfully" $ do             let Just s' = decode' (encode s) :: Maybe JwkSet                 kss = keys s'@@ -50,17 +50,18 @@                 RsaPublicJwk  _ key6Id Nothing    a6   = kss !! 6                 EcPrivateJwk  _ key7Id (Just Enc) _ _  = kss !! 7                 RsaPrivateJwk _ _      Nothing    a8   = kss !! 8+                Success utcKeyId = fromJSON (String "2015-05-16T18:00:14.259Z")             length kss @?= 9             a0      @?= Nothing-            key0Id  @?= Just "a0"-            key1Id  @?= Just "a1"-            key2Id  @?= Just "a2"+            key0Id  @?= Just (KeyId "a0")+            key1Id  @?= Just (KeyId "a1")+            key2Id  @?= Just (KeyId "a2")             public_curve k @?= getCurveByName SEC_p256r1-            key3Id  @?= Just "a3"-            key4Id  @?= Just "HMAC key used in JWS A.1 example"-            key5Id  @?= Just "1"-            key6Id  @?= Just "2011-04-29"-            key7Id  @?= Just "1"+            key3Id  @?= Just (KeyId "a3")+            key4Id  @?= Just (KeyId "HMAC key used in JWS A.1 example")+            key5Id  @?= Just (KeyId "1")+            key6Id  @?= Just (UTCKeyId utcKeyId)+            key7Id  @?= Just (KeyId "1")             key0Use @?= Just Enc             key1Use @?= Just Sig             key2Use @?= Just Sig@@ -81,7 +82,7 @@             length jwks' @?= 3          it "finds one key for RS256 decoding with kid specified" $ do-            let jwks' = filter (canDecodeJws (defJwsHdr {jwsAlg = RS256, jwsKid = Just "a1"})) jwks+            let jwks' = filter (canDecodeJws (defJwsHdr {jwsAlg = RS256, jwsKid = Just (KeyId "a1")})) jwks             length jwks' @?= 1          it "finds an RS1_5 key for encoding" $ do