diff --git a/apps/emqx_sasl/.gitignore b/apps/emqx_sasl/.gitignore deleted file mode 100644 index dce59c219..000000000 --- a/apps/emqx_sasl/.gitignore +++ /dev/null @@ -1,26 +0,0 @@ -.eunit -deps -*.o -*.beam -*.plt -erl_crash.dump -ebin -rel/example_project -.concrete/DEV_MODE -.rebar -data/ -*.swp -*.swo -.erlang.mk/ -emqx_sasl.d -erlang.mk -rebar3.crashdump -_build -cover/ -ct.coverdata -eunit.coverdata -logs/ -rebar.lock -test/ct.cover.spec -etc/emqx_sasl.conf.rendered -.rebar3/ \ No newline at end of file diff --git a/apps/emqx_sasl/README.md b/apps/emqx_sasl/README.md deleted file mode 100644 index 3284fb38c..000000000 --- a/apps/emqx_sasl/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# emqx-sasl -Simple Authentication and Security Layer diff --git a/apps/emqx_sasl/etc/emqx_sasl.conf b/apps/emqx_sasl/etc/emqx_sasl.conf deleted file mode 100644 index e69de29bb..000000000 diff --git a/apps/emqx_sasl/include/emqx_sasl.hrl b/apps/emqx_sasl/include/emqx_sasl.hrl deleted file mode 100644 index a1658f2b8..000000000 --- a/apps/emqx_sasl/include/emqx_sasl.hrl +++ /dev/null @@ -1,19 +0,0 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- - --define(APP, emqx_sasl). - --define(SCRAM_AUTH_TAB, scram_auth). \ No newline at end of file diff --git a/apps/emqx_sasl/priv/emqx_sasl.schema b/apps/emqx_sasl/priv/emqx_sasl.schema deleted file mode 100644 index e69de29bb..000000000 diff --git a/apps/emqx_sasl/rebar.config b/apps/emqx_sasl/rebar.config deleted file mode 100644 index 318f82a0b..000000000 --- a/apps/emqx_sasl/rebar.config +++ /dev/null @@ -1,19 +0,0 @@ -{deps, - [{pbkdf2, {git, "https://github.com/emqx/erlang-pbkdf2.git", {branch, "2.0.4"}}} - ]}. - -{edoc_opts, [{preprocess, true}]}. -{erl_opts, [warn_unused_vars, - warn_shadow_vars, - warnings_as_errors, - warn_unused_import, - warn_obsolete_guard, - debug_info, - {parse_transform}]}. - -{xref_checks, [undefined_function_calls, undefined_functions, - locals_not_used, deprecated_function_calls, - warnings_as_errors, deprecated_functions]}. -{cover_enabled, true}. -{cover_opts, [verbose]}. -{cover_export_enabled, true}. diff --git a/apps/emqx_sasl/src/emqx_sasl_api.erl b/apps/emqx_sasl/src/emqx_sasl_api.erl deleted file mode 100644 index a69ac557d..000000000 --- a/apps/emqx_sasl/src/emqx_sasl_api.erl +++ /dev/null @@ -1,227 +0,0 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- - --module(emqx_sasl_api). - --include("emqx_sasl.hrl"). - --import(minirest, [ return/0 - , return/1 - ]). - --rest_api(#{name => add, - method => 'POST', - path => "/sasl", - func => add, - descr => "Add authentication information"}). - --rest_api(#{name => delete, - method => 'DELETE', - path => "/sasl", - func => delete, - descr => "Delete authentication information"}). - --rest_api(#{name => update, - method => 'PUT', - path => "/sasl", - func => update, - descr => "Update authentication information"}). - --rest_api(#{name => get, - method => 'GET', - path => "/sasl", - func => get, - descr => "Get authentication information"}). - --export([ add/2 - , delete/2 - , update/2 - , get/2 - ]). - -add(_Bindings, Params) -> - case pipeline([fun ensure_required_add_params/1, - fun validate_params/1, - fun do_add/1], Params) of - ok -> - return(); - {error, Reason} -> - return({error, Reason}) - end. - -delete(_Bindings, Params) -> - case pipeline([fun ensure_required_delete_params/1, - fun validate_params/1, - fun do_delete/1], Params) of - ok -> - return(); - {error, Reason} -> - return({error, Reason}) - end. - -update(_Bindings, Params) -> - case pipeline([fun ensure_required_add_params/1, - fun validate_params/1, - fun do_update/1], Params) of - ok -> - return(); - {error, Reason} -> - return({error, Reason}) - end. - -get(Bindings, Params) when is_list(Params) -> - get(Bindings, maps:from_list(Params)); - -get(_Bindings, #{<<"mechanism">> := Mechanism0, - <<"username">> := Username0}) -> - Mechanism = urldecode(Mechanism0), - Username = urldecode(Username0), - case Mechanism of - <<"SCRAM-SHA-1">> -> - case emqx_sasl_scram:lookup(Username) of - {ok, AuthInfo = #{salt := Salt}} -> - return({ok, AuthInfo#{salt => base64:decode(Salt)}}); - {error, Reason} -> - return({error, Reason}) - end; - _ -> - return({error, unsupported_mechanism}) - end; -get(_Bindings, #{<<"mechanism">> := Mechanism}) -> - case urldecode(Mechanism) of - <<"SCRAM-SHA-1">> -> - Data = #{Mechanism => mnesia:dirty_all_keys(?SCRAM_AUTH_TAB)}, - return({ok, Data}); - _ -> - return({error, <<"Unsupported mechanism">>}) - end; - -get(_Bindings, _Params) -> - Data = lists:foldl(fun(Mechanism, Acc) -> - case Mechanism of - <<"SCRAM-SHA-1">> -> - [#{Mechanism => mnesia:dirty_all_keys(?SCRAM_AUTH_TAB)} | Acc] - end - end, [], emqx_sasl:supported()), - return({ok, Data}). - -ensure_required_add_params(Params) when is_list(Params) -> - case proplists:get_value(<<"mechanism">>, Params) of - undefined -> - {missing, missing_required_param}; - Mechaism -> - ensure_required_add_params(Mechaism, Params) - end. - -ensure_required_add_params(<<"SCRAM-SHA-1">>, Params) -> - Required = [<<"username">>, <<"password">>, <<"salt">>], - case erlang:map_size(maps:with(Required, maps:from_list(Params))) =:= erlang:length(Required) of - true -> ok; - false -> {missing, missing_required_param} - end; -ensure_required_add_params(_, _) -> - {error, unsupported_mechanism}. - -ensure_required_delete_params(Params) when is_list(Params) -> - case proplists:get_value(<<"mechanism">>, Params) of - undefined -> - {missing, missing_required_param}; - Mechaism -> - ensure_required_delete_params(Mechaism, Params) - end. - -ensure_required_delete_params(<<"SCRAM-SHA-1">>, Params) -> - Required = [<<"username">>], - case erlang:map_size(maps:with(Required, maps:from_list(Params))) =:= erlang:length(Required) of - true -> ok; - false -> {missing, missing_required_param} - end; -ensure_required_delete_params(_, _) -> - {error, unsupported_mechanism}. - -validate_params(Params) -> - Mechaism = proplists:get_value(<<"mechanism">>, Params), - validate_params(Mechaism, Params). - -validate_params(<<"SCRAM-SHA-1">>, []) -> - ok; -validate_params(<<"SCRAM-SHA-1">>, [{<<"username">>, Username} | More]) when is_binary(Username) -> - validate_params(<<"SCRAM-SHA-1">>, More); -validate_params(<<"SCRAM-SHA-1">>, [{<<"username">>, _} | _]) -> - {error, invalid_username}; -validate_params(<<"SCRAM-SHA-1">>, [{<<"password">>, Password} | More]) when is_binary(Password) -> - validate_params(<<"SCRAM-SHA-1">>, More); -validate_params(<<"SCRAM-SHA-1">>, [{<<"password">>, _} | _]) -> - {error, invalid_password}; -validate_params(<<"SCRAM-SHA-1">>, [{<<"salt">>, Salt} | More]) when is_binary(Salt) -> - validate_params(<<"SCRAM-SHA-1">>, More); -validate_params(<<"SCRAM-SHA-1">>, [{<<"salt">>, _} | _]) -> - {error, invalid_salt}; -validate_params(<<"SCRAM-SHA-1">>, [{<<"iteration_count">>, IterationCount} | More]) when is_integer(IterationCount) -> - validate_params(<<"SCRAM-SHA-1">>, More); -validate_params(<<"SCRAM-SHA-1">>, [{<<"iteration_count">>, _} | _]) -> - {error, invalid_iteration_count}; -validate_params(<<"SCRAM-SHA-1">>, [_ | More]) -> - validate_params(<<"SCRAM-SHA-1">>, More). - -do_add(Params) -> - Mechaism = proplists:get_value(<<"mechanism">>, Params), - do_add(Mechaism, Params). - -do_add(<<"SCRAM-SHA-1">>, Params) -> - Username = proplists:get_value(<<"username">>, Params), - Password = proplists:get_value(<<"password">>, Params), - Salt = proplists:get_value(<<"salt">>, Params), - IterationCount = proplists:get_value(<<"iteration_count">>, Params, 4096), - emqx_sasl_scram:add(Username, Password, Salt, IterationCount); -do_add(_, _) -> - {error, unsupported_mechanism}. - -do_delete(Params) -> - Mechaism = proplists:get_value(<<"mechanism">>, Params), - do_delete(Mechaism, Params). - -do_delete(<<"SCRAM-SHA-1">>, Params) -> - Username = proplists:get_value(<<"username">>, Params), - emqx_sasl_scram:delete(Username); -do_delete(_, _) -> - {error, unsupported_mechanism}. - -do_update(Params) -> - Mechaism = proplists:get_value(<<"mechanism">>, Params), - do_update(Mechaism, Params). - -do_update(<<"SCRAM-SHA-1">>, Params) -> - Username = proplists:get_value(<<"username">>, Params), - Password = proplists:get_value(<<"password">>, Params), - Salt = proplists:get_value(<<"salt">>, Params), - IterationCount = proplists:get_value(<<"iteration_count">>, Params, 4096), - emqx_sasl_scram:update(Username, Password, Salt, IterationCount); -do_update(_, _) -> - {error, unsupported_mechanism}. - -pipeline([], _) -> - ok; -pipeline([Fun | More], Params) -> - case Fun(Params) of - ok -> - pipeline(More, Params); - {error, Reason} -> - {error, Reason} - end. - -urldecode(S) -> - emqx_http_lib:uri_decode(S). diff --git a/apps/emqx_sasl/src/emqx_sasl_app.erl b/apps/emqx_sasl/src/emqx_sasl_app.erl deleted file mode 100644 index 0ce3dc076..000000000 --- a/apps/emqx_sasl/src/emqx_sasl_app.erl +++ /dev/null @@ -1,46 +0,0 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- - --module(emqx_sasl_app). - --behaviour(application). - --emqx_plugin(?MODULE). - --export([ start/2 - , stop/1 - ]). - --behaviour(supervisor). - --export([init/1]). - -start(_Type, _Args) -> - ok = emqx_sasl:init(), - _ = emqx_sasl:load(), - emqx_sasl_cli:load(), - supervisor:start_link({local, ?MODULE}, ?MODULE, []). - -stop(_State) -> - emqx_sasl_cli:unload(), - emqx_sasl:unload(). - -%%-------------------------------------------------------------------- -%% Dummy supervisor -%%-------------------------------------------------------------------- - -init([]) -> - {ok, { {one_for_all, 1, 10}, []} }. diff --git a/apps/emqx_sasl/src/emqx_sasl_cli.erl b/apps/emqx_sasl/src/emqx_sasl_cli.erl deleted file mode 100644 index 01e862373..000000000 --- a/apps/emqx_sasl/src/emqx_sasl_cli.erl +++ /dev/null @@ -1,82 +0,0 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- - --module(emqx_sasl_cli). - --include("emqx_sasl.hrl"). - -%% APIs --export([ load/0 - , unload/0 - , cli/1 - ]). - -load() -> - emqx_ctl:register_command(sasl, {?MODULE, cli}, []). - -unload() -> - emqx_ctl:unregister_command(sasl). - -cli(["scram", "add", Username, Password, Salt]) -> - cli(["scram", "add", Username, Password, Salt, "4096"]); -cli(["scram", "add", Username, Password, Salt, IterationCount]) -> - case emqx_sasl_scram:add(list_to_binary(Username), - list_to_binary(Password), - list_to_binary(Salt), - list_to_integer(IterationCount)) of - ok -> - emqx_ctl:print("Authentication information added successfully~n"); - {error, already_existed} -> - emqx_ctl:print("Authentication information already exists~n") - end; - -cli(["scram", "delete", Username0]) -> - Username = list_to_binary(Username0), - ok = emqx_sasl_scram:delete(Username), - emqx_ctl:print("Authentication information deleted successfully~n"); - -cli(["scram", "update", Username, Password, Salt]) -> - cli(["scram", "update", Username, Password, Salt, "4096"]); -cli(["scram", "update", Username, Password, Salt, IterationCount]) -> - case emqx_sasl_scram:update(list_to_binary(Username), - list_to_binary(Password), - list_to_binary(Salt), - list_to_integer(IterationCount)) of - ok -> - emqx_ctl:print("Authentication information updated successfully~n"); - {error, not_found} -> - emqx_ctl:print("Authentication information not found~n") - end; - -cli(["scram", "lookup", Username0]) -> - Username = list_to_binary(Username0), - case emqx_sasl_scram:lookup(Username) of - {ok, #{username := Username, - stored_key := StoredKey, - server_key := ServerKey, - salt := Salt, - iteration_count := IterationCount}} -> - emqx_ctl:print("Username: ~s, Stored Key: ~s, Server Key: ~s, Salt: ~s, Iteration Count: ~p~n", - [Username, StoredKey, ServerKey, base64:decode(Salt), IterationCount]); - {error, not_found} -> - emqx_ctl:print("Authentication information not found~n") - end; - -cli(_) -> - emqx_ctl:usage([{"sasl scram add []", "Add SCRAM-SHA-1 authentication information"}, - {"sasl scram delete ", "Delete SCRAM-SHA-1 authentication information"}, - {"sasl scram update []", "Update SCRAM-SHA-1 authentication information"}, - {"sasl scram lookup ", "Check if SCRAM-SHA-1 authentication information exists"}]). diff --git a/apps/emqx_sasl/src/emqx_sasl_scram.erl b/apps/emqx_sasl/src/emqx_sasl_scram.erl deleted file mode 100644 index beb415568..000000000 --- a/apps/emqx_sasl/src/emqx_sasl_scram.erl +++ /dev/null @@ -1,310 +0,0 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- - --module(emqx_sasl_scram). - --include("emqx_sasl.hrl"). - --export([ init/0 - , add/3 - , add/4 - , update/3 - , update/4 - , delete/1 - , lookup/1 - , check/2 - , make_client_first/1]). - --record(?SCRAM_AUTH_TAB, { - username, - stored_key, - server_key, - salt, - iteration_count :: integer() - }). - --ifdef(TEST). --compile(export_all). --compile(nowarn_export_all). --endif. - -init() -> - ok = ekka_mnesia:create_table(?SCRAM_AUTH_TAB, [ - {disc_copies, [node()]}, - {attributes, record_info(fields, ?SCRAM_AUTH_TAB)}, - {storage_properties, [{ets, [{read_concurrency, true}]}]}]), - ok = ekka_mnesia:copy_table(?SCRAM_AUTH_TAB, disc_copies). - -add(Username, Password, Salt) -> - add(Username, Password, Salt, 4096). - -add(Username, Password, Salt, IterationCount) -> - case lookup(Username) of - {error, not_found} -> - do_add(Username, Password, Salt, IterationCount); - _ -> - {error, already_existed} - end. - -update(Username, Password, Salt) -> - update(Username, Password, Salt, 4096). - -update(Username, Password, Salt, IterationCount) -> - case lookup(Username) of - {error, not_found} -> - {error, not_found}; - _ -> - do_add(Username, Password, Salt, IterationCount) - end. - -delete(Username) -> - ret(mnesia:transaction(fun mnesia:delete/3, [?SCRAM_AUTH_TAB, Username, write])). - -lookup(Username) -> - case mnesia:dirty_read(?SCRAM_AUTH_TAB, Username) of - [#scram_auth{username = Username, - stored_key = StoredKey, - server_key = ServerKey, - salt = Salt, - iteration_count = IterationCount}] -> - {ok, #{username => Username, - stored_key => StoredKey, - server_key => ServerKey, - salt => Salt, - iteration_count => IterationCount}}; - [] -> - {error, not_found} - end. - -do_add(Username, Password, Salt, IterationCount) -> - SaltedPassword = pbkdf2_sha_1(Password, Salt, IterationCount), - ClientKey = client_key(SaltedPassword), - ServerKey = server_key(SaltedPassword), - StoredKey = crypto:hash(sha, ClientKey), - AuthInfo = #scram_auth{username = Username, - stored_key = base64:encode(StoredKey), - server_key = base64:encode(ServerKey), - salt = base64:encode(Salt), - iteration_count = IterationCount}, - ret(mnesia:transaction(fun mnesia:write/3, [?SCRAM_AUTH_TAB, AuthInfo, write])). - -ret({atomic, ok}) -> ok; -ret({aborted, Error}) -> {error, Error}. - -check(Data, Cache) when map_size(Cache) =:= 0 -> - check_client_first(Data); -check(Data, Cache) -> - case maps:get(next_step, Cache, undefined) of - undefined -> check_server_first(Data, Cache); - check_client_final -> check_client_final(Data, Cache); - check_server_final -> check_server_final(Data, Cache) - end. - -check_client_first(ClientFirst) -> - ClientFirstWithoutHeader = without_header(ClientFirst), - Attributes = parse(ClientFirstWithoutHeader), - Username = proplists:get_value(username, Attributes), - ClientNonce = proplists:get_value(nonce, Attributes), - case lookup(Username) of - {error, not_found} -> - {error, not_found}; - {ok, #{stored_key := StoredKey0, - server_key := ServerKey0, - salt := Salt0, - iteration_count := IterationCount}} -> - StoredKey = base64:decode(StoredKey0), - ServerKey = base64:decode(ServerKey0), - Salt = base64:decode(Salt0), - ServerNonce = nonce(), - Nonce = list_to_binary(binary_to_list(ClientNonce) ++ binary_to_list(ServerNonce)), - ServerFirst = make_server_first(Nonce, Salt, IterationCount), - {continue, ServerFirst, #{next_step => check_client_final, - client_first_without_header => ClientFirstWithoutHeader, - server_first => ServerFirst, - stored_key => StoredKey, - server_key => ServerKey, - nonce => Nonce}} - end. - -check_client_final(ClientFinal, #{client_first_without_header := ClientFirstWithoutHeader, - server_first := ServerFirst, - server_key := ServerKey, - stored_key := StoredKey, - nonce := OldNonce}) -> - ClientFinalWithoutProof = without_proof(ClientFinal), - Attributes = parse(ClientFinal), - ClientProof = base64:decode(proplists:get_value(proof, Attributes)), - NewNonce = proplists:get_value(nonce, Attributes), - Auth0 = io_lib:format("~s,~s,~s", [ClientFirstWithoutHeader, ServerFirst, ClientFinalWithoutProof]), - Auth = iolist_to_binary(Auth0), - ClientSignature = hmac(StoredKey, Auth), - ClientKey = crypto:exor(ClientProof, ClientSignature), - case NewNonce =:= OldNonce andalso crypto:hash(sha, ClientKey) =:= StoredKey of - true -> - ServerSignature = hmac(ServerKey, Auth), - ServerFinal = make_server_final(ServerSignature), - {ok, ServerFinal, #{}}; - false -> - {error, invalid_client_final} - end. - -check_server_first(ServerFirst, #{password := Password, - client_first := ClientFirst}) -> - Attributes = parse(ServerFirst), - Nonce = proplists:get_value(nonce, Attributes), - ClientFirstWithoutHeader = without_header(ClientFirst), - ClientFinalWithoutProof = serialize([{channel_binding, <<"biws">>}, {nonce, Nonce}]), - Auth = list_to_binary(io_lib:format("~s,~s,~s", [ClientFirstWithoutHeader, ServerFirst, ClientFinalWithoutProof])), - Salt = base64:decode(proplists:get_value(salt, Attributes)), - IterationCount = binary_to_integer(proplists:get_value(iteration_count, Attributes)), - SaltedPassword = pbkdf2_sha_1(Password, Salt, IterationCount), - ClientKey = client_key(SaltedPassword), - StoredKey = crypto:hash(sha, ClientKey), - ClientSignature = hmac(StoredKey, Auth), - ClientProof = base64:encode(crypto:exor(ClientKey, ClientSignature)), - ClientFinal = serialize([{channel_binding, <<"biws">>}, - {nonce, Nonce}, - {proof, ClientProof}]), - {continue, ClientFinal, #{next_step => check_server_final, - password => Password, - client_first => ClientFirst, - server_first => ServerFirst}}. - -check_server_final(ServerFinal, #{password := Password, - client_first := ClientFirst, - server_first := ServerFirst}) -> - NewAttributes = parse(ServerFinal), - Attributes = parse(ServerFirst), - Nonce = proplists:get_value(nonce, Attributes), - ClientFirstWithoutHeader = without_header(ClientFirst), - ClientFinalWithoutProof = serialize([{channel_binding, <<"biws">>}, {nonce, Nonce}]), - Auth = list_to_binary(io_lib:format("~s,~s,~s", [ClientFirstWithoutHeader, ServerFirst, ClientFinalWithoutProof])), - Salt = base64:decode(proplists:get_value(salt, Attributes)), - IterationCount = binary_to_integer(proplists:get_value(iteration_count, Attributes)), - SaltedPassword = pbkdf2_sha_1(Password, Salt, IterationCount), - ServerKey = server_key(SaltedPassword), - ServerSignature = hmac(ServerKey, Auth), - case base64:encode(ServerSignature) =:= proplists:get_value(verifier, NewAttributes) of - true -> - {ok, <<>>, #{}}; - false -> - {stop, invalid_server_final} - end. - -make_client_first(Username) -> - list_to_binary("n,," ++ binary_to_list(serialize([{username, Username}, {nonce, nonce()}]))). - -make_server_first(Nonce, Salt, IterationCount) -> - serialize([{nonce, Nonce}, {salt, base64:encode(Salt)}, {iteration_count, IterationCount}]). - -make_server_final(ServerSignature) -> - serialize([{verifier, base64:encode(ServerSignature)}]). - -nonce() -> - base64:encode([$a + rand:uniform(26) || _ <- lists:seq(1, 10)]). - -pbkdf2_sha_1(Password, Salt, IterationCount) -> - {ok, Bin} = pbkdf2:pbkdf2(sha, Password, Salt, IterationCount), - pbkdf2:to_hex(Bin). - --if(?OTP_RELEASE >= 23). -hmac(Key, Data) -> - HMAC = crypto:mac_init(hmac, sha, Key), - HMAC1 = crypto:mac_update(HMAC, Data), - crypto:mac_final(HMAC1). --else. -hmac(Key, Data) -> - HMAC = crypto:hmac_init(sha, Key), - HMAC1 = crypto:hmac_update(HMAC, Data), - crypto:hmac_final(HMAC1). --endif. - -client_key(SaltedPassword) -> - hmac(<<"Client Key">>, SaltedPassword). - -server_key(SaltedPassword) -> - hmac(<<"Server Key">>, SaltedPassword). - -without_header(<<"n,,", ClientFirstWithoutHeader/binary>>) -> - ClientFirstWithoutHeader; -without_header(<>) -> - error({unsupported_gs2_cbind_flag, binary_to_atom(GS2CbindFlag, utf8)}). - -without_proof(ClientFinal) -> - [ClientFinalWithoutProof | _] = binary:split(ClientFinal, <<",p=">>, [global, trim_all]), - ClientFinalWithoutProof. - -parse(Message) -> - Attributes = binary:split(Message, <<$,>>, [global, trim_all]), - lists:foldl(fun(< - Acc ++ [",", to_short(Key), "=", to_list(Value)] - end, [], Attributes)). - -to_long(<<"a">>) -> - authzid; -to_long(<<"c">>) -> - channel_binding; -to_long(<<"n">>) -> - username; -to_long(<<"p">>) -> - proof; -to_long(<<"r">>) -> - nonce; -to_long(<<"s">>) -> - salt; -to_long(<<"v">>) -> - verifier; -to_long(<<"i">>) -> - iteration_count; -to_long(_) -> - error(test). - -to_short(authzid) -> - "a"; -to_short(channel_binding) -> - "c"; -to_short(username) -> - "n"; -to_short(proof) -> - "p"; -to_short(nonce) -> - "r"; -to_short(salt) -> - "s"; -to_short(verifier) -> - "v"; -to_short(iteration_count) -> - "i"; -to_short(_) -> - error(test). - -to_list(V) when is_binary(V) -> - binary_to_list(V); -to_list(V) when is_list(V) -> - V; -to_list(V) when is_integer(V) -> - integer_to_list(V); -to_list(_) -> - error(bad_type). - diff --git a/apps/emqx_sasl/test/emqx_sasl_scram_SUITE.erl b/apps/emqx_sasl/test/emqx_sasl_scram_SUITE.erl deleted file mode 100644 index 202b0da2a..000000000 --- a/apps/emqx_sasl/test/emqx_sasl_scram_SUITE.erl +++ /dev/null @@ -1,140 +0,0 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- - --module(emqx_sasl_scram_SUITE). - --compile(export_all). --compile(nowarn_export_all). - --include_lib("common_test/include/ct.hrl"). --include_lib("eunit/include/eunit.hrl"). - -init_per_suite(Config) -> - emqx_ct_helpers:start_apps([emqx_sasl]), - Config. - -end_per_suite(_Config) -> - emqx_ct_helpers:stop_apps([]). - -all() -> emqx_ct:all(?MODULE). - -t_crud(_) -> - Username = <<"test">>, - Password = <<"public">>, - Salt = <<"emqx">>, - IterationCount = 4096, - EncodedSalt = base64:encode(Salt), - SaltedPassword = emqx_sasl_scram:pbkdf2_sha_1(Password, Salt, IterationCount), - ClientKey = emqx_sasl_scram:client_key(SaltedPassword), - ServerKey = base64:encode(emqx_sasl_scram:server_key(SaltedPassword)), - StoredKey = base64:encode(crypto:hash(sha, ClientKey)), - - {error, not_found} = emqx_sasl_scram:lookup(Username), - ok = emqx_sasl_scram:add(Username, Password, Salt), - {error, already_existed} = emqx_sasl_scram:add(Username, Password, Salt), - - {ok, #{username := Username, - stored_key := StoredKey, - server_key := ServerKey, - salt := EncodedSalt, - iteration_count := IterationCount}} = emqx_sasl_scram:lookup(Username), - - NewSalt = <<"new salt">>, - NewEncodedSalt = base64:encode(NewSalt), - emqx_sasl_scram:update(Username, Password, NewSalt), - {ok, #{username := Username, - salt := NewEncodedSalt}} = emqx_sasl_scram:lookup(Username), - emqx_sasl_scram:delete(Username), - {error, not_found} = emqx_sasl_scram:lookup(Username). - -t_scram(_) -> - AuthMethod = <<"SCRAM-SHA-1">>, - [AuthMethod] = emqx_sasl:supported(), - - Username = <<"test">>, - Password = <<"public">>, - Salt = <<"emqx">>, - ok = emqx_sasl_scram:add(Username, Password, Salt), - ClientFirst = emqx_sasl_scram:make_client_first(Username), - - {ok, {continue, ServerFirst, Cache}} = emqx_sasl:check(AuthMethod, ClientFirst, #{}), - - {ok, {continue, ClientFinal, ClientCache}} = emqx_sasl:check(AuthMethod, ServerFirst, #{password => Password, client_first => ClientFirst}), - - {ok, {ok, ServerFinal, #{}}} = emqx_sasl:check(AuthMethod, ClientFinal, Cache), - - {ok, _} = emqx_sasl:check(AuthMethod, ServerFinal, ClientCache). - -%t_proto(_) -> -% process_flag(trap_exit, true), -% -% Username = <<"username">>, -% Password = <<"password">>, -% Salt = <<"emqx">>, -% AuthMethod = <<"SCRAM-SHA-1">>, -% -% {ok, Client0} = emqtt:start_link([{clean_start, true}, -% {proto_ver, v5}, -% {enhanced_auth, #{method => AuthMethod, -% params => #{username => Username, -% password => Password, -% salt => Salt}}}, -% {connect_timeout, 6000}]), -% {error,{not_authorized,#{}}} = emqtt:connect(Client0), -% -% ok = emqx_sasl_scram:add(Username, Password, Salt), -% {ok, Client1} = emqtt:start_link([{clean_start, true}, -% {proto_ver, v5}, -% {enhanced_auth, #{method => AuthMethod, -% params => #{username => Username, -% password => Password, -% salt => Salt}}}, -% {connect_timeout, 6000}]), -% {ok, _} = emqtt:connect(Client1), -% -% timer:sleep(200), -% ok = emqtt:reauthentication(Client1, #{params => #{username => Username, -% password => Password, -% salt => Salt}}), -% -% timer:sleep(200), -% ErrorFun = fun (_State) -> {ok, <<>>, #{}} end, -% ok = emqtt:reauthentication(Client1, #{params => #{},function => ErrorFun}), -% receive -% {disconnected,ReasonCode2,#{}} -> -% ?assertEqual(ReasonCode2, 135) -% after 500 -> -% error("emqx re-authentication failed") -% end, -% -% {ok, Client2} = emqtt:start_link([{clean_start, true}, -% {proto_ver, v5}, -% {enhanced_auth, #{method => AuthMethod, -% params => #{}, -% function =>fun (_State) -> {ok, <<>>, #{}} end}}, -% {connect_timeout, 6000}]), -% {error,{not_authorized,#{}}} = emqtt:connect(Client2), -% -% receive_msg(), -% process_flag(trap_exit, false). - -receive_msg() -> - receive - {'EXIT', Msg} -> - ct:print("==========+~p~n", [Msg]), - receive_msg() - after 200 -> ok - end. diff --git a/rebar.config.erl b/rebar.config.erl index 63a7ba97f..d0822f3b2 100644 --- a/rebar.config.erl +++ b/rebar.config.erl @@ -287,7 +287,6 @@ relx_plugin_apps(ReleaseType) -> , emqx_authentication , emqx_web_hook , emqx_rule_engine - , emqx_sasl , emqx_statsd ] ++ relx_plugin_apps_per_rel(ReleaseType)