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

module AEAD.ChaCha20Poly1305IETFProperties (
  testAEADIETF
  ) where

import           Util
import           Crypto.Saltine.Core.AEAD.ChaCha20Poly1305IETF
import           Crypto.Saltine.Class (decode)
import           Crypto.Saltine.Internal.AEAD.ChaCha20Poly1305IETF as Bytes

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 Bytes.aead_chacha20poly1305_ietf_npubbytes
           pure $ fromJust (decode bs)

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

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

-- | Detached ciphertext/tag can be decrypted
rightInverseDetachedProp :: Key -> Nonce -> Message -> Message -> Bool
rightInverseDetachedProp k n (Message bs) (Message aad) =
  let (tag,ct) = aeadDetached k n bs aad
  in Just bs == aeadOpenDetached k n tag ct aad

-- | Ciphertext cannot be decrypted if the ciphertext is perturbed
rightInverseFailureProp :: Key -> Nonce -> Message -> Message -> Perturb -> Property
rightInverseFailureProp k n (Message bs) (Message aad) p =
  S.length bs /= 0 ==>
   let ct = aead k n bs aad
       fakeCT = perturb ct p
   in fakeCT /= ct ==> Nothing == aeadOpen k n fakeCT aad

-- | Ciphertext cannot be decrypted if the aad is perturbed
rightInverseAADFailureProp :: Key -> Nonce -> Message -> Message -> Message -> Property
rightInverseAADFailureProp k n (Message bs) (Message aad) (Message aad2) =
  aad /= aad2 ==> Nothing == aeadOpen k n (aead k n bs aad) aad2

-- | Ciphertext cannot be decrypted if the tag is perturbed
rightInverseTagFailureProp :: Key -> Nonce -> Message -> Message -> Message -> Property
rightInverseTagFailureProp k n (Message bs) (Message aad) (Message newTag) =
   let (tag,ct) = aeadDetached k n bs aad
   in newTag /= tag ==> Nothing == aeadOpenDetached k n newTag ct aad

-- | Ciphertext cannot be decrypted if the ciphertext is perturbed
rightInverseFailureDetachedProp :: Key -> Nonce -> Message -> Message -> Perturb -> Property
rightInverseFailureDetachedProp k n (Message bs) (Message aad) p@(Perturb pBytes) =
  let (tag,ct) = aeadDetached k n bs aad
  in S.length bs > length pBytes ==>
        Nothing == aeadOpenDetached k n tag (perturb ct p) aad

-- | Ciphertext cannot be decrypted with a different key
cannotDecryptKeyProp :: Key -> Key -> Nonce -> Message -> Message -> Property
cannotDecryptKeyProp k1 k2 n (Message bs) (Message aad) =
  let ct = aead k1 n bs aad
  in k1 /= k2 ==> Nothing == aeadOpen k2 n ct aad

-- | Ciphertext cannot be decrypted with a different key
cannotDecryptKeyDetachedProp :: Key -> Key -> Nonce -> Message -> Message -> Property
cannotDecryptKeyDetachedProp k1 k2 n (Message bs) (Message aad) =
  let (tag,ct) = aeadDetached k1 n bs aad
  in k1 /= k2 ==> Nothing == aeadOpenDetached k2 n tag ct aad

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

-- | Ciphertext cannot be decrypted with a different nonce
cannotDecryptNonceDetachedProp :: Key -> Nonce -> Nonce -> Message -> Message -> Property
cannotDecryptNonceDetachedProp k n1 n2 (Message bs) (Message aad) =
  let (tag,ct) = aeadDetached k n1 bs aad
  in n1 /= n2 ==> Nothing == aeadOpenDetached k n2 tag ct aad

testAEADIETF :: Test
testAEADIETF = buildTest $ do

  return $ testGroup "...Internal.AEAD.ChaCha20Poly1305IETF" [

    testProperty "Can decrypt ciphertext"
    $ rightInverseProp,

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

    testGroup "Cannot decrypt ciphertext when..." [

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

      testProperty "... AAD is perturbed"
      $ rightInverseAADFailureProp,

      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

      ]
    ]
