packages feed

jose 0.3.41.2 → 0.4.0.0

raw patch · 23 files changed

+1099/−358 lines, 23 filesdep +QuickCheckdep +cryptonitedep +memorydep −crypto-pubkeydep −crypto-pubkey-typesdep −crypto-randomdep ~aesonPVP ok

version bump matches the API change (PVP)

Dependencies added: QuickCheck, cryptonite, memory, mtl, quickcheck-instances, tasty, tasty-hspec, tasty-quickcheck

Dependencies removed: crypto-pubkey, crypto-pubkey-types, crypto-random, cryptohash, ghc-prim, integer-gmp

Dependency ranges changed: aeson

API changes (from Hackage documentation)

- Crypto.JOSE.Classes: class Key k where type family KeyGenParam k type family KeyContent k
- Crypto.JOSE.Classes: fromKeyContent :: Key k => KeyContent k -> k
- Crypto.JOSE.Classes: gen :: (Key k, CPRG g) => KeyGenParam k -> g -> (k, g)
- Crypto.JOSE.Classes: public :: Key k => k -> Maybe k
- Crypto.JOSE.Classes: sign :: (Key k, CPRG g) => Alg -> k -> g -> ByteString -> (Either Error ByteString, g)
- Crypto.JOSE.Classes: verify :: Key k => Alg -> k -> ByteString -> ByteString -> Either Error Bool
- Crypto.JOSE.JWK: instance Crypto.JOSE.Classes.Key Crypto.JOSE.JWK.JWK
- Crypto.JOSE.Legacy: [_rsKeyParameters] :: JWK' -> RSKeyParameters
- Crypto.JOSE.Legacy: instance Crypto.JOSE.Classes.Key Crypto.JOSE.Legacy.JWK'
- Crypto.JOSE.Legacy: instance Crypto.JOSE.Classes.Key Crypto.JOSE.Legacy.RSKeyParameters
- Crypto.JOSE.Legacy: rsaKeyParameters :: Iso' RSKeyParameters RSAKeyParameters
- Crypto.JOSE.Types: Base64UrlString :: ByteString -> Base64UrlString
- Crypto.JOSE.Types: instance Data.Aeson.Types.Class.FromJSON Crypto.JOSE.Types.Base64UrlString
- Crypto.JOSE.Types: instance GHC.Classes.Eq Crypto.JOSE.Types.Base64UrlString
- Crypto.JOSE.Types: instance GHC.Show.Show Crypto.JOSE.Types.Base64UrlString
- Crypto.JOSE.Types: newtype Base64UrlString
+ Crypto.JOSE.Error: CryptoError :: CryptoError -> Error
+ Crypto.JOSE.JWE: JWE :: Maybe (Armour Text JWEHeader) -> Maybe JWEHeader -> Maybe Base64Octets -> Maybe Base64Octets -> Base64Octets -> Maybe Base64Octets -> [JWERecipient] -> JWE
+ Crypto.JOSE.JWE: JWEHeader :: Maybe AlgWithParams -> Maybe Enc -> Maybe String -> Maybe URI -> Maybe JWK -> Maybe String -> Maybe URI -> Maybe (NonEmpty Base64X509) -> Maybe Base64SHA1 -> Maybe Base64SHA256 -> Maybe String -> Maybe String -> Maybe CritParameters -> JWEHeader
+ Crypto.JOSE.JWE: [_jweAad] :: JWE -> Maybe Base64Octets
+ Crypto.JOSE.JWE: [_jweAlg] :: JWEHeader -> Maybe AlgWithParams
+ Crypto.JOSE.JWE: [_jweCiphertext] :: JWE -> Base64Octets
+ Crypto.JOSE.JWE: [_jweCrit] :: JWEHeader -> Maybe CritParameters
+ Crypto.JOSE.JWE: [_jweCty] :: JWEHeader -> Maybe String
+ Crypto.JOSE.JWE: [_jweEnc] :: JWEHeader -> Maybe Enc
+ Crypto.JOSE.JWE: [_jweIv] :: JWE -> Maybe Base64Octets
+ Crypto.JOSE.JWE: [_jweJku] :: JWEHeader -> Maybe URI
+ Crypto.JOSE.JWE: [_jweJwk] :: JWEHeader -> Maybe JWK
+ Crypto.JOSE.JWE: [_jweKid] :: JWEHeader -> Maybe String
+ Crypto.JOSE.JWE: [_jweProtected] :: JWE -> Maybe (Armour Text JWEHeader)
+ Crypto.JOSE.JWE: [_jweRecipients] :: JWE -> [JWERecipient]
+ Crypto.JOSE.JWE: [_jweTag] :: JWE -> Maybe Base64Octets
+ Crypto.JOSE.JWE: [_jweTyp] :: JWEHeader -> Maybe String
+ Crypto.JOSE.JWE: [_jweUnprotected] :: JWE -> Maybe JWEHeader
+ Crypto.JOSE.JWE: [_jweX5c] :: JWEHeader -> Maybe (NonEmpty Base64X509)
+ Crypto.JOSE.JWE: [_jweX5tS256] :: JWEHeader -> Maybe Base64SHA256
+ Crypto.JOSE.JWE: [_jweX5t] :: JWEHeader -> Maybe Base64SHA1
+ Crypto.JOSE.JWE: [_jweX5u] :: JWEHeader -> Maybe URI
+ Crypto.JOSE.JWE: [_jweZip] :: JWEHeader -> Maybe String
+ Crypto.JOSE.JWE: data JWE
+ Crypto.JOSE.JWE: data JWEHeader
+ Crypto.JOSE.JWE: instance Crypto.JOSE.Types.Armour.FromArmour Data.Text.Internal.Text Crypto.JOSE.Error.Error Crypto.JOSE.JWE.JWEHeader
+ Crypto.JOSE.JWE: instance Crypto.JOSE.Types.Armour.ToArmour Data.Text.Internal.Text Crypto.JOSE.JWE.JWEHeader
+ Crypto.JOSE.JWE: instance Data.Aeson.Types.Class.FromJSON Crypto.JOSE.JWE.CritParameters
+ Crypto.JOSE.JWE: instance Data.Aeson.Types.Class.FromJSON Crypto.JOSE.JWE.JWE
+ Crypto.JOSE.JWE: instance Data.Aeson.Types.Class.FromJSON Crypto.JOSE.JWE.JWEHeader
+ Crypto.JOSE.JWE: instance Data.Aeson.Types.Class.FromJSON Crypto.JOSE.JWE.JWERecipient
+ Crypto.JOSE.JWE: instance Data.Aeson.Types.Class.ToJSON Crypto.JOSE.JWE.CritParameters
+ Crypto.JOSE.JWE: instance Data.Aeson.Types.Class.ToJSON Crypto.JOSE.JWE.JWEHeader
+ Crypto.JOSE.JWE: instance GHC.Classes.Eq Crypto.JOSE.JWE.CritParameters
+ Crypto.JOSE.JWE: instance GHC.Classes.Eq Crypto.JOSE.JWE.JWEHeader
+ Crypto.JOSE.JWE: instance GHC.Show.Show Crypto.JOSE.JWE.CritParameters
+ Crypto.JOSE.JWE: instance GHC.Show.Show Crypto.JOSE.JWE.JWEHeader
+ Crypto.JOSE.JWK: P_256 :: Crv
+ Crypto.JOSE.JWK: P_384 :: Crv
+ Crypto.JOSE.JWK: P_521 :: Crv
+ Crypto.JOSE.JWK: asPublicKey :: AsPublicKey k => Prism' k k
+ Crypto.JOSE.JWK: class AsPublicKey k
+ Crypto.JOSE.JWK: data Crv
+ Crypto.JOSE.JWK: fromKeyMaterial :: KeyMaterial -> JWK
+ Crypto.JOSE.JWK: genJWK :: MonadRandom m => KeyMaterialGenParam -> m JWK
+ Crypto.JOSE.JWK: genKeyMaterial :: MonadRandom m => KeyMaterialGenParam -> m KeyMaterial
+ Crypto.JOSE.JWK: genRSA :: MonadRandom m => Int -> m RSAKeyParameters
+ Crypto.JOSE.JWK: instance Crypto.JOSE.JWA.JWK.AsPublicKey Crypto.JOSE.JWK.JWK
+ Crypto.JOSE.JWK: instance Test.QuickCheck.Arbitrary.Arbitrary Crypto.JOSE.JWK.JWK
+ Crypto.JOSE.JWK: rsaPublicKey :: RSAKeyParameters -> PublicKey
+ Crypto.JOSE.JWK: sign :: MonadRandom m => Alg -> KeyMaterial -> ByteString -> m (Either Error ByteString)
+ Crypto.JOSE.JWK: verify :: Alg -> KeyMaterial -> ByteString -> ByteString -> Either Error Bool
+ Crypto.JOSE.JWS: newJWS :: ByteString -> JWS
+ Crypto.JOSE.JWS: newJWSHeader :: Alg -> JWSHeader
+ Crypto.JOSE.Legacy: genJWK' :: MonadRandom m => Int -> m JWK'
+ Crypto.JOSE.Legacy: instance Crypto.JOSE.JWA.JWK.AsPublicKey Crypto.JOSE.Legacy.JWK'
+ Crypto.JOSE.Types: _Base64Integer :: Iso' Base64Integer Integer
+ Crypto.JOSE.Types: checkSize :: Int -> SizedBase64Integer -> Parser SizedBase64Integer
+ Crypto.JOSE.Types: genSizedBase64IntegerOf :: Int -> Gen SizedBase64Integer
+ Crypto.JOSE.Types: instance Test.QuickCheck.Arbitrary.Arbitrary Crypto.JOSE.Types.Base64Integer
+ Crypto.JOSE.Types: instance Test.QuickCheck.Arbitrary.Arbitrary Crypto.JOSE.Types.Base64Octets
+ Crypto.JOSE.Types: instance Test.QuickCheck.Arbitrary.Arbitrary Crypto.JOSE.Types.Base64SHA1
+ Crypto.JOSE.Types: instance Test.QuickCheck.Arbitrary.Arbitrary Crypto.JOSE.Types.Base64SHA256
+ Crypto.JOSE.Types: instance Test.QuickCheck.Arbitrary.Arbitrary Crypto.JOSE.Types.SizedBase64Integer
- Crypto.JOSE.JWS: signJWS :: CPRG g => g -> JWS -> JWSHeader -> JWK -> (Either Error JWS, g)
+ Crypto.JOSE.JWS: signJWS :: MonadRandom m => JWS -> JWSHeader -> JWK -> m (Either Error JWS)
- Crypto.JWT: createJWSJWT :: CPRG g => g -> JWK -> JWSHeader -> ClaimsSet -> (Either Error JWT, g)
+ Crypto.JWT: createJWSJWT :: MonadRandom m => JWK -> JWSHeader -> ClaimsSet -> m (Either Error JWT)

Files

README.md view
@@ -5,10 +5,8 @@ Token](https://tools.ietf.org/html/rfc7519).  Encryption (JWE) is not supported but signing is supported.  All key-types and algorithms are supported, but EC and symmetric key-generation is not yet implemented.--EC signing is currently vulnerable to timing attacks therefore its-use is **strongly discouraged**.  (EC validation is safe).+types and algorithms are supported, however, EC signing is currently+vulnerable to timing attacks therefore its use is **strongly+discouraged** (EC validation is safe).  Contributions are welcome.
jose.cabal view
@@ -1,5 +1,5 @@ name:                jose-version:             0.3.41.2+version:             0.4.0.0 synopsis:   Javascript Object Signing and Encryption and JSON Web Token library description:@@ -28,7 +28,7 @@   README.md author:              Fraser Tweedale maintainer:          frase@frase.id.au-copyright:           Copyright (C) 2013, 2014, 2015  Fraser Tweedale+copyright:           Copyright (C) 2013, 2014, 2015, 2016  Fraser Tweedale category:            Cryptography build-type:          Simple cabal-version:       >= 1.8@@ -36,9 +36,9 @@ library   exposed-modules:     Crypto.JOSE-    Crypto.JOSE.Classes     Crypto.JOSE.Compact     Crypto.JOSE.Error+    Crypto.JOSE.JWE     Crypto.JOSE.JWK     Crypto.JOSE.JWS     Crypto.JOSE.Legacy@@ -46,6 +46,7 @@     Crypto.JWT    other-modules:+    Crypto.JOSE.AESKW     Crypto.JOSE.JWA.JWK     Crypto.JOSE.JWA.JWS     Crypto.JOSE.JWA.JWE@@ -58,27 +59,26 @@    build-depends:     base == 4.*-    , ghc-prim-    , integer-gmp     , attoparsec     , base64-bytestring == 1.0.*     , bifunctors >= 4.0     , byteable == 0.1.*-    , crypto-pubkey >= 0.2.3-    , crypto-pubkey-types >= 0.3.2-    , crypto-random >= 0.0.7 && < 0.0.9-    , cryptohash == 0.11.*+    , cryptonite >= 0.7     , data-default-class     , lens >= 4.3+    , memory >= 0.7+    , mtl >= 2     , template-haskell >= 2.4     , safe >= 0.3     , semigroups >= 0.15-    , aeson >= 0.8.0.1 && < 0.9+    , aeson >= 0.8.0.1 && < 0.10     , unordered-containers == 0.2.*     , bytestring == 0.10.*     , text >= 1.1     , time >= 1.5     , network-uri >= 2.6+    , QuickCheck >= 2+    , quickcheck-instances     , x509 >= 1.4     , vector @@ -94,9 +94,11 @@   hs-source-dirs: src, test   main-is:        Test.hs   other-modules:+    AESKW     JWK     JWS     JWT+    Properties     Types    build-depends:@@ -105,12 +107,11 @@     , base64-bytestring     , bifunctors     , byteable-    , crypto-pubkey-    , crypto-pubkey-types-    , crypto-random-    , cryptohash+    , cryptonite     , data-default-class     , lens+    , memory+    , mtl     , template-haskell     , safe     , semigroups@@ -122,4 +123,10 @@     , network-uri     , vector     , x509++    , tasty+    , tasty-hspec+    , tasty-quickcheck     , hspec+    , QuickCheck+    , quickcheck-instances
src/Crypto/JOSE.hs view
@@ -19,14 +19,12 @@ -} module Crypto.JOSE   (-    module Crypto.JOSE.Classes-  , module Crypto.JOSE.Compact+    module Crypto.JOSE.Compact   , module Crypto.JOSE.Error   , module Crypto.JOSE.JWK   , module Crypto.JOSE.JWS   ) where -import Crypto.JOSE.Classes import Crypto.JOSE.Compact import Crypto.JOSE.Error import Crypto.JOSE.JWK
+ src/Crypto/JOSE/AESKW.hs view
@@ -0,0 +1,123 @@+-- Copyright (C) 2016  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.++{-# LANGUAGE ScopedTypeVariables #-}++{- |++Advanced Encryption Standard (AES) Key Wrap Algorithm;+<https://https://tools.ietf.org/html/rfc3394>.++-}+module Crypto.JOSE.AESKW+  (+    aesKeyWrap+  , aesKeyUnwrap+  ) where++import Control.Monad.State+import Crypto.Cipher.Types+import Data.Bits (xor)+import Data.ByteArray as BA hiding (replicate, xor)+import Data.Memory.Endian (BE(..), toBE)+import Data.Memory.PtrMethods (memCopy)+import Data.Word (Word64)+import Foreign.Ptr (Ptr, plusPtr)+import Foreign.Storable (peek, peekElemOff, poke, pokeElemOff)+import System.IO.Unsafe (unsafePerformIO)++iv :: Word64+iv = 0xA6A6A6A6A6A6A6A6++aesKeyWrapStep+  :: BlockCipher128 cipher+  => cipher+  -> Ptr Word64   -- ^ register+  -> (Int, Int)   -- ^ step (t) and offset (i)+  -> StateT Word64 IO ()+aesKeyWrapStep cipher p (t, i) = do+  a <- get+  r_i <- lift $ peekElemOff p i+  m :: ScrubbedBytes <-+    lift $ alloc 16 $ \p' -> poke p' a >> pokeElemOff p' 1 r_i+  let b = ecbEncrypt cipher m+  b_hi <- lift $ withByteArray b peek+  b_lo <- lift $ withByteArray b (`peekElemOff` 1)+  put (b_hi `xor` unBE (toBE (fromIntegral t)))+  lift $ pokeElemOff p i b_lo++-- | Wrap a secret.+--+-- Input size must be a multiple of 8 bytes, and at least 16 bytes.+-- Output size is input size plus 8 bytes.+--+aesKeyWrap+  :: (ByteArrayAccess m, ByteArray c, BlockCipher128 cipher)+  => cipher+  -> m+  -> c+aesKeyWrap cipher m = unsafePerformIO $ do+  let n = BA.length m+  c <- withByteArray m $ \p ->+    alloc (n + 8) $ \p' ->+      memCopy (p' `plusPtr` 8) p n+  withByteArray c $ \p -> do+    let coords = zip [1..] (join (replicate 6 [1 .. n `div` 8]))+    a <- execStateT (mapM_ (aesKeyWrapStep cipher p) coords) iv+    poke p a+  return c++aesKeyUnwrapStep+  :: BlockCipher128 cipher+  => cipher+  -> Ptr Word64   -- ^ register+  -> (Int, Int)   -- ^ step (t) and offset (i)+  -> StateT Word64 IO ()+aesKeyUnwrapStep cipher p (t, i) = do+  a <- get+  r_i <- lift $ peekElemOff p i+  let a_t = a `xor` unBE (toBE (fromIntegral t))+  m :: ScrubbedBytes <-+    lift $ alloc 16 $ \p' -> poke p' a_t >> pokeElemOff p' 1 r_i+  let b = ecbDecrypt cipher m+  b_hi <- lift $ withByteArray b peek+  b_lo <- lift $ withByteArray b (`peekElemOff` 1)+  put b_hi+  lift $ pokeElemOff p i b_lo++-- | Unwrap a secret.+--+-- Input size must be a multiple of 8 bytes, and at least 24 bytes.+-- Output size is input size minus 8 bytes.+--+-- Returns 'Nothing' if inherent integrity check fails.  Otherwise,+-- the chance that the key data is corrupt is 2 ^ -64.+--+aesKeyUnwrap+  :: (ByteArrayAccess c, ByteArray m, BlockCipher128 cipher)+  => cipher+  -> c+  -> Maybe m+aesKeyUnwrap cipher c = unsafePerformIO $ do+  let n = BA.length c - 8+  m <- withByteArray c $ \p' ->+    alloc n $ \p ->+      memCopy p (p' `plusPtr` 8) n+  a <- withByteArray c $ \p' -> peek p'+  a' <- withByteArray m $ \p -> do+    let n' = n `div` 8+    let tMax = n' * 6+    let coords = zip [tMax,tMax-1..1] (cycle [n'-1,n'-2..0])+    execStateT (mapM_ (aesKeyUnwrapStep cipher p) coords) a+  return $ if a' == iv then Just m else Nothing
− src/Crypto/JOSE/Classes.hs
@@ -1,61 +0,0 @@--- Copyright (C) 2013, 2014  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.--{-# LANGUAGE TypeFamilies #-}--{-|--Type classes for use with the JOSE modules.---}-module Crypto.JOSE.Classes-  (-    module Crypto.Random-  , Key(..)-  ) where--import qualified Data.ByteString as B--import Crypto.Random--import qualified Crypto.JOSE.JWA.JWS as JWA.JWS-import Crypto.JOSE.Error---- | A Key that can sign messages and validate signatures according--- to a given 'Alg'.------ Can fail with 'AlgorithmMismatch'----class Key k where-  type KeyGenParam k-  type KeyContent k-  gen :: CPRG g => KeyGenParam k -> g -> (k, g)-  fromKeyContent :: KeyContent k -> k-  sign-    :: CPRG g-    => JWA.JWS.Alg-    -> k-    -> g-    -> B.ByteString-    -> (Either Error B.ByteString, g)-  verify-    :: JWA.JWS.Alg-    -> k-    -> B.ByteString-    -> B.ByteString-    -> Either Error Bool--  -- | Remove secrets from a key-  ---  public :: k -> Maybe k
src/Crypto/JOSE/Error.hs view
@@ -23,6 +23,7 @@   ) where  import qualified Crypto.PubKey.RSA as RSA+import Crypto.Error (CryptoError)  -- | All the errors that can occur. --@@ -33,6 +34,7 @@   | KeySizeTooSmall           -- ^ Key size is too small   | OtherPrimesNotSupported   -- ^ RSA private key with >2 primes not supported   | RSAError RSA.Error        -- ^ RSA encryption, decryption or signing error+  | CryptoError CryptoError   -- ^ Various cryptonite library error cases   | CompactEncodeError String -- ^ Cannot produce compact representation of data   | CompactDecodeError String -- ^ Cannot decode compact representation   | JSONDecodeError String    -- ^ JSON (Aeson) decoding error
src/Crypto/JOSE/JWA/JWE.hs view
@@ -22,35 +22,144 @@ -} module Crypto.JOSE.JWA.JWE where +import Control.Applicative+import Data.Maybe (catMaybes)++import qualified Data.HashMap.Strict as M+ import Crypto.JOSE.JWK import Crypto.JOSE.TH import Crypto.JOSE.Types+import Crypto.JOSE.Types.Internal (objectPairs) +import Data.Aeson + -- -- JWA §4.  Cryptographic Algorithms for Encryption --+data AlgWithParams+  = RSA1_5+  | RSA_OAEP+  | RSA_OAEP_256+  | A128KW+  | A192KW+  | A256KW+  | Dir+  | ECDH_ES ECDHParameters+  | ECDH_ES_A128KW ECDHParameters+  | ECDH_ES_A192KW ECDHParameters+  | ECDH_ES_A256KW ECDHParameters+  | A128GCMKW AESGCMParameters+  | A192GCMKW AESGCMParameters+  | A256GCMKW AESGCMParameters+  | PBES2_HS256_A128KW PBES2Parameters+  | PBES2_HS384_A192KW PBES2Parameters+  | PBES2_HS512_A256KW PBES2Parameters+  deriving (Eq, Show) --- | JWA 4.1.  "alg" (Algorithms) Header Parameter Values for JWE+instance FromJSON AlgWithParams where+  parseJSON = withObject "Encryption alg and params" $ \o ->+    case M.lookup "alg" o of+      Nothing -> fail "\"alg\" parameter is required"+      Just "RSA1_5"             -> pure RSA1_5+      Just "RSA-OAEP"           -> pure RSA_OAEP+      Just "RSA-OAEP-256"       -> pure RSA_OAEP_256+      Just "A128KW"             -> pure A128KW+      Just "A192KW"             -> pure A192KW+      Just "A256KW"             -> pure A256KW+      Just "dir"                -> pure Dir+      Just "ECDH-ES"            -> ECDH_ES            <$> parseJSON (Object o)+      Just "ECDH-ES+A128KW"     -> ECDH_ES_A128KW     <$> parseJSON (Object o)+      Just "ECDH-ES+A192KW"     -> ECDH_ES_A192KW     <$> parseJSON (Object o)+      Just "ECDH-ES+A256KW"     -> ECDH_ES_A256KW     <$> parseJSON (Object o)+      Just "A128GCMKW"          -> A128GCMKW          <$> parseJSON (Object o)+      Just "A192GCMKW"          -> A192GCMKW          <$> parseJSON (Object o)+      Just "A256GCMKW"          -> A256GCMKW          <$> parseJSON (Object o)+      Just "PBES2-HS256+A128KW" -> PBES2_HS256_A128KW <$> parseJSON (Object o)+      Just "PBES2-HS384+A192KW" -> PBES2_HS384_A192KW <$> parseJSON (Object o)+      Just "PBES2-HS512+A256KW" -> PBES2_HS512_A256KW <$> parseJSON (Object o)+      _ -> fail $ "unrecognised value; expected: "+         ++ "[\"RSA1_5\",\"RSA-OAEP\",\"RSA-OAEP-256\",\"A128KW\",\"A192KW\",\"A256KW\",\"dir\",\"ECDH-ES\",\"ECDH-ES+A128KW\",\"ECDH-ES+A192KW\",\"ECDH-ES+A256KW\",\"A128GCMKW\",\"A192GCMKW\",\"A256GCMKW\",\"PBES2-HS256+A128KW\",\"PBES2-HS384+A128KW\",\"PBES2-HS512+A128KW\"]"++algObject :: Value -> Value+algObject s = object [("alg", s)]++algWithParamsObject :: ToJSON a => a -> Value -> Value+algWithParamsObject a s = object $ ("alg", s) : objectPairs (toJSON a)++instance ToJSON AlgWithParams where+  toJSON RSA1_5       = algObject "RSA1_5"+  toJSON RSA_OAEP     = algObject "RSA-OAEP"+  toJSON RSA_OAEP_256 = algObject "RSA-OAEP-256"+  toJSON A128KW       = algObject "A128KW"+  toJSON A192KW       = algObject "A192KW"+  toJSON A256KW       = algObject "A256KW"+  toJSON Dir          = algObject "Dir"+  toJSON (ECDH_ES params)             = algWithParamsObject params "ECDH-ES"+  toJSON (ECDH_ES_A128KW params)      = algWithParamsObject params "ECDH-ES+A128KW"+  toJSON (ECDH_ES_A192KW params)      = algWithParamsObject params "ECDH-ES+A192KW"+  toJSON (ECDH_ES_A256KW params)      = algWithParamsObject params "ECDH-ES+A256KW"+  toJSON (A128GCMKW params)           = algWithParamsObject params "A128GCMKW"+  toJSON (A192GCMKW params)           = algWithParamsObject params "A192GCMKW"+  toJSON (A256GCMKW params)           = algWithParamsObject params "A256GCMKW"+  toJSON (PBES2_HS256_A128KW params)  = algWithParamsObject params "PBES2-HS256+A128KW"+  toJSON (PBES2_HS384_A192KW params)  = algWithParamsObject params "PBES2-HS384+A192KW"+  toJSON (PBES2_HS512_A256KW params)  = algWithParamsObject params "PBES2-HS512+A256KW"+++-- | JWA §4.7.1.  Header Parameters Used for ECDH Key Agreement ---data JWEAlgHeaderParameters =-  -- JWA §4.7.1.  Header Parameters Used for ECDH Key Agreement-  ECDHParameters {-    epk :: JWK,   -- Ephemeral Public Key ; a JWK PUBLIC key-    apu :: Maybe Base64UrlString, -- Agreement PartyUInfo-    apv :: Maybe Base64UrlString  -- Agreement PartyVInfo-    }-  -- JWA §4.8.1.  Header Parameters Used for AES GCM Key Encryption-  | AESGCMParameters {-    iv :: Base64Octets, -- Initialization Vector-    tag :: Base64Octets -- Authentication Tag-    }-  -- JWA §4.9.1.  Header Parameters Used for PBES2 Key Encryption-  | PBES2Parameters {-    p2s :: Base64Octets,  -- PBKDF2 salt value-    p2c :: Int                  -- PBKDF2 iteration count ; POSITIVE integer-    }-  deriving (Show)+data ECDHParameters = ECDHParameters+  { _epk :: JWK                 -- ^ Ephemeral Public Key ; a JWK PUBLIC key+  , _apu :: Maybe Base64Octets  -- ^ Agreement PartyUInfo+  , _apv :: Maybe Base64Octets  -- ^ Agreement PartyVInfo+  } deriving (Eq, Show)++instance FromJSON ECDHParameters where+  parseJSON = withObject "ECDH Parameters" $ \o -> ECDHParameters+    <$> o .: "epk"+    <*> o .:? "apu"+    <*> o .:? "apv"++instance ToJSON ECDHParameters where+  toJSON (ECDHParameters epk apu apv) = object $ catMaybes+    [ Just ("epk" .= epk)+    , fmap ("apu" .=) apu+    , fmap ("apu" .=) apv+    ]+++-- | JWA §4.8.1.  Header Parameters Used for AES GCM Key Encryption+--+data AESGCMParameters = AESGCMParameters+  { _iv :: Base64Octets  -- ^ Initialization Vector  (must be 96 bits?)+  , _tag :: Base64Octets -- ^ Authentication Tag (must be 128 bits?)+  } deriving (Eq, Show)++instance FromJSON AESGCMParameters where+  parseJSON = withObject "AES-GCM Parameters" $ \o -> AESGCMParameters+    <$> o .: "iv"+    <*> o .: "tag"++instance ToJSON AESGCMParameters where+  toJSON (AESGCMParameters iv tag) = object ["iv" .= iv, "tag" .= tag]+++-- | JWA §4.9.1.  Header Parameters Used for PBES2 Key Encryption+--+data PBES2Parameters =  PBES2Parameters+  { _p2s :: Base64Octets   -- ^ PBKDF2 salt input+  , _p2c :: Int            -- ^ PBKDF2 iteration count ; POSITIVE integer+  } deriving (Eq, Show)++instance FromJSON PBES2Parameters where+  parseJSON = withObject "AES-GCM Parameters" $ \o -> PBES2Parameters+    <$> o .: "p2s"  -- TODO salt input value must be >= 8 octets+    <*> o .: "p2c"++instance ToJSON PBES2Parameters where+  toJSON (PBES2Parameters p2s p2c) = object ["p2s" .= p2s, "p2c" .= p2c]   -- | JWA §4.2.  "enc" (Encryption Method) Header Parameters Values for JWE
src/Crypto/JOSE/JWA/JWE/Alg.hs view
@@ -47,6 +47,6 @@   , "A192GCMKW"           -- AES in Galois/Counter Mode             Optional   , "A256GCMKW"           -- AES in Galois/Counter Mode             Optional   , "PBES2-HS256+A128KW"  -- PBES2 with HMAC SHA and AES Key Wrap   Optional-  , "PBES2-HS384+A128KW"  -- PBES2 with HMAC SHA and AES Key Wrap   Optional-  , "PBES2-HS512+A128KW"  -- PBES2 with HMAC SHA and AES Key Wrap   Optional+  , "PBES2-HS384+A192KW"  -- PBES2 with HMAC SHA and AES Key Wrap   Optional+  , "PBES2-HS512+A256KW"  -- PBES2 with HMAC SHA and AES Key Wrap   Optional   ])
src/Crypto/JOSE/JWA/JWK.hs view
@@ -1,4 +1,4 @@--- Copyright (C) 2013, 2014  Fraser Tweedale+-- Copyright (C) 2013, 2014, 2015, 2016  Fraser Tweedale -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License.@@ -12,8 +12,10 @@ -- See the License for the specific language governing permissions and -- limitations under the License. +{-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeFamilies #-}@@ -24,12 +26,16 @@  -} module Crypto.JOSE.JWA.JWK (+  -- * Type classes+    AsPublicKey(..)+   -- * \"kty\" (Key Type) Parameter Values-    EC(..)+  , EC(..)   , RSA(..)   , Oct(..)    -- * Parameters for Elliptic Curve Keys+  , Crv(..)   , ECKeyParameters(..)    -- * Parameters for RSA Keys@@ -41,12 +47,19 @@   , rsaKty   , rsaN   , rsaPrivateKeyParameters+  , rsaPublicKey+  , genRSA    -- * Parameters for Symmetric Keys   , OctKeyParameters(..)    , KeyMaterialGenParam(..)   , KeyMaterial(..)+  , genKeyMaterial+  , sign+  , verify++  , module Crypto.Random   ) where  import Control.Applicative@@ -55,25 +68,27 @@  import Control.Lens hiding ((.=)) import Crypto.Hash-import Crypto.PubKey.HashDescr+import Crypto.MAC.HMAC import qualified Crypto.PubKey.ECC.ECDSA as ECDSA+import qualified Crypto.PubKey.ECC.Generate as ECC import qualified Crypto.PubKey.RSA as RSA import qualified Crypto.PubKey.RSA.PKCS15 as PKCS15 import qualified Crypto.PubKey.RSA.PSS as PSS-import qualified Crypto.Types.PubKey.ECC as ECC+import qualified Crypto.PubKey.ECC.Types as ECC import Crypto.Random import Data.Aeson-import Data.Byteable+import qualified Data.ByteArray as BA import qualified Data.ByteString as B import qualified Data.HashMap.Strict as M import Data.List.NonEmpty+import Test.QuickCheck  import Crypto.JOSE.Error-import Crypto.JOSE.Classes import qualified Crypto.JOSE.JWA.JWS as JWA.JWS import qualified Crypto.JOSE.TH import qualified Crypto.JOSE.Types as Types import qualified Crypto.JOSE.Types.Internal as Types+import Crypto.JOSE.Types.Orphans ()   -- | Elliptic Curve key type (Recommeded+)@@ -88,7 +103,10 @@ -- $(Crypto.JOSE.TH.deriveJOSEType "Crv" ["P-256", "P-384", "P-521"]) +instance Arbitrary Crv where+  arbitrary = oneof $ pure <$> [P_256, P_384, P_521] + -- | \"oth\" (Other Primes Info) Parameter -- data RSAPrivateKeyOthElem = RSAPrivateKeyOthElem {@@ -107,7 +125,10 @@ instance ToJSON RSAPrivateKeyOthElem where   toJSON (RSAPrivateKeyOthElem r d t) = object ["r" .= r, "d" .= d, "t" .= t] +instance Arbitrary RSAPrivateKeyOthElem where+  arbitrary = RSAPrivateKeyOthElem <$> arbitrary <*> arbitrary <*> arbitrary + -- | Optional parameters for RSA private keys -- data RSAPrivateKeyOptionalParameters = RSAPrivateKeyOptionalParameters {@@ -138,7 +159,16 @@     , "qi" .= rsaQi     ] ++ maybe [] ((:[]) . ("oth" .=)) rsaOth +instance Arbitrary RSAPrivateKeyOptionalParameters where+  arbitrary = RSAPrivateKeyOptionalParameters+    <$> arbitrary+    <*> arbitrary+    <*> arbitrary+    <*> arbitrary+    <*> arbitrary+    <*> arbitrary + -- | RSA private key parameters -- data RSAPrivateKeyParameters = RSAPrivateKeyParameters@@ -159,7 +189,10 @@   toJSON RSAPrivateKeyParameters {..} = object $     ("d" .= rsaD) : maybe [] (Types.objectPairs . toJSON) rsaOptionalParameters +instance Arbitrary RSAPrivateKeyParameters where+  arbitrary = RSAPrivateKeyParameters <$> arbitrary <*> arbitrary + -- | Parameters for Elliptic Curve Keys -- data ECKeyParameters = ECKeyParameters@@ -173,71 +206,69 @@   deriving (Eq, Show)  instance FromJSON ECKeyParameters where-  parseJSON = withObject "EC" $ \o -> ECKeyParameters-    <$> o .: "kty"-    <*> o .: "crv"-    <*> o .: "x"-    <*> o .: "y"-    <*> o .:? "d"+  parseJSON = withObject "EC" $ \o -> do+    crv <- o .: "crv"+    ECKeyParameters+      <$> o .: "kty"+      <*> pure crv+      <*> (o .: "x" >>= Types.checkSize (ecCoordBytes crv))+      <*> (o .: "y" >>= Types.checkSize (ecCoordBytes crv))+      <*> (o .:? "d" >>= \case+        Nothing -> return Nothing+        Just v -> Just <$> Types.checkSize (ecDBytes crv) v)  instance ToJSON ECKeyParameters where   toJSON (ECKeyParameters {..}) = object $-    [ "crv" .= ecCrv+    [ "kty" .= ecKty+    , "crv" .= ecCrv     , "x" .= ecX     , "y" .= ecY     ] ++ fmap ("d" .=) (maybeToList ecD) -instance Key ECKeyParameters where-  type KeyGenParam ECKeyParameters = Crv-  type KeyContent ECKeyParameters = ECKeyParameters-  gen = undefined  -- TODO implement-  fromKeyContent = id-  sign JWA.JWS.ES256 k@(ECKeyParameters { ecCrv = P_256 }) =-    signEC hashDescrSHA256 k-  sign JWA.JWS.ES384 k@(ECKeyParameters { ecCrv = P_384 }) =-    signEC hashDescrSHA384 k-  sign JWA.JWS.ES512 k@(ECKeyParameters { ecCrv = P_521 }) =-    signEC hashDescrSHA512 k-  sign h _ = \g _ ->-    (Left $ AlgorithmMismatch  $ show h ++ "cannot be used with EC key", g)-  verify JWA.JWS.ES256 = verifyEC hashDescrSHA256-  verify JWA.JWS.ES384 = verifyEC hashDescrSHA384-  verify JWA.JWS.ES512 = verifyEC hashDescrSHA512-  verify h = \_ _ _ ->-    Left $ AlgorithmMismatch  $ show h ++ "cannot be used with EC key"-  public k = Just k { ecD = Nothing }+instance Arbitrary ECKeyParameters where+  arbitrary = do+    crv <- arbitrary+    let w = ecCoordBytes crv+    ECKeyParameters EC crv+      <$> Types.genSizedBase64IntegerOf w+      <*> Types.genSizedBase64IntegerOf w+      <*> oneof+        [ pure Nothing+        , Just <$> Types.genSizedBase64IntegerOf (ecDBytes crv)+        ]  signEC-  :: CPRG g-  => HashDescr+  :: (BA.ByteArrayAccess msg, HashAlgorithm h, MonadRandom m)+  => h   -> ECKeyParameters-  -> g-  -> B.ByteString-  -> (Either Error B.ByteString, g)-signEC h k@(ECKeyParameters {..}) g m = case ecD of-  Just ecD' -> first (Right . sigToBS) sig where-    sig = ECDSA.sign g privateKey (hashFunction h) m+  -> msg+  -> m (Either Error B.ByteString)+signEC h (ECKeyParameters {..}) m = case ecD of+  Just ecD' -> Right . sigToBS <$> sig where+    w = ecCoordBytes ecCrv+    sig = ECDSA.sign privateKey h m     sigToBS (ECDSA.Signature r s) =-      Types.integerToBS r `B.append` Types.integerToBS s-    privateKey = ECDSA.PrivateKey (curve k) (d ecD')+      Types.sizedIntegerToBS w r `B.append` Types.sizedIntegerToBS w s+    privateKey = ECDSA.PrivateKey (curve ecCrv) (d ecD')     d (Types.SizedBase64Integer _ n) = n-  Nothing -> (Left $ KeyMismatch "not an EC private key", g)+  Nothing -> return (Left $ KeyMismatch "not an EC private key")  verifyEC-  :: HashDescr+  :: (BA.ByteArrayAccess msg, HashAlgorithm h)+  => h   -> ECKeyParameters-  -> B.ByteString+  -> msg   -> B.ByteString   -> Either Error Bool-verifyEC h k m s = Right $ ECDSA.verify (hashFunction h) pubkey sig m+verifyEC h k m s = Right $ ECDSA.verify h pubkey sig m   where-  pubkey = ECDSA.PublicKey (curve k) (point k)+  pubkey = ECDSA.PublicKey (curve $ ecCrv k) (point k)   sig = uncurry ECDSA.Signature     $ bimap Types.bsToInteger Types.bsToInteger     $ B.splitAt (B.length s `div` 2) s -curve :: ECKeyParameters -> ECC.Curve-curve ECKeyParameters {..} = ECC.getCurveByName (curveName ecCrv) where+curve :: Crv -> ECC.Curve+curve = ECC.getCurveByName . curveName where   curveName P_256 = ECC.SEC_p256r1   curveName P_384 = ECC.SEC_p384r1   curveName P_521 = ECC.SEC_p521r1@@ -246,7 +277,16 @@ point ECKeyParameters {..} = ECC.Point (integer ecX) (integer ecY) where   integer (Types.SizedBase64Integer _ n) = n +ecCoordBytes :: Integral a => Crv -> a+ecCoordBytes P_256 = 32+ecCoordBytes P_384 = 48+ecCoordBytes P_521 = 66 +ecDBytes :: Integral a => Crv -> a+ecDBytes crv = ceiling (logBase 2 (fromIntegral order) / 8 :: Double) where+  order = ECC.ecc_n $ ECC.common_curve $ curve crv++ -- | Parameters for RSA Keys -- data RSAKeyParameters = RSAKeyParameters@@ -275,85 +315,61 @@     : ("e" .= _rsaE)     : maybe [] (Types.objectPairs . toJSON) _rsaPrivateKeyParameters -instance Key RSAKeyParameters where-  type KeyGenParam RSAKeyParameters = Int-  type KeyContent RSAKeyParameters =-    ( Types.SizedBase64Integer-    , Types.Base64Integer-    , Maybe RSAPrivateKeyParameters-    )-  gen size g =-    let-      i = Types.Base64Integer-      ((RSA.PublicKey s n e, RSA.PrivateKey _ d p q dp dq qi), g') =-        RSA.generate g size 65537-    in-      ( fromKeyContent-        ( Types.SizedBase64Integer s n-        , i e-        , Just (RSAPrivateKeyParameters (i d)-          (Just (RSAPrivateKeyOptionalParameters-            (i p) (i q) (i dp) (i dq) (i qi) Nothing))))-      , g')-  fromKeyContent (n, e, p) = RSAKeyParameters RSA n e p-  public = Just . set rsaPrivateKeyParameters Nothing-  sign JWA.JWS.RS256 = signPKCS15 hashDescrSHA256-  sign JWA.JWS.RS384 = signPKCS15 hashDescrSHA384-  sign JWA.JWS.RS512 = signPKCS15 hashDescrSHA512-  sign JWA.JWS.PS256 = signPSS hashDescrSHA256-  sign JWA.JWS.PS384 = signPSS hashDescrSHA384-  sign JWA.JWS.PS512 = signPSS hashDescrSHA512-  sign h = \_ g -> const-    (Left $ AlgorithmMismatch  $ show h ++ "cannot be used with RSA key", g)-  verify JWA.JWS.RS256 = verifyPKCS15 hashDescrSHA256-  verify JWA.JWS.RS384 = verifyPKCS15 hashDescrSHA384-  verify JWA.JWS.RS512 = verifyPKCS15 hashDescrSHA512-  verify JWA.JWS.PS256 = verifyPSS hashDescrSHA256-  verify JWA.JWS.PS384 = verifyPSS hashDescrSHA384-  verify JWA.JWS.PS512 = verifyPSS hashDescrSHA512-  verify h = \_ _ _ ->-    Left $ AlgorithmMismatch  $ show h ++ "cannot be used with RSA key"+instance Arbitrary RSAKeyParameters where+  arbitrary = RSAKeyParameters RSA+    <$> arbitrary+    <*> arbitrary+    <*> arbitrary +genRSA :: MonadRandom m => Int -> m RSAKeyParameters+genRSA size = do+  (RSA.PublicKey s n e, RSA.PrivateKey _ d p q dp dq qi) <- RSA.generate size 65537+  let i = Types.Base64Integer+  return $ RSAKeyParameters RSA+    ( Types.SizedBase64Integer s n )+    ( i e )+    ( Just (RSAPrivateKeyParameters (i d)+      (Just (RSAPrivateKeyOptionalParameters+        (i p) (i q) (i dp) (i dq) (i qi) Nothing))) )+ signPKCS15-  :: CPRG g-  => HashDescr+  :: (PKCS15.HashAlgorithmASN1 h, MonadRandom m)+  => h   -> RSAKeyParameters-  -> g   -> B.ByteString-  -> (Either Error B.ByteString, g)-signPKCS15 h k g m = case rsaPrivateKey k of-  Left e -> (Left e, g)-  Right k' -> first (first RSAError) $-    PKCS15.signSafer g h k' m+  -> m (Either Error B.ByteString)+signPKCS15 h k m = case rsaPrivateKey k of+  Left e -> return (Left e)+  Right k' -> first RSAError <$> PKCS15.signSafer (Just h) k' m  verifyPKCS15-  :: HashDescr+  :: PKCS15.HashAlgorithmASN1 h+  => h   -> RSAKeyParameters   -> B.ByteString   -> B.ByteString   -> Either Error Bool-verifyPKCS15 h k m = Right . PKCS15.verify h (rsaPublicKey k) m+verifyPKCS15 h k m = Right . PKCS15.verify (Just h) (rsaPublicKey k) m  signPSS-  :: CPRG g-  => HashDescr+  :: (HashAlgorithm h, MonadRandom m)+  => h   -> RSAKeyParameters-  -> g   -> B.ByteString-  -> (Either Error B.ByteString, g)-signPSS h k g m = case rsaPrivateKey k of-  Left e -> (Left e, g)-  Right k' -> first (first RSAError) $-   PSS.signSafer g (PSS.defaultPSSParams (hashFunction h)) k' m+  -> m (Either Error B.ByteString)+signPSS h k m = case rsaPrivateKey k of+  Left e -> return (Left e)+  Right k' -> first RSAError <$> PSS.signSafer (PSS.defaultPSSParams h) k' m  verifyPSS-  :: HashDescr+  :: (HashAlgorithm h)+  => h   -> RSAKeyParameters   -> B.ByteString   -> B.ByteString   -> Either Error Bool verifyPSS h k m = Right .-  PSS.verify (PSS.defaultPSSParams (hashFunction h)) (rsaPublicKey k) m+  PSS.verify (PSS.defaultPSSParams h) (rsaPublicKey k) m  rsaPrivateKey :: RSAKeyParameters -> Either Error RSA.PrivateKey rsaPrivateKey (RSAKeyParameters _@@ -392,27 +408,19 @@ instance ToJSON OctKeyParameters where   toJSON OctKeyParameters {..} = object ["kty" .= octKty, "k" .= octK] -instance Key OctKeyParameters where-  type KeyGenParam OctKeyParameters = Int-  type KeyContent OctKeyParameters = Types.Base64Octets-  gen = undefined  -- TODO implement-  fromKeyContent = OctKeyParameters Oct-  public = const Nothing-  sign JWA.JWS.HS256 k g = first Right . (,g) . signOct SHA256 k-  sign JWA.JWS.HS384 k g = first Right . (,g) . signOct SHA384 k-  sign JWA.JWS.HS512 k g = first Right . (,g) . signOct SHA512 k-  sign h _ g = const-    (Left $ AlgorithmMismatch $ show h ++ "cannot be used with Oct key", g)-  verify h k m s = fst (sign h k (undefined :: SystemRNG) m) >>= Right . (constEqBytes s)+instance Arbitrary OctKeyParameters where+  arbitrary = OctKeyParameters Oct <$> arbitrary  signOct-  :: HashAlgorithm a-  => a+  :: forall h. HashAlgorithm h+  => h   -> OctKeyParameters   -> B.ByteString-  -> B.ByteString-signOct a (OctKeyParameters _ (Types.Base64Octets k)) m-  = toBytes $ hmacAlg a k m+  -> Either Error B.ByteString+signOct h (OctKeyParameters _ (Types.Base64Octets k)) m =+  if B.length k < hashDigestSize h+  then Left KeySizeTooSmall+  else Right $ B.pack $ BA.unpack (hmac k m :: HMAC h)   -- | Key material sum type.@@ -423,6 +431,11 @@   | OctKeyMaterial OctKeyParameters   deriving (Eq, Show) +showKeyType :: KeyMaterial -> String+showKeyType (ECKeyMaterial (ECKeyParameters { ecCrv = crv })) = "ECDSA (" ++ show crv ++ ")"+showKeyType (RSAKeyMaterial _) = "RSA"+showKeyType (OctKeyMaterial _) = "symmetric"+ instance FromJSON KeyMaterial where   parseJSON = withObject "KeyMaterial" $ \o ->     ECKeyMaterial      <$> parseJSON (Object o)@@ -441,21 +454,88 @@   | RSAGenParam Int   | OctGenParam Int -instance Key KeyMaterial where-  type KeyGenParam KeyMaterial = KeyMaterialGenParam-  type KeyContent KeyMaterial = KeyMaterial-  gen (ECGenParam a) = first ECKeyMaterial . gen a-  gen (RSAGenParam a) = first RSAKeyMaterial . gen a-  gen (OctGenParam a) = first OctKeyMaterial . gen a-  fromKeyContent = id-  public (ECKeyMaterial k) = ECKeyMaterial <$> public k-  public (RSAKeyMaterial k) = RSAKeyMaterial <$> public k-  public (OctKeyMaterial k) = OctKeyMaterial <$> public k-  sign JWA.JWS.None _ = \g _ -> (Right "", g)-  sign h (ECKeyMaterial k)  = sign h k-  sign h (RSAKeyMaterial k) = sign h k-  sign h (OctKeyMaterial k) = sign h k-  verify JWA.JWS.None _ = \_ s -> Right $ s == ""-  verify h (ECKeyMaterial k)  = verify h k-  verify h (RSAKeyMaterial k) = verify h k-  verify h (OctKeyMaterial k) = verify h k+genKeyMaterial :: MonadRandom m => KeyMaterialGenParam -> m KeyMaterial+genKeyMaterial (ECGenParam crv) = do+  let+    xyValue = Types.SizedBase64Integer (ecCoordBytes crv)+    dValue = Types.SizedBase64Integer (ecDBytes crv)+  (ECDSA.PublicKey _ p, ECDSA.PrivateKey _ d) <- ECC.generate (curve crv)+  case p of+    ECC.Point x y -> return $ ECKeyMaterial $+      ECKeyParameters EC crv (xyValue x) (xyValue y) (Just (dValue d))+    ECC.PointO -> genKeyMaterial (ECGenParam crv)  -- JWK cannot represent point at infinity; recurse+genKeyMaterial (RSAGenParam size) = RSAKeyMaterial <$> genRSA size+genKeyMaterial (OctGenParam n) =+  OctKeyMaterial . OctKeyParameters Oct . Types.Base64Octets <$> getRandomBytes n++sign+  :: MonadRandom m+  => JWA.JWS.Alg+  -> KeyMaterial+  -> B.ByteString+  -> m (Either Error B.ByteString)+sign JWA.JWS.None _ = \_ -> return $ Right ""+sign JWA.JWS.ES256 (ECKeyMaterial k@(ECKeyParameters { ecCrv = P_256 })) = signEC SHA256 k+sign JWA.JWS.ES384 (ECKeyMaterial k@(ECKeyParameters { ecCrv = P_384 })) = signEC SHA384 k+sign JWA.JWS.ES512 (ECKeyMaterial k@(ECKeyParameters { ecCrv = P_521 })) = signEC SHA512 k+sign JWA.JWS.RS256 (RSAKeyMaterial k) = signPKCS15 SHA256 k+sign JWA.JWS.RS384 (RSAKeyMaterial k) = signPKCS15 SHA384 k+sign JWA.JWS.RS512 (RSAKeyMaterial k) = signPKCS15 SHA512 k+sign JWA.JWS.PS256 (RSAKeyMaterial k) = signPSS SHA256 k+sign JWA.JWS.PS384 (RSAKeyMaterial k) = signPSS SHA384 k+sign JWA.JWS.PS512 (RSAKeyMaterial k) = signPSS SHA512 k+sign JWA.JWS.HS256 (OctKeyMaterial k) = return . signOct SHA256 k+sign JWA.JWS.HS384 (OctKeyMaterial k) = return . signOct SHA384 k+sign JWA.JWS.HS512 (OctKeyMaterial k) = return . signOct SHA512 k+sign h k = \_ -> return $ Left $ AlgorithmMismatch+  $ show h ++ "cannot be used with " ++ showKeyType k ++ " key"++verify+  :: JWA.JWS.Alg+  -> KeyMaterial+  -> B.ByteString+  -> B.ByteString+  -> Either Error Bool+verify JWA.JWS.None _ = \_ s -> Right $ s == ""+verify JWA.JWS.ES256 (ECKeyMaterial k) = verifyEC SHA256 k+verify JWA.JWS.ES384 (ECKeyMaterial k) = verifyEC SHA384 k+verify JWA.JWS.ES512 (ECKeyMaterial k) = verifyEC SHA512 k+verify JWA.JWS.RS256 (RSAKeyMaterial k) = verifyPKCS15 SHA256 k+verify JWA.JWS.RS384 (RSAKeyMaterial k) = verifyPKCS15 SHA384 k+verify JWA.JWS.RS512 (RSAKeyMaterial k) = verifyPKCS15 SHA512 k+verify JWA.JWS.PS256 (RSAKeyMaterial k) = verifyPSS SHA256 k+verify JWA.JWS.PS384 (RSAKeyMaterial k) = verifyPSS SHA384 k+verify JWA.JWS.PS512 (RSAKeyMaterial k) = verifyPSS SHA512 k+verify JWA.JWS.HS256 (OctKeyMaterial k) = \m s -> BA.constEq s <$> signOct SHA256 k m+verify JWA.JWS.HS384 (OctKeyMaterial k) = \m s -> BA.constEq s <$> signOct SHA384 k m+verify JWA.JWS.HS512 (OctKeyMaterial k) = \m s -> BA.constEq s <$> signOct SHA512 k m+verify h k = \_ -> return $ Left $ AlgorithmMismatch+  $ show h ++ "cannot be used with " ++ showKeyType k ++ " key"++instance Arbitrary KeyMaterial where+  arbitrary = oneof+    [ ECKeyMaterial <$> arbitrary+    , RSAKeyMaterial <$> arbitrary+    , OctKeyMaterial <$> arbitrary+    ]+++class AsPublicKey k where+  asPublicKey :: Prism' k k+++instance AsPublicKey OctKeyParameters where+  asPublicKey = prism' id (const Nothing)++instance AsPublicKey RSAKeyParameters where+  asPublicKey = prism' id (Just . set rsaPrivateKeyParameters Nothing)++instance AsPublicKey ECKeyParameters where+  asPublicKey = prism' id (\k -> Just k { ecD = Nothing })++instance AsPublicKey KeyMaterial where+  asPublicKey = prism' id (\x -> case x of+    OctKeyMaterial k  -> OctKeyMaterial  <$> k ^? asPublicKey+    RSAKeyMaterial k  -> RSAKeyMaterial  <$> k ^? asPublicKey+    ECKeyMaterial k   -> ECKeyMaterial   <$> k ^? asPublicKey+    )
+ src/Crypto/JOSE/JWE.hs view
@@ -0,0 +1,297 @@+-- Copyright (C) 2015, 2016  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.++{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TupleSections #-}++module Crypto.JOSE.JWE+  (+    JWEHeader(..)++  , JWE(..)+  ) where++import Prelude hiding (mapM)+import Control.Applicative+import Data.Bifunctor (first, bimap)+import Data.Maybe (catMaybes)+import Data.Traversable (mapM)++import Data.Aeson+import Data.Aeson.Types+import qualified Data.ByteArray as BA+import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy as L+import qualified Data.ByteString.Base64.URL as B64U+import qualified Data.ByteString.Base64.URL.Lazy as B64UL+import qualified Data.Text as T+import qualified Data.Text.Encoding as T+import Data.List.NonEmpty (NonEmpty(..), toList)++import Crypto.Cipher.AES+import Crypto.Cipher.Types+import Crypto.Data.Padding+import Crypto.Error+import Crypto.Hash+import Crypto.MAC.HMAC+import Crypto.PubKey.MaskGenFunction+import qualified Crypto.PubKey.RSA.OAEP as OAEP++import Crypto.JOSE.AESKW+import Crypto.JOSE.Error+import Crypto.JOSE.JWA.JWE+import Crypto.JOSE.JWK+import qualified Crypto.JOSE.Types as Types+import qualified Crypto.JOSE.Types.Internal as Types+import Crypto.JOSE.Types.Armour+++critInvalidNames :: [T.Text]+critInvalidNames =+  [ "alg" , "enc" , "zip" , "jku" , "jwk" , "kid"+  , "x5u" , "x5c" , "x5t" , "x5t#S256" , "typ" , "cty" , "crit" ]++newtype CritParameters = CritParameters (NonEmpty (T.Text, Value))+  deriving (Eq, Show)++critObjectParser :: Object -> T.Text -> Parser (T.Text, Value)+critObjectParser o s+  | s `elem` critInvalidNames = fail "crit key is reserved"+  | otherwise                 = (\v -> (s, v)) <$> o .: s++parseCrit :: Object -> NonEmpty T.Text -> Parser CritParameters+parseCrit o = fmap CritParameters . mapM (critObjectParser o)+  -- TODO fail on duplicate strings++instance FromJSON CritParameters where+  parseJSON = withObject "crit" $ \o -> o .: "crit" >>= parseCrit o++instance ToJSON CritParameters where+  toJSON (CritParameters m) = object $ ("crit", toJSON $ fmap fst m) : toList m+++data JWEHeader = JWEHeader+  { _jweAlg :: Maybe AlgWithParams+  , _jweEnc :: Maybe Enc+  , _jweZip :: Maybe String  -- protected header only  "DEF" (DEFLATE) defined+  , _jweJku :: Maybe Types.URI+  , _jweJwk :: Maybe JWK+  , _jweKid :: Maybe String+  , _jweX5u :: Maybe Types.URI+  , _jweX5c :: Maybe (NonEmpty Types.Base64X509)+  , _jweX5t :: Maybe Types.Base64SHA1+  , _jweX5tS256 :: Maybe Types.Base64SHA256+  , _jweTyp :: Maybe String  -- ^ Content Type (of object)+  , _jweCty :: Maybe String  -- ^ Content Type (of payload)+  , _jweCrit :: Maybe CritParameters+  }+  deriving (Eq, Show)++newJWEHeader :: AlgWithParams -> JWEHeader+newJWEHeader alg = JWEHeader (Just alg) z z z z z z z z z z z z where z = Nothing+++instance FromJSON JWEHeader where+  parseJSON = withObject "JWE" $ \o -> JWEHeader+    <$> parseJSON (Object o)+    <*> o .: "enc"+    <*> o .:? "zip"+    <*> o .:? "jku"+    <*> o .:? "jwk"+    <*> o .:? "kid"+    <*> o .:? "x5u"+    <*> o .:? "x5c"+    <*> o .:? "x5t"+    <*> o .:? "x5t#S256"+    <*> o .:? "typ"+    <*> o .:? "cty"+    <*> (o .:? "crit" >>= mapM (parseCrit o))  -- TODO++instance ToJSON JWEHeader where+  toJSON (JWEHeader alg enc _zip jku jwk kid x5u x5c x5t x5tS256 typ cty crit) =+    object $ catMaybes+      [ fmap ("enc" .=) enc+      , fmap ("zip" .=) _zip+      , fmap ("jku" .=) jku+      , fmap ("jwk" .=) jwk+      , fmap ("kid" .=) kid+      , fmap ("x5u" .=) x5u+      , fmap ("x5c" .=) x5c+      , fmap ("x5t" .=) x5t+      , fmap ("x5t#S256" .=) x5tS256+      , fmap ("typ" .=) typ+      , fmap ("cty" .=) cty+      ]+      ++ Types.objectPairs (toJSON crit)+      ++ maybe [] (Types.objectPairs . toJSON) alg++instance FromArmour T.Text Error JWEHeader where+  parseArmour s =+        first (compactErr "header")+          (B64UL.decode (L.fromStrict $ Types.pad $ T.encodeUtf8 s))+        >>= first JSONDecodeError . eitherDecode+    where+    compactErr s' = CompactDecodeError . ((s' ++ " decode failed: ") ++)++instance ToArmour T.Text JWEHeader where+  toArmour = T.decodeUtf8 . Types.unpad . B64U.encode . L.toStrict . encode+++data JWERecipient = JWERecipient+  { _jweHeader :: Maybe JWEHeader -- ^ JWE Per-Recipient Unprotected Header+  , _jweEncryptedKey :: Maybe Types.Base64Octets  -- ^ JWE Encrypted Key+  }++instance FromJSON JWERecipient where+  parseJSON = withObject "JWE Recipient" $ \o -> JWERecipient+    <$> o .:? "header"+    <*> o .:? "encrypted_key"++data JWE = JWE+  { _jweProtected :: Maybe (Armour T.Text JWEHeader)+  , _jweUnprotected :: Maybe JWEHeader+  , _jweIv :: Maybe Types.Base64Octets  -- ^ JWE Initialization Vector+  , _jweAad :: Maybe Types.Base64Octets -- ^ JWE AAD+  , _jweCiphertext :: Types.Base64Octets  -- ^ JWE Ciphertext+  , _jweTag :: Maybe Types.Base64Octets  -- ^ JWE Authentication Tag+  , _jweRecipients :: [JWERecipient]+  }++instance FromJSON JWE where+  parseJSON =+    withObject "JWE JSON Serialization" $ \o -> JWE+      <$> o .:? "protected"+      <*> o .:? "unprotected"+      <*> o .:? "iv"+      <*> o .:? "aad"+      <*> o .: "ciphertext"+      <*> o .:? "tag"+      <*> o .: "recipients"+  -- TODO flattened serialization+++wrap+  :: MonadRandom m+  => AlgWithParams+  -> KeyMaterial+  -> B.ByteString  -- ^ message (key to wrap)+  -> m (Either Error (AlgWithParams, B.ByteString))+wrap alg@RSA_OAEP (RSAKeyMaterial k) m = bimap RSAError (alg,) <$>+  OAEP.encrypt (OAEP.OAEPParams SHA1 (mgf1 SHA1) Nothing) (rsaPublicKey k) m+wrap RSA_OAEP _ _ = return $ Left $ AlgorithmMismatch "Cannot use RSA_OAEP with non-RSA key"+wrap alg@RSA_OAEP_256 (RSAKeyMaterial k) m = bimap RSAError (alg,) <$>+  OAEP.encrypt (OAEP.OAEPParams SHA256 (mgf1 SHA256) Nothing) (rsaPublicKey k) m+wrap RSA_OAEP_256 _ _ = return $ Left $ AlgorithmMismatch "Cannot use RSA_OAEP_256 with non-RSA key"+wrap A128KW (OctKeyMaterial (OctKeyParameters _ (Types.Base64Octets k))) m+  = return $ (A128KW,) <$> wrapAESKW (cipherInit k :: CryptoFailable AES128) m+wrap A192KW (OctKeyMaterial (OctKeyParameters _ (Types.Base64Octets k))) m+  = return $ (A192KW,) <$> wrapAESKW (cipherInit k :: CryptoFailable AES192) m+wrap A256KW (OctKeyMaterial (OctKeyParameters _ (Types.Base64Octets k))) m+  = return $ (A256KW,) <$> wrapAESKW (cipherInit k :: CryptoFailable AES256) m+wrap (A128GCMKW _) k m = wrapAESGCM A128GCMKW A128GCM k m+wrap (A192GCMKW _) k m = wrapAESGCM A192GCMKW A192GCM k m+wrap (A256GCMKW _) k m = wrapAESGCM A256GCMKW A256GCM k m+wrap _ _ _ = return $ Left AlgorithmNotImplemented++wrapAESKW+  :: BlockCipher128 cipher+  => CryptoFailable cipher+  -> B.ByteString -- ^ plaintext key (to be encrypted)+  -> Either Error B.ByteString -- ^ encrypted key+wrapAESKW cipher m = case cipher of+  CryptoFailed e -> Left (CryptoError e)+  CryptoPassed cipher' -> Right (aesKeyWrap cipher' m)++wrapAESGCM+  :: MonadRandom m+  => (AESGCMParameters -> AlgWithParams)+  -> Enc+  -> KeyMaterial+  -> B.ByteString+  -> m (Either Error (AlgWithParams, B.ByteString))+wrapAESGCM f enc (OctKeyMaterial (OctKeyParameters _ (Types.Base64Octets k))) m =+  fmap (\(iv, tag, m') -> (f (AESGCMParameters (Types.Base64Octets iv) (Types.Base64Octets tag)), m'))+  <$> encrypt enc k m ""+wrapAESGCM _ _ _ _ = return $ Left $ AlgorithmMismatch "Cannot use AESGCMKW with non-Oct key"++encrypt+  :: MonadRandom m+  => Enc+  -> B.ByteString -- ^ key+  -> B.ByteString  -- ^ message+  -> B.ByteString  -- ^ AAD+  -> m (Either Error (B.ByteString, B.ByteString, B.ByteString))+encrypt A128CBC_HS256 k m a = case B.length k of+  32 -> _cbcHmacEnc (undefined :: AES128) SHA256 k m a+  _ -> return $ Left KeySizeTooSmall+encrypt A192CBC_HS384 k m a = case B.length k of+  48 -> _cbcHmacEnc (undefined :: AES192) SHA384 k m a+  _ -> return $ Left KeySizeTooSmall+encrypt A256CBC_HS512 k m a = case B.length k of+  64 -> _cbcHmacEnc (undefined :: AES256) SHA512 k m a+  _ -> return $ Left KeySizeTooSmall+encrypt A128GCM k m a = case B.length k of+  16 -> _gcmEnc (undefined :: AES128) k m a+  _ -> return $ Left KeySizeTooSmall+encrypt A192GCM k m a = case B.length k of+  24 -> _gcmEnc (undefined :: AES192) k m a+  _ -> return $ Left KeySizeTooSmall+encrypt A256GCM k m a = case B.length k of+  32 -> _gcmEnc (undefined :: AES256) k m a+  _ -> return $ Left KeySizeTooSmall++_cbcHmacEnc+  :: forall e h m. (BlockCipher e, HashAlgorithm h, MonadRandom m)+  => e+  -> h+  -> B.ByteString -- ^ key+  -> B.ByteString -- ^ message+  -> B.ByteString -- ^ additional authenticated data+  -> m (Either Error (B.ByteString, B.ByteString, B.ByteString))  -- ^ IV, cipertext and MAC+_cbcHmacEnc _ _ k m aad = do+  let+    kLen = B.length k `div` 2+    (eKey, mKey) = B.splitAt kLen k+    aadLen = B.reverse $ fst $ B.unfoldrN 8 (\x -> Just (fromIntegral x, x `div` 256)) (B.length aad)+  case cipherInit eKey of+    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)++_gcmEnc+  :: forall e m. (BlockCipher e, MonadRandom m)+  => e+  -> B.ByteString -- ^ key+  -> B.ByteString -- ^ message+  -> B.ByteString -- ^ additional authenticated data+  -> m (Either Error (B.ByteString, B.ByteString, B.ByteString))  -- ^ IV, tag and ciphertext+_gcmEnc _ k m aad = do+  iv <- getRandomBytes 12+  case cipherInit k of+    CryptoFailed _ -> return $ Left AlgorithmNotImplemented -- FIXME+    CryptoPassed (e :: e) -> case aeadInit AEAD_GCM e iv of+      CryptoFailed _ -> return $ Left AlgorithmNotImplemented -- FIXME+      CryptoPassed aead -> do+        let m' = pad (PKCS7 $ blockSize e) m+        let (c, aeadFinal) = aeadEncrypt (aeadAppendHeader aead aad) m'+        let tag = BA.pack $ BA.unpack $ aeadFinalize aeadFinal 16+        return $ Right (iv, tag, c)
src/Crypto/JOSE/JWK.hs view
@@ -1,4 +1,4 @@--- Copyright (C) 2013, 2014, 2015  Fraser Tweedale+-- Copyright (C) 2013, 2014, 2015, 2016  Fraser Tweedale -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License.@@ -37,6 +37,8 @@   , jwkX5c   , jwkX5t   , jwkX5tS256+  , fromKeyMaterial+  , genJWK    , JWKSet(..) @@ -44,14 +46,14 @@   ) where  import Control.Applicative-import Data.Bifunctor import Data.Maybe (catMaybes)  import Control.Lens hiding ((.=)) import Data.Aeson import Data.List.NonEmpty -import Crypto.JOSE.Classes+import Test.QuickCheck+ import qualified Crypto.JOSE.JWA.JWE.Alg as JWA.JWE import Crypto.JOSE.JWA.JWK import qualified Crypto.JOSE.JWA.JWS as JWA.JWS@@ -128,14 +130,27 @@     ]     ++ Types.objectPairs (toJSON _jwkMaterial) -instance Key JWK where-  type KeyGenParam JWK = Crypto.JOSE.JWA.JWK.KeyMaterialGenParam-  type KeyContent JWK = Crypto.JOSE.JWA.JWK.KeyMaterial-  gen p = first fromKeyContent . gen p-  fromKeyContent k = JWK k z z z z z z z z where z = Nothing-  public = jwkMaterial public-  sign h k = sign h $ k ^. jwkMaterial-  verify h k = verify h $ k ^. jwkMaterial+genJWK :: MonadRandom m => KeyMaterialGenParam -> m JWK+genJWK p = fromKeyMaterial <$> genKeyMaterial p++instance Arbitrary JWK where+  arbitrary = JWK+    <$> arbitrary+    <*> pure Nothing+    <*> pure Nothing+    <*> pure Nothing+    <*> arbitrary+    <*> pure Nothing+    <*> pure Nothing+    <*> arbitrary+    <*> arbitrary++fromKeyMaterial :: KeyMaterial -> JWK+fromKeyMaterial k = JWK k z z z z z z z z where z = Nothing+++instance AsPublicKey JWK where+  asPublicKey = prism' id (jwkMaterial (preview asPublicKey))   -- | JWK §4.  JSON Web Key Set (JWK Set) Format
src/Crypto/JOSE/JWS.hs view
@@ -24,8 +24,10 @@     Alg(..)    , JWSHeader(..)+  , newJWSHeader    , JWS(..)+  , newJWS   , jwsPayload   , signJWS 
src/Crypto/JOSE/JWS/Internal.hs view
@@ -1,4 +1,4 @@--- Copyright (C) 2013, 2014  Fraser Tweedale+-- Copyright (C) 2013, 2014, 2015  Fraser Tweedale -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License.@@ -23,7 +23,6 @@ import Control.Applicative import Control.Monad ((>=>), when, unless) import Data.Bifunctor-import Data.Char import Data.Maybe  import Control.Lens ((^.))@@ -43,7 +42,6 @@ import qualified Data.Text.Encoding as T import Data.Traversable (mapM) -import Crypto.JOSE.Classes import Crypto.JOSE.Compact import Crypto.JOSE.Error import qualified Crypto.JOSE.JWA.JWS as JWA.JWS@@ -106,12 +104,10 @@ instance FromArmour T.Text Error JWSHeader where   parseArmour s =         first (compactErr "header")-          (B64UL.decode (pad $ BSL.fromStrict $ T.encodeUtf8 s))+          (B64UL.decode (BSL.fromStrict $ Types.pad $ T.encodeUtf8 s))         >>= first JSONDecodeError . eitherDecode     where     compactErr s' = CompactDecodeError . ((s' ++ " decode failed: ") ++)-    pad t = t `BSL.append` BSL.replicate ((4 - BSL.length t `mod` 4) `mod` 4) c-    c = fromIntegral $ ord '='  instance ToArmour T.Text JWSHeader where   toArmour = T.decodeUtf8 . Types.unpad . B64U.encode . BSL.toStrict . encode@@ -123,9 +119,9 @@     <*> o .:? "jwk"     <*> o .:? "kid"     <*> o .:? "x5u"+    <*> o .:? "x5c"     <*> o .:? "x5t"     <*> o .:? "x5t#S256"-    <*> o .:? "x5c"     <*> o .:? "typ"     <*> o .:? "cty"     <*> (o .:? "crit" >>= mapM (parseCrit o))@@ -148,9 +144,10 @@ instance Default JWSHeader where   def = JWSHeader z z z z z z z z z z z where z = Nothing --- construct a minimal header with the given alg-algHeader :: JWA.JWS.Alg -> JWSHeader-algHeader alg = def { headerAlg = Just alg }+-- | Construct a minimal header with the given algorithm+--+newJWSHeader :: JWA.JWS.Alg -> JWSHeader+newJWSHeader alg = def { headerAlg = Just alg }   data Signature = Signature@@ -209,10 +206,14 @@       then fail "\"signatures\" member MUST NOT be present"       else (\p s -> JWS p [s]) <$> o .: "payload" <*> parseJSON v) v - instance ToJSON JWS where   toJSON (JWS p ss) = object ["payload" .= p, "signatures" .= ss] +-- | Construct a new (unsigned) JWS+--+newJWS :: BS.ByteString -> JWS+newJWS msg = JWS (Types.Base64Octets msg) []+ -- | Payload of a JWS, as a lazy bytestring. -- jwsPayload :: JWS -> BSL.ByteString@@ -257,15 +258,14 @@ -- | Create a new signature on a JWS. -- signJWS-  :: CPRG g-  => g        -- ^ Random number generator-  -> JWS      -- ^ JWS to sign+  :: MonadRandom m+  => JWS      -- ^ JWS to sign   -> JWSHeader  -- ^ Header for signature   -> JWK      -- ^ Key with which to sign-  -> (Either Error JWS, g) -- ^ JWS with new signature appended-signJWS g (JWS p sigs) h k = first (second appendSig) $ case headerAlg h of-    Nothing   -> (Left JWSMissingAlg, g)-    Just alg  -> sign alg k g (signingInput h' p)+  -> m (Either Error JWS) -- ^ JWS with new signature appended+signJWS (JWS p sigs) h k = case headerAlg h of+    Nothing   -> return $ Left JWSMissingAlg+    Just alg  -> fmap appendSig <$> sign alg (k ^. jwkMaterial) (signingInput h' p)   where     appendSig sig = JWS p (Signature h' Nothing (Types.Base64Octets sig):sigs)     h' = Just $ Unarmoured h@@ -324,5 +324,5 @@ verifySig :: JWK -> Types.Base64Octets -> Signature -> Either Error Bool verifySig k m sig@(Signature h _ (Types.Base64Octets s)) = maybe   (Left $ AlgorithmMismatch "No 'alg' header")  -- shouldn't happen-  (\alg -> verify alg k (signingInput h m) s)+  (\alg -> verify alg (k ^. jwkMaterial) (signingInput h m) s)   (algorithm sig)
src/Crypto/JOSE/Legacy.hs view
@@ -1,4 +1,4 @@--- Copyright (C) 2013, 2014  Fraser Tweedale+-- Copyright (C) 2013, 2014, 2015, 2016  Fraser Tweedale -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License.@@ -26,23 +26,20 @@ module Crypto.JOSE.Legacy   (     JWK'(..)+  , genJWK'   , toJWK   , RSKeyParameters()-  , rsaKeyParameters   ) where  import Control.Applicative-import Data.Bifunctor-import GHC.Types (Int(I#))-import GHC.Integer.Logarithms (integerLog2#)  import Control.Lens hiding ((.=))+import Crypto.Number.Basic (log2) import Data.Aeson import Data.Aeson.Types import qualified Data.Text as T import Safe (readMay) -import Crypto.JOSE.Classes import Crypto.JOSE.JWA.JWK import Crypto.JOSE.JWK import qualified Crypto.JOSE.Types.Internal as Types@@ -50,8 +47,8 @@ import Crypto.JOSE.TH  -newtype StringifiedInteger = StringifiedInteger { _unString :: Integer }-makeLenses ''StringifiedInteger+newtype StringifiedInteger = StringifiedInteger Integer+makePrisms ''StringifiedInteger  instance FromJSON StringifiedInteger where   parseJSON = withText "StringifiedInteger" $@@ -63,26 +60,24 @@   toJSON (StringifiedInteger n) = toJSON $ show n  b64Iso :: Iso' StringifiedInteger Base64Integer-b64Iso = iso-  (Base64Integer . view unString)-  (\(Base64Integer n) -> StringifiedInteger n)+b64Iso = _StringifiedInteger . from _Base64Integer  sizedB64Iso :: Iso' StringifiedInteger SizedBase64Integer sizedB64Iso = iso-  ((\n -> SizedBase64Integer (size n) n) . view unString)+  ((\n -> SizedBase64Integer (size n) n) . view _StringifiedInteger)   (\(SizedBase64Integer _ n) -> StringifiedInteger n)   where   size n =-    let (bytes, bits) = (I# (integerLog2# n) + 1) `divMod` 8+    let (bytes, bits) = (log2 n + 1) `divMod` 8     in bytes + signum bits   $(Crypto.JOSE.TH.deriveJOSEType "RS" ["RS"])  -newtype RSKeyParameters = RSKeyParameters { _rsaKeyParameters :: RSAKeyParameters }+newtype RSKeyParameters = RSKeyParameters RSAKeyParameters   deriving (Eq, Show)-makeLenses ''RSKeyParameters+makePrisms ''RSKeyParameters  instance FromJSON RSKeyParameters where   parseJSON = withObject "RS" $ \o -> fmap RSKeyParameters $ RSAKeyParameters@@ -101,21 +96,12 @@       ++ maybe [] (\p -> ["d" .= (rsaD p ^. from b64Iso)])         (k ^. rsaPrivateKeyParameters) -instance Key RSKeyParameters where-  type KeyGenParam RSKeyParameters = Int-  type KeyContent RSKeyParameters = RSAKeyParameters-  gen p = first fromKeyContent . gen p-  fromKeyContent = RSKeyParameters-  public = rsaKeyParameters public-  sign h (RSKeyParameters k) = sign h k-  verify h (RSKeyParameters k) = verify h k - -- | Legacy JSON Web Key data type. ---newtype JWK' = JWK' { _rsKeyParameters :: RSKeyParameters }+newtype JWK' = JWK' RSKeyParameters   deriving (Eq, Show)-makeLenses ''JWK'+makePrisms ''JWK'  instance FromJSON JWK' where   parseJSON = withObject "JWK'" $ \o -> JWK' <$> parseJSON (Object o)@@ -123,14 +109,11 @@ instance ToJSON JWK' where   toJSON (JWK' k) = object $ Types.objectPairs (toJSON k) -instance Key JWK' where-  type KeyGenParam JWK' = Int-  type KeyContent JWK' = RSKeyParameters-  gen p g = first JWK' $ gen p g-  fromKeyContent = JWK'-  public = rsKeyParameters public-  sign h (JWK' k) = sign h k-  verify h (JWK' k) = verify h k+instance AsPublicKey JWK' where+  asPublicKey = prism' id (_JWK' (_RSKeyParameters (preview asPublicKey))) +genJWK' :: MonadRandom m => Int -> m JWK'+genJWK' size = JWK' . RSKeyParameters <$> genRSA size+ toJWK :: JWK' -> JWK-toJWK (JWK' (RSKeyParameters k)) = fromKeyContent $ RSAKeyMaterial k+toJWK (JWK' (RSKeyParameters k)) = fromKeyMaterial $ RSAKeyMaterial k
src/Crypto/JOSE/Types.hs view
@@ -1,4 +1,4 @@--- Copyright (C) 2013, 2014, 2015  Fraser Tweedale+-- Copyright (C) 2013, 2014, 2015, 2016  Fraser Tweedale -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License.@@ -13,6 +13,7 @@ -- limitations under the License.  {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-}  {-| @@ -22,8 +23,10 @@ module Crypto.JOSE.Types   (     Base64Integer(..)+  , _Base64Integer   , SizedBase64Integer(..)-  , Base64UrlString(..)+  , genSizedBase64IntegerOf+  , checkSize   , Base64Octets(..)   , Base64SHA1(..)   , Base64SHA256(..)@@ -33,12 +36,16 @@  import Control.Applicative +import Control.Lens import Data.Aeson+import Data.Aeson.Types (Parser) import Data.Byteable import qualified Data.ByteString as B import qualified Data.ByteString.Base64.URL as B64U import Data.X509 import Network.URI (URI)+import Test.QuickCheck+import Test.QuickCheck.Instances ()  import Crypto.JOSE.Types.Internal import Crypto.JOSE.Types.Orphans ()@@ -48,6 +55,7 @@ -- newtype Base64Integer = Base64Integer Integer   deriving (Eq, Show)+makePrisms ''Base64Integer  instance FromJSON Base64Integer where   parseJSON = withText "base64url integer" $ parseB64Url $@@ -56,31 +64,50 @@ instance ToJSON Base64Integer where   toJSON (Base64Integer x) = encodeB64Url $ integerToBS x +instance Arbitrary Base64Integer where+  arbitrary = Base64Integer <$> arbitrarySizedNatural + -- | A base64url encoded octet sequence interpreted as an integer -- and where the number of octets carries explicit bit-length -- information. -- data SizedBase64Integer = SizedBase64Integer Int Integer-  deriving (Eq, Show)+  deriving (Show) +instance Eq SizedBase64Integer where+  SizedBase64Integer _ n == SizedBase64Integer _ m = n == m++instance Arbitrary SizedBase64Integer where+  arbitrary = do+    x <- arbitrarySizedNatural+    l <- arbitrarySizedNatural  -- arbitrary number of leading zero-bytes+    return $ SizedBase64Integer+      ((+ l) $ ceiling $ logBase 2 (fromInteger x :: Double))+      x++genByteStringOf :: Int -> Gen B.ByteString+genByteStringOf n = B.pack <$> vectorOf n arbitrary++-- | Generate a 'SizedBase64Integer' of the given number of bytes+--+genSizedBase64IntegerOf :: Int -> Gen SizedBase64Integer+genSizedBase64IntegerOf n =+  SizedBase64Integer n . bsToInteger <$> genByteStringOf n+ instance FromJSON SizedBase64Integer where   parseJSON = withText "full size base64url integer" $ parseB64Url (\bytes ->     pure $ SizedBase64Integer (B.length bytes) (bsToInteger bytes))  instance ToJSON SizedBase64Integer where-  toJSON (SizedBase64Integer s x) = encodeB64Url $ zeroPad $ integerToBS x-    where zeroPad xs = B.replicate (s - B.length xs) 0 `B.append` xs-+  toJSON (SizedBase64Integer w n) = encodeB64Url $ sizedIntegerToBS w n --- | A base64url encoded string.  This is used for the JWE--- /Agreement PartyUInfo/ and /Agreement PartyVInfo/ fields.+-- | Parsed a 'SizedBase64Integer' with an expected number of /bytes/. ---newtype Base64UrlString = Base64UrlString B.ByteString-  deriving (Eq, Show)--instance FromJSON Base64UrlString where-  parseJSON = withText "base64url string" $ parseB64Url $ pure . Base64UrlString+checkSize :: Int -> SizedBase64Integer -> Parser SizedBase64Integer+checkSize n a@(SizedBase64Integer m _) = if n == m+  then return a+  else fail $ "expected " ++ show n ++ " octets, found " ++ show m   -- | A base64url encoded octet sequence.  Used for payloads,@@ -98,7 +125,10 @@ instance ToJSON Base64Octets where   toJSON (Base64Octets bytes) = encodeB64Url bytes +instance Arbitrary Base64Octets where+  arbitrary = Base64Octets <$> arbitrary + -- | A base64url encoded SHA-1 digest.  Used for X.509 certificate -- thumbprints. --@@ -114,7 +144,10 @@ instance ToJSON Base64SHA1 where   toJSON (Base64SHA1 bytes) = encodeB64Url bytes +instance Arbitrary Base64SHA1 where+  arbitrary = Base64SHA1 <$> genByteStringOf 20 + -- | A base64url encoded SHA-256 digest.  Used for X.509 certificate -- thumbprints. --@@ -129,6 +162,9 @@  instance ToJSON Base64SHA256 where   toJSON (Base64SHA256 bytes) = encodeB64Url bytes++instance Arbitrary Base64SHA256 where+  arbitrary = Base64SHA256 <$> genByteStringOf 32   -- | A base64 encoded X.509 certificate.
src/Crypto/JOSE/Types/Internal.hs view
@@ -84,3 +84,7 @@   where     f x = if x == 0 then Nothing else Just (toWord8 $ quotRem x 256)     toWord8 (seed, x) = (seed, fromIntegral x)++sizedIntegerToBS :: Int -> Integer -> B.ByteString+sizedIntegerToBS w = zeroPad . integerToBS+  where zeroPad xs = B.replicate (w - B.length xs) 0 `B.append` xs
src/Crypto/JOSE/Types/Orphans.hs view
@@ -18,12 +18,14 @@  import Prelude hiding (mapM) +import Control.Applicative import Data.Traversable  import Data.List.NonEmpty (NonEmpty(..), toList) import qualified Data.Text as T import qualified Data.Vector as V import Network.URI (URI, parseURI)+import Test.QuickCheck  import Data.Aeson @@ -43,3 +45,7 @@  instance ToJSON URI where   toJSON = String . T.pack . show+++instance Arbitrary a => Arbitrary (NonEmpty a) where+  arbitrary = (:|) <$> arbitrary <*> arbitrary
src/Crypto/JWT.hs view
@@ -1,4 +1,4 @@--- Copyright (C) 2013, 2014  Fraser Tweedale+-- Copyright (C) 2013, 2014, 2015  Fraser Tweedale -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License.@@ -55,7 +55,7 @@ import Data.Bifunctor import Data.Maybe -import Control.Lens hiding ((.=))+import Control.Lens (makeLenses, over) import Data.Aeson import qualified Data.ByteString.Lazy as BSL import qualified Data.HashMap.Strict as M@@ -267,13 +267,12 @@ -- | Create a JWT that is a JWS. -- createJWSJWT-  :: CPRG g-  => g-  -> JWK+  :: MonadRandom m+  => JWK   -> JWSHeader   -> ClaimsSet-  -> (Either Error JWT, g)-createJWSJWT g k h c = first (fmap $ \jws -> JWT (JWTJWS jws) c) $-  signJWS g (JWS payload []) h k+  -> m (Either Error JWT)+createJWSJWT k h c = fmap (\jws -> JWT (JWTJWS jws) c) <$>+  signJWS (JWS payload []) h k   where     payload = Base64Octets $ BSL.toStrict $ encode c
+ test/AESKW.hs view
@@ -0,0 +1,54 @@+-- Copyright (C) 2016  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.++module AESKW where++import Control.Applicative ((<$>), pure)++import qualified Data.ByteString as B+import Crypto.Cipher.AES+import Crypto.Cipher.Types+import Crypto.Error++import Test.QuickCheck.Monadic+import Test.Tasty+import Test.Tasty.QuickCheck++import Crypto.JOSE.AESKW+++aeskwProperties = testGroup "AESKW"+  [ testProperty "AESKW round-trip" prop_roundTrip+  ]++prop_roundTrip :: Property+prop_roundTrip = monadicIO $ do+  cekLen <- (* 8) . (+ 2) <$> pick arbitrarySizedNatural+  cek <- pick $ B.pack <$> vectorOf cekLen arbitrary+  kekLen <- pick $ oneof $ pure <$> [16, 24, 32]+  kek <- pick $ B.pack <$> vectorOf kekLen arbitrary+  let+    check :: BlockCipher128 cipher => CryptoFailable cipher -> Bool+    check cipher' = case cipher' of+      CryptoFailed _ -> False+      CryptoPassed cipher ->+        let+          c = aesKeyWrap cipher cek :: B.ByteString+          cek' = aesKeyUnwrap cipher c+        in+          B.length c == cekLen + 8 && cek' == Just cek+  case kekLen of+    16 -> assert $ check (cipherInit kek :: CryptoFailable AES128)+    24 -> assert $ check (cipherInit kek :: CryptoFailable AES192)+    32 -> assert $ check (cipherInit kek :: CryptoFailable AES256)
test/JWS.hs view
@@ -1,4 +1,4 @@--- Copyright (C) 2013, 2014  Fraser Tweedale+-- Copyright (C) 2013, 2014, 2015  Fraser Tweedale -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License.@@ -18,6 +18,7 @@  import Data.Maybe +import Control.Lens import Data.Aeson import qualified Data.ByteString as BS import qualified Data.ByteString.Lazy as L@@ -25,7 +26,6 @@ import Data.Default.Class import Test.Hspec -import Crypto.JOSE.Classes import Crypto.JOSE.Compact import Crypto.JOSE.JWA.JWK import Crypto.JOSE.JWK@@ -36,8 +36,8 @@ import Crypto.JOSE.Types.Armour  -rng :: SystemRNG-rng = cprgCreate $ createTestEntropyPool "dummy CPRG for testing"+drg :: ChaChaDRG+drg = drgNewTest (1,2,3,4,5)  spec :: Spec spec = do@@ -72,7 +72,7 @@ critSpec' :: Spec critSpec' = describe "JWS §4.1.10. \"crit\" Header Parameter; full example" $   it "parses from JSON correctly" $-    decode s `shouldBe` Just ((algHeader JWA.JWS.ES256) { headerCrit = Just critValue })+    decode s `shouldBe` Just ((newJWSHeader JWA.JWS.ES256) { headerCrit = Just critValue })     where       s = "{\"alg\":\"ES256\",\"crit\":[\"exp\"],\"exp\":1363284000}"       critValue = CritParameters $ return ("exp", Number 1363284000)@@ -86,7 +86,7 @@       typValue = Just "JWT"     in       eitherDecode headerJSON-        `shouldBe` Right ((algHeader JWA.JWS.HS256) { headerTyp = typValue })+        `shouldBe` Right ((newJWSHeader JWA.JWS.HS256) { headerTyp = typValue })    it "parses signature correctly" $     let@@ -121,7 +121,7 @@     (encodeCompact jws >>= decodeCompact) `shouldBe` Right jws    it "computes the HMAC correctly" $-    fst (sign alg jwk rng (L.toStrict signingInput'))+    fst (withDRG drg $ sign alg (jwk ^. jwkMaterial) (L.toStrict signingInput'))       `shouldBe` Right (BS.pack macOctets)    it "validates the JWS correctly" $@@ -157,10 +157,11 @@ appendixA2Spec :: Spec appendixA2Spec = describe "JWS A.2. Example JWS using RSASSA-PKCS-v1_5 SHA-256" $ do   it "computes the signature correctly" $-    fst (sign JWA.JWS.RS256 jwk rng signingInput') `shouldBe` Right sig+    fst (withDRG drg $ sign JWA.JWS.RS256 (jwk ^. jwkMaterial) signingInput')+      `shouldBe` Right sig    it "validates the signature correctly" $-    verify JWA.JWS.RS256 jwk signingInput' sig `shouldBe` Right True+    verify JWA.JWS.RS256 (jwk ^. jwkMaterial) signingInput' sig `shouldBe` Right True    where     signingInput' = "\@@ -209,7 +210,7 @@ appendixA3Spec :: Spec appendixA3Spec = describe "JWS A.3.  Example JWS using ECDSA P-256 SHA-256" $   it "validates the signature correctly" $-    verify JWA.JWS.ES256 jwk signingInput' sig `shouldBe` Right True+    verify JWA.JWS.ES256 (jwk ^. jwkMaterial) signingInput' sig `shouldBe` Right True   where     signingInput' = "\       \eyJhbGciOiJFUzI1NiJ9\@@ -240,8 +241,8 @@     decodeCompact exampleJWS `shouldBe` jws    where-    jws = fst $-      signJWS rng (JWS examplePayload []) (algHeader JWA.JWS.None) undefined+    jws = fst $ withDRG drg $+      signJWS (JWS examplePayload []) (newJWSHeader JWA.JWS.None) undefined     exampleJWS = "eyJhbGciOiJub25lIn0\       \.\       \eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt\
+ test/Properties.hs view
@@ -0,0 +1,85 @@+-- Copyright (C) 2015  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.++{-# LANGUAGE ScopedTypeVariables #-}++module Properties where++import Control.Applicative++import Data.Aeson+import qualified Data.ByteString as B+import Data.Default.Class++import Test.Tasty+import Test.Tasty.QuickCheck+import Test.QuickCheck.Monadic+import Test.QuickCheck.Instances ()++import Crypto.JOSE.Types+import Crypto.JOSE.JWK+import Crypto.JOSE.JWS++properties = testGroup "Properties"+  [ testProperty "SizedBase64Integer round-trip"+    (prop_roundTrip :: SizedBase64Integer -> Bool)+  , testProperty "JWK round-trip" (prop_roundTrip :: JWK -> Bool)+  , testProperty "ECDSA gen, sign and verify" prop_ecSignAndVerify+  , testProperty "HMAC gen, sign and verify" prop_hmacSignAndVerify+  , testProperty "RSA gen, sign and verify" prop_rsaSignAndVerify+  ]++prop_roundTrip :: (Eq a, ToJSON a, FromJSON a) => a -> Bool+prop_roundTrip a = decode (encode [a]) == Just [a]++debugRoundTrip+  :: (Show a, Arbitrary a, ToJSON a, FromJSON a)+  => (a -> Bool)+  -> Property+debugRoundTrip f = monadicIO $ do+  a :: a <- pick arbitrary+  let encoded = encode [a]+  monitor $ counterexample $+    "JSON: \n" ++ show encoded ++ "\n\nDecoded: \n" ++ show (decode encoded :: Maybe [a])+  assert $ f a++prop_ecSignAndVerify :: Crv -> B.ByteString -> Property+prop_ecSignAndVerify crv msg = monadicIO $ do+  k :: JWK <- run $ genJWK (ECGenParam crv)+  let alg = case crv of P_256 -> ES256 ; P_384 -> ES384 ; P_521 -> ES512+  wp (signJWS (newJWS msg) (newJWSHeader alg) k) (checkSignJWS k)++prop_hmacSignAndVerify :: B.ByteString -> Property+prop_hmacSignAndVerify msg = monadicIO $ do+  (alg, minLen) <-+    pick $ oneof $ pure <$> [(HS256, 32), (HS384, 48), (HS512, 64)]+  keylen <- (+ minLen) <$> pick arbitrarySizedNatural+  k :: JWK <- run $ genJWK (OctGenParam keylen)+  wp (signJWS (newJWS msg) (newJWSHeader alg) k) (checkSignJWS k)++prop_rsaSignAndVerify :: B.ByteString -> Property+prop_rsaSignAndVerify msg = monadicIO $ do+  keylen <- pick $ oneof $ pure . (`div` 8) <$> [2048, 3072, 4096]+  k :: JWK <- run $ genJWK (RSAGenParam keylen)+  alg <- pick $ oneof $ pure <$> [RS256, RS384, RS512, PS256, PS384, PS512]+  wp (signJWS (newJWS msg) (newJWSHeader alg) k) (checkSignJWS k)++checkSignJWS :: (Monad m, Show e) => JWK -> Either e JWS -> PropertyM m ()+checkSignJWS k signResult = case signResult of+  Left e -> do+    monitor (counterexample $ "Failed to sign: " ++ show e)+    assert False+  Right jws -> do+    monitor (counterexample "Failed to verify")+    assert (verifyJWS def def k jws)
test/Test.hs view
@@ -1,4 +1,4 @@--- Copyright (C) 2013, 2014  Fraser Tweedale+-- Copyright (C) 2013, 2014, 2015  Fraser Tweedale -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License.@@ -12,17 +12,27 @@ -- See the License for the specific language governing permissions and -- limitations under the License. -import Test.Hspec+import Test.Tasty+import Test.Tasty.Hspec+import Test.Tasty.QuickCheck +import AESKW import JWK import JWS import JWT import Types+import Properties   main :: IO ()-main = hspec $ do-  Types.spec-  JWK.spec-  JWS.spec-  JWT.spec+main = do+  unitTests <- unitTestsIO+  defaultMain $ testGroup "Tests" [unitTests, properties, aeskwProperties]++unitTestsIO :: IO TestTree+unitTestsIO = do+  types <- testSpec "Types" Types.spec+  jwk <- testSpec "JWK" JWK.spec+  jws <- testSpec "JWS" JWS.spec+  jwt <- testSpec "JWT" JWT.spec+  return $ testGroup "Unit tests" [types, jwk, jws, jwt]
test/Types.hs view
@@ -26,18 +26,11 @@  spec :: Spec spec = do-  base64UrlSpec   base64OctetsSpec   uriSpec   base64IntegerSpec   sizedBase64IntegerSpec   base64X509Spec--base64UrlSpec :: Spec-base64UrlSpec = describe "Base64UrlString" $-  it "can be read from JSON" $ do-    decode "[\"QWxpY2U\"]" `shouldBe` Just [Base64UrlString "Alice"]-    decode "[\"Qm9i\"]"`shouldBe` Just [Base64UrlString "Bob"]  base64OctetsSpec :: Spec base64OctetsSpec = describe "Base64Octets" $