paseto-0.1.0.0: test/Test/Crypto/Paseto/TestVectors.hs
module Test.Crypto.Paseto.TestVectors
( Base16ByteString (..)
, LocalTestVector (..)
, PublicTestVector (..)
, TestVector (..)
, TestVectors (..)
, mkV3TestVectorProperties
, mkV4TestVectorProperties
) where
import Control.Monad.Except ( ExceptT, runExceptT )
import Control.Monad.Trans.Except.Extra ( left )
import Crypto.Paseto.Keys
( bytesToSymmetricKeyV3
, bytesToSymmetricKeyV4
, bytesToVerificationKeyV3
, bytesToVerificationKeyV4
)
import Crypto.Paseto.Token ( Footer (..), ImplicitAssertion (..) )
import Crypto.Paseto.Token.Encoding
( ValidatedToken (..)
, decodeTokenV3Local
, decodeTokenV3Public
, decodeTokenV4Local
, decodeTokenV4Public
)
import Data.Aeson ( FromJSON (..), withObject, withText, (.:), (.:?) )
import qualified Data.Aeson as Aeson
import qualified Data.Aeson.Types as Aeson
import Data.ByteArray ( ByteArrayAccess )
import qualified Data.ByteArray as BA
import Data.ByteString ( ByteString )
import qualified Data.ByteString.Base16 as B16
import Data.Text ( Text )
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import Hedgehog ( PropertyT, (===) )
import Prelude
emptyToNothing :: (ByteString -> a) -> ByteString -> Maybe a
emptyToNothing _ "" = Nothing
emptyToNothing f bs = Just (f bs)
data ExpectedFailure = ExpectedFailure
handlePropertyWithExpectedFailure
:: PropertyT IO (Either ExpectedFailure ())
-> PropertyT IO ()
handlePropertyWithExpectedFailure prop = do
res <- prop
case res of
Left ExpectedFailure -> pure ()
Right () -> pure ()
-- | 'ByteString' that was decoded from base16-encoded text.
newtype Base16ByteString = Base16ByteString
{ unBase16ByteString :: ByteString }
deriving newtype (Show, Eq, ByteArrayAccess)
instance FromJSON Base16ByteString where
parseJSON = withText "Base16ByteString" $ \t ->
case B16.decode (TE.encodeUtf8 t) of
Left err -> fail $ "error decoding text as base16: " <> err
Right x -> pure (Base16ByteString x)
-- | Test vector for a local PASETO token.
data LocalTestVector = LocalTestVector
{ ltvName :: !Text
, ltvExpectFail :: !Bool
, ltvKey :: !Base16ByteString
, ltvNonce :: !Base16ByteString
, ltvToken :: !Text
, ltvPayload :: !(Maybe Text)
, ltvFooter :: !(Maybe Footer)
, ltvImplicitAssertion :: !(Maybe ImplicitAssertion)
} deriving stock (Show, Eq)
instance FromJSON LocalTestVector where
parseJSON = withObject "LocalTestVector" $ \v ->
LocalTestVector
<$> v .: "name"
<*> v .: "expect-fail"
<*> v .: "key"
<*> v .: "nonce"
<*> v .: "token"
<*> v .: "payload"
<*> (emptyToNothing Footer . TE.encodeUtf8 <$> v .: "footer")
<*> (emptyToNothing ImplicitAssertion . TE.encodeUtf8 <$> v .: "implicit-assertion")
mkV3LocalTestVectorProperty :: LocalTestVector -> (String, PropertyT IO ())
mkV3LocalTestVectorProperty ltv =
(T.unpack ltvName, handlePropertyWithExpectedFailure (runExceptT mkProp))
where
LocalTestVector
{ ltvName
, ltvKey
, ltvToken
, ltvPayload
, ltvFooter
, ltvImplicitAssertion
} = ltv
mkProp :: ExceptT ExpectedFailure (PropertyT IO) ()
mkProp = do
key <-
case bytesToSymmetricKeyV3 (BA.convert ltvKey) of
Nothing -> fail "invalid version 3 symmetric key"
Just k -> pure k
case decodeTokenV3Local key [] ltvFooter ltvImplicitAssertion ltvToken of
Left err ->
case ltvPayload of
Nothing -> left ExpectedFailure
Just _ -> fail $ "invalid version 3 local token (" <> T.unpack ltvToken <> "): " <> show err
Right ValidatedToken{ vtClaims } ->
case ltvPayload of
Nothing -> fail "expected token decoding failure"
Just expectedPayload -> do
expectedClaims <-
case Aeson.eitherDecodeStrict (TE.encodeUtf8 expectedPayload) of
Left err -> fail $ "invalid claims payload: " <> show err
Right x -> pure x
expectedClaims === vtClaims
mkV4LocalTestVectorProperty :: LocalTestVector -> (String, PropertyT IO ())
mkV4LocalTestVectorProperty ltv =
(T.unpack ltvName, handlePropertyWithExpectedFailure (runExceptT mkProp))
where
LocalTestVector
{ ltvName
, ltvKey
, ltvToken
, ltvPayload
, ltvFooter
, ltvImplicitAssertion
} = ltv
mkProp :: ExceptT ExpectedFailure (PropertyT IO) ()
mkProp = do
key <-
case bytesToSymmetricKeyV4 (BA.convert ltvKey) of
Nothing -> fail "invalid version 4 symmetric key"
Just k -> pure k
case decodeTokenV4Local key [] ltvFooter ltvImplicitAssertion ltvToken of
Left err ->
case ltvPayload of
Nothing -> left ExpectedFailure
Just _ -> fail $ "invalid version 4 local token (" <> T.unpack ltvToken <> "): " <> show err
Right ValidatedToken{ vtClaims } ->
case ltvPayload of
Nothing -> fail "expected token decoding failure"
Just expectedPayload -> do
expectedClaims <-
case Aeson.eitherDecodeStrict (TE.encodeUtf8 expectedPayload) of
Left err -> fail $ "invalid claims payload: " <> show err
Right x -> pure x
expectedClaims === vtClaims
-- | Test vector for a public PASETO token.
data PublicTestVector = PublicTestVector
{ ptvName :: !Text
, ptvExpectFail :: !Bool
, ptvPublicKey :: !Base16ByteString
, ptvSecretKey :: !Base16ByteString
, ptvPublicKeyPem :: !Text
, ptvSecretKeyPem :: !Text
, ptvToken :: !Text
, ptvPayload :: !(Maybe Text)
, ptvFooter :: !(Maybe Footer)
, ptvImplicitAssertion :: !(Maybe ImplicitAssertion)
} deriving stock (Show, Eq)
instance FromJSON PublicTestVector where
parseJSON = withObject "PublicTestVector" $ \v ->
PublicTestVector
<$> v .: "name"
<*> v .: "expect-fail"
<*> v .: "public-key"
<*> v .: "secret-key"
<*> v .: "public-key-pem"
<*> v .: "secret-key-pem"
<*> v .: "token"
<*> v .: "payload"
<*> (emptyToNothing Footer . TE.encodeUtf8 <$> v .: "footer")
<*> (emptyToNothing ImplicitAssertion . TE.encodeUtf8 <$> v .: "implicit-assertion")
mkV3PublicTestVectorProperty :: PublicTestVector -> (String, PropertyT IO ())
mkV3PublicTestVectorProperty ptv =
(T.unpack ptvName, handlePropertyWithExpectedFailure (runExceptT mkProp))
where
PublicTestVector
{ ptvName
, ptvPublicKey
, ptvToken
, ptvPayload
, ptvFooter
, ptvImplicitAssertion
} = ptv
mkProp :: ExceptT ExpectedFailure (PropertyT IO) ()
mkProp = do
vk <-
case bytesToVerificationKeyV3 (unBase16ByteString ptvPublicKey) of
Left err -> fail $ "invalid version 3 verification key: " <> show err
Right k -> pure k
case decodeTokenV3Public vk [] ptvFooter ptvImplicitAssertion ptvToken of
Left err ->
case ptvPayload of
Nothing -> left ExpectedFailure
Just _ -> fail $ "invalid version 3 public token (" <> T.unpack ptvToken <> "): " <> show err
Right ValidatedToken{ vtClaims } ->
case ptvPayload of
Nothing -> fail "expected token decoding failure"
Just expectedPayload -> do
expectedClaims <-
case Aeson.eitherDecodeStrict (TE.encodeUtf8 expectedPayload) of
Left err -> fail $ "invalid claims payload: " <> show err
Right x -> pure x
expectedClaims === vtClaims
mkV4PublicTestVectorProperty :: PublicTestVector -> (String, PropertyT IO ())
mkV4PublicTestVectorProperty ptv =
(T.unpack ptvName, handlePropertyWithExpectedFailure (runExceptT mkProp))
where
PublicTestVector
{ ptvName
, ptvPublicKey
, ptvToken
, ptvPayload
, ptvFooter
, ptvImplicitAssertion
} = ptv
mkProp :: ExceptT ExpectedFailure (PropertyT IO) ()
mkProp = do
vk <-
case bytesToVerificationKeyV4 (unBase16ByteString ptvPublicKey) of
Nothing -> fail "invalid version 4 verification key"
Just k -> pure k
case decodeTokenV4Public vk [] ptvFooter ptvImplicitAssertion ptvToken of
Left err ->
case ptvPayload of
Nothing -> left ExpectedFailure
Just _ -> fail $ "invalid version 4 public token (" <> T.unpack ptvToken <> "): " <> show err
Right ValidatedToken{ vtClaims } ->
case ptvPayload of
Nothing -> fail "expected token decoding failure"
Just expectedPayload -> do
expectedClaims <-
case Aeson.eitherDecodeStrict (TE.encodeUtf8 expectedPayload) of
Left err -> fail $ "invalid claims payload: " <> show err
Right x -> pure x
expectedClaims === vtClaims
-- | Test vector.
data TestVector
= -- | Test vector for a local PASETO token.
TestVectorLocal !LocalTestVector
| -- | Test vector for a public PASETO token.
TestVectorPublic !PublicTestVector
deriving stock (Show, Eq)
instance FromJSON TestVector where
parseJSON v = withObject "TestVector" f v
where
f o = do
symmetricKey <- o .:? "key" :: Aeson.Parser (Maybe Base16ByteString)
case symmetricKey of
Just _ -> TestVectorLocal <$> parseJSON v
Nothing -> TestVectorPublic <$> parseJSON v
mkV3TestVectorProperty :: TestVector -> (String, PropertyT IO ())
mkV3TestVectorProperty tv =
case tv of
TestVectorLocal ltv -> mkV3LocalTestVectorProperty ltv
TestVectorPublic ptv -> mkV3PublicTestVectorProperty ptv
mkV4TestVectorProperty :: TestVector -> (String, PropertyT IO ())
mkV4TestVectorProperty tv =
case tv of
TestVectorLocal ltv -> mkV4LocalTestVectorProperty ltv
TestVectorPublic ptv -> mkV4PublicTestVectorProperty ptv
-- | List of 'TestVector's.
newtype TestVectors = TestVectors
{ unTestVectors :: [TestVector] }
deriving newtype (Show, Eq)
instance FromJSON TestVectors where
parseJSON = withObject "TestVectors" $ \v -> do
TestVectors <$> v .: "tests"
mkV3TestVectorProperties :: TestVectors -> [(String, PropertyT IO ())]
mkV3TestVectorProperties = map mkV3TestVectorProperty . unTestVectors
mkV4TestVectorProperties :: TestVectors -> [(String, PropertyT IO ())]
mkV4TestVectorProperties = map mkV4TestVectorProperty . unTestVectors