packages feed

clientsession 0.7.1 → 0.7.2

raw patch · 2 files changed

+66/−15 lines, 2 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- Web.ClientSession: defaultKeyFile :: String
+ Web.ClientSession: defaultKeyFile :: FilePath

Files

clientsession.cabal view
@@ -1,12 +1,13 @@ name:            clientsession-version:         0.7.1+version:         0.7.2 license:         BSD3 license-file:    LICENSE author:          Michael Snoyman <michael@snoyman.com> maintainer:      Michael Snoyman <michael@snoyman.com> synopsis:        Store session data in a cookie.-description:     Achieves security through AES encryption and MD5 hashing.-                 Uses base64 encoding to avoid any issues with characters.+description:     Achieves security through AES-CBC encryption and+                 HMAC-SHA256 authentication.  Uses Base64+                 encoding to avoid any issues with characters. category:        Web stability:       stable cabal-version:   >= 1.8
src/Web/ClientSession.hs view
@@ -3,6 +3,8 @@ {-# LANGUAGE TemplateHaskell #-} --------------------------------------------------------- --+-- |+-- -- Module        : Web.ClientSession -- Copyright     : Michael Snoyman -- License       : BSD3@@ -11,8 +13,28 @@ -- Stability     : Stable -- Portability   : portable ----- Stores session data in a client cookie.+-- Stores session data in a client cookie.  In order to do so,+-- we: --+-- * Encrypt the cookie data using AES in CBC mode.  This allows+-- you to store sensitive information on the client side without+-- worrying about eavesdropping.+--+-- * Sign the encrypted cookie data using HMAC-SHA256.  Besides+-- detecting potential errors in storage or transmission of the+-- cookies (integrity), the HMAC-SHA256 code also avoids+-- malicious modifications of the cookie data by assuring you+-- that the cookie data really was generated by this server+-- (authentication).+--+-- * Encode everything using Base64.  Thus we avoid problems with+-- non-printable characters by giving the browser a simple+-- string.+--+-- Simple usage of the library involves just calling+-- 'getDefaultKey' on the startup of your server, 'encryptIO'+-- when serializing cookies and 'decrypt' when parsing then back.+-- --------------------------------------------------------- module Web.ClientSession     ( -- * Automatic key generation@@ -42,23 +64,37 @@ import Crypto.Random (newGenIO, genBytes, SystemRandom) import Data.Serialize (encode) +-- | The keys used to store the cookies.  We have an AES key used+-- to encrypt the cookie and a HMAC-SHA256 key used verify the+-- authencity and integrity of the cookie.  The AES key needs to+-- have exactly 32 bytes (256 bits).  The HMAC-SHA256 should have+-- 64 bytes (512 bits), which is the block size of SHA256, but+-- any size may be used.+--+-- See also 'getDefaultKey' and 'initKey'. data Key = Key { aesKey  :: A.Key                , hmacKey :: MacKey }          deriving (Eq, Show) +-- | The initialization vector used by AES in CBC mode.  Should+-- be exactly 16 bytes long. newtype IV = IV S.ByteString     deriving Show +-- | Construct an initialization vector from a 'S.ByteString'.+-- Fails if there isn't exactly 16 bytes. mkIV :: S.ByteString -> Maybe IV mkIV bs     | S.length bs == 16 = Just $ IV bs     | otherwise = Nothing +-- | Randomly construct a fresh initialization vector.  You+-- /should not/ reuse initialization vectors. randomIV :: IO IV randomIV = fmap IV $ randomBytes 16  -- | The default key file.-defaultKeyFile :: String+defaultKeyFile :: FilePath defaultKeyFile = "client_session_key.aes"  -- | Simply calls 'getKey' 'defaultKeyFile'.@@ -67,8 +103,8 @@  -- | Get a key from the given text file. ----- If the file does not exist a random key will be generated and stored in that--- file.+-- If the file does not exist or is corrupted a random key will+-- be generated and stored in that file. getKey :: FilePath     -- ^ File name where key is stored.        -> IO Key       -- ^ The actual key. getKey keyFile = do@@ -82,16 +118,20 @@         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 64     case initKey bs of-        Left e -> error e -- should never happen+        Left e -> error $ "Web.ClientSession.randomKey: never here, " ++ e         Right key -> return (bs, key)  -- | Initializes a 'Key' from a random 'S.ByteString'.  It's@@ -110,15 +150,21 @@                  -- have exactly 512 bits, the size of the block                  -- used in SHA-256.  hmac' already deals with it. +-- | Same as 'encrypt', however randomly generates the+-- initialization vector for you. encryptIO :: Key -> S.ByteString -> IO S.ByteString encryptIO key x = do     iv <- randomIV     return $ encrypt key iv x -encrypt :: Key-        -> IV-        -> S.ByteString -- ^ data-        -> S.ByteString+-- | Encrypt (AES-CBC), sign (HMAC-SHA256) and encode (Base64)+-- the given cookie data.  The returned byte string is ready to+-- be used in a response header.+encrypt :: Key          -- ^ Key of the server.+        -> IV           -- ^ New, random initialization vector (see 'randomIV').+        -> S.ByteString -- ^ Serialized cookie data.+        -> S.ByteString -- ^ Encoded cookie data to be given to+                        -- the client browser. encrypt key (IV iv) x =     B.encode $ S.concat [iv, encode auth, encrypted]   where@@ -128,9 +174,13 @@     encrypted = A.encryptCBC (aesKey key) iv y     auth = hmac' (hmacKey key) encrypted :: SHA256 -decrypt :: Key -- ^ key-        -> S.ByteString -- ^ data-        -> Maybe S.ByteString+-- | Decode (Base64), verify the integrity and authenticity+-- (HMAC-SHA256) and decrypt (AES-CBC) the given encoded cookie+-- data.  Returns the original serialized cookie data.  Fails if+-- the data is corrupted.+decrypt :: Key                -- ^ Key of the server.+        -> S.ByteString       -- ^ Encoded cookie data given by the browser.+        -> Maybe S.ByteString -- ^ Serialized cookie data. decrypt key dataBS64 = do     dataBS <- either (const Nothing) Just $ B.decode dataBS64     if S.length dataBS `mod` 16 /= 0 || S.length dataBS < 48