botan-low-0.1.0.0: test/Test/Botan/Low/Cipher.hs
{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_GHC -Wno-deprecations #-}
module Test.Botan.Low.Cipher (tests) where
import Botan.Bindings.Version (botan_version_major,
botan_version_minor)
import Botan.Low.Cipher
import Botan.Low.RNG
import Control.Monad
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import System.IO.Unsafe (unsafePerformIO)
import Test.Hspec
import Test.Tasty
import Test.Tasty.Hspec
import Test.Util.ByteString
import Test.Util.Hspec
tests :: IO TestTree
tests = do
specs <- testSpec "spec_cipher" spec_cipher
pure $ testGroup "Test.Botan.Low.Cipher" [
specs
]
testModes :: [ByteString]
testModes = filter p (cipherModes ++ aeads)
where
p s
-- TODO: also test "Lion" and "Cascade"
| "Lion" `BS.isPrefixOf` s || "Cascade" `BS.isPrefixOf` s
= False
-- SIV and CCM have bugs on versions earlier than 3.5
| unsafePerformIO botan_version_major == 3
, unsafePerformIO botan_version_minor <= 4
, "SIV" `BS.isSuffixOf` s || "CCM" `BS.isSuffixOf` s
= False
| otherwise
= True
spec_cipher :: Spec
spec_cipher = testSuite testModes chars $ \ cipher -> do
it "can initialize a cipher encryption context" $ do
_ctx <- cipherInit cipher CipherEncrypt
pass
it "can initialize a cipher decryption context" $ do
_ctx <- cipherInit cipher CipherDecrypt
pass
it "has a name" $ do
ctx <- cipherInit cipher CipherEncrypt
_name <- cipherName ctx
-- name `shouldBe` bc -- Name expands to include default parameters - need to record
pass
it "has a key spec" $ do
ctx <- cipherInit cipher CipherEncrypt
(_,_,_) <- cipherGetKeyspec ctx
pass
it "can set the key" $ do
ctx <- cipherInit cipher CipherEncrypt
(_,mx,_) <- cipherGetKeyspec ctx
k <- systemRNGGet mx
cipherSetKey ctx k
pass
it "has a valid default nonce length" $ do
ctx <- cipherInit cipher CipherEncrypt
_nlen <- cipherGetDefaultNonceLength ctx
pass
it "can validate nonce length" $ do
ctx <- cipherInit cipher CipherEncrypt
nlen <- cipherGetDefaultNonceLength ctx
defaultIsValid <- cipherValidNonceLength ctx nlen
defaultIsValid `shouldBe` True
-- NOTE: Some ciphers accept nonces of any length, eg SIV
-- Others allow any length > 0, eg GCM
-- Some have ranges
-- Some have just 'if (length == 0) return false;' eg OCB
-- Search C++ source for 'valid_nonce_length' to find them all
-- zeroIsValid <- cipherValidNonceLength ctx 0
-- zeroIsValid `shouldBe` False
-- absurdlyLargeIsValid <- cipherValidNonceLength ctx maxBound
-- absurdlyLargeIsValid `shouldBe` False
pass
it "can calculate output length" $ do
ctx <- cipherInit cipher CipherEncrypt
_olen <- cipherOutputLength ctx 1024
pass
-- NOTE: This check should be ae / aead only
it "has a tag length" $ do
ctx <- cipherInit cipher CipherEncrypt
_ <- cipherGetTagLength ctx
pass
it "has an update graularity" $ do
ctx <- cipherInit cipher CipherEncrypt
_ <- cipherGetUpdateGranularity ctx
pass
it "has an ideal update granularity" $ do
ctx <- cipherInit cipher CipherEncrypt
_ <- cipherGetIdealUpdateGranularity ctx
pass
if cipher `elem` aeads
then it "can set the associated data" $ do
ctx <- cipherInit cipher CipherEncrypt
-- NOTE: Undocumented: A cipher must set the key before setting AD
(_,mx,_) <- cipherGetKeyspec ctx
k <- systemRNGGet mx
cipherSetKey ctx k
-- END NOTE
ad <- systemRNGGet 64
cipherSetAssociatedData ctx ad
pass
else pass
it "can reset all message state" $ do
ctx <- cipherInit cipher CipherEncrypt
cipherReset ctx
pass
it "can clear all internal state" $ do
ctx <- cipherInit cipher CipherEncrypt
cipherClear ctx
pass
it "can start processing a message" $ do
ctx <- cipherInit cipher CipherEncrypt
(_,mx,_) <- cipherGetKeyspec ctx
k <- systemRNGGet mx
cipherSetKey ctx k
if cipher `elem` aeads
then do
ad <- systemRNGGet 64
cipherSetAssociatedData ctx ad
else pass
n <- systemRNGGet =<< cipherGetDefaultNonceLength ctx
cipherStart ctx n
pass
-- TODO: More extensive testing in Botan; these are just binding sanity checks
it "can one-shot / offline encipher a message" $ do
ctx <- cipherInit cipher CipherEncrypt
(_,mx,_) <- cipherGetKeyspec ctx
k <- systemRNGGet mx
cipherSetKey ctx k
if cipher `elem` aeads
then do
ad <- systemRNGGet 64
cipherSetAssociatedData ctx ad
else pass
n <- systemRNGGet =<< cipherGetDefaultNonceLength ctx
cipherStart ctx n
g <- cipherGetIdealUpdateGranularity ctx
msg <- systemRNGGet g
_encmsg <- cipherEncrypt ctx msg
pass
it "can one-shot / offline decipher a message" $ do
-- Same as prior test
ctx <- cipherInit cipher CipherEncrypt
(_,mx,_) <- cipherGetKeyspec ctx
k <- systemRNGGet mx
cipherSetKey ctx k
ad <- systemRNGGet 64
if cipher `elem` aeads
then do
cipherSetAssociatedData ctx ad
else pass
n <- systemRNGGet =<< cipherGetDefaultNonceLength ctx
cipherStart ctx n
g <- cipherGetIdealUpdateGranularity ctx
msg <- systemRNGGet g
encmsg <- cipherEncrypt ctx msg
-- Start actual test
dctx <- cipherInit cipher CipherDecrypt
cipherSetKey dctx k
if cipher `elem` aeads
then do
cipherSetAssociatedData dctx ad
else pass
cipherStart dctx n
decmsg <- cipherDecrypt dctx encmsg
msg `shouldBe` decmsg
pass
it "can incrementally / online encipher a message" $ do
-- Same as prior test
ctx <- cipherInit cipher CipherEncrypt
(_,mx,_) <- cipherGetKeyspec ctx
k <- systemRNGGet mx
cipherSetKey ctx k
ad <- systemRNGGet 64
if cipher `elem` aeads
then do
cipherSetAssociatedData ctx ad
else pass
n <- systemRNGGet =<< cipherGetDefaultNonceLength ctx
cipherStart ctx n
g <- cipherGetIdealUpdateGranularity ctx
msg <- systemRNGGet (8 * g)
_encmsg <- cipherEncryptOnline ctx msg
pass
-- NOTE: Fails for SIV, CCM because they are offline-only algorithms
-- This is expected, but not reflected in the tests yet
it "can incrementally / online decipher a message" $ do
ctx <- cipherInit cipher CipherEncrypt
(_,mx,_) <- cipherGetKeyspec ctx
k <- systemRNGGet mx
cipherSetKey ctx k
ad <- systemRNGGet 64
if cipher `elem` aeads
then do
cipherSetAssociatedData ctx ad
else pass
n <- systemRNGGet =<< cipherGetDefaultNonceLength ctx
cipherStart ctx n
g <- cipherGetIdealUpdateGranularity ctx
msg <- systemRNGGet (8 * g)
encmsg <- cipherEncryptOnline ctx msg
-- Start actual test
dctx <- cipherInit cipher CipherDecrypt
cipherSetKey dctx k
if cipher `elem` aeads
then do
cipherSetAssociatedData dctx ad
else pass
cipherStart dctx n
decmsg <- cipherDecryptOnline dctx encmsg
msg `shouldBe` decmsg
pass
-- NOTE: Fails for SIV, CCM because they are offline-only algorithms
-- This is expected, but not reflected in the tests yet
it "has parity between online and offline" $ do
onlinectx <- cipherInit cipher CipherEncrypt
offlinectx <- cipherInit cipher CipherEncrypt
(_,mx,_) <- cipherGetKeyspec onlinectx
k <- systemRNGGet mx
cipherSetKey onlinectx k
cipherSetKey offlinectx k
ad <- systemRNGGet 64
when (cipher `elem` aeads) $ do
cipherSetAssociatedData onlinectx ad
cipherSetAssociatedData offlinectx ad
n <- systemRNGGet =<< cipherGetDefaultNonceLength onlinectx
cipherStart onlinectx n
cipherStart offlinectx n
g <- cipherGetIdealUpdateGranularity onlinectx
msg <- systemRNGGet (8 * g)
onlinemsg <- cipherEncryptOnline onlinectx msg
offlinemsg <- cipherEncrypt offlinectx msg
onlinemsg `shouldBe` offlinemsg
pass