./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 47 ChannelBinding = calculate_channel_binding(Socket, ScramPlus, Sha, AuthMech),
27 47 Fun = fun(Username, St0) ->
28 42 JID = jid:make(Username, LServer, <<>>),
29 42 HostType = mongoose_credentials:host_type(Creds),
30 42 case get_scram_attributes(HostType, JID, Sha) of
31 {AuthModule, {StoredKey, ServerKey, Salt, ItCount}} ->
32 41 Creds1 = fast_scram:mech_get(creds, St0, Creds),
33 41 R = [{username, Username}, {auth_module, AuthModule}],
34 41 Creds2 = mongoose_credentials:extend(Creds1, R),
35 41 St1 = fast_scram:mech_set(creds, Creds2, St0),
36 41 ExtraConfig = #{it_count => ItCount, salt => Salt,
37 auth_data => #{stored_key => StoredKey,
38 server_key => ServerKey}},
39 41 {St1, ExtraConfig};
40 {error, Reason, User} ->
41 1 {error, {Reason, User}}
42 end
43 end,
44 47 {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 47 St1 = fast_scram:mech_set(creds, Creds, St0),
51 47 {ok, St1}.
52
53 mech_step(State, ClientIn) ->
54 88 case fast_scram:mech_step(State, ClientIn) of
55 {continue, Msg, NewState} ->
56 41 {continue, Msg, NewState};
57 {ok, Msg, FinalState} ->
58 41 Creds0 = fast_scram:mech_get(creds, FinalState),
59 41 R = [{sasl_success_response, Msg}],
60 41 Creds1 = mongoose_credentials:extend(Creds0, R),
61 41 {ok, Creds1};
62 {error, Reason, _} ->
63 6 {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 42 case ejabberd_auth:get_passterm_with_authmodule(HostType, JID) of
69 false ->
70 1 {UserName, _} = jid:to_lus(JID),
71 1 {error, <<"not-authorized">>, UserName};
72 {Params, AuthModule} ->
73 41 {AuthModule, do_get_scram_attributes(Params, Sha)}
74 end.
75
76 do_get_scram_attributes(#{iteration_count := IterationCount} = Params, Sha) ->
77 31 #{Sha := #{salt := Salt, stored_key := StoredKey, server_key := ServerKey}} = Params,
78 31 {base64:decode(StoredKey), base64:decode(ServerKey),
79 base64:decode(Salt), IterationCount};
80 do_get_scram_attributes(Password, Sha) ->
81 10 TempSalt = crypto:strong_rand_bytes(?SALT_LENGTH),
82 10 SaltedPassword = mongoose_scram:salted_password(Sha, Password, TempSalt,
83 mongoose_scram:iterations()),
84 10 {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 47 {CBVariant, CBData} = maybe_get_tls_last_message(Socket, ScramPlus),
92 47 Advertised = is_scram_plus_advertised(Sha, AuthMech),
93 47 case {Advertised, CBVariant} of
94 30 {true, _} -> {CBVariant, CBData};
95 17 {false, none} -> {undefined, <<>>};
96
:-(
{false, CBVariant} -> {CBVariant, CBData}
97 end.
98
99 maybe_get_tls_last_message(Socket, true) ->
100 20 case mongoose_tls:get_tls_last_message(ejabberd_socket:get_socket(Socket)) of
101 {error, _Error} ->
102
:-(
{none, <<>>};
103 {ok, Msg} ->
104 20 {<<"tls-unique">>, Msg}
105 end;
106 maybe_get_tls_last_message(_, _) ->
107 27 {none, <<>>}.
108
109 11 is_scram_plus_advertised(sha, Mech) -> lists:member(<<"SCRAM-SHA-1-PLUS">>, Mech);
110 9 is_scram_plus_advertised(sha224, Mech) -> lists:member(<<"SCRAM-SHA-224-PLUS">>, Mech);
111 9 is_scram_plus_advertised(sha256, Mech) -> lists:member(<<"SCRAM-SHA-256-PLUS">>, Mech);
112 9 is_scram_plus_advertised(sha384, Mech) -> lists:member(<<"SCRAM-SHA-384-PLUS">>, Mech);
113 9 is_scram_plus_advertised(sha512, Mech) -> lists:member(<<"SCRAM-SHA-512-PLUS">>, Mech).
Line Hits Source