packages feed

amazonka-s3-encryption-2.0: test/Test/Amazonka/S3/Encryption/Envelope.hs

{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}

module Test.Amazonka.S3.Encryption.Envelope (envelopeTests) where

import Amazonka.Core
import Amazonka.S3.Encryption.Envelope
import Amazonka.S3.Encryption.Types
import Control.Monad.Trans.Resource
import Crypto.Cipher.AES
import Crypto.Cipher.Types
import Crypto.Error
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import Data.Conduit
import qualified Data.Conduit.List as CL
import qualified Data.Foldable as Fold
import Test.Amazonka.Prelude
import Test.QuickCheck.Instances.ByteString ()
import qualified Test.QuickCheck.Monadic as QC
import Test.Tasty.HUnit
import Test.Tasty.QuickCheck (Property, arbitrary, testProperty)
import Prelude hiding (length)

envelopeTests :: [TestTree]
envelopeTests =
  [ testGroup
      "encrypt/decrypt"
      [ testCase "empty input" testEncryptEmptyInput,
        testCase "tiny input" testEncryptTinyInput,
        testCase "multiple of block size" testEncryptMultipleOfBlockSize,
        testCase "leftover" testEncryptLeftover,
        testProperty "random" testEncryptRandom
      ]
  ]

testEncryptEmptyInput :: Assertion
testEncryptEmptyInput = do
  testEncryptDecrypt [""]
  testEncryptDecrypt ["", ""]

testEncryptTinyInput :: Assertion
testEncryptTinyInput = do
  testEncryptDecrypt ["a"]
  testEncryptDecrypt ["a", "b", "c"]

testEncryptMultipleOfBlockSize :: Assertion
testEncryptMultipleOfBlockSize = do
  let b = BS.pack $ replicate aesBlockSize 170
  testEncryptDecrypt [b]
  testEncryptDecrypt [b, b, b]
  testEncryptDecrypt [b, b <> b <> b, b <> b]

testEncryptLeftover :: Assertion
testEncryptLeftover = do
  let b = BS.pack $ replicate aesBlockSize 170
  testEncryptDecrypt [b, "a"]
  testEncryptDecrypt [b, b, b, "a"]
  testEncryptDecrypt [b, b <> b <> b, b <> b, "abc"]

testEncryptRandom :: Property
testEncryptRandom = QC.monadicIO $
  QC.forAllM arbitrary $ \b ->
    QC.run $ testEncryptDecrypt b

testEncryptDecrypt :: [ByteString] -> Assertion
testEncryptDecrypt bs = do
  let e = mkTestAESV2Envelope
      rqb = ChunkedBody defaultChunkSize (Fold.sum (fromIntegral . BS.length <$> bs)) (CL.sourceList bs)
      (Chunked ChunkedBody {body, length}) = bodyEncrypt e (Chunked rqb)

  length `mod` fromIntegral aesBlockSize @?= 0

  encRes <- runResourceT . runConduit $ body .| CL.consume

  let ResponseBody {body = rsb} =
        bodyDecrypt e $ ResponseBody $ CL.sourceList encRes

  decRes <- runResourceT . runConduit $ rsb .| CL.consume

  mconcat bs @?= mconcat decRes

mkTestAESV2Envelope :: Envelope
mkTestAESV2Envelope =
  let (CryptoPassed aes) = cipherInit ("0123456789abcdef0123456789abcdef" :: ByteString) :: CryptoFailable AES256

      e =
        V2Envelope
          { _v2Key = "don't care",
            _v2IV = nullIV,
            _v2CEKAlgorithm = AES_CBC_PKCS5Padding,
            _v2WrapAlgorithm = KMSWrap,
            _v2Description = Description mempty
          }
   in V2 aes e