jose 0.10 → 0.10.0.1
raw patch · 18 files changed
+261/−157 lines, 18 filesdep ~aesondep ~bytestringdep ~mtlPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependency ranges changed: aeson, bytestring, mtl, tasty-hedgehog, text
API changes (from Hackage documentation)
- Crypto.JOSE.JWA.JWE: [_apu] :: ECDHParameters -> Maybe Base64Octets
- Crypto.JOSE.JWA.JWE: [_apv] :: ECDHParameters -> Maybe Base64Octets
- Crypto.JOSE.JWA.JWE: [_epk] :: ECDHParameters -> JWK
- Crypto.JOSE.JWA.JWE: [_iv] :: AESGCMParameters -> Base64Octets
- Crypto.JOSE.JWA.JWE: [_p2c] :: PBES2Parameters -> Int
- Crypto.JOSE.JWA.JWE: [_p2s] :: PBES2Parameters -> Base64Octets
- Crypto.JOSE.JWA.JWE: [_tag] :: AESGCMParameters -> Base64Octets
- Crypto.JOSE.JWA.JWE: algObject :: Value -> Value
- Crypto.JOSE.JWA.JWE: algWithParamsObject :: ToJSON a => a -> Value -> Value
- Crypto.JOSE.Error: _AlgorithmMismatch :: AsError r_aemS => Prism' r_aemS String
+ Crypto.JOSE.Error: _AlgorithmMismatch :: AsError r_aesV => Prism' r_aesV String
- Crypto.JOSE.Error: _AlgorithmNotImplemented :: AsError r_aemS => Prism' r_aemS ()
+ Crypto.JOSE.Error: _AlgorithmNotImplemented :: AsError r_aesV => Prism' r_aesV ()
- Crypto.JOSE.Error: _CompactDecodeError :: AsError r_aemS => Prism' r_aemS CompactDecodeError
+ Crypto.JOSE.Error: _CompactDecodeError :: AsError r_aesV => Prism' r_aesV CompactDecodeError
- Crypto.JOSE.Error: _CryptoError :: AsError r_aemS => Prism' r_aemS CryptoError
+ Crypto.JOSE.Error: _CryptoError :: AsError r_aesV => Prism' r_aesV CryptoError
- Crypto.JOSE.Error: _Error :: AsError r_aemS => Prism' r_aemS Error
+ Crypto.JOSE.Error: _Error :: AsError r_aesV => Prism' r_aesV Error
- Crypto.JOSE.Error: _JSONDecodeError :: AsError r_aemS => Prism' r_aemS String
+ Crypto.JOSE.Error: _JSONDecodeError :: AsError r_aesV => Prism' r_aesV String
- Crypto.JOSE.Error: _JWSCritUnprotected :: AsError r_aemS => Prism' r_aemS ()
+ Crypto.JOSE.Error: _JWSCritUnprotected :: AsError r_aesV => Prism' r_aesV ()
- Crypto.JOSE.Error: _JWSInvalidSignature :: AsError r_aemS => Prism' r_aemS ()
+ Crypto.JOSE.Error: _JWSInvalidSignature :: AsError r_aesV => Prism' r_aesV ()
- Crypto.JOSE.Error: _JWSNoSignatures :: AsError r_aemS => Prism' r_aemS ()
+ Crypto.JOSE.Error: _JWSNoSignatures :: AsError r_aesV => Prism' r_aesV ()
- Crypto.JOSE.Error: _JWSNoValidSignatures :: AsError r_aemS => Prism' r_aemS ()
+ Crypto.JOSE.Error: _JWSNoValidSignatures :: AsError r_aesV => Prism' r_aesV ()
- Crypto.JOSE.Error: _KeyMismatch :: AsError r_aemS => Prism' r_aemS Text
+ Crypto.JOSE.Error: _KeyMismatch :: AsError r_aesV => Prism' r_aesV Text
- Crypto.JOSE.Error: _KeySizeTooSmall :: AsError r_aemS => Prism' r_aemS ()
+ Crypto.JOSE.Error: _KeySizeTooSmall :: AsError r_aesV => Prism' r_aesV ()
- Crypto.JOSE.Error: _NoUsableKeys :: AsError r_aemS => Prism' r_aemS ()
+ Crypto.JOSE.Error: _NoUsableKeys :: AsError r_aesV => Prism' r_aesV ()
- Crypto.JOSE.Error: _OtherPrimesNotSupported :: AsError r_aemS => Prism' r_aemS ()
+ Crypto.JOSE.Error: _OtherPrimesNotSupported :: AsError r_aesV => Prism' r_aesV ()
- Crypto.JOSE.Error: _RSAError :: AsError r_aemS => Prism' r_aemS Error
+ Crypto.JOSE.Error: _RSAError :: AsError r_aesV => Prism' r_aesV Error
- Crypto.JOSE.Error: class AsError r_aemS
+ Crypto.JOSE.Error: class AsError r_aesV
- Crypto.JWT: _JWSError :: AsJWTError r_a1acf => Prism' r_a1acf Error
+ Crypto.JWT: _JWSError :: AsJWTError r_a1agi => Prism' r_a1agi Error
- Crypto.JWT: _JWTClaimsSetDecodeError :: AsJWTError r_a1acf => Prism' r_a1acf String
+ Crypto.JWT: _JWTClaimsSetDecodeError :: AsJWTError r_a1agi => Prism' r_a1agi String
- Crypto.JWT: _JWTError :: AsJWTError r_a1acf => Prism' r_a1acf JWTError
+ Crypto.JWT: _JWTError :: AsJWTError r_a1agi => Prism' r_a1agi JWTError
- Crypto.JWT: _JWTExpired :: AsJWTError r_a1acf => Prism' r_a1acf ()
+ Crypto.JWT: _JWTExpired :: AsJWTError r_a1agi => Prism' r_a1agi ()
- Crypto.JWT: _JWTIssuedAtFuture :: AsJWTError r_a1acf => Prism' r_a1acf ()
+ Crypto.JWT: _JWTIssuedAtFuture :: AsJWTError r_a1agi => Prism' r_a1agi ()
- Crypto.JWT: _JWTNotInAudience :: AsJWTError r_a1acf => Prism' r_a1acf ()
+ Crypto.JWT: _JWTNotInAudience :: AsJWTError r_a1agi => Prism' r_a1agi ()
- Crypto.JWT: _JWTNotInIssuer :: AsJWTError r_a1acf => Prism' r_a1acf ()
+ Crypto.JWT: _JWTNotInIssuer :: AsJWTError r_a1agi => Prism' r_a1agi ()
- Crypto.JWT: _JWTNotYetValid :: AsJWTError r_a1acf => Prism' r_a1acf ()
+ Crypto.JWT: _JWTNotYetValid :: AsJWTError r_a1agi => Prism' r_a1agi ()
- Crypto.JWT: class AsJWTError r_a1acf
+ Crypto.JWT: class AsJWTError r_a1agi
- Crypto.JWT: class HasJWTValidationSettings c_a1b0w
+ Crypto.JWT: class HasJWTValidationSettings c_a1b4z
- Crypto.JWT: jWTValidationSettings :: HasJWTValidationSettings c_a1b0w => Lens' c_a1b0w JWTValidationSettings
+ Crypto.JWT: jWTValidationSettings :: HasJWTValidationSettings c_a1b4z => Lens' c_a1b4z JWTValidationSettings
- Crypto.JWT: jwtValidationSettingsAllowedSkew :: HasJWTValidationSettings c_a1b0w => Lens' c_a1b0w NominalDiffTime
+ Crypto.JWT: jwtValidationSettingsAllowedSkew :: HasJWTValidationSettings c_a1b4z => Lens' c_a1b4z NominalDiffTime
- Crypto.JWT: jwtValidationSettingsAudiencePredicate :: HasJWTValidationSettings c_a1b0w => Lens' c_a1b0w (StringOrURI -> Bool)
+ Crypto.JWT: jwtValidationSettingsAudiencePredicate :: HasJWTValidationSettings c_a1b4z => Lens' c_a1b4z (StringOrURI -> Bool)
- Crypto.JWT: jwtValidationSettingsCheckIssuedAt :: HasJWTValidationSettings c_a1b0w => Lens' c_a1b0w Bool
+ Crypto.JWT: jwtValidationSettingsCheckIssuedAt :: HasJWTValidationSettings c_a1b4z => Lens' c_a1b4z Bool
- Crypto.JWT: jwtValidationSettingsIssuerPredicate :: HasJWTValidationSettings c_a1b0w => Lens' c_a1b0w (StringOrURI -> Bool)
+ Crypto.JWT: jwtValidationSettingsIssuerPredicate :: HasJWTValidationSettings c_a1b4z => Lens' c_a1b4z (StringOrURI -> Bool)
- Crypto.JWT: jwtValidationSettingsValidationSettings :: HasJWTValidationSettings c_a1b0w => Lens' c_a1b0w ValidationSettings
+ Crypto.JWT: jwtValidationSettingsValidationSettings :: HasJWTValidationSettings c_a1b4z => Lens' c_a1b4z ValidationSettings
Files
- example/JWS.hs +25/−6
- jose.cabal +44/−9
- src/Crypto/JOSE/Compact.hs +6/−1
- src/Crypto/JOSE/Error.hs +2/−2
- src/Crypto/JOSE/JWA/JWE.hs +7/−1
- src/Crypto/JOSE/JWA/JWE/Alg.hs +3/−1
- src/Crypto/JOSE/JWA/JWS.hs +3/−1
- src/Crypto/JOSE/JWE.hs +8/−6
- src/Crypto/JOSE/TH.hs +3/−3
- src/Crypto/JWT.hs +107/−89
- test/AESKW.hs +3/−1
- test/Examples.hs +5/−2
- test/JWK.hs +4/−1
- test/JWS.hs +4/−5
- test/JWT.hs +29/−26
- test/Perf.hs +1/−1
- test/Properties.hs +4/−1
- test/Types.hs +3/−1
example/JWS.hs view
@@ -1,5 +1,24 @@-module JWS where+-- Copyright (C) 2017-2022 Fraser Tweedale+--+-- Licensed under the Apache License, Version 2.0 (the "License");+-- you may not use this file except in compliance with the License.+-- You may obtain a copy of the License at+--+-- http://www.apache.org/licenses/LICENSE-2.0+--+-- Unless required by applicable law or agreed to in writing, software+-- distributed under the License is distributed on an "AS IS" BASIS,+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+-- See the License for the specific language governing permissions and+-- limitations under the License. +{-# OPTIONS_GHC -fno-warn-incomplete-patterns #-}++module JWS+ ( doJwsSign+ , doJwsVerify+ ) where+ import System.Exit (exitFailure) import Data.Aeson (decode, encode)@@ -16,11 +35,11 @@ -- doJwsSign :: [String] -> IO () doJwsSign [jwkFilename, payloadFilename] = do- Just jwk <- decode <$> L.readFile jwkFilename+ Just k <- decode <$> L.readFile jwkFilename payload <- L.readFile payloadFilename result <- runJOSE $ do- h <- makeJWSHeader jwk- signJWS payload [(h :: JWSHeader Protection, jwk)]+ h <- makeJWSHeader k+ signJWS payload [(h :: JWSHeader Protection, k)] case result of Left e -> print (e :: Error) >> exitFailure Right jws -> L.putStr (encode jws)@@ -35,9 +54,9 @@ -- doJwsVerify :: [String] -> IO () doJwsVerify [jwkFilename, jwsFilename] = do- Just jwk <- decode <$> L.readFile jwkFilename+ Just k <- decode <$> L.readFile jwkFilename Just jws <- decode <$> L.readFile jwsFilename- result <- runJOSE $ verifyJWS' (jwk :: JWK) (jws :: GeneralJWS JWSHeader)+ result <- runJOSE $ verifyJWS' (k :: JWK) (jws :: GeneralJWS JWSHeader) case result of Left e -> print (e :: Error) >> exitFailure Right s -> L.putStr s
jose.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.2 name: jose-version: 0.10+version: 0.10.0.1 synopsis: JSON Object Signing and Encryption (JOSE) and JSON Web Token (JWT) library description:@@ -33,7 +33,7 @@ category: Cryptography build-type: Simple tested-with:- GHC==8.0.2, GHC==8.2.2, GHC==8.4.4, GHC==8.6.5, GHC==8.8.4, GHC==8.10.7, GHC==9.0.2, GHC==9.2.4, GHC==9.4.2+ GHC ==8.0.2 || ==8.2.2 || ==8.4.4 || ==8.6.5 || ==8.8.4 || ==8.10.7 || ==9.0.2 || ==9.2.7 || ==9.4.7 || ==9.6.3 || == 9.8.1 flag demos description: Build demonstration programs@@ -41,15 +41,45 @@ common common default-language: Haskell2010- ghc-options: -Wall -Werror=missing-methods+ ghc-options:+ -Wall+ -Widentities+ -Wincomplete-record-updates+ -Wincomplete-uni-patterns+ -Werror=missing-methods+ if impl(ghc >= 8.0)+ ghc-options:+ -Wcompat+ -Wnoncanonical-monad-instances+ -Wredundant-constraints+ if impl(ghc >= 8.2)+ ghc-options:+ -fhide-source-paths+ if impl(ghc >= 8.4)+ ghc-options:+ -Wmissing-export-lists+ -Wpartial-fields+ if impl(ghc >= 8.10)+ ghc-options:+ -Wunused-packages+ if impl(ghc >= 9.0)+ ghc-options:+ -Winvalid-haddock+ -Werror=unicode-bidirectional-format-characters+ if impl(ghc >= 9.2)+ ghc-options:+ -Wimplicit-lift+ -Woperator-whitespace+ -Wredundant-bang-patterns+ if impl(ghc >= 9.4)+ ghc-options:+ -Wredundant-strictness-flags build-depends: base >= 4.9 && < 5- , aeson >= 2.0.1.0 && < 3- , bytestring >= 0.10 && < 0.12+ , bytestring >= 0.10 && < 0.13 , lens >= 4.16- , mtl >= 2- , text >= 1.1+ , mtl >= 2.2.1 library import: common@@ -77,6 +107,7 @@ Crypto.JOSE.Types.URI build-depends:+ , aeson >= 2.0.1.0 && < 3 , base64-bytestring >= 1.2.1.0 && < 1.3 , concise >= 0.1 , containers >= 0.5@@ -84,6 +115,7 @@ , memory >= 0.7 , monad-time >= 0.3 , template-haskell >= 2.11+ , text >= 1.1 , time >= 1.5 , network-uri >= 2.6 , x509 >= 1.4@@ -109,6 +141,7 @@ Types build-depends:+ , aeson , base64-bytestring , containers , cryptonite@@ -121,7 +154,7 @@ , jose , tasty- , tasty-hedgehog+ , tasty-hedgehog >= 1.2 , tasty-hspec >= 1.0 , hedgehog , hspec@@ -145,5 +178,7 @@ JWS build-depends:- unix+ aeson+ , text+ , unix , jose
src/Crypto/JOSE/Compact.hs view
@@ -21,7 +21,12 @@ functions for working with such data. -}-module Crypto.JOSE.Compact where+module Crypto.JOSE.Compact+ ( FromCompact(..)+ , decodeCompact+ , ToCompact(..)+ , encodeCompact+ ) where import Control.Monad.Except (MonadError) import qualified Data.ByteString.Lazy as L
src/Crypto/JOSE/Error.hs view
@@ -44,8 +44,8 @@ import Data.Semigroup ((<>)) import Numeric.Natural -import Control.Monad.Except-import Control.Monad.Trans+import Control.Monad.Except (MonadError(..), ExceptT, runExceptT)+import Control.Monad.Trans (MonadIO(liftIO), MonadTrans(lift)) import qualified Crypto.PubKey.RSA as RSA import Crypto.Error (CryptoError) import Crypto.Random (MonadRandom(..))
src/Crypto/JOSE/JWA/JWE.hs view
@@ -20,7 +20,13 @@ JSON Web Encryption data types specified under JSON Web Algorithms. -}-module Crypto.JOSE.JWA.JWE where+module Crypto.JOSE.JWA.JWE+ ( Enc(..)+ , AlgWithParams(..)+ , AESGCMParameters(AESGCMParameters)+ , ECDHParameters(ECDHParameters)+ , PBES2Parameters(PBES2Parameters)+ ) where import Data.Maybe (catMaybes)
src/Crypto/JOSE/JWA/JWE/Alg.hs view
@@ -20,7 +20,9 @@ JSON Web Encryption algorithms. -}-module Crypto.JOSE.JWA.JWE.Alg where+module Crypto.JOSE.JWA.JWE.Alg+ ( Alg(..)+ ) where import qualified Crypto.JOSE.TH
src/Crypto/JOSE/JWA/JWS.hs view
@@ -20,7 +20,9 @@ JSON Web Signature algorithms. -}-module Crypto.JOSE.JWA.JWS where+module Crypto.JOSE.JWA.JWS+ ( Alg(..)+ ) where import qualified Crypto.JOSE.TH
src/Crypto/JOSE/JWE.hs view
@@ -262,12 +262,14 @@ CryptoFailed _ -> return $ Left AlgorithmNotImplemented -- FIXME CryptoPassed (e :: e) -> do iv <- getRandomBytes 16- let Just iv' = makeIV iv- let m' = pad (PKCS7 $ blockSize e) m- let c = cbcEncrypt e iv' m'- let hmacInput = B.concat [aad, iv, c, aadLen]- let tag = B.take kLen $ BA.pack $ BA.unpack (hmac mKey hmacInput :: HMAC h)- return $ Right (iv, c, tag)+ case makeIV iv of+ Nothing -> pure $ Left (CryptoError CryptoError_IvSizeInvalid)+ Just iv' -> do+ let m' = pad (PKCS7 $ blockSize e) m+ let c = cbcEncrypt e iv' m'+ let hmacInput = B.concat [aad, iv, c, aadLen]+ let tag = BA.convert $ BA.takeView (hmac mKey hmacInput :: HMAC h) kLen+ pure $ Right (iv, c, tag) _gcmEnc :: forall e m. (BlockCipher e, MonadRandom m)
src/Crypto/JOSE/TH.hs view
@@ -44,7 +44,7 @@ conize = mkName . capitalize . sanitize guardPred :: String -> ExpQ-guardPred s = [e| $(varE $ mkName "s") == s |]+guardPred s = [e| $(varE $ mkName "s") == $(lift s) |] guardExp :: String -> ExpQ guardExp s = [e| pure $(conE $ conize s) |]@@ -58,7 +58,7 @@ -- | Expression for an end guard. Arg describes type it was expecting. -- endGuardExp :: String -> ExpQ-endGuardExp s = [e| fail ("unrecognised value; expected: " ++ s) |]+endGuardExp s = [e| fail ("unrecognised value; expected: " ++ $(lift s)) |] -- | Build a catch-all guard that fails. String describes what is expected. --@@ -76,7 +76,7 @@ toJSONClause :: String -> ClauseQ-toJSONClause s = clause [conP (conize s) []] (normalB [| s |]) []+toJSONClause s = clause [conP (conize s) []] (normalB [| $(lift s) |]) [] toJSONFun :: [String] -> DecQ toJSONFun vs = funD 'toJSON (map toJSONClause vs)
src/Crypto/JWT.hs view
@@ -30,81 +30,23 @@ JWTs use the JWS /compact serialisation/. See "Crypto.JOSE.Compact" for details. -@-import Crypto.JWT--mkClaims :: IO 'ClaimsSet'-mkClaims = do- t <- 'currentTime'- pure $ 'emptyClaimsSet'- & 'claimIss' ?~ "alice"- & 'claimAud' ?~ 'Audience' ["bob"]- & 'claimIat' ?~ 'NumericDate' t--doJwtSign :: 'JWK' -> 'ClaimsSet' -> IO (Either 'JWTError' 'SignedJWT')-doJwtSign jwk claims = 'runJOSE' $ do- alg \<- 'bestJWSAlg' jwk- 'signClaims' jwk ('newJWSHeader' ((), alg)) claims--doJwtVerify :: 'JWK' -> 'SignedJWT' -> IO (Either 'JWTError' 'ClaimsSet')-doJwtVerify jwk jwt = 'runJOSE' $ do- let config = 'defaultJWTValidationSettings' (== "bob")- 'verifyClaims' config jwk jwt-@--Some JWT libraries have a function that takes two strings: the-"secret" (a symmetric key) and the raw JWT. The following function-achieves the same:--@-verify :: L.ByteString -> L.ByteString -> IO (Either 'JWTError' 'ClaimsSet')-verify k s = 'runJOSE' $ do- let- k' = 'fromOctets' k -- turn raw secret into symmetric JWK- audCheck = const True -- should be a proper audience check- jwt <- 'decodeCompact' s -- decode JWT- 'verifyClaims' ('defaultJWTValidationSettings' audCheck) k' jwt-@--For applications that use __additional claims__, define a data type that wraps-'ClaimsSet' and includes fields for the additional claims. You will also need-to define 'FromJSON' if verifying JWTs, and 'ToJSON' if producing JWTs. The-following example is taken from-<https://datatracker.ietf.org/doc/html/rfc7519#section-3.1 RFC 7519 §3.1>.--@-import qualified Data.Aeson.KeyMap as M--data Super = Super { jwtClaims :: 'ClaimsSet', isRoot :: Bool }--instance 'HasClaimsSet' Super where- 'claimsSet' f s = fmap (\\a' -> s { jwtClaims = a' }) (f (jwtClaims s))--instance FromJSON Super where- parseJSON = withObject "Super" $ \\o -> Super- \<$\> parseJSON (Object o)- \<*\> o .: "http://example.com/is_root"--instance ToJSON Super where- toJSON s =- ins "http://example.com/is_root" (isRoot s) (toJSON (jwtClaims s))- where- ins k v (Object o) = Object $ M.insert k (toJSON v) o- ins _ _ a = a-@--__Use 'signJWT' and 'verifyJWT' when using custom payload types__ (instead of-'signClaims' and 'verifyClaims' which are specialised to 'ClaimsSet').- -} module Crypto.JWT (- -- * Creating a JWT+ -- * Overview / HOWTO+ -- ** Basic usage+ -- $basic++ -- ** Supporting additional claims via subtypes #subtypes#+ -- $subtypes++ -- * API+ -- ** Creating a JWT SignedJWT , signClaims , signJWT - -- * Validating a JWT and extracting claims+ -- ** Validating a JWT and extracting claims , defaultJWTValidationSettings , verifyClaims , verifyJWT@@ -115,24 +57,25 @@ , JWTValidationSettings , HasJWTValidationSettings(..) - -- ** Specifying the verification time+ -- *** Specifying the verification time , WrappedUTCTime(..) , verifyClaimsAt , verifyJWTAt - -- * Claims Set- , HasClaimsSet(..)+ -- ** Claims Set , ClaimsSet , emptyClaimsSet+ , HasClaimsSet(..)+ , validateClaimsSet+ -- *** Unregistered claims (__deprecated__) , addClaim , unregisteredClaims- , validateClaimsSet - -- * JWT errors+ -- ** JWT errors , JWTError(..) , AsJWTError(..) - -- * Miscellaneous+ -- ** Miscellaneous types , Audience(..) , StringOrURI , stringOrUri@@ -140,6 +83,7 @@ , uri , NumericDate(..) + -- ** Re-exports , module Crypto.JOSE ) where@@ -174,7 +118,81 @@ import Crypto.JOSE import Crypto.JOSE.Types +{- $basic +@+import Crypto.JWT++mkClaims :: IO 'ClaimsSet'+mkClaims = do+ t <- 'currentTime'+ pure $ 'emptyClaimsSet'+ & 'claimIss' ?~ "alice"+ & 'claimAud' ?~ 'Audience' ["bob"]+ & 'claimIat' ?~ 'NumericDate' t++doJwtSign :: 'JWK' -> 'ClaimsSet' -> IO (Either 'JWTError' 'SignedJWT')+doJwtSign jwk claims = 'runJOSE' $ do+ alg \<- 'bestJWSAlg' jwk+ 'signClaims' jwk ('newJWSHeader' ((), alg)) claims++doJwtVerify :: 'JWK' -> 'SignedJWT' -> IO (Either 'JWTError' 'ClaimsSet')+doJwtVerify jwk jwt = 'runJOSE' $ do+ let config = 'defaultJWTValidationSettings' (== "bob")+ 'verifyClaims' config jwk jwt+@++Some JWT libraries have a function that takes two strings: the+"secret" (a symmetric key) and the raw JWT. The following function+achieves the same:++@+verify :: L.ByteString -> L.ByteString -> IO (Either 'JWTError' 'ClaimsSet')+verify k s = 'runJOSE' $ do+ let+ k' = 'fromOctets' k -- turn raw secret into symmetric JWK+ audCheck = const True -- should be a proper audience check+ jwt <- 'decodeCompact' s -- decode JWT+ 'verifyClaims' ('defaultJWTValidationSettings' audCheck) k' jwt+@++-}++{- $subtypes++For applications that use __additional claims__, define a data type that wraps+'ClaimsSet' and includes fields for the additional claims. You will also need+to define 'FromJSON' if verifying JWTs, and 'ToJSON' if producing JWTs. The+following example is taken from+<https://datatracker.ietf.org/doc/html/rfc7519#section-3.1 RFC 7519 §3.1>.++@+import qualified Data.Aeson.KeyMap as M++data Super = Super { jwtClaims :: 'ClaimsSet', isRoot :: Bool }++instance 'HasClaimsSet' Super where+ 'claimsSet' f s = fmap (\\a' -> s { jwtClaims = a' }) (f (jwtClaims s))++instance FromJSON Super where+ parseJSON = withObject \"Super\" $ \\o -> Super+ \<$\> parseJSON (Object o)+ \<*\> o .: "http://example.com/is_root"++instance ToJSON Super where+ toJSON s =+ ins "http://example.com/is_root" (isRoot s) (toJSON (jwtClaims s))+ where+ ins k v (Object o) = Object $ M.insert k (toJSON v) o+ ins _ _ a = a+@++__Use 'signJWT' and 'verifyJWT' when using custom payload types__ (instead of+'signClaims' and 'verifyClaims' which are specialised to 'ClaimsSet').++-}++ data JWTError = JWSError Error -- ^ A JOSE error occurred while processing the JWT@@ -274,12 +292,11 @@ -- | The JWT Claims Set represents a JSON object whose members are -- the registered claims defined by RFC 7519. To construct a--- @ClaimsSet@ use 'emptyClaimsSet' then use the lenses from this--- class to set relevant claims.+-- @ClaimsSet@ use 'emptyClaimsSet' then use the lenses defined in+-- 'HasClaimsSet' to set relevant claims. -- -- For applications that use additional claims beyond those defined--- by RFC 7519, define a new data type and instance 'HasClaimsSet'.--- See the module synopsis for more details and an example.+-- by RFC 7519, define a [subtype](#g:subtypes) and instance 'HasClaimsSet'. -- data ClaimsSet = ClaimsSet { _claimIss :: Maybe StringOrURI@@ -353,13 +370,13 @@ claimJti :: Lens' a (Maybe T.Text) {-# INLINE claimJti #-} - claimAud = ((.) claimsSet) claimAud- claimExp = ((.) claimsSet) claimExp- claimIat = ((.) claimsSet) claimIat- claimIss = ((.) claimsSet) claimIss- claimJti = ((.) claimsSet) claimJti- claimNbf = ((.) claimsSet) claimNbf- claimSub = ((.) claimsSet) claimSub+ claimAud = claimsSet . claimAud+ claimExp = claimsSet . claimExp+ claimIat = claimsSet . claimIat+ claimIss = claimsSet . claimIss+ claimJti = claimsSet . claimJti+ claimNbf = claimsSet . claimNbf+ claimSub = claimsSet . claimSub instance HasClaimsSet ClaimsSet where claimsSet = id@@ -392,7 +409,7 @@ unregisteredClaims f h@ClaimsSet{ _unregisteredClaims = a} = fmap (\a' -> h { _unregisteredClaims = a' }) (f a) {-# INLINE unregisteredClaims #-}-{-# DEPRECATED unregisteredClaims "use a sub-type" #-}+{-# DEPRECATED unregisteredClaims "use a [subtype](#g:subtypes) to define additional claims" #-} -- | Return an empty claims set. --@@ -404,7 +421,7 @@ -- addClaim :: T.Text -> Value -> ClaimsSet -> ClaimsSet addClaim k v = over unregisteredClaims (M.insert k v)-{-# DEPRECATED addClaim "'unregisteredClaims' is deprecated; use a sub-type" #-}+{-# DEPRECATED addClaim "use a [subtype](#g:subtypes) to define additional claims" #-} registeredClaims :: S.Set T.Text registeredClaims = S.fromDistinctAscList@@ -608,11 +625,12 @@ newtype WrappedUTCTime = WrappedUTCTime { getUTCTime :: UTCTime } +#if MIN_VERSION_monad_time(0,4,0)+-- | @'monotonicTime' = pure 0@. /jose/ doesn't use this so we fake it+#endif instance Monad m => MonadTime (ReaderT WrappedUTCTime m) where currentTime = asks getUTCTime #if MIN_VERSION_monad_time(0,4,0)- -- | /jose/ doesn't use this, so we fake it.- -- @monotonicTime = pure 0@ monotonicTime = pure 0 #endif
test/AESKW.hs view
@@ -15,7 +15,9 @@ {-# LANGUAGE NoMonomorphismRestriction #-} {-# LANGUAGE OverloadedStrings #-} -module AESKW where+module AESKW+ ( aeskwProperties+ ) where import qualified Data.ByteString as B import Crypto.Cipher.AES
test/Examples.hs view
@@ -1,7 +1,10 @@--- | Miscellaneous end-to-end examples. {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE LambdaCase #-}-module Examples where++-- | Miscellaneous end-to-end examples.+module Examples+ ( spec+ ) where import Control.Lens (_Right, (&), (?~), (.~)) import Control.Lens.Extras (is)
test/JWK.hs view
@@ -12,9 +12,12 @@ -- See the License for the specific language governing permissions and -- limitations under the License. +{-# OPTIONS_GHC -fno-warn-incomplete-uni-patterns #-} {-# LANGUAGE OverloadedStrings #-} -module JWK where+module JWK+ ( spec+ ) where import Data.Monoid ((<>))
test/JWS.hs view
@@ -14,7 +14,9 @@ {-# LANGUAGE OverloadedStrings #-} -module JWS where+module JWS+ ( spec+ ) where import Data.Maybe import Data.Monoid ((<>))@@ -169,7 +171,7 @@ let -- protected header: {"crit":["nonce"],"nonce":"bm9uY2U"} s = "{\"protected\":\"eyJjcml0IjpbIm5vbmNlIl0sIm5vbmNlIjoiYm05dVkyVSJ9\""- <>",\"header\":{\"alg\":\"none\"},\"signature\":\"\"}"+ <> ",\"header\":{\"alg\":\"none\"},\"signature\":\"\"}" in (eitherDecode s :: Either String (Signature Protection ACMEHeader)) `shouldSatisfy` is _Right@@ -201,9 +203,6 @@ \{\"iss\":\"joe\",\r\n\ \ \"exp\":1300819380,\r\n\ \ \"http://example.com/is_root\":true}"--examplePayload :: Types.Base64Octets-examplePayload = Types.Base64Octets examplePayloadBytes appendixA1Spec :: Spec
test/JWT.hs view
@@ -15,7 +15,9 @@ {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE OverloadedStrings #-} -module JWT where+module JWT+ ( spec+ ) where import Data.Maybe import Data.Monoid ((<>))@@ -108,7 +110,6 @@ it "JWT round-trip (sign, serialise, decode, verify) [extended payload type]" $ do let- claims = emptyClaimsSet valConf = defaultJWTValidationSettings (const True) now = utcTime "2010-01-01 00:00:00" k <- genJWK $ RSAGenParam 256@@ -116,8 +117,8 @@ token <- signJWT k (newJWSHeader ((), RS512)) super token' <- decodeCompact . encodeCompact $ token liftIO $ token' `shouldBe` token- claims' <- runReaderT (verifyJWT valConf k token') now- liftIO $ claims' `shouldBe` super+ claims <- runReaderT (verifyJWT valConf k token') now+ liftIO $ claims `shouldBe` super either (error . show) return (res :: Either JWTError ()) :: IO () it "formats to a parsable and equal value" $ do@@ -199,62 +200,64 @@ describe "with a Not Before claim" $ do let- claimsSet = emptyClaimsSet & claimNbf .~ intDate "2016-07-05 17:37:22"+ claims = emptyClaimsSet & claimNbf .~ intDate "2016-07-05 17:37:22" describe "when the current time is prior to the Not Before claim" $ do let now = utcTime "2016-07-05 17:37:20" -- 2s before nbf it "cannot be validated" $- runReaderT (validateClaimsSet conf claimsSet) now+ runReaderT (validateClaimsSet conf claims) now `shouldBe` Left JWTNotYetValid it "cannot be validated if nonzero skew tolerance < delta" $ let conf' = set allowedSkew 1 conf- in runReaderT (validateClaimsSet conf' claimsSet) now+ in runReaderT (validateClaimsSet conf' claims) now `shouldBe` Left JWTNotYetValid it "can be validated if nonzero skew tolerance = delta" $ let conf' = set allowedSkew 2 conf- in runReaderT (validateClaimsSet conf' claimsSet) now- `shouldBe` (Right claimsSet :: Either JWTError ClaimsSet)+ in runReaderT (validateClaimsSet conf' claims) now+ `shouldBe` (Right claims :: Either JWTError ClaimsSet) it "can be validated if nonzero skew tolerance > delta" $ let conf' = set allowedSkew 3 conf- in runReaderT (validateClaimsSet conf' claimsSet) now- `shouldBe` (Right claimsSet :: Either JWTError ClaimsSet)+ in runReaderT (validateClaimsSet conf' claims) now+ `shouldBe` (Right claims :: Either JWTError ClaimsSet) it "can be validated if negative skew tolerance = -delta" $ let conf' = set allowedSkew (-2) conf- in runReaderT (validateClaimsSet conf' claimsSet) now- `shouldBe` (Right claimsSet :: Either JWTError ClaimsSet)+ in runReaderT (validateClaimsSet conf' claims) now+ `shouldBe` (Right claims :: Either JWTError ClaimsSet) describe "when the current time is exactly equal to the Not Before claim" $ it "can be validated" $- runReaderT (validateClaimsSet conf claimsSet) (utcTime "2016-07-05 17:37:22")- `shouldBe` (Right claimsSet :: Either JWTError ClaimsSet)+ runReaderT (validateClaimsSet conf claims) (utcTime "2016-07-05 17:37:22")+ `shouldBe` (Right claims :: Either JWTError ClaimsSet) describe "when the current time is after the Not Before claim" $ it "can be validated" $- runReaderT (validateClaimsSet conf claimsSet) (utcTime "2017-01-01 00:00:00")- `shouldBe` (Right claimsSet :: Either JWTError ClaimsSet)+ runReaderT (validateClaimsSet conf claims) (utcTime "2017-01-01 00:00:00")+ `shouldBe` (Right claims :: Either JWTError ClaimsSet) describe "with Expiration Time and Not Before claims" $ do let- claimsSet = emptyClaimsSet & claimExp .~ intDate "2011-03-22 18:43:00"- & claimNbf .~ intDate "2011-03-20 17:37:22"+ claims =+ emptyClaimsSet+ & claimExp .~ intDate "2011-03-22 18:43:00"+ & claimNbf .~ intDate "2011-03-20 17:37:22" describe "when the current time is prior to the Not Before claim" $ it "cannot be validated" $- runReaderT (validateClaimsSet conf claimsSet) (utcTime "2011-03-18 00:00:00")+ runReaderT (validateClaimsSet conf claims) (utcTime "2011-03-18 00:00:00") `shouldBe` Left JWTNotYetValid describe "when the current time is exactly equal to the Not Before claim" $ it "can be validated" $- runReaderT (validateClaimsSet conf claimsSet) (utcTime "2011-03-20 17:37:22")- `shouldBe` (Right claimsSet :: Either JWTError ClaimsSet)+ runReaderT (validateClaimsSet conf claims) (utcTime "2011-03-20 17:37:22")+ `shouldBe` (Right claims :: Either JWTError ClaimsSet) describe "when the current time is between the Not Before and Expiration Time claims" $ it "can be validated" $- runReaderT (validateClaimsSet conf claimsSet) (utcTime "2011-03-21 18:00:00")- `shouldBe` (Right claimsSet :: Either JWTError ClaimsSet)+ runReaderT (validateClaimsSet conf claims) (utcTime "2011-03-21 18:00:00")+ `shouldBe` (Right claims :: Either JWTError ClaimsSet) describe "when the current time is exactly the Expiration Time" $ it "cannot be validated" $- runReaderT (validateClaimsSet conf claimsSet) (utcTime "2011-03-22 18:43:00")+ runReaderT (validateClaimsSet conf claims) (utcTime "2011-03-22 18:43:00") `shouldBe` Left JWTExpired describe "when the current time is after the Expiration Time claim" $ it "cannot be validated" $- runReaderT (validateClaimsSet conf claimsSet) (utcTime "2011-03-24 00:00:00")+ runReaderT (validateClaimsSet conf claims) (utcTime "2011-03-24 00:00:00") `shouldBe` Left JWTExpired describe "with an Audience claim" $ do
test/Perf.hs view
@@ -1,3 +1,4 @@+{-# OPTIONS_GHC -fno-warn-incomplete-uni-patterns #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} @@ -12,7 +13,6 @@ Related: https://github.com/frasertweedale/hs-jose/pull/103 -}-module Main where import Control.Lens ((^?), _Just) import Control.Monad.Except (ExceptT, runExceptT)
test/Properties.hs view
@@ -14,9 +14,12 @@ {-# LANGUAGE NoMonomorphismRestriction #-} {-# LANGUAGE OverloadedStrings #-}+{-# OPTIONS_GHC -fno-warn-incomplete-uni-patterns #-} {-# OPTIONS_GHC -fno-warn-orphans #-} -module Properties where+module Properties+ ( properties+ ) where import Control.Applicative (liftA2) import Control.Monad.IO.Class
test/Types.hs view
@@ -14,7 +14,9 @@ {-# LANGUAGE OverloadedStrings #-} -module Types where+module Types+ ( spec+ ) where import Data.Aeson import qualified Data.ByteString as BS