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 +6/−6
- src/Web/ClientSession.hs +77/−66
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