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 +4/−3
- src/Web/ClientSession.hs +62/−12
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