packages feed

clientsession 0.7.0 → 0.7.1

raw patch · 3 files changed

+158/−120 lines, 3 filesdep +cerealdep +clientsessiondep +cryptohashPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: cereal, clientsession, cryptohash

API changes (from Hackage documentation)

+ Web.ClientSession: Key :: Key -> MacKey -> Key
+ Web.ClientSession: aesKey :: Key -> Key
+ Web.ClientSession: hmacKey :: Key -> MacKey
+ Web.ClientSession: initKey :: ByteString -> Either String Key
+ Web.ClientSession: instance Eq Key
+ Web.ClientSession: instance Show Key
- Web.ClientSession: data Key :: *
+ Web.ClientSession: data Key

Files

− Web/ClientSession.hs
@@ -1,116 +0,0 @@-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE ForeignFunctionInterface #-}-{-# LANGUAGE TemplateHaskell #-}---------------------------------------------------------------- Module        : Web.ClientSession--- Copyright     : Michael Snoyman--- License       : BSD3------ Maintainer    : Michael Snoyman <michael@snoyman.com>--- Stability     : Stable--- Portability   : portable------ Stores session data in a client cookie.--------------------------------------------------------------module Web.ClientSession-    ( -- * Automatic key generation-      Key-    , IV-    , randomIV-    , mkIV-    , getKey-    , defaultKeyFile-    , getDefaultKey-      -- * Actual encryption/decryption-    , encrypt-    , encryptIO-    , decrypt-    ) where--import System.Directory (doesFileExist)-import qualified Data.ByteString as S-import qualified Crypto.Cipher.AES as A-import Crypto.Cipher.AES (Key)-import qualified Data.ByteString.Base64 as B-import Crypto.Random (newGenIO, genBytes, SystemRandom)--newtype IV = IV S.ByteString-    deriving Show--mkIV :: S.ByteString -> Maybe IV-mkIV bs-    | S.length bs == 16 = Just $ IV bs-    | otherwise = Nothing--randomIV :: IO IV-randomIV = fmap IV $ randomBytes 16---- | The default key file.-defaultKeyFile :: String-defaultKeyFile = "client_session_key.aes"---- | Simply calls 'getKey' 'defaultKeyFile'.-getDefaultKey :: IO Key-getDefaultKey = getKey defaultKeyFile---- | 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.-getKey :: FilePath     -- ^ File name where key is stored.-       -> IO Key       -- ^ The actual key.-getKey keyFile = do-    exists <- doesFileExist keyFile-    if exists-        then S.readFile keyFile >>= either (const newKey) return . A.initKey256-        else newKey-  where-    newKey = do-        (bs, key') <- randomKey-        S.writeFile keyFile bs-        return key'--randomBytes :: Int -> IO S.ByteString-randomBytes len = do-    g <- newGenIO-    either (error . show) (return . fst) $ genBytes len (g :: SystemRandom)--randomKey :: IO (S.ByteString, Key)-randomKey = do-    bs <- randomBytes 32-    case A.initKey256 bs of-        Left e -> error e -- should never happen-        Right key -> return (bs, key)--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 key (IV iv) x =-    B.encode $ iv `S.append` A.encryptCBC key iv y-  where-    toPad = 16 - S.length x `mod` 16-    pad = S.replicate toPad $ fromIntegral toPad-    y = pad `S.append` x--decrypt :: Key -- ^ key-        -> S.ByteString -- ^ data-        -> Maybe S.ByteString-decrypt key dataBS64 = do-    dataBS <- either (const Nothing) Just $ B.decode dataBS64-    if S.length dataBS `mod` 16 /= 0 || S.length dataBS < 16-        then Nothing-        else do-            let (iv, encrypted) = S.splitAt 16 dataBS-            let x = A.decryptCBC key iv encrypted-            (td, _) <- S.uncons x-            if td > 0 && td <= 16-                then Just $ S.drop (fromIntegral td) x-                else Nothing
clientsession.cabal view
@@ -1,5 +1,5 @@ name:            clientsession-version:         0.7.0+version:         0.7.1 license:         BSD3 license-file:    LICENSE author:          Michael Snoyman <michael@snoyman.com>@@ -20,19 +20,28 @@ library     build-depends:   base                >=4           && < 5                    , bytestring          >= 0.9        && < 0.10+                   , cereal              >= 0.3        && < 0.4                    , directory           >= 1          && < 1.2                    , crypto-api          >= 0.6.4      && < 0.7                    , cryptocipher        >= 0.2.5      && < 0.3+                   , cryptohash          >= 0.7.1      && < 0.8                    , base64-bytestring   >= 0.1.0.3    && < 0.2     exposed-modules: Web.ClientSession     ghc-options:     -Wall+    hs-source-dirs:  src  test-suite runtests     type: exitcode-stdio-1.0-    build-depends: hspec      == 0.6.*-                 , QuickCheck >= 2 && < 3-                 , HUnit+    build-depends:   base                >=4           && < 5+                   , bytestring          >= 0.9        && < 0.10+                   , cryptocipher        >= 0.2.5      && < 0.3+                   , hspec               == 0.6.*+                   , QuickCheck          >= 2          && < 3+                   , HUnit+                   -- finally, our own package+                   , clientsession     ghc-options:     -Wall+    hs-source-dirs:  tests     main-is:         runtests.hs  source-repository head
+ src/Web/ClientSession.hs view
@@ -0,0 +1,145 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ForeignFunctionInterface #-}+{-# LANGUAGE TemplateHaskell #-}+---------------------------------------------------------+--+-- Module        : Web.ClientSession+-- Copyright     : Michael Snoyman+-- License       : BSD3+--+-- Maintainer    : Michael Snoyman <michael@snoyman.com>+-- Stability     : Stable+-- Portability   : portable+--+-- Stores session data in a client cookie.+--+---------------------------------------------------------+module Web.ClientSession+    ( -- * Automatic key generation+      Key(..)+    , IV+    , randomIV+    , mkIV+    , getKey+    , defaultKeyFile+    , getDefaultKey+    , initKey+      -- * Actual encryption/decryption+    , encrypt+    , encryptIO+    , decrypt+    ) where++import Control.Arrow (second)+import Control.Monad (guard)+import Data.Bits (xor)+import System.Directory (doesFileExist)+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+import Crypto.Random (newGenIO, genBytes, SystemRandom)+import Data.Serialize (encode)++data Key = Key { aesKey  :: A.Key+               , hmacKey :: MacKey }+         deriving (Eq, Show)++newtype IV = IV S.ByteString+    deriving Show++mkIV :: S.ByteString -> Maybe IV+mkIV bs+    | S.length bs == 16 = Just $ IV bs+    | otherwise = Nothing++randomIV :: IO IV+randomIV = fmap IV $ randomBytes 16++-- | The default key file.+defaultKeyFile :: String+defaultKeyFile = "client_session_key.aes"++-- | Simply calls 'getKey' 'defaultKeyFile'.+getDefaultKey :: IO Key+getDefaultKey = getKey defaultKeyFile++-- | 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.+getKey :: FilePath     -- ^ File name where key is stored.+       -> IO Key       -- ^ The actual key.+getKey keyFile = do+    exists <- doesFileExist keyFile+    if exists+        then S.readFile keyFile >>= either (const newKey) return . initKey+        else newKey+  where+    newKey = do+        (bs, key') <- randomKey+        S.writeFile keyFile bs+        return key'++randomBytes :: Int -> IO S.ByteString+randomBytes len = do+    g <- newGenIO+    either (error . show) (return . fst) $ genBytes len (g :: SystemRandom)++randomKey :: IO (S.ByteString, Key)+randomKey = do+    bs <- randomBytes 64+    case initKey bs of+        Left e -> error e -- should never happen+        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.+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+    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.++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 key (IV iv) x =+    B.encode $ S.concat [iv, encode auth, encrypted]+  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++decrypt :: Key -- ^ key+        -> S.ByteString -- ^ data+        -> Maybe S.ByteString+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