Saturday, October 18, 2008

Secure Cookies for your web application...

Now that new erlang web framework are here, I think that sessions are still today a weakness.

Session and Cookies must be secure, there's no single day without some new vulnerability about session hijacking.

That's why very clever people design the secure cookie protocol [PDF].

Here's the Cookie value:
user name|expiration time|(data)k|HMAC( user name|expiration time|data|session key, k)

where
k=HMAC(user name|expiration time, sk)

and where sk is a secret key

Now you can verify the cookie using theses techniques:
1. Compare the cookie’s expiration time and the server’s current
time. If the cookie has expired, then return FALSE.
2. Compute the encryption key as follows:
k=HMAC(user name|expiration time, sk)
3. Decrypt the encrypted data using k.
4. Compute HMAC(user name|expiration time|data|session key, k),
and compare it with the keyed-hash message authentication code
of the cookie. If they match, then return TRUE;
otherwise return FALSE.
TRUE

Here's the erlang module
-module(scookies).
-export([start/0, gen_auth/1, gen_build/2, gen_check/2, read/1, check/4, test/0]).
-export([message/1]).

start() ->
application:start(crypto).

gen_build(ServerKey, IVec) ->
fun(Username, D, SessionKey) ->
Expiration = integer_to_list(1212559656),
Key = crypto:md5_mac( [Username, Expiration], ServerKey), %16bytes
Data = crypto:aes_cbc_128_encrypt(Key, IVec, D),
Hmac = crypto:sha_mac([Username, Expiration, Data, SessionKey], Key),
io:format("Build: ~p ~p ~p ~p ~p~n",[Username, Expiration, Data, SessionKey, Key]),
iolist_to_binary([ Username, $,, Expiration, $,, Data, $,, Hmac ])
end.

read(Cookie) ->
{A, B, C} = Cookie,
{A, B, C}.


gen_check(ServerKey, IVec) ->
fun(Cookie, SessionKey) ->
[ Username, Expiration, Crypted, Hmac ] = string:tokens(binary_to_list(Cookie), ","),
Key = crypto:md5_mac([ Username, Expiration ], ServerKey),
Data = crypto:aes_cbc_128_decrypt(Key, IVec, Crypted),
MAC = crypto:sha_mac([ Username, Expiration, Crypted, SessionKey], Key),
io:format("Check: ~p ~p ~p ~p ~p~n",[Username, Expiration, Data, SessionKey, Key]),
<<Len:16,Message:Len/binary,_/binary>> = Data,
io:format("Decrypted: ~p '~s'~n'~p'~n'~p'~n", [Len, Message, MAC, list_to_binary(Hmac)]),
[ Username, Expiration, {Len, Message}, MAC, list_to_binary(Hmac)]
end.

% Returns the build fun and check fun
% This is a helper fun to let you build in q simple way bot the build fun and
% the decode fun...
gen_auth(ServerKey) ->
IVec = <<"3985928509201031">>, %16bytes Must be Random
[ gen_build(ServerKey, IVec), gen_check(ServerKey, IVec) ].


check(Cookie, ServerKey, InitVec, SessionKey) ->
{Username, ExpirationTime, Crypted, CookieMAC} = read(Cookie),
case check_time(ExpirationTime) of % see later check_time...
ok ->
Key = crypto:sha_mac([ Username, ExpirationTime ], ServerKey),
Data = crypto:aes_cbc_128_decrypt(Key, InitVec, Crypted),
MAC = crypto:sha_mac([ Username, ExpirationTime, Data, SessionKey], Key),
compare(MAC, CookieMAC, Data);

_E ->
{error, _E}
end.

compare(_A, _A, Data) ->
{ok, Data};
compare(_A, _B, _Data) ->
{error, nomatch}.

check_time(1212559656) -> % It's up to you to set it
true;
check_time(_) ->
false.

message(Text) ->
Len = size(Text),
Pad = 64 - Len - 2,
<<Len:16,Text/binary, 0:Pad/unit:8>>.

test() ->
ServerKey = <<"serverkey">>,
SessionKey = <<"3ID409a0sd09">>,
[ Enc, Dec ] = gen_auth(ServerKey),
CCookie = Enc("rolphin", message(<<"stream/128693">>), SessionKey),
DCookie = Dec(CCookie, SessionKey),
io:format("C: ~s~n", [ CCookie ]),
display(DCookie).

display([Username, Expiration, {Len, Message}, _Mac, _Mac]) ->
io:format("Message ok: ~s (~s) ~p: '~s'~n", [Username, Expiration, Len, Message]);
display([Username, Expiration, {Len, Message}, _Mac, _OtherMac]) ->
io:format("Invalid Mac ! ~s (~s) ~p: '~s'~n", [Username, Expiration, Len, Message]).


4 comments:

offby1 said...

What's the last line of the "message" function s'posed to be? <> doesn't look like well-formed Erlang to either me nor my erl :-|

Grey Force said...

Hi there, I did write something like that in the past :
http://www.cestari.info/2008/4/10/cookiestore-on-yaws

Antoine said...

Thx @offby1 ! I've fixed the html...
blogger isn't good at posting code...

@grey force thanks for the link !

Unknown said...

Hello,

Thank you for that scookies module ; it helps.
I'm using it on one of my project ; I had to modify it a little because "," would sometime end up in the crypted data and cause an error.

In my version, Data and Hmac are not sepparated by a ",". And I rely on the hmac length of 20 bytes to get Crypted and HMac back in the check function.

Sticky