packages feed

jose-jwt 0.6.2 → 0.7

raw patch · 12 files changed

+607/−427 lines, 12 filesdep +cryptonitedep +memorydep −base64-bytestringdep −byteabledep −cipher-aesdep ~unordered-containersdep ~vectorPVP ok

version bump matches the API change (PVP)

Dependencies added: cryptonite, memory

Dependencies removed: base64-bytestring, byteable, cipher-aes, cprng-aes, crypto-cipher-types, crypto-numbers, crypto-pubkey, crypto-pubkey-types, crypto-random, cryptohash

Dependency ranges changed: unordered-containers, vector

API changes (from Hackage documentation)

- Jose.Jwa: instance Eq Alg
- Jose.Jwa: instance Eq Enc
- Jose.Jwa: instance Eq JweAlg
- Jose.Jwa: instance Eq JwsAlg
- Jose.Jwa: instance FromJSON Alg
- Jose.Jwa: instance FromJSON Enc
- Jose.Jwa: instance FromJSON JweAlg
- Jose.Jwa: instance FromJSON JwsAlg
- Jose.Jwa: instance Read JweAlg
- Jose.Jwa: instance Read JwsAlg
- Jose.Jwa: instance Show Alg
- Jose.Jwa: instance Show Enc
- Jose.Jwa: instance Show JweAlg
- Jose.Jwa: instance Show JwsAlg
- Jose.Jwa: instance ToJSON Alg
- Jose.Jwa: instance ToJSON Enc
- Jose.Jwa: instance ToJSON JweAlg
- Jose.Jwa: instance ToJSON JwsAlg
- Jose.Jwk: instance Constructor C1_0JwkData
- Jose.Jwk: instance Constructor C1_0JwkSet
- Jose.Jwk: instance Datatype D1JwkData
- Jose.Jwk: instance Datatype D1JwkSet
- Jose.Jwk: instance Eq EcCurve
- Jose.Jwk: instance Eq Jwk
- Jose.Jwk: instance Eq JwkSet
- Jose.Jwk: instance Eq KeyType
- Jose.Jwk: instance Eq KeyUse
- Jose.Jwk: instance FromJSON EcCurve
- Jose.Jwk: instance FromJSON Jwk
- Jose.Jwk: instance FromJSON JwkBytes
- Jose.Jwk: instance FromJSON JwkData
- Jose.Jwk: instance FromJSON JwkSet
- Jose.Jwk: instance FromJSON KeyType
- Jose.Jwk: instance FromJSON KeyUse
- Jose.Jwk: instance Generic JwkData
- Jose.Jwk: instance Generic JwkSet
- Jose.Jwk: instance Selector S1_0_0JwkData
- Jose.Jwk: instance Selector S1_0_0JwkSet
- Jose.Jwk: instance Selector S1_0_10JwkData
- Jose.Jwk: instance Selector S1_0_11JwkData
- Jose.Jwk: instance Selector S1_0_12JwkData
- Jose.Jwk: instance Selector S1_0_13JwkData
- Jose.Jwk: instance Selector S1_0_14JwkData
- Jose.Jwk: instance Selector S1_0_15JwkData
- Jose.Jwk: instance Selector S1_0_16JwkData
- Jose.Jwk: instance Selector S1_0_17JwkData
- Jose.Jwk: instance Selector S1_0_18JwkData
- Jose.Jwk: instance Selector S1_0_1JwkData
- Jose.Jwk: instance Selector S1_0_2JwkData
- Jose.Jwk: instance Selector S1_0_3JwkData
- Jose.Jwk: instance Selector S1_0_4JwkData
- Jose.Jwk: instance Selector S1_0_5JwkData
- Jose.Jwk: instance Selector S1_0_6JwkData
- Jose.Jwk: instance Selector S1_0_7JwkData
- Jose.Jwk: instance Selector S1_0_8JwkData
- Jose.Jwk: instance Selector S1_0_9JwkData
- Jose.Jwk: instance Show EcCurve
- Jose.Jwk: instance Show Jwk
- Jose.Jwk: instance Show JwkBytes
- Jose.Jwk: instance Show JwkData
- Jose.Jwk: instance Show JwkSet
- Jose.Jwk: instance Show KeyType
- Jose.Jwk: instance Show KeyUse
- Jose.Jwk: instance ToJSON EcCurve
- Jose.Jwk: instance ToJSON Jwk
- Jose.Jwk: instance ToJSON JwkBytes
- Jose.Jwk: instance ToJSON JwkData
- Jose.Jwk: instance ToJSON JwkSet
- Jose.Jwk: instance ToJSON KeyType
- Jose.Jwk: instance ToJSON KeyUse
- Jose.Jwt: jweAlg :: JweHeader -> JweAlg
- Jose.Jwt: jweCty :: JweHeader -> Maybe Text
- Jose.Jwt: jweEnc :: JweHeader -> Enc
- Jose.Jwt: jweKid :: JweHeader -> Maybe KeyId
- Jose.Jwt: jweTyp :: JweHeader -> Maybe Text
- Jose.Jwt: jweZip :: JweHeader -> Maybe Text
- Jose.Jwt: jwsAlg :: JwsHeader -> JwsAlg
- Jose.Jwt: jwsCty :: JwsHeader -> Maybe Text
- Jose.Jwt: jwsKid :: JwsHeader -> Maybe KeyId
- Jose.Jwt: jwsTyp :: JwsHeader -> Maybe Text
- Jose.Jwt: jwtAud :: JwtClaims -> !(Maybe [Text])
- Jose.Jwt: jwtExp :: JwtClaims -> !(Maybe IntDate)
- Jose.Jwt: jwtIat :: JwtClaims -> !(Maybe IntDate)
- Jose.Jwt: jwtIss :: JwtClaims -> !(Maybe Text)
- Jose.Jwt: jwtJti :: JwtClaims -> !(Maybe Text)
- Jose.Jwt: jwtNbf :: JwtClaims -> !(Maybe IntDate)
- Jose.Jwt: jwtSub :: JwtClaims -> !(Maybe Text)
- Jose.Jwt: unJwt :: Jwt -> ByteString
+ Jose.Internal.Crypto: keyWrap :: JweAlg -> ByteString -> ByteString -> Either JwtError ByteString
+ Jose.Jwa: A128KW :: JweAlg
+ Jose.Jwa: A192CBC_HS384 :: Enc
+ Jose.Jwa: A192GCM :: Enc
+ Jose.Jwa: A192KW :: JweAlg
+ Jose.Jwa: A256KW :: JweAlg
+ Jose.Jwa: instance Data.Aeson.Types.Class.FromJSON Jose.Jwa.Alg
+ Jose.Jwa: instance Data.Aeson.Types.Class.FromJSON Jose.Jwa.Enc
+ Jose.Jwa: instance Data.Aeson.Types.Class.FromJSON Jose.Jwa.JweAlg
+ Jose.Jwa: instance Data.Aeson.Types.Class.FromJSON Jose.Jwa.JwsAlg
+ Jose.Jwa: instance Data.Aeson.Types.Class.ToJSON Jose.Jwa.Alg
+ Jose.Jwa: instance Data.Aeson.Types.Class.ToJSON Jose.Jwa.Enc
+ Jose.Jwa: instance Data.Aeson.Types.Class.ToJSON Jose.Jwa.JweAlg
+ Jose.Jwa: instance Data.Aeson.Types.Class.ToJSON Jose.Jwa.JwsAlg
+ Jose.Jwa: instance GHC.Classes.Eq Jose.Jwa.Alg
+ Jose.Jwa: instance GHC.Classes.Eq Jose.Jwa.Enc
+ Jose.Jwa: instance GHC.Classes.Eq Jose.Jwa.JweAlg
+ Jose.Jwa: instance GHC.Classes.Eq Jose.Jwa.JwsAlg
+ Jose.Jwa: instance GHC.Read.Read Jose.Jwa.JweAlg
+ Jose.Jwa: instance GHC.Read.Read Jose.Jwa.JwsAlg
+ Jose.Jwa: instance GHC.Show.Show Jose.Jwa.Alg
+ Jose.Jwa: instance GHC.Show.Show Jose.Jwa.Enc
+ Jose.Jwa: instance GHC.Show.Show Jose.Jwa.JweAlg
+ Jose.Jwa: instance GHC.Show.Show Jose.Jwa.JwsAlg
+ Jose.Jwe: jwkDecode :: MonadRandom m => Jwk -> ByteString -> m (Either JwtError JwtContent)
+ Jose.Jwk: instance Data.Aeson.Types.Class.FromJSON Jose.Jwk.EcCurve
+ Jose.Jwk: instance Data.Aeson.Types.Class.FromJSON Jose.Jwk.Jwk
+ Jose.Jwk: instance Data.Aeson.Types.Class.FromJSON Jose.Jwk.JwkBytes
+ Jose.Jwk: instance Data.Aeson.Types.Class.FromJSON Jose.Jwk.JwkData
+ Jose.Jwk: instance Data.Aeson.Types.Class.FromJSON Jose.Jwk.JwkSet
+ Jose.Jwk: instance Data.Aeson.Types.Class.FromJSON Jose.Jwk.KeyType
+ Jose.Jwk: instance Data.Aeson.Types.Class.FromJSON Jose.Jwk.KeyUse
+ Jose.Jwk: instance Data.Aeson.Types.Class.ToJSON Jose.Jwk.EcCurve
+ Jose.Jwk: instance Data.Aeson.Types.Class.ToJSON Jose.Jwk.Jwk
+ Jose.Jwk: instance Data.Aeson.Types.Class.ToJSON Jose.Jwk.JwkBytes
+ Jose.Jwk: instance Data.Aeson.Types.Class.ToJSON Jose.Jwk.JwkData
+ Jose.Jwk: instance Data.Aeson.Types.Class.ToJSON Jose.Jwk.JwkSet
+ Jose.Jwk: instance Data.Aeson.Types.Class.ToJSON Jose.Jwk.KeyType
+ Jose.Jwk: instance Data.Aeson.Types.Class.ToJSON Jose.Jwk.KeyUse
+ Jose.Jwk: instance GHC.Classes.Eq Jose.Jwk.EcCurve
+ Jose.Jwk: instance GHC.Classes.Eq Jose.Jwk.Jwk
+ Jose.Jwk: instance GHC.Classes.Eq Jose.Jwk.JwkSet
+ Jose.Jwk: instance GHC.Classes.Eq Jose.Jwk.KeyType
+ Jose.Jwk: instance GHC.Classes.Eq Jose.Jwk.KeyUse
+ Jose.Jwk: instance GHC.Generics.Constructor Jose.Jwk.C1_0JwkData
+ Jose.Jwk: instance GHC.Generics.Constructor Jose.Jwk.C1_0JwkSet
+ Jose.Jwk: instance GHC.Generics.Datatype Jose.Jwk.D1JwkData
+ Jose.Jwk: instance GHC.Generics.Datatype Jose.Jwk.D1JwkSet
+ Jose.Jwk: instance GHC.Generics.Generic Jose.Jwk.JwkData
+ Jose.Jwk: instance GHC.Generics.Generic Jose.Jwk.JwkSet
+ Jose.Jwk: instance GHC.Generics.Selector Jose.Jwk.S1_0_0JwkData
+ Jose.Jwk: instance GHC.Generics.Selector Jose.Jwk.S1_0_0JwkSet
+ Jose.Jwk: instance GHC.Generics.Selector Jose.Jwk.S1_0_10JwkData
+ Jose.Jwk: instance GHC.Generics.Selector Jose.Jwk.S1_0_11JwkData
+ Jose.Jwk: instance GHC.Generics.Selector Jose.Jwk.S1_0_12JwkData
+ Jose.Jwk: instance GHC.Generics.Selector Jose.Jwk.S1_0_13JwkData
+ Jose.Jwk: instance GHC.Generics.Selector Jose.Jwk.S1_0_14JwkData
+ Jose.Jwk: instance GHC.Generics.Selector Jose.Jwk.S1_0_15JwkData
+ Jose.Jwk: instance GHC.Generics.Selector Jose.Jwk.S1_0_16JwkData
+ Jose.Jwk: instance GHC.Generics.Selector Jose.Jwk.S1_0_17JwkData
+ Jose.Jwk: instance GHC.Generics.Selector Jose.Jwk.S1_0_18JwkData
+ Jose.Jwk: instance GHC.Generics.Selector Jose.Jwk.S1_0_1JwkData
+ Jose.Jwk: instance GHC.Generics.Selector Jose.Jwk.S1_0_2JwkData
+ Jose.Jwk: instance GHC.Generics.Selector Jose.Jwk.S1_0_3JwkData
+ Jose.Jwk: instance GHC.Generics.Selector Jose.Jwk.S1_0_4JwkData
+ Jose.Jwk: instance GHC.Generics.Selector Jose.Jwk.S1_0_5JwkData
+ Jose.Jwk: instance GHC.Generics.Selector Jose.Jwk.S1_0_6JwkData
+ Jose.Jwk: instance GHC.Generics.Selector Jose.Jwk.S1_0_7JwkData
+ Jose.Jwk: instance GHC.Generics.Selector Jose.Jwk.S1_0_8JwkData
+ Jose.Jwk: instance GHC.Generics.Selector Jose.Jwk.S1_0_9JwkData
+ Jose.Jwk: instance GHC.Show.Show Jose.Jwk.EcCurve
+ Jose.Jwk: instance GHC.Show.Show Jose.Jwk.Jwk
+ Jose.Jwk: instance GHC.Show.Show Jose.Jwk.JwkBytes
+ Jose.Jwk: instance GHC.Show.Show Jose.Jwk.JwkSet
+ Jose.Jwk: instance GHC.Show.Show Jose.Jwk.KeyUse
+ Jose.Jwt: [jweAlg] :: JweHeader -> JweAlg
+ Jose.Jwt: [jweCty] :: JweHeader -> Maybe Text
+ Jose.Jwt: [jweEnc] :: JweHeader -> Enc
+ Jose.Jwt: [jweKid] :: JweHeader -> Maybe KeyId
+ Jose.Jwt: [jweTyp] :: JweHeader -> Maybe Text
+ Jose.Jwt: [jweZip] :: JweHeader -> Maybe Text
+ Jose.Jwt: [jwsAlg] :: JwsHeader -> JwsAlg
+ Jose.Jwt: [jwsCty] :: JwsHeader -> Maybe Text
+ Jose.Jwt: [jwsKid] :: JwsHeader -> Maybe KeyId
+ Jose.Jwt: [jwsTyp] :: JwsHeader -> Maybe Text
+ Jose.Jwt: [jwtAud] :: JwtClaims -> !(Maybe [Text])
+ Jose.Jwt: [jwtExp] :: JwtClaims -> !(Maybe IntDate)
+ Jose.Jwt: [jwtIat] :: JwtClaims -> !(Maybe IntDate)
+ Jose.Jwt: [jwtIss] :: JwtClaims -> !(Maybe Text)
+ Jose.Jwt: [jwtJti] :: JwtClaims -> !(Maybe Text)
+ Jose.Jwt: [jwtNbf] :: JwtClaims -> !(Maybe IntDate)
+ Jose.Jwt: [jwtSub] :: JwtClaims -> !(Maybe Text)
+ Jose.Jwt: [unJwt] :: Jwt -> ByteString
- Jose.Internal.Crypto: decryptPayload :: MonadError JwtError m => Enc -> ByteString -> ByteString -> ByteString -> ByteString -> ByteString -> m ByteString
+ Jose.Internal.Crypto: decryptPayload :: Enc -> ByteString -> ByteString -> ByteString -> AuthTag -> ByteString -> Maybe ByteString
- Jose.Internal.Crypto: encryptPayload :: Enc -> ByteString -> ByteString -> ByteString -> ByteString -> (ByteString, AuthTag)
+ Jose.Internal.Crypto: encryptPayload :: Enc -> ByteString -> ByteString -> ByteString -> ByteString -> Maybe (AuthTag, ByteString)
- Jose.Internal.Crypto: generateCmkAndIV :: CPRG g => g -> Enc -> ((ByteString, ByteString), g)
+ Jose.Internal.Crypto: generateCmkAndIV :: MonadRandom m => Enc -> m (ByteString, ByteString)
- Jose.Internal.Crypto: rsaDecrypt :: Maybe Blinder -> JweAlg -> PrivateKey -> ByteString -> Either JwtError ByteString
+ Jose.Internal.Crypto: rsaDecrypt :: Maybe Blinder -> PrivateKey -> JweAlg -> ByteString -> Either JwtError ByteString
- Jose.Internal.Crypto: rsaEncrypt :: CPRG g => g -> JweAlg -> PublicKey -> ByteString -> (ByteString, g)
+ Jose.Internal.Crypto: rsaEncrypt :: MonadRandom m => PublicKey -> JweAlg -> ByteString -> m (Either JwtError ByteString)
- Jose.Jwe: jwkEncode :: CPRG g => g -> JweAlg -> Enc -> Jwk -> Payload -> (Either JwtError Jwt, g)
+ Jose.Jwe: jwkEncode :: MonadRandom m => JweAlg -> Enc -> Jwk -> Payload -> m (Either JwtError Jwt)
- Jose.Jwe: rsaDecode :: CPRG g => g -> PrivateKey -> ByteString -> (Either JwtError Jwe, g)
+ Jose.Jwe: rsaDecode :: MonadRandom m => PrivateKey -> ByteString -> m (Either JwtError Jwe)
- Jose.Jwe: rsaEncode :: CPRG g => g -> JweAlg -> Enc -> PublicKey -> ByteString -> (Jwt, g)
+ Jose.Jwe: rsaEncode :: MonadRandom m => JweAlg -> Enc -> PublicKey -> ByteString -> m (Either JwtError Jwt)
- Jose.Jws: jwkEncode :: CPRG g => g -> JwsAlg -> Jwk -> Payload -> (Either JwtError Jwt, g)
+ Jose.Jws: jwkEncode :: MonadRandom m => JwsAlg -> Jwk -> Payload -> m (Either JwtError Jwt)
- Jose.Jws: rsaEncode :: CPRG g => g -> JwsAlg -> PrivateKey -> ByteString -> (Either JwtError Jwt, g)
+ Jose.Jws: rsaEncode :: MonadRandom m => JwsAlg -> PrivateKey -> ByteString -> m (Either JwtError Jwt)
- Jose.Jwt: decode :: CPRG g => g -> [Jwk] -> Maybe JwtEncoding -> ByteString -> (Either JwtError JwtContent, g)
+ Jose.Jwt: decode :: MonadRandom m => [Jwk] -> Maybe JwtEncoding -> ByteString -> m (Either JwtError JwtContent)
- Jose.Jwt: encode :: CPRG g => g -> [Jwk] -> JwtEncoding -> Payload -> (Either JwtError Jwt, g)
+ Jose.Jwt: encode :: MonadRandom m => [Jwk] -> JwtEncoding -> Payload -> m (Either JwtError Jwt)

Files

CHANGELOG.md view
@@ -1,3 +1,10 @@+0.7+---++* Add support for AES key wrap in JWEs.+* Support A192GCM and A192CBC-HS384 algorithms.+* Switch to cryptonite library.+ 0.6.2 ----- @@ -53,4 +60,3 @@  * New support for JWS validation using elliptic curve algorithms. * Added `Jwt.encode` function which takes a JWK argument, allowing key data (currently the key ID) to be encoded in the token header.-
Jose/Internal/Base64.hs view
@@ -6,28 +6,15 @@ module Jose.Internal.Base64 where  import Control.Monad.Error-import Data.ByteString (ByteString)-import qualified Data.ByteString as B-import qualified Data.ByteString.Char8 as BC-import qualified Data.ByteString.Base64.URL as B64+import Data.ByteArray+import Data.ByteArray.Encoding  import Jose.Types  -- | Base64 URL encode without padding.-encode :: ByteString -> ByteString-encode = strip . B64.encode-  where-    strip "" = ""-    strip bs = case BC.last bs of-      '=' -> strip $ B.init bs-      _   -> bs+encode :: (ByteArrayAccess input, ByteArray output) => input -> output+encode = convertToBase Base64URLUnpadded  -- | Base64 decode.-decode :: MonadError JwtError m => ByteString -> m ByteString-decode bs = either (throwError . Base64Error) return $ B64.decode bsPadded-  where-    bsPadded = case B.length bs `mod` 4 of-      3 -> bs `BC.snoc` '='-      2 -> bs `B.append` "=="-      _ -> bs-+decode :: (ByteArrayAccess input, ByteArray output, MonadError JwtError m) => input -> m output+decode bs = either (throwError . Base64Error) return $ convertFromBase Base64URLUnpadded bs
Jose/Internal/Crypto.hs view
@@ -15,26 +15,31 @@     , encryptPayload     , decryptPayload     , generateCmkAndIV+    , keyWrap+    , keyUnwrap     , pad     , unpad     ) where -import           Control.Applicative-import           Crypto.Cipher.Types (AuthTag(..))-import           Control.Monad.Error++import           Control.Monad (when, unless)+import           Crypto.Error+import           Crypto.Cipher.AES+import           Crypto.Cipher.Types+import           Crypto.Hash.Algorithms import           Crypto.Number.Serialize (os2ip) import qualified Crypto.PubKey.ECC.ECDSA as ECDSA import qualified Crypto.PubKey.RSA as RSA import qualified Crypto.PubKey.RSA.PKCS15 as PKCS15 import qualified Crypto.PubKey.RSA.OAEP as OAEP-import           Crypto.Random (CPRG, cprgGenerate)-import qualified Crypto.Cipher.AES as AES-import           Crypto.PubKey.HashDescr-import           Crypto.MAC.HMAC (hmac)-import           Data.Byteable (constEqBytes)+import           Crypto.Random (MonadRandom, getRandomBytes)+import           Crypto.MAC.HMAC (HMAC (..), hmac)+import           Data.Bits (xor)+import qualified Data.ByteArray as BA import           Data.ByteString (ByteString) import qualified Data.ByteString as B+import           Data.Either.Combinators import qualified Data.Serialize as Serialize import qualified Data.Text as T import           Data.Word (Word64, Word8)@@ -46,10 +51,12 @@ hmacSign :: JwsAlg      -- ^ HMAC algorithm to use          -> ByteString  -- ^ Key          -> ByteString  -- ^ The message/content-         -> Either JwtError ByteString  -- ^ HMAC output-hmacSign a k m = do-    hash <- maybe (Left $ BadAlgorithm $ T.pack $ "Not an HMAC algorithm: " ++ show a) return $ lookup a hmacHashes-    return $ hmac (hashFunction hash) 64 k m+         -> Either JwtError ByteString -- ^ HMAC output+hmacSign a k m = case a of+    HS256 -> Right $ BA.convert (hmac k m :: HMAC SHA256)+    HS384 -> Right $ BA.convert (hmac k m :: HMAC SHA384)+    HS512 -> Right $ BA.convert (hmac k m :: HMAC SHA512)+    _     -> Left $ BadAlgorithm $ T.pack $ "Not an HMAC algorithm: " ++ show a  -- | Verify the HMAC for a given message. -- Returns false if the MAC is incorrect or the 'Alg' is not an HMAC.@@ -58,7 +65,7 @@            -> ByteString  -- ^ The message/content            -> ByteString  -- ^ The signature to check            -> Bool        -- ^ Whether the signature is correct-hmacVerify a key msg sig = either (const False) (`constEqBytes` sig) $ hmacSign a key msg+hmacVerify a key msg sig = either (const False) (`BA.constEq` sig) $ hmacSign a key msg  -- | Sign a message using an RSA private key. --@@ -70,10 +77,13 @@         -> RSA.PrivateKey     -- ^ Private key to sign with         -> ByteString         -- ^ Message to sign         -> Either JwtError ByteString    -- ^ The signature-rsaSign blinder a key msg = do-    hash <- lookupRSAHash a-    either (const $ Left BadCrypto) Right $ PKCS15.sign blinder hash key msg+rsaSign blinder a key msg = case a of+    RS256 -> go SHA256+    RS384 -> go SHA384+    RS512 -> go SHA512+    _     -> Left . BadAlgorithm . T.pack $ "Not an RSA algorithm: " ++ show a   where+    go h = either (const $ Left BadCrypto) Right $ PKCS15.sign blinder (Just h) key msg  -- | Verify the signature for a message using an RSA public key. --@@ -84,9 +94,13 @@           -> ByteString    -- ^ The message/content           -> ByteString    -- ^ The signature to check           -> Bool          -- ^ Whether the signature is correct-rsaVerify a key msg sig = case lookupRSAHash a of-    Right hash -> PKCS15.verify hash key msg sig-    _          -> False+rsaVerify a key msg sig = case a of+    RS256 -> go SHA256+    RS384 -> go SHA384+    RS512 -> go SHA512+    _     -> False+  where+    go h = PKCS15.verify (Just h) key msg sig  -- | Verify the signature for a message using an EC public key. --@@ -97,136 +111,152 @@          -> ByteString      -- ^ The message/content          -> ByteString      -- ^ The signature to check          -> Bool            -- ^ Whether the signature is correct-ecVerify a key msg sig = case lookupECHash a of-    Just hash -> let (r, s) = B.splitAt (B.length sig `div` 2) sig-                 in  ECDSA.verify hash key (ECDSA.Signature (os2ip r) (os2ip s)) msg-    Nothing   -> False--hmacHashes :: [(JwsAlg, HashDescr)]-hmacHashes = [(HS256, hashDescrSHA256), (HS384, hashDescrSHA384), (HS512, hashDescrSHA512)]--lookupECHash :: JwsAlg -> Maybe HashFunction-lookupECHash alg = hashFunction <$> case alg of-    ES256 -> Just hashDescrSHA256-    ES384 -> Just hashDescrSHA384-    ES512 -> Just hashDescrSHA512-    _     -> Nothing--lookupRSAHash :: JwsAlg -> Either JwtError HashDescr-lookupRSAHash alg = case alg of-    RS256 -> Right hashDescrSHA256-    RS384 -> Right hashDescrSHA384-    RS512 -> Right hashDescrSHA512-    _     -> Left . BadAlgorithm . T.pack $ "Not an RSA algorithm: " ++ show alg+ecVerify a key msg sig = case a of+    ES256 -> go SHA256+    ES384 -> go SHA384+    ES512 -> go SHA512+    _     -> False+  where+    (r, s) = B.splitAt (B.length sig `div` 2) sig+    ecSig  = ECDSA.Signature (os2ip r) (os2ip s)+    go h   = ECDSA.verify h key ecSig msg  -- | Generates the symmetric key (content management key) and IV -- -- Used to encrypt a message.-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'')+generateCmkAndIV :: MonadRandom m+                 => Enc -- ^ The encryption algorithm to be used+                 -> m (B.ByteString, B.ByteString) -- ^ The key, IV+generateCmkAndIV e = do+    cmk <- getRandomBytes (keySize e)+    iv  <- getRandomBytes (ivSize e)   -- iv for aes gcm or cbc+    return (cmk, iv)   where-    (cmk, g') = cprgGenerate (keySize e) g-    (iv, g'') = cprgGenerate (ivSize e) g'  -- iv for aes gcm or cbc--keySize :: Enc -> Int-keySize A128GCM = 16-keySize A256GCM = 32-keySize A128CBC_HS256 = 32-keySize A256CBC_HS512 = 64+    keySize A128GCM = 16+    keySize A192GCM = 24+    keySize A256GCM = 32+    keySize A128CBC_HS256 = 32+    keySize A192CBC_HS384 = 48+    keySize A256CBC_HS512 = 64 -ivSize :: Enc -> Int-ivSize A128GCM = 12-ivSize A256GCM = 12-ivSize _       = 16+    ivSize A128GCM = 12+    ivSize A192GCM = 12+    ivSize A256GCM = 12+    ivSize _       = 16  -- | Encrypts a message (typically a symmetric key) using RSA.-rsaEncrypt :: CPRG g-           => g                  -- ^ Random number generator-           -> JweAlg             -- ^ The algorithm (either @RSA1_5@ or @RSA_OAEP@)-           -> RSA.PublicKey      -- ^ The encryption key-           -> B.ByteString       -- ^ The message to encrypt-           -> (B.ByteString, g)  -- ^ The encrypted messaged and new generator-rsaEncrypt gen a pubKey content = (ct, g')+rsaEncrypt :: MonadRandom m+    => RSA.PublicKey      -- ^ The encryption key+    -> JweAlg             -- ^ The algorithm (either @RSA1_5@ or @RSA_OAEP@)+    -> B.ByteString       -- ^ The message to encrypt+    -> m (Either JwtError B.ByteString)     -- ^ The encrypted message+rsaEncrypt k a bs = case a of+    RSA1_5   -> mapErr (PKCS15.encrypt k bs)+    RSA_OAEP -> mapErr (OAEP.encrypt (OAEP.defaultOAEPParams SHA1) k bs)+    _        -> return (Left (BadAlgorithm "Not an RSA algorithm"))   where-    encrypt = case a of-        RSA1_5   -> PKCS15.encrypt gen-        RSA_OAEP -> OAEP.encrypt gen oaepParams--- TODO: Check that we can't cause any errors here with our RSA public key-    (Right ct, g') = encrypt pubKey content+    mapErr = fmap (mapLeft (const BadCrypto))  -- | Decrypts an RSA encrypted message. rsaDecrypt :: Maybe RSA.Blinder-           -> JweAlg                        -- ^ The RSA algorithm to use            -> RSA.PrivateKey                -- ^ The decryption key+           -> JweAlg                        -- ^ The RSA algorithm to use            -> B.ByteString                  -- ^ The encrypted content            -> Either JwtError B.ByteString  -- ^ The decrypted key-rsaDecrypt blinder a rsaKey jweKey = either (const $ throwError BadCrypto) return $ decrypt rsaKey jweKey+rsaDecrypt blinder rsaKey a jweKey = case a of+    RSA1_5   -> mapErr (PKCS15.decrypt blinder rsaKey jweKey)+    RSA_OAEP -> mapErr (OAEP.decrypt blinder (OAEP.defaultOAEPParams SHA1) rsaKey jweKey)+    _        -> Left (BadAlgorithm "Not an RSA algorithm")   where-    decrypt = case a of-        RSA1_5   -> PKCS15.decrypt blinder-        RSA_OAEP -> OAEP.decrypt blinder oaepParams+    mapErr = mapLeft (const BadCrypto) -oaepParams :: OAEP.OAEPParams-oaepParams = OAEP.defaultOAEPParams (hashFunction hashDescrSHA1)+-- Dummy type to constrain Cipher type+data C c = C +initCipher :: BlockCipher c => C c -> B.ByteString -> Maybe c+initCipher _ k = maybeCryptoError $ cipherInit k+ -- | Decrypt an AES encrypted message.-decryptPayload :: MonadError JwtError m-               => Enc        -- ^ Encryption algorithm+decryptPayload :: Enc        -- ^ Encryption algorithm                -> ByteString -- ^ Content management key                -> ByteString -- ^ IV                -> ByteString -- ^ Additional authentication data-               -> ByteString -- ^ The integrity protection value to be checked+               -> AuthTag    -- ^ The integrity protection value to be checked                -> ByteString -- ^ The encrypted JWT payload-               -> m ByteString-decryptPayload e cek iv aad sig ct = do-    (plaintext, tag) <- case e of-        A128GCM        -> decryptedGCM-        A256GCM        -> decryptedGCM-        A128CBC_HS256  -> decryptedCBC 16 hashDescrSHA256-        A256CBC_HS512  -> decryptedCBC 32 hashDescrSHA512-    if tag == AuthTag sig-      then return plaintext-      else throwError BadSignature+               -> Maybe ByteString+decryptPayload enc cek iv aad sig ct = case enc of+    A128GCM       -> doGCM (C :: C AES128)+    A192GCM       -> doGCM (C :: C AES192)+    A256GCM       -> doGCM (C :: C AES256)+    A128CBC_HS256 -> doCBC (C :: C AES128) SHA256 16+    A192CBC_HS384 -> doCBC (C :: C AES192) SHA384 24+    A256CBC_HS512 -> doCBC (C :: C AES256) SHA512 32   where-    decryptedGCM = return $ AES.decryptGCM (AES.initAES cek) iv aad ct+    (cbcMacKey, cbcEncKey) = B.splitAt (B.length cek `div` 2) cek+    al = fromIntegral (B.length aad) * 8 :: Word64 -    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-      let mac = authTag l h macKey $ B.concat [aad, iv, ct, Serialize.encode al]-      return (plaintext, mac)+    doGCM :: BlockCipher c => C c -> Maybe ByteString+    doGCM c = do+        cipher <- initCipher c cek+        aead <- maybeCryptoError (aeadInit AEAD_GCM cipher iv)+        aeadSimpleDecrypt aead aad ct (AuthTag $ BA.convert sig) +    doCBC :: (HashAlgorithm a, BlockCipher c) => C c -> a -> Int -> Maybe ByteString+    doCBC c a tagLen = do+        checkMac a tagLen+        cipher <- initCipher c cbcEncKey+        iv'    <- makeIV iv+        unless (B.length ct `mod` blockSize cipher == 0) Nothing+        unpad $ cbcDecrypt cipher iv' ct++    checkMac :: HashAlgorithm a => a -> Int -> Maybe ()+    checkMac a l = do+        let mac = BA.take l $ BA.convert $ doMac a :: BA.Bytes+        unless (sig `BA.constEq` mac) Nothing++    doMac :: HashAlgorithm a => a -> HMAC a+    doMac _ = hmac cbcMacKey $ B.concat [aad, iv, ct, Serialize.encode al]+ -- | Encrypt a message using AES. encryptPayload :: Enc                   -- ^ Encryption algorithm                -> ByteString            -- ^ Content management key                -> ByteString            -- ^ IV                -> ByteString            -- ^ Additional authenticated data                -> ByteString            -- ^ The message/JWT claims-               -> (ByteString, AuthTag) -- ^ Ciphertext claims and signature tag+               -> Maybe (AuthTag, ByteString) -- ^ Ciphertext claims and signature tag encryptPayload e cek iv aad msg = case e of-    A128GCM        -> aesgcm-    A256GCM        -> aesgcm-    A128CBC_HS256  -> (aescbc, sig 16 hashDescrSHA256)-    A256CBC_HS512  -> (aescbc, sig 32 hashDescrSHA512)+    A128GCM       -> doGCM (C :: C AES128)+    A192GCM       -> doGCM (C :: C AES192)+    A256GCM       -> doGCM (C :: C AES256)+    A128CBC_HS256 -> doCBC (C :: C AES128) SHA256 16+    A192CBC_HS384 -> doCBC (C :: C AES192) SHA384 24+    A256CBC_HS512 -> doCBC (C :: C AES256) SHA512 32   where-    aesgcm = AES.encryptGCM (AES.initAES cek) iv aad msg-    (macKey, encKey) = B.splitAt (B.length cek `div` 2) cek-    aescbc = AES.encryptCBC (AES.initAES encKey) iv (pad msg)-    al     = fromIntegral (B.length aad) * 8 :: Word64-    sig l h = authTag l h macKey $ B.concat [aad, iv, aescbc, Serialize.encode al]+    (cbcMacKey, cbcEncKey) = B.splitAt (B.length cek `div` 2) cek+    al = fromIntegral (B.length aad) * 8 :: Word64 -authTag :: Int -> HashDescr -> ByteString -> ByteString -> AuthTag-authTag l h k m = AuthTag $ B.take l $ hmac (hashFunction h) 64 k m+    doGCM :: BlockCipher c => C c -> Maybe (AuthTag, ByteString)+    doGCM c = do+        cipher <- initCipher c cek+        aead <- maybeCryptoError (aeadInit AEAD_GCM cipher iv)+        return $ aeadSimpleEncrypt aead aad msg 16 -unpad :: MonadError JwtError m => ByteString -> m ByteString+    doCBC :: (HashAlgorithm a, BlockCipher c) => C c -> a -> Int -> Maybe (AuthTag, ByteString)+    doCBC c a tagLen = do+        cipher <- initCipher c cbcEncKey+        iv'    <- makeIV iv+        let ct = cbcEncrypt cipher iv' (pad msg)+            mac = doMac a ct+            tag = BA.take tagLen (BA.convert mac)+        return (AuthTag tag, ct)++    doMac :: HashAlgorithm a => a -> ByteString -> HMAC a+    doMac _ ct = hmac cbcMacKey $ B.concat [aad, iv, ct, Serialize.encode al]++unpad :: ByteString -> Maybe ByteString unpad bs-    | padLen > 16 || padLen /= B.length padding = throwError BadCrypto-    | B.any (/= padByte) padding = throwError BadCrypto+    | padLen > 16 || padLen /= B.length padding = Nothing+    | B.any (/= padByte) padding = Nothing     | otherwise = return pt   where     len     = B.length bs@@ -241,3 +271,70 @@     padByte       = fromIntegral $ 16 - lastBlockSize :: Word8     padding       = B.replicate (fromIntegral padByte) padByte +-- Key wrapping and unwrapping functions++-- | <https://tools.ietf.org/html/rfc3394#section-2.2.1>+keyWrap :: JweAlg -> ByteString -> ByteString -> Either JwtError ByteString+keyWrap alg kek cek = case alg of+    A128KW -> doKeyWrap (C :: C AES128)+    A192KW -> doKeyWrap (C :: C AES192)+    A256KW -> doKeyWrap (C :: C AES256)+    _      -> Left (BadAlgorithm "Not a keywrap algorithm")+  where+    l = B.length cek+    n = l `div` 8+    iv = BA.replicate 8 166 :: ByteString++    doKeyWrap c = do+        when (l < 16 || l `mod` 8 /= 0) (Left (KeyError "Invalid content key"))+        cipher <- maybe (Left (KeyError "cipher initialization failed")) return $ initCipher c kek+        let p = toBlocks cek+            (r0, r) = foldl (doRound (ecbEncrypt cipher) 1) (iv, p) [0..5]+        Right $ B.concat (r0 : r)++    doRound _ _  (a, []) _ = (a, [])+    doRound enc i (a, r:rs) j =+        let b  = enc $ B.concat [a, r]+            t  = fromIntegral ((n*j) + i) :: Word8+            a' = txor t (B.take 8 b)+            r' = B.drop 8 b+            next = doRound enc (i+1) (a', rs) j+        in (fst next, r' : snd next)++    txor t b = B.snoc (B.init b) (B.last b `xor` t)++toBlocks :: ByteString -> [ByteString]+toBlocks bytes+    | bytes == B.empty = []+    | otherwise    = let (b, bs') = B.splitAt 8 bytes+                        in  b : toBlocks bs'++keyUnwrap :: ByteString -> JweAlg -> ByteString -> Either JwtError ByteString+keyUnwrap kek alg encK = case alg of+    A128KW -> doUnWrap (C :: C AES128)+    A192KW -> doUnWrap (C :: C AES192)+    A256KW -> doUnWrap (C :: C AES256)+    _      -> Left (BadAlgorithm "Not a keywrap algorithm")+  where+    l = B.length encK+    n = (l `div` 8) - 1+    iv = BA.replicate 8 166 :: ByteString++    doUnWrap c = do+        when (l < 24 || l `mod` 8 /= 0) (Left BadCrypto)+        cipher <- maybe (Left BadCrypto) return $ initCipher c kek+        let r = toBlocks encK+            (p0, p) = foldl (doRound (ecbDecrypt cipher) n) (head r, reverse (tail r)) (reverse [0..5])+        unless (p0 == iv) (Left BadCrypto)+        Right $ B.concat (reverse p)++    doRound _ _  (a, []) _ = (a, [])+    doRound dec i (a, r:rs) j =+        let b  = dec $ B.concat [txor t a, r]+            t  = fromIntegral ((n*j) + i) :: Word8+            a' = B.take 8 b+            r' = B.drop 8 b+            next = doRound dec (i-1) (a', rs) j+        in (fst next, r' : snd next)++    txor t b = B.snoc (B.init b) (B.last b `xor` t)
Jose/Jwa.hs view
@@ -20,20 +20,19 @@ data Alg = Signed JwsAlg | Encrypted JweAlg deriving (Eq, Show)  -- | A subset of the signature algorithms from the--- <http://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-31#section-3 JWA Spec>.+-- <https://tools.ietf.org/html/rfc7518#section-3 JWA Spec>. data JwsAlg = None | HS256 | HS384 | HS512 | RS256 | RS384 | RS512 | ES256 | ES384 | ES512 deriving (Eq, Show, Read)  -- | A subset of the key management algorithms from the--- <http://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-31#section-5 JWA Spec>.-data JweAlg = RSA1_5 | RSA_OAEP deriving (Eq, Show, Read)+-- <https://tools.ietf.org/html/rfc7518#section-4 JWA Spec>.+data JweAlg = RSA1_5 | RSA_OAEP | A128KW | A192KW | A256KW deriving (Eq, Show, Read)  -- | Content encryption algorithms from the--- <http://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-31#section-5 JWA Spec>.--- The optional algorithms A192CBC-HS384 and A192GCM are not supported yet.-data Enc = A128CBC_HS256 | A256CBC_HS512 | A128GCM | A256GCM deriving (Eq, Show)+-- <https://tools.ietf.org/html/rfc7518#section-5 JWA Spec>.+data Enc = A128CBC_HS256 | A192CBC_HS384 | A256CBC_HS512 | A128GCM | A192GCM | A256GCM deriving (Eq, Show)  algs :: [(Text, Alg)]-algs = [("none", Signed None), ("HS256", Signed HS256), ("HS384", Signed HS384), ("HS512", Signed HS512), ("RS256", Signed RS256), ("RS384", Signed RS384), ("RS512", Signed RS512), ("ES256", Signed ES256), ("ES384", Signed ES384), ("ES512", Signed ES512), ("RSA1_5", Encrypted RSA1_5), ("RSA-OAEP", Encrypted RSA_OAEP)]+algs = [("none", Signed None), ("HS256", Signed HS256), ("HS384", Signed HS384), ("HS512", Signed HS512), ("RS256", Signed RS256), ("RS384", Signed RS384), ("RS512", Signed RS512), ("ES256", Signed ES256), ("ES384", Signed ES384), ("ES512", Signed ES512), ("RSA1_5", Encrypted RSA1_5), ("RSA-OAEP", Encrypted RSA_OAEP), ("A128KW", Encrypted A128KW), ("A192KW", Encrypted A192KW), ("A256KW", Encrypted A256KW)]  algName :: Alg -> Text algName a = fromJust $ lookup a algNames@@ -42,7 +41,7 @@ algNames = map swap algs  encs :: [(Text, Enc)]-encs = [("A128CBC-HS256", A128CBC_HS256), ("A256CBC-HS512", A256CBC_HS512), ("A128GCM", A128GCM), ("A256GCM", A256GCM)]+encs = [("A128CBC-HS256", A128CBC_HS256), ("A256CBC-HS512", A256CBC_HS512), ("A192CBC-HS384", A192CBC_HS384), ("A128GCM", A128GCM), ("A192GCM", A192GCM), ("A256GCM", A256GCM)]  encName :: Enc -> Text encName e = fromJust $ lookup e encNames@@ -79,4 +78,3 @@  instance ToJSON Enc where     toJSON = String . encName-
Jose/Jwe.hs view
@@ -6,27 +6,29 @@ -- -- >>> import Jose.Jwe -- >>> import Jose.Jwa--- >>> import Crypto.Random.AESCtr--- >>> g <- makeSystem+-- >>> import Crypto.Random+-- >>> g <- drgNew -- >>> import Crypto.PubKey.RSA--- >>> let ((kPub, kPr), g') = generate g 512 65537--- >>> let (Jwt jwt, g'') = rsaEncode g' RSA_OAEP A128GCM kPub "secret claims"--- >>> fst $ rsaDecode g'' kPr jwt+-- >>> let ((kPub, kPr), g') = withDRG g (generate 512 65537)+-- >>> let (Right (Jwt jwt), g'') = withDRG g' (rsaEncode RSA_OAEP A128GCM kPub "secret claims")+-- >>> fst $ withDRG g'' (rsaDecode kPr jwt) -- Right (JweHeader {jweAlg = RSA_OAEP, jweEnc = A128GCM, jweTyp = Nothing, jweCty = Nothing, jweZip = Nothing, jweKid = Nothing},"secret claims")  module Jose.Jwe     ( jwkEncode+    , jwkDecode     , rsaEncode     , rsaDecode     ) where -import Control.Arrow (first)-import Crypto.Cipher.Types (AuthTag(..))+import Control.Monad (unless)+import Control.Monad.Trans (lift) import Control.Monad.Trans.Either+import Crypto.Cipher.Types (AuthTag(..)) import Crypto.PubKey.RSA (PrivateKey(..), PublicKey(..), generateBlinder, private_pub)-import Control.Monad.State.Strict-import Crypto.Random.API (CPRG)+import Crypto.Random (MonadRandom)+import qualified Data.ByteArray as BA import Data.ByteString (ByteString) import qualified Data.ByteString as B import qualified Data.ByteString.Char8 as BC@@ -39,59 +41,44 @@ -- | Create a JWE using a JWK. -- The key and algorithms must be consistent or an error -- will be returned.-jwkEncode :: CPRG g-          => g                               -- ^ Random number generator-          -> JweAlg                          -- ^ Algorithm to use for key encryption-          -> Enc                             -- ^ Content encryption algorithm-          -> Jwk                             -- ^ The key to use to encrypt the content key-          -> Payload                         -- ^ The token content (claims or nested JWT)-          -> (Either JwtError Jwt, g)        -- ^ The encoded JWE if successful-jwkEncode rng a e jwk payload = case jwk of-    RsaPublicJwk kPub kid _ _ -> first Right $ rsaEncodeInternal rng (hdr kid) kPub bytes-    RsaPrivateJwk kPr kid _ _ -> first Right $ rsaEncodeInternal rng (hdr kid) (private_pub kPr) bytes-    _                         -> (Left $ KeyError "Only RSA JWKs can be used for encoding", rng)+jwkEncode :: MonadRandom m+    => JweAlg                          -- ^ Algorithm to use for key encryption+    -> Enc                             -- ^ Content encryption algorithm+    -> Jwk                             -- ^ The key to use to encrypt the content key+    -> Payload                         -- ^ The token content (claims or nested JWT)+    -> m (Either JwtError Jwt)         -- ^ The encoded JWE if successful+jwkEncode a e jwk payload = runEitherT $ case jwk of+    RsaPublicJwk kPub kid _ _ -> doEncode (hdr kid) (doRsa kPub) bytes+    RsaPrivateJwk kPr kid _ _ -> doEncode (hdr kid) (doRsa (private_pub kPr)) bytes+    SymmetricJwk  kek kid _ _ -> doEncode (hdr kid) (hoistEither . keyWrap a kek) bytes+    _                         -> left $ KeyError "JWK cannot encode a JWE"   where+    doRsa kPub = EitherT . rsaEncrypt kPub a     hdr kid = defJweHdr {jweAlg = a, jweEnc = e, jweKid = kid, jweCty = contentType}     (contentType, bytes) = case payload of         Claims c       -> (Nothing, c)         Nested (Jwt b) -> (Just "JWT", b) --- | Creates a JWE.-rsaEncode :: CPRG g-          => g               -- ^ Random number generator-          -> JweAlg          -- ^ RSA algorithm to use (@RSA_OAEP@ or @RSA1_5@)-          -> Enc             -- ^ Content encryption algorithm-          -> PublicKey       -- ^ RSA key to encrypt with-          -> ByteString      -- ^ The JWT claims (content)-          -> (Jwt, g) -- ^ The encoded JWE and new generator-rsaEncode rng a e = rsaEncodeInternal rng (defJweHdr {jweAlg = a, jweEnc = e})--rsaEncodeInternal :: CPRG g-                  => g-                  -> JweHeader-                  -> PublicKey-                  -> ByteString-                  -> (Jwt, g)-rsaEncodeInternal rng h pubKey claims = (Jwt jwe, rng'')-  where-    a   = jweAlg h-    e   = jweEnc h-    hdr = encodeHeader h-    ((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 = flip runState rng $ runEitherT $ do-    blinder <- state $ \g -> generateBlinder g (public_n $ private_pub pk)+-- | Try to decode a JWE using a JWK.+-- If the key type does not match the content encoding algorithm,+-- an error will be returned.+jwkDecode :: MonadRandom m+    => Jwk+    -> ByteString+    -> m (Either JwtError JwtContent)+jwkDecode jwk jwt = runEitherT $ case jwk of+    RsaPrivateJwk kPr _ _ _ -> do+        blinder <- lift $ generateBlinder (public_n $ private_pub kPr)+        e <- doDecode (rsaDecrypt (Just blinder) kPr) jwt+        return (Jwe e)+    SymmetricJwk kb   _ _ _ -> fmap Jwe (doDecode (keyUnwrap kb) jwt)+    _                       -> left $ KeyError "JWK cannot decode a JWE" +doDecode :: MonadRandom m+    => (JweAlg -> ByteString -> Either JwtError ByteString)+    -> ByteString+    -> EitherT JwtError m Jwe+doDecode decodeCek jwt = do     checkDots     let components = BC.split '.' jwt     let aad = head components@@ -103,17 +90,53 @@         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+    (dummyCek, dummyIv) <- lift $ generateCmkAndIV enc+    let decryptedCek = either (const dummyCek) id $ decodeCek alg ek         cek = if B.length decryptedCek == B.length dummyCek-                then decryptedCek-                else 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+        authTag = AuthTag $ BA.convert sig+    claims <- maybe (left BadCrypto) return $ decryptPayload enc cek iv aad authTag payload     return (hdr, claims)    where     checkDots = unless (BC.count '.' jwt == 4) $ left (BadDots 4) ++doEncode :: MonadRandom m+    => JweHeader+    -> (ByteString -> EitherT JwtError m ByteString)+    -> ByteString+    -> EitherT JwtError m Jwt+doEncode h encryptKey claims = do+    (cmk, iv) <- lift (generateCmkAndIV e)+    let Just (AuthTag sig, ct) = encryptPayload e cmk iv aad claims+    jweKey <- encryptKey cmk+    let jwe = B.intercalate "." $ map B64.encode [hdr, jweKey, iv, ct, BA.convert sig]+    return (Jwt jwe)+  where+    e   = jweEnc h+    hdr = encodeHeader h+    aad = B64.encode hdr++-- | Creates a JWE with the content key encoded using RSA.+rsaEncode :: MonadRandom m+    => JweAlg          -- ^ RSA algorithm to use (@RSA_OAEP@ or @RSA1_5@)+    -> Enc             -- ^ Content encryption algorithm+    -> PublicKey       -- ^ RSA key to encrypt with+    -> ByteString      -- ^ The JWT claims (content)+    -> m (Either JwtError Jwt) -- ^ The encoded JWE+rsaEncode a e kPub claims = runEitherT $ doEncode (defJweHdr {jweAlg = a, jweEnc = e}) (EitherT . rsaEncrypt kPub a) claims+++-- | Decrypts a JWE.+rsaDecode :: MonadRandom m+    => PrivateKey               -- ^ Decryption key+    -> ByteString               -- ^ The encoded JWE+    -> m (Either JwtError Jwe)  -- ^ The decoded JWT, unless an error occurs+rsaDecode pk jwt = runEitherT $ do+    blinder <- lift $ generateBlinder (public_n $ private_pub pk)+    doDecode (rsaDecrypt (Just blinder) pk) jwt
Jose/Jwk.hs view
@@ -1,9 +1,8 @@-{-# LANGUAGE OverloadedStrings, DeriveGeneric #-}+{-# LANGUAGE OverloadedStrings, DeriveGeneric, RecordWildCards #-} {-# OPTIONS_HADDOCK prune #-}  module Jose.Jwk-    ( KeyType-    , KeyUse (..)+    ( KeyUse (..)     , KeyId     , Jwk (..)     , JwkSet (..)@@ -20,14 +19,16 @@ where  import           Control.Applicative (pure)-import           Crypto.Random (CPRG)+import           Control.Monad (unless)+import           Crypto.Random (MonadRandom) import qualified Crypto.PubKey.RSA as RSA import qualified Crypto.PubKey.ECC.ECDSA as ECDSA-import qualified Crypto.Types.PubKey.ECC as ECC+import qualified Crypto.PubKey.ECC.Types as ECC import           Crypto.Number.Serialize import           Data.Aeson (genericToJSON, Value(..), FromJSON(..), ToJSON(..), withText) import           Data.Aeson.Types (Parser, Options (..), defaultOptions) import           Data.ByteString (ByteString)+import           Data.Maybe (isNothing) import           Data.Text (Text) import qualified Data.Text.Encoding as TE import           GHC.Generics (Generic)@@ -39,7 +40,7 @@ data KeyType = Rsa              | Ec              | Oct-               deriving (Eq, Show)+               deriving (Eq)  data EcCurve = P_256              | P_384@@ -50,27 +51,26 @@              | Enc                deriving (Eq,Show) -data Jwk = RsaPublicJwk  RSA.PublicKey (Maybe KeyId) (Maybe KeyUse) (Maybe Alg)-         | RsaPrivateJwk RSA.PrivateKey (Maybe KeyId) (Maybe KeyUse) (Maybe Alg)-         | EcPublicJwk   ECDSA.PublicKey (Maybe KeyId) (Maybe KeyUse) (Maybe Alg) EcCurve-         | EcPrivateJwk  ECDSA.KeyPair   (Maybe KeyId) (Maybe KeyUse) (Maybe Alg) EcCurve-         | SymmetricJwk  ByteString (Maybe KeyId) (Maybe KeyUse) (Maybe Alg)+data Jwk = RsaPublicJwk  !RSA.PublicKey   !(Maybe KeyId) !(Maybe KeyUse) !(Maybe Alg)+         | RsaPrivateJwk !RSA.PrivateKey  !(Maybe KeyId) !(Maybe KeyUse) !(Maybe Alg)+         | EcPublicJwk   !ECDSA.PublicKey !(Maybe KeyId) !(Maybe KeyUse) !(Maybe Alg) !EcCurve+         | EcPrivateJwk  !ECDSA.KeyPair   !(Maybe KeyId) !(Maybe KeyUse) !(Maybe Alg) !EcCurve+         | SymmetricJwk  !ByteString      !(Maybe KeyId) !(Maybe KeyUse) !(Maybe Alg)            deriving (Show, Eq)  data JwkSet = JwkSet     { keys :: [Jwk]     } deriving (Show, Eq, Generic) -generateRsaKeyPair :: (CPRG g)-    => g-    -> Int+generateRsaKeyPair :: (MonadRandom m)+    => 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')+    -> m (Jwk, Jwk)+generateRsaKeyPair nBytes id' kuse kalg = do+    (kPub, kPr) <- RSA.generate nBytes 65537+    return (RsaPublicJwk kPub (Just id') (Just kuse) kalg, RsaPrivateJwk kPr (Just id') (Just kuse) kalg)  isPublic :: Jwk -> Bool isPublic RsaPublicJwk {} = True@@ -126,17 +126,23 @@     case (jweAlg hdr, jwk) of         (RSA1_5,   RsaPrivateJwk {}) -> True         (RSA_OAEP, RsaPrivateJwk {}) -> True+        (A128KW,   SymmetricJwk {})  -> True+        (A192KW,   SymmetricJwk {})  -> True+        (A256KW,   SymmetricJwk {})  -> True         _                            -> False  canEncodeJwe :: JweAlg -> Jwk -> Bool canEncodeJwe a jwk = jwkUse jwk /= Just Sig &&     algCompatible (Encrypted a) jwk &&     case (a, jwk) of-        (RSA1_5,   RsaPublicJwk {}) -> True-        (RSA_OAEP, RsaPublicJwk {}) -> True+        (RSA1_5,   RsaPublicJwk {})  -> True+        (RSA_OAEP, RsaPublicJwk {})  -> True         (RSA1_5,   RsaPrivateJwk {}) -> True         (RSA_OAEP, RsaPrivateJwk {}) -> True-        _                           -> False+        (A128KW,   SymmetricJwk {})  -> True+        (A192KW,   SymmetricJwk {})  -> True+        (A256KW,   SymmetricJwk {})  -> True+        _                            -> False  keyIdCompatible :: Maybe KeyId -> Jwk -> Bool keyIdCompatible Nothing _ = True@@ -323,7 +329,7 @@     , x5u :: Maybe Text     , x5c :: Maybe [Text]     , x5t :: Maybe Text-    } deriving (Show, Generic)+    } deriving (Generic)  instance FromJSON JwkData instance ToJSON   JwkData where@@ -353,23 +359,40 @@     }  createJwk :: JwkData -> Either String Jwk-createJwk kd = case kd of-    J Rsa (Just nb) (Just eb) Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing u a i _ _ _ ->-        return $ RsaPublicJwk (rsaPub nb eb) i u a-    J Rsa (Just nb) (Just eb) (Just db) mp mq mdp mdq mqi Nothing Nothing Nothing Nothing u a i _ _ _ ->-        return $ RsaPrivateJwk (RSA.PrivateKey (rsaPub nb eb) (os2ip $ bytes db) (os2mip mp) (os2mip mq) (os2mip mdp) (os2mip mdq) (os2mip mqi)) i u a-    J Oct Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing (Just kb) Nothing Nothing Nothing u a i Nothing Nothing Nothing ->-        return $ SymmetricJwk (bytes kb) i u a-    J Ec  Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing (Just crv') (Just xb) (Just yb) u a i Nothing Nothing Nothing ->-        return $ EcPublicJwk (ECDSA.PublicKey (curve crv') (ecPoint xb yb)) i u a crv'-    J Ec  Nothing Nothing (Just db) Nothing Nothing Nothing Nothing Nothing Nothing (Just crv') (Just xb) (Just yb) u a i Nothing Nothing Nothing ->-        return $ EcPrivateJwk (ECDSA.KeyPair (curve crv') (ecPoint xb yb) (os2ip (bytes db))) i u a crv'-    _ -> Left $ "Invalid key data. Didn't match any known JWK parameter combinations:" ++ show kd+createJwk J {..} = case kty of+    Rsa -> do+        nb <- note "n is required for an RSA key" n+        eb <- note "e is required for an RSA key" e+        checkNoEc+        let kPub = rsaPub nb eb+        case d of+            Nothing -> do+                unless (isNothing (sequence [p, q, dp, dq, qi])) (Left "RSA private parameters can't be set for a public key")+                return (RsaPublicJwk kPub kid use alg)+            Just db -> return $ RsaPrivateJwk (RSA.PrivateKey kPub (os2ip (bytes db)) (os2mip p) (os2mip q) (os2mip dp) (os2mip dq) (os2mip qi)) kid use alg+    Oct -> do+        kb <- note "k is required for a symmetric key" k+        unless (isNothing (sequence [n, e, d, p, q, dp, dq, qi])) (Left "RSA parameters can't be set for a symmetric key")+        checkNoEc+        return $ SymmetricJwk (bytes kb) kid use alg+    Ec  -> do+        crv' <- note "crv is required for an elliptic curve key" crv+        let c = curve crv'+        ecPt <- ecPoint+        unless (isNothing (sequence [n, e, p, q, dp, dq, qi])) (Left "RSA parameters can't be set for an elliptic curve key")+        case d of+            Nothing -> return $ EcPublicJwk (ECDSA.PublicKey c ecPt) kid use alg crv'+            Just db -> return $ EcPrivateJwk (ECDSA.KeyPair c ecPt (os2ip (bytes db))) kid use alg crv'   where-    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)-    os2mip         = maybe 0 (os2ip . bytes)-    ecPoint xb yb  = ECC.Point (os2ip (bytes xb)) (os2ip (bytes yb))-+    checkNoEc = unless (isNothing crv) (Left "Elliptic curve type can't be set for an RSA key") >>+       unless (isNothing (sequence [x, y])) (Left "Elliptic curve coordinates can't be set for an RSA key")+    note err      = maybe (Left err) Right+    os2mip        = maybe 0 (os2ip . bytes)+    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)+    ecPoint       = do+        xb <- note "x is required for an EC key" x+        yb <- note "y is required for an EC key" y+        return $ ECC.Point (os2ip (bytes xb)) (os2ip (bytes yb))
Jose/Jws.hs view
@@ -28,7 +28,7 @@ import Control.Monad (unless) import qualified Crypto.PubKey.ECC.ECDSA as ECDSA import Crypto.PubKey.RSA (PrivateKey(..), PublicKey(..), generateBlinder)-import Crypto.Random (CPRG)+import Crypto.Random (MonadRandom) import Data.ByteString (ByteString) import qualified Data.ByteString as B import qualified Data.ByteString.Char8 as BC@@ -42,16 +42,15 @@ -- | Create a JWS signed with a JWK. -- The key and algorithm must be consistent or an error -- will be returned.-jwkEncode :: (CPRG g)-          => g-          -> JwsAlg                          -- ^ The algorithm to use+jwkEncode :: MonadRandom m+          => JwsAlg                          -- ^ The algorithm to use           -> Jwk                             -- ^ The key to sign with           -> Payload                         -- ^ The public JWT claims-          -> (Either JwtError Jwt, g)        -- ^ The encoded token, if successful-jwkEncode rng a key payload = case key of-    RsaPrivateJwk kPr kid _ _ -> rsaEncodeInternal rng a kPr (sigTarget a kid payload)-    SymmetricJwk  k   kid _ _ -> (hmacEncodeInternal a k (sigTarget a kid payload), rng)-    _                         -> (Left $ BadAlgorithm "EC signing is not supported", rng)+          -> m (Either JwtError Jwt)         -- ^ The encoded token, if successful+jwkEncode a key payload = case key of+    RsaPrivateJwk kPr kid _ _ -> rsaEncodeInternal a kPr (sigTarget a kid payload)+    SymmetricJwk  k   kid _ _ -> return $ hmacEncodeInternal a k (sigTarget a kid payload)+    _                         -> return $ Left $ BadAlgorithm "EC signing is not supported"  -- | Create a JWS with an HMAC for validation. hmacEncode :: JwsAlg       -- ^ The MAC algorithm to use@@ -73,24 +72,22 @@ hmacDecode key = decode (`hmacVerify` key)  -- | Creates a JWS with an RSA signature.-rsaEncode :: CPRG g-          => g-          -> JwsAlg                           -- ^ The RSA algorithm to use+rsaEncode :: MonadRandom m+          => JwsAlg                           -- ^ The RSA algorithm to use           -> PrivateKey                       -- ^ The key to sign with           -> ByteString                       -- ^ The public JWT claims (token content)-          -> (Either JwtError Jwt, g)  -- ^ The encoded JWS token-rsaEncode rng a pk payload = rsaEncodeInternal rng a pk (sigTarget a Nothing (Claims payload))+          -> m (Either JwtError Jwt)          -- ^ The encoded JWS token+rsaEncode a pk payload = rsaEncodeInternal a pk (sigTarget a Nothing (Claims payload)) -rsaEncodeInternal :: CPRG g-                  => g-                  -> JwsAlg+rsaEncodeInternal :: MonadRandom m+                  => JwsAlg                   -> PrivateKey                   -> ByteString-                  -> (Either JwtError Jwt, g)-rsaEncodeInternal rng a pk st = (sign blinder, rng')+                  -> m (Either JwtError Jwt)+rsaEncodeInternal a pk st = do+    blinder <- generateBlinder (public_n $ private_pub pk)+    return $ sign blinder   where-    (blinder, rng') = generateBlinder rng (public_n $ private_pub pk)-     sign b = case rsaSign (Just b) a pk st of         Right sig -> Right . Jwt $ B.concat [st, ".", B64.encode sig]         Left e    -> Left e@@ -135,4 +132,3 @@   where     spanEndDot bs = let (toDot, end) = BC.spanEnd (/= '.') bs                     in  (B.init toDot, end)-
Jose/Jwt.hs view
@@ -10,12 +10,12 @@ -- >>> import Jose.Jwk -- >>> import Data.ByteString -- >>> import Data.Aeson (decodeStrict)--- >>> import Crypto.Random.AESCtr--- >>> g <- makeSystem+-- >>> import Crypto.Random+-- >>> g <- drgNew -- >>> 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] (JwsEncoding RS256) (Claims "public claims")--- >>> let (Right jwtDecoded, g'') = Jose.Jwt.decode g' [jwk] (Just (JwsEncoding RS256)) jwtEncoded+-- >>> let (Right (Jwt jwtEncoded), g') = withDRG g $ encode [jwk] (JwsEncoding RS256) (Claims "public claims")+-- >>> let (Right jwtDecoded, g'') = withDRG g' $ Jose.Jwt.decode [jwk] (Just (JwsEncoding RS256)) jwtEncoded -- >>> jwtDecoded -- Jws (JwsHeader {jwsAlg = RS256, jwsTyp = Nothing, jwsCty = Nothing, jwsKid = Just (KeyId "mykey")},"public claims") @@ -27,11 +27,12 @@     ) where -import Control.Monad.State.Strict+import Control.Monad (when, unless, liftM)+import Control.Monad.Trans (lift) import Control.Monad.Trans.Either import qualified Crypto.PubKey.ECC.ECDSA as ECDSA import Crypto.PubKey.RSA (PrivateKey(..))-import Crypto.Random (CPRG)+import Crypto.Random (MonadRandom) import Data.Aeson (decodeStrict') import Data.ByteString (ByteString) import Data.List (find)@@ -51,43 +52,42 @@ -- The list of keys will be searched to locate one which is -- consistent with the chosen encoding algorithms. ---encode :: (CPRG g)-       => g                          -- ^ Random number generator-       -> [Jwk]                      -- ^ The key or keys. At least one must be consistent with the chosen algorithm-       -> 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 encoding msg = flip runState rng $ runEitherT $ case encoding of+encode :: MonadRandom m+    => [Jwk]                     -- ^ The key or keys. At least one must be consistent with the chosen algorithm+    -> JwtEncoding               -- ^ The encoding algorithm(s) used to encode the payload+    -> Payload                   -- ^ The payload (claims)+    -> m (Either JwtError Jwt)   -- ^ The encoded JWT, if successful+encode jwks encoding msg = 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)+        (k:_) -> hoistEither =<< lift (Jws.jwkEncode 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)+        (k:_) -> hoistEither =<< lift (Jwe.jwkEncode a e k msg)   where-    unsecuredHdr = B64.encode "{\"alg\":\"none\"}"+    unsecuredHdr = B64.encode (BC.pack "{\"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 for the encoding algorithm. ----- The algorithm(s) used can be optionally be supplied for validation+-- The algorithm(s) used can optionally be supplied for validation -- by setting the @JwtEncoding@ parameter, in which case an error will--- be returned if they don't match.+-- be returned if they don't match. If you expect the tokens to use+-- a particular algorithm, then you should set this parameter. -- -- 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 encoding jwt = flip runState rng $ runEitherT $ do+decode :: MonadRandom m+    => [Jwk]                           -- ^ The keys to use for decoding+    -> Maybe JwtEncoding               -- ^ The expected encoding information+    -> ByteString                      -- ^ The encoded JWT+    -> m (Either JwtError JwtContent)  -- ^ The decoded JWT payload, if successful+decode keySet encoding jwt = runEitherT $ do     let components = BC.split '.' jwt     when (length components < 3) $ left $ BadDots 2     hdr <- B64.decode (head components) >>= hoistEither . parseHeader@@ -106,7 +106,7 @@             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)+    decodeWithJws :: MonadRandom m => Jwk -> EitherT JwtError m (Maybe JwtContent)     decodeWithJws k = either (const $ return Nothing) (return . Just . Jws) $ case k of         RsaPublicJwk  kPub _ _ _ -> Jws.rsaDecode kPub jwt         RsaPrivateJwk kPr  _ _ _ -> Jws.rsaDecode (private_pub kPr) jwt@@ -114,12 +114,8 @@         EcPrivateJwk  kPr  _ _ _ _ -> Jws.ecDecode (ECDSA.toPublicKey kPr) jwt         SymmetricJwk  kb   _ _ _ -> Jws.hmacDecode kb jwt -    decodeWithJwe :: CPRG g => Jwk -> EitherT JwtError (State g) (Maybe JwtContent)-    decodeWithJwe k = case k of-        RsaPrivateJwk kPr _ _ _ -> do-            e <- state (\g -> Jwe.rsaDecode g kPr jwt)-            either (const $ return Nothing) (return . Just . Jwe) e-        _                       -> left $ KeyError "Not a JWE key (shouldn't happen)"+    decodeWithJwe :: MonadRandom m => Jwk -> EitherT JwtError m (Maybe JwtContent)+    decodeWithJwe k = liftM (either (const Nothing) Just) (lift (Jwe.jwkDecode k jwt))  -- | Convenience function to return the claims contained in a JWT. -- This is required in situations such as client assertion authentication,
jose-jwt.cabal view
@@ -1,20 +1,16 @@ Name:               jose-jwt-Version:            0.6.2+Version:            0.7 Synopsis:           JSON Object Signing and Encryption Library Homepage:           http://github.com/tekul/jose-jwt Bug-Reports:        http://github.com/tekul/jose-jwt/issues Description:     .-    Intended to provide support for the JOSE suite of IETF (draft)-    standards and the closely related JWT (JSON web token) spec-    (<http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-32/>).+    An implementation of the JOSE suite of IETF standards+    and the closely related JWT (JSON web token) spec+    (<https://tools.ietf.org/html/rfc7519/>).     .     Both signed and encrypted JWTs are supported, as well as simple-    JWK format keys.-    .-    The library is currently intended to support work on an OpenID-    Connect implementation and the APIs should not be considered-    complete, stable or secure for all use cases.+    JWK keys.  Author:             Luke Taylor <tekul.hs@gmail.com> Maintainer:         Luke Taylor <tekul.hs@gmail.com>@@ -47,24 +43,17 @@                     , Jose.Internal.Crypto   Other-Modules:      Jose.Types   Build-Depends:      base >= 4 && < 5-                    , mtl >= 2.1.3.1+                    , aeson >= 0.8.0.2                     , bytestring >= 0.9-                    , byteable >= 0.1.1                     , cereal >= 0.4                     , containers >= 0.4-                    , cryptohash >= 0.8-                    , crypto-cipher-types >= 0.0.9-                    , crypto-pubkey >= 0.2.5-                    , crypto-pubkey-types >= 0.4-                    , crypto-random >= 0.0.7-                    , crypto-numbers >= 0.2-                    , cipher-aes >= 0.2.6+                    , cryptonite >= 0.3                     , either-                    , aeson >= 0.8.1.0+                    , memory >= 0.10+                    , mtl >= 2.1.3.1                     , text  >= 0.11                     , time  >= 1.4                     , unordered-containers >= 0.2-                    , base64-bytestring >= 1                     , vector >= 0.10   Ghc-Options:        -Wall @@ -76,19 +65,16 @@                     , Tests.JwkSpec   Build-depends:      jose-jwt                     , base+                    , aeson                     , aeson-qq                     , bytestring-                    , base64-bytestring-                    , cryptohash-                    , crypto-pubkey-                    , crypto-pubkey-types-                    , crypto-random-                    , crypto-cipher-types-                    , cipher-aes+                    , cryptonite                     , either >= 4.0+                    , memory                     , mtl                     , text-                    , aeson+                    , unordered-containers+                    , vector                     , hspec >= 1.6                     , HUnit >= 1.2                     , QuickCheck >= 2.4@@ -100,14 +86,14 @@   Default-Language:   Haskell2010   Type:               exitcode-stdio-1.0   Main-is:            doctests.hs-  Ghc-options:        -XOverloadedStrings+  Default-Extensions: OverloadedStrings    if !flag(doctest)     Buildable: False   else     Build-depends:    base                     , doctest >= 0.9.11-                    , cprng-aes+                    , cryptonite  Benchmark bench-jwt   Default-Language:   Haskell2010@@ -119,8 +105,5 @@                     , base                     , bytestring                     , criterion-                    , crypto-pubkey-                    , crypto-random    Ghc-Options:        -Wall -O2-
tests/Tests/JweSpec.hs view
@@ -4,10 +4,11 @@ module Tests.JweSpec where  import Control.Applicative-import Data.Aeson (eitherDecode, decodeStrict')+import Data.Aeson (decodeStrict') import Data.Bits (xor)-import Data.Either.Combinators-import Data.Word (Word8)+import Data.Maybe (fromJust)+import Data.Word (Word8, Word64)+import qualified Data.ByteArray as BA import qualified Data.ByteString as B import qualified Data.ByteString.Char8 as BC import Test.Hspec@@ -15,11 +16,11 @@ import Test.QuickCheck  import qualified Crypto.PubKey.RSA as RSA+import Crypto.Hash (SHA1(..), hashDigestSize) import Crypto.PubKey.RSA.Prim (dp) import Crypto.PubKey.MaskGenFunction-import Crypto.PubKey.HashDescr import Crypto.Cipher.Types (AuthTag(..))-import Crypto.Random (CPRG(..))+import Crypto.Random (DRG(..), withDRG, drgNewTest) import Jose.Jwt import qualified Jose.Jwe as Jwe import Jose.Jwa@@ -37,122 +38,139 @@     context "when using JWE Appendix 1 data" $ do       let a1Header = defJweHdr {jweAlg = RSA_OAEP, jweEnc = A256GCM} -      it "generates the expected IV and CMK from the RNG" $ do-        let g = RNG $ B.append a1cek a1iv-        generateCmkAndIV g A256GCM @?= ((a1cek, a1iv), RNG "")+      it "generates the expected IV and CMK from the RNG" $+        withDRG (RNG $ B.append a1cek a1iv)+            (generateCmkAndIV A256GCM) @?= ((a1cek, a1iv), RNG "") -      it "generates the expected RSA-encrypted content key" $ do-        let g = RNG a1oaepSeed-        rsaEncrypt g RSA_OAEP a1PubKey a1cek @?= (a1jweKey, RNG "")+      it "generates the expected RSA-encrypted content key" $+        withDRG (RNG a1oaepSeed)+            (rsaEncrypt a1PubKey RSA_OAEP a1cek) @?= (Right a1jweKey, RNG "")        it "encrypts the payload to the expected ciphertext and authentication tag" $ do         let aad = B64.encode . encodeHeader $ a1Header-        encryptPayload A256GCM a1cek a1iv aad a1Payload @?= (a1Ciphertext, AuthTag a1Tag)+        encryptPayload A256GCM a1cek a1iv aad a1Payload @?= Just (AuthTag a1Tag, a1Ciphertext) -      it "encodes the payload to the expected JWT, leaving the RNG empty" $ do-        let g = RNG $ B.concat [a1cek, a1iv, a1oaepSeed]-        Jwe.rsaEncode g RSA_OAEP A256GCM a1PubKey a1Payload @?= (Jwt a1, RNG "")+      it "encodes the payload to the expected JWT, leaving the RNG empty" $+        withDRG (RNG $ B.concat [a1cek, a1iv, a1oaepSeed])+            (Jwe.rsaEncode RSA_OAEP A256GCM a1PubKey a1Payload) @?= (Right (Jwt a1), RNG "")        it "decodes the JWT to the expected header and payload" $-        fst (Jwe.rsaDecode blinderRNG a1PrivKey a1) @?= Right (a1Header, a1Payload)+        withBlinder (Jwe.rsaDecode 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+        let Just (Jwk.RsaPrivateJwk (RSA.PrivateKey pubKey d 0 0 0 0 0) _ _ _) = decodeStrict' a1jwk         RSA.public_n pubKey  @?= a1RsaModulus         RSA.public_e pubKey  @?= rsaExponent         d                    @?= a1RsaPrivateExponent        it "decodes the JWT using the JWK" $ do-        let Right k1 = eitherDecode a1jwk-            Just  k2 = decodeStrict' a2jwk-        fst (decode blinderRNG [k2, k1] (Just $ JweEncoding RSA_OAEP A256GCM) a1) @?= (Right $ Jwe (a1Header, a1Payload))+        let Just k1 = decodeStrict' a1jwk+            Just k2 = decodeStrict' a2jwk+        withBlinder (decode [k2, k1] (Just $ JweEncoding RSA_OAEP A256GCM) a1) @?= (Right $ Jwe (a1Header, a1Payload)) -      it "a truncated CEK returns BadSignature" $ do+      it "a truncated CEK returns BadCrypto" $ 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+            (Right newEk, _) = withDRG blinderRNG (rsaEncrypt a1PubKey RSA_OAEP (B.tail a1cek))+        withBlinder (Jwe.rsaDecode a1PrivKey (B.intercalate "." [hdr, B64.encode newEk, iv, payload, tag])) @?= Left BadCrypto -      it "a truncated payload returns BadSignature" $ do+      it "a truncated payload returns BadCrypto" $ 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+        withBlinder (Jwe.rsaDecode a1PrivKey (B.intercalate "." [hdr, ek, iv, B64.encode (B.tail ct), tag])) @?= Left BadCrypto -      it "a truncated IV returns BadSignature" $ do+      it "a truncated IV returns BadCrypto" $ 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+        withBlinder (Jwe.rsaDecode a1PrivKey (B.concat [fore, newIv, aft])) @?= Left BadCrypto       context "when using JWE Appendix 2 data" $ do       let a2Header = defJweHdr {jweAlg = RSA1_5, jweEnc = A128CBC_HS256}       let aad = B64.encode . encodeHeader $ a2Header -      it "generates the expected RSA-encrypted content key" $ do-        let g = RNG a2seed-        rsaEncrypt g RSA1_5 a2PubKey a2cek @?= (a2jweKey, RNG "")+      it "generates the expected RSA-encrypted content key" $+        withDRG (RNG a2seed) (rsaEncrypt a2PubKey RSA1_5 a2cek) @?= (Right a2jweKey, RNG "")        it "encrypts the payload to the expected ciphertext and authentication tag" $-        encryptPayload A128CBC_HS256 a2cek a2iv aad a2Payload @?= (a2Ciphertext, AuthTag a2Tag)+        encryptPayload A128CBC_HS256 a2cek a2iv aad a2Payload @?= Just (a2Tag, a2Ciphertext) -      it "encodes the payload to the expected JWT" $ do-        let g = RNG $ B.concat [a2cek, a2iv, a2seed]-        Jwe.rsaEncode g RSA1_5 A128CBC_HS256 a2PubKey a2Payload @?= (Jwt a2, RNG "")+      it "encodes the payload to the expected JWT" $+        withDRG (RNG $ B.concat [a2cek, a2iv, a2seed])+            (Jwe.rsaEncode RSA1_5 A128CBC_HS256 a2PubKey a2Payload) @?= (Right (Jwt a2), RNG "")        it "decrypts the ciphertext to the correct payload" $-        decryptPayload A128CBC_HS256 a2cek a2iv aad a2Tag a2Ciphertext @?= Right a2Payload+        decryptPayload A128CBC_HS256 a2cek a2iv aad a2Tag a2Ciphertext @?= Just a2Payload        it "decodes the JWT to the expected header and payload" $-        fst (Jwe.rsaDecode blinderRNG a2PrivKey a2) @?= Right (a2Header, a2Payload)+        withBlinder (Jwe.rsaDecode 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+            (Right newEk, _) = withDRG blinderRNG $ rsaEncrypt a2PubKey RSA1_5 (B.tail a2cek)+        withBlinder (Jwe.rsaDecode 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+        withBlinder (Jwe.rsaDecode a2PrivKey (B.intercalate "." [hdr, ek, iv, B64.encode (B.tail ct), tag])) @?= Left BadCrypto -      it "a truncated IV returns BadSignature" $ do+      it "a truncated IV returns BadCrypto" $ 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+        withBlinder (Jwe.rsaDecode a2PrivKey (B.concat [fore, newIv, aft])) @?= Left BadCrypto +    context "when using JWE Appendix 3 data" $ do+      let Just jwk = decodeStrict' a3jwk+          a3Header = defJweHdr {jweAlg = A128KW, jweEnc = A128CBC_HS256}+      it "encodes the payload to the epected JWT" $+        withDRG (RNG $ B.concat [a3cek, a3iv])+            (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))+     context "when used with quickcheck" $ do       it "padded msg is always a multiple of 16" $ property $         \s -> B.length (pad (B.pack s)) `mod` 16 == 0       it "unpad is the inverse of pad" $ property $-        \s -> let msg = B.pack s in (fromRight' . unpad . pad) msg == msg+        \s -> let msg = B.pack s in (fromJust . unpad . pad) msg == msg       it "jwe decode/decode returns the original payload" $ property jweRoundTrip +    context "miscellaneous tests" $ do+      it "Padding byte larger than 16 is rejected" $+        unpad "111a" @?= Nothing+      it "Padding byte which doesn't match padding length is rejected" $+        unpad "111\t\t\t\t\t\t\t" @?= Nothing+      it "Padding byte which matches padding length is OK" $+        unpad "1111111\t\t\t\t\t\t\t\t\t" @?= Just "1111111"+ -- verboseQuickCheckWith quickCheckWith stdArgs {maxSuccess=10000}  jweRoundTrip jweRoundTrip :: RNG -> JWEAlgs -> [Word8] -> Bool-jweRoundTrip g (JWEAlgs a e) msg = encodeDecode == Right (defJweHdr {jweAlg = a, jweEnc = e}, bs)+jweRoundTrip g (JWEAlgs a e) msg = encodeDecode == Right (Jwe (defJweHdr {jweAlg = a, jweEnc = e}, bs))   where+    jwks = map (fromJust . decodeStrict') [a1jwk, a2jwk, a3jwk]     bs = B.pack msg-    encodeDecode = fst $ Jwe.rsaDecode blinderRNG a2PrivKey $ unJwt $ fst $ Jwe.rsaEncode g a e a2PubKey bs+    encodeDecode = fst (withDRG blinderRNG (decode jwks Nothing encoded))+    Right encoded = unJwt <$> fst (withDRG g (encode jwks (JweEncoding a e) (Claims bs))) +withBlinder = fst . withDRG blinderRNG+ -- A decidedly non-random, random number generator which allows specific -- sequences of bytes to be supplied which match the JWE test data. data RNG = RNG B.ByteString deriving (Eq, Show) -genBytes :: Int -> RNG -> (B.ByteString, RNG)-genBytes 0 g = (B.empty, g)-genBytes n (RNG bs) = (bytes, RNG next)+genBytes :: BA.ByteArray ba => Int -> RNG -> (ba, RNG)+genBytes 0 g = (BA.empty, g)+genBytes n (RNG bs) = (BA.convert bytes, RNG next)   where-    (bytes, next) = if B.null bs-                      then error "RNG is empty"-                      else B.splitAt n bs+    (bytes, next) = if BA.null bs+                        then error "RNG is empty"+                        else BA.splitAt n bs -instance CPRG RNG where-    cprgCreate   = undefined-    cprgSetReseedThreshold = undefined-    cprgGenerate = genBytes-    cprgGenerateWithEntropy = undefined-    cprgFork = undefined+instance DRG RNG where+    randomBytesGenerate = genBytes -blinderRNG = RNG $ B.replicate 2000 255+blinderRNG = drgNewTest (w, w, w, w, w) where w = 1 :: Word64  -------------------------------------------------------------------------------- -- JWE Appendix 1 Test Data@@ -171,9 +189,9 @@  a1Ciphertext = B.pack [229, 236, 166, 241, 53, 191, 115, 196, 174, 43, 73, 109, 39, 122, 233, 96, 140, 206, 120, 52, 51, 237, 48, 11, 190, 219, 186, 80, 111, 104, 50, 142, 47, 167, 59, 61, 181, 127, 196, 21, 40, 82, 242, 32, 123, 143, 168, 226, 73, 216, 176, 144, 138, 247, 106, 60, 16, 205, 160, 109, 64, 63, 192] -a1Tag = B.pack [92, 80, 104, 49, 133, 25, 161, 215, 173, 101, 219, 211, 136, 91, 210, 145]+a1Tag = BA.pack [92, 80, 104, 49, 133, 25, 161, 215, 173, 101, 219, 211, 136, 91, 210, 145] -Right a1jweKey = B64.decode "OKOawDo13gRp2ojaHV7LFpZcgV7T6DVZKTyKOMTYUmKoTCVJRgckCL9kiMT03JGeipsEdY3mx_etLbbWSrFr05kLzcSr4qKAq7YN7e9jwQRb23nfa6c9d-StnImGyFDbSv04uVuxIp5Zms1gNxKKK2Da14B8S4rzVRltdYwam_lDp5XnZAYpQdb76FdIKLaVmqgfwX7XWRxv2322i-vDxRfqNzo_tETKzpVLzfiwQyeyPGLBIO56YJ7eObdv0je81860ppamavo35UgoRdbYaBcoh9QcfylQr66oc6vFWXRcZ_ZT2LawVCWTIy3brGPi6UklfCpIMfIjf7iGdXKHzg"+Right a1jweKey = B64.decode $ BC.pack "OKOawDo13gRp2ojaHV7LFpZcgV7T6DVZKTyKOMTYUmKoTCVJRgckCL9kiMT03JGeipsEdY3mx_etLbbWSrFr05kLzcSr4qKAq7YN7e9jwQRb23nfa6c9d-StnImGyFDbSv04uVuxIp5Zms1gNxKKK2Da14B8S4rzVRltdYwam_lDp5XnZAYpQdb76FdIKLaVmqgfwX7XWRxv2322i-vDxRfqNzo_tETKzpVLzfiwQyeyPGLBIO56YJ7eObdv0je81860ppamavo35UgoRdbYaBcoh9QcfylQr66oc6vFWXRcZ_ZT2LawVCWTIy3brGPi6UklfCpIMfIjf7iGdXKHzg"  a1jwk = "{\"kty\":\"RSA\", \"n\":\"oahUIoWw0K0usKNuOR6H4wkf4oBUXHTxRvgb48E-BVvxkeDNjbC4he8rUWcJoZmds2h7M70imEVhRU5djINXtqllXI4DFqcI1DgjT9LewND8MW2Krf3Spsk_ZkoFnilakGygTwpZ3uesH-PFABNIUYpOiN15dsQRkgr0vEhxN92i2asbOenSZeyaxziK72UwxrrKoExv6kc5twXTq4h-QChLOln0_mtUZwfsRaMStPs6mS6XrgxnxbWhojf663tuEQueGC-FCMfra36C9knDFGzKsNa7LZK2djYgyD3JR_MB_4NUJW_TqOQtwHYbxevoJArm-L5StowjzGy-_bq6Gw\", \"e\":\"AQAB\", \"d\":\"kLdtIj6GbDks_ApCSTYQtelcNttlKiOyPzMrXHeI-yk1F7-kpDxY4-WY5NWV5KntaEeXS1j82E375xxhWMHXyvjYecPT9fpwR_M9gV8n9Hrh2anTpTD93Dt62ypW3yDsJzBnTnrYu1iwWRgBKrEYY46qAZIrA2xAwnm2X7uGR1hghkqDp0Vqj3kbSCz1XyfCs6_LehBwtxHIyh8Ripy40p24moOAbgxVw3rxT_vlt3UVe4WO3JkJOzlpUf-KTVI2Ptgm-dARxTEtE-id-4OJr0h-K-VFs3VSndVTIznSxfyrj8ILL6MG_Uv8YAu7VILSB3lOW085-4qE3DzgrTjgyQ\" }" @@ -204,9 +222,9 @@  a2Ciphertext = B.pack [40, 57, 83, 181, 119, 33, 133, 148, 198, 185, 243, 24, 152, 230, 6, 75, 129, 223, 127, 19, 210, 82, 183, 230, 168, 33, 215, 104, 143, 112, 56, 102] -a2Tag = B.pack [246, 17, 244, 190, 4, 95, 98, 3, 231, 0, 115, 157, 242, 203, 100, 191]+a2Tag = AuthTag $ BA.pack [246, 17, 244, 190, 4, 95, 98, 3, 231, 0, 115, 157, 242, 203, 100, 191] -Right a2jweKey = B64.decode "UGhIOguC7IuEvf_NPVaXsGMoLOmwvc1GyqlIKOK1nN94nHPoltGRhWhw7Zx0-kFm1NJn8LE9XShH59_i8J0PH5ZZyNfGy2xGdULU7sHNF6Gp2vPLgNZ__deLKxGHZ7PcHALUzoOegEI-8E66jX2E4zyJKx-YxzZIItRzC5hlRirb6Y5Cl_p-ko3YvkkysZIFNPccxRU7qve1WYPxqbb2Yw8kZqa2rMWI5ng8OtvzlV7elprCbuPhcCdZ6XDP0_F8rkXds2vE4X-ncOIM8hAYHHi29NX0mcKiRaD0-D-ljQTP-cFPgwCp6X-nZZd9OHBv-B3oWh2TbqmScqXMR4gp_A"+Right a2jweKey = B64.decode $ BC.pack "UGhIOguC7IuEvf_NPVaXsGMoLOmwvc1GyqlIKOK1nN94nHPoltGRhWhw7Zx0-kFm1NJn8LE9XShH59_i8J0PH5ZZyNfGy2xGdULU7sHNF6Gp2vPLgNZ__deLKxGHZ7PcHALUzoOegEI-8E66jX2E4zyJKx-YxzZIItRzC5hlRirb6Y5Cl_p-ko3YvkkysZIFNPccxRU7qve1WYPxqbb2Yw8kZqa2rMWI5ng8OtvzlV7elprCbuPhcCdZ6XDP0_F8rkXds2vE4X-ncOIM8hAYHHi29NX0mcKiRaD0-D-ljQTP-cFPgwCp6X-nZZd9OHBv-B3oWh2TbqmScqXMR4gp_A"  a2jwk = "{\"kty\":\"RSA\", \"n\":\"sXchDaQebHnPiGvyDOAT4saGEUetSyo9MKLOoWFsueri23bOdgWp4Dy1WlUzewbgBHod5pcM9H95GQRV3JDXboIRROSBigeC5yjU1hGzHHyXss8UDprecbAYxknTcQkhslANGRUZmdTOQ5qTRsLAt6BTYuyvVRdhS8exSZEy_c4gs_7svlJJQ4H9_NxsiIoLwAEk7-Q3UXERGYw_75IDrGA84-lA_-Ct4eTlXHBIY2EaV7t7LjJaynVJCpkv4LKjTTAumiGUIuQhrNhZLuF_RJLqHpM2kgWFLU7-VTdL1VbC2tejvcI2BlMkEpk1BzBZI0KQB0GaDWFLN-aEAw3vRw\", \"e\":\"AQAB\", \"d\":\"VFCWOqXr8nvZNyaaJLXdnNPXZKRaWCjkU5Q2egQQpTBMwhprMzWzpR8Sxq1OPThh_J6MUD8Z35wky9b8eEO0pwNS8xlh1lOFRRBoNqDIKVOku0aZb-rynq8cxjDTLZQ6Fz7jSjR1Klop-YKaUHc9GsEofQqYruPhzSA-QgajZGPbE_0ZaVDJHfyd7UUBUKunFMScbflYAAOYJqVIVwaYR5zWEEceUjNnTNo_CVSj-VvXLO5VZfCUAVLgW4dpf1SrtZjSt34YLsRarSb127reG_DUwg9Ch-KyvjT1SkHgUWRVGcyly7uvVGRSDwsXypdrNinPA4jlhoNdizK2zF2CWQ\" }" @@ -221,7 +239,19 @@  a2seed = extractPKCS15Seed a2PrivKey a2jweKey +a3Payload = a2Payload +a3jwk = "{\"kty\":\"oct\", \"k\":\"GawgguFyGrWKav7AX4VKUg\"}"++a3cek = B.pack [4, 211, 31, 197, 84, 157, 252, 254, 11, 100, 157, 250, 63, 170, 106, 206, 107, 124, 212, 45, 111, 107, 9, 219, 200, 177, 0, 240, 143, 156, 44, 207]++a3iv = B.pack [3, 22, 60, 12, 43, 67, 104, 105, 108, 108, 105, 99, 111, 116, 104, 101]++a3 :: B.ByteString+a3 = "eyJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.6KB707dM9YTIgHtLvtgWQ8mKwboJW3of9locizkDTHzBC2IlrT1oOQ.AxY8DCtDaGlsbGljb3RoZQ.KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY.U0m_YmjN04DJvceFICbCVQ"+++ -------------------------------------------------------------------------------- -- Quickcheck Stuff --------------------------------------------------------------------------------@@ -230,19 +260,18 @@ data JWEAlgs = JWEAlgs JweAlg Enc deriving Show  instance Arbitrary Enc where-    arbitrary = elements [A128CBC_HS256, A256CBC_HS512, A128GCM, A256GCM]+    arbitrary = elements [A128CBC_HS256, A192CBC_HS384, A256CBC_HS512, A128GCM, A192GCM, A256GCM]  instance Arbitrary JWEAlgs where   arbitrary = do-    a <- elements [RSA1_5, RSA_OAEP]-    e <- elements [A128CBC_HS256, A256CBC_HS512, A128GCM, A256GCM]+    a <- elements [RSA1_5, RSA_OAEP, A128KW, A192KW, A256KW]+    e <- arbitrary     return $ JWEAlgs a e  instance Arbitrary RNG where   arbitrary = (RNG . B.pack) <$> vector 600  - -------------------------------------------------------------------------------- -- Utility Functions --------------------------------------------------------------------------------@@ -270,11 +299,10 @@ extractOaepSeed key ct = B.pack $ B.zipWith xor maskedSeed seedMask   where     em       = dp Nothing key ct-    sha1     = hashFunction hashDescrSHA1-    hashLen  = B.length $ sha1 B.empty+    hashLen  = hashDigestSize SHA1     em0      = B.tail em     (maskedSeed, maskedDB) = B.splitAt hashLen em0-    seedMask = mgf1 sha1 maskedDB hashLen+    seedMask = mgf1 SHA1 maskedDB hashLen   -- Decrypt, drop the 02 at the start and take the bytes up to the next 0 extractPKCS15Seed :: RSA.PrivateKey -> B.ByteString -> B.ByteString
tests/Tests/JwkSpec.hs view
@@ -8,8 +8,12 @@ import Data.Aeson import Data.Aeson.QQ import qualified Data.ByteString.Char8 ()-import Crypto.Types.PubKey.ECDSA-import Crypto.Types.PubKey.ECC+import qualified Data.HashMap.Strict as H+import Data.Word (Word64)+import qualified Data.Vector as V+import Crypto.PubKey.ECC.ECDSA+import Crypto.PubKey.ECC.Types+import Crypto.Random (drgNewTest, withDRG)  import Jose.Jwt (defJwsHdr, JwsHeader(..), KeyId(..)) import Jose.Jwk@@ -37,15 +41,20 @@ spec :: Spec spec = do     let Success s@(JwkSet _) = fromJSON (Object keySet) :: Result JwkSet-    describe "JWK encoding and decoding" $+        Just s'  = decode' (encode s) :: Maybe JwkSet+        Just s'' = decode' (encode s) :: Maybe JwkSet+        kss      = keys s'+        k0       = head kss+        k1       = kss !! 1+        k3       = kss !! 3+        k4       = kss !! 4+    describe "JWK encoding and decoding" $ do         it "decodes and encodes an entire key set successfully" $ do-            let Just s' = decode' (encode s) :: Maybe JwkSet-                kss = keys s'-                RsaPublicJwk  _ key0Id key0Use    a0   = head kss-                RsaPublicJwk  _ key1Id key1Use    _    = kss !! 1+            let RsaPublicJwk  _ key0Id key0Use    a0   = k0+                RsaPublicJwk  _ key1Id key1Use    _    = k1                 EcPublicJwk   k key2Id key2Use    _ _  = kss !! 2-                EcPublicJwk   _ key3Id key3Use    _ _  = kss !! 3-                SymmetricJwk  _ key4Id Nothing    _    = kss !! 4+                EcPublicJwk _ key3Id key3Use  _ _      = k3+                SymmetricJwk _ key4Id Nothing _        = k4                 EcPublicJwk   _ key5Id (Just Enc) _ _  = kss !! 5                 RsaPublicJwk  _ key6Id Nothing    a6   = kss !! 6                 EcPrivateJwk  _ key7Id (Just Enc) _ _  = kss !! 7@@ -68,7 +77,24 @@             key3Use @?= Just Enc             a6      @?= Just (Signed RS256)             a8      @?= Just (Signed RS256)+            isPublic k3 @?= True+            isPublic k4 @?= False+            isPrivate k4 @?= False+        it "shameless Show and Eq coverage boosting" $ do+            s' @?= s''+            assertBool "Different sets aren't equal" (s' /= JwkSet { keys = take 8 kss ++ [k0]})+            assertBool "Show stuff" $ showCov s' && showCov k0 && showCov k3 && showCov Sig+            assertBool "Different keys should be unequal" (k0 /= k1) +    describe "Errors in JWK data" $ do+        let Just (Array ks) = H.lookup "keys" keySet+            Object k0obj = V.head ks+        it "invalid Base64 returns an error" $ do+            let result = fromJSON (Object $ H.insert "n" (String "NotBase64**") k0obj) :: Result Jwk+            case result of+                Error _ -> assertBool "" True+                _       -> assertFailure "Expected an error for invalid base 64"+     describe "JWK Algorithm matching" $ do         let jwks = keys s         it "finds one key for RS256 encoding" $ do@@ -89,3 +115,24 @@             -- Only key a0 matches. The other 3 RSA keys are signing keys             let jwks' = filter (canEncodeJwe RSA1_5) jwks             length jwks' @?= 1++    describe "RSA Key generation" $ do+        let rng = drgNewTest (w, w, w, w, w) where w = 1 :: Word64+            kid = KeyId "mykey"+            ((kPub, kPr), _) = withDRG rng (generateRsaKeyPair 512 kid Sig Nothing)+        it "keys generated with same RNG are equal" $ do+            let ((kPub', kPr'), _) = withDRG rng (generateRsaKeyPair 512 kid Sig Nothing)+            kPub' @?= kPub+            kPr'  @?= kPr+        it "isPublic and isPrivate are correct for RSA keys" $ do+            isPublic kPub @?= True+            isPublic kPr  @?= False+            isPrivate kPr @?= True+        it "keys have supplied ID" $ do+            jwkId kPr  @?= Just kid+            jwkId kPub @?= Just kid+        it "keys have supplied use" $ do+            jwkUse kPr  @?= Just Sig+            jwkUse kPub @?= Just Sig+  where+    showCov x = showList [x] `seq` showsPrec 1 x `seq` show x `seq` True
tests/Tests/JwsSpec.hs view
@@ -9,28 +9,23 @@ import Data.Aeson (decodeStrict') import qualified Data.ByteString as B import qualified Data.ByteString.Char8 ()-import qualified Crypto.Hash.SHA256 as SHA256-import Crypto.MAC.HMAC (hmac)+import Data.Word (Word64)+import Crypto.Hash.Algorithms (SHA256(..))+import Crypto.MAC.HMAC (HMAC, hmac) import qualified Crypto.PubKey.RSA as RSA import qualified Crypto.PubKey.RSA.PKCS15 as RSAPKCS15-import Crypto.PubKey.HashDescr-import Crypto.Random (CPRG(..))+import Crypto.Random (withDRG, drgNewTest)  import Jose.Jwt import Jose.Jwa import qualified Jose.Internal.Base64 as B64 import qualified Jose.Jws as Jws --- Test CPRG which just produces a stream of '255' bytes-data RNG = RNG deriving (Show, Eq) -instance CPRG RNG where-    cprgCreate              = undefined-    cprgSetReseedThreshold  = undefined-    cprgGenerate n g        = (B.replicate n 255, g)-    cprgGenerateWithEntropy = undefined-    cprgFork                = undefined+testRNG = drgNewTest (w, w, w, w, w) where w = 1 :: Word64 +fstWithRNG = fst . withDRG testRNG+ {-- Examples from the JWS appendix A --}  spec :: Spec@@ -46,7 +41,7 @@          it "decodes the payload using the JWK" $ do           let Just k11 = decodeStrict' a11jwk-          fst (decode RNG [k11] Nothing a11) @?= fmap Jws a11decoded+          fstWithRNG (decode [k11] Nothing a11) @?= fmap Jws a11decoded          it "encodes/decodes using HS512" $           hmacRoundTrip HS512 a11Payload@@ -60,10 +55,10 @@          it "decodes the JWT to the expected header and payload with the JWK" $ do           let Just k21 = decodeStrict' a21jwk-          fst (decode RNG [k21] (Just (JwsEncoding RS256)) a21) @?= (Right $ Jws (defJwsHdr {jwsAlg = RS256}, a21Payload))+          fstWithRNG (decode [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+          let sign = either (error "Sign failed") id . RSAPKCS15.sign Nothing (Just SHA256) rsaPrivateKey           signWithHeader sign a21Header a21Payload @?= a21          it "encodes/decodes using RS256" $@@ -79,15 +74,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] Nothing a31) @?= fmap Jws a31decoded+          fstWithRNG (decode [k31] Nothing a31) @?= fmap Jws a31decoded        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'")+          fstWithRNG (decode [] 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'")+          fstWithRNG (decode [] (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 [] (Just (JwsEncoding None)) jwt61) @?= Right (Unsecured jwt61Payload)+          fstWithRNG (decode [] (Just (JwsEncoding None)) jwt61) @?= Right (Unsecured jwt61Payload)   signWithHeader sign hdr payload = B.intercalate "." [hdrPayload, B64.encode $ sign hdrPayload]@@ -97,7 +92,7 @@ hmacRoundTrip a msg = let Right (Jwt encoded) = Jws.hmacEncode a "asecretkey" msg                      in  Jws.hmacDecode "asecretkey" encoded @?= Right (defJwsHdr {jwsAlg = a}, msg) -rsaRoundTrip a msg = let Right (Jwt encoded) = fst $ Jws.rsaEncode RNG a rsaPrivateKey msg+rsaRoundTrip a msg = let Right (Jwt encoded) = fstWithRNG (Jws.rsaEncode a rsaPrivateKey msg)                      in  Jws.rsaDecode rsaPublicKey encoded @?= Right (defJwsHdr {jwsAlg = a}, msg)  -- Unsecured JWT from section 6.1@@ -155,4 +150,5 @@     , RSA.public_e = rsaExponent     } -a11mac = hmac SHA256.hash 64 hmacKey+a11mac :: B.ByteString -> HMAC SHA256+a11mac = hmac hmacKey