packages feed

clientsession 0.0.1 → 0.2.0

raw patch · 2 files changed

+47/−47 lines, 2 filesdep +failuredep −utf8-stringdep ~Cryptodep ~basedep ~bytestringPVP ok

version bump matches the API change (PVP)

Dependencies added: failure

Dependencies removed: utf8-string

Dependency ranges changed: Crypto, base, bytestring, dataenc, random

API changes (from Hackage documentation)

- Web.ClientSession: class IsByteString a
- Web.ClientSession: fromByteString :: (IsByteString a) => ByteString -> a
- Web.ClientSession: instance IsByteString ByteString
- Web.ClientSession: instance IsByteString String
- Web.ClientSession: instance Random Word8
- Web.ClientSession: toByteString :: (IsByteString a) => a -> ByteString
+ Web.ClientSession: data ClientSessionException
+ Web.ClientSession: instance Enum MyWord8
+ Web.ClientSession: instance Eq MyWord8
+ Web.ClientSession: instance Exception ClientSessionException
+ Web.ClientSession: instance Integral MyWord8
+ Web.ClientSession: instance Num MyWord8
+ Web.ClientSession: instance Ord MyWord8
+ Web.ClientSession: instance Random MyWord8
+ Web.ClientSession: instance Real MyWord8
+ Web.ClientSession: instance Show ClientSessionException
+ Web.ClientSession: instance Show MyWord8
+ Web.ClientSession: instance Typeable ClientSessionException
- Web.ClientSession: decrypt :: (AESKey k, Monad m, IsByteString b) => k -> String -> m b
+ Web.ClientSession: decrypt :: (AESKey k, MonadFailure ClientSessionException m) => k -> String -> m ByteString
- Web.ClientSession: encrypt :: (IsByteString b, AESKey k) => k -> b -> String
+ Web.ClientSession: encrypt :: (AESKey k) => k -> ByteString -> String

Files

Web/ClientSession.hs view
@@ -1,5 +1,6 @@-{-# OPTIONS_GHC -fno-warn-orphans #-}-{-# LANGUAGE TypeSynonymInstances #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-} --------------------------------------------------------- -- -- Module        : Web.ClientSession@@ -21,37 +22,28 @@       -- * Actual encryption/decryption     , encrypt     , decrypt-      -- * Classes-    , IsByteString (..)       -- * Key types and classes.     , Word256     , AESKey+      -- * Exceptions+    , ClientSessionException     ) where  import Codec.Encryption.AES (AESKey) import qualified Data.ByteString as BS-import qualified Data.ByteString.UTF8 as BSU+import Control.Failure+import Control.Monad (unless)  import Data.LargeWord (Word256) import Codec.Utils (listFromOctets, listToOctets) import Data.Word (Word8) import System.Random (getStdGen, randoms, Random, randomR, random)-import qualified Data.ByteString as BS import qualified Codec.Encryption.AES as AES import qualified Codec.Binary.Base64Url as Base64 import qualified Data.Digest.MD5 as MD5 --- | A class for anything which can become a 'Data.ByteString'.--- The 'String' instance uses UTF8 encoding.-class IsByteString a where-    toByteString :: a -> BS.ByteString-    fromByteString :: BS.ByteString -> a-instance IsByteString BS.ByteString where-    toByteString = id-    fromByteString = id-instance IsByteString String where-    toByteString = BSU.fromString-    fromByteString = BSU.toString+import Data.Typeable (Typeable)+import Control.Exception (Exception)  -- | The default key file. defaultKeyFile :: String@@ -61,6 +53,15 @@ getDefaultKey :: IO Word256 getDefaultKey = getKey defaultKeyFile +data ClientSessionException =+      KeyTooSmall BS.ByteString+    | InvalidBase64 String+    | MismatchedHash { _expected :: [Word8]+                     , _actual :: [Word8]+                     }+    deriving (Show, Typeable)+instance Exception ClientSessionException+ -- | Get a 256-bit key from the given text file. -- If the file does not exist, or did not contain enough bits, -- a random key will be generated and stored in that file.@@ -71,52 +72,51 @@         loadKeyFromFile = do                 contents <- BS.readFile keyFile                 if BS.length contents < 32-                        then fail "Key too small"+                        then failure $ KeyTooSmall contents                         else return $ head $ listFromOctets $ BS.unpack contents         generateNewKey :: IO Word256         generateNewKey = do                 stdGen <- getStdGen-                let word8s = take 32 $ randoms stdGen+                let word8s = map unMyWord8 $ take 32 $ randoms stdGen                 let newKey = head $ listFromOctets word8s                 BS.writeFile keyFile $ BS.pack word8s                 return newKey -instance Random Word8 where+newtype MyWord8 = MyWord8 { unMyWord8 :: Word8 }+    deriving (Integral, Real, Enum, Num, Ord, Eq, Show)+instance Random MyWord8 where         randomR (a,b) g =                 let (x, g') = randomR (toInteger a, toInteger b) g-                in (toEnum $ fromEnum $ mod x 256, g')-        random = randomR (minBound,maxBound)+                in (fromIntegral $ mod x 256, g')+        random = randomR (MyWord8 minBound, MyWord8 maxBound)  -- | Encrypt with the given key and base-64 encode. -- A hash is stored inside the encrypted key so that, upon decryption, -- integrity can be guaranteed.-encrypt :: (IsByteString b, AES.AESKey k)+encrypt :: AES.AESKey k         => k               -- ^ The key used for encryption.-        -> b               -- ^ The data to encrypt.+        -> BS.ByteString   -- ^ The data to encrypt.         -> String          -- ^ Encrypted and encoded data. encrypt k x =-        let unpacked = BS.unpack $ toByteString x+        let unpacked = BS.unpack x         in Base64.encode . listToOctets . map (AES.encrypt k) .            listFromOctets $ MD5.hash unpacked ++ unpacked --- | Helper function to convert a Maybe into any monad-liftMaybe :: Monad m => Maybe a -> m a-liftMaybe Nothing = fail "Nothing"-liftMaybe (Just x) = return x---- | Base-64 decode and decrypt with the given key, if possible.--- If either the original string is not a valid base-64 encoded string,--- or the hash at the beginning of the decrypted string does not match,--- this function 'fail's.-decrypt :: (AES.AESKey k, Monad m, IsByteString b)+-- | Base-64 decode and decrypt with the given key, if possible.  Calls+-- 'failure' if either the original string is not a valid base-64 encoded+-- string, or the hash at the beginning of the decrypted string does not match.+decrypt :: (AES.AESKey k, MonadFailure ClientSessionException m)         => k                    -- ^ The key used for encryption.         -> String               -- ^ Data to decrypt.-        -> m b                  -- ^ The decrypted data, if possible.+        -> m BS.ByteString      -- ^ The decrypted data, if possible. decrypt k x = do-        decoded <- liftMaybe $ Base64.decode x+        decoded <- case Base64.decode x of+                        Nothing -> failure $ InvalidBase64 x+                        Just y -> return y         let decrypted = listToOctets $ map (AES.decrypt k)                         $ listFromOctets decoded-        let (hash, rest) = splitAt 16 decrypted-        if hash == MD5.hash rest-                then return $ fromByteString $ BS.pack rest-                else fail "Invalid"+        let (expected, rest) = splitAt 16 decrypted+        let actual = MD5.hash rest+        unless (expected == actual) $ failure+                                    $ MismatchedHash expected actual+        return $ BS.pack rest
clientsession.cabal view
@@ -1,5 +1,5 @@ name:            clientsession-version:         0.0.1+version:         0.2.0 license:         BSD3 license-file:    LICENSE author:          Michael Snoyman <michael@snoyman.com>@@ -15,10 +15,10 @@  library     build-depends:   base >=4 && <5,-                     Crypto,-                     dataenc,-                     bytestring,-                     random,-                     utf8-string+                     Crypto >= 4.2.0 && < 4.3,+                     dataenc >= 0.13.0.2 && < 0.14,+                     random >= 1.0.0.2 && < 1.1,+                     failure >= 0.0.0.2 && < 0.1,+                     bytestring >= 0.9 && < 0.10     exposed-modules: Web.ClientSession     ghc-options:     -Wall