aboutsummaryrefslogtreecommitdiff
path: root/src/ejabberd_auth_internal.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/ejabberd_auth_internal.erl')
-rw-r--r--src/ejabberd_auth_internal.erl138
1 files changed, 124 insertions, 14 deletions
diff --git a/src/ejabberd_auth_internal.erl b/src/ejabberd_auth_internal.erl
index 847737f29..b196d3526 100644
--- a/src/ejabberd_auth_internal.erl
+++ b/src/ejabberd_auth_internal.erl
@@ -43,7 +43,7 @@
is_user_exists/2,
remove_user/2,
remove_user/3,
- storage_type/0,
+ store_type/0,
plain_password_required/0
]).
@@ -52,6 +52,8 @@
-record(passwd, {us, password}).
-record(reg_users_counter, {vhost, count}).
+-define(SALT_LENGTH, 16).
+
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
@@ -63,6 +65,7 @@ start(Host) ->
{attributes, record_info(fields, reg_users_counter)}]),
update_table(),
update_reg_users_counter_table(Host),
+ maybe_alert_password_scrammed_without_option(),
ok.
update_reg_users_counter_table(Server) ->
@@ -76,18 +79,26 @@ update_reg_users_counter_table(Server) ->
mnesia:sync_dirty(F).
plain_password_required() ->
- false.
+ case is_scrammed() of
+ false -> false;
+ true -> true
+ end.
-storage_type() ->
- plain.
+store_type() ->
+ case is_scrammed() of
+ false -> plain; %% allows: PLAIN DIGEST-MD5 SCRAM
+ true -> scram %% allows: PLAIN SCRAM
+ end.
check_password(User, Server, Password) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read({passwd, US}) of
- [#passwd{password = Password}] ->
+ [#passwd{password = Password}] when is_list(Password) ->
Password /= "";
+ [#passwd{password = Scram}] when is_record(Scram, scram) ->
+ is_password_scram_valid(Password, Scram);
_ ->
false
end.
@@ -97,7 +108,20 @@ check_password(User, Server, Password, Digest, DigestGen) ->
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read({passwd, US}) of
- [#passwd{password = Passwd}] ->
+ [#passwd{password = Passwd}] when is_list(Passwd) ->
+ DigRes = if
+ Digest /= "" ->
+ Digest == DigestGen(Passwd);
+ true ->
+ false
+ end,
+ if DigRes ->
+ true;
+ true ->
+ (Passwd == Password) and (Password /= "")
+ end;
+ [#passwd{password = Scram}] when is_record(Scram, scram) ->
+ Passwd = base64:decode(Scram#scram.storedkey),
DigRes = if
Digest /= "" ->
Digest == DigestGen(Passwd);
@@ -124,8 +148,12 @@ set_password(User, Server, Password) ->
{error, invalid_jid};
true ->
F = fun() ->
+ Password2 = case is_scrammed() and is_list(Password) of
+ true -> password_to_scram(Password);
+ false -> Password
+ end,
mnesia:write(#passwd{us = US,
- password = Password})
+ password = Password2})
end,
{atomic, ok} = mnesia:transaction(F),
ok
@@ -143,8 +171,12 @@ try_register(User, Server, Password) ->
F = fun() ->
case mnesia:read({passwd, US}) of
[] ->
+ Password2 = case is_scrammed() and is_list(Password) of
+ true -> password_to_scram(Password);
+ false -> Password
+ end,
mnesia:write(#passwd{us = US,
- password = Password}),
+ password = Password2}),
mnesia:dirty_update_counter(
reg_users_counter,
LServer, 1),
@@ -239,8 +271,13 @@ get_password(User, Server) ->
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read(passwd, US) of
- [#passwd{password = Password}] ->
+ [#passwd{password = Password}] when is_list(Password) ->
Password;
+ [#passwd{password = Scram}] when is_record(Scram, scram) ->
+ {base64:decode(Scram#scram.storedkey),
+ base64:decode(Scram#scram.serverkey),
+ base64:decode(Scram#scram.salt),
+ Scram#scram.iterationcount};
_ ->
false
end.
@@ -250,8 +287,10 @@ get_password_s(User, Server) ->
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read(passwd, US) of
- [#passwd{password = Password}] ->
+ [#passwd{password = Password}] when is_list(Password) ->
Password;
+ [#passwd{password = Scram}] when is_record(Scram, scram) ->
+ [];
_ ->
[]
end.
@@ -293,13 +332,21 @@ remove_user(User, Server, Password) ->
US = {LUser, LServer},
F = fun() ->
case mnesia:read({passwd, US}) of
- [#passwd{password = Password}] ->
+ [#passwd{password = Password}] when is_list(Password) ->
mnesia:delete({passwd, US}),
mnesia:dirty_update_counter(reg_users_counter,
LServer, -1),
ok;
- [_] ->
- not_allowed;
+ [#passwd{password = Scram}] when is_record(Scram, scram) ->
+ case is_password_scram_valid(Password, Scram) of
+ true ->
+ mnesia:delete({passwd, US}),
+ mnesia:dirty_update_counter(reg_users_counter,
+ LServer, -1),
+ ok;
+ false ->
+ not_allowed
+ end;
_ ->
not_exists
end
@@ -313,11 +360,11 @@ remove_user(User, Server, Password) ->
bad_request
end.
-
update_table() ->
Fields = record_info(fields, passwd),
case mnesia:table_info(passwd, attributes) of
Fields ->
+ maybe_scram_passwords(),
ok;
[user, password] ->
?INFO_MSG("Converting passwd table from "
@@ -356,5 +403,68 @@ update_table() ->
mnesia:transform_table(passwd, ignore, Fields)
end.
+%%%
+%%% SCRAM
+%%%
+
+%% The passwords are stored scrammed in the table either if the option says so,
+%% or if at least the first password is scrammed.
+is_scrammed() ->
+ OptionScram = is_option_scram(),
+ FirstElement = mnesia:dirty_read(passwd, mnesia:dirty_first(passwd)),
+ case {OptionScram, FirstElement} of
+ {true, _} ->
+ true;
+ {false, [#passwd{password = Scram}]} when is_record(Scram, scram) ->
+ true;
+ _ ->
+ false
+ end.
+is_option_scram() ->
+ scram == ejabberd_config:get_local_option({auth_password_format, ?MYNAME}).
+maybe_alert_password_scrammed_without_option() ->
+ case is_scrammed() andalso not is_option_scram() of
+ true ->
+ ?ERROR_MSG("Some passwords were stored in the database as SCRAM, "
+ "but 'auth_password_format' is not configured 'scram'. "
+ "The option will now be considered to be 'scram'.", []);
+ false ->
+ ok
+ end.
+
+maybe_scram_passwords() ->
+ case is_scrammed() of
+ true -> scram_passwords();
+ false -> ok
+ end.
+
+scram_passwords() ->
+ ?INFO_MSG("Converting the stored passwords into SCRAM bits", []),
+ Fun = fun(#passwd{password = Password} = P) ->
+ Scram = password_to_scram(Password),
+ P#passwd{password = Scram}
+ end,
+ Fields = record_info(fields, passwd),
+ mnesia:transform_table(passwd, Fun, Fields).
+
+password_to_scram(Password) ->
+ password_to_scram(Password, ?SCRAM_DEFAULT_ITERATION_COUNT).
+
+password_to_scram(Password, IterationCount) ->
+ 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),
+ #scram{storedkey = base64:encode(StoredKey),
+ serverkey = base64:encode(ServerKey),
+ salt = base64:encode(Salt),
+ iterationcount = IterationCount}.
+
+is_password_scram_valid(Password, Scram) ->
+ IterationCount = Scram#scram.iterationcount,
+ Salt = base64:decode(Scram#scram.salt),
+ SaltedPassword = scram:salted_password(Password, Salt, IterationCount),
+ StoredKey = scram:stored_key(scram:client_key(SaltedPassword)),
+ (base64:decode(Scram#scram.storedkey) == StoredKey).