entropy-0.4.1.11: System/EntropyNix.hs
{-# LANGUAGE CPP, ForeignFunctionInterface, BangPatterns, ScopedTypeVariables #-}
{-|
Maintainer: Thomas.DuBuisson@gmail.com
Stability: beta
Portability: portable
Obtain entropy from system sources or x86 RDRAND when available.
-}
module System.EntropyNix
( CryptHandle
, openHandle
, hGetEntropy
, closeHandle
, hardwareRandom
) where
import Control.Exception
import Control.Monad (liftM, when)
import Data.ByteString as B
import System.IO.Error (mkIOError, eofErrorType, ioeSetErrorString)
import System.IO.Unsafe
import Data.Bits (xor)
import Foreign (allocaBytes)
import Foreign.Ptr
import Foreign.C.Error
import Foreign.C.Types
import Data.ByteString.Internal as B
#ifdef arch_i386
-- See .cabal wrt GCC 4.8.2 asm compilation bug
#undef HAVE_RDRAND
#endif
import System.Posix (openFd, closeFd, fdReadBuf, OpenMode(..), defaultFileFlags, Fd, OpenFileFlags(..))
source :: FilePath
source = "/dev/urandom"
-- |Handle for manual resource management
data CryptHandle
= CH Fd
#ifdef HAVE_GETRANDOM
| UseGetRandom
#endif
-- | Get random values from the hardware RNG or return Nothing if no
-- supported hardware RNG is available.
--
-- Supported hardware:
-- * RDRAND
-- * Patches welcome
hardwareRandom :: Int -> IO (Maybe B.ByteString)
#ifdef HAVE_RDRAND
hardwareRandom n =
do b <- cpuHasRdRand
if b then Just <$> B.create n (\ptr ->
do r <- c_get_rand_bytes (castPtr ptr) (fromIntegral n)
when (r /= 0) (fail "RDRand failed to gather entropy"))
else pure Nothing
#else
hardwareRandom _ = pure Nothing
#endif
-- |Open a `CryptHandle`
openHandle :: IO CryptHandle
openHandle =
#ifdef HAVE_GETRANDOM
if systemHasGetRandom then return UseGetRandom else
#endif
fmap CH openRandomFile
openRandomFile :: IO Fd
openRandomFile = do
evaluate ensurePoolInitialized
#if MIN_VERSION_unix(2,8,0)
openFd source ReadOnly defaultFileFlags { creat = Nothing }
#else
openFd source ReadOnly Nothing defaultFileFlags
#endif
-- |Close the `CryptHandle`
closeHandle :: CryptHandle -> IO ()
closeHandle (CH h) = closeFd h
#ifdef HAVE_GETRANDOM
closeHandle UseGetRandom = return ()
#endif
-- |Read random data from a `CryptHandle`
hGetEntropy :: CryptHandle -> Int -> IO B.ByteString
hGetEntropy (CH h) n = fdReadBS h n
#ifdef HAVE_GETRANDOM
hGetEntropy UseGetRandom n = do
bs <- B.createUptoN n (\ptr -> do
r <- c_entropy_getrandom (castPtr ptr) (fromIntegral n)
return $ if r == 0 then n else 0)
if B.length bs == n then return bs
-- getrandom somehow failed. Fall back on /dev/urandom instead.
else bracket openRandomFile closeFd $ flip fdReadBS n
#endif
fdReadBS :: Fd -> Int -> IO B.ByteString
fdReadBS fd n =
allocaBytes n $ \buf -> go buf n
where
go buf 0 = B.packCStringLen (castPtr buf, fromIntegral n)
go buf cnt | cnt <= n = do
rc <- fdReadBuf fd (plusPtr buf (n - cnt)) (fromIntegral cnt)
case rc of
0 -> ioError (ioeSetErrorString (mkIOError eofErrorType "fdRead" Nothing Nothing) "EOF")
n' -> go buf (cnt - fromIntegral n')
go _ _ = error "Impossible! The count of bytes left to read is greater than the request or less than zero!"
#ifdef HAVE_GETRANDOM
foreign import ccall unsafe "system_has_getrandom"
c_system_has_getrandom :: IO CInt
foreign import ccall safe "entropy_getrandom"
c_entropy_getrandom :: Ptr CUChar -> CSize -> IO CInt
-- NOINLINE and unsafePerformIO are not totally necessary as getrandom will be
-- consistently either present or not, but it is cheaper not to check multiple
-- times.
systemHasGetRandom :: Bool
{-# NOINLINE systemHasGetRandom #-}
systemHasGetRandom = unsafePerformIO $ fmap (/= 0) c_system_has_getrandom
#endif
foreign import ccall safe "ensure_pool_initialized"
c_ensure_pool_initialized :: IO CInt
-- Similarly to systemHasGetRandom, NOINLINE is just an optimization.
ensurePoolInitialized :: CInt
{-# NOINLINE ensurePoolInitialized #-}
ensurePoolInitialized = unsafePerformIO $ throwErrnoIfMinus1 "ensurePoolInitialized" $ c_ensure_pool_initialized
#ifdef HAVE_RDRAND
foreign import ccall unsafe "cpu_has_rdrand"
c_cpu_has_rdrand :: IO CInt
foreign import ccall unsafe "get_rand_bytes"
c_get_rand_bytes :: Ptr CUChar -> CSize -> IO CInt
cpuHasRdRand :: IO Bool
cpuHasRdRand = (/= 0) `fmap` c_cpu_has_rdrand
#endif