jose-0.12: test/Properties.hs
-- 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 NoMonomorphismRestriction #-}
{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_GHC -fno-warn-incomplete-uni-patterns #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Properties
( properties
) where
import Control.Monad.IO.Class
import Control.Lens ((&), set, view)
import Crypto.Number.Basic (log2)
import Crypto.Random
import Data.Aeson (FromJSON, ToJSON, decode, encode)
import qualified Data.ByteString as B
import Hedgehog
import qualified Hedgehog.Gen as Gen
import qualified Hedgehog.Range as Range
import Test.Tasty
import Test.Tasty.Hedgehog
import Crypto.JOSE.Types
import Crypto.JOSE.JWK
import Crypto.JOSE.JWS
instance (MonadIO m) => MonadRandom (PropertyT m) where
getRandomBytes = liftIO . getRandomBytes
properties :: TestTree
properties = testGroup "Properties"
[ let n = "SizedBase64Integer round-trip" in testPropertyNamed n n (prop_roundTrip genSizedBase64Integer)
, let n = "JWK round-trip" in testPropertyNamed n n (prop_roundTrip genJWK')
, let n = "RSA gen, sign and verify" in testPropertyNamed n n prop_rsaSignAndVerify
, let n = "gen, sign with best alg, verify" in testPropertyNamed n n prop_bestJWSAlg
]
genBigInteger :: Gen Integer
genBigInteger = Gen.integral $ Range.exponential 0 (2 ^ (4096 :: Integer))
genBase64Integer :: Gen Base64Integer
genBase64Integer = Base64Integer <$> genBigInteger
genSizedBase64Integer :: Gen SizedBase64Integer
genSizedBase64Integer = do
x <- genBigInteger
l <- Gen.element [0, 1, 2] -- number of leading zero-bytes
pure $ SizedBase64Integer ((log2 x `div` 8) + 1 + l) x
prop_roundTrip :: (Eq a, Show a, ToJSON a, FromJSON a) => Gen a -> Property
prop_roundTrip gen = property $
forAll gen >>= \a -> decode (encode [a]) === Just [a]
prop_rsaSignAndVerify :: Property
prop_rsaSignAndVerify = property $ do
msg <- forAll $ Gen.bytes (Range.linear 0 100)
keylen <- forAll $ Gen.element ((`div` 8) <$> [2048, 3072, 4096])
k <- evalIO $ genJWK (RSAGenParam keylen)
alg_ <- forAll $ Gen.element [RS256, RS384, RS512, PS256, PS384, PS512]
collect alg_
msg' <- evalExceptT $ unwrapJOSE
( signJWS msg [(newJWSHeader (Protected, alg_), k)]
>>= verifyJWS defaultValidationSettings k
:: JOSE Error (PropertyT IO) B.ByteString
)
msg' === msg
genCrv :: Gen Crv
genCrv = Gen.element [P_256, P_384, P_521, Secp256k1]
genOKPCrv :: Gen OKPCrv
genOKPCrv = Gen.element [Ed25519, Ed448, X25519, X448]
genKeyMaterialGenParam :: Gen KeyMaterialGenParam
genKeyMaterialGenParam = Gen.choice
[ ECGenParam <$> genCrv
, RSAGenParam <$> Gen.element ((`div` 8) <$> [2048, 3072, 4096])
, OctGenParam <$> liftA2 (+) (Gen.integral (Range.exponential 0 64)) (Gen.element [32, 48, 64])
, OKPGenParam <$> genOKPCrv
]
prop_bestJWSAlg :: Property
prop_bestJWSAlg = property $ do
msg <- forAll $ Gen.bytes (Range.linear 0 100)
genParam <- forAll $ genKeyMaterialGenParam
k <- evalIO $ genJWK genParam
case bestJWSAlg k of
Left (KeyMismatch _) -> discard -- skip non-signing keys
Left _ -> assert False
Right alg_ -> do
collect alg_
msg' <- evalExceptT $ unwrapJOSE
( signJWS msg [(newJWSHeader (Protected, alg_), k)]
>>= verifyJWS defaultValidationSettings k
:: JOSE Error (PropertyT IO) B.ByteString
)
msg' === msg
genRSAPrivateKeyOthElem :: Gen RSAPrivateKeyOthElem
genRSAPrivateKeyOthElem =
RSAPrivateKeyOthElem <$> genBase64Integer <*> genBase64Integer <*> genBase64Integer
genRSAPrivateKeyOptionalParameters :: Gen RSAPrivateKeyOptionalParameters
genRSAPrivateKeyOptionalParameters =
RSAPrivateKeyOptionalParameters
<$> genBase64Integer
<*> genBase64Integer
<*> genBase64Integer
<*> genBase64Integer
<*> genBase64Integer
<*> Gen.maybe (Gen.nonEmpty (Range.linear 1 3) genRSAPrivateKeyOthElem)
genRSAPrivateKeyParameters :: Gen RSAPrivateKeyParameters
genRSAPrivateKeyParameters =
RSAPrivateKeyParameters
<$> genBase64Integer
<*> Gen.maybe (genRSAPrivateKeyOptionalParameters)
genRSAKeyParameters :: Gen RSAKeyParameters
genRSAKeyParameters =
RSAKeyParameters
<$> genBase64Integer
<*> genBase64Integer
<*> Gen.maybe (genRSAPrivateKeyParameters)
genDRG :: Gen ChaChaDRG
genDRG = do
let word64 = Gen.word64 Range.constantBounded
seed <- (,,,,) <$> word64 <*> word64 <*> word64 <*> word64 <*> word64
pure $ drgNewTest seed
genECKeyParameters :: Gen ECKeyParameters
genECKeyParameters = do
drg <- genDRG
crv <- genCrv
let (k, _) = withDRG drg (genEC crv)
includePrivate <- Gen.bool
pure $ if includePrivate
then k
else (let Just a = view asPublicKey k in a)
genOctKeyParameters :: Gen OctKeyParameters
genOctKeyParameters = OctKeyParameters . Base64Octets <$> Gen.bytes (Range.linear 16 128)
genOKPKeyParameters :: Gen OKPKeyParameters
genOKPKeyParameters = do
drg <- genDRG
crv <- genOKPCrv
let (k, _) = withDRG drg (genOKP crv)
includePrivate <- Gen.bool
pure $ if includePrivate
then k
else (let Just a = view asPublicKey k in a)
genKeyMaterial' :: Gen KeyMaterial
genKeyMaterial' = Gen.choice
[ ECKeyMaterial <$> genECKeyParameters
, RSAKeyMaterial <$> genRSAKeyParameters
, OctKeyMaterial <$> genOctKeyParameters
, OKPKeyMaterial <$> genOKPKeyParameters
]
genBase64SHA1 :: Gen Base64SHA1
genBase64SHA1 = Base64SHA1 <$> Gen.bytes (Range.singleton 20)
genBase64SHA256 :: Gen Base64SHA256
genBase64SHA256 = Base64SHA256 <$> Gen.bytes (Range.singleton 32)
genJWK' :: Gen JWK
genJWK' = do
key <- genKeyMaterial'
kid_ <- Gen.text (Range.linear 8 16) Gen.hexit
x5t_ <- genBase64SHA1
x5tS256_ <- genBase64SHA256
pure $ fromKeyMaterial key
& set jwkKid (Just kid_)
& set jwkX5t (Just x5t_)
& set jwkX5tS256 (Just x5tS256_)