File: EntropyNix.hs

package info (click to toggle)
haskell-entropy 0.4.1.11-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 100 kB
  • sloc: haskell: 358; ansic: 182; makefile: 2
file content (147 lines) | stat: -rw-r--r-- 4,391 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
{-# 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