botan-low-0.2.0.0: test/Test/Botan/Low/PwdHash.hs
{-# LANGUAGE OverloadedStrings #-}
module Test.Botan.Low.PwdHash (
tests
-- * Test parameters
, generateTestParams
) where
import Botan.Low.Error
import Botan.Low.Hash
import Botan.Low.MAC
import Botan.Low.PwdHash
import Control.Exception
import Control.Monad
import Data.ByteString
import Test.Hspec
import Test.Tasty
import Test.Tasty.Hspec
import Test.Tasty.HUnit
import Test.Util.ByteString
import Test.Util.Hspec
tests :: IO TestTree
tests = do
specs <- testSpec "spec_pwdhash" spec_pwdhash
pure $ testGroup "Test.Botan.Low.PwdHash" [
testCase "test_pwdhash_badSchemeName" $
test_pwdhash_badSchemeName False
, testCase "test_pwdhashTimed_badSchemeName" $
test_pwdhash_badSchemeName True
, specs
]
{-------------------------------------------------------------------------------
Test parameters
-------------------------------------------------------------------------------}
data TestParams = TestParams {
pbkdfName :: PBKDFName
, iterationsParam :: Int
, parallelismParam :: Int
, memoryParam :: Int
}
deriving stock (Show, Eq)
-- | Parameters for testing
--
-- These are generated using 'generateTestParams'.
testParams :: [TestParams]
testParams =
[ TestParams "PBKDF2(SHA-256)" 436000 0 0
, TestParams "PBKDF2(SHA-512)" 258000 0 0
, TestParams "Scrypt" 8 1 65536
, TestParams "Argon2d" 1 1 262144
, TestParams "Argon2i" 1 1 262144
, TestParams "Argon2id" 1 1 262144
, TestParams "Bcrypt-PBKDF" 92 0 0
, TestParams "OpenPGP-S2K(SHA-384)" 65011712 0 0
]
-- | Schemes for testing
testPbkdfs :: [PBKDFName]
testPbkdfs = [
-- PBKDF2
pbkdf2 SHA256
, pbkdf2 SHA512
-- Scrypt
, Scrypt
-- Argon
, Argon2d
, Argon2i
, Argon2id
-- Bcrypt
, Bcrypt_PBKDF
-- OpenPGP S2K
, openPGP_S2K SHA384
]
-- | Generate test parameters using 'tuneParams' on schemes listed in
-- 'testPbkdfs'.
--
-- NOTE: the test parameters can change per invocation, but theys should be
-- roughly similar.
generateTestParams :: IO [TestParams]
generateTestParams = forM testPbkdfs tuneParams
-- | Tune parameters for a given scheme
tuneParams :: PBKDFName -> IO TestParams
tuneParams name = do
(iterations, parallelism, memory, _) <- pwdhashTimed name 200 64 passphrase salt
pure $ TestParams name iterations parallelism memory
passphrase :: ByteString
passphrase = "Fee fi fo fum!"
salt :: ByteString
salt = "salt"
{-------------------------------------------------------------------------------
Tests
-------------------------------------------------------------------------------}
-- | Test that using 'pwdhash' or 'pwdhashTimed' with bad scheme names results
-- in errors.
test_pwdhash_badSchemeName :: Bool -> Assertion
test_pwdhash_badSchemeName useTimed = do
-- PBKDF2
go PBKDF2 >>= \case
Left BadParameterException{} -> pure ()
Right{} -> assertFailure "got success, but expected BadParameterException"
go (pbkdf2 HMAC) >>= \case
Left NotImplementedException{} -> pure ()
Right{} -> assertFailure "got success, but expected NotImplementedException"
go (pbkdf2 $ hmac "2") >>= \case
Left NotImplementedException{} -> pure ()
Right{} -> assertFailure "got success, but expected NotImplementedException"
go (pbkdf2 $ hmac "SHA-256") >>= \case
Left SomeException{} -> assertFailure "got SomeException, but expected success"
Right{} -> pure ()
go (pbkdf2 $ gmac "SHA-256") >>= \case
Left NotImplementedException{} -> pure ()
Right{} -> assertFailure "got success, but expected NotImplementedException"
go (pbkdf2 "SHA-256") >>= \case
Left SomeException{} -> assertFailure "got SomeException, but expected success"
Right{} -> pure ()
-- OpenPGP S2K
go OpenPGP_S2K >>= \case
Left NotImplementedException{} -> pure ()
Right{} -> assertFailure "got success, but expected NotImplementedException"
go (openPGP_S2K "2") >>= \case
Left NotImplementedException{} -> pure ()
Right{} -> assertFailure "got success, but expected NotImplementedException"
go (openPGP_S2K "SHA-384") >>= \case
Left SomeException{} -> assertFailure "got SomeException, but expected success"
Right{} -> pure ()
where
go schemeName
| useTimed = try $
void $ pwdhashTimed schemeName 200 64 passphrase salt
| otherwise = try $
void $ pwdhash schemeName 1 0 0 64 passphrase salt
{-------------------------------------------------------------------------------
Specs
-------------------------------------------------------------------------------}
-- | Run 'pwdhash' and 'pwdhashTimed', and check that their outputs match.
spec_pwdhash :: Spec
spec_pwdhash =
testSuite testParams (\(TestParams n _ _ _) -> chars n) $
\params@(TestParams pbkdf _ _ _) -> do
it "pwdhash" $ do
_ <- pwdhashCorrected params
pass
it "pwdhashTimed" $ do
_timed@(iterations', parallelism', memory', pwd) <- pwdhashTimed pbkdf 200 64 passphrase salt
pwd' <- pwdhashCorrected (TestParams pbkdf iterations' parallelism' memory')
pwd `shouldBe` pwd'
pass
where
-- For @Argon2@ and @Scrypt@ algorithms, 'botan_pwdhash_timed' returns
-- parameters in a different order than what 'botan_pwdhash' takes in.
pwdhashCorrected :: TestParams -> IO ByteString
pwdhashCorrected (TestParams pbkdf iterations parallelism memory) =
case pbkdf of
"Scrypt" -> pwdhash pbkdf memory iterations parallelism 64 passphrase salt
_ | "Argon" `isPrefixOf` pbkdf -> pwdhash pbkdf memory iterations parallelism 64 passphrase salt
_ -> pwdhash pbkdf iterations parallelism memory 64 passphrase salt