Created
April 10, 2010 16:26
-
-
Save bokner/362131 to your computer and use it in GitHub Desktop.
Erlang module implementing HTTP digest auth on client side
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
%% Author: bokner | |
%% Created: Apr 10, 2010 | |
%% Description: HTTP digest authentication | |
%% Note: the code follows the informal explanation given on Wikipedia. | |
%% Note: the test/0 function doesn't intend to send a proper data to webdav service, | |
%% it just shows how to pass the authorization. | |
%% Disclaimer: Use on your own risk. The author disclaims any liability | |
%% with regard to using this code. | |
-module(digest_auth). | |
%% | |
%% Include files | |
%% | |
%% | |
%% Exported Functions | |
%% | |
-export([auth/5]). | |
-export([test/0]). | |
%% | |
%% API Functions | |
%% | |
%% Does digest authentication. | |
%% Callback function takes an authorization header and a URL | |
%% For example, | |
%% AuthCallback = fun(AuthHeader, URL) -> | |
%% http:request(post, {URL, [{"Authorization", AuthHeader}], "application/x-www-form-urlencoded", | |
%% Body)}, [], []) | |
%% end. | |
auth(URL, Method, User, Password, AuthCallback) -> | |
{http, _, _, _, DigestURI, _} = http_uri:parse(URL), | |
{ok, {{_, ResponseCode, _}, | |
Fields, | |
_Response}} = http:request(get, {URL, []}, [], []), | |
case ResponseCode of | |
401 -> | |
AuthorizationHeader = buildAuthHeader(DigestURI, Method, User, Password, Fields), | |
AuthCallback(AuthorizationHeader, URL); | |
_ -> | |
{error, noAuthentication} | |
end. | |
buildAuthHeader(URI, Method, User, Password, Fields) -> | |
{Realm, Nonce, Nc, CNonce, Response, Opaque} = calcResponse(Fields, User, Password, URI, Method, "0000000000000000"), | |
lists:flatten(io_lib:format( | |
"Digest username=\"~s\",realm=\"~s\",nonce=\"~s\",uri=\"~s\",qop=auth,nc=~s,cnonce=\"~s\",response=\"~s\",opaque=\"~s\"", | |
[User, Realm, Nonce, URI, Nc, CNonce, Response, Opaque])). | |
calcResponse(Fields, User, Password, URI, Method, Nc) -> | |
io:format("Calc:~p~n~p~n~p~n~p~n~p~n~p~n", [Fields, User, Password, URI, Method, Nc]), | |
random:seed(now()), | |
DigestLine = proplists:get_value("www-authenticate", Fields), | |
[$D, $i, $g, $e, $s, $t, $ | DigestParamsStr] = DigestLine, | |
DigestParams = | |
lists:map(fun(T) -> [Name, Value] = re:split(T, "=", [{return, list}, {parts, 2}]), | |
{string:strip(Name, both, $ ), string:strip(Value, both, $")} end, | |
string:tokens(DigestParamsStr, ",")), | |
%% Calculate digest | |
io:format("Digest params:~p~n", [DigestParams]), | |
Realm = proplists:get_value("realm", DigestParams), | |
Opaque = proplists:get_value("opaque", DigestParams), | |
Nonce = proplists:get_value("nonce", DigestParams), | |
CNonce = hex(integer_to_list(erlang:trunc(random:uniform()*10000000000000000))), | |
Qop = proplists:get_value("qop", DigestParams), | |
Response = calc_response(Method, User, Password, URI, Realm, Opaque, Nonce, Nc, CNonce, Qop), | |
{Realm, Nonce, Nc, CNonce, Response, Opaque}. | |
calc_response(Method, User, Password, URI, Realm, Opaque, Nonce, Nc, CNonce, Qop) -> | |
HA1 = hex(binary_to_list(crypto:md5( string:join( | |
[User, Realm, Password], ":")))), | |
HA2 = hex(binary_to_list(crypto:md5( string:join([Method, URI], ":")))), | |
io:format("HA1:~p~n", [HA1]), | |
io:format("HA2:~p~n", [HA2]), | |
%HA1 result, server nonce (nonce), request counter (nc), client nonce (cnonce), quality of protection code (qop) and HA2 result is calculated. | |
Step3Arg = HA1 ++ ":" ++ | |
Nonce ++ ":" ++ | |
Nc ++ ":" ++ | |
CNonce ++ ":" ++ | |
Qop ++ ":" ++ | |
HA2, | |
io:format("3rd step:~p~n", [Step3Arg]), | |
hex(binary_to_list( | |
crypto:md5( Step3Arg | |
) | |
)). | |
%% Implements example of digest response calculation from Wikipedia | |
%% (http://en.wikipedia.org/wiki/Digest_access_authentication) | |
%% | |
test_calc_response() -> | |
crypto:start(), | |
io:format("Proper response is: 6629fae49393a05397450978507c4ef1~n"), | |
calc_response("GET", "Mufasa", "Circle Of Life", "/dir/index.html", "[email protected]", "5ccc069c403ebaf9f0171e9517f40e41", "dcd98b7102dd2f0e8b11d0f600bfb0c093", "00000001","0a4f113b", "auth"). | |
%% | |
%% Local Functions | |
%% | |
%% @hidden | |
digit_to_xchar(D) when (D >= 0) and (D < 10) -> | |
D + 48; | |
digit_to_xchar(D) -> | |
D + 87. | |
hex(S) -> | |
hex(S, []). | |
hex([], Res) -> | |
lists:reverse(Res); | |
hex([N | Ns], Res) -> | |
hex(Ns, [digit_to_xchar(N rem 16), | |
digit_to_xchar(N div 16) | Res]). | |
test() -> | |
Data = "test", | |
AuthCallback = fun(AuthHeader, URL) -> | |
http:request(post, | |
{URL ++ "/", [{"Authorization", AuthHeader}], "text/xml", | |
Data}, [], []) | |
end, | |
digest_auth:auth("http://test.webdav.org/auth-digest", "POST", "user1", "user1", AuthCallback). | |
There were few bugs in the code, try it now. test/0 uses WebDav site, and it passes authorization. I didn't try to submit a proper webdav request though, so it gets 400 error. Hope this helps.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Have you tried it on 'http://test.webdav.org'?