packages feed

eap 0.8.0.0 → 0.9.0.0

raw patch · 3 files changed

+190/−159 lines, 3 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Network.EAP.Authentication: authenticateMSCHAPv2 :: MSCHAPv2Data -> ByteString -> ByteString -> Except CryptoError Bool
- Network.EAP.Authentication: generateAuthenticatorResponse :: ByteString -> ByteString -> ByteString -> ByteString -> ByteString -> Except CryptoError ByteString
- Network.EAP.Authentication: generateNTResponse :: ByteString -> ByteString -> ByteString -> ByteString -> Except CryptoError ByteString
- Network.EAP.Authentication: ntPasswordHash :: ByteString -> ByteString
+ Network.EAP.MSCHAPv2: authenticateMSCHAPv2 :: MSCHAPv2Data -> ByteString -> ByteString -> Except CryptoError Bool
+ Network.EAP.MSCHAPv2: deriveMPPEKeys :: ByteString -> ByteString -> Except CryptoError (ByteString, ByteString)
+ Network.EAP.MSCHAPv2: generateAuthenticatorResponse :: ByteString -> ByteString -> ByteString -> ByteString -> ByteString -> Except CryptoError ByteString
+ Network.EAP.MSCHAPv2: generateNTResponse :: ByteString -> ByteString -> ByteString -> ByteString -> Except CryptoError ByteString
+ Network.EAP.MSCHAPv2: ntPasswordHash :: ByteString -> ByteString

Files

eap.cabal view
@@ -1,5 +1,5 @@ name:                eap-version:             0.8.0.0+version:             0.9.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@@ -15,7 +15,7 @@  library   hs-source-dirs:      src-  exposed-modules:     Network.EAP.Authentication+  exposed-modules:     Network.EAP.MSCHAPv2                      , Network.EAP.Encoding                      , Network.EAP.Types   build-depends:       base >= 4.7 && < 5
− src/Network/EAP/Authentication.hs
@@ -1,157 +0,0 @@-{-# LANGUAGE NamedFieldPuns, RecordWildCards, OverloadedStrings, ScopedTypeVariables #-}-{-|-Module      : Network.EAP.Authentication-Description : Collection of EAP authentication methods and related utility functions-Copyright   : (c) Erick Gonzalez, 2017-License     : BSD3-Maintainer  : erick@codemonkeylabs.de-Stability   : experimental-Portability : POSIX--This module provides functions implements EAP authentication schemes. New authentication types-will be added as needed and contributions are very welcome.---}-module Network.EAP.Authentication (authenticateMSCHAPv2,-                                   generateAuthenticatorResponse,-                                   generateNTResponse,-                                   ntPasswordHash) where--import Prelude hiding (concatMap)-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 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-  passwordHash = do-  let peerChallenge = getMSCHAPv2ResponsePeerChallenge-      username      = getMSCHAPv2ResponseName-  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    -- ^ 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 passwordHash = do-    let challenge        = challengeHash peerChallenge authenticatorChallenge username-        zPasswordHash    = (toStrict passwordHash) `SB.append` SB.replicate 5 0 -- pad to 21 octets-        (pHash0, rest)   = SB.splitAt 7 zPasswordHash-        (pHash1, pHash2) = SB.splitAt 7 rest-    r0 <- encryptDES pHash0 challenge-    r1 <- encryptDES pHash1 challenge-    r2 <- encryptDES pHash2 challenge-    return $ r0 `SB.append` r1 `SB.append` r2---- | 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-encryptDES key msg = do-  (cipher :: DES) <- ExceptT . return $ initCipher-  ExceptT . return . Right $ ecbEncrypt cipher msg-    where initCipher = case cipherInit $ addParity key of-                         CryptoFailed e -> throwError e-                         CryptoPassed c -> Right c---- | Used internally to add the parity bits to a 56 bit (7 octet key) thus becoming an 8--- octet key-addParity :: SB.ByteString -> SB.ByteString-addParity = expand . SB.foldl f ((0, 0), SB.empty)-    where f ((i, carry), acc) word =-              let v      = carry  .|. (word `shiftR` i)-                  carry' = word `shiftL` (7 - i)-                  v'     = v .&. 0xfe-                  v''    = v' .|. (complement $ parity v') .&. 1-                  acc'   = acc `SB.snoc` v''-              in ((i+1, carry'), acc')-          expand ((_, carry), str) = str `SB.snoc` carry-          parity x0 = foldl (\x i -> x `xor` (x `shiftR` i)) x0 [1, 2, 4, 8, 16] .&. 1
+ src/Network/EAP/MSCHAPv2.hs view
@@ -0,0 +1,188 @@+{-# LANGUAGE NamedFieldPuns, RecordWildCards, OverloadedStrings, ScopedTypeVariables #-}+{-|+Module      : Network.EAP.MSCHAPv2+Description : MSCHAPv2 EAP authentication method and related utility functions+Copyright   : (c) Erick Gonzalez, 2017+License     : BSD3+Maintainer  : erick@codemonkeylabs.de+Stability   : experimental+Portability : POSIX++This module provides functions to implement the EAP MSCHAPv2 authentication scheme.++-}+module Network.EAP.MSCHAPv2 (authenticateMSCHAPv2,+                             generateAuthenticatorResponse,+                             generateNTResponse,+                             ntPasswordHash,+                             deriveMPPEKeys) where++import Prelude hiding (concatMap, replicate, take)+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,+                                   replicate,+                                   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 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+  passwordHash = do+  let peerChallenge = getMSCHAPv2ResponsePeerChallenge+      username      = getMSCHAPv2ResponseName+  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    -- ^ 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 passwordHash = do+    let challenge        = challengeHash peerChallenge authenticatorChallenge username+        zPasswordHash    = (toStrict passwordHash) `SB.append` SB.replicate 5 0 -- pad to 21 octets+        (pHash0, rest)   = SB.splitAt 7 zPasswordHash+        (pHash1, pHash2) = SB.splitAt 7 rest+    r0 <- encryptDES pHash0 challenge+    r1 <- encryptDES pHash1 challenge+    r2 <- encryptDES pHash2 challenge+    return $ r0 `SB.append` r1 `SB.append` r2++-- | 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"++-- | Derive Microsoft Point-to-Point Encryption (MPPE) keys see [RFC3079]+deriveMPPEKeys :: ByteString -- ^ Hash of the NT hash+                -> ByteString -- ^ NT Response+                -> Except CryptoError (SB.ByteString, SB.ByteString) -- ^ (MPPE send key,+                                                                    --    MPPE recv key)+deriveMPPEKeys ntHashHash ntResponse = do+  let masterKey = fromStrict . SB.take 16 . sha1Hash $ do+                    hash ntHashHash+                    hash ntResponse+                    hash magic1+      assymetricStartKey magic = SB.take 16 . sha1Hash $ do+                                   hash masterKey+                                   hash shsPad1+                                   hash magic+                                   hash shsPad2+      recvKey = assymetricStartKey magic2+      sendKey = assymetricStartKey magic3+  return (sendKey, recvKey)+      where magic1 = "This is the MPPE Master Key"+            magic2 = "On the client side, this is the send key; on the server side, it is the receive key."+            magic3 = "On the client side, this is the receive key; on the server side, it is the send key."+            shsPad1 = replicate 40 0+            shsPad2 = replicate 40 0xf2++-- | Used internally to derive 8 octet challenge hash+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+encryptDES key msg = do+  (cipher :: DES) <- ExceptT . return $ initCipher+  ExceptT . return . Right $ ecbEncrypt cipher msg+    where initCipher = case cipherInit $ addParity key of+                         CryptoFailed e -> throwError e+                         CryptoPassed c -> Right c++-- | Used internally to add the parity bits to a 56 bit (7 octet key) thus becoming an 8+-- octet key+addParity :: SB.ByteString -> SB.ByteString+addParity = expand . SB.foldl f ((0, 0), SB.empty)+    where f ((i, carry), acc) word =+              let v      = carry  .|. (word `shiftR` i)+                  carry' = word `shiftL` (7 - i)+                  v'     = v .&. 0xfe+                  v''    = v' .|. (complement $ parity v') .&. 1+                  acc'   = acc `SB.snoc` v''+              in ((i+1, carry'), acc')+          expand ((_, carry), str) = str `SB.snoc` carry+          parity x0 = foldl (\x i -> x `xor` (x `shiftR` i)) x0 [1, 2, 4, 8, 16] .&. 1