Merge branch 'master' of https://github.com/emqx/emqx into merge-4.3-to-5.0

This commit is contained in:
zhanghongtong 2021-06-28 11:46:55 +08:00
commit d6e531bd8f
12 changed files with 0 additions and 872 deletions

View File

@ -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/

View File

@ -1,2 +0,0 @@
# emqx-sasl
Simple Authentication and Security Layer

View File

@ -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).

View File

@ -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}.

View File

@ -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).

View File

@ -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}, []} }.

View File

@ -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 <Username> <Password> <Salt> [<IterationCount>]", "Add SCRAM-SHA-1 authentication information"},
{"sasl scram delete <Username>", "Delete SCRAM-SHA-1 authentication information"},
{"sasl scram update <Username> <Password> <Salt> [<IterationCount>]", "Update SCRAM-SHA-1 authentication information"},
{"sasl scram lookup <Username>", "Check if SCRAM-SHA-1 authentication information exists"}]).

View File

@ -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(<<GS2CbindFlag:1/binary, _/binary>>) ->
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(<<Key:1/binary, "=", Value/binary>>, Acc) ->
[{to_long(Key), Value} | Acc]
end, [], Attributes).
serialize(Attributes) ->
iolist_to_binary(
lists:foldl(fun({Key, Value}, []) ->
[to_short(Key), "=", to_list(Value)];
({Key, Value}, Acc) ->
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).

View File

@ -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.

View File

@ -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)