./ct_report/coverage/cyrsasl_scram.COVER.html

1 %%%=============================================================================
2 %%% @copyright (C) 1999-2021, Erlang Solutions Ltd
3 %%% @doc SASL SCRAM implementation
4 %%% @end
5 %%%=============================================================================
6 -module(cyrsasl_scram).
7
8 -export([mech_new/3, mech_step/2]).
9
10 -include("mongoose.hrl").
11 -include("jlib.hrl").
12
13 -behaviour(cyrsasl).
14
15 -type sha() :: sha | sha224 | sha256 | sha384 | sha512.
16 -type scram_att() :: {module(), scram_keys()}.
17 -type scram_keys() :: term().
18 -type error() :: {error, binary()} | {error, binary(), binary()}.
19
20 -define(SALT_LENGTH, 16).
21 -define(NONCE_LENGTH, 16).
22
23 mech_new(LServer, Creds, #{sha := Sha,
24 socket := Socket,
25 auth_mech := AuthMech,
26 scram_plus := ScramPlus}) ->
27
:-(
ChannelBinding = calculate_channel_binding(Socket, ScramPlus, Sha, AuthMech),
28
:-(
{ok, St0} = fast_scram:mech_new(
29 #{entity => server,
30 hash_method => Sha,
31 retrieve_mechanism => retrieve_mechanism_fun(LServer, Creds, Sha),
32 nonce_size => ?NONCE_LENGTH,
33 channel_binding => ChannelBinding}),
34
:-(
St1 = fast_scram:mech_set(creds, Creds, St0),
35
:-(
{ok, St1}.
36
37 retrieve_mechanism_fun(LServer, Creds, Sha) ->
38
:-(
fun(Username, St0) ->
39
:-(
case jid:make_bare(Username, LServer) of
40
:-(
error -> {error, {invalid_username, Username}};
41 JID ->
42
:-(
retrieve_mechanism_continue(JID, Creds, Sha, St0)
43 end
44 end.
45
46 retrieve_mechanism_continue(#jid{luser = Username} = JID, Creds, Sha, St0) ->
47
:-(
HostType = mongoose_credentials:host_type(Creds),
48
:-(
case get_scram_attributes(HostType, JID, Sha) of
49 {AuthModule, {StoredKey, ServerKey, Salt, ItCount}} ->
50
:-(
Creds1 = fast_scram:mech_get(creds, St0, Creds),
51
:-(
R = [{username, Username}, {auth_module, AuthModule}],
52
:-(
Creds2 = mongoose_credentials:extend(Creds1, R),
53
:-(
St1 = fast_scram:mech_set(creds, Creds2, St0),
54
:-(
ExtraConfig = #{it_count => ItCount, salt => Salt,
55 auth_data => #{stored_key => StoredKey,
56 server_key => ServerKey}},
57
:-(
{St1, ExtraConfig};
58 {error, Reason, User} ->
59
:-(
{error, {Reason, User}}
60 end.
61
62 mech_step(State, ClientIn) ->
63
:-(
case fast_scram:mech_step(State, ClientIn) of
64 {continue, Msg, NewState} ->
65
:-(
{continue, Msg, NewState};
66 {ok, Msg, FinalState} ->
67
:-(
Creds0 = fast_scram:mech_get(creds, FinalState),
68
:-(
R = [{sasl_success_response, Msg}],
69
:-(
Creds1 = mongoose_credentials:extend(Creds0, R),
70
:-(
{ok, Creds1};
71 {error, Reason, _} ->
72
:-(
?LOG_INFO(#{what => scram_authentication_failed, reason => Reason}),
73
:-(
{error, <<"not-authorized">>}
74 end.
75
76 -spec get_scram_attributes(mongooseim:host_type(), jid:jid(), sha()) -> scram_att() | error().
77 get_scram_attributes(HostType, JID, Sha) ->
78
:-(
case ejabberd_auth:get_passterm_with_authmodule(HostType, JID) of
79 false ->
80
:-(
{UserName, _} = jid:to_lus(JID),
81
:-(
{error, <<"not-authorized">>, UserName};
82 {Params, AuthModule} ->
83
:-(
{AuthModule, do_get_scram_attributes(Params, Sha)}
84 end.
85
86 do_get_scram_attributes(#{iteration_count := IterationCount} = Params, Sha) ->
87
:-(
#{Sha := #{salt := Salt, stored_key := StoredKey, server_key := ServerKey}} = Params,
88
:-(
{base64:decode(StoredKey), base64:decode(ServerKey),
89 base64:decode(Salt), IterationCount};
90 do_get_scram_attributes(Password, Sha) ->
91
:-(
TempSalt = crypto:strong_rand_bytes(?SALT_LENGTH),
92
:-(
SaltedPassword = mongoose_scram:salted_password(Sha, Password, TempSalt,
93 mongoose_scram:iterations()),
94
:-(
{fast_scram:stored_key(Sha, fast_scram:client_key(Sha, SaltedPassword)),
95 fast_scram:server_key(Sha, SaltedPassword), TempSalt, mongoose_scram:iterations()}.
96
97 %%--------------------------------------------------------------------
98 %% Helpers
99 %%--------------------------------------------------------------------
100 calculate_channel_binding(Socket, ScramPlus, Sha, AuthMech) ->
101
:-(
{CBVariant, CBData} = maybe_get_tls_last_message(Socket, ScramPlus),
102
:-(
Advertised = is_scram_plus_advertised(Sha, AuthMech),
103
:-(
case {Advertised, CBVariant} of
104
:-(
{true, _} -> {CBVariant, CBData};
105
:-(
{false, none} -> {undefined, <<>>};
106
:-(
{false, CBVariant} -> {CBVariant, CBData}
107 end.
108
109 maybe_get_tls_last_message(Socket, true) ->
110
:-(
case mongoose_c2s_socket:get_tls_last_message(Socket) of
111 {error, _Error} ->
112
:-(
{none, <<>>};
113 {ok, Msg} ->
114
:-(
{<<"tls-unique">>, Msg}
115 end;
116 maybe_get_tls_last_message(_, _) ->
117
:-(
{none, <<>>}.
118
119
:-(
is_scram_plus_advertised(sha, Mech) -> lists:member(<<"SCRAM-SHA-1-PLUS">>, Mech);
120
:-(
is_scram_plus_advertised(sha224, Mech) -> lists:member(<<"SCRAM-SHA-224-PLUS">>, Mech);
121
:-(
is_scram_plus_advertised(sha256, Mech) -> lists:member(<<"SCRAM-SHA-256-PLUS">>, Mech);
122
:-(
is_scram_plus_advertised(sha384, Mech) -> lists:member(<<"SCRAM-SHA-384-PLUS">>, Mech);
123
:-(
is_scram_plus_advertised(sha512, Mech) -> lists:member(<<"SCRAM-SHA-512-PLUS">>, Mech).
Line Hits Source