]> granicus.if.org Git - ejabberd/commitdiff
Add password entropy check (EJAB-1326)
authorEvgeniy Khramtsov <ekhramtsov@process-one.net>
Sun, 24 Oct 2010 07:17:30 +0000 (17:17 +1000)
committerEvgeniy Khramtsov <ekhramtsov@process-one.net>
Sun, 24 Oct 2010 07:17:30 +0000 (17:17 +1000)
doc/guide.tex
src/ejabberd_auth.erl
src/mod_register.erl

index 69cd268f6a17cf009f79a63137f0b6739a2c9644..48900ce36386b305261d88c0f982b6b7685018bc 100644 (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.
 \titem{\{captcha\_protected, false|true\}} \ind{options!captcha\_protected}
 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
   is sent to each newly registered account. The first string is the subject, and
   the second string is the message body.
index 30ef97685f344f311b2cb340f4107f7289986c47..613ec1e3bef80825e8483d3b37eabc1328933bf5 100644 (file)
@@ -49,7 +49,8 @@
         is_user_exists_in_other_modules/3,
         remove_user/2,
         remove_user/3,
-        plain_password_required/1
+        plain_password_required/1,
+        entropy/1
        ]).
 
 -export([auth_modules/1]).
@@ -318,6 +319,29 @@ remove_user(User, Server, Password) ->
     end,
     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
index ea53bd82aaeeef652ec5e2cace179e242c94ae91..6fe1818e7d233cc5d9813b1cf05b03e30bbaf1da 100644 (file)
@@ -262,7 +262,7 @@ try_register_or_set_password(User, Server, Password, From, IQ,
                             SubEl, Source, Lang, CaptchaSucceed) ->
     case From of
        #jid{user = User, lserver = Server} ->
-           try_set_password(User, Server, Password, IQ, SubEl);
+           try_set_password(User, Server, Password, IQ, SubEl, Lang);
        _ when CaptchaSucceed ->
            case check_from(From, Server) of
                allow ->
@@ -285,18 +285,25 @@ try_register_or_set_password(User, Server, Password, From, IQ,
     end.
 
 %% @doc Try to change password and return IQ response
-try_set_password(User, Server, Password, IQ, SubEl) ->
-    case ejabberd_auth:set_password(User, Server, Password) of
-       ok ->
-            IQ#iq{type = result, sub_el = [SubEl]};
-       {error, empty_password} ->
-           IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]};
-       {error, not_allowed} ->
-           IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
-       {error, invalid_jid} ->
-           IQ#iq{type = error, sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]};
-       _ ->
-           IQ#iq{type = error, sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
+try_set_password(User, Server, Password, IQ, SubEl, Lang) ->
+    case is_strong_password(Server, Password) of
+       true ->
+           case ejabberd_auth:set_password(User, Server, Password) of
+               ok ->
+                   IQ#iq{type = result, sub_el = [SubEl]};
+               {error, empty_password} ->
+                   IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]};
+               {error, not_allowed} ->
+                   IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+               {error, invalid_jid} ->
+                   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.
 
 try_register(User, Server, Password, Source, Lang) ->
@@ -312,23 +319,30 @@ try_register(User, Server, Password, Source, Lang) ->
                allow ->
                    case check_timeout(Source) of
                        true ->
-                           case ejabberd_auth:try_register(User, Server, Password) of
-                               {atomic, ok} ->
-                                   send_welcome_message(JID),
-                                   send_registration_notifications(JID, Source),
-                                   ok;
-                               Error ->
-                                   remove_timeout(Source),
-                                   case Error of
-                                       {atomic, exists} ->
-                                           {error, ?ERR_CONFLICT};
-                                       {error, invalid_jid} ->
-                                           {error, ?ERR_JID_MALFORMED};
-                                       {error, not_allowed} ->
-                                           {error, ?ERR_NOT_ALLOWED};
-                                       {error, _Reason} ->
-                                           {error, ?ERR_INTERNAL_SERVER_ERROR}
-                                   end
+                           case is_strong_password(Server, Password) of
+                               true ->
+                                   case ejabberd_auth:try_register(
+                                          User, Server, Password) of
+                                       {atomic, ok} ->
+                                           send_welcome_message(JID),
+                                           send_registration_notifications(JID, Source),
+                                           ok;
+                                       Error ->
+                                           remove_timeout(Source),
+                                           case Error of
+                                               {atomic, exists} ->
+                                                   {error, ?ERR_CONFLICT};
+                                               {error, invalid_jid} ->
+                                                   {error, ?ERR_JID_MALFORMED};
+                                               {error, not_allowed} ->
+                                                   {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;
                        false ->
                            ErrText = "Users are not allowed to register "
@@ -502,3 +516,18 @@ process_xdata_submit(El) ->
                    error
            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.