packages feed

hdigest (empty) → 1.0

raw patch · 4 files changed

+232/−0 lines, 4 filesdep +Cryptodep +basedep +cgisetup-changed

Dependencies added: Crypto, base, cgi, network, parsec, random, time

Files

+ COPYING view
@@ -0,0 +1,30 @@+Copyright © 2009 Jamey Sharp+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are+met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above copyright+      notice, this list of conditions and the following disclaimer in+      the documentation and/or other materials provided with the+      distribution.++    * Neither the name of Jamey Sharp nor the names of its+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED+TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Network/HTTP/Digest.hs view
@@ -0,0 +1,162 @@+-- Copyright © 2009 Jamey Sharp and Josh Triplett+-- See the file COPYING for license details.+--+-- HTTP Digest implementation based on RFC2617, with modifications to handle+-- the ways that various browsers violate that specification.+module Network.HTTP.Digest (+    authenticateDigest, DigestResult(..), makeHA1, setDigestRequired+) where++import Control.Monad+import Data.Bits+import Data.Char+import qualified Data.Digest.MD5 as MD5+import Data.Maybe+import Data.Time.Clock.POSIX+import Network.CGI+import Network.URI+import System.IO.Unsafe+import System.Random+import Text.ParserCombinators.Parsec hiding (token)++makeHA1 :: String -> String -> String -> String+makeHA1 username realm password = md5Hash $ username ++ ':' : realm ++ ':' : password++digestSecret :: String+digestSecret = show (unsafePerformIO randomIO :: Int)++hashTime :: Integer -> String+hashTime time = md5Hash (show time ++ ':' : digestSecret)++getNonceFor :: POSIXTime -> String+getNonceFor time = let secs = truncate time in show secs ++ ':' : hashTime secs++getTimeOf :: String -> Maybe POSIXTime+getTimeOf nonce = case reads nonce of+    [(time, ':' : hash)] | hashTime time == hash -> Just (fromInteger time)+    _ -> Nothing++makeNonce :: IO String+makeNonce = liftM getNonceFor getPOSIXTime++requiredAttribute :: Monad m => String -> [(String, String)] -> (String -> Bool) -> m String+requiredAttribute name attrs check = case lookup name attrs of+    Nothing -> fail $ "Digest authorization missing \"" ++ name ++ "\" attribute"+    Just value -> if check value then return value else+        fail $ "Digest authorization has invalid \"" ++ name ++ "\" attribute, \"" ++ value ++ "\""++checkAttributes :: String -> URI -> CharParser st ([(String, String)], String, String)+checkAttributes realm uri = do+    attrs <- parseDigestAuthorization+    requiredAttribute "realm" attrs (== realm)+    requiredAttribute "uri" attrs (urimatch . parseRelativeReference)+    username <- requiredAttribute "username" attrs (const True)+    nonce <- requiredAttribute "nonce" attrs (const True)+    return (attrs, username, nonce)+    where+    dom u = (uriScheme u, uriAuthority u)+    urimatch Nothing = False+    urimatch (Just authuri) = null (uriFragment authuri)+        && uriPath uri == uriPath authuri+        && (dom authuri == ("", Nothing) || dom uri == dom authuri)+        && (uriQuery uri == uriQuery authuri || null (uriQuery authuri)) -- work around IE bug++data DigestResult u = DigestSuccess u | DigestBadRequest String | DigestStale | DigestIncorrect | DigestMissing++-- Note that this implementation does not check for reuse of nonce-count+-- values, as that requires persistent state.+authenticateDigest :: (MonadCGI m, MonadIO m) => String -> (String -> m (Maybe (u, String))) -> m (DigestResult u)+authenticateDigest realm userfunc = do+    uri <- requestURI+    method <- requestMethod+    let header = "Authorization"+    maybeAuth <- requestHeader header+    case maybeAuth of+        Just auth -> case parse (checkAttributes realm uri) header auth of+            Left msg -> return (DigestBadRequest $ show msg)+            Right (attrs, username, nonce) -> do+                maybeUser <- userfunc username+                case maybeUser of+                    Just (user, ha1) | checkDigestAuthorization method attrs ha1 -> do+                        now <- liftIO getPOSIXTime+                        case getTimeOf nonce of+                            Just time | time >= now - 300 ->+                                setAuthenticationInfo >> return (DigestSuccess user)+                            _ -> return DigestStale+                    _ -> return DigestIncorrect+        _ -> return DigestMissing++-- XXX: setDigestRequired does not set the domain parameter, which according+-- to RFC2617 means "the protection space consists of all URIs on the+-- responding server."+setDigestRequired :: (MonadCGI m, MonadIO m) => String -> Bool -> m ()+setDigestRequired realm stale = do+    setStatus 401 "Unauthorized"+    nonce <- liftIO makeNonce+    setHeader "WWW-Authenticate" $+        "Digest realm=\"" ++ realm ++ "\", nonce=\"" ++ nonce ++ "\", qop=\"auth\"" +++        if stale then ", stale=TRUE" else ""++setAuthenticationInfo :: (MonadCGI m, MonadIO m) => m ()+setAuthenticationInfo = do+    nextnonce <- liftIO makeNonce+    setHeader "Authentication-Info" $ "nextnonce=\"" ++ nextnonce ++ "\""++parseDigestAuthorization :: CharParser st [(String, String)]+parseDigestAuthorization = do+    let stringLower s = (mapM_ (\x-> satisfy (\y-> x == toLower y)) s >> return s) <?> show s+    let lws = (optional (string "\r\n") >> skipMany1 (satisfy (\x-> x == ' ' || x == '\t')) >> return ' ') <?> "LWS"+    let isHTTPTokenChar c = c > ' ' && c < '\DEL' && c `notElem` "()<>@,;:\\\"/[]?={}"+    let token = many1 (satisfy isHTTPTokenChar) <?> "token"+    let tokenLower = fmap (map toLower) token+    let quoted = between (char '"') (char '"')+    let quotedPair = (char '\\' >> satisfy (<= '\DEL')) <?> "quoted-pair"+    let qdtext = (lws <|> satisfy (\x-> x > ' ' && x <= '\DEL' && x /= '"')) <?> "qdtext"+    let quotedString = (quoted $ many $ quotedPair <|> qdtext) <?> "quoted-string"+    let lhex = satisfy (`elem` "0123456789abcdef") <?> "LHEX"+    -- RFC2617 requires exactly 8 characters for nc, but some browsers don't+    -- provide the leading 0s.+    let ncValue = many1 lhex <?> "nc-value"+    let list e = fmap msum $ (skipMany lws >> option mzero (fmap return e)) `sepBy` (skipMany lws >> char ',')+    let nameValue kinds = do+            name <- tokenLower+            skipMany lws >> char '=' >> skipMany lws+            value <- fromMaybe (quotedString <|> token) $ lookup name kinds+            return (name, value)+    -- RFC2617 does not allow quoting algorithm, qop, or nc; allow it anyway+    -- because some clients quote everything.+    between (stringLower "digest" >> lws) eof $ list $ nameValue [+            ("username", quotedString),+            ("realm", quotedString),+            ("nonce", quotedString),+            ("uri", quotedString <|> string "*"),+            ("response", quoted (count 32 lhex) <?> "request-digest"),+            -- ("algorithm", token),+            ("cnonce", quotedString),+            ("opaque", quotedString),+            -- ("qop", token),+            ("nc", ncValue <|> quoted ncValue)+        ]++md5Hash :: String -> String+md5Hash = foldr octetToHex "" . MD5.hash . map (toEnum . fromEnum) where+    octetToHex n rest = wordToDigit (n `shiftR` 4) : wordToDigit (n .&. 0xF) : rest+    wordToDigit = intToDigit . fromEnum++checkDigestAuthorization :: String -> [(String, String)] -> String -> Bool+checkDigestAuthorization method attrs ha1 = isJust $ do+    nonce <- lookup "nonce" attrs+    digestURI <- lookup "uri" attrs+    response <- lookup "response" attrs+    let algorithm = lookup "algorithm" attrs+    guard $ algorithm == Nothing || algorithm == Just "MD5" -- "MD5-sess" not implemented yet+    let kd secret contents = md5Hash (secret ++ ':' : contents)+    let ha2 = md5Hash (method ++ ':' : digestURI)+    expected <- case lookup "qop" attrs of+        Just qop -> do+            guard $ qop == "auth" -- "auth-int" not implemented yet+            cnonce <- lookup "cnonce" attrs+            nc <- lookup "nc" attrs+            return $ kd ha1 $ nonce ++ ':' : nc ++ ':' : cnonce ++ ':' : qop ++ ':' : ha2+        Nothing -> return $ kd ha1 $ nonce ++ ':' : ha2+    guard $ response == expected
+ Setup.hs view
@@ -0,0 +1,3 @@+#!/usr/bin/env runhaskell+import Distribution.Simple+main = defaultMain
+ hdigest.cabal view
@@ -0,0 +1,37 @@+Name:                hdigest+Version:             1.0+Synopsis:            Server-side HTTP Digest (RFC2617) in the CGI monad+Description:+  For web applications running in a CGI monad container, this module+  provides a simple interface to generate HTTP Digest authentication+  challenges and to process the responses from clients. It implements+  RFC2617, but accepts the quirky responses generated by some+  non-compliant browsers.+  .+  This module was originally implemented for Serialist+  (<http://serialist.net>) and is deployed there in a FastCGI+  environment using the fastcgi package. It has been tested against a+  variety of browsers, including Firefox, Safari, IE, Opera, and others.+Category:            Web+Cabal-Version:       >= 1.6+Build-Type:          Simple+License:             BSD3+License-file:        COPYING+Author:              Jamey Sharp+Copyright:           © 2009 Jamey Sharp+Maintainer:          Jamey Sharp <jamey@minilop.net>++Library+  Exposed-Modules:   Network.HTTP.Digest+  GHC-Options:       -Wall+  Build-Depends:     base >= 3, base < 5,+                     cgi,+                     Crypto,+                     network,+                     parsec,+                     random,+                     time++Source-Repository head+  Type:              git+  Location:          git://minilop.net/git/hdigest