mirror of
https://github.com/processone/ejabberd.git
synced 2024-12-20 17:27:00 +01:00
Allow to use different hash for storing scram passwords
This commit is contained in:
parent
0c09599d7b
commit
1dc0ecd1e9
@ -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"}}}
|
||||
]}.
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 ->
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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).
|
||||
|
@ -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},
|
||||
|
@ -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))
|
||||
}.
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user