@@ -16,13 +16,16 @@ module System.EntropyNix
16 , hardwareRandom
16 , hardwareRandom
19+import Control.Exception
19import Control.Monad (liftM, when)
20import Control.Monad (liftM, when)
20import Data.ByteString as B
21import Data.ByteString as B
21import System.IO.Error (mkIOError, eofErrorType, ioeSetErrorString)
22import System.IO.Error (mkIOError, eofErrorType, ioeSetErrorString)
23+import System.IO.Unsafe
22import Data.Bits (xor)
24import Data.Bits (xor)
24import Foreign (allocaBytes)
26import Foreign (allocaBytes)
25import Foreign.Ptr
27import Foreign.Ptr
28+import Foreign.C.Error
26import Foreign.C.Types
29import Foreign.C.Types
27import Data.ByteString.Internal as B
30import Data.ByteString.Internal as B
@@ -39,6 +42,9 @@ source = "/dev/urandom"
39-- |Handle for manual resource management
42-- |Handle for manual resource management
40data CryptHandle
43data CryptHandle
43-- | Get random values from the hardward RNG or return Nothing if no
49-- | Get random values from the hardward RNG or return Nothing if no
44-- supported hardware RNG is available.
50-- supported hardware RNG is available.
@@ -50,8 +56,7 @@ hardwareRandom :: Int -> IO (Maybe B.ByteString)
50#ifdef HAVE_RDRAND
56#ifdef HAVE_RDRAND
51hardwareRandom n =
57hardwareRandom n =
52 do b <- cpuHasRdRand
58 do b <- cpuHasRdRand
54− then Just <$> B.create n (\ptr ->
59+ if b then Just <$> B.create n (\ptr ->
55 do r <- c_get_rand_bytes (castPtr ptr) (fromIntegral n)
60 do r <- c_get_rand_bytes (castPtr ptr) (fromIntegral n)
56 when (r /= 0) (fail "RDRand failed to gather entropy"))
61 when (r /= 0) (fail "RDRand failed to gather entropy"))
57 else pure Nothing
62 else pure Nothing
@@ -61,18 +66,36 @@ hardwareRandom _ = pure Nothing
62-- |Open a `CryptHandle`
67-- |Open a `CryptHandle`
63openHandle :: IO CryptHandle
68openHandle :: IO CryptHandle
64−openHandle = do CH `fmap` nonRDRandHandle
69+openHandle =
65− where
70+#ifdef HAVE_GETRANDOM
66− nonRDRandHandle :: IO Fd
71+ if systemHasGetRandom then return UseGetRandom else
73+ fmap CH openRandomFile
75+openRandomFile :: IO Fd
77+ evaluate ensurePoolInitialized
67− nonRDRandHandle = openFd source ReadOnly Nothing defaultFileFlags
78+ openFd source ReadOnly Nothing defaultFileFlags
69-- |Close the `CryptHandle`
80-- |Close the `CryptHandle`
70closeHandle :: CryptHandle -> IO ()
81closeHandle :: CryptHandle -> IO ()
71closeHandle (CH h) = closeFd h
82closeHandle (CH h) = closeFd h
84+closeHandle UseGetRandom = return ()
73-- |Read random data from a `CryptHandle`
87-- |Read random data from a `CryptHandle`
74hGetEntropy :: CryptHandle -> Int -> IO B.ByteString
88hGetEntropy :: CryptHandle -> Int -> IO B.ByteString
75−hGetEntropy (CH h) = fdReadBS h
89+hGetEntropy (CH h) n = fdReadBS h n
91+hGetEntropy UseGetRandom n = do
92+ bs <- B.createUptoN n (\ptr -> do
93+ r <- c_entropy_getrandom (castPtr ptr) (fromIntegral n)
94+ return $ if r == 0 then n else 0)
95+ if B.length bs == n then return bs
96+ -- getrandom somehow failed. Fall back on /dev/urandom instead.
97+ else bracket openRandomFile closeFd $ flip fdReadBS n
77fdReadBS :: Fd -> Int -> IO B.ByteString
100fdReadBS :: Fd -> Int -> IO B.ByteString
78fdReadBS fd n =
101fdReadBS fd n =
@@ -86,6 +109,28 @@ fdReadBS fd n =
86 n' -> go buf (cnt - fromIntegral n')
109 n' -> go buf (cnt - fromIntegral n')
87 go _ _ = error "Impossible! The count of bytes left to read is greater than the request or less than zero!"
110 go _ _ = error "Impossible! The count of bytes left to read is greater than the request or less than zero!"
112+#ifdef HAVE_GETRANDOM
113+foreign import ccall unsafe "system_has_getrandom"
114+ c_system_has_getrandom :: IO CInt
115+foreign import ccall safe "entropy_getrandom"
116+ c_entropy_getrandom :: Ptr CUChar -> CSize -> IO CInt
118+-- NOINLINE and unsafePerformIO are not totally necessary as getrandom will be
119+-- consistently either present or not, but it is cheaper not to check multiple
121+systemHasGetRandom :: Bool
122+{-# NOINLINE systemHasGetRandom #-}
123+systemHasGetRandom = unsafePerformIO $ fmap (/= 0) c_system_has_getrandom
126+foreign import ccall safe "ensure_pool_initialized"
127+ c_ensure_pool_initialized :: IO CInt
129+-- Similarly to systemHasGetRandom, NOINLINE is just an optimization.
130+ensurePoolInitialized :: CInt
131+{-# NOINLINE ensurePoolInitialized #-}
132+ensurePoolInitialized = unsafePerformIO $ throwErrnoIfMinus1 "ensurePoolInitialized" $ c_ensure_pool_initialized
89#ifdef HAVE_RDRAND
134#ifdef HAVE_RDRAND
90foreign import ccall unsafe "cpu_has_rdrand"
135foreign import ccall unsafe "cpu_has_rdrand"
91 c_cpu_has_rdrand :: IO CInt
136 c_cpu_has_rdrand :: IO CInt