{-# LANGUAGE OverloadedStrings #-}
module Main (main) where
import System.POSIX.Crypt
import System.POSIX.Crypt.SHA512
import Data.Monoid ((<>))
import Test.Tasty
import Test.Tasty.HUnit
import Test.Tasty.QuickCheck
import Test.QuickCheck.Instances ()
import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as BS8
main :: IO ()
main = do
let enc = crypt "pass" "$6$saltstring"
isGlibc <- if fmap BS.length enc == Just 100
then return True
else do
putStrLn $ "crypt doesn't recognize $6$ prefix in salt"
putStrLn $ "returned " ++ show enc
putStrLn $ "most likely you don't have glibc >=2.17"
putStrLn $ "we will skip some tests"
return False
defaultMain $ testGroup "Tests" $
[ sha512examples "cryptSHA512" cryptSHA512
, cryptSHA512RawExamples
] ++ glibcTests isGlibc
where
glibcTests True =
[ sha512examples "crypt" crypt
, testProperty "glibc and native impl comparison" glibcNativeProp
]
glibcTests False = []
glibcNativeProp :: Input -> Property
glibcNativeProp (Input key salt) =
crypt key salt === cryptSHA512 key salt
data Input = Input BS.ByteString BS.ByteString
deriving Show
instance Arbitrary Input where
arbitrary = do
key <- fmap BS.pack $ listOf $ elements $ BS.unpack alphabet
salt <- fmap BS.pack $ listOf $ elements $ BS.unpack alphabet
return (Input key $ "$6$" <> salt)
where
alphabet = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
sha512examples
:: String
-> (BS.ByteString -> BS.ByteString -> Maybe BS.ByteString) -- crypt function
-> TestTree
sha512examples name c = testGroup name $
[ m "$6$saltstring"
"Hello world!"
"$6$saltstring$svn8UoSVapNtMuq1ukKS4tPQd8iKwSMHWjl/O817G3uBnIFNjnQJuesI68u4OTLiBFdcbYEdFCoEOfaS35inz1"
, m "$6$rounds=10000$saltstringsaltstring"
"Hello world!"
"$6$rounds=10000$saltstringsaltst$OW1/O6BYHV6BcXZu8QVeXbDWra3Oeqh0sbHbbMCVNSnCM/UrjmM0Dp8vOuZeHBy/YTBmSK6H9qs/y3RnOaw5v."
, m "$6$rounds=5000$toolongsaltstring"
"This is just a test"
"$6$rounds=5000$toolongsaltstrin$lQ8jolhgVRVhY4b5pZKaysCLi0QBxGoNeKQzQ3glMhwllF7oGDZxUhx1yxdYcz/e1JSbq3y6JMxxl8audkUEm0"
, m "$6$rounds=1400$anotherlongsaltstring"
"a very much longer text to encrypt. This one even stretches over morethan one line."
"$6$rounds=1400$anotherlongsalts$POfYwTEok97VWcjxIiSOjiykti.o/pQs.wPvMxQ6Fm7I6IoYN3CmLs66x9t0oSwbtEW7o7UmJEiDwGqd8p4ur1"
, m "$6$rounds=77777$short"
"we have a short salt string but not a short password"
"$6$rounds=77777$short$WuQyW2YR.hBNpjjRhpYD/ifIw05xdfeEyQoMxIXbkvr0gge1a1x3yRULJ5CCaUeOxFmtlcGZelFl5CxtgfiAc0"
, m "$6$rounds=123456$asaltof16chars.."
"a short string"
"$6$rounds=123456$asaltof16chars..$BtCwjqMJGx5hrJhZywWvt0RLE8uZ4oPwcelCjmw2kSYu.Ec6ycULevoBK25fs2xXgMNrCzIMVcgEJAstJeonj1"
, m "$6$rounds=10$roundstoolow"
"the minimum number is still observed"
"$6$rounds=1000$roundstoolow$kUMsbe306n21p9R.FRkW3IGn.S9NPN0x50YhH1xhLsPuWGsUSklZt58jaTfF4ZEQpyUNGc0dqbpBYYBaHHrsX."
-- regressions
, m "$6$saltstring"
"xXHJ"
"$6$saltstring$RiyF6qtDbUHgB5fPL.afjplfUYs7h6e8AC8OGC1IZ0ZzQQuh6BWLwaGzlxnl2cSJ8kQvEGUxj4mwH/DosJPzx/"
, m "$6$saltstring"
"xyny33AP6"
"$6$saltstring$Ev29zY/SO64MAYRTBEO3wrxXeeGGNxagYNWDK3Mh2Zucgx3d1Y7/i4tWvHd86WIDLH2iLaZByx3GRvaikhBpb."
, m "$6$saltstring"
"xyny33AP6!"
"$6$saltstring$nsREMPgSFfMi0atBqyK8d78dtjt2IdZP4ih2hE38sTshzqC8D5E5wFBUxT9eN/gEos64NxxhUQzDaAyqogHUL0"
]
where
m salt key expected = testCase (BS8.unpack salt) $ do
c key salt @?= Just expected
cryptSHA512RawExamples :: TestTree
cryptSHA512RawExamples = testGroup "cryptSHA512Raw"
[ m Nothing "saltstring"
"Hello world!"
"$6$saltstring$svn8UoSVapNtMuq1ukKS4tPQd8iKwSMHWjl/O817G3uBnIFNjnQJuesI68u4OTLiBFdcbYEdFCoEOfaS35inz1"
, m (Just 10000) "saltstringsaltstring"
"Hello world!"
"$6$rounds=10000$saltstringsaltst$OW1/O6BYHV6BcXZu8QVeXbDWra3Oeqh0sbHbbMCVNSnCM/UrjmM0Dp8vOuZeHBy/YTBmSK6H9qs/y3RnOaw5v."
, m (Just 5000) "toolongsaltstring"
"This is just a test"
"$6$rounds=5000$toolongsaltstrin$lQ8jolhgVRVhY4b5pZKaysCLi0QBxGoNeKQzQ3glMhwllF7oGDZxUhx1yxdYcz/e1JSbq3y6JMxxl8audkUEm0"
, m (Just 1400) "anotherlongsaltstring"
"a very much longer text to encrypt. This one even stretches over morethan one line."
"$6$rounds=1400$anotherlongsalts$POfYwTEok97VWcjxIiSOjiykti.o/pQs.wPvMxQ6Fm7I6IoYN3CmLs66x9t0oSwbtEW7o7UmJEiDwGqd8p4ur1"
, m (Just 77777) "short"
"we have a short salt string but not a short password"
"$6$rounds=77777$short$WuQyW2YR.hBNpjjRhpYD/ifIw05xdfeEyQoMxIXbkvr0gge1a1x3yRULJ5CCaUeOxFmtlcGZelFl5CxtgfiAc0"
, m (Just 123456) "asaltof16chars.."
"a short string"
"$6$rounds=123456$asaltof16chars..$BtCwjqMJGx5hrJhZywWvt0RLE8uZ4oPwcelCjmw2kSYu.Ec6ycULevoBK25fs2xXgMNrCzIMVcgEJAstJeonj1"
, m (Just 10) "roundstoolow"
"the minimum number is still observed"
"$6$rounds=1000$roundstoolow$kUMsbe306n21p9R.FRkW3IGn.S9NPN0x50YhH1xhLsPuWGsUSklZt58jaTfF4ZEQpyUNGc0dqbpBYYBaHHrsX."
]
where
m rounds salt key expected = testCase (BS8.unpack salt ++ " " ++ show rounds) $ do
cryptSHA512Raw rounds key salt @?= expected