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 +6/−0
- Jose/Internal/Crypto.hs +15/−14
- Jose/Jwe.hs +28/−22
- Jose/Jwk.hs +28/−1
- Jose/Jwt.hs +1/−1
- Jose/Types.hs +19/−2
- jose-jwt.cabal +4/−3
- tests/Tests/JweSpec.hs +33/−1
- tests/Tests/JwkSpec.hs +14/−13
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