packages feed

sbv-14.8: SBVTestSuite/TestSuite/Crypto/AES.hs

-----------------------------------------------------------------------------
-- |
-- Module    : TestSuite.Crypto.AES
-- Copyright : (c) Levent Erkok
-- License   : BSD3
-- Maintainer: erkokl@gmail.com
-- Stability : experimental
--
-- Test suite for Documentation.SBV.Examples.Crypto.AES
-----------------------------------------------------------------------------

{-# OPTIONS_GHC -Wall -Werror #-}

{-# LANGUAGE DataKinds #-}

module TestSuite.Crypto.AES(tests) where

import Data.List (intercalate)
import System.Exit (ExitCode(..))
import System.FilePath ((</>))
import System.IO.Temp (withSystemTempDirectory)
import System.Process (readProcessWithExitCode)
import Test.Tasty.HUnit (assertEqual)

import Data.SBV.Internals
import Data.SBV.Tools.CodeGen (compileToC, compileToCLib)
import Documentation.SBV.Examples.Crypto.AES

import Utils.CCodeGen (generatedMakeOptions)
import Utils.SBVTestFramework

-- Test suite
tests :: TestTree
tests = testGroup "Crypto.AES" [
   goldenVsStringShow "aes128Enc" $ thd <$> compileToC'    "aes128Enc" (aes128EncDec True)
 , goldenVsStringShow "aes128Dec" $ thd <$> compileToC'    "aes128Dec" (aes128EncDec False)
 , goldenVsStringShow "aes128Lib" $ thd <$> compileToCLib' "aes128Lib" aes128Comps
 , testCase "generated standalone encryption" standaloneEncryption
 , testCase "generated AES128 library" $ libraryVector 128 aes128Key aes128CT
 , testCase "generated AES192 library" $ libraryVector 192 aes192Key aes192CT
 , testCase "generated AES256 library" $ libraryVector 256 aes256Key aes256CT
 ]
 where aes128EncDec d = do pt  <- cgInputArr 4 "pt"
                           key <- cgInputArr 4 "key"
                           cgSetDriverValues $ repeat 0
                           let (encKs, decKs) = aesKeySchedule key
                               res | d    = aesEncrypt pt encKs
                                   | True = aesDecrypt pt decKs
                           cgOutputArr "ct" res
       aes128Comps = [(f, setVals c) | (f, _, c) <- aesLibComponents 128]
       setVals c = cgSetDriverValues (repeat 0) >> c
       thd (_, _, r) = r

-- | Exercise the exact code-generation action shown in the documentation.
standaloneEncryption :: Assertion
standaloneEncryption = withSystemTempDirectory "sbv-aes-block" $ \dir -> do
  compileToC (Just dir) "aes128BlockEncrypt" (aes128BlockEncrypt >> cgOverwriteFiles True)
  runCaller dir "aes128BlockEncrypt" "aes128BlockEncrypt.o"
    [ cArray "pt" commonPT
    , cArray "key" aes128Key
    , cArray "expected" aes128CT
    , "SWord32 ct[4];"
    , "aes128BlockEncrypt(pt, key, ct);"
    , "assert(memcmp(ct, expected, sizeof expected) == 0);"
    ]

-- | Compose the generated key-schedule and block functions in C, checking
-- encryption and both decryption paths against the standard's test vectors.
-- Using an independent caller ensures that the key schedules are computed
-- by generated C, rather than supplied as precomputed Haskell driver values.
libraryVector :: Int -> Key -> [SWord 32] -> Assertion
libraryVector size key ciphertext = withSystemTempDirectory "sbv-aes-library" $ \dir -> do
  _ <- compileToCLib (Just dir) libraryName
         [(functionName, cgSetDriverValues values >> program >> cgOverwriteFiles True)
         | (functionName, values, program) <- aesLibComponents size]
  runCaller dir libraryName (libraryName ++ ".a")
    [ cArray "pt" commonPT
    , cArray "key" key
    , cArray "expected" ciphertext
    -- The public inverse-schedule wrapper reverses each group of key words.
    , cArray "invKey" (concatMap reverse (chop4 (extractFinalKey key)))
    , "SWord32 encKS[" ++ show expandedWords ++ "], decKS[" ++ show expandedWords ++ "], invKS[" ++ show expandedWords ++ "];"
    , "SWord32 ct[4], recovered[4];"
    , prefix ++ "KeySchedule(key, encKS, decKS);"
    , prefix ++ "BlockEncrypt(pt, encKS, ct);"
    , "assert(memcmp(ct, expected, sizeof expected) == 0);"
    , prefix ++ "BlockDecrypt(expected, decKS, recovered);"
    , "assert(memcmp(recovered, pt, sizeof pt) == 0);"
    , prefix ++ "InvKeySchedule(invKey, invKS);"
    , prefix ++ "OTFDecrypt(expected, invKS, recovered);"
    , "assert(memcmp(recovered, pt, sizeof pt) == 0);"
    ]
 where prefix = "aes" ++ show size
       libraryName = prefix ++ "Lib"
       expandedWords = 4 * (size `div` 32 + 7)

-- | Render the existing concrete test vectors as C arrays.
cArray :: String -> [SWord 32] -> String
cArray arrayName values = "const SWord32 " ++ arrayName ++ "[] = {"
                       ++ intercalate ", " ["0x" ++ hex8 value ++ "UL" | value <- values] ++ "};"

-- | Build and run a caller using the generated Makefile's compiler and flags.
-- Keep assertions enabled, and preserve optional sanitizer instrumentation.
runCaller :: FilePath -> String -> FilePath -> [String] -> Assertion
runCaller dir header dependency body = do
  writeFile (dir </> "caller.c") $ unlines $
    [ "#undef NDEBUG"
    , "#include <assert.h>"
    , "#include <string.h>"
    , "#include \"" ++ header ++ ".h\""
    , "int main(void) {"
    ] ++ map ("  " ++) body ++ ["  return 0;", "}"]
  writeFile (dir </> "caller.mk") $ unlines
    [ "caller: caller.c " ++ header ++ ".h " ++ dependency
    , "\t${CC} ${CCFLAGS} caller.c " ++ dependency ++ " ${LDFLAGS} ${SBV_LIBS} -o $@"
    ]
  makeOptions <- generatedMakeOptions dir
  (buildExit, buildOutput, buildError) <- readProcessWithExitCode "make"
    (["-C", dir, "caller"] ++ makeOptions) ""
  assertEqual (buildOutput ++ buildError) ExitSuccess buildExit
  (runExit, runOutput, runError) <- readProcessWithExitCode (dir </> "caller") [] ""
  assertEqual (runOutput ++ runError) ExitSuccess runExit