packages feed

jose-jwt 0.8.0 → 0.9.0

raw patch · 13 files changed

+396/−38 lines, 13 filesdep −eitherdep ~basePVP ok

version bump matches the API change (PVP)

Dependencies removed: either

Dependency ranges changed: base

API changes (from Hackage documentation)

+ Jose.Internal.Crypto: ed25519Verify :: JwsAlg -> PublicKey -> ByteString -> ByteString -> Bool
+ Jose.Internal.Crypto: ed448Verify :: JwsAlg -> PublicKey -> ByteString -> ByteString -> Bool
+ Jose.Jwa: EdDSA :: JwsAlg
+ Jose.Jws: ed25519Decode :: PublicKey -> ByteString -> Either JwtError Jws
+ Jose.Jws: ed25519Encode :: SecretKey -> PublicKey -> ByteString -> Jwt
+ Jose.Jws: ed448Decode :: PublicKey -> ByteString -> Either JwtError Jws
+ Jose.Jws: ed448Encode :: SecretKey -> PublicKey -> ByteString -> Jwt
- Jose.Internal.Crypto: decryptPayload :: forall ba. (ByteArray ba) => Enc -> ScrubbedBytes -> IV -> ba -> Tag -> ba -> Maybe ba
+ Jose.Internal.Crypto: decryptPayload :: forall ba. ByteArray ba => Enc -> ScrubbedBytes -> IV -> ba -> Tag -> ba -> Maybe ba
- Jose.Jwt: JwtClaims :: !(Maybe Text) -> !(Maybe Text) -> !(Maybe [Text]) -> !(Maybe IntDate) -> !(Maybe IntDate) -> !(Maybe IntDate) -> !(Maybe Text) -> JwtClaims
+ Jose.Jwt: JwtClaims :: !Maybe Text -> !Maybe Text -> !Maybe [Text] -> !Maybe IntDate -> !Maybe IntDate -> !Maybe IntDate -> !Maybe Text -> JwtClaims
- Jose.Jwt: [jwtAud] :: JwtClaims -> !(Maybe [Text])
+ Jose.Jwt: [jwtAud] :: JwtClaims -> !Maybe [Text]
- Jose.Jwt: [jwtExp] :: JwtClaims -> !(Maybe IntDate)
+ Jose.Jwt: [jwtExp] :: JwtClaims -> !Maybe IntDate
- Jose.Jwt: [jwtIat] :: JwtClaims -> !(Maybe IntDate)
+ Jose.Jwt: [jwtIat] :: JwtClaims -> !Maybe IntDate
- Jose.Jwt: [jwtIss] :: JwtClaims -> !(Maybe Text)
+ Jose.Jwt: [jwtIss] :: JwtClaims -> !Maybe Text
- Jose.Jwt: [jwtJti] :: JwtClaims -> !(Maybe Text)
+ Jose.Jwt: [jwtJti] :: JwtClaims -> !Maybe Text
- Jose.Jwt: [jwtNbf] :: JwtClaims -> !(Maybe IntDate)
+ Jose.Jwt: [jwtNbf] :: JwtClaims -> !Maybe IntDate
- Jose.Jwt: [jwtSub] :: JwtClaims -> !(Maybe Text)
+ Jose.Jwt: [jwtSub] :: JwtClaims -> !Maybe Text
- Jose.Jwt: decodeClaims :: (FromJSON a) => ByteString -> Either JwtError (JwtHeader, a)
+ Jose.Jwt: decodeClaims :: FromJSON a => ByteString -> Either JwtError (JwtHeader, a)

Files

CHANGELOG.md view
@@ -1,3 +1,8 @@+0.9.0+-----++* Support for EdDSA signing algorithms as defined in [RFC 8037](https://tools.ietf.org/html/rfc8037).+ 0.8.0 ----- 
Jose/Internal/Crypto.hs view
@@ -8,6 +8,8 @@ module Jose.Internal.Crypto     ( hmacSign     , hmacVerify+    , ed25519Verify+    , ed448Verify     , rsaSign     , rsaVerify     , rsaEncrypt@@ -32,17 +34,19 @@ import           Crypto.Hash.Algorithms import           Crypto.Number.Serialize (os2ip) import qualified Crypto.PubKey.ECC.ECDSA as ECDSA+import qualified Crypto.PubKey.Ed25519 as Ed25519+import qualified Crypto.PubKey.Ed448 as Ed448 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 (MonadRandom, getRandomBytes) import           Crypto.MAC.HMAC (HMAC (..), hmac) import           Data.Bits (xor)+import           Data.Bifunctor (first) import           Data.ByteArray (ByteArray, ScrubbedBytes) 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)@@ -51,6 +55,10 @@ import           Jose.Types (JwtError(..)) import           Jose.Internal.Parser (IV(..), Tag(..)) +rightToMaybe :: Either a b -> Maybe b+rightToMaybe (Right x) = Just x+rightToMaybe Left{}    = Nothing+ -- | Sign a message with an HMAC key. hmacSign :: JwsAlg      -- ^ HMAC algorithm to use          -> ByteString  -- ^ Key@@ -71,6 +79,41 @@            -> Bool        -- ^ Whether the signature is correct hmacVerify a key msg sig = either (const False) (`BA.constEq` sig) $ hmacSign a key msg ++-- | Verify an Ed25519 signed message+ed25519Verify :: JwsAlg+              -> Ed25519.PublicKey+              -> ByteString+              -- ^ The message/content+              -> ByteString+              -- ^ The signature to check+              -> Bool+              -- ^ Whether the signature is correct+ed25519Verify EdDSA pubKey msg sig =+    case Ed25519.signature sig of+       CryptoPassed sig_ ->+         Ed25519.verify pubKey msg sig_+       _ -> False+ed25519Verify _ _ _ _ = False+++-- | Verify an Ed448 signed message+ed448Verify :: JwsAlg+            -> Ed448.PublicKey+            -> ByteString+            -- ^ The message/content+            -> ByteString+            -- ^ The signature to check+            -> Bool+            -- ^ Whether the signature is correct+ed448Verify EdDSA pubKey msg sig =+    case Ed448.signature sig of+       CryptoPassed sig_ ->+         Ed448.verify pubKey msg sig_+       _ -> False+ed448Verify _ _ _ _ = False++ -- | Sign a message using an RSA private key. -- -- The failure condition should only occur if the algorithm is not an RSA@@ -167,7 +210,7 @@     _            -> return (Left (BadAlgorithm "Not an RSA algorithm"))   where     bs = BA.convert msg-    mapErr = fmap (mapLeft (const BadCrypto))+    mapErr = fmap (first (const BadCrypto))  -- | Decrypts an RSA encrypted message. rsaDecrypt :: ByteArray ct@@ -187,7 +230,7 @@     _            -> Left (BadAlgorithm "Not an RSA algorithm")   where     bs = BA.convert ct-    mapErr = mapLeft (const BadCrypto)+    mapErr = first (const BadCrypto)  -- Dummy type to constrain Cipher type data C c = C
Jose/Internal/Parser.hs view
@@ -17,6 +17,7 @@ where  import           Control.Applicative+import           Data.Bifunctor (first) import Data.Aeson (eitherDecodeStrict') import           Data.Attoparsec.ByteString (Parser) import qualified Data.Attoparsec.ByteString as P@@ -24,7 +25,6 @@ import           Data.ByteArray.Encoding (convertFromBase, Base(..)) import           Data.ByteString (ByteString) import qualified Data.ByteString as B-import           Data.Either.Combinators (mapLeft)  import           Jose.Jwa import           Jose.Types (JwtError(..), JwtHeader(..), JwsHeader(..), JweHeader(..))@@ -55,7 +55,7 @@   parseJwt :: ByteString -> Either JwtError DecodableJwt-parseJwt bs = mapLeft (const BadCrypto) $ P.parseOnly jwt bs+parseJwt bs = first (const BadCrypto) $ P.parseOnly jwt bs   jwt :: Parser DecodableJwt
Jose/Jwa.hs view
@@ -20,7 +20,7 @@  -- | A subset of the signature algorithms from the -- <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)+data JwsAlg = None | HS256 | HS384 | HS512 | RS256 | RS384 | RS512 | ES256 | ES384 | ES512 | EdDSA deriving (Eq, Show, Read)  -- | A subset of the key management algorithms from the -- <https://tools.ietf.org/html/rfc7518#section-4 JWA Spec>.@@ -31,7 +31,7 @@ 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), ("RSA-OAEP-256", Encrypted RSA_OAEP_256), ("A128KW", Encrypted A128KW), ("A192KW", Encrypted A192KW), ("A256KW", Encrypted A256KW)]+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), ("EdDSA", Signed EdDSA), ("RSA1_5", Encrypted RSA1_5), ("RSA-OAEP", Encrypted RSA_OAEP), ("RSA-OAEP-256", Encrypted RSA_OAEP_256), ("A128KW", Encrypted A128KW), ("A192KW", Encrypted A192KW), ("A256KW", Encrypted A256KW)]  algName :: Alg -> Text algName a = let Just n = lookup a algNames in n
Jose/Jwk.hs view
@@ -22,13 +22,17 @@  import           Control.Applicative (pure) import           Control.Monad (unless)+import           Crypto.Error (CryptoFailable(..)) import           Crypto.Random (MonadRandom, getRandomBytes) import qualified Crypto.PubKey.RSA as RSA import qualified Crypto.PubKey.ECC.ECDSA as ECDSA+import qualified Crypto.PubKey.Ed25519 as Ed25519+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.Types (Parser, Options (..), defaultOptions)+import qualified Data.ByteArray as BA import           Data.ByteString (ByteString) import qualified Data.ByteString as B import           Data.Maybe (isNothing)@@ -42,6 +46,7 @@  data KeyType = Rsa              | Ec+             | Okp              | Oct                deriving (Eq) @@ -58,6 +63,10 @@          | 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+         | Ed25519PrivateJwk !Ed25519.SecretKey !Ed25519.PublicKey !(Maybe KeyId)+         | Ed25519PublicJwk !Ed25519.PublicKey !(Maybe KeyId)+         | Ed448PrivateJwk !Ed448.SecretKey !Ed448.PublicKey !(Maybe KeyId)+         | Ed448PublicJwk !Ed448.PublicKey !(Maybe KeyId)          | SymmetricJwk  !ByteString      !(Maybe KeyId) !(Maybe KeyUse) !(Maybe Alg)            deriving (Show, Eq) @@ -100,6 +109,10 @@     keyIdCompatible (jwsKid hdr) jwk &&     algCompatible (Signed (jwsAlg hdr)) jwk &&     case (jwsAlg hdr, jwk) of+        (EdDSA, Ed25519PublicJwk {}) -> True+        (EdDSA, Ed25519PrivateJwk {}) -> True+        (EdDSA, Ed448PublicJwk {}) -> True+        (EdDSA, Ed448PrivateJwk {}) -> True         (RS256, RsaPublicJwk {}) -> True         (RS384, RsaPublicJwk {}) -> True         (RS512, RsaPublicJwk {}) -> True@@ -121,6 +134,8 @@ canEncodeJws a jwk = jwkUse jwk /= Just Enc &&     algCompatible (Signed a) jwk &&     case (a, jwk) of+        (EdDSA, Ed25519PrivateJwk {}) -> True+        (EdDSA, Ed448PrivateJwk {}) -> True         (RS256, RsaPrivateJwk {}) -> True         (RS384, RsaPrivateJwk {}) -> True         (RS512, RsaPrivateJwk {}) -> True@@ -169,14 +184,25 @@     Nothing -> True     Just ka -> a == ka -curve :: EcCurve -> ECC.Curve-curve c = ECC.getCurveByName $ case c of-    P_256 -> ECC.SEC_p256r1-    P_384 -> ECC.SEC_p384r1-    P_521 -> ECC.SEC_p521r1+ecCurve :: Text -> Maybe (EcCurve, ECC.Curve)+ecCurve c = case c of+    "P-256" -> Just (P_256, ECC.getCurveByName ECC.SEC_p256r1)+    "P-384" -> Just (P_384, ECC.getCurveByName ECC.SEC_p384r1)+    "P-521" -> Just (P_521, ECC.getCurveByName ECC.SEC_p521r1)+    _ -> Nothing +ecCurveName :: EcCurve -> Text+ecCurveName c = case c of+    P_256 -> "P-256"+    P_384 -> "P-384"+    P_521 -> "P-521"+ jwkId :: Jwk -> Maybe KeyId jwkId key = case key of+    Ed25519PrivateJwk _ _ keyId -> keyId+    Ed25519PublicJwk _ keyId -> keyId+    Ed448PrivateJwk _ _ keyId -> keyId+    Ed448PublicJwk _ keyId -> keyId     RsaPublicJwk  _ keyId _ _ -> keyId     RsaPrivateJwk _ keyId _ _ -> keyId     EcPublicJwk   _ keyId _ _ _ -> keyId@@ -185,6 +211,10 @@  jwkUse :: Jwk -> Maybe KeyUse jwkUse key = case key of+    Ed25519PrivateJwk _ _ _ -> Just Sig+    Ed25519PublicJwk _ _ -> Just Sig+    Ed448PrivateJwk _ _ _ -> Just Sig+    Ed448PublicJwk _ _ -> Just Sig     RsaPublicJwk  _ _ u _ -> u     RsaPrivateJwk _ _ u _ -> u     EcPublicJwk   _ _ u _ _ -> u@@ -193,6 +223,10 @@  jwkAlg :: Jwk -> Maybe Alg jwkAlg key = case key of+    Ed25519PrivateJwk _ _ _ -> Just (Signed EdDSA)+    Ed25519PublicJwk _ _ -> Just (Signed EdDSA)+    Ed448PrivateJwk _ _ _ -> Just (Signed EdDSA)+    Ed448PublicJwk _ _ -> Just (Signed EdDSA)     RsaPublicJwk  _ _ _ a -> a     RsaPrivateJwk _ _ _ a -> a     EcPublicJwk   _ _ _ a _ -> a@@ -200,13 +234,13 @@     SymmetricJwk  _ _ _ a -> a  - newtype JwkBytes = JwkBytes {bytes :: ByteString} deriving (Show)  instance FromJSON KeyType where     parseJSON = withText "KeyType" $ \t ->         case t of           "RSA" -> pure Rsa+          "OKP" -> pure Okp           "EC"  -> pure Ec           "oct" -> pure Oct           _     -> fail "unsupported key type"@@ -214,6 +248,7 @@ instance ToJSON KeyType where     toJSON kt = case kt of                     Rsa -> String "RSA"+                    Okp -> String "OKP"                     Ec  -> String "EC"                     Oct -> String "oct" @@ -274,6 +309,38 @@                 , dq = i2b $ RSA.private_dQ   privKey                 , qi = i2b $ RSA.private_qinv privKey                 }++        Ed25519PrivateJwk kPr kPub kid_ -> defJwk+            { kty = Okp+            , crv = Just "Ed25519"+            , d = Just (JwkBytes (BA.convert kPr))+            , x = Just (JwkBytes (BA.convert kPub))+            , kid = kid_+            }++        Ed25519PublicJwk kPub kid_ -> defJwk+            { kty = Okp+            , crv = Just "Ed25519"+            , x = Just (JwkBytes (BA.convert kPub))+            , kid = kid_+            }++        Ed448PrivateJwk kPr kPub kid_ -> defJwk+            { kty = Okp+            , crv = Just "Ed25519"+            , d = Just (JwkBytes (BA.convert kPr))+            , x = Just (JwkBytes (BA.convert kPub))+            , kid = kid_+            }++        Ed448PublicJwk kPub kid_ -> defJwk+            { kty = Okp+            , crv = Just "Ed25519"+            , x = Just (JwkBytes (BA.convert kPub))+            , kid = kid_+            }++         SymmetricJwk bs mId mUse mAlg -> defJwk             { kty = Oct             , k   = Just $ JwkBytes bs@@ -289,7 +356,7 @@             , kid = mId             , use = mUse             , alg = mAlg-            , crv = Just c+            , crv = Just (ecCurveName c)             }          EcPrivateJwk kp mId mUse mAlg c -> defJwk@@ -300,7 +367,7 @@             , kid = mId             , use = mUse             , alg = mAlg-            , crv = Just c+            , crv = Just (ecCurveName c)             }       where         i2b 0 = Nothing@@ -336,7 +403,7 @@     , dq  :: Maybe JwkBytes     , qi  :: Maybe JwkBytes     , k   :: Maybe JwkBytes-    , crv :: Maybe EcCurve+    , crv :: Maybe Text     , x   :: Maybe JwkBytes     , y   :: Maybe JwkBytes     , use :: Maybe KeyUse@@ -391,17 +458,46 @@         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+    Okp -> do+        crv' <- note "crv is required for an OKP key" crv+        x' <- note "x is required for an OKP key" x+        unless (isNothing (sequence [n, e, p, q, dp, dq, qi])) (Left "RSA parameters can't be set for an OKP key")+        case crv' of+          "Ed25519" -> case d of+              Just db -> do+                  secKey <- createOkpKey Ed25519.secretKey (bytes db)+                  pubKey <- createOkpKey Ed25519.publicKey (bytes x')+                  unless (pubKey == Ed25519.toPublic secKey) (Left "Public key x doesn't match private key d")+                  return (Ed25519PrivateJwk secKey pubKey kid)+              Nothing -> do+                  pubKey <- createOkpKey Ed25519.publicKey (bytes x')+                  return (Ed25519PublicJwk pubKey kid)+          "Ed448" -> case d of+              Just db -> do+                  secKey <- createOkpKey Ed448.secretKey (bytes db)+                  pubKey <- createOkpKey Ed448.publicKey (bytes x')+                  unless (pubKey == Ed448.toPublic secKey) (Left "Public key x doesn't match private key d")+                  return (Ed448PrivateJwk secKey pubKey kid)+              Nothing -> do+                  pubKey <- createOkpKey Ed448.publicKey (bytes x')+                  return (Ed448PublicJwk pubKey kid)++          _ -> Left "Unknown or unsupported OKP type"     Ec  -> do         crv' <- note "crv is required for an elliptic curve key" crv-        let c = curve crv'+        (crv'', c) <- note "crv must be a valid EC curve name" (ecCurve 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'+            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     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")+    createOkpKey f ba = case f ba of+       CryptoPassed k_ -> Right k_+       _ -> Left "Invalid OKP key data"+     note err      = maybe (Left err) Right     os2mip        = maybe 0 (os2ip . bytes)     rsaPub nb eb  = let m  = os2ip $ bytes nb
Jose/Jws.hs view
@@ -21,11 +21,17 @@     , rsaEncode     , rsaDecode     , ecDecode+    , ed25519Encode+    , ed25519Decode+    , ed448Encode+    , ed448Decode     ) 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 import Crypto.PubKey.RSA (PrivateKey(..), PublicKey(..), generateBlinder) import Crypto.Random (MonadRandom) import Data.ByteString (ByteString)@@ -49,6 +55,14 @@ 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)+    Ed25519PrivateJwk kPr kPub kid -> return $+        case a of+            EdDSA -> Right $ ed25519EncodeInternal kPr kPub (sigTarget EdDSA kid payload)+            _ -> Left (KeyError "Algorithm cannot be used with an Ed25519 key")+    Ed448PrivateJwk kPr kPub kid -> return $+        case a of+            EdDSA -> Right $ ed448EncodeInternal kPr kPub (sigTarget EdDSA kid payload)+            _ -> Left (KeyError "Algorithm cannot be used with an Ed448 key")     _                         -> return $ Left $ BadAlgorithm "EC signing is not supported"  -- | Create a JWS with an HMAC for validation.@@ -90,6 +104,57 @@     sign b = case rsaSign (Just b) a pk st of         Right sig -> Right . Jwt $ B.concat [st, ".", B64.encode sig]         Left e    -> Left e+++ed25519Decode :: Ed25519.PublicKey+              -> ByteString+              -> Either JwtError Jws+ed25519Decode key = decode (`ed25519Verify` key)+++ed25519Encode :: Ed25519.SecretKey+              -> Ed25519.PublicKey+              -> ByteString+              -> Jwt+ed25519Encode kPr kPub payload =+    ed25519EncodeInternal kPr kPub (sigTarget EdDSA Nothing (Claims payload))+++ed25519EncodeInternal :: Ed25519.SecretKey+                      -> Ed25519.PublicKey+                      -> ByteString+                      -> Jwt+ed25519EncodeInternal kPr kPub signMe =+  let+     sig = Ed25519.sign kPr kPub signMe+  in+     Jwt (B.concat [signMe, ".", B64.encode sig])+++ed448Decode :: Ed448.PublicKey+            -> ByteString+            -> Either JwtError Jws+ed448Decode key = decode (`ed448Verify` key)+++ed448Encode :: Ed448.SecretKey+            -> Ed448.PublicKey+            -> ByteString+            -> Jwt+ed448Encode kPr kPub payload =+    ed448EncodeInternal kPr kPub (sigTarget EdDSA Nothing (Claims payload))+++ed448EncodeInternal :: Ed448.SecretKey+                    -> Ed448.PublicKey+                    -> ByteString+                    -> Jwt+ed448EncodeInternal kPr kPub signMe =+  let+     sig = Ed448.sign kPr kPub signMe+  in+     Jwt (B.concat [signMe, ".", B64.encode sig])+  -- | Decode and validate an RSA signed JWS. rsaDecode :: PublicKey            -- ^ The key to check the signature with
Jose/Jwt.hs view
@@ -109,6 +109,10 @@   where     decodeWithJws :: MonadRandom m => Jwk -> ExceptT JwtError m (Maybe JwtContent)     decodeWithJws k = either (const $ return Nothing) (return . Just . Jws) $ case k of+        Ed25519PublicJwk kPub _ -> Jws.ed25519Decode kPub jwt+        Ed25519PrivateJwk _ kPub _ -> Jws.ed25519Decode kPub jwt+        Ed448PublicJwk kPub _ -> Jws.ed448Decode kPub jwt+        Ed448PrivateJwk _ kPub _ -> Jws.ed448Decode kPub jwt         RsaPublicJwk  kPub _ _ _ -> Jws.rsaDecode kPub jwt         RsaPrivateJwk kPr  _ _ _ -> Jws.rsaDecode (private_pub kPr) jwt         EcPublicJwk   kPub _ _ _ _ -> Jws.ecDecode kPub jwt
+ README.md view
@@ -0,0 +1,64 @@+# `jose-jwt`++A Haskell implementation of the JSON Object Signing and Encryption (JOSE) specifications and the related [JWT specification](http://tools.ietf.org/html/draft-ietf-oauth-json-web-token), as used, for example, in [OpenID Connect](http://openid.net/connect/).++## Background++The [JWT specification](https://tools.ietf.org/html/rfc7519) was split into [`JWS`](https://www.rfc-editor.org/rfc/rfc7515.html) and [`JWE`](https://www.rfc-editor.org/rfc/rfc7515.html) during its development and thus does not contain much. Basically, a JWT is either a JWS or a JWE depending on whether it is signed or encrypted. It is encoded as a sequence of base64 strings separated by '.' characters [1].++Technically, the content of a JWT should be JSON (unless it's a nested JWT), but the library doesn't care - it only requires a bytestring. The calling application should verify that the content is valid. Exactly what that means will depend on what you are using JWTs for.++## Examples++You can either use the high-level `encode` and `decode` functions in the [`Jwt`](https://hackage.haskell.org/package/jose-jwt/docs/Jose-Jwt.html) module or specific functions in the [`Jws`](https://hackage.haskell.org/package/jose-jwt/docs/Jose-Jws.html) and [`Jwe`](https://hackage.haskell.org/package/jose-jwt/docs/Jose-Jwe.html) modules.++The following examples can be entered directly into `ghci`. Use++    > :set -XOverloadedStrings++to begin with.++### JWS signing example with a symmetric HMAC algorithm++HMAC is a good choice when both signer and verifier have a copy of the key.++    > import Jose.Jws (hmacEncode, hmacDecode)+    > import Jose.Jwa (JwsAlg(HS256))+    >+    > hmacEncode HS256 "aRANDOMlygeneratedkey" "my JSON message"+    Right (Jwt {unJwt = "eyJhbGciOiJIUzI1NiJ9.bXkgSlNPTiBtZXNzYWdl.lTJx7ECLwYF3P7WbrrUpcp_2SdLiFXaDwK-PXcipt5Q"})+    > hmacDecode "aRANDOMlygeneratedkey" "eyJhbGciOiJIUzI1NiJ9.bXkgSlNPTiBtZXNzYWdl.lTJx7ECLwYF3P7WbrrUpcp_2SdLiFXaDwK-PXcipt5Q"+    Right (JwsHeader {jwsAlg = HS256, jwsTyp = Nothing, jwsCty = Nothing, jwsKid = Nothing},"my JSON message")++Trying to decode with a different key would return a `Left BadSignature` [2].++### JWS signing using Ed25519 private key++Some situations require the use of public key cryptography for signing. For example, only a trusted party is allowed to create a signed token, but it must be verified by others.++Elliptic-curve EdDSA signing and verification are supported as defined in [RFC 8037](https://tools.ietf.org/html/rfc8037), as well as the older RSA JWS algorithms.++    > import Jose.Jwt+    > import Jose.Jwk+    > import Jose.Jwa (JwsAlg(EdDSA))+    > import Data.ByteString (ByteString)+    > import Data.Aeson (decodeStrict)+    >+    > jsonJwk = "{\"kty\":\"OKP\", \"crv\":\"Ed25519\", \"d\":\"nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A\", \"x\":\"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo\"}" :: ByteString+    > Just jwk = decodeStrict jsonJwk :: Maybe Jwk+    > Jose.Jwt.encode [jwk] (JwsEncoding EdDSA) (Claims "public claims")+    Right (Jwt {unJwt = "eyJhbGciOiJFZERTQSJ9.cHVibGljIGNsYWltcw.xYekeeGSQVpnQbl16lOCqFcmYsUj3goSTrZ4UBQqogjHLrvFUaVJ_StBqly-Tb-0xvayjUMM4INYBTwFMt_xAQ"})++To verify the JWT you would use the `Jose.Jwt.decode` function with the corresponding public key.++More examples can be found in the [package documentation](https://hackage.haskell.org/package/jose-jwt).++### Build Status+![Build Status](https://github.com/tekul/jose-jwt/workflows/Haskell%20CI/badge.svg)+++[1] This is now referred to as "compact serialization". The additional "JSON serialization" is not supported in this library.++[2] Note that a real key for HMAC256 should be a much longer, random string of bytes. See, for example,+[this stackexchange answer](https://crypto.stackexchange.com/a/34866).+
benchmarks/bench.hs view
@@ -3,6 +3,8 @@  import Criterion.Main import Crypto.Random+import qualified Crypto.PubKey.Ed25519 as Ed25519+import qualified Crypto.PubKey.Ed448 as Ed448 import Data.Word (Word64) import Jose.Jws import qualified Jose.Jwe as Jwe@@ -19,16 +21,23 @@  main = do     kwKek <- getRandomBytes 32 >>= \k -> return $ SymmetricJwk k Nothing Nothing Nothing :: IO Jwk+    ed25519PrivKey <- Ed25519.generateSecretKey+    ed448PrivKey <- Ed448.generateSecretKey+    let ed25519PubKey = Ed25519.toPublic ed25519PrivKey+        ed448PubKey = Ed448.toPublic ed448PrivKey     Right rsaOAEPJwe <- Jwe.rsaEncode RSA_OAEP A256GCM jwsRsaPublicKey msg     Right keywrapJwe <- Jwe.jwkEncode A256KW A256GCM kwKek (Claims msg)      defaultMain       [ benchJwsHmac       , benchJwsRsa+      , benchJwsEd25519 ed25519PrivKey ed25519PubKey+      , benchJwsEd448 ed448PrivKey ed448PubKey       , benchJweKeywrap (unJwt keywrapJwe) kwKek       , benchJweRsa (unJwt rsaOAEPJwe)       ] + benchJweRsa jwe = bgroup "JWE-RSA"     [ bench "decode RSA_OAEP" $ nf rsaDecrypt jwe     ]@@ -44,6 +53,15 @@      keywrapDecode m = case fstWithRNG (Jwe.jwkDecode jwk m) of         Right (Jwe j) -> snd j         _ -> error "RSA decode of JWE shouldn't fail"+++benchJwsEd25519 kPr kPub = bgroup "Ed25519"+    [ bench "encode Ed25519" $ nf (unJwt . ed25519Encode kPr kPub) msg+    ]++benchJwsEd448 kPr kPub = bgroup "Ed448"+    [ bench "encode Ed448" $ nf (unJwt . ed448Encode kPr kPub) msg+    ]  benchJwsRsa = bgroup "JWS-RSA"     [ bench "encode RSA256" $ nf (rsaE RS256)  msg
jose-jwt.cabal view
@@ -1,5 +1,5 @@ Name:               jose-jwt-Version:            0.8.0+Version:            0.9.0 Synopsis:           JSON Object Signing and Encryption Library Homepage:           http://github.com/tekul/jose-jwt Bug-Reports:        http://github.com/tekul/jose-jwt/issues@@ -21,12 +21,13 @@ Category:           JSON, Cryptography  Extra-Source-Files:+    README.md     CHANGELOG.md     tests/*.json  -- disable doctests with -f-doctest Flag doctest-  default: True+  default: False   manual: True  Source-Repository head@@ -48,14 +49,13 @@   if impl(ghc < 8.0)     Buildable: False   else-    Build-depends:    base+    Build-depends:    base >= 4.9 && < 5                     , aeson >= 0.8.0.2                     , attoparsec >= 0.12.0.0                     , bytestring >= 0.9                     , cereal >= 0.4                     , containers >= 0.4                     , cryptonite >= 0.19-                    , either                     , memory >= 0.10                     , mtl >= 2.1.3.1                     , text  >= 0.11@@ -73,11 +73,10 @@                     , Tests.JweSpec                     , Tests.JwkSpec   Build-depends:      jose-jwt-                    , base+                    , base >= 4.9 && < 5                     , aeson                     , bytestring                     , cryptonite-                    , either >= 4.0                     , memory                     , mtl                     , text@@ -99,7 +98,7 @@   if !flag(doctest)     Buildable: False   else-    Build-depends:    base+    Build-depends:    base  >= 4.9 && < 5                     , doctest >= 0.9.11                     , cryptonite @@ -110,7 +109,7 @@   Other-Modules:      Keys   Type:               exitcode-stdio-1.0   Build-depends:      jose-jwt-                    , base+                    , base >= 4.9 && < 5                     , bytestring                     , criterion                     , cryptonite
tests/Tests/JwkSpec.hs view
@@ -42,8 +42,10 @@                 RsaPublicJwk  _ key6Id Nothing    a6   = kss !! 6                 EcPrivateJwk  _ key7Id (Just Enc) _ _  = kss !! 7                 RsaPrivateJwk _ _      Nothing    a8   = kss !! 8+                Ed25519PrivateJwk _ _ key9Id = kss !! 9+                Ed25519PublicJwk _ key10Id = kss !! 10                 Success utcKeyId = fromJSON (String "2015-05-16T18:00:14.259Z")-            length kss @?= 9+            length kss @?= 11             a0      @?= Nothing             key0Id  @?= Just (KeyId "a0")             key1Id  @?= Just (KeyId "a1")@@ -54,6 +56,8 @@             key5Id  @?= Just (KeyId "1")             key6Id  @?= Just (UTCKeyId utcKeyId)             key7Id  @?= Just (KeyId "1")+            key9Id  @?= Just (KeyId "rfc8037SecretKey")+            key10Id  @?= Just (KeyId "rfc8037PublicKey")             key0Use @?= Just Enc             key1Use @?= Just Sig             key2Use @?= Just Sig
tests/Tests/JwsSpec.hs view
@@ -12,11 +12,14 @@ import Data.Word (Word64) import Crypto.Hash.Algorithms (SHA256(..)) import Crypto.MAC.HMAC (HMAC, hmac)+import qualified Crypto.PubKey.Ed25519 as Ed25519+import qualified Crypto.PubKey.Ed448 as Ed448 import qualified Crypto.PubKey.RSA as RSA import qualified Crypto.PubKey.RSA.PKCS15 as RSAPKCS15 import Crypto.Random (withDRG, drgNewTest) -import Jose.Jwt+import Jose.Jwt as Jwt+import Jose.Jwk (Jwk(..)) import Jose.Jwa import qualified Jose.Internal.Base64 as B64 import qualified Jose.Jws as Jws@@ -29,7 +32,7 @@ {-- Examples from the JWS appendix A --}  spec :: Spec-spec =+spec = do     describe "JWS encoding and decoding" $ do       context "when using JWS Appendix A.1 data" $ do         let a11decoded = Right (defJwsHdr {jwsAlg = HS256, jwsTyp = Just "JWT"}, a11Payload)@@ -41,7 +44,7 @@          it "decodes the payload using the JWK" $ do           let Just k11 = decodeStrict' a11jwk-          fstWithRNG (decode [k11] Nothing a11) @?= fmap Jws a11decoded+          fstWithRNG (Jwt.decode [k11] Nothing a11) @?= fmap Jws a11decoded          it "encodes/decodes using HS512" $           hmacRoundTrip HS512 a11Payload@@ -55,7 +58,7 @@          it "decodes the JWT to the expected header and payload with the JWK" $ do           let Just k21 = decodeStrict' a21jwk-          fstWithRNG (decode [k21] (Just (JwsEncoding RS256)) a21) @?= (Right $ Jws (defJwsHdr {jwsAlg = RS256}, a21Payload))+          fstWithRNG (Jwt.decode [k21] (Just (JwsEncoding RS256)) a21) @?= (Right $ Jws (defJwsHdr {jwsAlg = RS256}, a21Payload))          it "decodes the successfully without verification" $ do            let Right (_, claims) = decodeClaims a21 :: Either JwtError (JwtHeader, JwtClaims)@@ -74,23 +77,62 @@         it "encodes/decodes using RS512" $           rsaRoundTrip RS512 a21Payload --       context "when using JWS Appendix A.3 data" $ do         let a31decoded = Right (defJwsHdr {jwsAlg = ES256}, a31Payload)         it "decodes the JWT to the expected header and payload" $ do           let Just k31 = decodeStrict' a31jwk-          fstWithRNG (decode [k31] Nothing a31) @?= fmap Jws a31decoded+          fstWithRNG (Jwt.decode [k31] Nothing a31) @?= fmap Jws a31decoded        context "when using an unsecured JWT" $ do         it "returns an error if chosen alg is unset" $-          fstWithRNG (decode [] Nothing jwt61) @?= Left (BadAlgorithm "JWT is unsecured but expected 'alg' was not 'none'")+          fstWithRNG (Jwt.decode [] Nothing jwt61) @?= Left (BadAlgorithm "JWT is unsecured but expected 'alg' was not 'none'")         it "returns an error if chosen alg is not 'none'" $-          fstWithRNG (decode [] (Just (JwsEncoding RS256)) jwt61) @?= Left (BadAlgorithm "JWT is unsecured but expected 'alg' was not 'none'")+          fstWithRNG (Jwt.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 if chosen alg is 'none'" $-          fstWithRNG (decode [] (Just (JwsEncoding None)) jwt61) @?= Right (Unsecured jwt61Payload)+          fstWithRNG (Jwt.decode [] (Just (JwsEncoding None)) jwt61) @?= Right (Unsecured jwt61Payload) +    describe "Ed25519 signing and verification" $ do+      context "When using RFC8037 Appendix A data" $ do+        let ed25519JwtDecoded = Right (defJwsHdr { jwsAlg = EdDSA }, ed25519Payload)+            Just pubKey = decodeStrict' ed25519PubJwk+            Just (secKey@(Ed25519PrivateJwk kPr kPub _)) = decodeStrict' ed25519SecJwk+            sign = Ed25519.sign kPr kPub+        it "decodes the JWT to the expected header and payload" $ do+          fstWithRNG (Jwt.decode [pubKey] Nothing ed25519Jwt) @?= fmap Jws ed25519JwtDecoded+          fstWithRNG (Jwt.decode [secKey] Nothing ed25519Jwt) @?= fmap Jws ed25519JwtDecoded +        it "encodes the payload to the exected JWT" $ do+          -- Don't really need signWithHeader here, since our function gives the correct value+          signWithHeader sign ed25519Hdr ed25519Payload @?= ed25519Jwt+          Jws.ed25519Encode kPr kPub ed25519Payload @?= Jwt ed25519Jwt++        it "roundtrip encode/decode" $ do+          let Right (Jwt encoded) = fstWithRNG (Jwt.encode [pubKey, secKey] (JwsEncoding EdDSA) (Claims "hello there"))+          fstWithRNG (Jwt.decode [pubKey] (Just (JwsEncoding EdDSA)) encoded) @?= Right (Jws (defJwsHdr { jwsAlg = EdDSA }, "hello there"))++        it "encoding rejects invalid alg for Ed25519 key" $ do+          fstWithRNG (Jws.jwkEncode RS256 secKey (Claims "hello")) @?= Left (KeyError "Algorithm cannot be used with an Ed25519 key")+          fstWithRNG (Jwt.encode [pubKey, secKey] (JwsEncoding RS256) (Claims "hello")) @?= Left (KeyError "No matching key found for JWS algorithm")++        it "verification fails with invalid alg in header" $ do+          let badJwt = signWithHeader sign a21Header ed25519Payload+          fstWithRNG (Jwt.decode [pubKey] Nothing badJwt) @?= Left (KeyError "No suitable key was found to decode the JWT")++    describe "Ed448 signing and verification" $ do+      let Just pubKey = decodeStrict' ed448PubJwk+          Just (secKey@(Ed448PrivateJwk kPr kPub _)) = decodeStrict' ed448SecJwk+          sign = Ed448.sign kPr kPub++      context "" $ do+        it "JWT is encoded to the expected value" $+          signWithHeader sign ed448Hdr "{}" @?= ed448Jwt++        it "roundtrip encode/decode" $ do+          let Right (Jwt encoded) = fstWithRNG (Jwt.encode [pubKey, secKey] (JwsEncoding EdDSA) (Claims "hello"))+          fstWithRNG (Jwt.decode [pubKey] (Just (JwsEncoding EdDSA)) encoded) @?= Right (Jws (defJwsHdr { jwsAlg = EdDSA }, "hello"))+          Jws.ed448Decode kPub (unJwt (Jws.ed448Encode kPr kPub "hello")) @?= Right (defJwsHdr { jwsAlg = EdDSA }, "hello")++ signWithHeader sign hdr payload = B.intercalate "." [hdrPayload, B64.encode $ sign hdrPayload]   where     hdrPayload = B.intercalate "." $ map B64.encode [hdr, payload]@@ -100,6 +142,22 @@  rsaRoundTrip a msg = let Right (Jwt encoded) = fstWithRNG (Jws.rsaEncode a rsaPrivateKey msg)                      in  Jws.rsaDecode rsaPublicKey encoded @?= Right (defJwsHdr {jwsAlg = a}, msg)++-- Ed25519 Data from https://tools.ietf.org/html/rfc8037#appendix-A++ed25519SecJwk = "{\"kty\":\"OKP\", \"crv\":\"Ed25519\", \"d\":\"nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A\", \"x\":\"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo\"}" :: B.ByteString+ed25519PubJwk = "{\"kty\":\"OKP\",\"crv\":\"Ed25519\", \"x\":\"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo\"}"+ed25519Hdr = "{\"alg\":\"EdDSA\"}" :: B.ByteString+ed25519Payload = "Example of Ed25519 signing"+ed25519Jwt = "eyJhbGciOiJFZERTQSJ9.RXhhbXBsZSBvZiBFZDI1NTE5IHNpZ25pbmc.hgyY0il_MGCjP0JzlnLWG1PPOt7-09PGcvMg3AIbQR6dWbhijcNR4ki4iylGjg5BhVsPt9g7sVvpAr_MuM0KAg"++-- Ed448 Data signed with ruby for comparison++ed448SecJwk = "{\"kty\":\"OKP\", \"crv\":\"Ed448\", \"d\":\"-ox5cBHY-QLR0hRdE2gd97LkQ8oRZCT89ALXm-FqhINLdVEd_PtfHuetZoKeHALqwu-NfuADYDBL\",  \"x\": \"BnJNZy1_JXpGRlrNLYsz_9I5NCM-Py39P1kEOyrLRXJj38rnOJe7cJaVsOnPj2NkL_jVtG_qkjOA\" }"+ed448PubJwk = "{\"kty\":\"OKP\", \"crv\":\"Ed448\", \"x\": \"BnJNZy1_JXpGRlrNLYsz_9I5NCM-Py39P1kEOyrLRXJj38rnOJe7cJaVsOnPj2NkL_jVtG_qkjOA\" }"+ed448Hdr = "{\"alg\":\"Ed448\"}" :: B.ByteString+ed448Jwt = "eyJhbGciOiJFZDQ0OCJ9.e30.UlqTx962FvZP1G5pZOrScRXlAB0DJI5dtZkknNTm1E70AapkONi8vzpvKd355czflQdc7uyOzTeAz0-eLvffCKgWm_zebLly7L3DLBliynQk14qgJgz0si-60mBFYOIxRghk95kk5hCsFpxpVE45jRIA" :: B.ByteString+  -- Unsecured JWT from section 6.1 jwt61 = "eyJhbGciOiJub25lIn0.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ."
tests/jwks.json view
@@ -8,5 +8,7 @@     , {"kty":"RSA", "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw", "e":"AQAB", "alg":"RS256", "kid":"2015-05-16T18:00:14.259Z"}     , {"kty":"EC", "crv":"P-256", "x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", "y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", "d":"870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE", "use":"enc", "kid":"1"}     , {"kty":"RSA", "n":"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw", "e":"AQAB", "d":"X4cTteJY_gn4FYPsXB8rdXix5vwsg1FLN5E3EaG6RJoVH-HLLKD9M7dx5oo7GURknchnrRweUkC7hT5fJLM0WbFAKNLWY2vv7B6NqXSzUvxT0_YSfqijwp3RTzlBaCxWp4doFk5N2o8Gy_nHNKroADIkJ46pRUohsXywbReAdYaMwFs9tv8d_cPVY3i07a3t8MN6TNwm0dSawm9v47UiCl3Sk5ZiG7xojPLu4sbg1U2jx4IBTNBznbJSzFHK66jT8bgkuqsk0GjskDJk19Z4qwjwbsnn4j2WBii3RL-Us2lGVkY8fkFzme1z0HbIkfz0Y6mqnOYtqc0X4jfcKoAC8Q", "p":"83i-7IvMGXoMXCskv73TKr8637FiO7Z27zv8oj6pbWUQyLPQBQxtPVnwD20R-60eTDmD2ujnMt5PoqMrm8RfmNhVWDtjjMmCMjOpSXicFHj7XOuVIYQyqVWlWEh6dN36GVZYk93N8Bc9vY41xy8B9RzzOGVQzXvNEvn7O0nVbfs", "q":"3dfOR9cuYq-0S-mkFLzgItgMEfFzB2q3hWehMuG0oCuqnb3vobLyumqjVZQO1dIrdwgTnCdpYzBcOfW5r370AFXjiWft_NGEiovonizhKpo9VVS78TzFgxkIdrecRezsZ-1kYd_s1qDbxtkDEgfAITAG9LUnADun4vIcb6yelxk", "dp":"G4sPXkc6Ya9y8oJW9_ILj4xuppu0lzi_H7VTkS8xj5SdX3coE0oimYwxIi2emTAue0UOa5dpgFGyBJ4c8tQ2VF402XRugKDTP8akYhFo5tAA77Qe_NmtuYZc3C3m3I24G2GvR5sSDxUyAN2zq8Lfn9EUms6rY3Ob8YeiKkTiBj0", "dq":"s9lAH9fggBsoFR8Oac2R_E2gw282rT2kGOAhvIllETE1efrA6huUUvMfBcMpn8lqeW6vzznYY5SSQF7pMdC_agI3nG8Ibp1BUb0JUiraRNqUfLhcQb_d9GF4Dh7e74WbRsobRonujTYN1xCaP6TO61jvWrX-L18txXw494Q_cgk", "qi":"GyM_p6JrXySiz1toFgKbWV-JdI3jQ4ypu9rbMWx3rQJBfmt0FoYzgUIZEVFEcOqwemRN81zoDAaa-Bk0KWNGDjJHZDdDmFhW3AN7lI-puxk_mHZGJ11rxyR8O55XLSe3SPmRfKwZI6yU24ZxvQKFYItdldUKGzO6Ia6zTKhAVRU", "alg":"RS256", "kid":"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"}     ] }