packages feed

clientsession 0.7.2 → 0.7.3

raw patch · 2 files changed

+83/−72 lines, 2 filesdep +skeindep −cryptohashPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: skein

Dependencies removed: cryptohash

API changes (from Hackage documentation)

- Web.ClientSession: data IV
- Web.ClientSession: hmacKey :: Key -> MacKey
- Web.ClientSession: instance Eq Key
- Web.ClientSession: instance Show IV
+ Web.ClientSession: macKey :: Key -> ByteString -> Skein_512_256
+ Web.ClientSession: type IV = IV AES256
- Web.ClientSession: Key :: Key -> MacKey -> Key
+ Web.ClientSession: Key :: AES256 -> (ByteString -> Skein_512_256) -> Key
- Web.ClientSession: aesKey :: Key -> Key
+ Web.ClientSession: aesKey :: Key -> AES256

Files

clientsession.cabal view
@@ -1,12 +1,12 @@ name:            clientsession-version:         0.7.2+version:         0.7.3 license:         BSD3 license-file:    LICENSE-author:          Michael Snoyman <michael@snoyman.com>+author:          Michael Snoyman <michael@snoyman.com>, Felipe Lessa <felipe.lessa@gmail.com> maintainer:      Michael Snoyman <michael@snoyman.com>-synopsis:        Store session data in a cookie.-description:     Achieves security through AES-CBC encryption and-                 HMAC-SHA256 authentication.  Uses Base64+synopsis:        Securely store session data in a client-side cookie.+description:     Achieves security through AES-CTR encryption and+                 Skein-MAC-512-256 authentication.  Uses Base64                  encoding to avoid any issues with characters. category:        Web stability:       stable@@ -25,7 +25,7 @@                    , directory           >= 1          && < 1.2                    , crypto-api          >= 0.6.4      && < 0.7                    , cryptocipher        >= 0.2.5      && < 0.3-                   , cryptohash          >= 0.7.1      && < 0.8+                   , skein               >= 0.1        && < 0.2                    , base64-bytestring   >= 0.1.0.3    && < 0.2     exposed-modules: Web.ClientSession     ghc-options:     -Wall
src/Web/ClientSession.hs view
@@ -16,16 +16,16 @@ -- Stores session data in a client cookie.  In order to do so, -- we: ----- * Encrypt the cookie data using AES in CBC mode.  This allows+-- * Encrypt the cookie data using AES in CTR 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).+-- * Sign the encrypted cookie data using Skein-MAC-512-256.+-- Besides detecting potential errors in storage or transmission+-- of the cookies (integrity), the MAC also avoids malicious+-- modifications of the cookie data by assuring you that the+-- cookie data really was generated by this server+-- (authenticity). -- -- * Encode everything using Base64.  Thus we avoid problems with -- non-printable characters by giving the browser a simple@@ -52,46 +52,64 @@     , decrypt     ) where -import Control.Arrow (second)+-- from base import Control.Monad (guard)-import Data.Bits (xor)++-- from directory import System.Directory (doesFileExist)++-- from bytestring import qualified Data.ByteString as S-import qualified Crypto.Cipher.AES as A-import Crypto.Hash.SHA256 (SHA256)-import Crypto.HMAC (MacKey(..), hmac') import qualified Data.ByteString.Base64 as B++-- from cereal+import Data.Serialize (encode, decode)++-- from crypto-api+import Crypto.Classes (buildKey) import Crypto.Random (newGenIO, genBytes, SystemRandom)-import Data.Serialize (encode)+import qualified Crypto.Modes as Modes +-- from cryptocipher+import qualified Crypto.Cipher.AES as A++-- from skein+import Crypto.Skein (skeinMAC', Skein_512_256)+ -- | 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.+-- to encrypt the cookie and a Skein-MAC-512-256 key used verify+-- the authencity and integrity of the cookie.  The AES key needs+-- to have exactly 32 bytes (256 bits) while Skein-MAC-512-256+-- should have 64 bytes (512 bits). -- -- See also 'getDefaultKey' and 'initKey'.-data Key = Key { aesKey  :: A.Key-               , hmacKey :: MacKey }-         deriving (Eq, Show)+data Key = Key { aesKey :: A.AES256+                 -- ^ AES key with 32 bytes.+               , macKey :: S.ByteString -> Skein_512_256+                 -- ^ Skein-MAC key.  Instead of storing the key+                 -- data, we store a partially applied function+                 -- for calculating the MAC (see 'skeinMAC'').+               } --- | The initialization vector used by AES in CBC mode.  Should--- be exactly 16 bytes long.-newtype IV = IV S.ByteString-    deriving Show+-- | Dummy 'Show' instance.+instance Show Key where+    show _ = "<Web.ClientSession.Key>" +-- | The initialization vector used by AES.  Should be exactly 16+-- bytes long.+type IV = Modes.IV A.AES256+ -- | 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+mkIV bs = case (S.length bs, decode bs) of+            (16, Right iv) -> Just iv+            _              -> Nothing  -- | Randomly construct a fresh initialization vector.  You -- /should not/ reuse initialization vectors. randomIV :: IO IV-randomIV = fmap IV $ randomBytes 16+randomIV = Modes.getIVIO  -- | The default key file. defaultKeyFile :: FilePath@@ -129,26 +147,23 @@ -- saved for later use. randomKey :: IO (S.ByteString, Key) randomKey = do-    bs <- randomBytes 64+    bs <- randomBytes 96     case initKey bs of         Left e -> error $ "Web.ClientSession.randomKey: never here, " ++ e         Right key -> return (bs, key) --- | Initializes a 'Key' from a random 'S.ByteString'.  It's--- better to give a 'S.ByteString' with exactly 64 bytes, but--- anything with at least 32 bytes will work.+-- | Initializes a 'Key' from a random 'S.ByteString'.  Fails if+-- there isn't exactly 96 bytes (256 bits for AES and 512 bits+-- for Skein-MAC-512-512). initKey :: S.ByteString -> Either String Key-initKey bs | S.length bs < 32 = Left $ "Web.ClientSession.initKey: length of " ++-                                       show (S.length bs) ++ " too small."-initKey bs = fmap mk $ A.initKey256 preAesKey+initKey bs | S.length bs /= 96 = Left $ "Web.ClientSession.initKey: length of " +++                                         show (S.length bs) ++ " /= 96."+initKey bs = case buildKey preAesKey of+               Nothing -> Left $ "Web.ClientSession.initKey: unknown error with buildKey."+               Just k  -> Right $ Key { aesKey = k+                                      , macKey = skeinMAC' preMacKey }     where-      preAesKey | S.length bs >= 64 = S.pack $ uncurry (S.zipWith xor) $ S.splitAt 32 bs-                | otherwise         = S.take 32 bs-      mk k = Key { aesKey  = k-                 , hmacKey = MacKey bs }-                 -- It's okay to have a MacKey where bs doesn't-                 -- have exactly 512 bits, the size of the block-                 -- used in SHA-256.  hmac' already deals with it.+      (preMacKey, preAesKey) = S.splitAt 64 bs  -- | Same as 'encrypt', however randomly generates the -- initialization vector for you.@@ -157,39 +172,35 @@     iv <- randomIV     return $ encrypt key iv x --- | 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 (AES-CTR), sign (Skein-MAC-512-256) 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]+encrypt key iv x = B.encode final   where-    toPad = 16 - S.length x `mod` 16-    pad = S.replicate toPad $ fromIntegral toPad-    y = pad `S.append` x-    encrypted = A.encryptCBC (aesKey key) iv y-    auth = hmac' (hmacKey key) encrypted :: SHA256+    (encrypted, _) = Modes.ctr' Modes.incIV (aesKey key) iv x+    toBeAuthed     = encode iv `S.append` encrypted+    auth           = macKey key toBeAuthed+    final          = encode auth `S.append` toBeAuthed  -- | 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.+-- (Skein-MAC-512-256) and decrypt (AES-CTR) 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-        then Nothing-        else do-            let (iv, (auth, encrypted)) = second (S.splitAt 32) $ S.splitAt 16 dataBS-                auth' = hmac' (hmacKey key) encrypted :: SHA256-            guard (encode auth' == auth)-            let x = A.decryptCBC (aesKey key) iv encrypted-            (td, _) <- S.uncons x-            guard (td > 0 && td <= 16)-            return $ S.drop (fromIntegral td) x+    guard (S.length dataBS >= 48) -- 16 bytes of IV + 32 bytes of Skein-MAC-512-256+    let (auth, toBeAuthed) = S.splitAt 32 dataBS+        auth' = macKey key toBeAuthed+    guard (encode auth' == auth)+    let (iv_e, encrypted) = S.splitAt 16 toBeAuthed+    iv <- either (const Nothing) Just $ decode iv_e+    let (x, _) = Modes.unCtr' Modes.incIV (aesKey key) iv encrypted+    return x