-------------------------------------------------------------------------------- -- Title: LWDigestAuthentication.lua -- Description: Like a square peg in a round hole -- Author: Raphaël Szwarc http://alt.textdrive.com/lua/ -- Creation Date: February 1, 2005 -- Legal: Copyright (C) 2005 Raphaël Szwarc -------------------------------------------------------------------------------- -- import dependencies local LUObject = require( "LUObject" ) local LUCrypto = require( "LUCrypto" ) local LUString = require( "LUString" ) local MIMEBase64 = require( "MIMEBase64" ) local os = require( "os" ) require( "md5" ) -- define the class local super = LUObject local self = super() -- constants local Key = LUCrypto:key() -- method to create a nonce function self:nonce( aContext ) local aTime = os.time() local anAddress = aContext:request():reader():socket():getpeername() anAddress = MIMEBase64:encode( anAddress ) local aKey = LUCrypto:sign( Key, aTime .. anAddress ) aKey = aKey:sub( 1, 32 ) aKey = MIMEBase64:encode( LUString:decode( aKey ) ) local aNonce = aTime .. ":" .. anAddress .. ":" .. aKey return aNonce end -- method to validate a given nonce function self:validateNonce( aContext, aNonce ) if aNonce ~= nil then local aNonceTime, aNonceAddress, aNonceKey = aNonce:match( "(%d+):(.+):(.+)" ) if aNonceTime ~= nil and aNonceAddress ~= nil and aNonceKey ~= nil then local anInterval = os.time() - tonumber( aNonceTime ) if anInterval >= 0 then local anAddress = aContext:request():reader():socket():getpeername() if anAddress == MIMEBase64:decode( aNonceAddress ) == true then if MIMEBase64:decode( aNonceKey ):len() == 16 then return true end end end end end return false end -- method to access this authentication password for a given user name and realm function self:password( aContext, anUserName, aRealm ) return anUserName end -- method to access this authentication realm function self:realm( aContext ) return aContext:request():headers():get( "host" ) or "" end -- method to check if the given name requires authentication function self:requiresAuthentication( aContext, anUserName ) return true end -- method to validate the given authentication function self:validateAuthentication( aContext, anUserName, aRealm, aPassword ) if anUserName ~= nil then return true end return false end -- method to authenticate a given context function self:authenticate( aContext ) local someAttributes = aContext:request():headerAttributes( "authorization" ) local anUserName = someAttributes:get( "username" ) if self:requiresAuthentication( aContext, anUserName ) == true then local aRealm = someAttributes:get( "realm" ) if self:validateAuthentication( aContext, anUserName, aRealm ) == true then local aNonce = someAttributes:get( "nonce" ) if self:validateNonce( aContext, aNonce ) == true then local aResponse = someAttributes:get( "response" ) local aNonceCount = someAttributes:get( "nc" ) local aClientNonce = someAttributes:get( "cnonce" ) local aProtection = someAttributes:get( "qop" ) local anURI = someAttributes:get( "uri" ) if aResponse ~= nil and aNonceCount ~= nil and aClientNonce ~= nil and aProtection == "auth" and anURI == aContext:request():rawURI() then local aPassword = self:password( aContext, anUserName, aRealm ) local anUserDigest = md5.digest( anUserName .. ":" .. aRealm .. ":" .. aPassword ) local aMethod = aContext:request():method() local aRequestDigest = md5.digest( aMethod .. ":" .. anURI ) local aDigest = md5.digest( anUserDigest .. ":" .. aNonce .. ":" .. aNonceCount .. ":" .. aClientNonce .. ":" .. aProtection .. ":" .. aRequestDigest ) if aDigest:lower() == aResponse:lower() then return true else self:challenge( aContext ) return false end else self:challenge( aContext ) return false end else self:challenge( aContext ) return false end else self:challenge( aContext ) return false end else return true end end -- method to challenge a given context function self:challenge( aContext ) local aChallenge = "Digest " aChallenge = aChallenge .. "realm=\"" .. self:realm( aContext ) .. "\", " aChallenge = aChallenge .. "domain=\"" .. aContext:request():rawURI() .. "\", " aChallenge = aChallenge .. "nonce=\"" .. self:nonce( aContext ) .. "\", " aChallenge = aChallenge .. "algorithm=\"MD5\", " aChallenge = aChallenge .. "qop=\"auth\"" aContext:response():headers():put( "www-authenticate", aChallenge ) aContext:response():writeStatus( 401 ) end return self