-
Notifications
You must be signed in to change notification settings - Fork 474
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Session module for managing session cookies #94
Changes from 7 commits
1be66c0
92464d0
1f1867b
7aa6d1e
d21e5b2
ba86dff
ae7aa5d
df5a881
68c48b1
1f353dd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
%% @author Asier Azkuenaga Batiz <asier@zebixe.com> | ||
|
||
|
||
%% @doc HTTP Cookie session. Note that the user name and expiration time travel unencrypted as far as this module concerns. | ||
%% In order to achieve more security, it is adviced to use https | ||
|
||
-module(mochiweb_session). | ||
-export([generate_session_data/4,generate_session_cookie/4,check_session_cookie/4]). | ||
-export([cookie_encode/1,cookie_decode/1,timestamp_sec/1]).%Useful fuctions for more specific purposes | ||
|
||
%% @spec generate_session_data(ExpirationTime,Data :: string(),FSessionKey : function(A),ServerKey) -> string() | ||
%% @doc generates a secure encrypted string convining all the parameters. | ||
%% The expiration time is considered in seconds | ||
generate_session_data(ExpirationTime,Data,FSessionKey,ServerKey) when is_integer(ExpirationTime),is_list(Data), is_function(FSessionKey)-> | ||
BData=list_to_binary(Data), | ||
ExpTime=integer_to_list(ExpirationTime), | ||
BExpTime=list_to_binary(ExpTime), | ||
Key=gen_key(ExpTime,ServerKey), | ||
Hmac=gen_hmac(ExpTime,BData,FSessionKey(integer_to_list(ExpirationTime)),Key), | ||
EData=encrypt_data(BData,Key), | ||
bin_to_hexstr(<< BExpTime/binary,",", EData/binary,Hmac/binary>>). | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would either separate all the parameters with a comma or remove comma at all (since expiration time length is a known thing). If you choose the latter, swap Hmac and EData, you'll be able to do simple pattern-matching instead of binary module splits. |
||
|
||
%% @spec generate_session_data(UserName,ExpirationTime,SessionExtraData,FSessionKey : function(A),ServerKey) -> mochiweb_cookie() | ||
%% @doc generates a secure encrypted cookie using the generate_session_data function. | ||
generate_session_cookie(ExpirationTime,Data,FSessionKey,ServerKey) when is_integer(ExpirationTime)-> | ||
CookieData=generate_session_data(ExpirationTime,Data,FSessionKey,ServerKey), | ||
mochiweb_cookies:cookie("id",CookieData,[{max_age,20000},{local_time,calendar:universal_time_to_local_time(calendar:universal_time())}]). | ||
|
||
%% @spec cookie_check_session(RawData,ExpirationTime,FSessionKey : function(A), ServerKey)->{false,[UserName,Expiration,Data]} | | ||
%% {false,[]} | | ||
%% {true,[UserName,Expiration,Data]} | ||
check_session_cookie(undefined,_,_,_) -> | ||
{false,[]}; | ||
check_session_cookie([],_,_,_) -> | ||
{false,[]}; | ||
check_session_cookie(ECookie,ExpirationTime,FSessionKey,ServerKey) -> | ||
Cookie=hexstr_to_bin(ECookie), | ||
{P1,_}=binary:match(Cookie,<<",">>), | ||
ExpirationTimeCookie=binary:part(Cookie,0,P1), | ||
Data=binary:part(Cookie,P1+1,byte_size(Cookie)-(20+P1+1)), | ||
Hmac=binary:part(Cookie,byte_size(Cookie)-20,20), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See the comment from above. This may be rewritten to either <<ExpTime:10/binary,Hmac:16/binary,Data/binary>> = Cookie. IMO, the latter is a better way, since it operates with binaries and doesn't involve commas overhead. |
||
check_session_cookie(binary_to_list(ExpirationTimeCookie),Data,Hmac,ExpirationTime,FSessionKey,ServerKey); | ||
check_session_cookie(_,_,_,_) -> | ||
{false,[]}. | ||
check_session_cookie(ExpirationTime1, EData, BHmac,ExpirationTime,FSessionKey,ServerKey) | ||
when is_integer(ExpirationTime) , is_list(ServerKey), is_list(ExpirationTime1)-> | ||
ExpTime=list_to_integer(ExpirationTime1), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can save up 6 bytes here using |
||
Key=gen_key(ExpirationTime1,ServerKey), | ||
Data=decrypt_data(EData,Key), | ||
Hmac2=gen_hmac(ExpirationTime1,Data,FSessionKey(ExpirationTime1),Key), | ||
if ExpTime<ExpirationTime -> {false,[ExpirationTime1,binary_to_list(Data)]}; | ||
true -> | ||
if Hmac2==BHmac -> {true,[ExpirationTime1,binary_to_list(Data)]}; | ||
true -> {false,[ExpirationTime1,binary_to_list(Data)]} | ||
end | ||
end; | ||
check_session_cookie(_,_,_,_,_,_) -> | ||
{false,[]}. | ||
|
||
|
||
encrypt_data(Data,Key) -> | ||
IV = crypto:rand_bytes(16), | ||
Crypt=crypto:aes_cfb_128_encrypt(Key, IV, Data), | ||
<<IV/binary,Crypt/binary>>. | ||
decrypt_data(<<IV:16/binary,Crypt/binary>>,Key) -> | ||
crypto:aes_cfb_128_decrypt(Key, IV,Crypt). | ||
|
||
gen_key(ExpirationTime,ServerKey)-> | ||
crypto:md5_mac(ServerKey, [ExpirationTime]). | ||
gen_hmac(ExpirationTime,Data,SessionKey,Key)-> | ||
crypto:sha_mac(Key,[ExpirationTime,Data,SessionKey]). | ||
|
||
%% @doc Using | ||
cookie_decode (Encoded) -> | ||
hexstr_to_bin(Encoded). | ||
cookie_encode (Term) -> | ||
bin_to_hexstr(Term). | ||
|
||
bin_to_hexstr(Bin) -> | ||
lists:flatten([io_lib:format("~2.16.0B", [X]) || | ||
X <- binary_to_list(Bin)]). | ||
|
||
hexstr_to_bin(S) -> | ||
hexstr_to_bin(S, []). | ||
hexstr_to_bin([], Acc) -> | ||
list_to_binary(lists:reverse(Acc)); | ||
hexstr_to_bin([X,Y|T], Acc) -> | ||
{ok, [V], []} = io_lib:fread("~16u", [X,Y]), | ||
hexstr_to_bin(T, [V | Acc]). | ||
|
||
|
||
timestamp_sec({MGS,S,_})-> | ||
MGS*1000000+S. | ||
|
||
|
||
-ifdef(TEST). | ||
-include_lib("eunit/include/eunit.hrl"). | ||
|
||
generate_check_session_cookie_test_()-> | ||
{setup, | ||
fun server_key/0, %setup function | ||
fun generate_check_session_cookie/1}. | ||
|
||
server_key()->%setup function | ||
["adfasdfasfs",timestamp_sec(now())]. | ||
|
||
generate_check_session_cookie([ServerKey,TimeStamp]) -> | ||
[?_assertEqual({true,[integer_to_list(TimeStamp+1000),"alice"]}, | ||
check_session_cookie(generate_session_data(TimeStamp+1000,"alice",fun(A)-> A end,ServerKey) | ||
, | ||
TimeStamp,fun(A)-> A end,ServerKey)), | ||
?_assertEqual({true,[integer_to_list(TimeStamp+1000),"alice and"]}, | ||
check_session_cookie(generate_session_data(TimeStamp+1000,"alice and",fun(A)-> A end,ServerKey), | ||
TimeStamp,fun(A)-> A end,ServerKey)), | ||
?_assertEqual({true,[integer_to_list(TimeStamp+1000),"alice and bob"]}, | ||
check_session_cookie(generate_session_data(TimeStamp+1000,"alice and bob",fun(A)-> A end,ServerKey), | ||
TimeStamp,fun(A)-> A end,ServerKey)), | ||
?_assertEqual({true,[integer_to_list(TimeStamp+1000),"alice jlkjfkjsdfg sdkfjgldsjgl"]}, | ||
check_session_cookie(generate_session_data(TimeStamp+1000,"alice jlkjfkjsdfg sdkfjgldsjgl",fun(A)-> A end,ServerKey), | ||
TimeStamp,fun(A)-> A end,ServerKey)), | ||
?_assertEqual({true,[integer_to_list(TimeStamp+1000),"alice .'¡'ç+-$%/(&\""]}, | ||
check_session_cookie(generate_session_data(TimeStamp+1000,"alice .'¡'ç+-$%/(&\"",fun(A)-> A end,ServerKey), | ||
TimeStamp,fun(A)-> A end,ServerKey)), | ||
?_assertEqual({true,[integer_to_list(TimeStamp+1000),"alice456689875"]}, | ||
check_session_cookie(generate_session_data(TimeStamp+1000,["alice","456689875"],fun(A)-> A end,ServerKey), | ||
TimeStamp,fun(A)-> A end,ServerKey)), | ||
?_assertError(function_clause, | ||
check_session_cookie(generate_session_data(TimeStamp+1000,{tuple,one},fun(A)-> A end,ServerKey), | ||
TimeStamp,fun(A)-> A end,ServerKey)), | ||
?_assertEqual({false,[integer_to_list(TimeStamp-1),"bob"]}, | ||
check_session_cookie(generate_session_data(TimeStamp-1,"bob",fun(A)-> A end,ServerKey), | ||
TimeStamp,fun(A)-> A end,ServerKey))%current timestamp newer than cookie, it's expired | ||
]. | ||
|
||
-endif. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, Data can be a binary here too.