packages feed

crypton-2.0.0: tests/AFISSpec.hs

{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE OverloadedStrings #-}

module AFISSpec (spec) where

import Imports

import Control.Exception (evaluate)
import qualified Crypto.Data.AFIS as AFIS
import Crypto.Error
import Crypto.Hash
import Crypto.Random
import qualified Data.ByteString as B

mergeVec :: [(Int, SHA1, B.ByteString, B.ByteString)]
mergeVec =
    [
        ( 3
        , SHA1
        , "\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02"
        , "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\xd4\x76\xc8\x58\xbd\xf0\x15\xbe\x9f\x40\xe3\x65\x20\x1c\x9c\xb8\xd8\x1c\x16\x64"
        )
    ,
        ( 3
        , SHA1
        , "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17"
        , "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\xd6\x75\xc8\x59\xbb\xf7\x11\xbb\x95\x4b\xeb\x6c\x2e\x13\x90\xb5\xca\x0f\x06\x75\x17\x70\x39\x28"
        )
    ]

mergeKATs = zipWith toProp mergeVec [(0 :: Int) ..]
  where
    toProp (nbExpands, hashAlg, expected, dat) i =
        it ("merge " ++ show i) (AFIS.merge hashAlg nbExpands dat `shouldBe` expected)

data AFISParams = AFISParams B.ByteString Int SHA1 ChaChaDRG

instance Show AFISParams where
    show (AFISParams dat expand _ _) = "data: " ++ show dat ++ " expanded: " ++ show expand

instance Arbitrary AFISParams where
    arbitrary =
        AFISParams
            <$> arbitraryBSof 3 46
            <*> choose (2, 2)
            <*> elements [SHA1]
            <*> arbitrary

instance Arbitrary ChaChaDRG where
    arbitrary = drgNewTest <$> arbitrary

-- | Parameters neither function can work with.  An expand count of zero used
-- to divide by zero in merge, a negative one reported the data as null, and an
-- expand count of one was accepted and handed the diffused data straight back
-- as though it were the secret -- which is the one that does not announce
-- itself.  split already refused all three, so it had nothing to say about a
-- secret of no bytes, which it split into nothing that merge then refused.
invalidParameterTests :: Spec
invalidParameterTests =
    describe "invalid parameters" $ do
        it "merge refuses an expand count of zero" $
            evaluate (tryMerge 0 diffused) `shouldThrow` refused
        it "merge refuses a negative expand count" $
            evaluate (tryMerge (-1) diffused) `shouldThrow` refused
        it "merge refuses an expand count of one" $
            evaluate (tryMerge 1 diffused) `shouldThrow` refused
        it "merge refuses data that is not a multiple of the expand count" $
            evaluate (tryMerge 3 diffused) `shouldThrow` refused
        it "merge refuses empty data" $
            evaluate (tryMerge 4 B.empty) `shouldThrow` refused
        it "split refuses an expand count below two" $ do
            evaluate (trySplit 0 secret) `shouldThrow` refused
            evaluate (trySplit 1 secret) `shouldThrow` refused
            evaluate (trySplit (-1) secret) `shouldThrow` refused
        it "split refuses an empty secret" $
            evaluate (trySplit 4 B.empty) `shouldThrow` refused
        it "the recoverable variants report instead of raising" $ do
            AFIS.tryMerge SHA1 0 diffused `shouldBe` failed
            AFIS.tryMerge SHA1 1 diffused `shouldBe` failed
            AFIS.tryMerge SHA1 3 diffused `shouldBe` failed
            AFIS.tryMerge SHA1 4 B.empty `shouldBe` failed
            fmap fst (AFIS.trySplit SHA1 rng 1 secret) `shouldBe` failed
            fmap fst (AFIS.trySplit SHA1 rng 4 B.empty) `shouldBe` failed
        it "the recoverable variants still split and merge" $ do
            let d = fmap fst (AFIS.trySplit SHA1 rng 4 secret)
            d `shouldBe` CryptoPassed diffused
            (d >>= AFIS.tryMerge SHA1 4) `shouldBe` CryptoPassed secret
        it "a good split still merges back" $
            AFIS.merge SHA1 4 diffused `shouldBe` secret
  where
    rng = drgNewTest (1, 2, 3, 4, 5)
    secret = "0123456789abcdef0123" :: B.ByteString
    diffused = fst (AFIS.split SHA1 rng 4 secret) :: B.ByteString
    tryMerge e d = AFIS.merge SHA1 e d :: B.ByteString
    trySplit e d = fst (AFIS.split SHA1 rng e d) :: B.ByteString
    failed = CryptoFailed CryptoError_ParameterInvalid :: CryptoFailable B.ByteString
    refused e = e == CryptoError_ParameterInvalid

spec :: Spec
spec = do
    describe "KAT merge" $ sequence_ mergeKATs
    invalidParameterTests
    prop "merge.split == id" $ \(AFISParams bs e hf rng) -> bs == (AFIS.merge hf e $ fst (AFIS.split hf rng e bs))