packages feed

jose-jwt 0.4.2 → 0.5

raw patch · 6 files changed

+62/−41 lines, 6 filesdep ~errorsPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: errors

API changes (from Hackage documentation)

+ Jose.Jwt: JweEncoding :: JweAlg -> Enc -> JwtEncoding
+ Jose.Jwt: JwsEncoding :: JwsAlg -> JwtEncoding
+ Jose.Jwt: data JwtEncoding
- Jose.Jwt: decode :: CPRG g => g -> [Jwk] -> ByteString -> (Either JwtError JwtContent, g)
+ Jose.Jwt: decode :: CPRG g => g -> [Jwk] -> Maybe JwtEncoding -> ByteString -> (Either JwtError JwtContent, g)
- Jose.Jwt: encode :: CPRG g => g -> [Jwk] -> Alg -> Maybe Enc -> Payload -> (Either JwtError Jwt, g)
+ Jose.Jwt: encode :: CPRG g => g -> [Jwk] -> JwtEncoding -> Payload -> (Either JwtError Jwt, g)

Files

CHANGELOG.md view
@@ -1,3 +1,8 @@+0.5+---++* Add JwtEncoding type. Changes API of `Jwt.encode` and `Jwt.decode`.+ 0.4.2 ----- 
Jose/Jwt.hs view
@@ -14,8 +14,8 @@ -- >>> g <- makeSystem -- >>> let jsonJwk = "{\"kty\":\"RSA\", \"kid\":\"mykey\", \"n\":\"ofgWCuLjybRlzo0tZWJjNiuSfb4p4fAkd_wWJcyQoTbji9k0l8W26mPddxHmfHQp-Vaw-4qPCJrcS2mJPMEzP1Pt0Bm4d4QlL-yRT-SFd2lZS-pCgNMsD1W_YpRPEwOWvG6b32690r2jZ47soMZo9wGzjb_7OMg0LOL-bSf63kpaSHSXndS5z5rexMdbBYUsLA9e-KXBdQOS-UTo7WTBEMa2R2CapHg665xsmtdVMTBQY4uDZlxvb3qCo5ZwKh9kG4LT6_I5IhlJH7aGhyxXFvUK-DWNmoudF8NAco9_h9iaGNj8q2ethFkMLs91kzk2PAcDTW9gb54h4FRWyuXpoQ\", \"e\":\"AQAB\", \"d\":\"Eq5xpGnNCivDflJsRQBXHx1hdR1k6Ulwe2JZD50LpXyWPEAeP88vLNO97IjlA7_GQ5sLKMgvfTeXZx9SE-7YwVol2NXOoAJe46sui395IW_GO-pWJ1O0BkTGoVEn2bKVRUCgu-GjBVaYLU6f3l9kJfFNS3E0QbVdxzubSu3Mkqzjkn439X0M_V51gfpRLI9JYanrC4D4qAdGcopV_0ZHHzQlBjudU2QvXt4ehNYTCBr6XCLQUShb1juUO1ZdiYoFaFQT5Tw8bGUl_x_jTj3ccPDVZFD9pIuhLhBOneufuBiB4cS98l2SR_RQyGWSeWjnczT0QU91p1DhOVRuOopznQ\"}" :: ByteString -- >>> let Just jwk = decodeStrict jsonJwk :: Maybe Jwk--- >>> let (Right (Jwt jwtEncoded), g')  = encode g [jwk] (Signed RS256) Nothing (Claims "public claims")--- >>> let (Right jwtDecoded, g'') = Jose.Jwt.decode g' [jwk] jwtEncoded+-- >>> 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") @@ -49,45 +49,45 @@  -- | Use the supplied JWKs to create a JWT. -- The list of keys will be searched to locate one which is--- consistent with the chosen algorithm.+-- consistent with the chosen encoding algorithms. -- encode :: (CPRG g)-       => g                          -- ^ Random number generator.+       => g                          -- ^ Random number generator        -> [Jwk]                      -- ^ The key or keys. At least one must be consistent with the chosen algorithm-       -> Alg                        -- ^ The JWS or JWE algorithm-       -> Maybe Enc                  -- ^ The payload encryption algorithm (if applicable)+       -> JwtEncoding                -- ^ The encoding algorithm(s) used to encode the payload        -> Payload                    -- ^ The payload (claims)        -> (Either JwtError Jwt, g)   -- ^ The encoded JWT, if successful-encode rng jwks alg enc msg = flip runState rng $ runEitherT $ case alg of-    Signed None -> do-        unless (isNothing enc) $ left (BadAlgorithm "Enc cannot be set for an unsecured JWT")-        case msg of-            Claims p -> return $ Jwt $ BC.intercalate "." [unsecuredHdr, B64.encode p]-            Nested _ -> left BadClaims-    Signed a    -> do-        unless (isNothing enc) $ left (BadAlgorithm "Enc cannot be set for a JWS")-        case filter (canEncodeJws a) jwks of-            []    -> left (KeyError "No matching key found for JWS algorithm")-            (k:_) -> hoistEither =<< state (\g -> Jws.jwkEncode g a k msg)-    Encrypted a -> do-        e <- hoistEither $ note (BadAlgorithm "Enc must be supplied for a JWE") enc-        case filter (canEncodeJwe a) jwks of-            []    -> left (KeyError "No matching key found for JWE algorithm")-            (k:_) -> hoistEither =<< state (\g -> Jwe.jwkEncode g a e k msg)+encode rng jwks encoding msg = flip runState rng $ runEitherT $ case encoding of+    JwsEncoding None -> case msg of+        Claims p -> return $ Jwt $ BC.intercalate "." [unsecuredHdr, B64.encode p]+        Nested _ -> left BadClaims+    JwsEncoding a    -> case filter (canEncodeJws a) jwks of+        []    -> left (KeyError "No matching key found for JWS algorithm")+        (k:_) -> hoistEither =<< state (\g -> Jws.jwkEncode g a k msg)+    JweEncoding a e -> case filter (canEncodeJwe a) jwks of+        []    -> left (KeyError "No matching key found for JWE algorithm")+        (k:_) -> hoistEither =<< state (\g -> Jwe.jwkEncode g a e k msg)   where     unsecuredHdr = B64.encode "{\"alg\":\"none\"}"   -- | Uses the supplied keys to decode a JWT. -- Locates a matching key by header @kid@ value where possible--- or by suitable key type.--- The JWK @use@ and @alg@ options are currently ignored.+-- or by suitable key type for the encoding algorithm.+--+-- The algorithm(s) used can be optionally be supplied for validation+-- by setting the @JwtEncoding@ parameter, in which case an error will+-- be returned if they don't match.+--+-- For unsecured tokens (with algorithm "none"), the expected algorithm+-- must be set to @Just (JwsEncoding None)@ or an error will be returned. decode :: CPRG g        => g                               -- ^ Random number generator. Only used for RSA blinding        -> [Jwk]                           -- ^ The keys to use for decoding+       -> Maybe JwtEncoding               -- ^ The expected encoding information        -> ByteString                      -- ^ The encoded JWT        -> (Either JwtError JwtContent, g) -- ^ The decoded JWT payload, if successful-decode rng keySet jwt = flip runState rng $ runEitherT $ do+decode rng keySet encoding jwt = flip runState rng $ runEitherT $ do     let components = BC.split '.' jwt     when (length components < 3) $ left $ BadDots 2     hdr <- B64.decode (head components) >>= hoistEither . parseHeader@@ -95,9 +95,15 @@     -- Now we have one or more suitable keys (or none for the unsecured case).     -- Try each in turn until successful     decodings <- case hdr of-        UnsecuredH -> B64.decode (components !! 1) >>= \p -> return [Just (Unsecured p)]-        JwsH _     -> mapM decodeWithJws ks-        JweH _     -> mapM decodeWithJwe ks+        UnsecuredH -> do+            unless (encoding == Just (JwsEncoding None)) $ left (BadAlgorithm "JWT is unsecured but expected 'alg' was not 'none'")+            B64.decode (components !! 1) >>= \p -> return [Just (Unsecured p)]+        JwsH h     -> do+            unless (isNothing encoding || encoding == Just (JwsEncoding (jwsAlg h))) $ left (BadAlgorithm "Expected 'alg' doesn't match JWS header")+            mapM decodeWithJws ks+        JweH h     -> do+            unless (isNothing encoding || encoding == Just (JweEncoding (jweAlg h) (jweEnc h))) $ left (BadAlgorithm "Expected encoding doesn't match JWE header")+            mapM decodeWithJwe ks     maybe (left $ KeyError "None of the keys was able to decode the JWT") (return . fromJust) $ find isJust decodings   where     decodeWithJws :: CPRG g => Jwk -> EitherT JwtError (State g) (Maybe JwtContent)@@ -142,4 +148,3 @@     -- TODO Move checks to JWK and support better error messages     checkKeys [] = left $ KeyError "No suitable key was found to decode the JWT"     checkKeys ks = return ks-
Jose/Types.hs view
@@ -10,6 +10,7 @@     , JwsHeader (..)     , JweHeader (..)     , JwtContent (..)+    , JwtEncoding (..)     , JwtError (..)     , IntDate (..)     , Payload (..)@@ -54,6 +55,14 @@  -- | A decoded JWT which can be either a JWE or a JWS, or an unsecured JWT. data JwtContent = Unsecured !ByteString | Jws !Jws | Jwe !Jwe deriving (Show, Eq)++-- | Defines the encoding information for a JWT.+--+-- Used for both encoding new JWTs and validating existing ones.+data JwtEncoding+    = JwsEncoding JwsAlg+    | JweEncoding JweAlg Enc+      deriving (Eq, Show)  data JwtHeader = JweH JweHeader                | JwsH JwsHeader
jose-jwt.cabal view
@@ -1,5 +1,5 @@ Name:               jose-jwt-Version:            0.4.2+Version:            0.5 Synopsis:           JSON Object Signing and Encryption Library Homepage:           http://github.com/tekul/jose-jwt Bug-Reports:        http://github.com/tekul/jose-jwt/issues
tests/Tests/JweSpec.hs view
@@ -41,7 +41,7 @@         generateCmkAndIV g A256GCM @?= (a1cek, a1iv, RNG "")        it "generates the expected RSA-encrypted content key" $ do-        let g = RNG $ a1oaepSeed+        let g = RNG a1oaepSeed         rsaEncrypt g RSA_OAEP a1PubKey a1cek @?= (a1jweKey, RNG "")        it "encrypts the payload to the expected ciphertext and authentication tag" $ do@@ -52,8 +52,8 @@         let g = RNG $ B.concat [a1cek, a1iv, a1oaepSeed]         Jwe.rsaEncode g RSA_OAEP A256GCM a1PubKey a1Payload @?= (Jwt a1, RNG "") -      it "decodes the JWT to the expected header and payload" $ do-        (fst $ Jwe.rsaDecode blinderRNG a1PrivKey a1) @?= Right (a1Header, a1Payload)+      it "decodes the JWT to the expected header and payload" $+        fst (Jwe.rsaDecode blinderRNG a1PrivKey a1) @?= Right (a1Header, a1Payload)        it "decodes the JWK to the correct RSA key values" $ do         let Right (Jwk.RsaPrivateJwk (RSA.PrivateKey pubKey d 0 0 0 0 0) _ _ _) = eitherDecode a1jwk@@ -64,7 +64,7 @@       it "decodes the JWT using the JWK" $ do         let Right k1 = eitherDecode a1jwk             Just  k2 = decodeStrict' a2jwk-        fst (decode blinderRNG [k2, k1] a1) @?= (Right $ Jwe (a1Header, a1Payload))+        fst (decode blinderRNG [k2, k1] (Just $ JweEncoding RSA_OAEP A256GCM) a1) @?= (Right $ Jwe (a1Header, a1Payload))      context "when using JWE Appendix 2 data" $ do       let a2Header = defJweHdr {jweAlg = RSA1_5, jweEnc = A128CBC_HS256}@@ -247,4 +247,3 @@  -- Decrypt, drop the 02 at the start and take the bytes up to the next 0 extractPKCS15Seed :: RSA.PrivateKey -> B.ByteString -> B.ByteString extractPKCS15Seed key ct = B.takeWhile (/= 0) . B.drop 2 $ dp Nothing key ct-
tests/Tests/JwsSpec.hs view
@@ -46,7 +46,7 @@          it "decodes the payload using the JWK" $ do           let Just k11 = decodeStrict' a11jwk-          fst (decode RNG [k11] a11) @?= fmap Jws a11decoded+          fst (decode RNG [k11] Nothing a11) @?= fmap Jws a11decoded          it "encodes/decodes using HS512" $           hmacRoundTrip HS512 a11Payload@@ -60,7 +60,7 @@          it "decodes the JWT to the expected header and payload with the JWK" $ do           let Just k21 = decodeStrict' a21jwk-          fst (decode RNG [k21] a21) @?= (Right $ Jws (defJwsHdr {jwsAlg = RS256}, a21Payload))+          fst (decode RNG [k21] (Just (JwsEncoding RS256)) a21) @?= (Right $ Jws (defJwsHdr {jwsAlg = RS256}, a21Payload))          it "encodes the payload to the expected JWT" $ do           let sign = either (error "Sign failed") id . RSAPKCS15.sign Nothing hashDescrSHA256 rsaPrivateKey@@ -79,11 +79,15 @@         let a31decoded = Right (defJwsHdr {jwsAlg = ES256}, a31Payload)         it "decodes the JWT to the expected header and payload" $ do           let Just k31 = decodeStrict' a31jwk-          fst (decode RNG [k31] a31) @?= fmap Jws a31decoded+          fst (decode RNG [k31] Nothing a31) @?= fmap Jws a31decoded -      context "when using an unsecured JWT" $+      context "when using an unsecured JWT" $ do+        it "returns an error if alg is unset" $+          fst (decode RNG [] Nothing jwt61) @?= Left (BadAlgorithm "JWT is unsecured but expected 'alg' was not 'none'")+        it "returns an error if alg is is not 'none'" $+          fst (decode RNG [] (Just (JwsEncoding RS256)) jwt61) @?= Left (BadAlgorithm "JWT is unsecured but expected 'alg' was not 'none'")         it "decodes the JWT to the expected header and payload" $-          fst (decode RNG [] jwt61) @?= Right (Unsecured jwt61Payload)+          fst (decode RNG [] (Just (JwsEncoding None)) jwt61) @?= Right (Unsecured jwt61Payload)   signWithHeader sign hdr payload = B.intercalate "." [hdrPayload, B64.encode $ sign hdrPayload]@@ -152,4 +156,3 @@     }  a11mac = hmac SHA256.hash 64 hmacKey-