diff --git a/rebar.config b/rebar.config index 61748af56..c464de277 100644 --- a/rebar.config +++ b/rebar.config @@ -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"}}} ]}. diff --git a/src/ejabberd_auth.erl b/src/ejabberd_auth.erl index aa3284f3d..f5530b9ea 100644 --- a/src/ejabberd_auth.erl +++ b/src/ejabberd_auth.erl @@ -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 diff --git a/src/ejabberd_auth_mnesia.erl b/src/ejabberd_auth_mnesia.erl index fb728313e..76bd35340 100644 --- a/src/ejabberd_auth_mnesia.erl +++ b/src/ejabberd_auth_mnesia.erl @@ -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 -> diff --git a/src/ejabberd_auth_sql.erl b/src/ejabberd_auth_sql.erl index e42b42dbf..da51594f0 100644 --- a/src/ejabberd_auth_sql.erl +++ b/src/ejabberd_auth_sql.erl @@ -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, diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl index 56410ed82..3f42e9dd6 100644 --- a/src/ejabberd_c2s.erl +++ b/src/ejabberd_c2s.erl @@ -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); diff --git a/src/ejabberd_option.erl b/src/ejabberd_option.erl index e5b47f01f..873f76ed1 100644 --- a/src/ejabberd_option.erl +++ b/src/ejabberd_option.erl @@ -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). diff --git a/src/ejabberd_options.erl b/src/ejabberd_options.erl index a03071dc1..445a3515b 100644 --- a/src/ejabberd_options.erl +++ b/src/ejabberd_options.erl @@ -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}, diff --git a/src/ejabberd_piefxis.erl b/src/ejabberd_piefxis.erl index 2712d599a..fb1525f71 100644 --- a/src/ejabberd_piefxis.erl +++ b/src/ejabberd_piefxis.erl @@ -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)) }.