packages feed

intel-aes-0.1.1: Codec/Crypto/ConvertRNG.hs

{-|
   Module      :  Codec.Crypto.ConvertRNG
   Copyright   :  (c) Ryan Newton 2011
   License     :  BSD-style (see the file LICENSE)
   Maintainer  :  rrnewton@gmail.com
   Stability   :  experimental
   Portability :  portable, GHC

 This module bridges these three interfaces:

@
   Crypto.Classes.BlockCipher
   Crypto.Random.CryptoRandomGen
   System.Random.RandomGen
@

    Specifically, a block cipher can be converted to generate a
    @CryptoRandomGen@, which in turn can be converted to provide the
    @RandomGen@ interface.

  -}
{-# OPTIONS_GHC -fwarn-unused-imports #-}
{-# LANGUAGE  ScopedTypeVariables #-}
-- FlexibleInstances, EmptyDataDecls, FlexibleContexts, NamedFieldPuns, , ForeignFunctionInterface

module Codec.Crypto.ConvertRNG 
  ( BCtoCRG(..), convertCRG     
  , CRGtoRG()
  , CRGtoRG0(..) -- Inefficient version for testing...
  )
where

import System.Random 
import System.IO.Unsafe (unsafePerformIO)
import GHC.IO (unsafeDupablePerformIO)

-- import Data.List
import Data.Word
import Data.Tagged
import Data.Serialize

import qualified Data.Bits
import qualified Data.ByteString as B
-- import qualified Data.ByteString.Char8 as BC
import qualified Data.ByteString.Internal as BI

-- import Crypto.Random.DRBG ()
-- import Crypto.Modes

import Crypto.Random (CryptoRandomGen(..), GenError(..), splitGen, genBytes)
import Crypto.Classes (BlockCipher(..), blockSizeBytes)
import Crypto.Types (ByteLength)

import Control.Monad
-- import Foreign.Ptr
import qualified Foreign.ForeignPtr as FP
import Foreign.Storable


----------------------------------------------------------------------------------------------------
-- Converting CryptoRandomGen to RandomGen
------------------------------------------

-- There's a potential overlapping instances problem here.  Someone
-- may want to do their own RandomGen instance, creating a problem
-- with this:
--
--   instance CryptoRandomGen g => RandomGen g where 
--
-- NOTE: The above would also be an undecidable instance.  Another
-- option is to have a type used just for lifting.  See below.


-- | Converting CryptoRandomGen to RandomGen.
--   This naive version is probably pretty inefficent:
data CRGtoRG0 a = CRGtoRG0 a
instance CryptoRandomGen g => RandomGen (CRGtoRG0 g) where 
   next  (CRGtoRG0 g) = 
--       case genBytes (max bytes_in_int (keyLength g `quot` 8)) g of 
       case genBytes bytes_in_int g of 
         Left err -> error$ "CryptoRandomGen genBytes error: " ++ show err
	 Right (bytes,g') -> 
           case decode bytes of 
	      Left err -> error$ "Deserialization error:"++ show err
	      Right n -> (n, CRGtoRG0 g')
	     
   split (CRGtoRG0 g) = 
       case splitGen g of 
         Left err      -> error$ "CryptoRandomGen splitGen error:"++ show err
	 Right (g1,g2) -> (CRGtoRG0 g1, CRGtoRG0 g2)

-- Another option would be to amortize overhead by generating a large
-- buffer of random bits at once.
-- data CRGtoRG a = CRGtoRG a BUFFER INDEX

-- Any better way to do this?
bytes_in_int = (round $ 1 + logBase 2 (fromIntegral (maxBound :: Int)))  `quot` 8
-- steps = 128 `quot` bits_in_int

------------------------------------------------------------
-- | Converting CryptoRandomGen to RandomGen.
--   Keep a buffer of random bits and an index into that buffer.
data CRGtoRG a = CRGtoRG a 
    {-#UNPACK#-}!         (FP.ForeignPtr Int)
    {-#UNPACK#-}!         Int

instance CryptoRandomGen g => RandomGen (CRGtoRG g) where 
   next (CRGtoRG g _ ind) | ind == bufsize = next (convertCRG g) -- Refill the buffer
   next (CRGtoRG g buf ind) = 
       -- As long as this memory is in use it will not be modified.
       -- The peek action should therefore be dupable:
       unsafeDupablePerformIO $ 
         FP.withForeignPtr buf $ \ ptr -> 
           do x <- peekElemOff ptr ind 
	      return (x, CRGtoRG g buf (ind+1))
	     
   split (CRGtoRG g buf ind) = 
       case splitGen g of 
         Left err      -> error$ "CryptoRandomGen splitGen error:"++ show err
	 Right (g1,g2) -> (CRGtoRG g1 buf ind, convertCRG g2)


-- | The constructor for CRGtoRG values.
convertCRG :: CryptoRandomGen g => g -> CRGtoRG g
convertCRG crg = CRGtoRG g' (FP.castForeignPtr ptr) 0
 where 
  (ptr,_,_)     = BI.toForeignPtr bs
  Right (bs,g') = genBytes (bufsize * bytes_in_int) crg


-- How many 8 byte chunks should we buffer each time?
-- TODO: Autotune this...
bufsize = 256



----------------------------------------------------------------------------------------------------
-- We would also like every BlockCipher to constitute a valid CryptoRandomGen.
-- Again there's the tension with UndecidableInstances vs explicit lifting.

-- | A BlockCipher can generate random numbers.
--   When lifting we include a counter which increments as random numbers are generated:
data BCtoCRG a = BCtoCRG a Word64

instance BlockCipher x => CryptoRandomGen (BCtoCRG x) where 
  newGen  bytes = case buildKey bytes of Nothing -> Left NotEnoughEntropy 
					 Just x  -> Right (BCtoCRG x 0)
  genSeedLength = Tagged 128

  -- If this is called for less than blockSize data there's some waste but it should work.
  genBytes req (BCtoCRG (bcgen :: k) counter) = 
      -- What's the most efficient way to do this?
      unsafePerformIO $ do  -- Potentially heavyweight... not allowing dupable.
--      unsafeDupablePerformIO $ do
	-- Number of times to stamp out the counter:
        let bsize = untag (blockSizeBytes :: Tagged k ByteLength)
	    numstamps = (req + 7) `quot` 8
	    numblocks = (req + bsize - 1) `quot` bsize
	    total     = max (numstamps * 8) (numblocks * bsize)

        -- putStrLn$ "[temp] requested "++show req++" bytes, stamping  "++show (numstamps*8)++
	-- 	  " into "++show numblocks++" block(s), output buf size "++show total

        buf :: FP.ForeignPtr Word64 <- FP.mallocForeignPtrBytes total
	FP.withForeignPtr buf $ \ptr -> 
	  forM_ [0..numstamps-1] $ \i -> 
	    pokeElemOff ptr i (counter + fromIntegral i)
        let cipher = encryptBlock bcgen (BI.fromForeignPtr (FP.castForeignPtr buf) 0 total)
	    newgen = BCtoCRG bcgen (counter + fromIntegral numstamps)
	-- At the end we may have requested more bytes than needed, so we might crop:
	if req==total then return$ Right (cipher, newgen)
	              else return$ Right (B.take req cipher, newgen)

  reseed bs (BCtoCRG k _) = newGen (xorExtendBS (encode k) bs)

xorExtendBS a b = B.append (B.pack$ B.zipWith Data.Bits.xor a b) rem
      where
      al = B.length a
      bl = B.length b
      rem | bl > al   = B.drop al b
          | otherwise = B.drop bl a