packages feed

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