hopenssl 1.7 → 2
raw patch · 10 files changed
+593/−336 lines, 10 filesdep +HUnitdep +doctestdep +hopenssldep −mtldep ~basePVP ok
version bump matches the API change (PVP)
Dependencies added: HUnit, doctest, hopenssl
Dependencies removed: mtl
Dependency ranges changed: base
API changes (from Hackage documentation)
- OpenSSL.Digest: DSS :: MessageDigest
- OpenSSL.Digest: DSS1 :: MessageDigest
- OpenSSL.Digest: DST :: (Ptr OpaqueContext) -> DigestState
- OpenSSL.Digest: MD5 :: MessageDigest
- OpenSSL.Digest: MDC2 :: MessageDigest
- OpenSSL.Digest: Null :: MessageDigest
- OpenSSL.Digest: OpaqueContext :: OpaqueContext
- OpenSSL.Digest: OpaqueMDEngine :: OpaqueMDEngine
- OpenSSL.Digest: RIPEMD160 :: MessageDigest
- OpenSSL.Digest: SHA :: MessageDigest
- OpenSSL.Digest: SHA1 :: MessageDigest
- OpenSSL.Digest: SHA224 :: MessageDigest
- OpenSSL.Digest: SHA256 :: MessageDigest
- OpenSSL.Digest: SHA384 :: MessageDigest
- OpenSSL.Digest: SHA512 :: MessageDigest
- OpenSSL.Digest: ctxCreate :: IO Context
- OpenSSL.Digest: ctxDestroy :: Context -> IO ()
- OpenSSL.Digest: ctxInit :: Context -> IO ()
- OpenSSL.Digest: data MessageDigest
- OpenSSL.Digest: data OpaqueContext
- OpenSSL.Digest: data OpaqueMDEngine
- OpenSSL.Digest: digestFinal :: Context -> Ptr Word8 -> Ptr CUInt -> IO CInt
- OpenSSL.Digest: digestInit :: Context -> MDEngine -> IO CInt
- OpenSSL.Digest: digestUpdate :: Context -> Ptr Word8 -> CUInt -> IO CInt
- OpenSSL.Digest: final :: Digest [Word8]
- OpenSSL.Digest: instance Bounded MessageDigest
- OpenSSL.Digest: instance Enum MessageDigest
- OpenSSL.Digest: instance Eq MessageDigest
- OpenSSL.Digest: instance Show MessageDigest
- OpenSSL.Digest: maxMDSize :: Int
- OpenSSL.Digest: mdDSS :: IO MDEngine
- OpenSSL.Digest: mdDSS1 :: IO MDEngine
- OpenSSL.Digest: mdMD5 :: IO MDEngine
- OpenSSL.Digest: mdMDC2 :: IO MDEngine
- OpenSSL.Digest: mdNull :: IO MDEngine
- OpenSSL.Digest: mdRIPEMD160 :: IO MDEngine
- OpenSSL.Digest: mdSHA :: IO MDEngine
- OpenSSL.Digest: mdSHA1 :: IO MDEngine
- OpenSSL.Digest: mdSHA224 :: IO MDEngine
- OpenSSL.Digest: mdSHA256 :: IO MDEngine
- OpenSSL.Digest: mdSHA384 :: IO MDEngine
- OpenSSL.Digest: mdSHA512 :: IO MDEngine
- OpenSSL.Digest: mkDigest :: MessageDigest -> (DigestState -> IO a) -> IO a
- OpenSSL.Digest: newtype DigestState
- OpenSSL.Digest: toMDEngine :: MessageDigest -> IO MDEngine
- OpenSSL.Digest: type Context = Ptr OpaqueContext
- OpenSSL.Digest: type Digest a = StateT DigestState IO a
- OpenSSL.Digest: type MDEngine = Ptr OpaqueMDEngine
- OpenSSL.Digest: update :: [Word8] -> Digest ()
- OpenSSL.Digest: update' :: (Ptr Word8, Int) -> Digest ()
- OpenSSL.Digest.ByteString: digest :: MessageDigest -> ByteString -> IO [Word8]
- OpenSSL.Digest.ByteString: update :: ByteString -> Digest Int
- OpenSSL.Digest.ByteString.Lazy: digest :: MessageDigest -> ByteString -> IO [Word8]
- OpenSSL.Digest.ByteString.Lazy: update :: ByteString -> Digest Int
+ OpenSSL.Digest: class Digestable a
+ OpenSSL.Digest: data DigestDescription
+ OpenSSL.Digest: digestByName :: String -> DigestDescription
+ OpenSSL.Digest: digestByName' :: String -> Maybe DigestDescription
+ OpenSSL.Digest: digestString :: DigestDescription -> String -> MessageDigest
+ OpenSSL.Digest: instance OpenSSL.Digest.Digestable (GHC.Ptr.Ptr a, Foreign.C.Types.CInt)
+ OpenSSL.Digest: instance OpenSSL.Digest.Digestable (GHC.Ptr.Ptr a, Foreign.C.Types.CSize)
+ OpenSSL.Digest: instance OpenSSL.Digest.Digestable (GHC.Ptr.Ptr a, Foreign.C.Types.CUInt)
+ OpenSSL.Digest: instance OpenSSL.Digest.Digestable (GHC.Ptr.Ptr a, GHC.Types.Int)
+ OpenSSL.Digest: instance OpenSSL.Digest.Digestable OpenSSL.Digest.LazyByteString
+ OpenSSL.Digest: instance OpenSSL.Digest.Digestable OpenSSL.Digest.StrictByteString
+ OpenSSL.Digest: instance OpenSSL.Digest.Digestable [GHC.Word.Word8]
+ OpenSSL.Digest: type LazyByteString = ByteString
+ OpenSSL.Digest: type MessageDigest = StrictByteString
+ OpenSSL.Digest: type StrictByteString = ByteString
+ OpenSSL.Digest: updateChunk :: Digestable a => DigestContext -> a -> IO ()
+ OpenSSL.EVP.Digest: AttemptToConstructDigestContextFromNullPointer :: AttemptToConstructDigestContextFromNullPointer
+ OpenSSL.EVP.Digest: DigestAlgorithmNotAvailableInOpenSSL :: String -> DigestAlgorithmNotAvailableInOpenSSL
+ OpenSSL.EVP.Digest: DigestContext :: Ptr OpaqueDigestContext -> DigestContext
+ OpenSSL.EVP.Digest: DigestDescription :: Ptr OpaqueDigestDescription -> DigestDescription
+ OpenSSL.EVP.Digest: [getDigestContext] :: DigestContext -> Ptr OpaqueDigestContext
+ OpenSSL.EVP.Digest: [getDigestDescription] :: DigestDescription -> Ptr OpaqueDigestDescription
+ OpenSSL.EVP.Digest: _addAllDigests :: IO ()
+ OpenSSL.EVP.Digest: _cleanupContext :: Ptr OpaqueDigestContext -> IO CInt
+ OpenSSL.EVP.Digest: _createContext :: IO (Ptr OpaqueDigestContext)
+ OpenSSL.EVP.Digest: _destroyContext :: Ptr OpaqueDigestContext -> IO ()
+ OpenSSL.EVP.Digest: _digestBlockSize :: Ptr OpaqueDigestDescription -> CInt
+ OpenSSL.EVP.Digest: _digestByName :: CString -> Ptr OpaqueDigestDescription
+ OpenSSL.EVP.Digest: _digestSize :: Ptr OpaqueDigestDescription -> CInt
+ OpenSSL.EVP.Digest: _finalizeDigest :: Ptr OpaqueDigestContext -> Ptr Word8 -> Ptr CUInt -> IO CInt
+ OpenSSL.EVP.Digest: _initContext :: Ptr OpaqueDigestContext -> IO ()
+ OpenSSL.EVP.Digest: _initDigest :: Ptr OpaqueDigestContext -> Ptr OpaqueDigestDescription -> Ptr OpaqueDigestEngine -> IO CInt
+ OpenSSL.EVP.Digest: _updateDigest :: Ptr OpaqueDigestContext -> Ptr a -> CSize -> IO CInt
+ OpenSSL.EVP.Digest: cleanupContext :: DigestContext -> IO ()
+ OpenSSL.EVP.Digest: createContext :: IO DigestContext
+ OpenSSL.EVP.Digest: data AttemptToConstructDigestContextFromNullPointer
+ OpenSSL.EVP.Digest: data OpaqueDigestContext
+ OpenSSL.EVP.Digest: data OpaqueDigestDescription
+ OpenSSL.EVP.Digest: data OpaqueDigestEngine
+ OpenSSL.EVP.Digest: destroyContext :: DigestContext -> IO ()
+ OpenSSL.EVP.Digest: digestByName :: String -> DigestDescription
+ OpenSSL.EVP.Digest: digestByName' :: String -> Maybe DigestDescription
+ OpenSSL.EVP.Digest: digestContext :: Ptr OpaqueDigestContext -> DigestContext
+ OpenSSL.EVP.Digest: finalizeDigest :: DigestContext -> Ptr Word8 -> IO ()
+ OpenSSL.EVP.Digest: initContext :: DigestContext -> IO ()
+ OpenSSL.EVP.Digest: initDigest :: DigestDescription -> DigestContext -> IO ()
+ OpenSSL.EVP.Digest: instance Data.String.IsString OpenSSL.EVP.Digest.DigestDescription
+ OpenSSL.EVP.Digest: instance Foreign.Storable.Storable OpenSSL.EVP.Digest.OpaqueDigestContext
+ OpenSSL.EVP.Digest: instance GHC.Classes.Eq OpenSSL.EVP.Digest.DigestDescription
+ OpenSSL.EVP.Digest: instance GHC.Exception.Exception OpenSSL.EVP.Digest.AttemptToConstructDigestContextFromNullPointer
+ OpenSSL.EVP.Digest: instance GHC.Exception.Exception OpenSSL.EVP.Digest.DigestAlgorithmNotAvailableInOpenSSL
+ OpenSSL.EVP.Digest: instance GHC.Show.Show OpenSSL.EVP.Digest.AttemptToConstructDigestContextFromNullPointer
+ OpenSSL.EVP.Digest: instance GHC.Show.Show OpenSSL.EVP.Digest.DigestAlgorithmNotAvailableInOpenSSL
+ OpenSSL.EVP.Digest: instance GHC.Show.Show OpenSSL.EVP.Digest.DigestDescription
+ OpenSSL.EVP.Digest: isDigestEngineInitialized :: MVar Bool
+ OpenSSL.EVP.Digest: maxDigestSize :: Int
+ OpenSSL.EVP.Digest: newtype DigestAlgorithmNotAvailableInOpenSSL
+ OpenSSL.EVP.Digest: newtype DigestContext
+ OpenSSL.EVP.Digest: newtype DigestDescription
+ OpenSSL.EVP.Digest: throwIfZero :: String -> IO CInt -> IO ()
+ OpenSSL.EVP.Digest: toHex :: Word8 -> String
+ OpenSSL.EVP.Digest: updateDigest :: DigestContext -> Ptr a -> CSize -> IO ()
- OpenSSL.Digest: digest :: MessageDigest -> [Word8] -> IO [Word8]
+ OpenSSL.Digest: digest :: Digestable a => DigestDescription -> a -> MessageDigest
- OpenSSL.Digest: toHex :: Word8 -> String
+ OpenSSL.Digest: toHex :: MessageDigest -> StrictByteString
Files
- OpenSSL/Digest.hs +0/−237
- OpenSSL/Digest/ByteString.hs +0/−38
- OpenSSL/Digest/ByteString/Lazy.hs +0/−32
- hopenssl.cabal +53/−29
- src/OpenSSL/Digest.hs +201/−0
- src/OpenSSL/EVP/Digest.hsc +260/−0
- test/CheckHighLevelDigestAPI.hs +18/−0
- test/CheckLowLevelDigestAPI.hs +34/−0
- test/OpenSesame.hs +15/−0
- test/doctests.hs +12/−0
− OpenSSL/Digest.hs
@@ -1,237 +0,0 @@-{-# LANGUAGE ForeignFunctionInterface #-}--{- |- Module : OpenSSL.Digest- Copyright : (c) 2014 by Peter Simons- License : BSD3-- Maintainer : simons@cryp.to- Stability : provisional- Portability : portable-- This module proivdes a high-level API to the message- digest algorithms found in OpenSSL's @crypto@ library.- Link with @-lcrypto@ when using this module.-- Here is a short example program which runs all available- digests on a string:-- > example :: (Enum a) => [a] -> IO [String]- > example input = mapM hash [minBound .. maxBound]- > where- > hash f = fmap (fmt f) (digest f (toWord input))- > fmt f = shows f . (": \t"++) . (>>=toHex)- > toWord = map (toEnum . fromEnum)-- And when called, the function prints:-- > *Digest> example "open sesame" >>= putStr . unlines- > Null:- > MD5: 54ef36ec71201fdf9d1423fd26f97f6b- > SHA: 2ccefef64c76ac0d42ca1657457977675890c42f- > SHA1: 5bcaff7f22ff533ca099b3408ead876c0ebba9a7- > DSS: 5bcaff7f22ff533ca099b3408ead876c0ebba9a7- > DSS1: 5bcaff7f22ff533ca099b3408ead876c0ebba9a7- > RIPEMD160: bdb2bba6ec93bd566dc1181cadbc92176aa78382- > MDC2: 112db2200ce1e9db3c2d132aea4ef7d0- > SHA224: 1ee0f9d93a873a67fe781852d716cb3e5904e015aafaa4d1ff1a81bc- > SHA256: 41ef4bb0b23661e66301aac36066912dac037827b4ae63a7b1165a5aa93ed4eb- > SHA384: ae2a5d6649035c00efe2bc1b5c97f4d5ff97fa2df06f273afa0231c425e8aff30e4cc1db5e5756e8d2245a1514ad1a2d- > SHA512: 8470cdd3bf1ef85d5f092bce5ae5af97ce50820481bf43b2413807fec37e2785b533a65d4c7d71695b141d81ebcd4b6c4def4284e6067f0b400000001b230205--}--module OpenSSL.Digest where--import Control.Exception ( bracket )-import Foreign-import Foreign.C-import Control.Monad.State-import Numeric ( showHex )---- * High-level API---- |The message digest algorithms we support.--data MessageDigest- = Null -- ^ 0 bit- | MD5 -- ^ 128 bit- | SHA -- ^ 160 bit- | SHA1 -- ^ 160 bit- | DSS -- ^ other name for SHA1- | DSS1 -- ^ other name for SHA1- | RIPEMD160 -- ^ 160 bit- | MDC2 -- ^ 128 bit- | SHA224 -- ^ 224 bit- | SHA256 -- ^ 256 bit- | SHA384 -- ^ 384 bit- | SHA512 -- ^ 512 bit- deriving (Show, Eq, Enum, Bounded)---- |A convenience wrapper which computes the given digest--- over a list of 'Word8'. Unlike the monadic interface,--- this function does not allow the computation to be--- restarted.--digest :: MessageDigest -> [Word8] -> IO [Word8]-digest mdType xs =- mkDigest mdType $ evalStateT (update xs >> final)---- |A monadic interface to the digest computation.--type Digest a = StateT DigestState IO a---- |The internal EVP context.--newtype DigestState = DST (Ptr OpaqueContext)---- |Run an 'IO' computation with an initialized--- 'DigestState'. All resources will be freed when the--- computation returns.--mkDigest :: MessageDigest -> (DigestState -> IO a) -> IO a-mkDigest mdType f =- bracket ctxCreate ctxDestroy $ \ctx -> do- when (ctx == nullPtr) (fail "Digest.mkDigest: ctxCreate failed")- md <- toMDEngine mdType- when (md == nullPtr) (fail ("Digest.mkDigest: can't access "++show mdType))- rc <- digestInit ctx md- when (rc == 0) (fail ("Digest.mkDigest: can't initialize "++show mdType))- f (DST ctx)---- |Update the internal state with a block of data. This--- function is just a wrapper for 'update'', which creates--- an array in memory using 'withArray'.--update :: [Word8] -> Digest ()-update xs = do- st <- get- liftIO $- withArray xs $ \p ->- evalStateT (update' (p, length xs)) st---- |Update the internal state with a block of data from--- memory. This is the /faster/ version of 'update'.--update' :: (Ptr Word8, Int) -> Digest ()-update' (p,n) = do- DST ctx <- get- rc <- liftIO $ digestUpdate ctx p (toEnum (fromEnum n))- when (rc == 0) (fail "Digest.update failed")---- |Wrap up the computation, add padding, do whatever has to--- be done, and return the final hash. The length of the--- result depends on the chosen 'MessageDigest'. Do not call--- more than once!--final :: Digest [Word8]-final = do- DST ctx <- get- liftIO $- allocaArray maxMDSize $ \p ->- allocaArray (sizeOf (undefined :: CUInt)) $ \i -> do- rc <- digestFinal ctx p i- when (rc == 0) (fail "Digest.Final failed")- i' <- peek i- peekArray (fromEnum i') p---- * Low-level API---- |The EVP context used by OpenSSL is opaque for us; we--- only access it through a 'Ptr'.--data OpaqueContext = OpaqueContext-type Context = Ptr OpaqueContext---- |The message digest engines are opaque for us as well.--data OpaqueMDEngine = OpaqueMDEngine-type MDEngine = Ptr OpaqueMDEngine---- |Maximum size of all message digests supported by--- OpenSSL. Allocate a buffer of this size for 'digestFinal'--- if you want to stay generic.--maxMDSize :: Int-maxMDSize = 36---- |Create an EVP context. May be 'nullPtr'.--foreign import ccall unsafe "EVP_MD_CTX_create" ctxCreate ::- IO Context---- |Initialize an EVP context.--foreign import ccall unsafe "EVP_MD_CTX_init" ctxInit ::- Context -> IO ()---- |Destroy an EVP context and free the allocated resources.--foreign import ccall unsafe "EVP_MD_CTX_destroy" ctxDestroy ::- Context -> IO ()---- |Set the message digest engine for 'digestUpdate' calls.--- Returns @\/=0@ in case of an error.--foreign import ccall unsafe "EVP_DigestInit" digestInit ::- Context -> MDEngine -> IO CInt---- |Update the internal context with a block of input.--- Returns @\/=0@ in case of an error.--foreign import ccall unsafe "EVP_DigestUpdate" digestUpdate ::- Context -> Ptr Word8 -> CUInt -> IO CInt---- |Wrap up the digest computation and return the final--- digest. Do not call repeatedly on the same context!--- Returns @\/=0@ in case of an error. The pointer to the--- unsigned integer may be 'nullPtr'. If it is not,--- 'digestFinal' will store the length of the computed--- digest there.--foreign import ccall unsafe "EVP_DigestFinal" digestFinal ::- Context -> Ptr Word8 -> Ptr CUInt -> IO CInt---- ** Message Digest Engines--foreign import ccall unsafe "EVP_dss" mdDSS :: IO MDEngine-foreign import ccall unsafe "EVP_dss1" mdDSS1 :: IO MDEngine-foreign import ccall unsafe "EVP_md5" mdMD5 :: IO MDEngine-foreign import ccall unsafe "EVP_md_null" mdNull :: IO MDEngine-foreign import ccall unsafe "EVP_mdc2" mdMDC2 :: IO MDEngine-foreign import ccall unsafe "EVP_ripemd160" mdRIPEMD160 :: IO MDEngine-foreign import ccall unsafe "EVP_sha" mdSHA :: IO MDEngine-foreign import ccall unsafe "EVP_sha1" mdSHA1 :: IO MDEngine-foreign import ccall unsafe "EVP_sha224" mdSHA224 :: IO MDEngine-foreign import ccall unsafe "EVP_sha256" mdSHA256 :: IO MDEngine-foreign import ccall unsafe "EVP_sha384" mdSHA384 :: IO MDEngine-foreign import ccall unsafe "EVP_sha512" mdSHA512 :: IO MDEngine---- |Map a 'MessageDigest' type into the the corresponding--- 'MDEngine'.--toMDEngine :: MessageDigest -> IO MDEngine-toMDEngine Null = mdNull-toMDEngine MD5 = mdMD5-toMDEngine SHA = mdSHA-toMDEngine SHA1 = mdSHA1-toMDEngine DSS = mdDSS-toMDEngine DSS1 = mdDSS1-toMDEngine RIPEMD160 = mdRIPEMD160-toMDEngine MDC2 = mdMDC2-toMDEngine SHA224 = mdSHA224-toMDEngine SHA256 = mdSHA256-toMDEngine SHA384 = mdSHA384-toMDEngine SHA512 = mdSHA512---- * Helper Functions---- |Neat helper to print digests with:--- @--- \\ws :: [Word8] -> ws >>= toHex--- @--toHex :: Word8 -> String-toHex w = case showHex w "" of- w1:w2:[] -> w1:w2:[]- w2:[] -> '0':w2:[]- _ -> error "showHex returned []"
− OpenSSL/Digest/ByteString.hs
@@ -1,38 +0,0 @@-{- |- Module : OpenSSL.Digest.ByteString- Copyright : (c) 2010 by Peter Simons- License : BSD3-- Maintainer : simons@cryp.to- Stability : provisional- Portability : portable-- Wrappers for "OpenSSL.Digest" that supports 'ByteString'.- -}--module OpenSSL.Digest.ByteString where--import OpenSSL.Digest hiding ( update )-import Control.Monad.State ( evalStateT, lift, get )-import Foreign.Ptr ( castPtr )-import Data.Word ( Word8 )-import Data.ByteString ( ByteString )-import Data.ByteString.Unsafe ( unsafeUseAsCStringLen )---- |A convenience wrapper which computes the given digest type of a--- 'ByteString'. Unlike the monadic interface, this function does not--- allow the computation to be restarted.--digest :: MessageDigest -> ByteString -> IO [Word8]-digest mdType xs =- mkDigest mdType $ evalStateT (update xs >> final)---- |Update the internal state with a block of data.--update :: ByteString -> Digest Int-update bs = do- DST ctx <- get- l <- lift $- unsafeUseAsCStringLen bs $ \(ptr, len) ->- digestUpdate ctx (castPtr ptr) (fromIntegral len)- return (fromEnum l)
− OpenSSL/Digest/ByteString/Lazy.hs
@@ -1,32 +0,0 @@-{- |- Module : OpenSSL.Digest.ByteString.Lazy- Copyright : (c) 2010 by Peter Simons- License : BSD3-- Maintainer : simons@cryp.to- Stability : provisional- Portability : portable-- Wrappers for "OpenSSL.Digest" that supports lazy 'ByteString'.- -}--module OpenSSL.Digest.ByteString.Lazy where--import OpenSSL.Digest hiding ( update )-import Data.Word ( Word8 )-import Control.Monad.State ( evalStateT )-import qualified OpenSSL.Digest.ByteString as BS ( update )-import Data.ByteString.Lazy ( ByteString, toChunks )---- |A convenience wrapper which computes the given digest type of a--- 'ByteString'. Unlike the monadic interface, this function does not--- allow the computation to be restarted.--digest :: MessageDigest -> ByteString -> IO [Word8]-digest mdType xs =- mkDigest mdType $ evalStateT (update xs >> final)---- |Update the internal state with a block of data.--update :: ByteString -> Digest Int-update = fmap sum . mapM BS.update . toChunks
hopenssl.cabal view
@@ -1,31 +1,55 @@-Name: hopenssl-Version: 1.7-Copyright: (c) 2004-2013 Peter Simons-License: BSD3-License-File: LICENSE-Author: Peter Simons <simons@cryp.to>,- Jesper Louis Andersen <jesper.louis.andersen@gmail.com>,- Markus Rothe <markus@unixforces.net>-Maintainer: Peter Simons <simons@cryp.to>-Homepage: http://github.com/peti/hopenssl-Category: Foreign, Cryptography-Synopsis: FFI bindings to OpenSSL's EVP digest interface-Description: Foreign-function bindings to the OpenSSL library- <http://www.openssl.org/>. Currently provides- access to the messages digests MD5, DSS, DSS1,- RIPEMD160, and several variants of SHA through- the EVP digest interface.-Cabal-Version: >= 1.6-Build-Type: Simple-Tested-With: GHC >= 6.10.4 && <= 7.8.3+name: hopenssl+version: 2+copyright: (c) 2004-2017 Peter Simons+license: BSD3+license-file: LICENSE+author: Peter Simons, Markus Rothe+maintainer: Peter Simons <simons@cryp.to>+homepage: http://github.com/peti/hopenssl+category: Foreign, Cryptography+synopsis: FFI bindings to OpenSSL's EVP digest interface+description: Foreign-function bindings to the+ <http://www.openssl.org/ OpenSSL library>. Currently+ provides access to the messages digests MD5, DSS, DSS1,+ RIPEMD160, and several variants of SHA through the EVP+ digest interface.+cabal-version: >= 1.8+build-type: Simple+tested-with: GHC > 7.6 && < 8.1 -Source-Repository head- Type: git- Location: git://github.com/peti/hopenssl.git+source-repository head+ type: git+ location: git://github.com/peti/hopenssl.git -Library- Build-Depends: base >= 3 && < 5, mtl, bytestring- Extensions: ForeignFunctionInterface- Extra-Libraries: crypto- Includes: "openssl/evp.h"- Exposed-Modules: OpenSSL.Digest, OpenSSL.Digest.ByteString, OpenSSL.Digest.ByteString.Lazy+library+ build-depends: base >= 3 && < 5, bytestring+ hs-source-dirs: src+ other-extensions: ForeignFunctionInterface, EmptyDataDecls, DeriveDataTypeable,+ FlexibleInstances, TypeSynonymInstances+ extra-libraries: crypto+ includes: "openssl/evp.h"+ exposed-modules: OpenSSL.Digest+ OpenSSL.EVP.Digest++test-suite check-low-level-digest-api+ type: exitcode-stdio-1.0+ main-is: CheckLowLevelDigestAPI.hs+ other-modules: OpenSesame+ hs-source-dirs: test+ build-depends: base >= 3 && < 5, hopenssl, HUnit+ ghc-options: -threaded++test-suite check-high-level-digest-api+ type: exitcode-stdio-1.0+ main-is: CheckHighLevelDigestAPI.hs+ other-modules: OpenSesame+ hs-source-dirs: test+ build-depends: base >= 3 && < 5, hopenssl, HUnit+ ghc-options: -threaded++test-suite doctests+ type: exitcode-stdio-1.0+ main-is: doctests.hs+ hs-source-dirs: test+ build-depends: base, hopenssl, doctest+ ghc-options: -threaded
+ src/OpenSSL/Digest.hs view
@@ -0,0 +1,201 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE TypeSynonymInstances #-}++{- |+ Maintainer: simons@cryp.to+ Stability: provisional+ Portability: portable++ This module provides a generic high-level API to the message digest+ algorithms found in OpenSSL's @crypto@ library. There are two functions of+ particular interest: 'digestByName' and 'digest'. The former can be used to+ retrieve a 'DigestDescription', i.e. an OpenSSL object that implements a+ particular algorithm. That type can then be used to compute actual message+ digests with the latter function:++ >>> import Data.ByteString.Char8 ( pack )+ >>> digest (digestByName "md5") (pack "Hello, world.")+ "\b\n\239\131\155\149\250\207s\236Y\147u\233-G"++ Neat pretty-printing can be achieved with 'toHex', which converts the binary+ representation of a message digest into the common hexadecimal one:++ >>> toHex $ digest (digestByName "md5") (pack "Hello, world.")+ "080aef839b95facf73ec599375e92d47"+ >>> toHex $ digest (digestByName "sha1") (pack "Hello, world.")+ "2ae01472317d1935a84797ec1983ae243fc6aa28"++ The precise set of available digest algorithms provided by OpenSSL depends+ on the version of the library installed into the system, obviously, but it's+ reasonable to expect the following algorithms to be present: MD5, RIPEMD160,+ SHA1, SHA224, SHA256, SHA384, and SHA512. If an algorithm is not available,+ 'digestByName' will throw an 'DigestAlgorithmNotAvailableInOpenSSL'+ exception. If you don't like exceptions, use the tamer 'digestByName''+ variant:++ >>> digestByName' "i bet this algorithm won't exist"+ Nothing++ 'DigestDescription' is an instance of 'IsString', so with the proper GHC+ extensions enabled it's possible to simplify the call to 'digest' even+ further:++ >>> :set -XOverloadedStrings+ >>> toHex $ digest "sha256" (pack "The 'Through the Universe' podcast rules.")+ "73624694a9435095c8fdaad711273a23c02226196c452f817cfd86f965895614"++ Last but not least, 'digest' is actually a class method of 'Digestable',+ which collects things we can compute digests of. The defaults are+ conservative, i.e. we support all things that correspond roughly to C's+ construct of "void pointer plus a length". @digest@ can use with any of the+ following signatures:++ >>> let shape1 = digest :: DigestDescription -> (Ptr (), CSize) -> MessageDigest+ >>> let shape2 = digest :: DigestDescription -> (Ptr Word8, CSize) -> MessageDigest+ >>> let shape3 = digest :: DigestDescription -> (Ptr Word8, CUInt) -> MessageDigest+ >>> let shape4 = digest :: DigestDescription -> (Ptr (), Int) -> MessageDigest+ >>> let shape5 = digest :: DigestDescription -> StrictByteString -> MessageDigest+ >>> let shape6 = digest :: DigestDescription -> LazyByteString -> MessageDigest++ 'StrictByteString' and 'LazyByteString' are also instances of 'IsString' and+ therefore subject to implicit construction from string literals:++ >>> shape5 "sha256" "hello" == shape6 "sha256" "hello"+ True++ Note that this code offers no overloaded 'digest' version for 'String',+ because that function would produce non-deterministic results for Unicode+ characters. There is an instance for @[Word8]@, though, so strings can be+ hashed after a proper encoding has been applied. For those who don't care+ about determinism, there is the following specialized function:++ >>> toHex $ digestString "md5" "no Digestable instance for this sucker"+ "a74827f849005794565f83fbd68ad189"++ If you don't mind orphaned instances, however, feel free to shoot yourself+ in the foot:++ >>> :set -XFlexibleInstances+ >>> instance Digestable String where updateChunk ctx str = withCStringLen str (updateChunk ctx)+ >>> toHex $ digest "sha256" ("now we can hash strings" :: String)+ "7f2989f173125810aa917c4ffe0e26ae1b5f7fb852274829c210297a43dfc7f9"+-}++module OpenSSL.Digest+ ( -- * Generic digest API+ MessageDigest, digest, Digestable(..), digestByName, digestByName', DigestDescription+ , -- * Special instances+ digestString+ , -- * Helper Types and Functions+ toHex, StrictByteString, LazyByteString+ )+ where++import OpenSSL.EVP.Digest hiding ( toHex )++import Control.Exception+import qualified Data.ByteString as Strict ( ByteString, packCStringLen, concatMap )+import Data.ByteString.Char8 as Strict8 ( pack )+import qualified Data.ByteString.Lazy as Lazy ( ByteString, toChunks )+import Data.ByteString.Unsafe ( unsafeUseAsCStringLen )+import Foreign+import Foreign.C+import Numeric ( showHex )+import System.IO.Unsafe as IO++-- $setup+-- >>> import Data.Maybe++-- Generic Class API ----------------------------------------------------------++-- |A message digest is essentially an array of 'Word8' octets.++type MessageDigest = StrictByteString++-- | Compute the given message digest of any 'Digestable' thing, i.e. any type+-- that can be converted /efficiently/ and /unambiguously/ into a continuous+-- memory buffer or a sequence of continuous memory buffers. Note that 'String'+-- does /not/ have that property, because there . The actual+-- binary representation chosen for Unicode characters during that process is+-- determined by the system's locale and is therefore non-deterministic.++digest :: Digestable a => DigestDescription -> a -> MessageDigest+digest algo input =+ IO.unsafePerformIO $+ bracket createContext destroyContext $ \ctx -> do+ initDigest algo ctx+ updateChunk ctx input+ let mdSize = fromIntegral (_digestSize (getDigestDescription algo))+ allocaArray mdSize $ \md -> do+ finalizeDigest ctx md+ Strict.packCStringLen (castPtr md, mdSize)++class Digestable a where+ updateChunk :: DigestContext -> a -> IO ()++instance Digestable (Ptr a, CSize) where+ {-# INLINE updateChunk #-}+ updateChunk ctx = uncurry (updateDigest ctx)++instance Digestable (Ptr a, CUInt) where+ {-# INLINE updateChunk #-}+ updateChunk ctx = updateChunk ctx . fmap (fromIntegral :: CUInt -> CSize)++instance Digestable (Ptr a, CInt) where+ {-# INLINE updateChunk #-}+ updateChunk ctx = updateChunk ctx . fmap (fromIntegral :: CInt -> CSize)++instance Digestable (Ptr a, Int) where+ {-# INLINE updateChunk #-}+ updateChunk ctx = updateChunk ctx . fmap (fromIntegral :: Int -> CSize)++instance Digestable [Word8] where+ {-# INLINE updateChunk #-}+ updateChunk ctx buf = withArrayLen buf $ \len ptr -> updateChunk ctx (ptr,len)++instance Digestable StrictByteString where+ {-# INLINE updateChunk #-}+ updateChunk ctx str = unsafeUseAsCStringLen str (updateChunk ctx)++instance Digestable LazyByteString where+ {-# INLINE updateChunk #-}+ updateChunk ctx = mapM_ (updateChunk ctx) . Lazy.toChunks++-- |We do /not/ define a 'Digestable' instance for 'String', because there is+-- no one obviously correct way to encode Unicode characters for purposes of+-- calculating a digest. We have, however, this specialized function which+-- computes a digest over a @String@ by means of 'withCStrinLen'. This means+-- that the representation of Unicode characters depends on the process locale+-- a.k.a. it's non-deterministc!+--+-- >>> toHex $ digestString (digestByName "sha1") "Hello, world."+-- "2ae01472317d1935a84797ec1983ae243fc6aa28"++digestString :: DigestDescription -> String -> MessageDigest+digestString algo str = IO.unsafePerformIO $+ withCStringLen str (return . digest algo)++-- Helper functions -----------------------------------------------------------++-- | Synonym for the strict 'Strict.ByteString' variant to improve readability.++type StrictByteString = Strict.ByteString++-- | Synonym for the lazy 'Lazy.ByteString' variant to improve readability.++type LazyByteString = Lazy.ByteString++-- | Pretty-print a given message digest from binary into hexadecimal+-- representation.+--+-- >>> toHex (Data.ByteString.pack [0..15])+-- "000102030405060708090a0b0c0d0e0f"++toHex :: MessageDigest -> StrictByteString+toHex = Strict.concatMap f+ where+ f :: Word8 -> StrictByteString+ f w = case showHex w "" of+ [w1,w2] -> pack [w1, w2]+ [w2] -> pack ['0', w2]+ _ -> error "showHex returned []"
+ src/OpenSSL/EVP/Digest.hsc view
@@ -0,0 +1,260 @@+{-# LANGUAGE ForeignFunctionInterface #-}+{-# LANGUAGE EmptyDataDecls #-}+{-# LANGUAGE DeriveDataTypeable #-}++{- |+ Maintainer: simons@cryp.to+ Stability: provisional+ Portability: portable++ Computing message digests with OpenSSL's EVP interface involves the+ following types:++ * Every digest algorithm has an description, 'OpaqueDigestDescription' that+ can be looked up by name. We can do very few things with that type. We+ can use it to retrieve the size of the algorithm's output, '_digestSize'++ * TODO: complete this when I know what the high-level API looks like.++-}++module OpenSSL.EVP.Digest where++import Control.Concurrent.MVar+import Control.Exception+import Control.Monad+import Data.Maybe+import Data.String ( IsString(..) )+import Data.Typeable ( Typeable )+import Foreign+import Foreign.C+import Numeric ( showHex )+import System.IO.Unsafe as IO++#include "openssl/evp.h"++#if __GLASGOW_HASKELL__ < 800+# let alignment t = "%lu", (unsigned long)offsetof(struct {char x__; t (y__); }, y__)+#endif++-- * Low-level API++-------------------------------------------------------------------------------+-- ** OpenSSL Library Initialization+-------------------------------------------------------------------------------++-- | Initialize the OpenSSL EVP engine and register all known digest types in+-- the internal data structures. This function must be called before+-- '_digestByName' can succeed. Calling it multiple times is probably not+-- harmful, but it certainly unnecessary and should be avoided. Users of+-- 'digestByName'' and 'digestByName' don't have to worry about this.++foreign import ccall unsafe "openssl/evp.h OpenSSL_add_all_digests" _addAllDigests :: IO ()++-------------------------------------------------------------------------------+-- ** Accessing the Supported Digest Types+-------------------------------------------------------------------------------++data OpaqueDigestDescription++-- | Look up a 'Digest' by name. Be sure to call '_addAllDigests' before you+-- use this function.++foreign import ccall unsafe "openssl/evp.h EVP_get_digestbyname" _digestByName :: CString -> Ptr OpaqueDigestDescription++-- | Return the size of the digest the given algorithm will produce.++foreign import ccall unsafe "openssl/evp.h EVP_MD_size" _digestSize :: Ptr OpaqueDigestDescription -> CInt++-- | Return the block size the the given algorithm operates with.++foreign import ccall unsafe "openssl/evp.h EVP_MD_block_size" _digestBlockSize :: Ptr OpaqueDigestDescription -> CInt++-- | The largest possible digest size of any of the algorithms supported by+-- this library. So if you want to store a digest without bothering to retrieve+-- the appropriate size with '_digestSize' first, allocate a buffer of that+-- size.++maxDigestSize :: Int+maxDigestSize = #{const EVP_MAX_MD_SIZE}++-- | We don't support choosing specific engines. Always pass 'nullPtr' where+-- such a thing is expected to get the default engine for the given algorithm.++data OpaqueDigestEngine++-------------------------------------------------------------------------------+-- ** Digest Contexts+-------------------------------------------------------------------------------++-- | A context in which -- when initialized -- digest computations can be run.+-- There is a 'Storable' solely for the benefit of being able to create that+-- type with 'alloca' and '_init' instead of having to use '_create', which+-- uses the heap. Anyway, that instance does not define 'peek' nor 'poke' since+-- those make no sense.++data OpaqueDigestContext++instance Storable OpaqueDigestContext where+ sizeOf _ = #{size EVP_MD_CTX}+ alignment _ = #{alignment EVP_MD_CTX}+ peek _ = error "Don't do this. OpaqueDigestContext is, like, opaque."+ poke _ _ = error "Don't do this. OpaqueDigestContext is, like, opaque."++-- | Allocate an (initialized) 'OpaqueDigestContext' for use in a digest+-- computation on the heap. Release its underlying memory after use with+-- '_destroy'.++foreign import ccall unsafe "openssl/evp.h EVP_MD_CTX_create" _createContext :: IO (Ptr OpaqueDigestContext)++-- | Initialize an 'OpaqueDigestContext' for use in a digest computation. The+-- type can be allocated on the stack with 'alloca' or on the heap with+-- '_create'.++foreign import ccall unsafe "openssl/evp.h EVP_MD_CTX_init" _initContext :: Ptr OpaqueDigestContext -> IO ()++-- | Release all resources associated with a digest computation's context, but+-- don't release the underlying digest context structure. This allows the context+-- to be re-initiaized for use another computation.++foreign import ccall unsafe "openssl/evp.h EVP_MD_CTX_cleanup" _cleanupContext :: Ptr OpaqueDigestContext -> IO CInt++-- | Release all resources associated with a digest computation's context and the+-- context structure itself. Use this only for context's acquired with '_create'.++foreign import ccall unsafe "openssl/evp.h EVP_MD_CTX_destroy" _destroyContext :: Ptr OpaqueDigestContext -> IO ()++-------------------------------------------------------------------------------+-- ** State of a Digest Computation+-------------------------------------------------------------------------------++-- | Configure the given /initialized/ digest context to use the given message+-- digest algorithm. The third parameter allows developers to choose a specific+-- engine for that digest, too, but these bindings don't support choosing any+-- specific engine, so pass 'nullPtr' here to the default choice determined by+-- OpenSSL.++foreign import ccall unsafe "openssl/evp.h EVP_DigestInit_ex" _initDigest :: Ptr OpaqueDigestContext -> Ptr OpaqueDigestDescription -> Ptr OpaqueDigestEngine -> IO CInt++-- | Hash the given block of memory and update the digest state accordingly.+-- Naturally, this function can be called many times. Then use+-- '_finalizeDigest' to retrieve the actual hash value.++foreign import ccall unsafe "openssl/evp.h EVP_DigestUpdate" _updateDigest :: Ptr OpaqueDigestContext -> Ptr a -> CSize -> IO CInt++-- | Finalize the digest calculation and return the result in the 'Word8' array+-- passed as an argument. Naturally, that array is expected to be large enough+-- to contain the digest. '_digestSize' or 'maxDigestSize' are your friends. If+-- the 'CUInt' pointer is not 'nullPtr', then the actual size of the generated+-- digest is written into that integer. This function does /not/ clean up the+-- digest context; this has to be done with an explicit call to+-- '_cleanupContext' or '_destroyContext'. However, it does invalidate the+-- digest state so that no further calls of '_digestUpdate' can be made without+-- re-initializing the state with '_initDigest' first.++foreign import ccall unsafe "openssl/evp.h EVP_DigestFinal_ex" _finalizeDigest :: Ptr OpaqueDigestContext -> Ptr Word8 -> Ptr CUInt -> IO CInt++-------------------------------------------------------------------------------+-- * High-level interface+-------------------------------------------------------------------------------++newtype DigestDescription = DigestDescription { getDigestDescription :: Ptr OpaqueDigestDescription }+ deriving (Show, Eq)++digestByName :: String -> DigestDescription+digestByName algo =+ fromMaybe (throw (DigestAlgorithmNotAvailableInOpenSSL algo))+ (digestByName' algo)++digestByName' :: String -> Maybe DigestDescription+digestByName' algo = if ptr == nullPtr then Nothing else Just (DigestDescription ptr)+ where ptr = IO.unsafePerformIO $ withCString algo $ \name -> do+ modifyMVar_ isDigestEngineInitialized $ \isInitialized ->+ unless isInitialized _addAllDigests >> return True+ return (_digestByName name)++newtype DigestContext = DigestContext { getDigestContext :: Ptr OpaqueDigestContext }++digestContext :: Ptr OpaqueDigestContext -> DigestContext+digestContext ptr+ | ptr == nullPtr = throw AttemptToConstructDigestContextFromNullPointer+ | otherwise = DigestContext ptr++initContext :: DigestContext -> IO ()+initContext (DigestContext ctx) = _initContext ctx++createContext :: IO DigestContext+createContext =+ fmap DigestContext (throwIfNull "OpenSSL.EVP.Digest.createContext failed" _createContext)++-- | Simplified variant of '_initDigest' that (a) always chooses the default+-- digest engine and (b) reports failure by means of an exception.++initDigest :: DigestDescription -> DigestContext -> IO ()+initDigest (DigestDescription algo) (DigestContext ctx) =+ throwIfZero "OpenSSL.EVP.Digest.initDigest" (_initDigest ctx algo nullPtr)++cleanupContext :: DigestContext -> IO ()+cleanupContext (DigestContext ctx) =+ throwIfZero "OpenSSL.EVP.Digest.cleanupContext" (_cleanupContext ctx)++destroyContext :: DigestContext -> IO ()+destroyContext (DigestContext ctx) = _destroyContext ctx++updateDigest :: DigestContext -> Ptr a -> CSize -> IO ()+updateDigest (DigestContext ctx) ptr len =+ throwIfZero "OpenSSL.EVP.Digest.updateDigest" (_updateDigest ctx ptr len)++finalizeDigest :: DigestContext -> Ptr Word8 -> IO ()+finalizeDigest (DigestContext ctx) ptr =+ throwIfZero "OpenSSL.EVP.Digest.finalizeDigest" (_finalizeDigest ctx ptr nullPtr)++-- * Helper Types and Functions++-- | Most OpenSSL functions return an approximation of @Bool@ to signify+-- failure. This wrapper makes it easier to move the error handling to the+-- exception layer where appropriate.++throwIfZero :: String -> IO CInt -> IO ()+throwIfZero fname =+ throwIf_ (==0) (const (showString fname " failed with error code 0"))++-- |Neat helper to pretty-print digests into the common hexadecimal notation:+--+-- >>> [0..15] >>= toHex+-- "000102030405060708090a0b0c0d0e0f"++toHex :: Word8 -> String+toHex w = case showHex w "" of+ [w1,w2] -> [w1, w2]+ [w2] -> ['0', w2]+ _ -> "showHex returned []"++{-# NOINLINE isDigestEngineInitialized #-}+isDigestEngineInitialized :: MVar Bool+isDigestEngineInitialized = IO.unsafePerformIO $ newMVar False++-- | This instance allows the compiler to translate the string @"sha256"@ into+-- @digestByName "sha256"@ whenever a 'String' is passed in a location that+-- expects a 'DigestDescription'. If that digest engine does not exist, then an+-- exception is thrown. This feature requires the @OverloadedStrings@ extension+-- enabled.++instance IsString DigestDescription where+ fromString = digestByName++-- | A custom exception type which is thrown by 'digestByName' in case the+-- requested digest algorithm is not available in the OpenSSL system library.++newtype DigestAlgorithmNotAvailableInOpenSSL = DigestAlgorithmNotAvailableInOpenSSL String+ deriving (Show, Typeable)++instance Exception DigestAlgorithmNotAvailableInOpenSSL++-- | A custom exception type thrown by 'digestContext' if the function is used+-- to construct a 'DigestContext' from a 'nullPtr'.++data AttemptToConstructDigestContextFromNullPointer = AttemptToConstructDigestContextFromNullPointer+ deriving (Show, Typeable)++instance Exception AttemptToConstructDigestContextFromNullPointer
+ test/CheckHighLevelDigestAPI.hs view
@@ -0,0 +1,18 @@+module Main ( main ) where++import OpenSSL.Digest+import OpenSesame++import Test.HUnit++main :: IO ()+main = runTestTT (TestList tests) >> return ()++tests :: [Test]+tests = map (uncurry (mkTest "open sesame")) opensesame++mkTest :: String -> String -> String -> Test+mkTest input algoName expect = TestCase $+ case digestByName' algoName of+ Nothing -> return ()+ Just algo -> assertEqual algoName expect (show (toHex (digestString algo input)))
+ test/CheckLowLevelDigestAPI.hs view
@@ -0,0 +1,34 @@+module Main ( main ) where++import OpenSSL.EVP.Digest+import OpenSesame++import Control.Exception+import Foreign+import Foreign.C.String+import Test.HUnit++main :: IO ()+main = runTestTT (TestList tests) >> return ()++tests :: [Test]+tests = map (uncurry (mkTest "open sesame")) opensesame++mkTest :: String -> String -> String -> Test+mkTest input algoName expect = TestCase $+ case digestByName' algoName of+ Nothing -> return ()+ Just algo -> digest algo input >>= assertEqual algoName expect++digest :: DigestDescription -> String -> IO String+digest algo input = do+ let digestSize = _digestSize (getDigestDescription algo)+ md <- alloca $ \ctx' -> do+ let ctx = digestContext ctx'+ bracket_ (initContext ctx) (cleanupContext ctx) $ do+ initDigest algo ctx+ withCStringLen input $ \(ptr,len) -> updateDigest ctx ptr (fromIntegral len)+ allocaArray (fromIntegral digestSize) $ \md -> do+ finalizeDigest ctx md+ peekArray (fromIntegral digestSize) md+ return (md >>= toHex)
+ test/OpenSesame.hs view
@@ -0,0 +1,15 @@+module OpenSesame where++opensesame :: [(String, String)]+opensesame = [ ("MD5", "54ef36ec71201fdf9d1423fd26f97f6b")+ , ("SHA", "2ccefef64c76ac0d42ca1657457977675890c42f")+ , ("SHA1", "5bcaff7f22ff533ca099b3408ead876c0ebba9a7")+ , ("DSS", "5bcaff7f22ff533ca099b3408ead876c0ebba9a7")+ , ("DSS1", "5bcaff7f22ff533ca099b3408ead876c0ebba9a7")+ , ("RIPEMD160", "bdb2bba6ec93bd566dc1181cadbc92176aa78382")+ , ("MDC2", "112db2200ce1e9db3c2d132aea4ef7d0")+ , ("SHA224", "1ee0f9d93a873a67fe781852d716cb3e5904e015aafaa4d1ff1a81bc")+ , ("SHA256", "41ef4bb0b23661e66301aac36066912dac037827b4ae63a7b1165a5aa93ed4eb")+ , ("SHA384", "ae2a5d6649035c00efe2bc1b5c97f4d5ff97fa2df06f273afa0231c425e8aff30e4cc1db5e5756e8d2245a1514ad1a2d")+ , ("SHA512", "8470cdd3bf1ef85d5f092bce5ae5af97ce50820481bf43b2413807fec37e2785b533a65d4c7d71695b141d81ebcd4b6c4def4284e6067f0b9ddc318b1b230205")+ ]
+ test/doctests.hs view
@@ -0,0 +1,12 @@+module Main (main) where++import Test.DocTest+import System.Environment+import Data.Maybe++main :: IO ()+main = do+ distDir <- fromMaybe "dist" `fmap` lookupEnv "HASKELL_DIST_DIR"+ let hscFilesDir = distDir ++ "/build"+ packageDB = distDir ++ "/package.conf.inplace"+ doctest [ "-i" ++ hscFilesDir, "-package-db=" ++ packageDB, "-package=hopenssl", "src" ]