{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedLists #-} module PasswordProperties ( testPassword ) where import Crypto.Saltine.Core.Password import Crypto.Saltine.Internal.Util import Data.Maybe (isJust, isNothing, fromJust) import Data.Monoid import Data.Text (Text) import Test.Framework.Providers.QuickCheck2 import Test.Framework import Test.QuickCheck import qualified Crypto.Saltine.Internal.Password as I import qualified Data.Text as T instance Arbitrary Text where arbitrary = T.pack <$> arbitrary instance Arbitrary Algorithm where arbitrary = elements $ enumFromTo minBound maxBound -- | Sadly using the actual maximum limit is just way too slow instance Arbitrary Memlimit where arbitrary = I.Memlimit <$> chooseInt ( max I.pwhash_argon2i_memlimit_min I.pwhash_argon2id_memlimit_min , max I.pwhash_argon2i_memlimit_min I.pwhash_argon2id_memlimit_min * 4 ) instance Arbitrary Opslimit where arbitrary = I.Opslimit <$> chooseInt ( max I.pwhash_argon2i_opslimit_min I.pwhash_argon2id_opslimit_min , max I.pwhash_argon2i_opslimit_min I.pwhash_argon2id_opslimit_min * 4 ) instance Arbitrary Policy where arbitrary = applyArbitrary3 Policy rightInverseProp :: Text -> Policy -> IO Bool rightInverseProp pw pol = do h <- pwhashStr pw pol pure $ pwhashStrVerify (fromJust h) pw rightInverseFailureProp1 :: Text -> Policy -> Text -> IO Bool rightInverseFailureProp1 pw pol per = let npw = T.reverse pw <> T.pack "0" <> per in do h <- pwhashStr pw pol pure . not $ pwhashStrVerify (fromJust h) npw rightProp :: Text -> Policy -> IO Bool rightProp pw pol = do h <- pwhashStr pw pol pure $ Just False == needsRehash (opsPolicy pol) (memPolicy pol) (fromJust h) rightFailureProp :: Text -> Opslimit -> Opslimit -> Memlimit -> Memlimit -> IO Bool rightFailureProp pw ops1 ops2 mem1 mem2 = do h <- pwhashStr pw (Policy ops1 mem1 defaultAlgorithm) pure $ Just True == needsRehash ops2 mem2 (fromJust h) || ops1 == ops2 rightFailureProp2 :: Text -> Opslimit -> Memlimit -> Bool rightFailureProp2 invhash ops mem = isNothing $ needsRehash ops mem (I.PasswordHash invhash) rightProp2 :: Salt -> Text -> Policy -> Gen Bool rightProp2 salt pw pol = do i <- chooseInt (I.pwhash_bytes_min, 1024) pure $ isJust $ pwhash pw i salt pol testPassword :: Test testPassword = buildTest $ do salt <- newSalt return $ testGroup "... Password" [ testProperty "Can hash passwords and verify them..." $ ioProperty . uncurry rightInverseProp, testProperty "Hashed passwords cannot be verified with the wrong password..." $ ioProperty . uncurry3 rightInverseFailureProp1, testProperty "Hashed passwords do not need to be rehashed with the same policy..." $ ioProperty . uncurry rightProp, testProperty "Hashed passwords do need to be rehashed with a different policy..." $ ioProperty . uncurry5 rightFailureProp, testProperty "needsRehash detects invalid hashes..." rightFailureProp2, testProperty "Deriving a key from a password..." (rightProp2 salt) ]