{-# LANGUAGE FlexibleInstances, TypeSynonymInstances, DeriveDataTypeable, CPP,
BangPatterns #-}
module Crypto.Random
(
CryptoRandomGen(..)
, GenError (..)
, ReseedInfo (..)
, splitGen
, throwLeft
, SystemRandom
) where
import Control.Monad (liftM)
import Control.Exception
import Crypto.Types
import Crypto.Util
import Data.Bits (xor, setBit, shiftR, shiftL, (.&.))
import Data.Data
import Data.List (foldl')
import Data.Tagged
import Data.Typeable
import Data.Word
import System.Entropy
import System.IO.Unsafe(unsafeInterleaveIO)
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as L
import qualified Foreign.ForeignPtr as FP
#if MIN_VERSION_tagged(0,2,0)
import Data.Proxy
#endif
data GenError =
GenErrorOther String
| RequestedTooManyBytes
| RangeInvalid
| NeedReseed
| NotEnoughEntropy
| NeedsInfiniteSeed
deriving (Eq, Ord, Show, Read, Data, Typeable)
data ReseedInfo
= InXBytes {-# UNPACK #-} !Word64
| InXCalls {-# UNPACK #-} !Word64
| NotSoon
| Never
deriving (Eq, Ord, Show, Read, Data, Typeable)
instance Exception GenError
class CryptoRandomGen g where
newGen :: B.ByteString -> Either GenError g
genSeedLength :: Tagged g ByteLength
genBytes :: ByteLength -> g -> Either GenError (B.ByteString, g)
reseedInfo :: g -> ReseedInfo
reseedPeriod :: g -> ReseedInfo
genBytesWithEntropy :: ByteLength -> B.ByteString -> g -> Either GenError (B.ByteString, g)
genBytesWithEntropy len entropy g =
let res = genBytes len g
in case res of
Left err -> Left err
Right (bs,g') ->
let entropy' = B.append entropy (B.replicate (len - B.length entropy) 0)
in Right (zwp' entropy' bs, g')
reseed :: B.ByteString -> g -> Either GenError g
newGenIO :: IO g
newGenIO = go 0
where
go 1000 = throw $ GenErrorOther $
"The generator instance requested by" ++
"newGenIO never instantiates (1000 tries). " ++
"It must be broken."
go i = do
let p = Proxy
getTypedGen :: (CryptoRandomGen g) => Proxy g -> IO (Either GenError g)
getTypedGen pr = liftM newGen (getEntropy $ proxy genSeedLength pr)
res <- getTypedGen p
case res of
Left _ -> go (i+1)
Right g -> return (g `asProxyTypeOf` p)
getSystemGen :: IO SystemRandom
getSystemGen = do
ch <- openHandle
let getBS = unsafeInterleaveIO $ do
bs <- hGetEntropy ch ((2^15) - 16)
more <- getBS
return (bs:more)
liftM (SysRandom . L.fromChunks) getBS
data SystemRandom = SysRandom L.ByteString
instance CryptoRandomGen SystemRandom where
newGen _ = Left NeedsInfiniteSeed
genSeedLength = Tagged maxBound
genBytes req (SysRandom bs) =
let reqI = fromIntegral req
rnd = L.take reqI bs
rest = L.drop reqI bs
in if L.length rnd == reqI
then Right (B.concat $ L.toChunks rnd, SysRandom rest)
else Left RequestedTooManyBytes
reseed _ _ = Left NeedsInfiniteSeed
newGenIO = getSystemGen
reseedInfo _ = Never
reseedPeriod _ = Never
splitGen :: CryptoRandomGen g => g -> Either GenError (g,g)
splitGen g =
let e = genBytes (genSeedLength `for` g) g
in case e of
Left e -> Left e
Right (ent,g') ->
case newGen ent of
Right new -> Right (g',new)
Left e -> Left e