packages feed

crypton-2.0.0: tests/KDF/Argon2Spec.hs

{-# LANGUAGE OverloadedStrings #-}

module KDF.Argon2Spec (spec) where

import Control.Exception (evaluate)
import Crypto.Error
import qualified Crypto.KDF.Argon2 as Argon2
import qualified Data.ByteString as B
import Imports

data KDFVector = KDFVector
    { kdfPass :: ByteString
    , kdfSalt :: ByteString
    , kdfOptions :: Argon2.Options
    , kdfResult :: ByteString
    }

argon2i_13 :: Argon2.TimeCost -> Argon2.MemoryCost -> Argon2.Options
argon2i_13 iters memory =
    Argon2.Options
        { Argon2.iterations = iters
        , Argon2.memory = memory
        , Argon2.parallelism = 1
        , Argon2.variant = Argon2.Argon2i
        , Argon2.version = Argon2.Version13
        }

vectors =
    [ KDFVector
        "password"
        "somesalt"
        (argon2i_13 2 65536)
        "\xc1\x62\x88\x32\x14\x7d\x97\x20\xc5\xbd\x1c\xfd\x61\x36\x70\x78\x72\x9f\x6d\xfb\x6f\x8f\xea\x9f\xf9\x81\x58\xe0\xd7\x81\x6e\xd0"
    ]

kdfTests :: [Spec]
kdfTests = zipWith toKDFTest is vectors
  where
    toKDFTest i v =
        it
            (show i)
            ( Argon2.hash (kdfOptions v) (kdfPass v) (kdfSalt v) (B.length $ kdfResult v)
                `shouldBe` CryptoPassed (kdfResult v)
            )

    is :: [Int]
    is = [1 ..]

-- | 'Argon2.hash' returns a 'CryptoFailable', but the bounds on iterations,
-- memory and parallelism are only enforced by the C implementation, whose
-- return code was turned into an 'error' raised from inside the allocation.
-- Invalid options have to come back through the failure the type already
-- offers.
--
-- The bytes are forced, because 'CryptoPassed' holds them lazily; a raise
-- rather than a 'CryptoFailed' therefore fails the example.
outcome :: CryptoFailable ByteString -> IO (Either CryptoError Int)
outcome (CryptoFailed err) = return (Left err)
outcome (CryptoPassed bs) = Right <$> evaluate (B.length bs)

refuses :: String -> Argon2.Options -> Spec
refuses name options =
    it name $
        outcome (Argon2.hash options pass salt outLen)
            `shouldReturn` Left CryptoError_ParameterInvalid

pass :: ByteString
pass = "password"

salt :: ByteString
salt = "somesalt"

outLen :: Int
outLen = 32

optionTests :: [Spec]
optionTests =
    [ it "valid options hash" $
        outcome (Argon2.hash (argon2i_13 2 65536) pass salt outLen)
            `shouldReturn` Right outLen
    , refuses
        "parallelism of 0 is refused"
        (argon2i_13 2 65536){Argon2.parallelism = 0}
    , refuses "iterations of 0 is refused" (argon2i_13 0 65536)
    , refuses "memory below the minimum is refused" (argon2i_13 2 1)
    ]

spec :: Spec
spec = do
    describe "KATs" $ sequence_ kdfTests
    describe "options" $ sequence_ optionTests