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 +41/−41
- clientsession.cabal +6/−6
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