clientsession 0.7.3.4 → 0.7.3.5
raw patch · 2 files changed
+56/−27 lines, 2 filesdep +cprng-aesdep ~hspecPVP ok
version bump matches the API change (PVP)
Dependencies added: cprng-aes
Dependency ranges changed: hspec
API changes (from Hackage documentation)
Files
- clientsession.cabal +3/−2
- src/Web/ClientSession.hs +53/−25
clientsession.cabal view
@@ -1,5 +1,5 @@ name: clientsession-version: 0.7.3.4+version: 0.7.3.5 license: BSD3 license-file: LICENSE author: Michael Snoyman <michael@snoyman.com>, Felipe Lessa <felipe.lessa@gmail.com>@@ -30,6 +30,7 @@ , skein >= 0.1 && < 0.2 , base64-bytestring >= 0.1.0.3 && < 0.2 , entropy >= 0.2.1+ , cprng-aes >= 0.2 exposed-modules: Web.ClientSession ghc-options: -Wall hs-source-dirs: src@@ -39,7 +40,7 @@ build-depends: base >=4 && < 5 , bytestring >= 0.9 && < 0.10 , cryptocipher >= 0.2.5 && < 0.3- , hspec == 0.6.*+ , hspec >= 0.6 && < 0.10 , QuickCheck >= 2 && < 3 , HUnit , transformers
src/Web/ClientSession.hs view
@@ -59,7 +59,6 @@ import qualified Data.IORef as I import System.IO.Unsafe (unsafePerformIO) import Control.Concurrent (forkIO)-import Control.Applicative ((<$>)) -- from directory import System.Directory (doesFileExist)@@ -72,8 +71,8 @@ import Data.Serialize (encode, decode) -- from crypto-api-import Crypto.Classes (buildKey, encryptBlock)-import Crypto.Random (newGenIO, genBytes, SystemRandom)+import Crypto.Classes (buildKey)+import Crypto.Random (reseed) import qualified Crypto.Modes as Modes -- from cryptocipher@@ -85,6 +84,9 @@ -- from entropy import System.Entropy (getEntropy) +-- from cprng-aes+import Crypto.Random.AESCtr (AESRNG, makeSystem, genRandomBytes)+ -- | The keys used to store the cookies. We have an AES key used -- to encrypt the cookie and a Skein-MAC-512-256 key used verify -- the authencity and integrity of the cookie. The AES key needs@@ -145,18 +147,12 @@ S.writeFile keyFile bs return key' --- | Generate the given number of random bytes.-randomBytes :: Int -> IO S.ByteString-randomBytes len = do- g <- newGenIO- either (error . show) (return . fst) $ genBytes len (g :: SystemRandom)- -- | Generate a random 'Key'. Besides the 'Key', the -- 'ByteString' passed to 'initKey' is returned so that it can be -- saved for later use. randomKey :: IO (S.ByteString, Key) randomKey = do- bs <- randomBytes 96+ bs <- getEntropy 96 case initKey bs of Left e -> error $ "Web.ClientSession.randomKey: never here, " ++ e Right key -> return (bs, key)@@ -221,31 +217,63 @@ S.length s1 == S.length s2 && foldl' (.|.) 0 (S.zipWith xor s1 s2) == 0 --- Significantly more efficien random IV generation. Initial benchmarks placed--- it at 7.144764 us versus 1.686288 ms for Modes.getIVIO, since it does not--- require /dev/urandom I/O for every call.+-- Significantly more efficient random IV generation. Initial+-- benchmarks placed it at 6.06 us versus 1.69 ms for Modes.getIVIO,+-- since it does not require /dev/urandom I/O for every call. -data AESState = ASt {-# UNPACK #-} !A.AES256- {-# UNPACK #-} !(Modes.IV A.AES256)+data AESState =+ ASt {-# UNPACK #-} !AESRNG -- Our CPRNG using AES on CTR mode+ {-# UNPACK #-} !Int -- How many IVs were generated with this+ -- AESRNG. Used to control reseeding. +-- | Construct initial state of the CPRNG. aesSeed :: IO AESState aesSeed = do- Just key <- buildKey <$> getEntropy 32- return $! ASt key Modes.zeroIV+ rng <- makeSystem+ return $! ASt rng 0 +-- | Reseed the CPRNG with new entropy from the system pool.+aesReseed :: IO ()+aesReseed = do+ ent <- getEntropy 64 -- XXX: Is it possible for getEntropy+ -- to return less entropy than+ -- than we asked it? I assume "no",+ -- but there's a check here just+ -- in case.+ if S.length ent < 64+ then I.atomicModifyIORef aesRef $+ -- Use the old RNG, but force a reseed+ -- after another 'threshold' uses of it.+ \(ASt rng _) -> (ASt rng 0, ())+ else I.atomicModifyIORef aesRef $+ \(ASt rng _) ->+ let Right rng' = reseed ent rng+ in (ASt rng' 0, ())++-- | 'IORef' that keeps the current state of the CPRNG. Yep,+-- global state. Used in thread-safe was only, though. aesRef :: I.IORef AESState aesRef = unsafePerformIO $ aesSeed >>= I.newIORef+{-# NOINLINE aesRef #-} +-- | Construct a new 16-byte IV using our CPRNG. Forks another+-- thread to reseed the CPRNG should its usage count reach a+-- hardcoded threshold. aesRNG :: IO IV aesRNG = do- ASt key count <-- I.atomicModifyIORef aesRef $ \t@(ASt key' count') ->- (ASt key' (Modes.incIV count'), t)- when (count == threshold) $ void $ forkIO $ aesSeed >>= I.writeIORef aesRef- let nextiv = encryptBlock key $ encode count- either (error . show) return $ decode nextiv+ (bs, count) <-+ I.atomicModifyIORef aesRef $ \(ASt rng count) ->+ let (bs', rng') = genRandomBytes rng 16+ in (ASt rng' (succ count), (bs', count))+ when (count == threshold) $ void $ forkIO aesReseed+ either (error . show) return $ decode bs where void f = f >> return () -threshold :: Modes.IV A.AES256-threshold = iterate Modes.incIV Modes.zeroIV !! 1000+-- | How many IVs should be generated before reseeding the CPRNG.+-- This number depends basically on how paranoid you are. We+-- think 100.000 is a good compromise: larger numbers give only a+-- small performance advantage, while it still is a small number+-- since we only generate 1.5 MiB of random data between reseeds.+threshold :: Int+threshold = 100000