{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}

module SecretBoxProperties (
  testSecretBox
  ) where

import           Util
import           Crypto.Saltine.Core.SecretBox
import           Crypto.Saltine.Class
import           Crypto.Saltine.Internal.SecretBox as Internal

import qualified Data.ByteString                      as S
import           Data.Maybe (fromJust)
import           Test.Framework.Providers.QuickCheck2
import           Test.Framework
import           Test.QuickCheck (Property, (==>))
import           Test.QuickCheck.Arbitrary

instance Arbitrary Nonce where
    arbitrary =
        do bs <- S.pack <$> vector Internal.secretbox_noncebytes
           pure $ fromJust  (decode bs)

instance Arbitrary Key where
    arbitrary =
        do bs <- S.pack <$> vector Internal.secretbox_keybytes
           pure $ fromJust (decode bs)

-- | Ciphertext can be decrypted
rightInverseProp :: Key -> Nonce -> Message -> Bool
rightInverseProp k n (Message bs) =
  Just bs == secretboxOpen k n (secretbox k n bs)

-- | Detached ciphertext/tag can be decrypted
rightInverseDetachedProp :: Key -> Nonce -> Message -> Bool
rightInverseDetachedProp k n (Message bs) =
  Just bs == uncurry (secretboxOpenDetached k n) (secretboxDetached k n bs)

-- | Ciphertext cannot be decrypted if the ciphertext is perturbed
rightInverseFailureProp :: Key -> Nonce -> Message -> Perturb -> Property
rightInverseFailureProp k n (Message bs) p =
  let ct     = secretbox k n bs
      fakeCT = perturb ct p
  in ct /= fakeCT ==> Nothing == secretboxOpen k n fakeCT

-- | Ciphertext cannot be decrypted if the tag is perturbed
rightInverseTagFailureProp :: Key -> Nonce -> Message -> Message -> Property
rightInverseTagFailureProp k n (Message bs) (Message fakeTagBs) =
  let (realTag, ct) = secretboxDetached k n bs
      fakeTag       = Internal.Au fakeTagBs
  in realTag /= fakeTag ==> Nothing == secretboxOpenDetached k n fakeTag ct

-- | Ciphertext cannot be decrypted if the ciphertext is perturbed
rightInverseFailureDetachedProp :: Key -> Nonce -> Message -> Perturb -> Property
rightInverseFailureDetachedProp k n (Message bs) p =
  let (tag,ct) = secretboxDetached k n bs
      fakeCT = perturb ct p
  in fakeCT /= ct ==> Nothing == secretboxOpenDetached k n tag fakeCT

-- | Ciphertext cannot be decrypted with a different key
cannotDecryptKeyProp :: Key -> Key -> Nonce -> Message -> Property
cannotDecryptKeyProp k1 k2 n (Message bs) =
  k1 /= k2 ==> Nothing == secretboxOpen k2 n (secretbox k1 n bs)

-- | Ciphertext cannot be decrypted with a different key
cannotDecryptKeyDetachedProp :: Key -> Key -> Nonce -> Message -> Property
cannotDecryptKeyDetachedProp k1 k2 n (Message bs) =
  k1 /= k2 ==> Nothing == uncurry (secretboxOpenDetached k2 n) (secretboxDetached k1 n bs)

-- | Ciphertext cannot be decrypted with a different nonce
cannotDecryptNonceProp :: Key -> Nonce -> Nonce -> Message -> Property
cannotDecryptNonceProp k n1 n2 (Message bs) =
  n1 /= n2 ==> Nothing == secretboxOpen k n2 (secretbox k n1 bs)

-- | Ciphertext cannot be decrypted with a different nonce
cannotDecryptNonceDetachedProp :: Key -> Nonce -> Nonce -> Message -> Property
cannotDecryptNonceDetachedProp k n1 n2 (Message bs) =
  n1 /= n2 ==> Nothing == uncurry (secretboxOpenDetached k n2) (secretboxDetached k n1 bs)

testSecretBox :: Test
testSecretBox = buildTest $ do

  return $ testGroup "...Internal.SecretBox" [

    testProperty "Can decrypt ciphertext"
    $ rightInverseProp,

    testProperty "Can decrypt ciphertext (detached)"
    $ rightInverseDetachedProp,

    testGroup "Cannot decrypt ciphertext when..." [

      testProperty "... ciphertext is perturbed"
      $ rightInverseFailureProp,

      testProperty "... ciphertext is perturbed (detached)"
      $ rightInverseFailureDetachedProp,

      testProperty "... tag is perturbed (detached)"
      $ rightInverseTagFailureProp,

      testProperty "... using the wrong key"
      $ cannotDecryptKeyProp,

      testProperty "... using the wrong key (detached)"
      $ cannotDecryptKeyDetachedProp,

      testProperty "... using the wrong nonce"
      $ cannotDecryptNonceProp,

      testProperty "... using the wrong nonce (detached"
      $ cannotDecryptNonceDetachedProp

      ]
    ]
