raaz-0.3.11: api/random/PRGenerator.hs
-- | This module implements the pseudo-random generator using the
-- /fast key erasure technique/
-- (<https://blog.cr.yp.to/20170723-random.html>) parameterised on the
-- signatures "Implementation" and "Entropy". This technique is the
-- underlying algorithm used in systems like OpenBSD in their
-- implementation of arc4random.
--
-- __WARNING:__ These details are only for developers and reviewers of
-- raaz the library. A casual user should not be looking into this
-- module this let alone tweaking the code here.
--
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE RecordWildCards #-}
-- {-# LANGUAGE NamedFieldPun #-}
module PRGenerator
( -- * Pseudo-random generator
-- $internals$
RandomState, reseed, fillRandomBytes
-- ** Information about the cryptographic generator.
, entropySource, csprgName, csprgDescription
) where
import Foreign.Ptr ( castPtr )
import Entropy
import Prelude
import Raaz.Core
import Raaz.Core.Memory
import Implementation
import Context
-- $internals$
--
-- Generating unpredictable stream of bytes is one task that has burnt
-- the fingers of a lot of programmers. Unfortunately, getting it
-- correct is something of a black art. We give the internal details
-- of the cryptographic pseudo-random generator used in raaz. Note
-- that none of the details here are accessible or tuneable by the
-- user. This is a deliberate design choice to insulate the user from
-- things that are pretty easy to mess up.
--
-- The pseudo-random generator is essentially a primitive that
-- supports the generation of multiple blocks of data once its
-- internals are set. The overall idea is to set the internals from a
-- truly random source and then use the primitive to expand the
-- internal state into pseudo-random bytes. However, there are tricky
-- issues regarding forward security that will make such a simplistic
-- algorithm insecure. Besides, where do we get our truly random seed
-- to begin the process?
--
-- We more or less follow the /fast key erasure technique/
-- (<https://blog.cr.yp.to/20170723-random.html>) which is used in the
-- arc4random implementation in OpenBSD. The two main steps in the
-- generation of the required random bytes are the following:
--
-- [Seeding:] Setting the internal state of a primitive. We use the
-- `getEntropy` function for this purposes.
--
-- [Sampling:] Pre-computing a few blocks using `randomBlocks` that
-- will later on be used to satisfy satisfy the requests for random
-- bytes.
--
-- Instead of running the `randomBlocks` for every request, we
-- generate `RandomBufferSize` blocks of random blocks in an auxiliary
-- buffer and satisfy requests for random bytes from this buffer. To
-- ensure that the compromise of the PRG state does not compromise the
-- random data already generated and given out, we do the following.
--
-- 1. After generating `RandomBufferSize` blocks of data in the
-- auxiliary buffer, we immediately re-initialise the internals of
-- the primitive from the auxiliary buffer. This ensures that there
-- is no way to know which internal state was used to generate the
-- current contents in the auxiliary buffer.
--
-- 2. Every use of data from the auxiliary buffer, whether it is to
-- satisfy a request for random bytes or to reinitialise the
-- internals in step 1 is wiped out immediately.
--
-- Assuming the security of the entropy source given by the
-- `getEntropy` and the random block generator given by the
-- `randomBlocks` we have the following security guarantee.
--
-- [Security Guarantee:] At any point of time, a compromise of the
-- cipher state (i.e. key iv pair) and/or the auxiliary buffer does
-- not reveal the random data that is given out previously.
--
-- | Name of the csprg used for stretching the seed.
csprgName :: String
csprgName = name
-- | A short description of the csprg.
csprgDescription :: String
csprgDescription = description
-- | Memory for storing the csprg state.
data RandomState = RandomState { randomCxt :: Cxt RandomBufferSize
, randomGenBlocks :: MemoryCell (BlockCount Prim)
}
instance Memory RandomState where
memoryAlloc = RandomState <$> memoryAlloc <*> memoryAlloc
unsafeToPointer = unsafeToPointer . randomCxt
-- | Gives access into the internals of the associated cipher.
instance WriteAccessible RandomState where
writeAccess = writeAccess . cxtInternals . randomCxt
afterWriteAdjustment = afterWriteAdjustment . cxtInternals . randomCxt
-------------------------------- The PRG operations ---------------------------------------------
-- | Generate a new sample, i.e. fill the context with psrg.
sample :: RandomState -> IO ()
sample rstate@RandomState{..} = do
genBlocks <- extract randomGenBlocks
if genBlocks >= reseedAfter then reseed rstate
else generateRandom rstate
-- | Reseed the state from the system entropy pool. The CSPRG
-- interface automatically takes care of reseeding from the entropy
-- pool at regular intervals and the user almost never needs to use
-- this.
reseed :: RandomState -> IO ()
reseed rstate@RandomState{..} = do
unsafeInitWithEntropy rstate
initialise zeroBlocks randomGenBlocks
generateRandom rstate
-- | Generate random bytes into the context in one go which will then
-- be slowly released to the outside world. We also keep track of how
-- much blocks is generated which will be used to check when to reseed
-- the generator from system entropy.
generateRandom :: RandomState -> IO ()
generateRandom rstate@RandomState{..} = do
unsafeGenerateBlocks randomBlocks randomCxt
modifyMem (mappend $ cxtBlockCount $ pure randomCxt) randomGenBlocks
unsafeInitFromBuffer rstate
------------------------------ DANGEROUS ACCESS manipulation ------------------------
--
-- These are highly unsafe code do not export. All hell breaks loose
-- otherwise.
--
-- | Initialise the internals from the entropy source.
unsafeInitWithEntropy :: RandomState -> IO ()
unsafeInitWithEntropy = mapM_ initWithEntropy . writeAccess
where initWithEntropy Access{..} = getEntropy accessSize accessPtr
-- | Initialise the internals from the already generated blocks. CSPRG
-- implementations should ensure that the context is large enough to
-- hold enough bytes so even after initialising the internals, there
-- is enough data left to give out for subsequent calls. Otherwise
-- each sampling will result in a infinite loop.
unsafeInitFromBuffer :: RandomState -> IO ()
unsafeInitFromBuffer rstate@RandomState{..} = mapM_ initFromBuffer $ writeAccess rstate
where initFromBuffer Access{..}
= unsafeWriteTo accessSize (destination accessPtr) randomCxt
-- | Zero blocks of the primitive
zeroBlocks :: BlockCount Prim
zeroBlocks = 0 `blocksOf` Proxy
unsafeRandomBytes :: BYTES Int
-> Dest (Ptr Word8)
-> RandomState -> IO ()
unsafeRandomBytes sz destPtr rstate@RandomState{..}
= go sz destPtr
where go n ptr
| n <= 0 = return ()
| otherwise = do trfed <- unsafeWriteTo n ptr randomCxt
let more = n - trfed
nextPtr = (`movePtr` trfed) <$> ptr
in when (more > 0) $ sample rstate >> go more nextPtr
-- | Fill a buffer pointed by the given pointer with random bytes.
fillRandomBytes :: (LengthUnit l, Pointer ptr)
=> l
-> Dest (ptr a)
-> RandomState
-> IO ()
fillRandomBytes l ptr = unsafeRandomBytes (inBytes l) wptr
where wptr = fmap (castPtr . unsafeRawPtr) ptr
instance ByteSource RandomState where
fillBytes n rstate ptr
= unsafeRandomBytes n (destination (castPtr ptr)) rstate >> return (Remaining rstate)