packages feed

crypt-sha512-0: test/Example.hs

{-# 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