biscuit-haskell-0.2.0.0: test/Spec/NewCrypto.hs
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{- HLINT ignore "Reduce duplication" -}
module Spec.NewCrypto (specs) where
import Data.ByteString (ByteString)
import Data.List.NonEmpty (NonEmpty ((:|)))
import qualified Data.List.NonEmpty as NE
import Data.Maybe (isJust)
import Test.Tasty
import Test.Tasty.HUnit
import Auth.Biscuit.Crypto
import Crypto.PubKey.Ed25519
-- This test module is only there to test the crypto layer of biscuits,
-- so we define a custom token type that only cares about the envelope,
-- not the actual payload
data Token = Token
{ payload :: Blocks
, privKey :: SecretKey
}
data SealedToken = SealedToken
{ payload :: Blocks
, sig :: Signature
}
signToken :: ByteString -> SecretKey -> IO Token
signToken p sk = do
(signedBlock, privKey) <- signBlock sk p
pure Token
{ payload = pure signedBlock
, privKey
}
snocNE :: NonEmpty a -> a -> NonEmpty a
snocNE (h :| t) e = h :| (t <> [e])
append :: Token -> ByteString -> IO Token
append t@Token{payload} p = do
(signedBlock, privKey) <- signBlock (privKey t) p
pure Token
{ payload = snocNE payload signedBlock
, privKey
}
seal :: Token -> SealedToken
seal Token{payload,privKey} =
let lastBlock = NE.last payload
in SealedToken
{ sig = getSignatureProof lastBlock privKey
, payload
}
verifyToken :: Token
-> PublicKey
-> Bool
verifyToken Token{payload, privKey} rootPk =
let blocks = payload
sigChecks = verifyBlocks blocks rootPk
lastCheck = verifySecretProof privKey (NE.last payload)
in sigChecks && lastCheck
verifySealedToken :: SealedToken
-> PublicKey
-> Bool
verifySealedToken SealedToken{payload, sig} rootPk =
let blocks = payload
sigChecks = verifyBlocks blocks rootPk
lastCheck = verifySignatureProof sig (NE.last payload)
in sigChecks && lastCheck
specs :: TestTree
specs = testGroup "new biscuit crypto"
[ testGroup "signature algorithm - normal"
[ singleBlockRoundtrip
, multiBlockRoundtrip
, tamperedAuthority
, tamperedBlock
, removedBlock
]
, testGroup "signature algorithm - sealed"
[ singleBlockRoundtripSealed
, multiBlockRoundtripSealed
, tamperedAuthoritySealed
, tamperedBlockSealed
, removedBlockSealed
]
]
singleBlockRoundtrip :: TestTree
singleBlockRoundtrip = testCase "Single block roundtrip" $ do
sk <- generateSecretKey
let pk = toPublic sk
content = "content"
token <- signToken content sk
let res = verifyToken token pk
res @?= True
multiBlockRoundtrip :: TestTree
multiBlockRoundtrip = testCase "Multi block roundtrip" $ do
sk <- generateSecretKey
let pk = toPublic sk
content = "content"
token <- signToken content sk
attenuated <- append token "block1"
let res = verifyToken attenuated pk
res @?= True
alterPayload :: (Blocks -> Blocks)
-> Token
-> Token
alterPayload f Token{..} = Token { payload = f payload, ..}
tamperedAuthority :: TestTree
tamperedAuthority = testCase "Tampered authority" $ do
sk <- generateSecretKey
let pk = toPublic sk
content = "content"
token <- signToken content sk
attenuated <- append token "block1"
let tamper ((_, s, pk) :| o) = ("tampered", s, pk) :| o
tampered = alterPayload tamper attenuated
let res = verifyToken tampered pk
res @?= False
tamperedBlock :: TestTree
tamperedBlock = testCase "Tampered block" $ do
sk <- generateSecretKey
let pk = toPublic sk
content = "content"
token <- signToken content sk
attenuated <- append token "block1"
let tamper (h :| ((_, s, pk): t)) = h :| (("tampered", s, pk) : t)
tampered = alterPayload tamper attenuated
let res = verifyToken tampered pk
res @?= False
removedBlock :: TestTree
removedBlock = testCase "Removed block" $ do
sk <- generateSecretKey
let pk = toPublic sk
content = "content"
token <- signToken content sk
attenuated <- append token "block1"
let tamper (h :| _) = h :| []
tampered = alterPayload tamper attenuated
let res = verifyToken tampered pk
res @?= False
singleBlockRoundtripSealed :: TestTree
singleBlockRoundtripSealed = testCase "Single block roundtrip" $ do
sk <- generateSecretKey
let pk = toPublic sk
content = "content"
token <- seal <$> signToken content sk
let res = verifySealedToken token pk
res @?= True
multiBlockRoundtripSealed :: TestTree
multiBlockRoundtripSealed = testCase "Multi block roundtrip" $ do
sk <- generateSecretKey
let pk = toPublic sk
content = "content"
token <- signToken content sk
attenuated <- seal <$> append token "block1"
let res = verifySealedToken attenuated pk
res @?= True
alterPayloadSealed :: (Blocks -> Blocks)
-> SealedToken
-> SealedToken
alterPayloadSealed f SealedToken{..} = SealedToken { payload = f payload, ..}
tamperedAuthoritySealed :: TestTree
tamperedAuthoritySealed = testCase "Tampered authority" $ do
sk <- generateSecretKey
let pk = toPublic sk
content = "content"
token <- signToken content sk
attenuated <- seal <$> append token "block1"
let tamper ((_, s, pk) :| o) = ("tampered", s, pk) :| o
tampered = alterPayloadSealed tamper attenuated
let res = verifySealedToken tampered pk
res @?= False
tamperedBlockSealed :: TestTree
tamperedBlockSealed = testCase "Tampered block" $ do
sk <- generateSecretKey
let pk = toPublic sk
content = "content"
token <- signToken content sk
attenuated <- seal <$> append token "block1"
let tamper (h :| ((_, s, pk): t)) = h :| (("tampered", s, pk) : t)
tampered = alterPayloadSealed tamper attenuated
let res = verifySealedToken tampered pk
res @?= False
removedBlockSealed :: TestTree
removedBlockSealed = testCase "Removed block" $ do
sk <- generateSecretKey
let pk = toPublic sk
content = "content"
token <- signToken content sk
attenuated <- seal <$> append token "block1"
let tamper (h :| _) = h :| []
tampered = alterPayloadSealed tamper attenuated
let res = verifySealedToken tampered pk
res @?= False