25
1
mirror of https://github.com/processone/ejabberd.git synced 2024-11-24 16:23:40 +01:00

Allow to use different hash for storing scram passwords

This commit is contained in:
Paweł Chmielowski 2020-12-08 12:06:52 +01:00
parent 0c09599d7b
commit 1dc0ecd1e9
8 changed files with 106 additions and 57 deletions

View File

@ -57,7 +57,7 @@
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.23"}}},
{if_var_true, stun,
{stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.39"}}}},
{xmpp, ".*", {git, "https://github.com/processone/xmpp", "e21de94967c9d6b632058b1f5d34614e0dc9bfe8"}},
{xmpp, ".*", {git, "https://github.com/processone/xmpp", "651050f9619fd768491e8abb6e8db6c778eec2b3"}},
{yconf, ".*", {git, "https://github.com/processone/yconf", {tag, "1.0.8"}}}
]}.

View File

@ -33,7 +33,7 @@
set_password/3, check_password/4,
check_password/6, check_password_with_authmodule/4,
check_password_with_authmodule/6, try_register/3,
get_users/0, get_users/1, password_to_scram/1,
get_users/0, get_users/1, password_to_scram/2,
get_users/2, import_info/0,
count_users/1, import/5, import_start/2,
count_users/2, get_password/2,
@ -554,7 +554,7 @@ db_try_register(User, Server, Password, Mod) ->
case erlang:function_exported(Mod, try_register, 3) of
true ->
Password1 = case Mod:store_type(Server) of
scram -> password_to_scram(Password);
scram -> password_to_scram(Server, Password);
_ -> Password
end,
Ret = case use_cache(Mod, Server) of
@ -579,7 +579,7 @@ db_set_password(User, Server, Password, Mod) ->
case erlang:function_exported(Mod, set_password, 3) of
true ->
Password1 = case Mod:store_type(Server) of
scram -> password_to_scram(Password);
scram -> password_to_scram(Server, Password);
_ -> Password
end,
Ret = case use_cache(Mod, Server) of
@ -753,25 +753,28 @@ is_password_scram_valid(Password, Scram) ->
false;
_ ->
IterationCount = Scram#scram.iterationcount,
Hash = Scram#scram.hash,
Salt = base64:decode(Scram#scram.salt),
SaltedPassword = scram:salted_password(sha, Password, Salt, IterationCount),
StoredKey = scram:stored_key(sha, scram:client_key(sha, SaltedPassword)),
SaltedPassword = scram:salted_password(Hash, Password, Salt, IterationCount),
StoredKey = scram:stored_key(Hash, scram:client_key(Hash, SaltedPassword)),
base64:decode(Scram#scram.storedkey) == StoredKey
end.
password_to_scram(Password) ->
password_to_scram(Password, ?SCRAM_DEFAULT_ITERATION_COUNT).
password_to_scram(Host, Password) ->
password_to_scram(Host, Password, ?SCRAM_DEFAULT_ITERATION_COUNT).
password_to_scram(#scram{} = Password, _IterationCount) ->
password_to_scram(_Host, #scram{} = Password, _IterationCount) ->
Password;
password_to_scram(Password, IterationCount) ->
password_to_scram(Host, Password, IterationCount) ->
Hash = ejabberd_option:auth_scram_hash(Host),
Salt = p1_rand:bytes(?SALT_LENGTH),
SaltedPassword = scram:salted_password(sha, Password, Salt, IterationCount),
StoredKey = scram:stored_key(sha, scram:client_key(sha, SaltedPassword)),
ServerKey = scram:server_key(sha, SaltedPassword),
SaltedPassword = scram:salted_password(Hash, Password, Salt, IterationCount),
StoredKey = scram:stored_key(Hash, scram:client_key(Hash, SaltedPassword)),
ServerKey = scram:server_key(Hash, SaltedPassword),
#scram{storedkey = base64:encode(StoredKey),
serverkey = base64:encode(ServerKey),
salt = base64:encode(Salt),
hash = Hash,
iterationcount = IterationCount}.
%%%----------------------------------------------------------------------
@ -938,7 +941,7 @@ convert_to_scram(Server) ->
fun({U, S}) ->
case get_password(U, S) of
Pass when is_binary(Pass) ->
SPass = password_to_scram(Pass),
SPass = password_to_scram(Server, Pass),
set_password(U, S, SPass);
_ ->
ok

View File

@ -255,7 +255,7 @@ transform(#passwd{us = {U, S}, password = Password} = P)
[U, S]),
P;
_ ->
Scram = ejabberd_auth:password_to_scram(Password),
Scram = ejabberd_auth:password_to_scram(global, Password),
P#passwd{password = Scram}
end;
plain ->

View File

@ -56,16 +56,19 @@ store_type(Server) ->
ejabberd_auth:password_format(Server).
set_password(User, Server, Password) ->
F = fun() ->
if is_record(Password, scram) ->
set_password_scram_t(
User, Server,
Password#scram.storedkey, Password#scram.serverkey,
Password#scram.salt, Password#scram.iterationcount);
true ->
set_password_t(User, Server, Password)
end
end,
F =
fun() ->
case Password of
#scram{hash = Hash, storedkey = SK, serverkey = SEK,
salt = Salt, iterationcount = IC} ->
SK2 = scram_hash_encode(Hash, SK),
set_password_scram_t(
User, Server,
SK2, SEK, Salt, IC);
_ ->
set_password_t(User, Server, Password)
end
end,
case ejabberd_sql:sql_transaction(Server, F) of
{atomic, _} ->
{cache, {ok, Password}};
@ -74,14 +77,17 @@ set_password(User, Server, Password) ->
end.
try_register(User, Server, Password) ->
Res = if is_record(Password, scram) ->
add_user_scram(
Server, User,
Password#scram.storedkey, Password#scram.serverkey,
Password#scram.salt, Password#scram.iterationcount);
true ->
add_user(Server, User, Password)
end,
Res =
case Password of
#scram{hash = Hash, storedkey = SK, serverkey = SEK,
salt = Salt, iterationcount = IC} ->
SK2 = scram_hash_encode(Hash, SK),
add_user_scram(
Server, User,
SK2, SEK, Salt, IC);
_ ->
add_user(Server, User, Password)
end,
case Res of
{updated, 1} -> {cache, {ok, Password}};
_ -> {nocache, {error, exists}}
@ -106,9 +112,15 @@ get_password(User, Server) ->
{selected, [{Password, <<>>, <<>>, 0}]} ->
{cache, {ok, Password}};
{selected, [{StoredKey, ServerKey, Salt, IterationCount}]} ->
{cache, {ok, #scram{storedkey = StoredKey,
{Hash, SK} = case StoredKey of
<<"sha256:", Rest/binary>> -> {sha256, Rest};
<<"sha512:", Rest/binary>> -> {sha512, Rest};
Other -> {sha, Other}
end,
{cache, {ok, #scram{storedkey = SK,
serverkey = ServerKey,
salt = Salt,
hash = Hash,
iterationcount = IterationCount}}};
{selected, []} ->
{cache, error};
@ -126,6 +138,13 @@ remove_user(User, Server) ->
-define(BATCH_SIZE, 1000).
scram_hash_encode(Hash, StoreKey) ->
case Hash of
sha -> StoreKey;
sha256 -> <<"sha256:", StoreKey/binary>>;
sha512 -> <<"sha512:", StoreKey/binary>>
end.
set_password_scram_t(LUser, LServer,
StoredKey, ServerKey, Salt, IterationCount) ->
?SQL_UPSERT_T(
@ -282,7 +301,7 @@ export(_Server) ->
"password=%(Password)s"])];
(Host, #passwd{us = {LUser, LServer}, password = #scram{} = Scram})
when LServer == Host ->
StoredKey = Scram#scram.storedkey,
StoredKey = scram_hash_encode(Scram#scram.hash, Scram#scram.storedkey),
ServerKey = Scram#scram.serverkey,
Salt = Scram#scram.salt,
IterationCount = Scram#scram.iterationcount,

View File

@ -376,18 +376,23 @@ authenticated_stream_features(#{lserver := LServer}) ->
sasl_mechanisms(Mechs, #{lserver := LServer, stream_encrypted := Encrypted} = State) ->
Type = ejabberd_auth:store_type(LServer),
Mechs1 = ejabberd_option:disable_sasl_mechanisms(LServer),
ScramHash = ejabberd_option:auth_scram_hash(LServer),
ShaAv = Type == plain orelse (Type == scram andalso ScramHash == sha),
Sha256Av = Type == plain orelse (Type == scram andalso ScramHash == sha256),
Sha512Av = Type == plain orelse (Type == scram andalso ScramHash == sha512),
%% I re-created it from cyrsasl ets magic, but I think it's wrong
%% TODO: need to check before 18.09 release
lists:filter(
fun(<<"ANONYMOUS">>) ->
ejabberd_auth_anonymous:is_sasl_anonymous_enabled(LServer);
(<<"DIGEST-MD5">>) -> Type == plain;
(<<"SCRAM-SHA-1">>) -> Type /= external;
(<<"SCRAM-SHA-1-PLUS">>) -> Type /= external andalso Encrypted;
(<<"SCRAM-SHA-256">>) -> Type == plain;
(<<"SCRAM-SHA-256-PLUS">>) -> Type == plain andalso Encrypted;
(<<"SCRAM-SHA-512">>) -> Type == plain;
(<<"SCRAM-SHA-512-PLUS">>) -> Type == plain andalso Encrypted;
(<<"SCRAM-SHA-1">>) -> ShaAv;
(<<"SCRAM-SHA-1-PLUS">>) -> ShaAv andalso Encrypted;
(<<"SCRAM-SHA-256">>) -> Sha256Av;
(<<"SCRAM-SHA-256-PLUS">>) -> Sha256Av andalso Encrypted;
(<<"SCRAM-SHA-512">>) -> Sha512Av;
(<<"SCRAM-SHA-512-PLUS">>) -> Sha512Av andalso Encrypted;
(<<"PLAIN">>) -> true;
(<<"X-OAUTH2">>) -> [ejabberd_auth_anonymous] /= ejabberd_auth:auth_modules(LServer);
(<<"EXTERNAL">>) -> maps:get(tls_verify, State, false);

View File

@ -17,6 +17,7 @@
-export([auth_method/0, auth_method/1]).
-export([auth_opts/0, auth_opts/1]).
-export([auth_password_format/0, auth_password_format/1]).
-export([auth_scram_hash/0, auth_scram_hash/1]).
-export([auth_use_cache/0, auth_use_cache/1]).
-export([c2s_cafile/0, c2s_cafile/1]).
-export([c2s_ciphers/0, c2s_ciphers/1]).
@ -90,9 +91,9 @@
-export([oom_killer/0]).
-export([oom_queue/0]).
-export([oom_watermark/0]).
-export([outgoing_s2s_families/0, outgoing_s2s_families/1]).
-export([outgoing_s2s_ipv4_address/0, outgoing_s2s_ipv4_address/1]).
-export([outgoing_s2s_ipv6_address/0, outgoing_s2s_ipv6_address/1]).
-export([outgoing_s2s_families/0, outgoing_s2s_families/1]).
-export([outgoing_s2s_port/0, outgoing_s2s_port/1]).
-export([outgoing_s2s_timeout/0, outgoing_s2s_timeout/1]).
-export([pam_service/0, pam_service/1]).
@ -138,8 +139,8 @@
-export([sql_connect_timeout/0, sql_connect_timeout/1]).
-export([sql_database/0, sql_database/1]).
-export([sql_keepalive_interval/0, sql_keepalive_interval/1]).
-export([sql_password/0, sql_password/1]).
-export([sql_odbc_driver/0, sql_odbc_driver/1]).
-export([sql_password/0, sql_password/1]).
-export([sql_pool_size/0, sql_pool_size/1]).
-export([sql_port/0, sql_port/1]).
-export([sql_prepared_statements/0, sql_prepared_statements/1]).
@ -238,6 +239,13 @@ auth_password_format() ->
auth_password_format(Host) ->
ejabberd_config:get_option({auth_password_format, Host}).
-spec auth_scram_hash() -> 'sha' | 'sha256' | 'sha512'.
auth_scram_hash() ->
auth_scram_hash(global).
-spec auth_scram_hash(global | binary()) -> 'sha' | 'sha256' | 'sha512'.
auth_scram_hash(Host) ->
ejabberd_config:get_option({auth_scram_hash, Host}).
-spec auth_use_cache() -> boolean().
auth_use_cache() ->
auth_use_cache(global).
@ -669,17 +677,17 @@ outgoing_s2s_families() ->
outgoing_s2s_families(Host) ->
ejabberd_config:get_option({outgoing_s2s_families, Host}).
-spec outgoing_s2s_ipv4_address() -> inet:ip4_address().
-spec outgoing_s2s_ipv4_address() -> 'undefined' | inet:ip4_address().
outgoing_s2s_ipv4_address() ->
outgoing_s2s_ipv4_address(global).
-spec outgoing_s2s_ipv4_address(global | binary()) -> inet:ip4_address().
-spec outgoing_s2s_ipv4_address(global | binary()) -> 'undefined' | inet:ip4_address().
outgoing_s2s_ipv4_address(Host) ->
ejabberd_config:get_option({outgoing_s2s_ipv4_address, Host}).
-spec outgoing_s2s_ipv6_address() -> inet:ip6_address().
-spec outgoing_s2s_ipv6_address() -> 'undefined' | inet:ip6_address().
outgoing_s2s_ipv6_address() ->
outgoing_s2s_ipv6_address(global).
-spec outgoing_s2s_ipv6_address(global | binary()) -> inet:ip6_address().
-spec outgoing_s2s_ipv6_address(global | binary()) -> 'undefined' | inet:ip6_address().
outgoing_s2s_ipv6_address(Host) ->
ejabberd_config:get_option({outgoing_s2s_ipv6_address, Host}).
@ -938,13 +946,6 @@ sql_keepalive_interval() ->
sql_keepalive_interval(Host) ->
ejabberd_config:get_option({sql_keepalive_interval, Host}).
-spec sql_password() -> binary().
sql_password() ->
sql_password(global).
-spec sql_password(global | binary()) -> binary().
sql_password(Host) ->
ejabberd_config:get_option({sql_password, Host}).
-spec sql_odbc_driver() -> binary().
sql_odbc_driver() ->
sql_odbc_driver(global).
@ -952,6 +953,13 @@ sql_odbc_driver() ->
sql_odbc_driver(Host) ->
ejabberd_config:get_option({sql_odbc_driver, Host}).
-spec sql_password() -> binary().
sql_password() ->
sql_password(global).
-spec sql_password(global | binary()) -> binary().
sql_password(Host) ->
ejabberd_config:get_option({sql_password, Host}).
-spec sql_pool_size() -> pos_integer().
sql_pool_size() ->
sql_pool_size(global).

View File

@ -79,6 +79,8 @@ opt_type(auth_opts) ->
end;
opt_type(auth_password_format) ->
econf:enum([plain, scram]);
opt_type(auth_scram_hash) ->
econf:enum([sha, sha256, sha512]);
opt_type(auth_use_cache) ->
econf:bool();
opt_type(c2s_cafile) ->
@ -517,6 +519,7 @@ options() ->
fun(Host) -> [ejabberd_config:default_db(Host, ejabberd_auth)] end},
{auth_opts, []},
{auth_password_format, plain},
{auth_scram_hash, sha},
{auth_use_cache,
fun(Host) -> ejabberd_config:get_option({use_cache, Host}) end},
{c2s_cafile, undefined},

View File

@ -181,21 +181,32 @@ export_user(User, Server, Fd) ->
{<<"password">>, Pass}],
children = Els})).
format_scram_password(#scram{storedkey = StoredKey, serverkey = ServerKey,
format_scram_password(#scram{hash = Hash, storedkey = StoredKey, serverkey = ServerKey,
salt = Salt, iterationcount = IterationCount}) ->
StoredKeyB64 = base64:encode(StoredKey),
ServerKeyB64 = base64:encode(ServerKey),
SaltB64 = base64:encode(Salt),
IterationCountBin = (integer_to_binary(IterationCount)),
<<"scram:", StoredKeyB64/binary, ",", ServerKeyB64/binary, ",", SaltB64/binary, ",", IterationCountBin/binary>>.
Hash2 = case Hash of
sha -> <<>>;
sha256 -> <<"sha256,">>;
sha512 -> <<"sha512,">>
end,
<<"scram:", Hash2/binary, StoredKeyB64/binary, ",", ServerKeyB64/binary, ",", SaltB64/binary, ",", IterationCountBin/binary>>.
parse_scram_password(PassData) ->
Split = binary:split(PassData, <<",">>, [global]),
[StoredKeyB64, ServerKeyB64, SaltB64, IterationCountBin] = Split,
[Hash, StoredKeyB64, ServerKeyB64, SaltB64, IterationCountBin] =
case Split of
[K1, K2, K3, K4] -> [sha, K1, K2, K3, K4];
[<<"sha256">>, K1, K2, K3, K4] -> [sha256, K1, K2, K3, K4];
[<<"sha512">>, K1, K2, K3, K4] -> [sha512, K1, K2, K3, K4]
end,
#scram{
storedkey = base64:decode(StoredKeyB64),
serverkey = base64:decode(ServerKeyB64),
salt = base64:decode(SaltB64),
hash = Hash,
iterationcount = (binary_to_integer(IterationCountBin))
}.