{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes       #-}
{- HLINT ignore "Reduce duplication" -}
module Spec.Crypto (specs) where

import           Data.ByteString    (ByteString)
import           Data.List.NonEmpty (NonEmpty ((:|)))
import           Test.Tasty
import           Test.Tasty.HUnit

import           Auth.Biscuit
import qualified Auth.Biscuit.Sel   as Sel
import           Auth.Biscuit.Token (Biscuit (..))

specs :: TestTree
specs = testGroup "biscuit crypto"
  [ testGroup "signature algorithm"
      [ singleBlockRoundtrip
      , multiBlockRoundtrip
      , tamperedAuthority
      , tamperedBlock
      ]
  , testGroup "high-level functions"
      [ singleBlockRoundtrip'
      , multiBlockRoundtrip'
      , tamperedAuthority'
      , tamperedBlock'
      ]
  ]

singleBlockRoundtrip :: TestTree
singleBlockRoundtrip = testCase "Single block roundtrip" $ do
  rootKp <- newKeypair
  let pub = publicKey rootKp
      content = "content"
      token = (pub, content) :| []
  sig <- Sel.signBlock rootKp content
  result <- Sel.verifySignature token sig
  result @?= True

multiBlockRoundtrip :: TestTree
multiBlockRoundtrip = testCase "Multi block roundtrip" $ do
  kp' <- newKeypair
  kp <- newKeypair
  let pub = publicKey kp
      pub' = publicKey kp'
      content = "content"
      content' = "block"
      token = (pub, content) :| [(pub', content')]
  sig    <- Sel.signBlock kp content
  sig'   <- Sel.aggregate sig =<< Sel.signBlock kp' content'
  result <- Sel.verifySignature token sig'
  result @?= True

tamperedAuthority :: TestTree
tamperedAuthority = testCase "Tampered authority" $ do
  kp' <- newKeypair
  kp <- newKeypair
  let pub = publicKey kp
      pub' = publicKey kp'
      content = "content"
      content' = "block"
      token  = (pub, "modified") :| []
      token' = (pub, "modified") :| [(pub', content')]
  sig    <- Sel.signBlock kp content
  sig'   <- Sel.aggregate sig =<< Sel.signBlock kp' content'
  result <- Sel.verifySignature token sig'
  result @?= False
  result' <- Sel.verifySignature token' sig'
  result' @?= False

tamperedBlock :: TestTree
tamperedBlock = testCase "Tampered block" $ do
  kp' <- newKeypair
  kp <- newKeypair
  let pub = publicKey kp
      pub' = publicKey kp'
      content = "content"
      content' = "block"
      token = (pub, content) :| [(pub', "modified")]
  sig    <- Sel.signBlock kp content
  sig'   <- Sel.aggregate sig =<< Sel.signBlock kp' content'
  result <- Sel.verifySignature token sig'
  result @?= False

singleBlockRoundtrip' :: TestTree
singleBlockRoundtrip' = testCase "Single block roundtrip" $ do
  rootKp <- newKeypair
  let pub = publicKey rootKp
  b <- mkBiscuit rootKp [block|right(#authority,#read);|]
  result <- checkBiscuitSignature b pub
  result @?= True

multiBlockRoundtrip' :: TestTree
multiBlockRoundtrip' = testCase "Multi block roundtrip" $ do
  kp <- newKeypair
  let pub = publicKey kp
  b <- mkBiscuit kp [block|right(#authority,#read);|]
  b' <- addBlock [block|check if true;|] b
  result <- checkBiscuitSignature b' pub
  result @?= True

tamper :: (PublicKey, (ByteString, Block))
       -> (PublicKey, (ByteString, Block))
tamper (pk, (_, b)) = (pk, ("tampered", b))

tamperedAuthority' :: TestTree
tamperedAuthority' = testCase "Tampered authority" $ do
  kp <- newKeypair
  let pub = publicKey kp
  b <- mkBiscuit kp [block|right(#authority,#read);|]
  b' <- addBlock [block|check if true;|] b
  let modified = b'
        { authority = tamper $ authority b
        }
  result <- checkBiscuitSignature modified pub
  result @?= False

tamperedBlock' :: TestTree
tamperedBlock' = testCase "Tampered block" $ do
  kp <- newKeypair
  let pub = publicKey kp
  b <- mkBiscuit kp [block|right(#authority,#read);|]
  b' <- addBlock [block|check if true;|] b
  let modified = b'
        { blocks = tamper <$> blocks b
        }
  result <- checkBiscuitSignature modified pub
  result @?= False