Skip to content

Instantly share code, notes, and snippets.

@bokner
Created April 10, 2010 16:26
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save bokner/362131 to your computer and use it in GitHub Desktop.
Save bokner/362131 to your computer and use it in GitHub Desktop.
Erlang module implementing HTTP digest auth on client side
%% 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", "testrealm@host.com", "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).
@superbobry
Copy link

Have you tried it on 'http://test.webdav.org'?

@bokner
Copy link
Author

bokner commented Feb 24, 2012

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