jwt 0.8.0 → 0.9.0
raw patch · 3 files changed
+58/−57 lines, 3 filesdep +x509dep +x509-storedep −HsOpenSSLdep −RSAdep ~basenew-uploaderPVP ok
version bump matches the API change (PVP)
Dependencies added: x509, x509-store
Dependencies removed: HsOpenSSL, RSA
Dependency ranges changed: base
API changes (from Hackage documentation)
+ Web.JWT: instance GHC.Base.Semigroup Web.JWT.ClaimsMap
+ Web.JWT: instance GHC.Base.Semigroup Web.JWT.JOSEHeader
+ Web.JWT: instance GHC.Base.Semigroup Web.JWT.JWTClaimsSet
+ Web.JWT: readRsaSecret :: ByteString -> Maybe PrivateKey
Files
- CHANGELOG.md +5/−0
- jwt.cabal +8/−7
- src/Web/JWT.hs +45/−50
CHANGELOG.md view
@@ -1,3 +1,8 @@+# 2018-01-04 0.9.0++* Switch from RSA and HsOpenSSL to x509-store+* Add Semigroup instances for GHC 8.6 compatibility+ # 2018-03-21 0.8.0 * Support RS256 algorithm
jwt.cabal view
@@ -2,12 +2,12 @@ -- documentation, see http://haskell.org/cabal/users-guide/ name: jwt-version: 0.8.0+version: 0.9.0 synopsis: JSON Web Token (JWT) decoding and encoding license: MIT license-file: LICENSE-author: Stefan Saasen-maintainer: stefan@saasen.me+author: Brian McKenna+maintainer: brian@brianmckenna.org homepage: https://bitbucket.org/ssaasen/haskell-jwt bug-reports: https://bitbucket.org/ssaasen/haskell-jwt/issues category: Web@@ -46,8 +46,8 @@ , vector >= 0.7.1 , semigroups >= 0.15.4 , network-uri- , RSA- , HsOpenSSL+ , x509+ , x509-store hs-source-dirs: src default-language: Haskell2010@@ -68,6 +68,7 @@ , Web.JWTTestsCompat , Data.Text.Extended , Data.Text.ExtendedTests+ , Data.ByteString.Extended , Data.ByteString.ExtendedTests hs-source-dirs: tests/src, src build-depends: base < 5 && >= 4.4@@ -92,8 +93,8 @@ , vector >= 0.7.1 , semigroups >= 0.15.4 , network-uri- , RSA- , HsOpenSSL+ , x509+ , x509-store cpp-options: -DTEST
src/Web/JWT.hs view
@@ -40,7 +40,7 @@ -- ** Common , tokenIssuer , hmacSecret- , rsaKeySecret+ , readRsaSecret -- ** JWT structure , claims , header@@ -72,18 +72,23 @@ , StringOrURI , JWTHeader , JOSEHeader++ -- * Deprecated+ , rsaKeySecret ) where +import qualified Data.ByteString.Char8 as C8 import qualified Data.ByteString.Lazy.Char8 as BL (fromStrict, toStrict) import qualified Data.ByteString.Extended as BS import qualified Data.Text.Extended as T import qualified Data.Text.Encoding as TE -import Codec.Crypto.RSA (PrivateKey(..), PublicKey(..), sign) import Control.Applicative import Control.Monad import Crypto.Hash.Algorithms import Crypto.MAC.HMAC+import Crypto.PubKey.RSA (PrivateKey)+import Crypto.PubKey.RSA.PKCS15 (sign) import Data.ByteArray.Encoding import Data.Aeson hiding (decode, encode) import qualified Data.Aeson as JSON@@ -91,24 +96,11 @@ import qualified Data.Map as Map import Data.Maybe import Data.Scientific+import qualified Data.Semigroup as Semigroup import Data.Time.Clock (NominalDiffTime)+import Data.X509 (PrivKey (PrivKeyRSA))+import Data.X509.Memory (readKeyFileFromMemory) import qualified Network.URI as URI-import OpenSSL.EVP.PKey (toKeyPair)-import OpenSSL.PEM (PemPasswordSupply(..), readPrivateKey)-import OpenSSL.RSA- ( RSAKeyPair- , RSAPubKey- , rsaCopyPublic- , rsaD- , rsaDMP1- , rsaDMQ1- , rsaE- , rsaIQMP- , rsaN- , rsaP- , rsaQ- , rsaSize- ) import Prelude hiding (exp) -- $setup@@ -209,9 +201,12 @@ instance Monoid JOSEHeader where mempty = JOSEHeader Nothing Nothing Nothing- mappend (JOSEHeader a b c) (JOSEHeader a' b' c') =- JOSEHeader (a <|> a') (b <|> b') (c <|> c')+ mappend = (Semigroup.<>) +instance Semigroup.Semigroup JOSEHeader where+ JOSEHeader a b c <> JOSEHeader a' b' c' =+ JOSEHeader (a <|> a') (b <|> b') (c <|> c')+ -- | The JWT Claims Set represents a JSON object whose members are the claims conveyed by the JWT. data JWTClaimsSet = JWTClaimsSet { -- Registered Claim Names@@ -245,9 +240,12 @@ instance Monoid JWTClaimsSet where mempty = JWTClaimsSet Nothing Nothing Nothing Nothing Nothing Nothing Nothing $ ClaimsMap Map.empty- mappend (JWTClaimsSet a b c d e f g h) (JWTClaimsSet a' b' c' d' e' f' g' h') =- JWTClaimsSet (a <|> a') (b <|> b') (c <|> c') (d <|> d') (e <|> e') (f <|> f') (g <|> g') (mappend h h')+ mappend = (Semigroup.<>) +instance Semigroup.Semigroup JWTClaimsSet where+ JWTClaimsSet a b c d e f g h <> JWTClaimsSet a' b' c' d' e' f' g' h' =+ JWTClaimsSet (a <|> a') (b <|> b') (c <|> c') (d <|> d') (e <|> e') (f <|> f') (g <|> g') (h Semigroup.<> h')+ -- | Encode a claims set using the given secret -- -- @@@ -378,11 +376,17 @@ -- | Create an RSAPrivateKey from PEM contents ----- > rsaKeySecret =<< readFile "foo.pem"+-- Please, consider using 'readRsaSecret' instead.+rsaKeySecret :: String -> IO (Maybe Signer)+rsaKeySecret = pure . fmap RSAPrivateKey . readRsaSecret . C8.pack++-- | Create an RSA 'PrivateKey' from PEM contents --+-- > readRsaSecret <$> BS.readFile "foo.pem"+-- -- >>> :{ -- -- A random example key created with `ssh-keygen -t rsa`--- fmap (\(Just (RSAPrivateKey pk)) -> pk) $ rsaKeySecret $ unlines+-- fromJust . readRsaSecret . C8.pack $ unlines -- [ "-----BEGIN RSA PRIVATE KEY-----" -- , "MIIEowIBAAKCAQEAkkmgbLluo5HommstpHr1h53uWfuN3CwYYYR6I6a2MzAHIMIv" -- , "8Ak2ha+N2UDeYsfVhZ/DOnE+PMm2RpYSaiYT0l2a7ZkmRSbcyvVFt3XLePJbmUgo"@@ -414,27 +418,11 @@ -- :} -- PrivateKey {private_pub = PublicKey {public_size = 256, public_n = 1846..., public_e = 65537}, private_d = 8823..., private_p = 135..., private_q = 1358..., private_dP = 1373..., private_dQ = 9100..., private_qinv = 8859...} ---rsaKeySecret :: String -> IO (Maybe Signer)-rsaKeySecret k = do- mKeyPair <- toKeyPair <$> readPrivateKey k PwNone- mPublicKey <- mapM rsaCopyPublic mKeyPair- return $ RSAPrivateKey <$>- (fromRSAKey <$> mKeyPair <*> mPublicKey)- where- fromRSAKey :: RSAKeyPair -> RSAPubKey -> PrivateKey- fromRSAKey kp pk = PrivateKey- { private_pub = PublicKey- { public_size = rsaSize pk- , public_n = rsaN pk- , public_e = rsaE pk- }- , private_d = rsaD kp- , private_p = rsaP kp- , private_q = rsaQ kp- , private_dP = fromMaybe 0 $ rsaDMP1 kp- , private_dQ = fromMaybe 0 $ rsaDMQ1 kp- , private_qinv = fromMaybe 0 $ rsaIQMP kp- }+readRsaSecret :: BS.ByteString -> (Maybe PrivateKey)+readRsaSecret bs =+ case readKeyFileFromMemory bs of+ [(PrivKeyRSA k)] -> Just k+ _ -> Nothing -- | Convert the `NominalDiffTime` into an IntDate. Returns a Nothing if the -- argument is invalid (e.g. the NominalDiffTime must be convertible into a@@ -494,10 +482,14 @@ calculateDigest (RSAPrivateKey key) msg = TE.decodeUtf8 $ convertToBase Base64URLUnpadded- $ BL.toStrict- $ sign key- $ BL.fromStrict+ $ sign' $ TE.encodeUtf8 msg+ where+ sign' :: BS.ByteString -> BS.ByteString+ sign' bs = case sign Nothing (Just SHA256) key bs of+ Right sig -> sig+ Left _ -> error "impossible" -- This function can only fail with @SignatureTooLong@,+ -- which is impossible because we use a hash. -- ================================================================================= @@ -507,8 +499,11 @@ instance Monoid ClaimsMap where mempty = ClaimsMap mempty- mappend (ClaimsMap a) (ClaimsMap b) =- ClaimsMap $ mappend a b+ mappend = (Semigroup.<>)++instance Semigroup.Semigroup ClaimsMap where+ ClaimsMap a <> ClaimsMap b =+ ClaimsMap $ a Semigroup.<> b fromHashMap :: Object -> ClaimsMap fromHashMap = ClaimsMap . Map.fromList . StrictMap.toList