_site/cover/pgmp_mm_auth_sasl.COVER.html

1 %% Copyright (c) 2022 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
16 -module(pgmp_mm_auth_sasl).
17
18
19 -export([callback_mode/0]).
20 -export([handle_event/4]).
21 -import(pgmp_codec, [marshal/2]).
22 -import(pgmp_codec, [size_inclusive/1]).
23 -import(pgmp_statem, [nei/1]).
24 -include_lib("kernel/include/logger.hrl").
25
26
27 callback_mode() ->
28 66 [handle_event_function, state_enter].
29
30
31 handle_event(internal, {recv, {authentication, authenticated}}, _, Data) ->
32 66 {next_state, authenticated, Data, pop_callback_module};
33
34 handle_event(internal, {recv, {error_response, Errors}}, _, Data) ->
35
:-(
{next_state,
36 startup_failure,
37 Data#{errors => Errors},
38 pop_callback_module};
39
40 %%
41 %% https://en.wikipedia.org/wiki/Salted_Challenge_Response_Authentication_Mechanism
42 %%
43
44 handle_event(internal,
45 {sasl = EventName, [<<"SCRAM-SHA-256">> = Mechanism | _]},
46 _,
47 Data) ->
48 66 {keep_state,
49 Data#{sasl => #{mechanism => Mechanism}},
50 [nei({telemetry, EventName, #{count => 1}, #{mechanism => Mechanism}}),
51
52 nei({send,
53 ["p",
54 size_inclusive(
55 [marshal(string, Mechanism), marshal(int32, -1)])]})]};
56
57 handle_event(internal,
58 {sasl = EventName, [<<"SCRAM-SHA-256-PLUS">> | Mechanisms]},
59 _,
60 _) ->
61 66 {keep_state_and_data, nei({EventName, Mechanisms})};
62
63 handle_event(internal,
64 {recv = EventName, {authentication = Tag, {Action, Encoded}}},
65 _,
66 #{sasl := #{mechanism := <<"SCRAM-SHA-256">>}})
67 when Action == sasl_continue; Action == sasl_final ->
68 198 {keep_state_and_data,
69 [nei({telemetry,
70 EventName,
71 #{count => 1},
72 #{tag => Tag, action => Action}}),
73
74 nei({Action, Encoded, pgmp_scram:decode(Encoded)})]};
75
76 handle_event(internal,
77 {sasl_final, _, #{v := V}},
78 _,
79 #{sasl := #{client := #{v := V}}}) ->
80 66 keep_state_and_data;
81
82 handle_event(
83 internal,
84 {sasl_continue,
85 ServerFirstMessage,
86 #{r := R, s := Salt, i := I} = Server},
87 _,
88 #{config := #{password := Password},
89 sasl := #{client := #{header := Header, nonce := Nonce} = Client,
90 mechanism := <<"SCRAM-SHA-256">> = Mechanism} = SASL} = Data) ->
91
92 %% SaltedPassword := Hi(Normalize(password), salt, i)
93 66 SaltedPassword = pgmp_scram:salted_password(
94 Mechanism,
95 pgmp_scram:normalize(Password()),
96 Salt,
97 I),
98
99 %% ClientKey := HMAC(SaltedPassword, "Client Key")
100 66 ClientKey = pgmp_scram:client_key(Mechanism, SaltedPassword),
101
102 %% StoredKey := H(ClientKey)
103 66 StoredKey = pgmp_scram:stored_key(Mechanism, ClientKey),
104
105 %% AuthMessage := client-first-message-bare + "," +
106 %% server-first-message + "," +
107 %% client-final-message-without-proof
108 66 ClientFirstBare = pgmp_scram:client_first_bare(<<>>, Nonce),
109
110 66 ClientFinalWithoutProof = pgmp_scram:client_final_without_proof(
111 Header,
112 R),
113
114 66 AuthMessage = pgmp_scram:auth_message(
115 ClientFirstBare,
116 ServerFirstMessage,
117 ClientFinalWithoutProof),
118
119 %% ClientSignature := HMAC(StoredKey, AuthMessage)
120 66 ClientSignature = pgmp_scram:client_signature(
121 Mechanism,
122 StoredKey,
123 AuthMessage),
124
125 %% ClientProof := ClientKey XOR ClientSignature
126 66 ClientProof = pgmp_scram:client_proof(
127 ClientKey,
128 ClientSignature),
129
130 %% ServerKey := HMAC(SaltedPassword, "Server Key")
131 66 ServerKey = pgmp_scram:server_key(Mechanism, SaltedPassword),
132
133 %% ServerSignature := HMAC(ServerKey, AuthMessage)
134 66 ServerSignature = pgmp_scram:server_signature(Mechanism, ServerKey, AuthMessage),
135
136 66 {keep_state,
137 Data#{sasl := SASL#{server => Server,
138 client := Client#{v => ServerSignature}}},
139 nei({send,
140 ["p",
141 size_inclusive(
142 [marshal(
143 byte,
144 pgmp_scram:client_final(
145 Header,
146 R,
147 ClientProof))])]})};
148
149 handle_event(internal,
150 {sasl_continue, <<>>, _},
151 _,
152 #{sasl := #{mechanism := <<"SCRAM-SHA-256">>} = SASL} = Data) ->
153 66 Header = "n,,",
154 66 Nonce = base64:encode(crypto:strong_rand_bytes(21)),
155 66 {keep_state,
156 Data#{sasl := SASL#{client => #{header => Header, nonce => Nonce}}},
157 nei({send,
158 ["p",
159 size_inclusive(
160 [marshal(
161 byte,
162 [Header,
163 pgmp_scram:client_first_bare(
164 <<>>,
165 Nonce)])])]})};
166
167 handle_event(EventType, EventContent, State, Data) ->
168 2112 pgmp_mm_common:handle_event(EventType,
169 EventContent,
170 State,
171 Data).
Line Hits Source