Add password entropy check (EJAB-1326)

This commit is contained in:
Evgeniy Khramtsov 2010-10-24 17:17:30 +10:00
parent 0a1b0498a6
commit 641dc7d695
3 changed files with 88 additions and 31 deletions

View File

@ -3777,6 +3777,10 @@ change it by defining access rule in this option. Use with care: allowing regist
from s2s leads to uncontrolled massive accounts creation by rogue users. from s2s leads to uncontrolled massive accounts creation by rogue users.
\titem{\{captcha\_protected, false|true\}} \ind{options!captcha\_protected} \titem{\{captcha\_protected, false|true\}} \ind{options!captcha\_protected}
Protect registrations with CAPTCHA (see section \ref{captcha}). The default is \term{false}. Protect registrations with CAPTCHA (see section \ref{captcha}). The default is \term{false}.
\titem{\{password\_strength, Entropy\}} \ind{options!password\_strength}
This option sets the minimum informational entropy for passwords. The value \term{Entropy}
is a number of bits of entropy. The recommended minimum is 32 bits.
The default is 0, i.e. no checks are performed.
\titem{\{welcome\_message, Message\}} \ind{options!welcomem}Set a welcome message that \titem{\{welcome\_message, Message\}} \ind{options!welcomem}Set a welcome message that
is sent to each newly registered account. The first string is the subject, and is sent to each newly registered account. The first string is the subject, and
the second string is the message body. the second string is the message body.

View File

@ -49,7 +49,8 @@
is_user_exists_in_other_modules/3, is_user_exists_in_other_modules/3,
remove_user/2, remove_user/2,
remove_user/3, remove_user/3,
plain_password_required/1 plain_password_required/1,
entropy/1
]). ]).
-export([auth_modules/1]). -export([auth_modules/1]).
@ -318,6 +319,29 @@ remove_user(User, Server, Password) ->
end, end,
R. R.
%% @spec (IOList) -> non_negative_float()
%% @doc Calculate informational entropy.
entropy(IOList) ->
case binary_to_list(iolist_to_binary(IOList)) of
"" ->
0.0;
S ->
Set = lists:foldl(
fun(C, [Digit, Printable, LowLetter, HiLetter, Other]) ->
if C >= $a, C =< $z ->
[Digit, Printable, 26, HiLetter, Other];
C >= $0, C =< $9 ->
[9, Printable, LowLetter, HiLetter, Other];
C >= $A, C =< $Z ->
[Digit, Printable, LowLetter, 26, Other];
C >= 16#21, C =< 16#7e ->
[Digit, 33, LowLetter, HiLetter, Other];
true ->
[Digit, Printable, LowLetter, HiLetter, 128]
end
end, [0, 0, 0, 0, 0], S),
length(S) * math:log(lists:sum(Set))/math:log(2)
end.
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
%%% Internal functions %%% Internal functions

View File

@ -262,7 +262,7 @@ try_register_or_set_password(User, Server, Password, From, IQ,
SubEl, Source, Lang, CaptchaSucceed) -> SubEl, Source, Lang, CaptchaSucceed) ->
case From of case From of
#jid{user = User, lserver = Server} -> #jid{user = User, lserver = Server} ->
try_set_password(User, Server, Password, IQ, SubEl); try_set_password(User, Server, Password, IQ, SubEl, Lang);
_ when CaptchaSucceed -> _ when CaptchaSucceed ->
case check_from(From, Server) of case check_from(From, Server) of
allow -> allow ->
@ -285,18 +285,25 @@ try_register_or_set_password(User, Server, Password, From, IQ,
end. end.
%% @doc Try to change password and return IQ response %% @doc Try to change password and return IQ response
try_set_password(User, Server, Password, IQ, SubEl) -> try_set_password(User, Server, Password, IQ, SubEl, Lang) ->
case ejabberd_auth:set_password(User, Server, Password) of case is_strong_password(Server, Password) of
ok -> true ->
IQ#iq{type = result, sub_el = [SubEl]}; case ejabberd_auth:set_password(User, Server, Password) of
{error, empty_password} -> ok ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]}; IQ#iq{type = result, sub_el = [SubEl]};
{error, not_allowed} -> {error, empty_password} ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}; IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]};
{error, invalid_jid} -> {error, not_allowed} ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]}; IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
_ -> {error, invalid_jid} ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]} IQ#iq{type = error, sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]};
_ ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
end;
false ->
ErrText = "The password is too weak",
IQ#iq{type = error,
sub_el = [SubEl, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)]}
end. end.
try_register(User, Server, Password, Source, Lang) -> try_register(User, Server, Password, Source, Lang) ->
@ -312,23 +319,30 @@ try_register(User, Server, Password, Source, Lang) ->
allow -> allow ->
case check_timeout(Source) of case check_timeout(Source) of
true -> true ->
case ejabberd_auth:try_register(User, Server, Password) of case is_strong_password(Server, Password) of
{atomic, ok} -> true ->
send_welcome_message(JID), case ejabberd_auth:try_register(
send_registration_notifications(JID, Source), User, Server, Password) of
ok; {atomic, ok} ->
Error -> send_welcome_message(JID),
remove_timeout(Source), send_registration_notifications(JID, Source),
case Error of ok;
{atomic, exists} -> Error ->
{error, ?ERR_CONFLICT}; remove_timeout(Source),
{error, invalid_jid} -> case Error of
{error, ?ERR_JID_MALFORMED}; {atomic, exists} ->
{error, not_allowed} -> {error, ?ERR_CONFLICT};
{error, ?ERR_NOT_ALLOWED}; {error, invalid_jid} ->
{error, _Reason} -> {error, ?ERR_JID_MALFORMED};
{error, ?ERR_INTERNAL_SERVER_ERROR} {error, not_allowed} ->
end {error, ?ERR_NOT_ALLOWED};
{error, _Reason} ->
{error, ?ERR_INTERNAL_SERVER_ERROR}
end
end;
false ->
ErrText = "The password is too weak",
{error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)}
end; end;
false -> false ->
ErrText = "Users are not allowed to register " ErrText = "Users are not allowed to register "
@ -502,3 +516,18 @@ process_xdata_submit(El) ->
error error
end end
end. end.
is_strong_password(Server, Password) ->
LServer = jlib:nameprep(Server),
case gen_mod:get_module_opt(LServer, ?MODULE, password_strength, 0) of
Entropy when is_number(Entropy), Entropy >= 0 ->
if Entropy == 0 ->
true;
true ->
ejabberd_auth:entropy(Password) >= Entropy
end;
Wrong ->
?WARNING_MSG("Wrong value for password_strength option: ~p",
[Wrong]),
true
end.