packages feed

newhope-0.1.0.0: test/Crypto/NewHope/Test/CPA_KEM.hs

{-# LANGUAGE Trustworthy #-}
{-|
  Module        : Crypto.NewHope.Test.CPA_KEM
  Description   : Tests for IND-CPA-secure operations for NewHope.
  Copyright     : © Jeremy Bornstein 2019
  License       : Apache 2.0
  Maintainer    : jeremy@bornstein.org
  Stability     : experimental
  Portability   : portable

  These tests duplicate (and in some cases improve on) the CPA tests
  executed by the "test_*" binaries from the reference C code.

-}

module Crypto.NewHope.Test.CPA_KEM where


import           Data.Bits
import qualified Data.ByteString            as BS
import           Test.Tasty                 (TestTree, adjustOption, testGroup)
import           Test.Tasty.ExpectedFailure
import           Test.Tasty.QuickCheck      as QC hiding (output)


import           AuxUtil
import qualified Crypto.NewHope.Internal.CPA_KEM as CPA_KEM
import qualified Crypto.NewHope.Internal.RNG     as RNG
import qualified Crypto.NewHope.Internals        ()
import           Crypto.NewHope.Test.RNG
import           Util


-- Test the basic full key exchange.  Note that we use multiple ctx on
-- input, which is an improvement (and more realistic) as compared to
-- the reference code.
propKeyExchange :: WrapN -> WrapContext -> WrapContext -> Bool
propKeyExchange wrappedN wrappedCtx wrappedCtx2 = keyA == keyB
  where
    WrapN    n    = wrappedN
    WrapContext ctx  = wrappedCtx
    WrapContext ctx2 = wrappedCtx2

    (pk, skA, _ctx') = CPA_KEM.keypair ctx n         -- Alice generates a public key
    (sendb, keyB, _ctx'2) = CPA_KEM.encrypt ctx2 pk  -- Bob derives a secret key and creates a response
    keyA = CPA_KEM.decrypt sendb skA                 -- Alice uses Bob's response to get her secret key


-- Test that using random data as the secret key causes the key exchange to fail.
propInvalidSkA :: WrapN -> WrapContext -> WrapContext -> WrapContext -> Bool
propInvalidSkA wn wctx wctx2 wctx3 = keyA /= keyB
  where
    WrapN    n    = wn
    WrapContext ctx  = wctx
    WrapContext ctx2 = wctx2
    WrapContext ctx3 = wctx3

    (pk, _skA, _ctx') = CPA_KEM.keypair ctx n        -- Alice generates a public key
    (sendb, keyB, _ctx'2) = CPA_KEM.encrypt ctx2 pk  -- Bob derives the SharedSecret and creates a response
    keyA = CPA_KEM.decrypt sendb skA'               -- Alice tries to use Bob's response to get the SharedSecret
      where
        (randomData, _ctx'3) = RNG.randomBytes ctx3 $ CPA_KEM.secretKeyBytes n
        skA' = CPA_KEM.SecretKey randomData          -- Replace secret key with random values


-- | Test that the key exchange fails if three bits in the CipherText
-- are changed. This test (mirroring the reference C implementation)
-- changes the same three bits each time. An improved version of the
-- test would probably change three different bits.
propInvalidCiphertext :: WrapN -> WrapContext -> WrapContext -> Bool
propInvalidCiphertext wn wctx wctx2 = keyA /= keyB
  where
    WrapN n = wn
    WrapContext ctx = wctx
    WrapContext ctx2 = wctx2

    (pk, skA, _ctx') = CPA_KEM.keypair ctx n         -- Alice generates a public key
    (sendb, keyB, _ctx'2) = CPA_KEM.encrypt ctx2 pk  -- Bob derives a secret key and creates a response
    sendb' = mutateCipherText sendb                   -- alternate version of sendb to use for a bad decryption
    keyA = CPA_KEM.decrypt sendb' skA               -- Alice tries to use Bob's response to get her secret key


-- | Change three bits (always the same three) in the input
-- CipherText. This is the test from the reference library.
mutateCipherText :: CPA_KEM.CipherText -> CPA_KEM.CipherText
mutateCipherText (CPA_KEM.CipherText input) = CPA_KEM.CipherText output
  where
    (part1, part2) = BS.splitAt 42 input
    (part2a, part2b) = BS.splitAt 1 part2
    part2a' = BS.pack [xor (BS.index part2a 0) 0x23]
    output = BS.concat [part1, part2a', part2b]


-- | Each time, randomly mutate some number of bits in the ciphertext.
propInvalidCiphertextMutateNBits :: Int -> WrapN -> WrapContext -> WrapContext -> WrapContext -> Bool
propInvalidCiphertextMutateNBits bits wn wctx wctx2 wctx3 = keyA /= keyB
  where
    WrapN    n    = wn
    WrapContext ctx  = wctx
    WrapContext ctx2 = wctx2
    WrapContext ctx3 = wctx3

    (pk, skA, _ctx') = CPA_KEM.keypair ctx n                -- Alice generates a public key
    (sendb, keyB, _ctx'2) = CPA_KEM.encrypt ctx2 pk         -- Bob derives a secret key and creates a response
    (sendb', _ctx'3) = mutateCipherTextBits ctx3 bits sendb -- Change some byte in the ciphertext (i.e., encapsulated key)
    keyA = CPA_KEM.decrypt sendb' skA                       -- Alice uses the damaged version of Bob's response to get her secret key


mutateCipherTextBits :: RNG.Context -> Int -> CPA_KEM.CipherText -> (CPA_KEM.CipherText, RNG.Context)
mutateCipherTextBits ctx count (CPA_KEM.CipherText input) = (CPA_KEM.CipherText output, ctx')
  where
    (output, ctx') = mutateBits ctx count input


tests :: TestTree
tests = testGroup "CPA_KEM Tests" [ QC.testProperty "top level key exchange" propKeyExchange
                                  , QC.testProperty "invalid skA" propInvalidSkA
                                  , QC.testProperty "invalid ciphertext" propInvalidCiphertext
                                  , expectFail
                                    $ adjustOption (\ _ -> QuickCheckTests 256)
                                    $ QC.testProperty "invalid ciphertext (one random bit changed)"
                                    $ propInvalidCiphertextMutateNBits 1
                                  , expectFail
                                    $ adjustOption (\ _ -> QuickCheckTests 256)
                                    $ QC.testProperty "invalid ciphertext (two random bits changed)"
                                    $ propInvalidCiphertextMutateNBits 2
                                  , expectFail
                                    $ adjustOption (\ _ -> QuickCheckTests 512)
                                    $ QC.testProperty "invalid ciphertext (three random bits changed)"
                                    $ propInvalidCiphertextMutateNBits 3
                                  , expectFail
                                    $ adjustOption (\ _ -> QuickCheckTests 3200)
                                    $ QC.testProperty "invalid ciphertext (four random bits changed)"
                                    $ propInvalidCiphertextMutateNBits 4
                                  , QC.testProperty "invalid ciphertext (five random bits changed)" $ propInvalidCiphertextMutateNBits 5
                                  ]