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