_site/cover/msc_mm_auth.COVER.html

1 %% Copyright (c) 2023 Peter Morgan <peter.james.morgan@gmail.com>
2 %%
3 %% Licensed under the Apache License, Version 2.0 (the "License");
4 %% you may not use this file except in compliance with the License.
5 %% You may obtain a copy of the License at
6 %%
7 %% http://www.apache.org/licenses/LICENSE-2.0
8 %%
9 %% Unless required by applicable law or agreed to in writing, software
10 %% distributed under the License is distributed on an "AS IS" BASIS,
11 %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 %% See the License for the specific language governing permissions and
13 %% limitations under the License.
14
15 %% @doc Middleman dealing with the authentication process.
16
17 -module(msc_mm_auth).
18
19
20 -export([callback_mode/0]).
21 -export([handle_event/4]).
22 -import(msc_statem, [nei/1]).
23 -include_lib("kernel/include/logger.hrl").
24 -include_lib("public_key/include/public_key.hrl").
25
26
27 callback_mode() ->
28 2 handle_event_function.
29
30
31 handle_event({call, From}, {request, _}, {error, _} = Error, _) ->
32
:-(
{stop_and_reply, normal, {reply, From, Error}};
33
34 handle_event({call, _}, {request, _}, _, _) ->
35 2 {keep_state_and_data, postpone};
36
37
38 %% Receive a handshake from the server. If we agree on TLS, then reply
39 %% with a SSL request while upgrading the socket to TLS. Otherwise,
40 %% without TLS we proceed with a handshake response to initiate
41 %% authentication.
42 %%
43 handle_event(internal,
44 {recv,
45 #{packet := #{action := handshake,
46 character_set := CharacterSet,
47 operator := Operator,
48 auth_plugin_name := ClientPluginName} = Handshake,
49 sequence := Sequence} = Received},
50 _,
51 #{config := #{user := User,
52 database := Database,
53 password := Password}} = Data) ->
54
55 2 case client_flags(Handshake) of
56
57 #{ssl := true} = ClientFlags ->
58 %% We agree on TLS, reply with a SSL request packet, while
59 %% upgrading the socket to TLS.
60 %%
61 2 {next_state,
62 authenticating,
63 Data#{encoder => msmp_codec:encode(
64 msmp_ssl_request:encode()),
65 decoder => msmp_codec:decode(
66 scran_branch:alt(
67 [msmp_packet_ok:decode(ClientFlags),
68 msmp_auth_switch_request:decode(),
69 msmp_packet_error:decode(ClientFlags)])),
70 client_flags => ClientFlags,
71 character_set => CharacterSet,
72 operator => Operator,
73 handshake => Received},
74 [nei({send,
75 #{packet => maps:merge(
76 maybe_extended_capabilities(
77 Handshake),
78 #{action => ssl_request,
79 client_flags => ClientFlags,
80 max_packet_size => msc_config:maximum(packet_size),
81 character_set => CharacterSet}),
82 sequence => Sequence + 1}}),
83 nei(upgrade)]};
84
85 #{ssl := false} = ClientFlags ->
86 %% TLS is not acceptable, respond with a handshake to
87 %% initiate authentication.
88 %%
89
:-(
{next_state,
90 authenticating,
91 Data#{client_flags => ClientFlags,
92 character_set => CharacterSet,
93 operator => Operator,
94 handshake => Received,
95 encoder => msmp_codec:encode(
96 msmp_handshake_response:encode()),
97 decoder => msmp_codec:decode(
98 scran_branch:alt(
99 [msmp_packet_ok:decode(ClientFlags),
100 msmp_auth_switch_request:decode(),
101 msmp_auth_more_data:decode(),
102 msmp_packet_error:decode(ClientFlags)]))},
103 nei({send,
104 #{packet => maps:merge(
105 maybe_extended_capabilities(Handshake),
106 #{action => handshake_response,
107 client_flags => ClientFlags,
108 max_packet_size => msc_config:maximum(packet_size),
109 character_set => CharacterSet,
110 username => User,
111 auth_response => msmp_handshake_response:auth_response(
112 Handshake,
113 Password),
114 connect_attrs => connect_attrs(),
115 database => Database,
116 client_plugin_name => ClientPluginName}),
117 sequence => Sequence + 1}})}
118 end;
119
120 %% Stop if an error occurs while upgrading the socket to TLS
121 %%
122 handle_event(internal,
123 {response,
124 #{label := {msc_mm_common, upgrade},
125 reply := {error, Reason}}},
126 _,
127 _) ->
128
:-(
{stop, Reason};
129
130 %% The process of upgrading the socket to TLS has completed. Proceed
131 %% with a handshake response to complete authentication.
132 %%
133 handle_event(
134 internal,
135 {response,
136 #{label := {msc_mm_common, upgrade}, reply := ok}},
137 _,
138 #{handshake :=
139 #{packet :=
140 #{action := handshake,
141 character_set := CharacterSet,
142 auth_plugin_name := ClientPluginName} = Handshake,
143 sequence := Sequence},
144 character_set := CharacterSet,
145 client_flags := ClientFlags,
146 config := #{user := User,
147 database := Database,
148 password := Password}} = Data) ->
149 2 {keep_state,
150 Data#{client_flags => ClientFlags,
151 character_set => CharacterSet,
152 encoder => msmp_codec:encode(
153 msmp_handshake_response:encode()),
154 decoder => msmp_codec:decode(
155 scran_branch:alt(
156 [msmp_packet_ok:decode(ClientFlags),
157 msmp_auth_switch_request:decode(),
158 msmp_auth_more_data:decode(),
159 msmp_packet_error:decode(ClientFlags)]))},
160 nei({send,
161 #{packet => maps:merge(
162 maybe_extended_capabilities(Handshake),
163 #{action => handshake_response,
164 client_flags => ClientFlags,
165 max_packet_size => msc_config:maximum(packet_size),
166 character_set => CharacterSet,
167 username => User,
168 auth_response => msmp_handshake_response:auth_response(
169 Handshake,
170 Password),
171 connect_attrs => connect_attrs(),
172 database => Database,
173 client_plugin_name => ClientPluginName}),
174 sequence => Sequence + 2}})};
175
176
177 %% The server has responded with a request to perform full
178 %% authentication. We are using TLS, send the password to server in
179 %% plain text.
180 %%
181 handle_event(
182 internal,
183 {recv,
184 #{packet := #{status := perform_full_authentication,
185 action := auth_more_data},
186 sequence := Sequence}},
187 _,
188 #{handshake := #{packet := #{auth_plugin_name := caching_sha2_password}},
189 client_flags := #{ssl := true},
190 config := #{password := Password}} = Data) ->
191 1 {keep_state,
192 Data#{encoder := msmp_codec:encode(
193 msmp_string_null_terminated:encode())},
194 nei({send, #{sequence => Sequence + 1, packet => Password}})};
195
196
197 %% The server has responded with a request to perform full
198 %% authentication. We are not using TLS. Request a public key from the
199 %% server and use that to encrypt the password.
200 %%
201 handle_event(
202 internal,
203 {recv,
204 #{packet := #{status := perform_full_authentication,
205 action := auth_more_data},
206 sequence := Sequence}},
207 _,
208 #{handshake := #{packet := #{auth_plugin_name := caching_sha2_password}},
209 client_flags := #{ssl := false}} = Data) ->
210 %% Clear text: request public key from server so that we can
211 %% encrypt scrambled password.
212 %%
213 %% sql-common/client_authentication.cc
214 %%
215
:-(
{keep_state,
216 Data#{encoder := msmp_codec:encode(
217 msmp_integer_fixed:encode(1))},
218 nei({send, #{sequence => Sequence + 1, packet => 2}})};
219
220
221 %% As part of the perform full authentication process when without
222 %% TLS. We have received a public key from the server, which is used
223 %% to encrypt the password.
224 %%
225 handle_event(
226 internal,
227 {recv,
228 #{packet := #{public_key := PublicKey,
229 action := auth_more_data},
230 sequence := Sequence}},
231 _,
232 #{handshake := #{packet := #{auth_plugin_data_part_1 := PartOne,
233 auth_plugin_data_part_2 := <<PartTwo:12/bytes, 0>>,
234 auth_plugin_name := caching_sha2_password}},
235 config := #{password := Password},
236 client_flags := #{ssl := false}} = Data) ->
237 %% Clear text: scramble password with original handshake, encrypt
238 %% with public key.
239 %%
240 %% mysys/crypt_genhash_impl.cc
241 %% sql-common/client_authentication.cc
242 %%
243
:-(
{keep_state,
244 Data#{encoder := msmp_codec:encode(narcs_combinator:rest())},
245 nei({send,
246 #{sequence => Sequence + 1,
247 packet => public_key:encrypt_public(
248 exor(<<Password/bytes, 0>>,
249 <<PartOne/bytes, PartTwo/bytes>>),
250 pem_decode(PublicKey),
251 [{rsa_padding, rsa_pkcs1_oaep_padding}])}})};
252
253
254 %% An informational message from the server that fast authentication
255 %% has succeeded.
256 %%
257 handle_event(
258 internal,
259 {recv,
260 #{packet := #{status := fast_auth_success,
261 action := auth_more_data}}},
262 _,
263 #{handshake := #{packet := #{auth_plugin_name := caching_sha2_password}}}) ->
264 1 keep_state_and_data;
265
266
267 %% An OK from the server indicates that the authentication process has
268 %% completed successfully.
269 %%
270 handle_event(internal,
271 {recv, #{packet := #{action := ok}}},
272 authenticating,
273 Data) ->
274 2 {next_state,
275 authenticated,
276 maps:without([handshake], Data),
277 pop_callback_module};
278
279
280 %% The authentication process has not completed successfully.
281 handle_event(internal,
282 {recv, #{packet := #{action := error} = Packet}},
283 authenticating,
284 Data) ->
285
:-(
{next_state, {error, maps:without([action], Packet)}, Data};
286
287
288 %% The server is negotiating to use a different authentication plugin,
289 %% or client parameters.
290 %%
291 handle_event(
292 internal,
293 {recv,
294 #{packet := #{action := auth_switch_request,
295 plugin_name := PluginName} = AuthSwitchRequest,
296 sequence := Sequence}},
297 authenticating,
298 #{client_flags := ClientFlags,
299 character_set := CharacterSet,
300 handshake := #{packet := Handshake},
301 config := #{user := User,
302 database := Database,
303 password := Password}} = Data) ->
304
:-(
{keep_state,
305 Data#{decoder => msmp_codec:decode(
306 scran_branch:alt(
307 [msmp_packet_ok:decode(ClientFlags),
308 msmp_packet_error:decode(ClientFlags)]))},
309 nei({send,
310 #{packet => maps:merge(
311 maybe_extended_capabilities(Handshake),
312 #{action => handshake_response,
313 client_flags => ClientFlags,
314 max_packet_size => msc_config:maximum(packet_size),
315 character_set => CharacterSet,
316 username => User,
317 auth_response => msmp_handshake_response:auth_response(
318 AuthSwitchRequest,
319 Password),
320 connect_attrs => connect_attrs(),
321 database => Database,
322 client_plugin_name => PluginName}),
323 sequence => Sequence + 1}})};
324
325 handle_event(EventType, EventContent, State, Data) ->
326 92 msc_mm_common:handle_event(EventType,
327 EventContent,
328 State,
329 Data).
330
331
332 client_flags(#{capability_flags_1 := LowerFlags,
333 capability_flags_2 := UpperFlags}) ->
334 2 maps:map(
335 fun
336 (Name, Status) ->
337 64 Status andalso msc_config:client_flag(Name)
338 end,
339 maps:merge(LowerFlags, UpperFlags)).
340
341
342 connect_attrs() ->
343 2 #{<<"_client_name">> => <<"libmariadb">>,
344 <<"_client_version">> => <<"3.3.6">>,
345 <<"_os">> => <<"Linux">>,<<"_pid">> => <<"166">>,
346 <<"_platform">> => <<"aarch64">>,
347 <<"_server_host">> => <<"m0">>,
348 <<"program_name">> => <<"mysql">>}.
349
350
351 pem_decode([#'RSAPublicKey'{} = PublicKey]) ->
352
:-(
PublicKey;
353
354 pem_decode([#'SubjectPublicKeyInfo'{} = KeyInfo]) ->
355
:-(
public_key:pem_entry_decode(KeyInfo);
356
357 pem_decode(Encoded) ->
358
:-(
?FUNCTION_NAME(public_key:pem_decode(Encoded)).
359
360
361 exor(To, Pattern) when size(To) =< size(Pattern) ->
362
:-(
crypto:exor(To, binary:part(Pattern, {0, size(To)})).
363
364
365 maybe_extended_capabilities(#{extended_capabilities := _}) ->
366
:-(
#{extended_capabilities => 0};
367 maybe_extended_capabilities(#{}) ->
368 4 #{}.
Line Hits Source