├── .gitignore ├── cabal.haskell-ci ├── cbits ├── rdrand.h ├── getrandom.c ├── random_initialized.c └── rdrand.c ├── README.md ├── System ├── EntropyGhcjs.hs ├── Entropy.hs ├── EntropyWindows.hs └── EntropyNix.hs ├── LICENSE ├── entropy.cabal ├── Setup.hs └── .github └── workflows └── haskell-ci.yml /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | dist-newstyle 3 | -------------------------------------------------------------------------------- /cabal.haskell-ci: -------------------------------------------------------------------------------- 1 | branches: main 2 | cabal-check: False 3 | haddock: >=8.2 -------------------------------------------------------------------------------- /cbits/rdrand.h: -------------------------------------------------------------------------------- 1 | #ifndef rdrand_h 2 | #ifdef HAVE_RDRAND 3 | #include 4 | 5 | int cpu_has_rdrand() 6 | 7 | // Returns 0 on success, non-zero on failure. 8 | int get_rand_bytes(uint8_t *therand, size_t len) 9 | #endif // HAVE_RDRAND 10 | #endif // rdrand_h 11 | -------------------------------------------------------------------------------- /cbits/getrandom.c: -------------------------------------------------------------------------------- 1 | #ifdef HAVE_GETRANDOM 2 | 3 | #define _GNU_SOURCE 4 | #include 5 | 6 | #ifdef HAVE_LIBC_GETRANDOM 7 | #include 8 | #else 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #ifndef SYS_getrandom 16 | #define SYS_getrandom __NR_getrandom 17 | #endif 18 | 19 | static ssize_t getrandom(void* buf, size_t buflen, unsigned int flags) 20 | { 21 | return syscall(SYS_getrandom, buf, buflen, flags); 22 | } 23 | 24 | #endif 25 | 26 | int system_has_getrandom() 27 | { 28 | char tmp; 29 | return getrandom(&tmp, sizeof(tmp), GRND_NONBLOCK) != -1 || errno != ENOSYS; 30 | } 31 | 32 | // Returns 0 on success, non-zero on failure. 33 | int entropy_getrandom(unsigned char* buf, size_t len) 34 | { 35 | while (len) { 36 | ssize_t bytes_read = getrandom(buf, len, 0); 37 | 38 | if (bytes_read == -1) { 39 | if (errno != EINTR) 40 | return -1; 41 | else 42 | continue; 43 | } 44 | 45 | len -= bytes_read; 46 | buf += bytes_read; 47 | } 48 | 49 | return 0; 50 | } 51 | 52 | #endif 53 | -------------------------------------------------------------------------------- /cbits/random_initialized.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #ifdef HAVE_GETENTROPY 9 | #ifndef DO_NOT_USE_GET_ENTROPY 10 | static int ensure_pool_initialized_getentropy() 11 | { 12 | char tmp; 13 | return getentropy(&tmp, sizeof(tmp)); 14 | } 15 | #endif 16 | #endif 17 | 18 | // Poll /dev/random to wait for randomness. This is a proxy for the /dev/urandom 19 | // pool being initialized. 20 | static int ensure_pool_initialized_poll() 21 | { 22 | struct pollfd pfd; 23 | int dev_random = open("/dev/random", O_RDONLY); 24 | if (dev_random == -1) 25 | return -1; 26 | 27 | pfd.fd = dev_random; 28 | pfd.events = POLLIN; 29 | pfd.revents = 0; 30 | 31 | while (1) { 32 | int ret = poll(&pfd, 1, -1); 33 | if (ret < 0 && (errno == EAGAIN || errno == EINTR)) 34 | continue; 35 | if (ret != 1) { 36 | close(dev_random); 37 | errno = EIO; 38 | return -1; 39 | } 40 | 41 | break; 42 | } 43 | 44 | return close(dev_random); 45 | } 46 | 47 | // Returns 0 on success, non-zero on failure. 48 | int ensure_pool_initialized() 49 | { 50 | #ifdef HAVE_GETENTROPY 51 | #ifndef DO_NOT_USE_GET_ENTROPY 52 | if (ensure_pool_initialized_getentropy() == 0) 53 | return 0; 54 | #endif 55 | #endif 56 | 57 | return ensure_pool_initialized_poll(); 58 | } 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | This package allows Haskell users to easily acquire entropy for use in critical 4 | security applications by calling out to either windows crypto api, unix/linux's 5 | `getrandom` and `/dev/urandom`. Hardware RNGs (currently RDRAND, patches 6 | welcome) are supported via the `hardwareRNG` function. 7 | 8 | ## Quick Start 9 | 10 | To simply get random bytes use `getEntropy`: 11 | 12 | ``` 13 | #!/usr/bin/env cabal 14 | {- cabal: 15 | build-depends: base, entropy, bytestring 16 | -} 17 | import qualified Data.ByteString as BS 18 | import System.Entropy 19 | 20 | main :: IO () 21 | main = print . BS.unpack =<< getEntropy 16 22 | -- Example output: [241,191,215,193,225,27,121,244,16,155,252,41,131,38,6,100] 23 | ``` 24 | 25 | ## Faster Randoms from Hardware 26 | 27 | Most x86 systems include a hardware random number generator. These can be 28 | faster but require more trust in the platform: 29 | 30 | ``` 31 | import qualified Data.ByteString as B 32 | import System.Entropy 33 | 34 | eitherRNG :: Int -> IO B.ByteString 35 | eitherRNG sz = maybe (getEntropy sz) pure =<< getHardwareEntropy sz 36 | 37 | main :: IO () 38 | main = print . B.unpack =<< eitherRNG 32 39 | ``` 40 | 41 | This package supports Windows, {li,u}nix, QNX, and has preliminary support for HaLVM. 42 | 43 | Typically tested on Linux and OSX - testers are as welcome as patches. 44 | 45 | [![Build Status](https://travis-ci.org/TomMD/entropy.svg?branch=master)](https://travis-ci.org/TomMD/entropy) 46 | -------------------------------------------------------------------------------- /System/EntropyGhcjs.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | Maintainer: Thomas.DuBuisson@gmail.com 3 | Stability: beta 4 | Portability: portable 5 | 6 | Obtain entropy from system sources or x86 RDRAND when available. 7 | 8 | -} 9 | 10 | module System.EntropyGhcjs 11 | ( CryptHandle 12 | , openHandle 13 | , hGetEntropy 14 | , closeHandle 15 | , hardwareRandom 16 | ) where 17 | 18 | import Data.ByteString as B 19 | import GHCJS.DOM.Crypto as Crypto 20 | import GHCJS.DOM.Types (ArrayBufferView (..), fromJSValUnchecked) 21 | import GHCJS.DOM.GlobalCrypto (getCrypto) 22 | import GHCJS.DOM (globalThisUnchecked) 23 | import Language.Javascript.JSaddle.Object as JS 24 | 25 | 26 | -- |Handle for manual resource management 27 | newtype CryptHandle = CH Crypto 28 | 29 | -- | Get random values from the hardware RNG or return Nothing if no 30 | -- supported hardware RNG is available. 31 | -- 32 | -- Not supported on ghcjs. 33 | hardwareRandom :: Int -> IO (Maybe B.ByteString) 34 | hardwareRandom _ = pure Nothing 35 | 36 | -- |Open a `CryptHandle` 37 | openHandle :: IO CryptHandle 38 | openHandle = do 39 | this <- globalThisUnchecked 40 | CH <$> getCrypto this 41 | 42 | -- |Close the `CryptHandle` 43 | closeHandle :: CryptHandle -> IO () 44 | closeHandle _ = pure () 45 | 46 | -- |Read random data from a `CryptHandle` 47 | hGetEntropy :: CryptHandle -> Int -> IO B.ByteString 48 | hGetEntropy (CH h) n = do 49 | arr <- JS.new (jsg "Int8Array") [n] 50 | getRandomValues_ h (ArrayBufferView arr) 51 | B.pack <$> fromJSValUnchecked arr 52 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) Thomas DuBuisson 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions 7 | are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright 13 | notice, this list of conditions and the following disclaimer in the 14 | documentation and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the author nor the names of his contributors 17 | may be used to endorse or promote products derived from this software 18 | without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS 21 | OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 22 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR 24 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 28 | STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 29 | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /System/Entropy.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP, ForeignFunctionInterface, BangPatterns, ScopedTypeVariables #-} 2 | {-| 3 | Maintainer: Thomas.DuBuisson@gmail.com 4 | Stability: beta 5 | Portability: portable 6 | 7 | Obtain entropy from system sources or x86 RDRAND when available. 8 | 9 | Currently supporting: 10 | 11 | - Windows via CryptoAPI 12 | - *nix systems via @\/dev\/urandom@ 13 | - Includes QNX 14 | - ghcjs/browser via JavaScript crypto API. 15 | -} 16 | 17 | module System.Entropy 18 | ( getEntropy, 19 | getHardwareEntropy, 20 | CryptHandle, 21 | openHandle, 22 | hGetEntropy, 23 | closeHandle 24 | ) where 25 | 26 | #ifdef ghcjs_HOST_OS 27 | import System.EntropyGhcjs 28 | #elif defined(isWindows) 29 | import System.EntropyWindows 30 | #else 31 | import System.EntropyNix 32 | #endif 33 | 34 | import qualified Data.ByteString as B 35 | import Control.Exception (bracket) 36 | 37 | -- |Get a specific number of bytes of cryptographically 38 | -- secure random data using the *system-specific* sources. 39 | -- (As of 0.4. Versions <0.4 mixed system and hardware sources) 40 | -- 41 | -- The returned random value is considered cryptographically secure but not true entropy. 42 | -- 43 | -- On some platforms this requires a file handle which can lead to resource 44 | -- exhaustion in some situations. 45 | getEntropy :: Int -- ^ Number of bytes 46 | -> IO B.ByteString 47 | getEntropy = bracket openHandle closeHandle . flip hGetEntropy 48 | 49 | -- |Get a specific number of bytes of cryptographically 50 | -- secure random data using a supported *hardware* random bit generator. 51 | -- 52 | -- If there is no hardware random number generator then @Nothing@ is returned. 53 | -- If any call returns non-Nothing then it should never be @Nothing@ unless 54 | -- there has been a hardware failure. 55 | -- 56 | -- If trust of the CPU allows it and no context switching is important, 57 | -- a bias to the hardware rng with system rng as fall back is trivial: 58 | -- 59 | -- @ 60 | -- let fastRandom nr = maybe (getEntropy nr) pure =<< getHardwareEntropy nr 61 | -- @ 62 | -- 63 | -- The old, @<0.4@, behavior is possible using @xor@ from 'Data.Bits': 64 | -- 65 | -- @ 66 | -- let oldRandom nr = 67 | -- do hwRnd <- maybe (replicate nr 0) BS.unpack <$> getHardwareEntropy nr 68 | -- sysRnd <- BS.unpack <$> getEntropy nr 69 | -- pure $ BS.pack $ zipWith xor sysRnd hwRnd 70 | -- @ 71 | -- 72 | -- A less maliable mixing can be accomplished by replacing `xor` with a 73 | -- composition of concat and cryptographic hash. 74 | getHardwareEntropy :: Int -- ^ Number of bytes 75 | -> IO (Maybe B.ByteString) 76 | getHardwareEntropy = hardwareRandom 77 | -------------------------------------------------------------------------------- /cbits/rdrand.c: -------------------------------------------------------------------------------- 1 | #ifdef HAVE_RDRAND 2 | 3 | #include 4 | #include 5 | 6 | int cpu_has_rdrand() 7 | { 8 | uint32_t ax,bx,cx,dx,func=1; 9 | __asm__ volatile ("cpuid":\ 10 | "=a" (ax), "=b" (bx), "=c" (cx), "=d" (dx) : "a" (func)); 11 | return (cx & 0x40000000); 12 | } 13 | 14 | #ifdef arch_x86_64 15 | // Returns 1 on success 16 | static inline int _rdrand64_step(uint64_t *therand) 17 | { 18 | unsigned char err; 19 | asm volatile("rdrand %0 ; setc %1" 20 | : "=r" (*therand), "=qm" (err)); 21 | return (int) err; 22 | } 23 | 24 | // Returns 0 on success, non-zero on failure. 25 | int get_rand_bytes(uint8_t *therand, size_t len) 26 | { 27 | int cnt; 28 | int fail=0; 29 | uint8_t *p = therand; 30 | uint8_t *end = therand + len; 31 | if((uint64_t)p%8 != 0) { 32 | uint64_t tmp; 33 | fail |= !_rdrand64_step(&tmp); 34 | while((uint64_t)p%8 != 0 && p != end) { 35 | *p = (uint8_t)(tmp & 0xFF); 36 | tmp = tmp >> 8; 37 | p++; 38 | } 39 | } 40 | for(; p <= end - sizeof(uint64_t); p+=sizeof(uint64_t)) { 41 | fail |= !_rdrand64_step((uint64_t *)p); 42 | } 43 | if(p != end) { 44 | uint64_t tmp; 45 | int cnt; 46 | fail |= !_rdrand64_step(&tmp); 47 | while(p != end) { 48 | *p = (uint8_t)(tmp & 0xFF); 49 | tmp = tmp >> 8; 50 | p++; 51 | } 52 | } 53 | return fail; 54 | } 55 | #endif /* x86-64 */ 56 | 57 | #ifdef arch_i386 58 | // Returns 1 on success 59 | static inline int _rdrand32_step(uint32_t *therand) 60 | { 61 | unsigned char err; 62 | asm volatile("rdrand %0 ; setc %1" 63 | : "=r" (*therand), "=qm" (err)); 64 | return (int) err; 65 | } 66 | 67 | int get_rand_bytes(uint8_t *therand, size_t len) 68 | { 69 | int cnt; 70 | int fail=0; 71 | uint8_t *p = therand; 72 | uint8_t *end = therand + len; 73 | if((uint32_t)p % sizeof(uint32_t) != 0) { 74 | uint32_t tmp; 75 | fail |= !_rdrand32_step(&tmp); 76 | while((uint32_t)p % sizeof(uint32_t) != 0 && p != end) { 77 | *p = (uint8_t)(tmp & 0xFF); 78 | tmp = tmp >> 8; 79 | p++; 80 | } 81 | } 82 | for(; p <= end - sizeof(uint32_t); p+=sizeof(uint32_t)) { 83 | fail |= !_rdrand32_step((uint32_t *)p); 84 | } 85 | if(p != end) { 86 | uint32_t tmp; 87 | int cnt; 88 | fail |= !_rdrand32_step(&tmp); 89 | while(p != end) { 90 | *p = (uint8_t)(tmp & 0xFF); 91 | tmp = tmp >> 8; 92 | p++; 93 | } 94 | } 95 | return fail; 96 | } 97 | #endif /* i386 */ 98 | 99 | #endif // RDRAND 100 | -------------------------------------------------------------------------------- /entropy.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: >=1.10 2 | name: entropy 3 | version: 0.4.1.11 4 | description: A mostly platform independent method to obtain cryptographically strong entropy 5 | (RDRAND, urandom, CryptAPI, and patches welcome) 6 | Users looking for cryptographically strong (number-theoretically 7 | sound) PRNGs should see the 'DRBG' package too. 8 | synopsis: A platform independent entropy source 9 | license: BSD3 10 | license-file: LICENSE 11 | copyright: Thomas DuBuisson 12 | author: Thomas DuBuisson 13 | maintainer: Thomas DuBuisson 14 | category: Data, Cryptography 15 | homepage: https://github.com/TomMD/entropy 16 | bug-reports: https://github.com/TomMD/entropy/issues 17 | stability: stable 18 | 19 | build-type: Custom 20 | 21 | tested-with: 22 | GHC == 9.12.1 23 | GHC == 9.10.1 24 | GHC == 9.8.4 25 | GHC == 9.6.6 26 | GHC == 9.4.8 27 | GHC == 9.2.8 28 | GHC == 9.0.2 29 | GHC == 8.10.7 30 | GHC == 8.8.4 31 | GHC == 8.6.5 32 | GHC == 8.4.4 33 | GHC == 8.2.2 34 | 35 | extra-source-files: 36 | ./cbits/getrandom.c 37 | ./cbits/random_initialized.c 38 | ./cbits/rdrand.c 39 | ./cbits/rdrand.h 40 | README.md 41 | 42 | Flag DoNotGetEntropy 43 | Description: Avoid use of the getentropy() *nix function. By default getentropy will be used 44 | if detected during compilation (this plays poorly with cross compilation). 45 | Default: False 46 | Manual: True 47 | 48 | custom-setup 49 | setup-depends: Cabal >= 1.10 && < 3.15 50 | , base < 5 51 | , filepath < 1.6 52 | , directory < 1.4 53 | , process < 1.7 54 | 55 | library 56 | ghc-options: -O2 57 | exposed-modules: System.Entropy 58 | if impl(ghcjs) || os(ghcjs) 59 | other-modules: System.EntropyGhcjs 60 | else { 61 | if os(windows) 62 | other-modules: System.EntropyWindows 63 | else { 64 | other-modules: System.EntropyNix 65 | } 66 | } 67 | other-extensions: CPP, ForeignFunctionInterface, BangPatterns, 68 | ScopedTypeVariables 69 | build-depends: base >= 4.8 && < 5, bytestring 70 | 71 | default-language: Haskell2010 72 | 73 | if impl(ghcjs) || os(ghcjs) { 74 | build-depends: ghcjs-dom >= 0.9.5.0 && < 1 75 | , jsaddle 76 | } 77 | else { 78 | if arch(x86_64) 79 | cpp-options: -Darch_x86_64 80 | cc-options: -Darch_x86_64 -O2 81 | -- gcc 4.8.2 on i386 fails to compile rdrand.c when using -fPIC! 82 | c-sources: cbits/rdrand.c 83 | include-dirs: cbits 84 | if arch(i386) 85 | cpp-options: -Darch_i386 86 | cc-options: -Darch_i386 -O2 87 | if os(windows) 88 | build-depends: Win32 >= 2.5 89 | cpp-options: -DisWindows 90 | cc-options: -DisWindows 91 | extra-libraries: advapi32 92 | else 93 | Build-Depends: unix 94 | c-sources: cbits/getrandom.c cbits/random_initialized.c 95 | } 96 | if flag(DoNotGetEntropy) { 97 | cc-options: -DDO_NOT_USE_GET_ENTROPY 98 | } 99 | 100 | 101 | source-repository head 102 | type: git 103 | location: https://github.com/TomMD/entropy 104 | -------------------------------------------------------------------------------- /System/EntropyWindows.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP, ForeignFunctionInterface, BangPatterns, ScopedTypeVariables #-} 2 | {-| 3 | Maintainer: Thomas.DuBuisson@gmail.com 4 | Stability: beta 5 | Portability: portable 6 | 7 | Obtain entropy from system sources. 8 | -} 9 | 10 | module System.EntropyWindows 11 | ( CryptHandle 12 | , openHandle 13 | , hGetEntropy 14 | , closeHandle 15 | , hardwareRandom 16 | ) where 17 | 18 | import Control.Monad (liftM, when) 19 | import System.IO.Error (mkIOError, eofErrorType, ioeSetErrorString) 20 | import System.Win32.Types (ULONG_PTR, errorWin) 21 | import Foreign (allocaBytes) 22 | import Data.ByteString as B 23 | import Data.ByteString.Internal as BI 24 | import Data.Int (Int32) 25 | import Data.Bits (xor) 26 | import Data.Word (Word32, Word8) 27 | import Foreign.C.String (CString, withCString) 28 | import Foreign.C.Types 29 | import Foreign.Ptr (Ptr, nullPtr, castPtr) 30 | import Foreign.Marshal.Alloc (alloca) 31 | import Foreign.Marshal.Utils (toBool) 32 | import Foreign.Storable (peek) 33 | 34 | -- C example for windows rng - taken from a blog, can't recall which one but thank you! 35 | -- #include 36 | -- #include 37 | -- ... 38 | -- // 39 | -- // DISCLAIMER: Don't forget to check your error codes!! 40 | -- // I am not checking as to make the example simple... 41 | -- // 42 | -- HCRYPTPROV hCryptCtx = NULL; 43 | -- BYTE randomArray[128]; 44 | -- 45 | -- CryptAcquireContext(&hCryptCtx, NULL, MS_DEF_PROV, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT); 46 | -- CryptGenRandom(hCryptCtx, 128, randomArray); 47 | -- CryptReleaseContext(hCryptCtx, 0); 48 | 49 | 50 | #ifdef arch_i386 51 | -- See .cabal wrt GCC 4.8.2 asm compilation bug 52 | #undef HAVE_RDRAND 53 | #endif 54 | 55 | #ifdef HAVE_RDRAND 56 | foreign import ccall unsafe "cpu_has_rdrand" 57 | c_cpu_has_rdrand :: IO CInt 58 | 59 | foreign import ccall unsafe "get_rand_bytes" 60 | c_get_rand_bytes :: Ptr CUChar -> CSize -> IO CInt 61 | 62 | cpuHasRdRand :: IO Bool 63 | cpuHasRdRand = (/= 0) `fmap` c_cpu_has_rdrand 64 | #endif 65 | 66 | type HCRYPTPROV = ULONG_PTR 67 | data CryptHandle 68 | = CH HCRYPTPROV 69 | 70 | 71 | -- | Get random values from the hardware RNG or return Nothing if no 72 | -- supported hardware RNG is available. 73 | -- 74 | -- Supported hardware: 75 | -- * RDRAND 76 | -- * Patches welcome 77 | hardwareRandom :: Int -> IO (Maybe B.ByteString) 78 | #ifdef HAVE_RDRAND 79 | hardwareRandom n = 80 | do b <- cpuHasRdRand 81 | if b 82 | then Just <$> BI.create n (\ptr -> 83 | do r <- c_get_rand_bytes (castPtr ptr) (fromIntegral n) 84 | when (r /= 0) (fail "RDRand failed to gather entropy")) 85 | else pure Nothing 86 | #else 87 | hardwareRandom _ = pure Nothing 88 | #endif 89 | 90 | -- Define the constants we need from WinCrypt.h 91 | msDefProv :: String 92 | msDefProv = "Microsoft Base Cryptographic Provider v1.0" 93 | provRSAFull :: Word32 94 | provRSAFull = 1 95 | cryptVerifyContext :: Word32 96 | cryptVerifyContext = fromIntegral 0xF0000000 97 | 98 | -- Declare the required CryptoAPI imports 99 | foreign import stdcall unsafe "CryptAcquireContextA" 100 | c_cryptAcquireCtx :: Ptr HCRYPTPROV -> CString -> CString -> Word32 -> Word32 -> IO Int32 101 | foreign import stdcall unsafe "CryptGenRandom" 102 | c_cryptGenRandom :: HCRYPTPROV -> Word32 -> Ptr Word8 -> IO Int32 103 | foreign import stdcall unsafe "CryptReleaseContext" 104 | c_cryptReleaseCtx :: HCRYPTPROV -> Word32 -> IO Int32 105 | 106 | cryptAcquireCtx :: IO HCRYPTPROV 107 | cryptAcquireCtx = 108 | alloca $ \handlePtr -> 109 | withCString msDefProv $ \provName -> do 110 | stat <- c_cryptAcquireCtx handlePtr nullPtr provName provRSAFull cryptVerifyContext 111 | if (toBool stat) 112 | then peek handlePtr 113 | else errorWin "c_cryptAcquireCtx" 114 | 115 | cryptGenRandom :: HCRYPTPROV -> Int -> IO B.ByteString 116 | cryptGenRandom h i = 117 | BI.create i $ \c_buffer -> do 118 | stat <- c_cryptGenRandom h (fromIntegral i) c_buffer 119 | if (toBool stat) 120 | then return () 121 | else errorWin "c_cryptGenRandom" 122 | 123 | cryptReleaseCtx :: HCRYPTPROV -> IO () 124 | cryptReleaseCtx h = do 125 | stat <- c_cryptReleaseCtx h 0 126 | if (toBool stat) 127 | then return () 128 | else errorWin "c_cryptReleaseCtx" 129 | 130 | -- |Open a handle from which random data can be read 131 | openHandle :: IO CryptHandle 132 | openHandle = CH `fmap` cryptAcquireCtx 133 | 134 | -- |Close the `CryptHandle` 135 | closeHandle :: CryptHandle -> IO () 136 | closeHandle (CH h) = cryptReleaseCtx h 137 | 138 | -- |Read from `CryptHandle` 139 | hGetEntropy :: CryptHandle -> Int -> IO B.ByteString 140 | hGetEntropy (CH h) n = cryptGenRandom h n 141 | -------------------------------------------------------------------------------- /System/EntropyNix.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP, ForeignFunctionInterface, BangPatterns, ScopedTypeVariables #-} 2 | {-| 3 | Maintainer: Thomas.DuBuisson@gmail.com 4 | Stability: beta 5 | Portability: portable 6 | 7 | Obtain entropy from system sources or x86 RDRAND when available. 8 | 9 | -} 10 | 11 | module System.EntropyNix 12 | ( CryptHandle 13 | , openHandle 14 | , hGetEntropy 15 | , closeHandle 16 | , hardwareRandom 17 | ) where 18 | 19 | import Control.Exception 20 | import Control.Monad (liftM, when) 21 | import Data.ByteString as B 22 | import System.IO.Error (mkIOError, eofErrorType, ioeSetErrorString) 23 | import System.IO.Unsafe 24 | import Data.Bits (xor) 25 | 26 | import Foreign (allocaBytes) 27 | import Foreign.Ptr 28 | import Foreign.C.Error 29 | import Foreign.C.Types 30 | import Data.ByteString.Internal as B 31 | 32 | #ifdef arch_i386 33 | -- See .cabal wrt GCC 4.8.2 asm compilation bug 34 | #undef HAVE_RDRAND 35 | #endif 36 | 37 | import System.Posix (openFd, closeFd, fdReadBuf, OpenMode(..), defaultFileFlags, Fd, OpenFileFlags(..)) 38 | 39 | source :: FilePath 40 | source = "/dev/urandom" 41 | 42 | -- |Handle for manual resource management 43 | data CryptHandle 44 | = CH Fd 45 | #ifdef HAVE_GETRANDOM 46 | | UseGetRandom 47 | #endif 48 | 49 | -- | Get random values from the hardware RNG or return Nothing if no 50 | -- supported hardware RNG is available. 51 | -- 52 | -- Supported hardware: 53 | -- * RDRAND 54 | -- * Patches welcome 55 | hardwareRandom :: Int -> IO (Maybe B.ByteString) 56 | #ifdef HAVE_RDRAND 57 | hardwareRandom n = 58 | do b <- cpuHasRdRand 59 | if b then Just <$> B.create n (\ptr -> 60 | do r <- c_get_rand_bytes (castPtr ptr) (fromIntegral n) 61 | when (r /= 0) (fail "RDRand failed to gather entropy")) 62 | else pure Nothing 63 | #else 64 | hardwareRandom _ = pure Nothing 65 | #endif 66 | 67 | -- |Open a `CryptHandle` 68 | openHandle :: IO CryptHandle 69 | openHandle = 70 | #ifdef HAVE_GETRANDOM 71 | if systemHasGetRandom then return UseGetRandom else 72 | #endif 73 | fmap CH openRandomFile 74 | 75 | openRandomFile :: IO Fd 76 | openRandomFile = do 77 | evaluate ensurePoolInitialized 78 | #if MIN_VERSION_unix(2,8,0) 79 | openFd source ReadOnly defaultFileFlags { creat = Nothing } 80 | #else 81 | openFd source ReadOnly Nothing defaultFileFlags 82 | #endif 83 | 84 | -- |Close the `CryptHandle` 85 | closeHandle :: CryptHandle -> IO () 86 | closeHandle (CH h) = closeFd h 87 | #ifdef HAVE_GETRANDOM 88 | closeHandle UseGetRandom = return () 89 | #endif 90 | 91 | -- |Read random data from a `CryptHandle` 92 | hGetEntropy :: CryptHandle -> Int -> IO B.ByteString 93 | hGetEntropy (CH h) n = fdReadBS h n 94 | #ifdef HAVE_GETRANDOM 95 | hGetEntropy UseGetRandom n = do 96 | bs <- B.createUptoN n (\ptr -> do 97 | r <- c_entropy_getrandom (castPtr ptr) (fromIntegral n) 98 | return $ if r == 0 then n else 0) 99 | if B.length bs == n then return bs 100 | -- getrandom somehow failed. Fall back on /dev/urandom instead. 101 | else bracket openRandomFile closeFd $ flip fdReadBS n 102 | #endif 103 | 104 | fdReadBS :: Fd -> Int -> IO B.ByteString 105 | fdReadBS fd n = 106 | allocaBytes n $ \buf -> go buf n 107 | where 108 | go buf 0 = B.packCStringLen (castPtr buf, fromIntegral n) 109 | go buf cnt | cnt <= n = do 110 | rc <- fdReadBuf fd (plusPtr buf (n - cnt)) (fromIntegral cnt) 111 | case rc of 112 | 0 -> ioError (ioeSetErrorString (mkIOError eofErrorType "fdRead" Nothing Nothing) "EOF") 113 | n' -> go buf (cnt - fromIntegral n') 114 | go _ _ = error "Impossible! The count of bytes left to read is greater than the request or less than zero!" 115 | 116 | #ifdef HAVE_GETRANDOM 117 | foreign import ccall unsafe "system_has_getrandom" 118 | c_system_has_getrandom :: IO CInt 119 | foreign import ccall safe "entropy_getrandom" 120 | c_entropy_getrandom :: Ptr CUChar -> CSize -> IO CInt 121 | 122 | -- NOINLINE and unsafePerformIO are not totally necessary as getrandom will be 123 | -- consistently either present or not, but it is cheaper not to check multiple 124 | -- times. 125 | systemHasGetRandom :: Bool 126 | {-# NOINLINE systemHasGetRandom #-} 127 | systemHasGetRandom = unsafePerformIO $ fmap (/= 0) c_system_has_getrandom 128 | #endif 129 | 130 | foreign import ccall safe "ensure_pool_initialized" 131 | c_ensure_pool_initialized :: IO CInt 132 | 133 | -- Similarly to systemHasGetRandom, NOINLINE is just an optimization. 134 | ensurePoolInitialized :: CInt 135 | {-# NOINLINE ensurePoolInitialized #-} 136 | ensurePoolInitialized = unsafePerformIO $ throwErrnoIfMinus1 "ensurePoolInitialized" $ c_ensure_pool_initialized 137 | 138 | #ifdef HAVE_RDRAND 139 | foreign import ccall unsafe "cpu_has_rdrand" 140 | c_cpu_has_rdrand :: IO CInt 141 | 142 | foreign import ccall unsafe "get_rand_bytes" 143 | c_get_rand_bytes :: Ptr CUChar -> CSize -> IO CInt 144 | 145 | cpuHasRdRand :: IO Bool 146 | cpuHasRdRand = (/= 0) `fmap` c_cpu_has_rdrand 147 | #endif 148 | -------------------------------------------------------------------------------- /Setup.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP #-} 2 | import Control.Monad 3 | import Distribution.Simple 4 | import Distribution.Simple.LocalBuildInfo 5 | import Distribution.Simple.Setup 6 | import Distribution.PackageDescription 7 | import Distribution.Simple.Utils 8 | import Distribution.Simple.Program 9 | import Distribution.Verbosity 10 | import System.Process 11 | import System.Directory 12 | import System.FilePath 13 | import System.Exit 14 | import System.IO 15 | 16 | main = defaultMainWithHooks hk 17 | where 18 | hk = simpleUserHooks { buildHook = \pd lbi uh bf -> do 19 | -- let ccProg = Program "gcc" undefined undefined undefined 20 | let mConf = lookupProgram ghcProgram (withPrograms lbi) 21 | err = error "Could not determine C compiler" 22 | cc = locationPath . programLocation . maybe err id $ mConf 23 | lbiNew <- checkRDRAND cc lbi >>= checkGetrandom cc >>= checkGetentropy cc 24 | buildHook simpleUserHooks pd lbiNew uh bf 25 | } 26 | 27 | compileCheck :: FilePath -> String -> String -> String -> IO Bool 28 | compileCheck cc testName message sourceCode = do 29 | withTempDirectory normal "" testName $ \tmpDir -> do 30 | writeFile (tmpDir ++ "/" ++ testName ++ ".c") sourceCode 31 | ec <- myRawSystemExitCode normal cc [tmpDir testName ++ ".c", "-o", tmpDir ++ "/a","-no-hs-main"] 32 | notice normal $ message ++ show (ec == ExitSuccess) 33 | return (ec == ExitSuccess) 34 | 35 | addOptions :: [String] -> [String] -> LocalBuildInfo -> LocalBuildInfo 36 | addOptions cArgs hsArgs lbi = lbi {withPrograms = newWithPrograms } 37 | where newWithPrograms1 = userSpecifyArgs "gcc" cArgs (withPrograms lbi) 38 | newWithPrograms = userSpecifyArgs "ghc" (hsArgs ++ map ("-optc" ++) cArgs) newWithPrograms1 39 | 40 | checkRDRAND :: FilePath -> LocalBuildInfo -> IO LocalBuildInfo 41 | checkRDRAND cc lbi = do 42 | b <- compileCheck cc "testRDRAND" "Result of RDRAND Test: " 43 | (unlines [ "#include " 44 | , "int main() {" 45 | , " uint64_t therand;" 46 | , " unsigned char err;" 47 | , " asm volatile(\"rdrand %0 ; setc %1\"" 48 | , " : \"=r\" (therand), \"=qm\" (err));" 49 | , " return (!err);" 50 | , "}" 51 | ]) 52 | return $ if b then addOptions cArgs cArgs lbi else lbi 53 | where cArgs = ["-DHAVE_RDRAND"] 54 | 55 | checkGetrandom :: FilePath -> LocalBuildInfo -> IO LocalBuildInfo 56 | checkGetrandom cc lbi = do 57 | libcGetrandom <- compileCheck cc "testLibcGetrandom" "Result of libc getrandom() Test: " 58 | (unlines [ "#define _GNU_SOURCE" 59 | , "#include " 60 | , "#include " 61 | 62 | , "int main()" 63 | , "{" 64 | , " char tmp;" 65 | , " return getrandom(&tmp, sizeof(tmp), GRND_NONBLOCK) != -1;" 66 | , "}" 67 | ]) 68 | if libcGetrandom then return $ addOptions cArgsLibc cArgsLibc lbi 69 | else do 70 | syscallGetrandom <- compileCheck cc "testSyscallGetrandom" "Result of syscall getrandom() Test: " 71 | (unlines [ "#define _GNU_SOURCE" 72 | , "#include " 73 | , "#include " 74 | , "#include " 75 | , "#include " 76 | , "#include " 77 | 78 | , "static ssize_t getrandom(void* buf, size_t buflen, unsigned int flags)" 79 | , "{" 80 | , " return syscall(SYS_getrandom, buf, buflen, flags);" 81 | , "}" 82 | 83 | , "int main()" 84 | , "{" 85 | , " char tmp;" 86 | , " return getrandom(&tmp, sizeof(tmp), GRND_NONBLOCK) != -1;" 87 | , "}" 88 | ]) 89 | return $ if syscallGetrandom then addOptions cArgs cArgs lbi else lbi 90 | where cArgs = ["-DHAVE_GETRANDOM"] 91 | cArgsLibc = cArgs ++ ["-DHAVE_LIBC_GETRANDOM"] 92 | 93 | checkGetentropy :: FilePath -> LocalBuildInfo -> IO LocalBuildInfo 94 | checkGetentropy cc lbi = do 95 | b <- compileCheck cc "testGetentropy" "Result of getentropy() Test: " 96 | (unlines [ "#define _GNU_SOURCE" 97 | , "#include " 98 | 99 | , "int main()" 100 | , "{" 101 | , " char tmp;" 102 | , " return getentropy(&tmp, sizeof(tmp));" 103 | , "}" 104 | ]) 105 | return $ if b then addOptions cArgs cArgs lbi else lbi 106 | where cArgs = ["-DHAVE_GETENTROPY"] 107 | 108 | myRawSystemExitCode :: Verbosity -> FilePath -> [String] -> IO ExitCode 109 | #if MIN_VERSION_Cabal(3,14,0) 110 | myRawSystemExitCode verbosity program arguments = 111 | rawSystemExitCode verbosity Nothing program arguments Nothing 112 | #elif __GLASGOW_HASKELL__ >= 704 113 | -- We know for sure, that if GHC >= 7.4 implies Cabal >= 1.14 114 | myRawSystemExitCode = rawSystemExitCode 115 | #else 116 | -- Legacy branch: 117 | -- We implement our own 'rawSystemExitCode', this will even work if 118 | -- the user happens to have Cabal >= 1.14 installed with GHC 7.0 or 119 | -- 7.2 120 | myRawSystemExitCode verbosity path args = do 121 | printRawCommandAndArgs verbosity path args 122 | hFlush stdout 123 | exitcode <- rawSystem path args 124 | unless (exitcode == ExitSuccess) $ do 125 | debug verbosity $ path ++ " returned " ++ show exitcode 126 | return exitcode 127 | where 128 | printRawCommandAndArgs :: Verbosity -> FilePath -> [String] -> IO () 129 | printRawCommandAndArgs verbosity path args 130 | | verbosity >= deafening = print (path, args) 131 | | verbosity >= verbose = putStrLn $ unwords (path : args) 132 | | otherwise = return () 133 | #endif 134 | -------------------------------------------------------------------------------- /.github/workflows/haskell-ci.yml: -------------------------------------------------------------------------------- 1 | # This GitHub workflow config has been generated by a script via 2 | # 3 | # haskell-ci 'github' 'entropy.cabal' 4 | # 5 | # To regenerate the script (for example after adjusting tested-with) run 6 | # 7 | # haskell-ci regenerate 8 | # 9 | # For more information, see https://github.com/andreasabel/haskell-ci 10 | # 11 | # version: 0.19.20241219 12 | # 13 | # REGENDATA ("0.19.20241219",["github","entropy.cabal"]) 14 | # 15 | name: Haskell-CI 16 | on: 17 | push: 18 | branches: 19 | - main 20 | pull_request: 21 | branches: 22 | - main 23 | jobs: 24 | linux: 25 | name: Haskell-CI - Linux - ${{ matrix.compiler }} 26 | runs-on: ubuntu-20.04 27 | timeout-minutes: 28 | 60 29 | container: 30 | image: buildpack-deps:jammy 31 | continue-on-error: ${{ matrix.allow-failure }} 32 | strategy: 33 | matrix: 34 | include: 35 | - compiler: ghc-9.12.1 36 | compilerKind: ghc 37 | compilerVersion: 9.12.1 38 | setup-method: ghcup 39 | allow-failure: false 40 | - compiler: ghc-9.10.1 41 | compilerKind: ghc 42 | compilerVersion: 9.10.1 43 | setup-method: ghcup 44 | allow-failure: false 45 | - compiler: ghc-9.8.4 46 | compilerKind: ghc 47 | compilerVersion: 9.8.4 48 | setup-method: ghcup 49 | allow-failure: false 50 | - compiler: ghc-9.6.6 51 | compilerKind: ghc 52 | compilerVersion: 9.6.6 53 | setup-method: ghcup 54 | allow-failure: false 55 | - compiler: ghc-9.4.8 56 | compilerKind: ghc 57 | compilerVersion: 9.4.8 58 | setup-method: ghcup 59 | allow-failure: false 60 | - compiler: ghc-9.2.8 61 | compilerKind: ghc 62 | compilerVersion: 9.2.8 63 | setup-method: ghcup 64 | allow-failure: false 65 | - compiler: ghc-9.0.2 66 | compilerKind: ghc 67 | compilerVersion: 9.0.2 68 | setup-method: ghcup 69 | allow-failure: false 70 | - compiler: ghc-8.10.7 71 | compilerKind: ghc 72 | compilerVersion: 8.10.7 73 | setup-method: ghcup 74 | allow-failure: false 75 | - compiler: ghc-8.8.4 76 | compilerKind: ghc 77 | compilerVersion: 8.8.4 78 | setup-method: ghcup 79 | allow-failure: false 80 | - compiler: ghc-8.6.5 81 | compilerKind: ghc 82 | compilerVersion: 8.6.5 83 | setup-method: ghcup 84 | allow-failure: false 85 | - compiler: ghc-8.4.4 86 | compilerKind: ghc 87 | compilerVersion: 8.4.4 88 | setup-method: ghcup 89 | allow-failure: false 90 | - compiler: ghc-8.2.2 91 | compilerKind: ghc 92 | compilerVersion: 8.2.2 93 | setup-method: ghcup 94 | allow-failure: false 95 | fail-fast: false 96 | steps: 97 | - name: apt-get install 98 | run: | 99 | apt-get update 100 | apt-get install -y --no-install-recommends gnupg ca-certificates dirmngr curl git software-properties-common libtinfo5 libnuma-dev 101 | - name: Install GHCup 102 | run: | 103 | mkdir -p "$HOME/.ghcup/bin" 104 | curl -sL https://downloads.haskell.org/ghcup/0.1.30.0/x86_64-linux-ghcup-0.1.30.0 > "$HOME/.ghcup/bin/ghcup" 105 | chmod a+x "$HOME/.ghcup/bin/ghcup" 106 | - name: Install cabal-install (prerelease) 107 | run: | 108 | "$HOME/.ghcup/bin/ghcup" config add-release-channel https://raw.githubusercontent.com/haskell/ghcup-metadata/master/ghcup-prereleases-0.0.8.yaml; 109 | "$HOME/.ghcup/bin/ghcup" install cabal 3.15.0.0.2024.10.3 || (cat "$HOME"/.ghcup/logs/*.* && false) 110 | echo "CABAL=$HOME/.ghcup/bin/cabal-3.15.0.0.2024.10.3 -vnormal+nowrap" >> "$GITHUB_ENV" 111 | - name: Install GHC (GHCup) 112 | if: matrix.setup-method == 'ghcup' 113 | run: | 114 | "$HOME/.ghcup/bin/ghcup" install ghc "$HCVER" || (cat "$HOME"/.ghcup/logs/*.* && false) 115 | HC=$("$HOME/.ghcup/bin/ghcup" whereis ghc "$HCVER") 116 | HCPKG=$(echo "$HC" | sed 's#ghc$#ghc-pkg#') 117 | HADDOCK=$(echo "$HC" | sed 's#ghc$#haddock#') 118 | echo "HC=$HC" >> "$GITHUB_ENV" 119 | echo "HCPKG=$HCPKG" >> "$GITHUB_ENV" 120 | echo "HADDOCK=$HADDOCK" >> "$GITHUB_ENV" 121 | env: 122 | HCKIND: ${{ matrix.compilerKind }} 123 | HCNAME: ${{ matrix.compiler }} 124 | HCVER: ${{ matrix.compilerVersion }} 125 | - name: Set PATH and environment variables 126 | run: | 127 | echo "$HOME/.cabal/bin" >> $GITHUB_PATH 128 | echo "LANG=C.UTF-8" >> "$GITHUB_ENV" 129 | echo "CABAL_DIR=$HOME/.cabal" >> "$GITHUB_ENV" 130 | echo "CABAL_CONFIG=$HOME/.cabal/config" >> "$GITHUB_ENV" 131 | HCNUMVER=$(${HC} --numeric-version|perl -ne '/^(\d+)\.(\d+)\.(\d+)(\.(\d+))?$/; print(10000 * $1 + 100 * $2 + ($3 == 0 ? $5 != 1 : $3))') 132 | echo "HCNUMVER=$HCNUMVER" >> "$GITHUB_ENV" 133 | echo "ARG_TESTS=--enable-tests" >> "$GITHUB_ENV" 134 | echo "ARG_BENCH=--enable-benchmarks" >> "$GITHUB_ENV" 135 | if [ $((HCNUMVER >= 91200)) -ne 0 ] ; then echo "HEADHACKAGE=true" >> "$GITHUB_ENV" ; else echo "HEADHACKAGE=false" >> "$GITHUB_ENV" ; fi 136 | echo "ARG_COMPILER=--$HCKIND --with-compiler=$HC" >> "$GITHUB_ENV" 137 | env: 138 | HCKIND: ${{ matrix.compilerKind }} 139 | HCNAME: ${{ matrix.compiler }} 140 | HCVER: ${{ matrix.compilerVersion }} 141 | - name: env 142 | run: | 143 | env 144 | - name: write cabal config 145 | run: | 146 | mkdir -p $CABAL_DIR 147 | cat >> $CABAL_CONFIG <> $CABAL_CONFIG <> $CABAL_CONFIG < cabal-plan.xz 192 | echo 'f62ccb2971567a5f638f2005ad3173dba14693a45154c1508645c52289714cb2 cabal-plan.xz' | sha256sum -c - 193 | xz -d < cabal-plan.xz > $HOME/.cabal/bin/cabal-plan 194 | rm -f cabal-plan.xz 195 | chmod a+x $HOME/.cabal/bin/cabal-plan 196 | cabal-plan --version 197 | - name: checkout 198 | uses: actions/checkout@v4 199 | with: 200 | path: source 201 | - name: initial cabal.project for sdist 202 | run: | 203 | touch cabal.project 204 | echo "packages: $GITHUB_WORKSPACE/source/." >> cabal.project 205 | cat cabal.project 206 | - name: sdist 207 | run: | 208 | mkdir -p sdist 209 | $CABAL sdist all --output-dir $GITHUB_WORKSPACE/sdist 210 | - name: unpack 211 | run: | 212 | mkdir -p unpacked 213 | find sdist -maxdepth 1 -type f -name '*.tar.gz' -exec tar -C $GITHUB_WORKSPACE/unpacked -xzvf {} \; 214 | - name: generate cabal.project 215 | run: | 216 | PKGDIR_entropy="$(find "$GITHUB_WORKSPACE/unpacked" -maxdepth 1 -type d -regex '.*/entropy-[0-9.]*')" 217 | echo "PKGDIR_entropy=${PKGDIR_entropy}" >> "$GITHUB_ENV" 218 | rm -f cabal.project cabal.project.local 219 | touch cabal.project 220 | touch cabal.project.local 221 | echo "packages: ${PKGDIR_entropy}" >> cabal.project 222 | echo "package entropy" >> cabal.project 223 | echo " ghc-options: -Werror=missing-methods" >> cabal.project 224 | cat >> cabal.project <> cabal.project 228 | fi 229 | $HCPKG list --simple-output --names-only | perl -ne 'for (split /\s+/) { print "constraints: any.$_ installed\n" unless /^(entropy)$/; }' >> cabal.project.local 230 | cat cabal.project 231 | cat cabal.project.local 232 | - name: dump install plan 233 | run: | 234 | $CABAL v2-build $ARG_COMPILER $ARG_TESTS $ARG_BENCH --dry-run all 235 | cabal-plan 236 | - name: restore cache 237 | uses: actions/cache/restore@v4 238 | with: 239 | key: ${{ runner.os }}-${{ matrix.compiler }}-${{ github.sha }} 240 | path: ~/.cabal/store 241 | restore-keys: ${{ runner.os }}-${{ matrix.compiler }}- 242 | - name: install dependencies 243 | run: | 244 | $CABAL v2-build $ARG_COMPILER --disable-tests --disable-benchmarks --dependencies-only -j2 all 245 | $CABAL v2-build $ARG_COMPILER $ARG_TESTS $ARG_BENCH --dependencies-only -j2 all 246 | - name: build w/o tests 247 | run: | 248 | $CABAL v2-build $ARG_COMPILER --disable-tests --disable-benchmarks all 249 | - name: build 250 | run: | 251 | $CABAL v2-build $ARG_COMPILER $ARG_TESTS $ARG_BENCH all --write-ghc-environment-files=always 252 | - name: haddock 253 | run: | 254 | $CABAL v2-haddock --disable-documentation --haddock-all $ARG_COMPILER --with-haddock $HADDOCK $ARG_TESTS $ARG_BENCH all 255 | - name: unconstrained build 256 | run: | 257 | rm -f cabal.project.local 258 | $CABAL v2-build $ARG_COMPILER --disable-tests --disable-benchmarks all 259 | - name: save cache 260 | if: always() 261 | uses: actions/cache/save@v4 262 | with: 263 | key: ${{ runner.os }}-${{ matrix.compiler }}-${{ github.sha }} 264 | path: ~/.cabal/store 265 | --------------------------------------------------------------------------------