packages feed

clientsession 0.2.1 → 0.3.0

raw patch · 2 files changed

+62/−70 lines, 2 filesdep +SimpleAESdep +binarydep +directorydep −Cryptodep −randomPVP ok

version bump matches the API change (PVP)

Dependencies added: SimpleAES, binary, directory, pureMD5

Dependencies removed: Crypto, random

API changes (from Hackage documentation)

- Web.ClientSession: class (AESKeyIndirection a) => AESKey a
- Web.ClientSession: instance Enum MyWord8
- Web.ClientSession: instance Eq MyWord8
- 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 MyWord8
- Web.ClientSession: type Word256 = LargeKey Word64 Word192
+ Web.ClientSession: InvalidBase64 :: String -> ClientSessionException
+ Web.ClientSession: InvalidHash :: String -> ClientSessionException
+ Web.ClientSession: KeyTooSmall :: ByteString -> ClientSessionException
+ Web.ClientSession: MismatchedHash :: ByteString -> ByteString -> ClientSessionException
+ Web.ClientSession: actualHash :: ClientSessionException -> ByteString
+ Web.ClientSession: expectedHash :: ClientSessionException -> ByteString
+ Web.ClientSession: instance Eq ClientSessionException
- Web.ClientSession: decrypt :: (AESKey k, Monad m, Failure ClientSessionException m) => k -> String -> m ByteString
+ Web.ClientSession: decrypt :: (Monad m, Failure ClientSessionException m) => Key -> String -> m ByteString
- Web.ClientSession: encrypt :: (AESKey k) => k -> ByteString -> String
+ Web.ClientSession: encrypt :: Key -> ByteString -> IO (String)
- Web.ClientSession: getDefaultKey :: IO Word256
+ Web.ClientSession: getDefaultKey :: IO Key
- Web.ClientSession: getKey :: FilePath -> IO Word256
+ Web.ClientSession: getKey :: FilePath -> IO Key

Files

Web/ClientSession.hs view
@@ -5,7 +5,7 @@ -- -- Module        : Web.ClientSession -- Copyright     : Michael Snoyman--- License       : BSD3+-- License       : SD3 -- -- Maintainer    : Michael Snoyman <michael@snoyman.com> -- Stability     : Stable@@ -22,101 +22,91 @@       -- * Actual encryption/decryption     , encrypt     , decrypt-      -- * Key types and classes.-    , Word256-    , AESKey       -- * Exceptions-    , ClientSessionException+    , ClientSessionException (..)     ) where -import Codec.Encryption.AES (AESKey)-import qualified Data.ByteString as BS+import Codec.Crypto.SimpleAES 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 Codec.Encryption.AES as AES+import qualified Codec.Crypto.SimpleAES as AES import qualified Codec.Binary.Base64Url as Base64-import qualified Data.Digest.MD5 as MD5+import qualified Data.Digest.Pure.MD5 as MD5  import Data.Typeable (Typeable)-import Control.Exception (Exception)+import Control.Exception +import System.Directory++import qualified Data.ByteString as S+import qualified Data.ByteString.Lazy as L++import Data.Binary+ -- | The default key file. defaultKeyFile :: String defaultKeyFile = "client_session_key.aes"  -- | Simply calls 'getKey' \"client_session_key.aes\"-getDefaultKey :: IO Word256+getDefaultKey :: IO AES.Key getDefaultKey = getKey defaultKeyFile  data ClientSessionException =-      KeyTooSmall BS.ByteString+      KeyTooSmall S.ByteString     | InvalidBase64 String-    | MismatchedHash { _expected :: [Word8]-                     , _actual :: [Word8]+    | InvalidHash String+    | MismatchedHash { expectedHash :: L.ByteString+                     , actualHash   :: L.ByteString                      }-    deriving (Show, Typeable)+    deriving (Show, Typeable, Eq) 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.+-- | 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 Word256   -- ^ The actual 256-bit key.-getKey keyFile = catch loadKeyFromFile $ const generateNewKey where-        loadKeyFromFile :: IO Word256-        loadKeyFromFile = do-                contents <- BS.readFile keyFile-                if BS.length contents < 32-                        then failure $ KeyTooSmall contents-                        else return $ head $ listFromOctets $ BS.unpack contents-        generateNewKey :: IO Word256-        generateNewKey = do-                stdGen <- getStdGen-                let word8s = map unMyWord8 $ take 32 $ randoms stdGen-                let newKey = head $ listFromOctets word8s-                BS.writeFile keyFile $ BS.pack word8s-                return newKey--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 (fromIntegral $ mod x 256, g')-        random = randomR (MyWord8 minBound, MyWord8 maxBound)+       -> IO AES.Key   -- ^ The actual key.+getKey keyFile = do+    exists <- doesFileExist keyFile+    if exists+        then S.readFile keyFile+        else do+            key <- AES.randomKey+            S.writeFile keyFile key+            return key  -- | 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 :: AES.AESKey k-        => k               -- ^ The key used for encryption.-        -> BS.ByteString   -- ^ The data to encrypt.-        -> String          -- ^ Encrypted and encoded data.-encrypt k x =-        let unpacked = BS.unpack x-        in Base64.encode . listToOctets . map (AES.encrypt k) .-           listFromOctets $ MD5.hash unpacked ++ unpacked+encrypt :: AES.Key         -- ^ The key used for encryption.+        -> L.ByteString    -- ^ The data to encrypt.+        -> IO (String)     -- ^ Encrypted and encoded data.+encrypt k bs = do+    let withHash = encode (MD5.md5 bs) `L.append` bs+    encrypted <- AES.encryptMsg mode k withHash+    return $ Base64.encode $ L.unpack encrypted +mode :: AES.Mode+mode = ECB+ -- | 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, Monad m, Failure ClientSessionException m)-        => k                    -- ^ The key used for encryption.+decrypt :: (Monad m, Failure ClientSessionException m)+        => AES.Key              -- ^ The key used for encryption.         -> String               -- ^ Data to decrypt.-        -> m BS.ByteString      -- ^ The decrypted data, if possible.+        -> m L.ByteString       -- ^ The decrypted data, if possible. decrypt k x = do-        decoded <- case Base64.decode x of-                        Nothing -> failure $ InvalidBase64 x-                        Just y -> return y-        let decrypted = listToOctets $ map (AES.decrypt k)-                        $ listFromOctets decoded-        let (expected, rest) = splitAt 16 decrypted-        let actual = MD5.hash rest-        unless (expected == actual) $ failure-                                    $ MismatchedHash expected actual-        return $ BS.pack rest+    decoded <- case Base64.decode x of+                    Nothing -> failure $ InvalidBase64 x+                    Just y -> return y+    decrypted <- case AES.decryptMsg' mode k $ L.pack decoded of+                    Left s -> failure $ InvalidHash s+                    Right z -> return z+    let (expected, rest) = L.splitAt 16 decrypted+    let actual = encode $ MD5.md5 rest+    unless (expected == actual) $ failure+                                $ MismatchedHash expected actual+    return rest
clientsession.cabal view
@@ -1,5 +1,5 @@ name:            clientsession-version:         0.2.1+version:         0.3.0 license:         BSD3 license-file:    LICENSE author:          Michael Snoyman <michael@snoyman.com>@@ -15,10 +15,12 @@  library     build-depends:   base >=4 && <5,-                     Crypto >= 4.2.0 && < 4.3,+                     SimpleAES >= 0.4.1 && < 0.5,+                     pureMD5 >= 1.1.0.0 && < 1.2,                      dataenc >= 0.13.0.2 && < 0.14,-                     random >= 1.0.0.1 && < 1.1,                      failure >= 0.0.0.2 && < 0.2,-                     bytestring >= 0.9 && < 0.10+                     bytestring >= 0.9 && < 0.10,+                     directory >= 1 && < 1.1,+                     binary >= 0.5.0.2 && < 0.6     exposed-modules: Web.ClientSession     ghc-options:     -Wall