eap 0.6.0.0 → 0.7.0.0
raw patch · 2 files changed
+88/−33 lines, 2 filesdep ~cryptonitedep ~memorydep ~mtlPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: cryptonite, memory, mtl, pretty-hex
API changes (from Hackage documentation)
+ Network.EAP.Authentication: generateAuthenticatorResponse :: ByteString -> ByteString -> ByteString -> ByteString -> ByteString -> Except CryptoError ByteString
+ Network.EAP.Authentication: ntPasswordHash :: ByteString -> ByteString
Files
- eap.cabal +1/−1
- src/Network/EAP/Authentication.hs +87/−32
eap.cabal view
@@ -1,5 +1,5 @@ name: eap-version: 0.6.0.0+version: 0.7.0.0 synopsis: Extensible Authentication Protocol (EAP) description: This module provides types and on the wire de/coding of EAP packets as per RFC 3748 homepage: https://github.com/erickg/eap#readme
src/Network/EAP/Authentication.hs view
@@ -13,55 +13,64 @@ -} module Network.EAP.Authentication (authenticateMSCHAPv2,- generateNTResponse) where+ generateAuthenticatorResponse,+ generateNTResponse,+ ntPasswordHash) where import Prelude hiding (concatMap)-import Data.Bits ((.|.), (.&.), complement, shiftL, shiftR, xor)-import Data.ByteString.Lazy (ByteString, cons, concatMap, toStrict)-import Data.ByteArray (convert)-import Control.Monad.Except (ExceptT(..), Except, throwError)-import Control.Monad.State.Lazy (execState, modify)-import Crypto.Cipher.DES (DES)-import Crypto.Cipher.Types (cipherInit, ecbEncrypt)-import Crypto.Hash.Algorithms (MD4, SHA1(..))-import Crypto.Hash (Digest, hashFinalize, hashInitWith, hashUpdate, hashlazy)-import Crypto.Error (CryptoError, CryptoFailable(..))+import Data.Bits ((.|.), (.&.), complement, shiftL, shiftR, xor)+import Data.ByteString.Lazy.Char8 (pack, unpack)+import Data.ByteString.Builder (toLazyByteString, byteStringHex)+import Data.ByteString.Lazy (ByteString, append, cons, concatMap, fromStrict, toStrict)+import Data.ByteArray (convert)+import Data.Char (toUpper)+import Control.Monad.Except (ExceptT(..), Except, throwError)+import Control.Monad.State.Lazy (State, execState, modify)+import Crypto.Cipher.DES (DES)+import Crypto.Cipher.Types (cipherInit, ecbEncrypt)+import Crypto.Hash.Algorithms (MD4, SHA1(..))+import Crypto.Hash (Context,+ Digest,+ hashFinalize,+ hashInitWith,+ hashUpdate,+ hashlazy)+import Crypto.Error (CryptoError, CryptoFailable(..)) import Network.EAP.Types import qualified Data.ByteString as SB -- | Authenticate the MSCHAPv2 response data to a given challenge request, using the supplied -- cleartext password. authenticateMSCHAPv2- :: MSCHAPv2Data -- ^ Decoded data from the MSCHAPv2 response- -> ByteString -- ^ Authenticator challenge sent to the peer on a previous request- -> ByteString -- ^ Authenticating user password+ :: MSCHAPv2Data -- ^ Decoded data from the MSCHAPv2 response+ -> ByteString -- ^ Authenticator challenge sent to the peer on a previous request+ -> SB.ByteString -- ^ Authenticating user password NT hash (MD4) -> Except CryptoError Bool -- ^ Returns either an error from one of the encryption -- routines or a boolean indicating whether the user -- response matches the expected value authenticateMSCHAPv2 MSCHAPv2ResponseData{ getMSCHAPv2ResponseData = MSCHAPv2ResponseDataField{..}, .. } challenge- password = do+ passwordHash = do let peerChallenge = getMSCHAPv2ResponsePeerChallenge username = getMSCHAPv2ResponseName- r <- generateNTResponse challenge peerChallenge username password+ r <- generateNTResponse challenge peerChallenge username passwordHash return $ r == toStrict getMSCHAPv2ResponseNTResponse authenticateMSCHAPv2 msCHAPv2Data _ _ = error $ "Invalid authentication attempt of " ++ show msCHAPv2Data -- | Calculate the NT Response as per [RFC2759], Section 8.1-generateNTResponse :: ByteString -- ^ Authenticator challenge sent to the peer on a previous- -- request- -> ByteString -- ^ Challenge sent back by authenticating peer- -> ByteString -- ^ MSCHAP username- -> ByteString -- ^ Cleartext user password+generateNTResponse :: ByteString -- ^ Authenticator challenge sent to the peer on a previous+ -- request+ -> ByteString -- ^ Challenge sent back by authenticating peer+ -> ByteString -- ^ MSCHAP username+ -> SB.ByteString -- ^ NT hash (MD4) of user password -> Except CryptoError SB.ByteString -- ^ Returns either an error from one of -- the encryption routines or the -- calculated NT response-generateNTResponse authenticatorChallenge peerChallenge username password = do- let challenge = challengeHash- passwordHash = ntPasswordHash password+generateNTResponse authenticatorChallenge peerChallenge username passwordHash = do+ let challenge = challengeHash peerChallenge authenticatorChallenge username zPasswordHash = passwordHash `SB.append` SB.replicate 5 0 -- pad to 21 octets (pHash0, rest) = SB.splitAt 7 zPasswordHash (pHash1, pHash2) = SB.splitAt 7 rest@@ -69,14 +78,60 @@ r1 <- encryptDES pHash1 challenge r2 <- encryptDES pHash2 challenge return $ r0 `SB.append` r1 `SB.append` r2- where challengeHash = SB.take 8 . convert . hashFinalize . flip execState ctx0 $ do- hash peerChallenge- hash authenticatorChallenge- hash username- hash = modify . flip hashUpdate . toStrict- ctx0 = hashInitWith SHA1- ntPasswordHash = convert . (hashlazy :: ByteString -> Digest MD4) . concatMap with0s- with0s = flip cons "\NUL"++-- | Calculate authenticator response as per [RFC2759], Section 8.7+generateAuthenticatorResponse :: ByteString -- ^ Username+ -> ByteString -- ^ NT password hash (MD4)+ -> ByteString -- ^ NT Response+ -> ByteString -- ^ Authenticator challenge+ -> ByteString -- ^ Peer challenge+ -> Except CryptoError ByteString -- ^ Returns either an error from+ -- one of the crypto routines or+ -- upon success, a 42 byte+ -- authenticator response+generateAuthenticatorResponse username passwordHash ntResponse authChallenge peerChallenge = do+ let passwordHashHash = md4Hash passwordHash+ digest1 = sha1Hash $ do+ hash . fromStrict $ passwordHashHash+ hash ntResponse+ hash magic1+ challenge = challengeHash peerChallenge authChallenge username+ digest2 = sha1Hash $ do+ hash . fromStrict $ digest1+ hash . fromStrict $ challenge+ hash magic2+ return $ "S=" `append` (pack . fmap toUpper . unpack . toLazyByteString $ byteStringHex digest2)+ where magic1 = "Magic server to client signing constant" -- seriously Microsoft?+ magic2 = "Pad to make it do more than one iteration"++challengeHash :: ByteString -- ^ Peer challenge+ -> ByteString -- ^ Authenticator challenge+ -> ByteString -- ^ Username+ -> SB.ByteString -- ^ Resulting SHA1 hash+challengeHash peerChallenge authenticatorChallenge username =+ SB.take 8 . sha1Hash $ do+ hash peerChallenge+ hash authenticatorChallenge+ hash username++-- | Hash an NT ascii plain-text. Password with MD4. Note that this function converts then+-- password internally to Unicode, so feeding a Unicode password to it will *not* work+ntPasswordHash :: ByteString -> SB.ByteString+ntPasswordHash = md4Hash . concatMap with0s+ where with0s = flip cons "\NUL"++-- | Used internally to calculate an MD4 hash.+md4Hash :: ByteString -> SB.ByteString+md4Hash = convert . (hashlazy :: ByteString -> Digest MD4)++-- | Used internally to calculate a hash digest incrementally+sha1Hash :: State (Context SHA1) () -> SB.ByteString+sha1Hash = convert . hashFinalize . flip execState ctx0+ where ctx0 = hashInitWith SHA1++-- | Used internally to update an incremental hash function in the state monad+hash :: ByteString -> State (Context SHA1) ()+hash = modify . flip hashUpdate . toStrict -- | Used internally to encrypt a message using a DES cipher in ECB mode encryptDES :: SB.ByteString -> SB.ByteString -> Except CryptoError SB.ByteString