jose-jwt 0.9.1 → 0.9.2
raw patch · 13 files changed
+64/−43 lines, 13 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
Files
- CHANGELOG.md +5/−0
- Jose/Internal/Crypto.hs +0/−1
- Jose/Internal/Parser.hs +0/−1
- Jose/Jwa.hs +0/−1
- Jose/Jwe.hs +4/−2
- Jose/Jwk.hs +45/−26
- Jose/Jws.hs +0/−1
- Jose/Jwt.hs +1/−0
- Jose/Types.hs +0/−2
- jose-jwt.cabal +1/−1
- tests/Tests/JweSpec.hs +4/−6
- tests/Tests/JwkSpec.hs +1/−2
- tests/jwks.json +3/−0
CHANGELOG.md view
@@ -1,3 +1,8 @@+0.9.2+-----++* Add `UnsupportedJwt` constructor to `Jwk` type which wraps the JSON value. This prevents failure loading keys which have `alg` or `kty` values which we don't support (see #31).+ 0.9.1 -----
Jose/Internal/Crypto.hs view
@@ -26,7 +26,6 @@ where -import Control.Applicative import Control.Monad (when, unless) import Crypto.Error import Crypto.Cipher.AES
Jose/Internal/Parser.hs view
@@ -16,7 +16,6 @@ ) where -import Control.Applicative import Data.Bifunctor (first) import Data.Aeson (eitherDecodeStrict') import Data.Attoparsec.ByteString (Parser)
Jose/Jwa.hs view
@@ -10,7 +10,6 @@ ) where -import Control.Applicative (pure) import Data.Aeson import Data.Text (Text) import Data.Tuple (swap)
Jose/Jwe.hs view
@@ -13,7 +13,8 @@ -- RSA anyone can send you a JWE if they have a copy of your public key. -- -- In the example below, we show encoding and decoding using a 2048 bit RSA key pair--- (256 bytes).+-- (256 bytes). If using RSA, use one of the @RSA_OAEP@ algorithms. @RSA1_5@ is+-- deprecated due to <https://robotattack.org/ known vulnerabilities>. -- -- >>> import Jose.Jwe -- >>> import Jose.Jwa@@ -106,7 +107,8 @@ e <- doDecode (rsaDecrypt (Just blinder) kPr) jwt return (Jwe e) SymmetricJwk kb _ _ _ -> fmap Jwe (doDecode (keyUnwrap (BA.convert kb)) jwt)- _ -> throwE $ KeyError "JWK cannot decode a JWE"+ UnsupportedJwk _ -> throwE (KeyError "Unsupported JWK cannot be used to decode JWE")+ _ -> throwE $ KeyError "This JWK cannot decode a JWE" doDecode :: MonadRandom m
Jose/Jwk.hs view
@@ -20,7 +20,6 @@ ) where -import Control.Applicative (pure) import Control.Monad (unless) import Crypto.Error (CryptoFailable(..)) import Crypto.Random (MonadRandom, getRandomBytes)@@ -30,12 +29,13 @@ import qualified Crypto.PubKey.Ed448 as Ed448 import qualified Crypto.PubKey.ECC.Types as ECC import Crypto.Number.Serialize-import Data.Aeson (genericToJSON, Value(..), FromJSON(..), ToJSON(..), withText)+import Data.Aeson (fromJSON, genericToJSON, Object, Result(..), Value(..), FromJSON(..), ToJSON(..), withText) import Data.Aeson.Types (Parser, Options (..), defaultOptions) import qualified Data.ByteArray as BA import Data.ByteString (ByteString) import qualified Data.ByteString as B-import Data.Maybe (isNothing)+import qualified Data.HashMap.Strict as H+import Data.Maybe (isNothing, fromMaybe) import Data.Text (Text) import qualified Data.Text.Encoding as TE import GHC.Generics (Generic)@@ -68,9 +68,10 @@ | Ed448PrivateJwk !Ed448.SecretKey !Ed448.PublicKey !(Maybe KeyId) | Ed448PublicJwk !Ed448.PublicKey !(Maybe KeyId) | SymmetricJwk !ByteString !(Maybe KeyId) !(Maybe KeyUse) !(Maybe Alg)+ | UnsupportedJwk Object deriving (Show, Eq) -data JwkSet = JwkSet+newtype JwkSet = JwkSet { keys :: [Jwk] } deriving (Show, Eq, Generic) @@ -208,30 +209,33 @@ EcPublicJwk _ keyId _ _ _ -> keyId EcPrivateJwk _ keyId _ _ _ -> keyId SymmetricJwk _ keyId _ _ -> keyId+ UnsupportedJwk _ -> Nothing jwkUse :: Jwk -> Maybe KeyUse jwkUse key = case key of- Ed25519PrivateJwk _ _ _ -> Just Sig+ Ed25519PrivateJwk {} -> Just Sig Ed25519PublicJwk _ _ -> Just Sig- Ed448PrivateJwk _ _ _ -> Just Sig+ Ed448PrivateJwk {} -> Just Sig Ed448PublicJwk _ _ -> Just Sig RsaPublicJwk _ _ u _ -> u RsaPrivateJwk _ _ u _ -> u EcPublicJwk _ _ u _ _ -> u EcPrivateJwk _ _ u _ _ -> u SymmetricJwk _ _ u _ -> u+ UnsupportedJwk _ -> Nothing jwkAlg :: Jwk -> Maybe Alg jwkAlg key = case key of- Ed25519PrivateJwk _ _ _ -> Just (Signed EdDSA)+ Ed25519PrivateJwk {} -> Just (Signed EdDSA) Ed25519PublicJwk _ _ -> Just (Signed EdDSA)- Ed448PrivateJwk _ _ _ -> Just (Signed EdDSA)+ Ed448PrivateJwk {} -> Just (Signed EdDSA) Ed448PublicJwk _ _ -> Just (Signed EdDSA) RsaPublicJwk _ _ _ a -> a RsaPrivateJwk _ _ _ a -> a EcPublicJwk _ _ _ a _ -> a EcPrivateJwk _ _ _ a _ -> a SymmetricJwk _ _ _ a -> a+ UnsupportedJwk _ -> Nothing newtype JwkBytes = JwkBytes {bytes :: ByteString} deriving (Show)@@ -288,20 +292,33 @@ toJSON (JwkBytes b) = String . TE.decodeUtf8 $ B64.encode b instance FromJSON Jwk where- parseJSON o@(Object _) = do- jwkData <- parseJSON o :: Parser JwkData- case createJwk jwkData of- Left err -> fail err- Right jwk -> return jwk+ parseJSON (Object k) = parseJwk k parseJSON _ = fail "Jwk must be a JSON object" +parseJwk :: Object -> Parser Jwk+parseJwk k =+ case (checkAlg, checkKty) of+ (Success _, Success _) -> do+ jwkData <- parseJSON (Object k) :: Parser JwkData+ case createJwk jwkData of+ Left err -> fail err+ Right jwk -> return jwk+ _ -> pure (UnsupportedJwk k)+ where+ algValue = fromMaybe Null (H.lookup "alg" k)+ -- kty is required so if it's missing here we do nothing and allow decoding to fail+ -- later+ ktyValue = fromMaybe Null (H.lookup "kty" k)+ checkAlg = fromJSON algValue :: Result (Maybe Alg)+ checkKty = fromJSON ktyValue :: Result (Maybe KeyType)+ instance ToJSON Jwk where- toJSON jwk = toJSON $ case jwk of+ toJSON jwk = case jwk of RsaPublicJwk pubKey mId mUse mAlg ->- createPubData pubKey mId mUse mAlg+ toJSON $ createPubData pubKey mId mUse mAlg RsaPrivateJwk privKey mId mUse mAlg -> let pubData = createPubData (RSA.private_pub privKey) mId mUse mAlg- in pubData+ in toJSON $ pubData { d = Just . JwkBytes . i2osp $ RSA.private_d privKey , p = i2b $ RSA.private_p privKey , q = i2b $ RSA.private_q privKey@@ -310,7 +327,7 @@ , qi = i2b $ RSA.private_qinv privKey } - Ed25519PrivateJwk kPr kPub kid_ -> defJwk+ Ed25519PrivateJwk kPr kPub kid_ -> toJSON $ defJwk { kty = Okp , crv = Just "Ed25519" , d = Just (JwkBytes (BA.convert kPr))@@ -318,30 +335,30 @@ , kid = kid_ } - Ed25519PublicJwk kPub kid_ -> defJwk+ Ed25519PublicJwk kPub kid_ -> toJSON $ defJwk { kty = Okp , crv = Just "Ed25519" , x = Just (JwkBytes (BA.convert kPub)) , kid = kid_ } - Ed448PrivateJwk kPr kPub kid_ -> defJwk+ Ed448PrivateJwk kPr kPub kid_ -> toJSON $ defJwk { kty = Okp- , crv = Just "Ed25519"+ , crv = Just "Ed448" , d = Just (JwkBytes (BA.convert kPr)) , x = Just (JwkBytes (BA.convert kPub)) , kid = kid_ } - Ed448PublicJwk kPub kid_ -> defJwk+ Ed448PublicJwk kPub kid_ -> toJSON $ defJwk { kty = Okp- , crv = Just "Ed25519"+ , crv = Just "Ed448" , x = Just (JwkBytes (BA.convert kPub)) , kid = kid_ } - SymmetricJwk bs mId mUse mAlg -> defJwk+ SymmetricJwk bs mId mUse mAlg -> toJSON $ defJwk { kty = Oct , k = Just $ JwkBytes bs , kid = mId@@ -349,7 +366,7 @@ , alg = mAlg } - EcPublicJwk pubKey mId mUse mAlg c -> defJwk+ EcPublicJwk pubKey mId mUse mAlg c -> toJSON $ defJwk { kty = Ec , x = fst (ecPoint pubKey) , y = snd (ecPoint pubKey)@@ -359,7 +376,7 @@ , crv = Just (ecCurveName c) } - EcPrivateJwk kp mId mUse mAlg c -> defJwk+ EcPrivateJwk kp mId mUse mAlg c -> toJSON $ defJwk { kty = Ec , x = fst (ecPoint (ECDSA.toPublicKey kp)) , y = snd (ecPoint (ECDSA.toPublicKey kp))@@ -369,6 +386,8 @@ , alg = mAlg , crv = Just (ecCurveName c) }++ UnsupportedJwk k -> Object k where i2b 0 = Nothing i2b i = Just . JwkBytes . i2osp $ i@@ -503,7 +522,7 @@ rsaPub nb eb = let m = os2ip $ bytes nb ex = os2ip $ bytes eb in RSA.PublicKey (rsaSize m 1) m ex- rsaSize m i = if (2 ^ (i * 8)) > m then i else rsaSize m (i+1)+ rsaSize m i = if 2 ^ (i * 8) > m then i else rsaSize m (i+1) ecPoint = do xb <- note "x is required for an EC key" x yb <- note "y is required for an EC key" y
Jose/Jws.hs view
@@ -28,7 +28,6 @@ ) where -import Control.Applicative import qualified Crypto.PubKey.ECC.ECDSA as ECDSA import qualified Crypto.PubKey.Ed25519 as Ed25519 import qualified Crypto.PubKey.Ed448 as Ed448
Jose/Jwt.hs view
@@ -118,6 +118,7 @@ EcPublicJwk kPub _ _ _ _ -> Jws.ecDecode kPub jwt EcPrivateJwk kPr _ _ _ _ -> Jws.ecDecode (ECDSA.toPublicKey kPr) jwt SymmetricJwk kb _ _ _ -> Jws.hmacDecode kb jwt+ UnsupportedJwk _ -> Left (KeyError "Unsupported JWKs cannot be used") decodeWithJwe :: MonadRandom m => Jwk -> ExceptT JwtError m (Maybe JwtContent) decodeWithJwe k = fmap (either (const Nothing) Just) (lift (Jwe.jwkDecode k jwt))
Jose/Types.hs view
@@ -22,9 +22,7 @@ ) where -import Control.Applicative import Data.Aeson-import Data.Aeson.Types import Data.Char (toUpper, toLower) import Data.ByteString (ByteString) import qualified Data.ByteString.Lazy as BL
jose-jwt.cabal view
@@ -1,5 +1,5 @@ Name: jose-jwt-Version: 0.9.1+Version: 0.9.2 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
@@ -4,13 +4,11 @@ module Tests.JweSpec where -import Control.Applicative-import Data.Aeson (decodeStrict', ToJSON)+import Data.Aeson (decodeStrict') import Data.Bits (xor) import Data.Word (Word8, Word64) import qualified Data.ByteArray as BA import qualified Data.ByteString as B-import qualified Data.ByteString.Lazy as BL import qualified Data.ByteString.Char8 as BC import Test.Hspec import Test.HUnit hiding (Test)@@ -68,7 +66,7 @@ it "decodes the JWT using the JWK" $ do let Just k1 = decodeStrict' a1jwk Just k2 = decodeStrict' a2jwk- withBlinder (decode [k2, k1] (Just $ JweEncoding RSA_OAEP A256GCM) a1) @?= (Right $ Jwe (a1Header, a1Payload))+ withBlinder (decode [k2, k1] (Just $ JweEncoding RSA_OAEP A256GCM) a1) @?= Right (Jwe (a1Header, a1Payload)) it "a truncated CEK returns BadCrypto" $ do let [hdr, _, iv, payload, tag] = BC.split '.' a1@@ -136,7 +134,7 @@ (Jwe.jwkEncode A128KW A128CBC_HS256 jwk (Claims a3Payload)) @?= (Right (Jwt a3), RNG "") it "decodes the JWT using the JWK" $- withBlinder (decode [jwk] Nothing a3) @?= (Right $ Jwe (a3Header, a3Payload))+ withBlinder (decode [jwk] Nothing a3) @?= Right (Jwe (a3Header, a3Payload)) context "when used with quickcheck" $ do it "padded msg is always a multiple of 16" $ property $@@ -297,7 +295,7 @@ return $ JWEAlgs a e instance Arbitrary RNG where- arbitrary = (RNG . B.pack) <$> vector 600+ arbitrary = RNG . B.pack <$> vector 600 --------------------------------------------------------------------------------
tests/Tests/JwkSpec.hs view
@@ -18,7 +18,6 @@ import Jose.Jwk import Jose.Jwa --- TODO: Support for {"kty": "oct", "alg": "A128KW", "k":"GawgguFyGrWKav7AX4VKUg"} spec :: Spec spec = do jwkFile <- runIO (B.readFile "tests/jwks.json")@@ -45,7 +44,7 @@ Ed25519PrivateJwk _ _ key9Id = kss !! 9 Ed25519PublicJwk _ key10Id = kss !! 10 Success utcKeyId = fromJSON (String "2015-05-16T18:00:14.259Z")- length kss @?= 11+ length kss @?= 14 a0 @?= Nothing key0Id @?= Just (KeyId "a0") key1Id @?= Just (KeyId "a1")
tests/jwks.json view
@@ -10,5 +10,8 @@ , {"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"} , {"kty":"OKP","crv":"Ed25519", "d":"nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A", "x":"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo", "kid": "rfc8037SecretKey"} , {"kty":"OKP","crv":"Ed25519", "x":"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo", "kid": "rfc8037PublicKey"}+ , {"kty": "oct", "alg": "A128KW", "k":"GawgguFyGrWKav7AX4VKUg"}+ , {"kty":"EC","use":"enc","crv":"P-256","kid":"jwe-encryption-key-from-pr-31","key_ops":["encrypt"],"x":"G8CZn6Zo7UqKLhpDFOh9njuDwBRn2xawrvf65c_t1UM","y":"L3tlkG4tQhNitkJp-5XxvqnbgBGjJFpFr-WEyIUbEvA","alg":"ECDH-ES" }+ , {"kty": "hypothetical-new-kty", "alg": "A256KW", "k":"GawgguFyGrWKav7AX4VKUg"} ] }