25
1
mirror of https://github.com/processone/ejabberd.git synced 2024-11-22 16:20:52 +01:00

Preliminary patch for SASL SCRAM-SHA-1 (thanks to Stephen Röttger)(EJAB-1196)

This commit is contained in:
Badlop 2011-08-16 00:25:03 +02:00
parent 573a84b2ec
commit e80b92b481
16 changed files with 673 additions and 14 deletions

View File

@ -34,7 +34,7 @@
server_start/3,
server_step/2]).
-record(sasl_mechanism, {mechanism, module, require_plain_password}).
-record(sasl_mechanism, {mechanism, module, password_type}).
-record(sasl_state, {service, myname, realm,
get_password, check_password, check_password_digest,
mech_mod, mech_state}).
@ -52,14 +52,15 @@ start() ->
{keypos, #sasl_mechanism.mechanism}]),
cyrsasl_plain:start([]),
cyrsasl_digest:start([]),
cyrsasl_scram:start([]),
cyrsasl_anonymous:start([]),
ok.
register_mechanism(Mechanism, Module, RequirePlainPassword) ->
register_mechanism(Mechanism, Module, PasswordType) ->
ets:insert(sasl_mechanism,
#sasl_mechanism{mechanism = Mechanism,
module = Module,
require_plain_password = RequirePlainPassword}).
password_type = PasswordType}).
%%% TODO: use callbacks
%%-include("ejabberd.hrl").
@ -97,16 +98,16 @@ check_credentials(_State, Props) ->
end.
listmech(Host) ->
RequirePlainPassword = ejabberd_auth:plain_password_required(Host),
Mechs = ets:select(sasl_mechanism,
[{#sasl_mechanism{mechanism = '$1',
require_plain_password = '$2',
password_type = '$2',
_ = '_'},
if
RequirePlainPassword ->
[{'==', '$2', false}];
true ->
case ejabberd_auth:storage_type(Host) of
external ->
[{'==', '$2', plain}];
scram ->
[{'/=', '$2', digest}];
_Else ->
[]
end,
['$1']}]),
@ -152,6 +153,13 @@ server_step(State, ClientIn) ->
{error, Error} ->
{error, Error}
end;
{ok, Props, ServerOut} ->
case check_credentials(State, Props) of
ok ->
{ok, Props, ServerOut};
{error, Error} ->
{error, Error}
end;
{continue, ServerOut, NewMechState} ->
{continue, ServerOut,
State#sasl_state{mech_state = NewMechState}};

View File

@ -34,7 +34,7 @@
-record(state, {server}).
start(_Opts) ->
cyrsasl:register_mechanism("ANONYMOUS", ?MODULE, false),
cyrsasl:register_mechanism("ANONYMOUS", ?MODULE, plain),
ok.
stop() ->

View File

@ -40,7 +40,7 @@
host}).
start(_Opts) ->
cyrsasl:register_mechanism("DIGEST-MD5", ?MODULE, true).
cyrsasl:register_mechanism("DIGEST-MD5", ?MODULE, digest).
stop() ->
ok.

View File

@ -34,7 +34,7 @@
-record(state, {check_password}).
start(_Opts) ->
cyrsasl:register_mechanism("PLAIN", ?MODULE, false),
cyrsasl:register_mechanism("PLAIN", ?MODULE, plain),
ok.
stop() ->

168
src/cyrsasl_scram.erl Normal file
View File

@ -0,0 +1,168 @@
-module(cyrsasl_scram).
-author('stephen.roettger@googlemail.com').
-export([start/1,
stop/0,
mech_new/4,
mech_step/2]).
-include("ejabberd.hrl").
-behaviour(cyrsasl).
-record(state, {step, stored_key, server_key, username, get_password, check_password,
auth_message, client_nonce, server_nonce}).
-define(DEFAULT_ITERATION_COUNT, 4096).
-define(SALT_LENGTH, 16).
-define(NONCE_LENGTH, 16).
start(_Opts) ->
cyrsasl:register_mechanism("SCRAM-SHA-1", ?MODULE, scram).
stop() ->
ok.
mech_new(_Host, GetPassword, _CheckPassword, _CheckPasswordDigest) ->
{ok, #state{step = 2, get_password = GetPassword}}.
mech_step(#state{step = 2} = State, ClientIn) ->
case string:tokens(ClientIn, ",") of
["n", UserNameAttribute, ClientNonceAttribute] ->
case parse_attribute(UserNameAttribute) of
{$n, EscapedUserName} ->
case unescape_username(EscapedUserName) of
error ->
{error, "protocol-error-bad-username"};
UserName ->
case parse_attribute(ClientNonceAttribute) of
{$r, ClientNonce} ->
case (State#state.get_password)(UserName) of
{false, _} ->
{error, "not-authorized", UserName};
{Ret, _AuthModule} ->
{StoredKey, ServerKey, Salt, IterationCount} = if
is_tuple(Ret) ->
Ret;
true ->
TempSalt = crypto:rand_bytes(?SALT_LENGTH),
SaltedPassword = scram:salted_password(Ret, TempSalt, ?DEFAULT_ITERATION_COUNT),
{scram:stored_key(scram:client_key(SaltedPassword)), TempSalt, ?DEFAULT_ITERATION_COUNT}
end,
ClientFirstMessageBare = string:substr(ClientIn, string:str(ClientIn, "n=")),
ServerNonce = base64:encode_to_string(crypto:rand_bytes(?NONCE_LENGTH)),
ServerFirstMessage = "r=" ++ ClientNonce ++ ServerNonce ++ "," ++
"s=" ++ base64:encode_to_string(Salt) ++ "," ++
"i=" ++ integer_to_list(IterationCount),
{continue,
ServerFirstMessage,
State#state{step = 4, stored_key = StoredKey, server_key = ServerKey,
auth_message = ClientFirstMessageBare ++ "," ++ ServerFirstMessage,
client_nonce = ClientNonce, server_nonce = ServerNonce, username = UserName}}
end;
_Else ->
{error, "not-supported"}
end
end;
{error, Reason} ->
{error, Reason};
_Else ->
{error, "bad-protocol"}
end;
_Else ->
{error, "bad-protocol"}
end;
mech_step(#state{step = 4} = State, ClientIn) ->
case string:tokens(ClientIn, ",") of
[GS2ChannelBindingAttribute, NonceAttribute, ClientProofAttribute] ->
case parse_attribute(GS2ChannelBindingAttribute) of
{$c, "biws"} -> %biws is base64 for n,, => channelbinding not supported
Nonce = State#state.client_nonce ++ State#state.server_nonce,
case parse_attribute(NonceAttribute) of
{$r, CompareNonce} when CompareNonce == Nonce ->
case parse_attribute(ClientProofAttribute) of
{$p, ClientProofB64} ->
ClientProof = base64:decode(ClientProofB64),
AuthMessage = State#state.auth_message ++ "," ++ string:substr(ClientIn, 1, string:str(ClientIn, ",p=")-1),
ClientSignature = scram:client_signature(State#state.stored_key, AuthMessage),
ClientKey = scram:client_key(ClientProof, ClientSignature),
CompareStoredKey = scram:stored_key(ClientKey),
if CompareStoredKey == State#state.stored_key ->
ServerSignature = scram:server_signature(State#state.server_key, AuthMessage),
{ok, [{username, State#state.username}], "v=" ++ base64:encode_to_string(ServerSignature)};
true ->
{error, "bad-auth"}
end;
_Else ->
{error, "bad-protocol"}
end;
{$r, _} ->
{error, "bad-nonce"};
_Else ->
{error, "bad-protocol"}
end;
_Else ->
{error, "bad-protocol"}
end;
_Else ->
{error, "bad-protocol"}
end.
parse_attribute(Attribute) ->
AttributeLen = string:len(Attribute),
if
AttributeLen > 3 ->
SecondChar = lists:nth(2, Attribute),
case is_alpha(lists:nth(1, Attribute)) of
true ->
if
SecondChar == $= ->
case string:substr(Attribute, 3) of
String when is_list(String) ->
{lists:nth(1, Attribute), String};
_Else ->
{error, "bad-format failed"}
end;
true ->
{error, "bad-format second char not equal sign"}
end;
_Else ->
{error, "bad-format first char not a letter"}
end;
true ->
{error, "bad-format attribute too short"}
end.
unescape_username("") ->
"";
unescape_username(EscapedUsername) ->
Pos = string:str(EscapedUsername, "="),
if
Pos == 0 ->
EscapedUsername;
true ->
Start = string:substr(EscapedUsername, 1, Pos-1),
End = string:substr(EscapedUsername, Pos),
EndLen = string:len(End),
if
EndLen < 3 ->
error;
true ->
case string:substr(End, 1, 3) of
"=2C" ->
Start ++ "," ++ unescape_username(string:substr(End, 4));
"=3D" ->
Start ++ "=" ++ unescape_username(string:substr(End, 4));
_Else ->
error
end
end
end.
is_alpha(Char) when Char >= $a, Char =< $z ->
true;
is_alpha(Char) when Char >= $A, Char =< $Z ->
true;
is_alpha(_) ->
true.

View File

@ -10,6 +10,7 @@
cyrsasl,
cyrsasl_digest,
cyrsasl_plain,
cyrsasl_scram,
ejabberd_admin,
ejabberd_app,
ejabberd_auth_anonymous,

View File

@ -50,6 +50,7 @@
remove_user/2,
remove_user/3,
plain_password_required/1,
storage_type/1,
entropy/1
]).
@ -75,6 +76,21 @@ plain_password_required(Server) ->
M:plain_password_required()
end, auth_modules(Server)).
storage_type(Server) ->
lists:foldl(
fun(_, external) ->
external;
(M, scram) ->
case M:storage_type() of
external ->
external;
_Else ->
scram
end;
(M, plain) ->
M:storage_type()
end, plain, auth_modules(Server)).
%% @doc Check if the user and password can login in server.
%% @spec (User::string(), Server::string(), Password::string()) ->
%% true | false

View File

@ -51,6 +51,7 @@
is_user_exists/2,
remove_user/2,
remove_user/3,
storage_type/0,
plain_password_required/0]).
-include("ejabberd.hrl").
@ -247,3 +248,6 @@ remove_user(_User, _Server, _Password) ->
plain_password_required() ->
false.
storage_type() ->
plain.

View File

@ -43,6 +43,7 @@
is_user_exists/2,
remove_user/2,
remove_user/3,
storage_type/0,
plain_password_required/0
]).
@ -78,6 +79,9 @@ check_cache_last_options(Server) ->
plain_password_required() ->
true.
storage_type() ->
external.
check_password(User, Server, Password) ->
case get_cache_option(Server) of
false -> check_password_extauth(User, Server, Password);

View File

@ -43,6 +43,7 @@
is_user_exists/2,
remove_user/2,
remove_user/3,
storage_type/0,
plain_password_required/0
]).
@ -77,6 +78,9 @@ update_reg_users_counter_table(Server) ->
plain_password_required() ->
false.
storage_type() ->
plain.
check_password(User, Server, Password) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),

View File

@ -0,0 +1,369 @@
-module(ejabberd_auth_internal_scram).
-author('stephen.roettger@googlemail.com').
%% External exports
-export([start/1,
set_password/3,
check_password/3,
check_password/5,
try_register/3,
dirty_get_registered_users/0,
get_vh_registered_users/1,
get_vh_registered_users/2,
get_vh_registered_users_number/1,
get_vh_registered_users_number/2,
get_password/2,
get_password_s/2,
is_user_exists/2,
remove_user/2,
remove_user/3,
storage_type/0,
plain_password_required/0
]).
-include("ejabberd.hrl").
-record(passwd, {us, stored_key, salt, iteration_count, server_key}).
-record(reg_users_counter, {vhost, count}).
-define(DEFAULT_ITERATION_COUNT, 4096).
-define(SALT_LENGTH, 16).
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
start(Host) ->
mnesia:create_table(passwd, [{disc_copies, [node()]},
{attributes, record_info(fields, passwd)}]),
mnesia:create_table(reg_users_counter,
[{ram_copies, [node()]},
{attributes, record_info(fields, reg_users_counter)}]),
update_table(),
update_reg_users_counter_table(Host),
ok.
update_reg_users_counter_table(Server) ->
Set = get_vh_registered_users(Server),
Size = length(Set),
LServer = jlib:nameprep(Server),
F = fun() ->
mnesia:write(#reg_users_counter{vhost = LServer,
count = Size})
end,
mnesia:sync_dirty(F).
plain_password_required() ->
true.
storage_type() ->
scram.
check_password(User, Server, Password) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
[UserEntry] = (catch mnesia:dirty_read({passwd, US})),
IterationCount = UserEntry#passwd.iteration_count,
Salt = UserEntry#passwd.salt,
SaltedPassword = scram:salted_password(Password, Salt, IterationCount),
StoredKey = scram:stored_key(scram:client_key(SaltedPassword)),
if
(UserEntry#passwd.stored_key == StoredKey) ->
true;
true ->
false
end.
check_password(User, Server, Password, Digest, DigestGen) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read({passwd, US}) of
[#passwd{stored_key = Passwd}] ->
DigRes = if
Digest /= "" ->
Digest == DigestGen(Passwd);
true ->
false
end,
if DigRes ->
true;
true ->
(Passwd == Password) and (Password /= "")
end;
_ ->
false
end.
%% @spec (User::string(), Server::string(), Password::string()) ->
%% ok | {error, invalid_jid}
set_password(User, Server, Password) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
Salt = crypto:rand_bytes(?SALT_LENGTH),
IterationCount = ?DEFAULT_ITERATION_COUNT,
SaltedPassword = scram:salted_password(Password, Salt, IterationCount),
StoredKey = scram:stored_key(scram:client_key(SaltedPassword)),
ServerKey = scram:server_key(SaltedPassword),
if
(LUser == error) or (LServer == error) ->
{error, invalid_jid};
true ->
F = fun() ->
mnesia:write(#passwd{us = US,
stored_key = StoredKey,
salt = Salt,
iteration_count = IterationCount,
server_key = ServerKey})
end,
{atomic, ok} = mnesia:transaction(F),
ok
end.
%% @spec (User, Server, Password) -> {atomic, ok} | {atomic, exists} | {error, invalid_jid} | {aborted, Reason}
try_register(User, Server, Password) ->
try_register(User, Server, Password, ?DEFAULT_ITERATION_COUNT).
try_register(User, Server, Password, IterationCount) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
Salt = crypto:rand_bytes(?SALT_LENGTH),
SaltedPassword = scram:salted_password(Password, Salt, IterationCount),
StoredKey = scram:stored_key(scram:client_key(SaltedPassword)),
ServerKey = scram:server_key(SaltedPassword),
if
(LUser == error) or (LServer == error) ->
{error, invalid_jid};
true ->
F = fun() ->
case mnesia:read({passwd, US}) of
[] ->
mnesia:write(#passwd{us = US,
stored_key = StoredKey,
salt = Salt,
iteration_count = IterationCount,
server_key = ServerKey}),
mnesia:dirty_update_counter(
reg_users_counter,
LServer, 1),
ok;
[_E] ->
exists
end
end,
mnesia:transaction(F)
end.
%% Get all registered users in Mnesia
dirty_get_registered_users() ->
mnesia:dirty_all_keys(passwd).
get_vh_registered_users(Server) ->
LServer = jlib:nameprep(Server),
mnesia:dirty_select(
passwd,
[{#passwd{us = '$1', _ = '_'},
[{'==', {element, 2, '$1'}, LServer}],
['$1']}]).
get_vh_registered_users(Server, [{from, Start}, {to, End}])
when is_integer(Start) and is_integer(End) ->
get_vh_registered_users(Server, [{limit, End-Start+1}, {offset, Start}]);
get_vh_registered_users(Server, [{limit, Limit}, {offset, Offset}])
when is_integer(Limit) and is_integer(Offset) ->
case get_vh_registered_users(Server) of
[] ->
[];
Users ->
Set = lists:keysort(1, Users),
L = length(Set),
Start = if Offset < 1 -> 1;
Offset > L -> L;
true -> Offset
end,
lists:sublist(Set, Start, Limit)
end;
get_vh_registered_users(Server, [{prefix, Prefix}])
when is_list(Prefix) ->
Set = [{U,S} || {U, S} <- get_vh_registered_users(Server), lists:prefix(Prefix, U)],
lists:keysort(1, Set);
get_vh_registered_users(Server, [{prefix, Prefix}, {from, Start}, {to, End}])
when is_list(Prefix) and is_integer(Start) and is_integer(End) ->
get_vh_registered_users(Server, [{prefix, Prefix}, {limit, End-Start+1}, {offset, Start}]);
get_vh_registered_users(Server, [{prefix, Prefix}, {limit, Limit}, {offset, Offset}])
when is_list(Prefix) and is_integer(Limit) and is_integer(Offset) ->
case [{U,S} || {U, S} <- get_vh_registered_users(Server), lists:prefix(Prefix, U)] of
[] ->
[];
Users ->
Set = lists:keysort(1, Users),
L = length(Set),
Start = if Offset < 1 -> 1;
Offset > L -> L;
true -> Offset
end,
lists:sublist(Set, Start, Limit)
end;
get_vh_registered_users(Server, _) ->
get_vh_registered_users(Server).
get_vh_registered_users_number(Server) ->
LServer = jlib:nameprep(Server),
Query = mnesia:dirty_select(
reg_users_counter,
[{#reg_users_counter{vhost = LServer, count = '$1'},
[],
['$1']}]),
case Query of
[Count] ->
Count;
_ -> 0
end.
get_vh_registered_users_number(Server, [{prefix, Prefix}]) when is_list(Prefix) ->
Set = [{U, S} || {U, S} <- get_vh_registered_users(Server), lists:prefix(Prefix, U)],
length(Set);
get_vh_registered_users_number(Server, _) ->
get_vh_registered_users_number(Server).
get_password(User, Server) ->
%false.
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read(passwd, US) of
[#passwd{stored_key = Password, server_key = ServerKey, salt = Salt, iteration_count = IterationCount}] ->
{Password, ServerKey, Salt, IterationCount};
_ ->
false
end.
get_password_s(User, Server) ->
%"".
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read(passwd, US) of
[#passwd{stored_key = Password, server_key = ServerKey, salt = Salt, iteration_count = IterationCount}] ->
{Password, ServerKey, Salt, IterationCount};
_ ->
[]
end.
%% @spec (User, Server) -> true | false | {error, Error}
is_user_exists(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read({passwd, US}) of
[] ->
false;
[_] ->
true;
Other ->
{error, Other}
end.
%% @spec (User, Server) -> ok
%% @doc Remove user.
%% Note: it returns ok even if there was some problem removing the user.
remove_user(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
F = fun() ->
mnesia:delete({passwd, US}),
mnesia:dirty_update_counter(reg_users_counter,
LServer, -1)
end,
mnesia:transaction(F),
ok.
%% @spec (User, Server, Password) -> ok | not_exists | not_allowed | bad_request
%% @doc Remove user if the provided password is correct.
remove_user(User, Server, Password) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
F = fun() ->
case mnesia:read({passwd, US}) of
[UserEntry] ->
IterationCount = UserEntry#passwd.iteration_count,
Salt = UserEntry#passwd.salt,
SaltedPassword = scram:salted_password(Password, Salt, IterationCount),
StoredKey = scram:stored_key(scram:client_key(SaltedPassword)),
if
(UserEntry#passwd.stored_key == StoredKey) ->
mnesia:delete({passwd, US}),
mnesia:dirty_update_counter(reg_users_counter,
LServer, -1),
ok;
true ->
not_allowed
end;
_Else ->
not_exists
end
end,
case mnesia:transaction(F) of
{atomic, ok} ->
ok;
{atomic, Res} ->
Res;
_ ->
bad_request
end.
update_table() ->
Fields = record_info(fields, passwd),
case mnesia:table_info(passwd, attributes) of
Fields ->
ok;
[user, stored_key] ->
?INFO_MSG("Converting passwd table from "
"{user, stored_key} format", []),
Host = ?MYNAME,
{atomic, ok} = mnesia:create_table(
ejabberd_auth_internal_tmp_table,
[{disc_only_copies, [node()]},
{type, bag},
{local_content, true},
{record_name, passwd},
{attributes, record_info(fields, passwd)}]),
mnesia:transform_table(passwd, ignore, Fields),
F1 = fun() ->
mnesia:write_lock_table(ejabberd_auth_internal_tmp_table),
mnesia:foldl(
fun(#passwd{us = U} = R, _) ->
mnesia:dirty_write(
ejabberd_auth_internal_tmp_table,
R#passwd{us = {U, Host}})
end, ok, passwd)
end,
mnesia:transaction(F1),
mnesia:clear_table(passwd),
F2 = fun() ->
mnesia:write_lock_table(passwd),
mnesia:foldl(
fun(R, _) ->
mnesia:dirty_write(R)
end, ok, ejabberd_auth_internal_tmp_table)
end,
mnesia:transaction(F2),
mnesia:delete_table(ejabberd_auth_internal_tmp_table);
_ ->
?INFO_MSG("Recreating passwd table", []),
mnesia:transform_table(passwd, ignore, Fields)
end.

View File

@ -54,6 +54,7 @@
is_user_exists/2,
remove_user/2,
remove_user/3,
storage_type/0,
plain_password_required/0
]).
@ -137,6 +138,9 @@ init(Host) ->
plain_password_required() ->
true.
storage_type() ->
external.
check_password(User, Server, Password) ->
%% In LDAP spec: empty password means anonymous authentication.
%% As ejabberd is providing other anonymous authentication mechanisms

View File

@ -43,6 +43,7 @@
is_user_exists/2,
remove_user/2,
remove_user/3,
storage_type/0,
plain_password_required/0
]).
@ -57,6 +58,9 @@ start(_Host) ->
plain_password_required() ->
false.
storage_type() ->
plain.
%% @spec (User, Server, Password) -> true | false | {error, Error}
check_password(User, Server, Password) ->
case jlib:nodeprep(User) of

View File

@ -39,6 +39,7 @@
is_user_exists/2,
remove_user/2,
remove_user/3,
storage_type/0,
plain_password_required/0
]).
@ -106,6 +107,9 @@ remove_user(_User, _Server, _Password) ->
plain_password_required() ->
true.
storage_type() ->
external.
%%====================================================================
%% Internal functions
%%====================================================================

View File

@ -772,6 +772,24 @@ wait_for_sasl_response({xmlstreamelement, El}, StateData) ->
authenticated = true,
auth_module = AuthModule,
user = U});
{ok, Props, ServerOut} ->
(StateData#state.sockmod):reset_stream(
StateData#state.socket),
send_element(StateData,
{xmlelement, "success",
[{"xmlns", ?NS_SASL}],
[{xmlcdata,
jlib:encode_base64(ServerOut)}]}),
U = xml:get_attr_s(username, Props),
AuthModule = xml:get_attr_s(auth_module, Props),
?INFO_MSG("(~w) Accepted authentication for ~s by ~p",
[StateData#state.socket, U, AuthModule]),
fsm_next_state(wait_for_stream,
StateData#state{
streamid = new_id(),
authenticated = true,
auth_module = AuthModule,
user = U});
{continue, ServerOut, NewSASLState} ->
send_element(StateData,
{xmlelement, "challenge",

55
src/scram.erl Normal file
View File

@ -0,0 +1,55 @@
-module(scram).
-author('stephen.roettger@googlemail.com').
%% External exports
-export([salted_password/3,
stored_key/1,
server_key/1,
server_signature/2,
client_signature/2,
client_key/1,
client_key/2
]).
salted_password(Password, Salt, IterationCount) ->
hi(jlib:nameprep(Password), Salt, IterationCount).
client_key(SaltedPassword) ->
crypto:sha_mac(SaltedPassword, "Client Key").
stored_key(ClientKey) ->
crypto:sha(ClientKey).
server_key(SaltedPassword) ->
crypto:sha_mac(SaltedPassword, "Server Key").
client_signature(StoredKey, AuthMessage) ->
crypto:sha_mac(StoredKey, AuthMessage).
client_key(ClientProof, ClientSignature) ->
binary:list_to_bin(lists:zipwith(fun(X, Y) ->
X bxor Y
end,
binary:bin_to_list(ClientProof),
binary:bin_to_list(ClientSignature))).
server_signature(ServerKey, AuthMessage) ->
crypto:sha_mac(ServerKey, AuthMessage).
hi(Password, Salt, IterationCount) ->
U1 = crypto:sha_mac(Password, string:concat(binary:bin_to_list(Salt), [0,0,0,1])),
binary:list_to_bin(lists:zipwith(fun(X, Y) ->
X bxor Y
end,
binary:bin_to_list(U1),
binary:bin_to_list(hi_round(Password, U1, IterationCount-1)))).
hi_round(Password, UPrev, 1) ->
crypto:sha_mac(Password, UPrev);
hi_round(Password, UPrev, IterationCount) ->
U = crypto:sha_mac(Password, UPrev),
binary:list_to_bin(lists:zipwith(fun(X, Y) ->
X bxor Y
end,
binary:bin_to_list(U),
binary:bin_to_list(hi_round(Password, U, IterationCount-1)))).