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