packages feed

jose 0.8.3.1 → 0.8.4

raw patch · 6 files changed

+89/−87 lines, 6 filesdep ~base64-bytestringPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependency ranges changed: base64-bytestring

API changes (from Hackage documentation)

+ Crypto.JOSE.JWS: rawProtectedHeader :: (HasParams a, ProtectionIndicator p) => Signature p a -> ByteString
- Crypto.JWT: _JWSError :: AsJWTError r_a1fD6 => Prism' r_a1fD6 Error
+ Crypto.JWT: _JWSError :: AsJWTError r_a1fhJ => Prism' r_a1fhJ Error
- Crypto.JWT: _JWTClaimsSetDecodeError :: AsJWTError r_a1fD6 => Prism' r_a1fD6 String
+ Crypto.JWT: _JWTClaimsSetDecodeError :: AsJWTError r_a1fhJ => Prism' r_a1fhJ String
- Crypto.JWT: _JWTError :: AsJWTError r_a1fD6 => Prism' r_a1fD6 JWTError
+ Crypto.JWT: _JWTError :: AsJWTError r_a1fhJ => Prism' r_a1fhJ JWTError
- Crypto.JWT: _JWTExpired :: AsJWTError r_a1fD6 => Prism' r_a1fD6 ()
+ Crypto.JWT: _JWTExpired :: AsJWTError r_a1fhJ => Prism' r_a1fhJ ()
- Crypto.JWT: _JWTIssuedAtFuture :: AsJWTError r_a1fD6 => Prism' r_a1fD6 ()
+ Crypto.JWT: _JWTIssuedAtFuture :: AsJWTError r_a1fhJ => Prism' r_a1fhJ ()
- Crypto.JWT: _JWTNotInAudience :: AsJWTError r_a1fD6 => Prism' r_a1fD6 ()
+ Crypto.JWT: _JWTNotInAudience :: AsJWTError r_a1fhJ => Prism' r_a1fhJ ()
- Crypto.JWT: _JWTNotInIssuer :: AsJWTError r_a1fD6 => Prism' r_a1fD6 ()
+ Crypto.JWT: _JWTNotInIssuer :: AsJWTError r_a1fhJ => Prism' r_a1fhJ ()
- Crypto.JWT: _JWTNotYetValid :: AsJWTError r_a1fD6 => Prism' r_a1fD6 ()
+ Crypto.JWT: _JWTNotYetValid :: AsJWTError r_a1fhJ => Prism' r_a1fhJ ()
- Crypto.JWT: class AsJWTError r_a1fD6
+ Crypto.JWT: class AsJWTError r_a1fhJ
- Crypto.JWT: class HasJWTValidationSettings c_a1gnU
+ Crypto.JWT: class HasJWTValidationSettings c_a1g2x
- Crypto.JWT: jWTValidationSettings :: HasJWTValidationSettings c_a1gnU => Lens' c_a1gnU JWTValidationSettings
+ Crypto.JWT: jWTValidationSettings :: HasJWTValidationSettings c_a1g2x => Lens' c_a1g2x JWTValidationSettings
- Crypto.JWT: jwtValidationSettingsAllowedSkew :: HasJWTValidationSettings c_a1gnU => Lens' c_a1gnU NominalDiffTime
+ Crypto.JWT: jwtValidationSettingsAllowedSkew :: HasJWTValidationSettings c_a1g2x => Lens' c_a1g2x NominalDiffTime
- Crypto.JWT: jwtValidationSettingsAudiencePredicate :: HasJWTValidationSettings c_a1gnU => Lens' c_a1gnU (StringOrURI -> Bool)
+ Crypto.JWT: jwtValidationSettingsAudiencePredicate :: HasJWTValidationSettings c_a1g2x => Lens' c_a1g2x (StringOrURI -> Bool)
- Crypto.JWT: jwtValidationSettingsCheckIssuedAt :: HasJWTValidationSettings c_a1gnU => Lens' c_a1gnU Bool
+ Crypto.JWT: jwtValidationSettingsCheckIssuedAt :: HasJWTValidationSettings c_a1g2x => Lens' c_a1g2x Bool
- Crypto.JWT: jwtValidationSettingsIssuerPredicate :: HasJWTValidationSettings c_a1gnU => Lens' c_a1gnU (StringOrURI -> Bool)
+ Crypto.JWT: jwtValidationSettingsIssuerPredicate :: HasJWTValidationSettings c_a1g2x => Lens' c_a1g2x (StringOrURI -> Bool)
- Crypto.JWT: jwtValidationSettingsValidationSettings :: HasJWTValidationSettings c_a1gnU => Lens' c_a1gnU ValidationSettings
+ Crypto.JWT: jwtValidationSettingsValidationSettings :: HasJWTValidationSettings c_a1g2x => Lens' c_a1g2x ValidationSettings

Files

jose.cabal view
@@ -1,6 +1,6 @@ cabal-version:       2.2 name:                jose-version:             0.8.3.1+version:             0.8.4 synopsis:   Javascript Object Signing and Encryption and JSON Web Token library description:@@ -79,7 +79,7 @@    build-depends:     attoparsec-    , base64-bytestring >= 1.0 && < 1.3+    , base64-bytestring >= 1.1 && < 1.3     , concise >= 0.1     , containers >= 0.5     , cryptonite >= 0.7
src/Crypto/JOSE/Header.hs view
@@ -67,10 +67,9 @@ import Data.Monoid ((<>)) import Data.Proxy (Proxy(..)) -import Control.Lens (Lens', Getter, to)+import Control.Lens (Lens', Getter, review, to) import Data.Aeson (FromJSON(..), Object, Value, encode, object) import Data.Aeson.Types (Pair, Parser)-import qualified Data.ByteString.Base64.URL.Lazy as B64UL import qualified Data.ByteString.Lazy as L import qualified Data.HashMap.Strict as M import qualified Data.Text as T@@ -78,7 +77,7 @@ import qualified Crypto.JOSE.JWA.JWS as JWA.JWS import Crypto.JOSE.JWK (JWK) import Crypto.JOSE.Types.Orphans ()-import Crypto.JOSE.Types.Internal (unpad)+import Crypto.JOSE.Types.Internal (base64url) import qualified Crypto.JOSE.Types as Types  @@ -119,13 +118,13 @@     [] -> Nothing     xs -> Just (object xs) --- | Return the encoded protected parameters+-- | Return the base64url-encoded protected parameters -- protectedParamsEncoded   :: (HasParams a, ProtectionIndicator p)   => a p -> L.ByteString protectedParamsEncoded =-  maybe mempty (unpad . B64UL.encode . encode) . protectedParams+  maybe mempty (review base64url . encode) . protectedParams  -- | Return unprotected params as a JSON 'Value' (always an object) --
src/Crypto/JOSE/JWS.hs view
@@ -1,4 +1,4 @@--- Copyright (C) 2013, 2014, 2015, 2016  Fraser Tweedale+-- Copyright (C) 2013, 2014, 2015, 2016, 2020  Fraser Tweedale -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License.@@ -73,6 +73,7 @@   , Signature   , header   , signature+  , rawProtectedHeader    -- * JWS headers   , Alg(..)@@ -313,9 +314,9 @@     )  instance (HasParams a, ProtectionIndicator p) => ToJSON (Signature p a) where-  toJSON (Signature _ h sig) =+  toJSON s@(Signature _ h sig) =     let-      pro = case protectedParamsEncoded h of+      pro = case rawProtectedHeader s of         "" -> id         bs -> ("protected" .= String (T.decodeUtf8 (view recons bs)) :)       unp = case unprotectedParams h of@@ -430,22 +431,32 @@  signingInput   :: (HasParams a, ProtectionIndicator p)-  => Either T.Text (a p)-  -> B.ByteString+  => Signature p a+  -> Types.Base64Octets   -> B.ByteString-signingInput h p = B.intercalate "."-  [ either T.encodeUtf8 (view recons . protectedParamsEncoded) h-  , review Types.base64url p-  ]+signingInput sig (Types.Base64Octets p) =+  rawProtectedHeader sig <> "." <> review Types.base64url p +-- | Return the raw base64url-encoded protected header value.+-- If the Signature was decoded from JSON, this returns the+-- original string value as-is.+--+-- Application code should never need to use this.  It is exposed+-- for testing purposes.+rawProtectedHeader+  :: (HasParams a, ProtectionIndicator p)+  => Signature p a -> B.ByteString+rawProtectedHeader (Signature raw h _) =+  maybe (view recons $ protectedParamsEncoded h) T.encodeUtf8 raw+ -- Convert JWS to compact serialization. -- -- The operation is defined only when there is exactly one -- signature and returns Nothing otherwise -- instance HasParams a => ToCompact (JWS Identity () a) where-  toCompact (JWS (Types.Base64Octets p) (Identity (Signature raw h (Types.Base64Octets sig)))) =-    [ view recons $ signingInput (maybe (Right h) Left raw) p+  toCompact (JWS p (Identity s@(Signature _ _ (Types.Base64Octets sig)))) =+    [ view recons $ signingInput s p     , review Types.base64url sig     ] @@ -487,8 +498,14 @@      )   => B.ByteString -> a p -> JWK -> m (Signature p a) mkSignature p h k =-  Signature Nothing h . Types.Base64Octets-  <$> sign (view (alg . param) h) (k ^. jwkMaterial) (signingInput (Right h) p)+  let+    almostSig = Signature Nothing h . Types.Base64Octets+  in+    almostSig+    <$> sign+          (view (alg . param) h)+          (k ^. jwkMaterial)+          (signingInput (almostSig "") (Types.Base64Octets p))   -- | Validation policy.@@ -633,7 +650,7 @@   -> Signature p a   -> JWK   -> Either Error Bool-verifySig (Types.Base64Octets m) (Signature raw h (Types.Base64Octets s)) k =+verifySig msg sig@(Signature _ h (Types.Base64Octets s)) k =   verify (view (alg . param) h) (view jwkMaterial k) tbs s   where-  tbs = signingInput (maybe (Right h) Left raw) m+  tbs = signingInput sig msg
src/Crypto/JOSE/Types/Internal.hs view
@@ -28,8 +28,6 @@   , parseB64   , encodeB64Url   , parseB64Url-  , pad-  , unpad   , bsToInteger   , integerToBS   , intBytes@@ -38,7 +36,6 @@   ) where  import Data.Bifunctor (first)-import Data.Char (ord) import Data.Monoid ((<>)) import Data.Tuple (swap) import Data.Word (Word8)@@ -48,7 +45,6 @@ import Crypto.Number.Basic (log2) import Data.Aeson.Types import qualified Data.ByteString as B-import qualified Data.ByteString.Lazy as L import qualified Data.ByteString.Base64 as B64 import qualified Data.ByteString.Base64.URL as B64U import qualified Data.HashMap.Strict as M@@ -74,63 +70,7 @@ encodeB64 :: B.ByteString -> Value encodeB64 = String . E.decodeUtf8 . B64.encode -class IsChar a where-  fromChar :: Char -> a -instance IsChar Char where-  fromChar = id--instance IsChar Word8 where-  fromChar = fromIntegral . ord---- | Add appropriate base64 '=' padding.----pad :: (Snoc s s a a, IsChar a) => s -> s-pad = rpad 4 (fromChar '=')-{-# INLINE [2] pad #-}--rpad :: (Snoc s s a a) => Int -> a -> s -> s-rpad w a s =-  let n = ((w - snocLength s `mod` w) `mod` w)-  in foldr (.) id (replicate n (`snoc` a)) s-{-# INLINE rpad #-}--snocLength :: (Snoc s s a a) => s -> Int-snocLength s = case unsnoc s of-  Nothing -> 0-  Just (s', _) -> 1 + snocLength s'-{-# INLINE snocLength #-}--padB :: B.ByteString -> B.ByteString-padB s = s <> B.replicate ((4 - B.length s `mod` 4) `mod` 4) 61-{-# RULES "pad/padB" pad = padB #-}--padL :: L.ByteString -> L.ByteString-padL s = s <> L.replicate ((4 - L.length s `mod` 4) `mod` 4) 61-{-# RULES "pad/padL" pad = padL #-}----- | Strip base64 '=' padding.----unpad :: (Snoc s s a a, IsChar a, Eq a) => s -> s-unpad = rstrip (== fromChar '=')-{-# INLINE [2] unpad #-}--rstrip :: (Snoc s s a a) => (a -> Bool) -> s -> s-rstrip p s = case unsnoc s of-  Nothing -> s-  Just (s', a) -> if p a then rstrip p s' else s-{-# INLINE rstrip #-}--unpadB :: B.ByteString -> B.ByteString-unpadB = B.reverse . B.dropWhile (== 61) . B.reverse-{-# RULES "unpad/unpadB" unpad = unpadB #-}--unpadL :: L.ByteString -> L.ByteString-unpadL = L.reverse . L.dropWhile (== 61) . L.reverse-{-# RULES "unpad/unpadL" unpad = unpadL #-}-- -- | Prism for encoding / decoding base64url. -- -- To encode, @'review' base64url@.@@ -143,10 +83,9 @@   , Cons s1 s1 Word8 Word8   , Cons s2 s2 Word8 Word8   ) => Prism' s1 s2-base64url = reconsIso . padder . b64u . reconsIso+base64url = reconsIso . b64u . reconsIso   where-    padder = iso pad unpad-    b64u = prism B64U.encode (\s -> first (const s) (B64U.decode s))+    b64u = prism B64U.encodeUnpadded (\s -> first (const s) (B64U.decodeUnpadded s))     reconsIso = iso (view recons) (view recons)  
test/JWS.hs view
@@ -50,6 +50,8 @@   appendixA5Spec   appendixA6Spec   cfrgSpec+  base64urlSpec+  reserialiseSpec   -- Extension of JWSHeader to test "crit" behaviour@@ -519,3 +521,48 @@   it "validates signatures correctly" $     verify JWA.JWS.EdDSA (view jwkMaterial jwk) signingInput sig       `shouldBe` (Right True :: Either Error Bool)++base64urlSpec :: Spec+base64urlSpec = describe "base64url-decoding behaviour" $+  it "rejects baseurl-encoded values with padding" $+    -- from https://github.com/frasertweedale/hs-jose/issues/99+    --  and https://github.com/frasertweedale/hs-jose/issues/97+    let+      inJws = "{\"signature\":\"fake\",\"protected\":\"eyJraWQiOiI1ODJhMTdkZS0zYjg4LTQxZTgtOGM3MC0zZjBkNmYwMDNmY2QiLCJhbGciOiJSUzI1NiJ9\",\"payload\":\"eyJ1c2VybmFtZSI6Imp1c3BheSIsImdzdGluIjoiMjBhYWFhYTEyMzRhMWEyIn0=\"}"+    in+      (eitherDecode inJws :: Either String (FlattenedJWS JWSHeader))+        `shouldSatisfy` is _Left++reserialiseSpec :: Spec+reserialiseSpec = describe "reserialisation of JWS" $+  it "preserves raw protected header value" $ do+    -- https://github.com/frasertweedale/hs-jose/issues/98+    --+    -- Test two different JWKs with same protected header fields+    -- but different order.  For each input parse then reserialise,+    -- then parse again and ensure that the raw protected header+    -- string is the same for both parses.+    --+    -- The two strings with different orders are needed to test+    -- that we preserve and use the recorded raw protected header+    -- when reserialising the object.  (We don't know what order+    -- the keys will be in the aeson object hashmap, so we test+    -- with two different orders to avoid a false success).+    let+      -- {"kid":"0","alg":"RS256"}+      s1 = "{\"protected\":\"eyJraWQiOiIwIiwiYWxnIjoiUlMyNTYifQ\",\"payload\":\"\",\"signature\":\"\"}"+      -- {"alg":"RS256","kid":"0"}+      s2 = "{\"signature\":\"\",\"protected\":\"eyJhbGciOiJSUzI1NiIsImtpZCI6IjAifQ\",\"payload\":\"\"}"++      check s = do+        let+          jws = eitherDecode s :: Either String (FlattenedJWS JWSHeader)+          s' = encode <$> jws+          jws' = s' >>= eitherDecode :: Either String (FlattenedJWS JWSHeader)+        jws `shouldSatisfy` is _Right+        toListOf (signatures . to rawProtectedHeader) <$> jws+          `shouldBe`+          toListOf (signatures . to rawProtectedHeader) <$> jws'++    check s1+    check s2
test/Types.hs view
@@ -35,8 +35,8 @@ base64OctetsSpec :: Spec base64OctetsSpec = describe "Base64Octets" $   it "can be read from JSON" $ do-    decode "[\"AxY8DCtDaGlsbGljb3RoZQ\"]" `shouldBe` Just [Base64Octets iv]-    decode "[\"9hH0vgRfYgPnAHOd8stkvw\"]" `shouldBe` Just [Base64Octets tag]+    eitherDecode "[\"AxY8DCtDaGlsbGljb3RoZQ\"]" `shouldBe` Right [Base64Octets iv]+    eitherDecode "[\"9hH0vgRfYgPnAHOd8stkvw\"]" `shouldBe` Right [Base64Octets tag]   where     iv = BS.pack [3, 22, 60, 12, 43, 67, 104, 105, 108, 108, 105, 99, 111, 116, 104, 101]     tag = BS.pack [246, 17, 244, 190, 4, 95, 98, 3, 231, 0, 115, 157, 242, 203, 100, 191]