./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 12299 fast_scram:salted_password(Sha, jid:resourceprep(Password), Salt, IterationCount).
57
58 enabled(HostType) ->
59 5450 mongoose_config:get_opt([{auth, HostType}, password, format]) =:= scram.
60
61 11867 enabled(HostType, cyrsasl_scram_sha1) -> is_password_format_allowed(HostType, sha);
62 11856 enabled(HostType, cyrsasl_scram_sha224) -> is_password_format_allowed(HostType, sha224);
63 11861 enabled(HostType, cyrsasl_scram_sha256) -> is_password_format_allowed(HostType, sha256);
64 11856 enabled(HostType, cyrsasl_scram_sha384) -> is_password_format_allowed(HostType, sha384);
65 11856 enabled(HostType, cyrsasl_scram_sha512) -> is_password_format_allowed(HostType, sha512);
66 11850 enabled(HostType, cyrsasl_scram_sha1_plus) -> is_password_format_allowed(HostType, sha);
67 11850 enabled(HostType, cyrsasl_scram_sha224_plus) -> is_password_format_allowed(HostType, sha224);
68 11850 enabled(HostType, cyrsasl_scram_sha256_plus) -> is_password_format_allowed(HostType, sha256);
69 11850 enabled(HostType, cyrsasl_scram_sha384_plus) -> is_password_format_allowed(HostType, sha384);
70 11850 enabled(HostType, cyrsasl_scram_sha512_plus) -> is_password_format_allowed(HostType, sha512);
71 11841 enabled(_HostType, _Mechanism) -> false.
72
73 is_password_format_allowed(HostType, Sha) ->
74 118546 case mongoose_config:get_opt([{auth, HostType}, password]) of
75 116805 #{format := scram, hash := ConfiguredSha} -> lists:member(Sha, ConfiguredSha);
76 1741 #{format := _PlainOrScram} -> true
77 end.
78
79 %% This function is exported and used from other modules
80 239 iterations() -> ?SCRAM_DEFAULT_ITERATION_COUNT.
81
82 iterations(HostType) ->
83 5411 mongoose_config:get_opt([{auth, HostType}, password, scram_iterations]).
84
85 password_to_scram_sha(Password, IterationCount, HashType) ->
86 31 ScramHash = do_password_to_scram(Password, IterationCount, HashType),
87 31 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 5411 ServerStoredKeys = [do_password_to_scram(Password, IterationCount, HashType)
96 5411 || {HashType, _Prefix} <- configured_sha_types(HostType)],
97 5411 ResultList = lists:merge([{iteration_count, IterationCount}], ServerStoredKeys),
98 5411 maps:from_list(ResultList).
99
100 do_password_to_scram(Password, IterationCount, HashType) ->
101 5602 Salt = crypto:strong_rand_bytes(?SALT_LENGTH),
102 5602 SaltedPassword = salted_password(HashType, Password, Salt, IterationCount),
103 5602 StoredKey = fast_scram:stored_key(HashType, fast_scram:client_key(HashType, SaltedPassword)),
104 5602 ServerKey = fast_scram:server_key(HashType, SaltedPassword),
105 5602 {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 6686 #{iteration_count := IterationCount} = ScramMap,
114 6686 [Sha | _] = [ShaKey || {ShaKey, _Prefix} <- supported_sha_types(),
115 33430 maps:is_key(ShaKey, ScramMap)],
116 6686 #{Sha := #{salt := Salt, stored_key := StoredKey}} = ScramMap,
117 6686 SaltedPassword = salted_password(Sha, Password, base64:decode(Salt), IterationCount),
118 6686 ClientStoredKey = fast_scram:stored_key(Sha, fast_scram:client_key(Sha, SaltedPassword)),
119 6686 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 5442 IterationCountBin = integer_to_binary(IterationCount),
129 5442 ConfigedSha = [{ShaKey, Prefix} || {ShaKey, Prefix} <- supported_sha_types(),
130 27210 maps:is_key(ShaKey, ScramMap)],
131 5442 Header = [?MULTI_SCRAM_SERIAL_PREFIX, IterationCountBin],
132 5442 do_serialize(Header, ScramMap, ConfigedSha).
133
134 do_serialize(Serialized, _ ,[]) ->
135 5442 erlang:iolist_to_binary(Serialized);
136 do_serialize(Header, ScramMap, [{Sha, Prefix} | RemainingSha]) ->
137 5602 #{Sha := #{salt := Salt,
138 server_key := ServerKey,
139 stored_key := StoredKey}} = ScramMap,
140 5602 ShaSerialization = [$, , Prefix, Salt, $|, StoredKey, $|, ServerKey],
141 5602 NewHeader = [Header | ShaSerialization],
142 5602 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 6768 case catch binary:split(Serialized, <<",">>, [global]) of
157 [IterationCountBin | ListOfShaSpecificDetails] ->
158 6768 IterationCount = binary_to_integer(IterationCountBin),
159 6768 DeserializedKeys = [deserialize(supported_sha_types(), ShaDetails)
160 6768 || ShaDetails <- ListOfShaSpecificDetails],
161 6768 ResultList = lists:merge([{iteration_count, IterationCount}],
162 lists:flatten(DeserializedKeys)),
163 6768 {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 7072 case catch binary:split(ShaDetails, <<"|">>, [global]) of
177 [Salt, StoredKey, ServerKey] ->
178 7072 {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 15078 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 24611 [{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 5411 case mongoose_config:lookup_opt([{auth, HostType}, password, hash]) of
226 {ok, ScramSha} ->
227 5371 lists:filter(fun({Sha, _Prefix}) ->
228 26855 lists:member(Sha, ScramSha) end, supported_sha_types());
229 40 _ -> supported_sha_types()
230 end.
Line Hits Source