./ct_report/coverage/mongoose_scram.COVER.html

1 -module(mongoose_scram).
2
3 -include("mongoose.hrl").
4 -include("scram.hrl").
5
6 % Core SCRAM functions
7 -export([salted_password/4]).
8
9 -export([
10 enabled/1,
11 enabled/2,
12 iterations/0,
13 iterations/1,
14 password_to_scram/2,
15 password_to_scram/3,
16 password_to_scram_sha/3,
17 check_password/2,
18 check_digest/4
19 ]).
20
21 -export([serialize/1, deserialize/1]).
22
23 -export([scram_to_tuple/1, scram_record_to_map/1]).
24
25 -ignore_xref([password_to_scram/2, scram_to_tuple/1]).
26
27 -type scram_tuple() :: { StoredKey :: binary(), ServerKey :: binary(),
28 Salt :: binary(), Iterations :: non_neg_integer() }.
29
30 -type scram_map() ::
31 #{iteration_count := non_neg_integer(),
32 sha_key() := server_and_stored_key_type()}.
33
34 -type sha_key() :: sha | sha224 | sha256 | sha384 | sha512.
35
36 -type server_and_stored_key_type() :: #{salt := binary(),
37 server_key := binary(),
38 stored_key := binary()}.
39
40 -type scram() :: #scram{}.
41
42 -export_type([scram_tuple/0, scram/0, scram_map/0]).
43
44 -define(SALT_LENGTH, 16).
45 -define(SCRAM_DEFAULT_ITERATION_COUNT, 10000).
46 -define(SCRAM_SERIAL_PREFIX, "==SCRAM==,").
47 -define(MULTI_SCRAM_SERIAL_PREFIX, "==MULTI_SCRAM==,").
48 -define(SCRAM_SHA1_PREFIX, "===SHA1===").
49 -define(SCRAM_SHA224_PREFIX, "==SHA224==").
50 -define(SCRAM_SHA256_PREFIX, "==SHA256==").
51 -define(SCRAM_SHA384_PREFIX, "==SHA384==").
52 -define(SCRAM_SHA512_PREFIX, "==SHA512==").
53
54 %% ejabberd doesn't implement SASLPREP, so we use the similar RESOURCEPREP instead
55 salted_password(Sha, Password, Salt, IterationCount) ->
56 206 fast_scram:salted_password(Sha, jid:resourceprep(Password), Salt, IterationCount).
57
58 enabled(HostType) ->
59 33 mongoose_config:get_opt([{auth, HostType}, password, format]) =:= scram.
60
61 153 enabled(HostType, cyrsasl_scram_sha1) -> is_password_format_allowed(HostType, sha);
62 153 enabled(HostType, cyrsasl_scram_sha224) -> is_password_format_allowed(HostType, sha224);
63 153 enabled(HostType, cyrsasl_scram_sha256) -> is_password_format_allowed(HostType, sha256);
64 153 enabled(HostType, cyrsasl_scram_sha384) -> is_password_format_allowed(HostType, sha384);
65 153 enabled(HostType, cyrsasl_scram_sha512) -> is_password_format_allowed(HostType, sha512);
66 153 enabled(HostType, cyrsasl_scram_sha1_plus) -> is_password_format_allowed(HostType, sha);
67 153 enabled(HostType, cyrsasl_scram_sha224_plus) -> is_password_format_allowed(HostType, sha224);
68 153 enabled(HostType, cyrsasl_scram_sha256_plus) -> is_password_format_allowed(HostType, sha256);
69 153 enabled(HostType, cyrsasl_scram_sha384_plus) -> is_password_format_allowed(HostType, sha384);
70 153 enabled(HostType, cyrsasl_scram_sha512_plus) -> is_password_format_allowed(HostType, sha512);
71 145 enabled(_HostType, _Mechanism) -> false.
72
73 is_password_format_allowed(HostType, Sha) ->
74 1530 case mongoose_config:get_opt([{auth, HostType}, password]) of
75 60 #{format := scram, hash := ConfiguredSha} -> lists:member(Sha, ConfiguredSha);
76 1470 #{format := _PlainOrScram} -> true
77 end.
78
79 %% This function is exported and used from other modules
80 200 iterations() -> ?SCRAM_DEFAULT_ITERATION_COUNT.
81
82 iterations(HostType) ->
83 33 mongoose_config:get_opt([{auth, HostType}, password, scram_iterations]).
84
85 password_to_scram_sha(Password, IterationCount, HashType) ->
86
:-(
ScramHash = do_password_to_scram(Password, IterationCount, HashType),
87
:-(
maps:from_list([{iteration_count, IterationCount}, ScramHash]).
88
89 password_to_scram(HostType, Password) ->
90
:-(
password_to_scram(HostType, Password, ?SCRAM_DEFAULT_ITERATION_COUNT).
91
92 password_to_scram(_, #scram{} = Password, _) ->
93
:-(
scram_record_to_map(Password);
94 password_to_scram(HostType, Password, IterationCount) ->
95 33 ServerStoredKeys = [do_password_to_scram(Password, IterationCount, HashType)
96 33 || {HashType, _Prefix} <- configured_sha_types(HostType)],
97 33 ResultList = lists:merge([{iteration_count, IterationCount}], ServerStoredKeys),
98 33 maps:from_list(ResultList).
99
100 do_password_to_scram(Password, IterationCount, HashType) ->
101 149 Salt = crypto:strong_rand_bytes(?SALT_LENGTH),
102 149 SaltedPassword = salted_password(HashType, Password, Salt, IterationCount),
103 149 StoredKey = fast_scram:stored_key(HashType, fast_scram:client_key(HashType, SaltedPassword)),
104 149 ServerKey = fast_scram:server_key(HashType, SaltedPassword),
105 149 {HashType, #{salt => base64:encode(Salt),
106 server_key => base64:encode(ServerKey),
107 stored_key => base64:encode(StoredKey)}}.
108
109 check_password(Password, Scram) when is_record(Scram, scram)->
110
:-(
ScramMap = scram_record_to_map(Scram),
111
:-(
check_password(Password, ScramMap);
112 check_password(Password, ScramMap) when is_map(ScramMap) ->
113 57 #{iteration_count := IterationCount} = ScramMap,
114 57 [Sha | _] = [ShaKey || {ShaKey, _Prefix} <- supported_sha_types(),
115 285 maps:is_key(ShaKey, ScramMap)],
116 57 #{Sha := #{salt := Salt, stored_key := StoredKey}} = ScramMap,
117 57 SaltedPassword = salted_password(Sha, Password, base64:decode(Salt), IterationCount),
118 57 ClientStoredKey = fast_scram:stored_key(Sha, fast_scram:client_key(Sha, SaltedPassword)),
119 57 ClientStoredKey == base64:decode(StoredKey).
120
121 serialize(#scram{storedkey = StoredKey, serverkey = ServerKey,
122 salt = Salt, iterationcount = IterationCount})->
123
:-(
IterationCountBin = integer_to_binary(IterationCount),
124
:-(
<< <<?SCRAM_SERIAL_PREFIX>>/binary,
125 StoredKey/binary, $,, ServerKey/binary,
126 $,, Salt/binary, $,, IterationCountBin/binary>>;
127 serialize(#{iteration_count := IterationCount} = ScramMap) ->
128
:-(
IterationCountBin = integer_to_binary(IterationCount),
129
:-(
ConfigedSha = [{ShaKey, Prefix} || {ShaKey, Prefix} <- supported_sha_types(),
130
:-(
maps:is_key(ShaKey, ScramMap)],
131
:-(
Header = [?MULTI_SCRAM_SERIAL_PREFIX, IterationCountBin],
132
:-(
do_serialize(Header, ScramMap, ConfigedSha).
133
134 do_serialize(Serialized, _ ,[]) ->
135
:-(
erlang:iolist_to_binary(Serialized);
136 do_serialize(Header, ScramMap, [{Sha, Prefix} | RemainingSha]) ->
137
:-(
#{Sha := #{salt := Salt,
138 server_key := ServerKey,
139 stored_key := StoredKey}} = ScramMap,
140
:-(
ShaSerialization = [$, , Prefix, Salt, $|, StoredKey, $|, ServerKey],
141
:-(
NewHeader = [Header | ShaSerialization],
142
:-(
do_serialize(NewHeader, ScramMap, RemainingSha).
143
144 deserialize(<<?SCRAM_SERIAL_PREFIX, Serialized/binary>>) ->
145
:-(
case catch binary:split(Serialized, <<",">>, [global]) of
146 [StoredKey, ServerKey, Salt, IterationCount] ->
147
:-(
{ok, #{iteration_count => binary_to_integer(IterationCount),
148 sha => #{salt => Salt,
149 stored_key => StoredKey,
150 server_key => ServerKey}}};
151 _ ->
152
:-(
?LOG_WARNING(#{what => scram_serialisation_incorrect}),
153
:-(
{error, incorrect_scram}
154 end;
155 deserialize(<<?MULTI_SCRAM_SERIAL_PREFIX, Serialized/binary>>) ->
156
:-(
case catch binary:split(Serialized, <<",">>, [global]) of
157 [IterationCountBin | ListOfShaSpecificDetails] ->
158
:-(
IterationCount = binary_to_integer(IterationCountBin),
159
:-(
DeserializedKeys = [deserialize(supported_sha_types(), ShaDetails)
160
:-(
|| ShaDetails <- ListOfShaSpecificDetails],
161
:-(
ResultList = lists:merge([{iteration_count, IterationCount}],
162 lists:flatten(DeserializedKeys)),
163
:-(
{ok, maps:from_list(ResultList)};
164 _ ->
165
:-(
?LOG_WARNING(#{what => scram_serialisation_incorrect}),
166
:-(
{error, incorrect_scram}
167 end;
168 deserialize(_) ->
169
:-(
?LOG_WARNING(#{what => scram_serialisation_corrupted}),
170
:-(
{error, corrupted_scram}.
171
172 deserialize([], _) ->
173
:-(
[];
174 deserialize([{Sha, Prefix} | _RemainingSha],
175 <<Prefix:10/binary, ShaDetails/binary>>) ->
176
:-(
case catch binary:split(ShaDetails, <<"|">>, [global]) of
177 [Salt, StoredKey, ServerKey] ->
178
:-(
{Sha, #{salt => Salt, server_key => ServerKey, stored_key => StoredKey}};
179 _ ->
180
:-(
?LOG_WARNING(#{what => scram_serialisation_incorrect})
181 end;
182 deserialize([_CurrentSha | RemainingSha], ShaDetails) ->
183
:-(
deserialize(RemainingSha, ShaDetails).
184
185 -spec scram_to_tuple(scram()) -> scram_tuple().
186 scram_to_tuple(Scram) ->
187
:-(
{base64:decode(Scram#scram.storedkey),
188 base64:decode(Scram#scram.serverkey),
189 base64:decode(Scram#scram.salt),
190 Scram#scram.iterationcount}.
191
192 -spec scram_record_to_map(scram()) -> scram_map().
193 scram_record_to_map(Scram) ->
194
:-(
#{iteration_count => Scram#scram.iterationcount,
195 sha => #{salt => Scram#scram.salt,
196 stored_key => Scram#scram.storedkey,
197 server_key => Scram#scram.serverkey}}.
198
199 -spec check_digest(Scram, binary(), fun(), binary()) -> boolean() when
200 Scram :: scram_map() | scram().
201 check_digest(Scram, Digest, DigestGen, Password) when is_record(Scram, scram) ->
202
:-(
ScramMap = scram_record_to_map(Scram),
203
:-(
check_digest(ScramMap, Digest, DigestGen, Password);
204 check_digest(ScramMap, Digest, DigestGen, Password) ->
205
:-(
do_check_digest(supported_sha_types(), ScramMap, Digest, DigestGen, Password).
206
207 do_check_digest([] , _, _, _, _) ->
208
:-(
false;
209 do_check_digest([{Sha,_Prefix} | RemainingSha], ScramMap, Digest, DigestGen, Password) ->
210
:-(
#{Sha := #{stored_key := StoredKey}} = ScramMap,
211
:-(
Passwd = base64:decode(StoredKey),
212
:-(
case ejabberd_auth:check_digest(Digest, DigestGen, Password, Passwd) of
213
:-(
true -> true;
214
:-(
false -> do_check_digest(RemainingSha, Digest, DigestGen, Password, Passwd)
215 end.
216
217 supported_sha_types() ->
218 90 [{sha, <<?SCRAM_SHA1_PREFIX>>},
219 {sha224, <<?SCRAM_SHA224_PREFIX>>},
220 {sha256, <<?SCRAM_SHA256_PREFIX>>},
221 {sha384, <<?SCRAM_SHA384_PREFIX>>},
222 {sha512, <<?SCRAM_SHA512_PREFIX>>}].
223
224 configured_sha_types(HostType) ->
225 33 case mongoose_config:lookup_opt([{auth, HostType}, password, hash]) of
226 {ok, ScramSha} ->
227 4 lists:filter(fun({Sha, _Prefix}) ->
228 20 lists:member(Sha, ScramSha) end, supported_sha_types());
229 29 _ -> supported_sha_types()
230 end.
Line Hits Source