/src/dovecot/src/lib-auth/auth-scram-server.c
Line | Count | Source |
1 | | /* |
2 | | * SCRAM-SHA-1 SASL authentication, see RFC-5802 |
3 | | * |
4 | | * Copyright (c) 2011-2016 Florian Zeitz <florob@babelmonkeys.de> |
5 | | * Copyright (c) 2022-2023 Dovecot Oy |
6 | | * |
7 | | * This software is released under the MIT license. |
8 | | */ |
9 | | |
10 | | #include "lib.h" |
11 | | #include "base64.h" |
12 | | #include "buffer.h" |
13 | | #include "hmac.h" |
14 | | #include "randgen.h" |
15 | | #include "safe-memset.h" |
16 | | #include "str.h" |
17 | | #include "strfuncs.h" |
18 | | #include "strnum.h" |
19 | | |
20 | | #include "auth-gs2.h" |
21 | | #include "auth-scram-server.h" |
22 | | |
23 | | /* s-nonce length */ |
24 | | #define SCRAM_SERVER_NONCE_LEN 64 |
25 | | |
26 | | static bool |
27 | | auth_scram_server_set_username(struct auth_scram_server *server, |
28 | | const char *username) |
29 | 1.44k | { |
30 | 1.44k | return server->backend->set_username(server, username); |
31 | 1.44k | } |
32 | | static bool |
33 | | auth_scram_server_set_login_username(struct auth_scram_server *server, |
34 | | const char *username) |
35 | 666 | { |
36 | 666 | return server->backend->set_login_username(server, username); |
37 | 666 | } |
38 | | |
39 | | static void |
40 | | auth_scram_server_start_channel_binding(struct auth_scram_server *server, |
41 | | const char *type) |
42 | 526 | { |
43 | 526 | i_assert(server->backend->start_channel_binding != NULL); |
44 | 526 | server->backend->start_channel_binding(server, type); |
45 | 526 | } |
46 | | |
47 | | static int |
48 | | auth_scram_server_accept_channel_binding(struct auth_scram_server *server, |
49 | | buffer_t **data_r) |
50 | 383 | { |
51 | 383 | i_assert(server->backend->accept_channel_binding != NULL); |
52 | 383 | return server->backend->accept_channel_binding(server, data_r); |
53 | 383 | } |
54 | | |
55 | | static int |
56 | | auth_scram_server_credentials_lookup(struct auth_scram_server *server) |
57 | 1.39k | { |
58 | 1.39k | const struct hash_method *hmethod = server->set.hash_method; |
59 | 1.39k | struct auth_scram_key_data *kdata = &server->key_data; |
60 | 1.39k | pool_t pool = server->pool; |
61 | | |
62 | 1.39k | i_zero(kdata); |
63 | 1.39k | kdata->pool = pool; |
64 | 1.39k | kdata->hmethod = hmethod; |
65 | 1.39k | kdata->stored_key = p_malloc(pool, hmethod->digest_size); |
66 | 1.39k | kdata->server_key = p_malloc(pool, hmethod->digest_size); |
67 | | |
68 | 1.39k | i_assert(server->backend->credentials_lookup != NULL); |
69 | 1.39k | return server->backend->credentials_lookup(server, kdata); |
70 | 1.39k | } |
71 | | |
72 | | void auth_scram_server_init(struct auth_scram_server *server_r, pool_t pool, |
73 | | const struct auth_scram_server_settings *set, |
74 | | const struct auth_scram_server_backend *backend) |
75 | 1.68k | { |
76 | 1.68k | pool_ref(pool); |
77 | | |
78 | 1.68k | i_assert(set->hash_method != NULL); |
79 | | |
80 | 1.68k | i_zero(server_r); |
81 | 1.68k | server_r->pool = pool; |
82 | 1.68k | server_r->set = *set; |
83 | 1.68k | server_r->backend = backend; |
84 | 1.68k | } |
85 | | |
86 | | void auth_scram_server_deinit(struct auth_scram_server *server) |
87 | 1.68k | { |
88 | 1.68k | i_assert(server->set.hash_method != NULL); |
89 | 1.68k | if (server->proof != NULL) |
90 | 764 | buffer_clear_safe(server->proof); |
91 | 1.68k | auth_scram_key_data_clear(&server->key_data); |
92 | 1.68k | pool_unref(&server->pool); |
93 | 1.68k | } |
94 | | |
95 | | static int |
96 | | auth_scram_parse_client_first(struct auth_scram_server *server, |
97 | | const unsigned char *data, size_t size, |
98 | | const char **username_r, |
99 | | const char **login_username_r, |
100 | | const char **error_r) |
101 | 1.56k | { |
102 | 1.56k | struct auth_gs2_header gs2_header; |
103 | 1.56k | const unsigned char *gs2_header_end; |
104 | 1.56k | const char *cfm_bare, *login_username = NULL, *username, *nonce; |
105 | 1.56k | const char *const *fields; |
106 | 1.56k | const char *error; |
107 | | |
108 | | /* RFC 5802, Section 7: |
109 | | |
110 | | client-first-message = gs2-header client-first-message-bare |
111 | | gs2-header = gs2-cbind-flag "," [ authzid ] "," |
112 | | gs2-cbind-flag = ("p=" cb-name) / "n" / "y" |
113 | | |
114 | | client-first-message-bare = [reserved-mext ","] |
115 | | username "," nonce ["," extensions] |
116 | | reserved-mext = "m=" 1*(value-char) |
117 | | |
118 | | username = "n=" saslname |
119 | | nonce = "r=" c-nonce [s-nonce] |
120 | | |
121 | | extensions = attr-val *("," attr-val) |
122 | | ;; All extensions are optional, |
123 | | ;; i.e., unrecognized attributes |
124 | | ;; not defined in this document |
125 | | ;; MUST be ignored. |
126 | | attr-val = ALPHA "=" value |
127 | | */ |
128 | | |
129 | 1.56k | if (auth_gs2_header_decode(data, size, FALSE, |
130 | 1.56k | &gs2_header, &gs2_header_end, &error) < 0) { |
131 | 69 | *error_r = t_strdup_printf("Invalid initial client message: %s", |
132 | 69 | error); |
133 | 69 | return -1; |
134 | 69 | } |
135 | | |
136 | 1.50k | size_t gs2_header_size = gs2_header_end - data; |
137 | 1.50k | size_t cfm_bare_size = size - gs2_header_size; |
138 | | |
139 | 1.50k | cfm_bare = t_strndup(gs2_header_end, cfm_bare_size); |
140 | 1.50k | fields = t_strsplit(cfm_bare, ","); |
141 | 1.50k | if (str_array_length(fields) < 2) { |
142 | 2 | *error_r = "Invalid initial client message: " |
143 | 2 | "Missing nonce field"; |
144 | 2 | return -1; |
145 | 2 | } |
146 | 1.49k | username = fields[0]; |
147 | 1.49k | nonce = fields[1]; |
148 | | |
149 | | /* gs2-cbind-flag = ("p=" cb-name) / "n" / "y" |
150 | | */ |
151 | 1.49k | enum auth_scram_cbind_server_support cbind_support = |
152 | 1.49k | server->set.cbind_support; |
153 | | |
154 | 1.49k | switch (gs2_header.cbind.status) { |
155 | 526 | case AUTH_GS2_CBIND_STATUS_PROVIDED: |
156 | 526 | if (cbind_support == AUTH_SCRAM_CBIND_SERVER_SUPPORT_NONE) { |
157 | 0 | *error_r = "Channel binding not supported"; |
158 | 0 | return -1; |
159 | 0 | } |
160 | 526 | auth_scram_server_start_channel_binding( |
161 | 526 | server, gs2_header.cbind.name); |
162 | 526 | break; |
163 | 1 | case AUTH_GS2_CBIND_STATUS_NO_SERVER_SUPPORT: |
164 | | /* RFC 5802, Section 6: |
165 | | |
166 | | If the flag is set to "y" and the server supports channel |
167 | | binding, the server MUST fail authentication. This is because |
168 | | if the client sets the channel binding flag to "y", then the |
169 | | client must have believed that the server did not support |
170 | | channel binding -- if the server did in fact support channel |
171 | | binding, then this is an indication that there has been a |
172 | | downgrade attack (e.g., an attacker changed the server's |
173 | | mechanism list to exclude the -PLUS suffixed SCRAM mechanism |
174 | | name(s)). |
175 | | */ |
176 | 1 | if (cbind_support != AUTH_SCRAM_CBIND_SERVER_SUPPORT_NONE) { |
177 | 1 | *error_r = "Potential downgrade attack detected"; |
178 | 1 | return -1; |
179 | 1 | } |
180 | 0 | break; |
181 | 971 | case AUTH_GS2_CBIND_STATUS_NO_CLIENT_SUPPORT: |
182 | 971 | if (cbind_support == AUTH_SCRAM_CBIND_SERVER_SUPPORT_REQUIRED) { |
183 | 0 | *error_r = "Channel binding required"; |
184 | 0 | return -1; |
185 | 0 | } |
186 | 971 | break; |
187 | 971 | default: |
188 | 0 | *error_r = "Invalid GS2 header"; |
189 | 0 | return -1; |
190 | 1.49k | } |
191 | | |
192 | | /* authzid = "a=" saslname |
193 | | ;; Protocol specific. |
194 | | */ |
195 | 1.49k | if (gs2_header.authzid != NULL) |
196 | 690 | login_username = gs2_header.authzid; |
197 | | |
198 | | /* reserved-mext = "m=" 1*(value-char) |
199 | | */ |
200 | 1.49k | if (username[0] == 'm') { |
201 | 1 | *error_r = "Mandatory extension(s) not supported"; |
202 | 1 | return -1; |
203 | 1 | } |
204 | | /* username = "n=" saslname |
205 | | */ |
206 | 1.49k | if (username[0] == 'n' && username[1] == '=') { |
207 | 1.48k | const char *uname_enc = username + 2; |
208 | 1.48k | size_t uname_enc_size = strlen(uname_enc); |
209 | | |
210 | | /* Unescape username */ |
211 | 1.48k | if (auth_gs2_decode_username((const unsigned char *)uname_enc, |
212 | 1.48k | uname_enc_size, &username) < 0) { |
213 | 18 | *error_r = "Username escaping is invalid"; |
214 | 18 | return -1; |
215 | 18 | } |
216 | 1.48k | } else { |
217 | 12 | *error_r = "Invalid username field"; |
218 | 12 | return -1; |
219 | 12 | } |
220 | | |
221 | | /* nonce = "r=" c-nonce [s-nonce] */ |
222 | 1.46k | if (nonce[0] == 'r' && nonce[1] == '=') |
223 | 1.44k | server->cnonce = p_strdup(server->pool, nonce+2); |
224 | 19 | else { |
225 | 19 | *error_r = "Invalid client nonce"; |
226 | 19 | return -1; |
227 | 19 | } |
228 | | |
229 | 1.44k | *username_r = username; |
230 | 1.44k | *login_username_r = login_username; |
231 | | |
232 | 1.44k | server->gs2_header = p_strndup(server->pool, data, gs2_header_size); |
233 | 1.44k | server->client_first_message_bare = p_strdup(server->pool, cfm_bare); |
234 | 1.44k | return 0; |
235 | 1.46k | } |
236 | | |
237 | | static string_t * |
238 | | auth_scram_get_server_first(struct auth_scram_server *server) |
239 | 1.39k | { |
240 | 1.39k | const struct hash_method *hmethod = server->set.hash_method; |
241 | 1.39k | struct auth_scram_key_data *kdata = &server->key_data; |
242 | 1.39k | unsigned char snonce[SCRAM_SERVER_NONCE_LEN+1]; |
243 | 1.39k | string_t *str; |
244 | 1.39k | size_t i; |
245 | | |
246 | | /* RFC 5802, Section 7: |
247 | | |
248 | | server-first-message = |
249 | | [reserved-mext ","] nonce "," salt "," |
250 | | iteration-count ["," extensions] |
251 | | |
252 | | nonce = "r=" c-nonce [s-nonce] |
253 | | |
254 | | salt = "s=" base64 |
255 | | |
256 | | iteration-count = "i=" posit-number |
257 | | ;; A positive number. |
258 | | */ |
259 | | |
260 | 1.39k | i_assert(kdata->pool == server->pool); |
261 | 1.39k | i_assert(kdata->hmethod == hmethod); |
262 | 1.39k | i_assert(kdata->salt != NULL); |
263 | 1.39k | i_assert(kdata->iter_count != 0); |
264 | | |
265 | 1.39k | random_fill(snonce, sizeof(snonce)-1); |
266 | | |
267 | | /* Make sure snonce is printable and does not contain ',' */ |
268 | 90.4k | for (i = 0; i < sizeof(snonce)-1; i++) { |
269 | 89.0k | snonce[i] = (snonce[i] % ('~' - '!')) + '!'; |
270 | 89.0k | if (snonce[i] == ',') |
271 | 0 | snonce[i] = '~'; |
272 | 89.0k | } |
273 | 1.39k | snonce[sizeof(snonce)-1] = '\0'; |
274 | 1.39k | server->snonce = p_strndup(server->pool, snonce, sizeof(snonce)); |
275 | | |
276 | 1.39k | str = t_str_new(32 + strlen(server->cnonce) + sizeof(snonce) + |
277 | 1.39k | strlen(kdata->salt)); |
278 | 1.39k | str_printfa(str, "r=%s%s,s=%s,i=%d", server->cnonce, server->snonce, |
279 | 1.39k | kdata->salt, kdata->iter_count); |
280 | | |
281 | 1.39k | server->server_first_message = p_strdup(server->pool, str_c(str)); |
282 | | |
283 | 1.39k | return str; |
284 | 1.39k | } |
285 | | |
286 | | static bool |
287 | | auth_scram_server_verify_credentials(struct auth_scram_server *server) |
288 | 716 | { |
289 | 716 | const struct hash_method *hmethod = server->set.hash_method; |
290 | 716 | struct auth_scram_key_data *kdata = &server->key_data; |
291 | 716 | struct hmac_context ctx; |
292 | 716 | const char *auth_message; |
293 | 716 | unsigned char client_key[hmethod->digest_size]; |
294 | 716 | unsigned char client_signature[hmethod->digest_size]; |
295 | 716 | unsigned char stored_key[hmethod->digest_size]; |
296 | 716 | size_t i; |
297 | | |
298 | 716 | i_assert(kdata->pool == server->pool); |
299 | 716 | i_assert(kdata->hmethod == hmethod); |
300 | | |
301 | | /* RFC 5802, Section 3: |
302 | | |
303 | | AuthMessage := client-first-message-bare + "," + |
304 | | server-first-message + "," + |
305 | | client-final-message-without-proof |
306 | | ClientSignature := HMAC(StoredKey, AuthMessage) |
307 | | */ |
308 | 716 | auth_message = t_strconcat(server->client_first_message_bare, ",", |
309 | 716 | server->server_first_message, ",", |
310 | 716 | server->client_final_message_without_proof, NULL); |
311 | | |
312 | 716 | hmac_init(&ctx, kdata->stored_key, hmethod->digest_size, hmethod); |
313 | 716 | hmac_update(&ctx, auth_message, strlen(auth_message)); |
314 | 716 | hmac_final(&ctx, client_signature); |
315 | | |
316 | | /* ClientProof := ClientKey XOR ClientSignature */ |
317 | 716 | const unsigned char *proof_data = server->proof->data; |
318 | 17.7k | for (i = 0; i < sizeof(client_signature); i++) |
319 | 16.9k | client_key[i] = proof_data[i] ^ client_signature[i]; |
320 | 716 | buffer_clear_safe(server->proof); |
321 | | |
322 | | /* StoredKey := H(ClientKey) */ |
323 | 716 | hash_method_get_digest(hmethod, client_key, sizeof(client_key), |
324 | 716 | stored_key); |
325 | | |
326 | 716 | safe_memset(client_key, 0, sizeof(client_key)); |
327 | 716 | safe_memset(client_signature, 0, sizeof(client_signature)); |
328 | | |
329 | 716 | return mem_equals_timing_safe(stored_key, kdata->stored_key, |
330 | 716 | sizeof(stored_key)); |
331 | 716 | } |
332 | | |
333 | | static int |
334 | | auth_scram_parse_client_final(struct auth_scram_server *server, |
335 | | const unsigned char *data, size_t size, |
336 | | const char **error_r) |
337 | 903 | { |
338 | 903 | const struct hash_method *hmethod = server->set.hash_method; |
339 | 903 | const char **fields, *nonce_str; |
340 | 903 | const void *cbind_input; |
341 | 903 | size_t cbind_input_size; |
342 | 903 | unsigned int field_count; |
343 | 903 | string_t *str; |
344 | | |
345 | | /* RFC 5802, Section 7: |
346 | | |
347 | | client-final-message-without-proof = |
348 | | channel-binding "," nonce ["," |
349 | | extensions] |
350 | | client-final-message = |
351 | | client-final-message-without-proof "," proof |
352 | | */ |
353 | 903 | fields = t_strsplit(t_strndup(data, size), ","); |
354 | 903 | field_count = str_array_length(fields); |
355 | 903 | if (field_count < 3) { |
356 | 17 | *error_r = "Invalid final client message"; |
357 | 17 | return -1; |
358 | 17 | } |
359 | | |
360 | | /* channel-binding = "c=" base64 |
361 | | ;; base64 encoding of cbind-input. |
362 | | |
363 | | cbind-data = 1*OCTET |
364 | | cbind-input = gs2-header [ cbind-data ] |
365 | | ;; cbind-data MUST be present for |
366 | | ;; gs2-cbind-flag of "p" and MUST be absent |
367 | | ;; for "y" or "n". |
368 | | */ |
369 | 886 | if (server->gs2_header[0] != 'p') { |
370 | 503 | cbind_input = server->gs2_header; |
371 | 503 | cbind_input_size = strlen(server->gs2_header); |
372 | 503 | } else { |
373 | 383 | buffer_t *cbind_data; |
374 | | |
375 | 383 | if (auth_scram_server_accept_channel_binding(server, |
376 | 383 | &cbind_data) < 0) { |
377 | 0 | *error_r = "Channel binding failed"; |
378 | 0 | return -1; |
379 | 0 | } |
380 | | |
381 | 383 | size_t gs2_header_len = strlen(server->gs2_header); |
382 | 383 | buffer_t *cbind_buf; |
383 | | |
384 | 383 | cbind_buf = t_buffer_create(gs2_header_len + cbind_data->used); |
385 | 383 | buffer_append(cbind_buf, server->gs2_header, gs2_header_len); |
386 | 383 | buffer_append_buf(cbind_buf, cbind_data, 0, SIZE_MAX); |
387 | 383 | cbind_input = cbind_buf->data; |
388 | 383 | cbind_input_size = cbind_buf->used; |
389 | 383 | } |
390 | 886 | str = t_str_new(2 + MAX_BASE64_ENCODED_SIZE(cbind_input_size)); |
391 | 886 | str_append(str, "c="); |
392 | 886 | base64_encode(cbind_input, cbind_input_size, str); |
393 | | |
394 | 886 | if (!str_equals_timing_almost_safe(fields[0], str_c(str))) { |
395 | 101 | *error_r = "Invalid channel binding data"; |
396 | 101 | return -1; |
397 | 101 | } |
398 | | |
399 | | /* nonce = "r=" c-nonce [s-nonce] |
400 | | ;; Second part provided by server. |
401 | | c-nonce = printable |
402 | | s-nonce = printable |
403 | | */ |
404 | 785 | nonce_str = t_strconcat("r=", server->cnonce, server->snonce, NULL); |
405 | 785 | if (!str_equals_timing_almost_safe(fields[1], nonce_str)) { |
406 | 11 | *error_r = "Wrong nonce"; |
407 | 11 | return -1; |
408 | 11 | } |
409 | | |
410 | | /* proof = "p=" base64 |
411 | | */ |
412 | 774 | if (fields[field_count-1][0] == 'p') { |
413 | 764 | size_t len = strlen(&fields[field_count-1][2]); |
414 | | |
415 | 764 | server->proof = buffer_create_dynamic(server->pool, |
416 | 764 | MAX_BASE64_DECODED_SIZE(len)); |
417 | 764 | if (base64_decode(&fields[field_count-1][2], len, |
418 | 764 | server->proof) < 0) { |
419 | 41 | *error_r = "Invalid base64 encoding"; |
420 | 41 | return -1; |
421 | 41 | } |
422 | 723 | if (server->proof->used != hmethod->digest_size) { |
423 | 7 | *error_r = "Invalid ClientProof length"; |
424 | 7 | return -1; |
425 | 7 | } |
426 | 723 | } else { |
427 | 10 | *error_r = "Invalid ClientProof"; |
428 | 10 | return -1; |
429 | 10 | } |
430 | | |
431 | 716 | (void)str_array_remove(fields, fields[field_count-1]); |
432 | 716 | server->client_final_message_without_proof = |
433 | 716 | p_strdup(server->pool, t_strarray_join(fields, ",")); |
434 | | |
435 | 716 | return 0; |
436 | 774 | } |
437 | | |
438 | | static string_t * |
439 | | auth_scram_get_server_final(struct auth_scram_server *server) |
440 | 373 | { |
441 | 373 | const struct hash_method *hmethod = server->set.hash_method; |
442 | 373 | struct auth_scram_key_data *kdata = &server->key_data; |
443 | 373 | struct hmac_context ctx; |
444 | 373 | const char *auth_message; |
445 | 373 | unsigned char server_signature[hmethod->digest_size]; |
446 | 373 | string_t *str; |
447 | | |
448 | | /* RFC 5802, Section 3: |
449 | | |
450 | | AuthMessage := client-first-message-bare + "," + |
451 | | server-first-message + "," + |
452 | | client-final-message-without-proof |
453 | | ServerSignature := HMAC(ServerKey, AuthMessage) |
454 | | */ |
455 | 373 | auth_message = t_strconcat(server->client_first_message_bare, ",", |
456 | 373 | server->server_first_message, ",", |
457 | 373 | server->client_final_message_without_proof, NULL); |
458 | | |
459 | 373 | hmac_init(&ctx, kdata->server_key, hmethod->digest_size, hmethod); |
460 | 373 | hmac_update(&ctx, auth_message, strlen(auth_message)); |
461 | 373 | hmac_final(&ctx, server_signature); |
462 | | |
463 | | /* RFC 5802, Section 7: |
464 | | |
465 | | server-final-message = (server-error / verifier) |
466 | | ["," extensions] |
467 | | |
468 | | verifier = "v=" base64 |
469 | | ;; base-64 encoded ServerSignature. |
470 | | |
471 | | */ |
472 | 373 | str = t_str_new(2 + MAX_BASE64_ENCODED_SIZE(sizeof(server_signature))); |
473 | 373 | str_append(str, "v="); |
474 | 373 | base64_encode(server_signature, sizeof(server_signature), str); |
475 | | |
476 | 373 | return str; |
477 | 373 | } |
478 | | |
479 | | static int |
480 | | auth_scram_parse_client_finish(struct auth_scram_server *server ATTR_UNUSED, |
481 | | const unsigned char *data ATTR_UNUSED, |
482 | | size_t size, const char **error_r) |
483 | 0 | { |
484 | 0 | if (size != 0) { |
485 | 0 | *error_r = "Spurious extra client message"; |
486 | 0 | return -1; |
487 | 0 | } |
488 | 0 | return 0; |
489 | 0 | } |
490 | | |
491 | | bool auth_scram_server_acces_granted(struct auth_scram_server *server) |
492 | 0 | { |
493 | 0 | return (server->state == AUTH_SCRAM_SERVER_STATE_SERVER_FINAL); |
494 | 0 | } |
495 | | |
496 | | static int |
497 | | auth_scram_server_input_client_first(struct auth_scram_server *server, |
498 | | const unsigned char *input, |
499 | | size_t input_len, |
500 | | enum auth_scram_server_error *error_code_r, |
501 | | const char **error_r) |
502 | 1.56k | { |
503 | 1.56k | const char *username, *login_username; |
504 | 1.56k | int ret; |
505 | | |
506 | 1.56k | username = login_username = NULL; |
507 | | |
508 | | /* Parse client-first message */ |
509 | 1.56k | ret = auth_scram_parse_client_first(server, input, input_len, |
510 | 1.56k | &username, &login_username, |
511 | 1.56k | error_r); |
512 | 1.56k | if (ret < 0) { |
513 | 122 | *error_code_r = AUTH_SCRAM_SERVER_ERROR_PROTOCOL_VIOLATION; |
514 | 122 | return -1; |
515 | 122 | } |
516 | | |
517 | | /* Pass usernames to backend */ |
518 | 1.56k | i_assert(username != NULL); |
519 | 1.44k | if (!auth_scram_server_set_username(server, username)) { |
520 | 26 | *error_r = "Bad username"; |
521 | 26 | *error_code_r = AUTH_SCRAM_SERVER_ERROR_BAD_USERNAME; |
522 | 26 | return -1; |
523 | 26 | } |
524 | 1.42k | if (login_username != NULL && |
525 | 666 | !auth_scram_server_set_login_username(server, login_username)) { |
526 | 26 | *error_r = "Bad login username"; |
527 | 26 | *error_code_r = AUTH_SCRAM_SERVER_ERROR_BAD_LOGIN_USERNAME; |
528 | 26 | return -1; |
529 | 26 | } |
530 | | |
531 | 1.39k | return 0; |
532 | 1.42k | } |
533 | | |
534 | | static int |
535 | | auth_scram_server_input_client_final(struct auth_scram_server *server, |
536 | | const unsigned char *input, |
537 | | size_t input_len, |
538 | | enum auth_scram_server_error *error_code_r, |
539 | | const char **error_r) |
540 | 903 | { |
541 | 903 | int ret; |
542 | | |
543 | | /* Parse client-final message */ |
544 | 903 | ret = auth_scram_parse_client_final(server, input, input_len, error_r); |
545 | 903 | if (ret < 0) { |
546 | 187 | *error_code_r = AUTH_SCRAM_SERVER_ERROR_PROTOCOL_VIOLATION; |
547 | 187 | return -1; |
548 | 187 | } |
549 | | |
550 | | /* Verify client credentials */ |
551 | 716 | if (!auth_scram_server_verify_credentials(server)) { |
552 | 343 | *error_code_r = AUTH_SCRAM_SERVER_ERROR_VERIFICATION_FAILED; |
553 | 343 | *error_r = "Password mismatch"; |
554 | 343 | return -1; |
555 | 343 | } |
556 | | |
557 | 373 | return 0; |
558 | 716 | } |
559 | | |
560 | | int auth_scram_server_input(struct auth_scram_server *server, |
561 | | const unsigned char *input, size_t input_len, |
562 | | enum auth_scram_server_error *error_code_r, |
563 | | const char **error_r) |
564 | 2.47k | { |
565 | 2.47k | struct auth_scram_key_data *kdata = &server->key_data; |
566 | 2.47k | int ret = 0; |
567 | | |
568 | 2.47k | *error_code_r = AUTH_SCRAM_SERVER_ERROR_NONE; |
569 | 2.47k | *error_r = NULL; |
570 | | |
571 | 2.47k | switch (server->state) { |
572 | 1.56k | case AUTH_SCRAM_SERVER_STATE_INIT: |
573 | 1.56k | server->state = AUTH_SCRAM_SERVER_STATE_CLIENT_FIRST; |
574 | | /* Fall through */ |
575 | 1.56k | case AUTH_SCRAM_SERVER_STATE_CLIENT_FIRST: |
576 | | /* Handle client-first message */ |
577 | 1.56k | ret = auth_scram_server_input_client_first( |
578 | 1.56k | server, input, input_len, error_code_r, error_r); |
579 | 1.56k | if (ret < 0) { |
580 | 174 | server->state = AUTH_SCRAM_SERVER_STATE_ERROR; |
581 | 174 | ret = -1; |
582 | 174 | break; |
583 | 174 | } |
584 | | |
585 | | /* Initiate credentials lookup */ |
586 | 1.39k | server->state = AUTH_SCRAM_SERVER_STATE_CREDENTIALS_LOOKUP; |
587 | 1.39k | if (auth_scram_server_credentials_lookup(server) < 0) { |
588 | 0 | *error_code_r = AUTH_SCRAM_SERVER_ERROR_LOOKUP_FAILED; |
589 | 0 | *error_r = "Credentials lookup failed"; |
590 | 0 | server->state = AUTH_SCRAM_SERVER_STATE_ERROR; |
591 | 0 | ret = -1; |
592 | 0 | break; |
593 | 0 | } |
594 | 1.39k | if (server->state == |
595 | 1.39k | AUTH_SCRAM_SERVER_STATE_CREDENTIALS_LOOKUP) { |
596 | 3 | server->state = AUTH_SCRAM_SERVER_STATE_SERVER_FIRST; |
597 | 3 | ret = (kdata->salt != NULL ? 1 : 0); |
598 | 3 | break; |
599 | 3 | } |
600 | 1.39k | i_assert(server->state >= AUTH_SCRAM_SERVER_STATE_SERVER_FIRST); |
601 | 1.39k | ret = 0; |
602 | 1.39k | break; |
603 | 0 | case AUTH_SCRAM_SERVER_STATE_CREDENTIALS_LOOKUP: |
604 | 0 | case AUTH_SCRAM_SERVER_STATE_SERVER_FIRST: |
605 | 0 | i_unreached(); |
606 | 903 | case AUTH_SCRAM_SERVER_STATE_CLIENT_FINAL: |
607 | | /* Handle client-final message */ |
608 | 903 | ret = auth_scram_server_input_client_final( |
609 | 903 | server, input, input_len, error_code_r, error_r); |
610 | 903 | if (ret < 0) { |
611 | 530 | server->state = AUTH_SCRAM_SERVER_STATE_ERROR; |
612 | 530 | break; |
613 | 530 | } |
614 | 373 | server->state = AUTH_SCRAM_SERVER_STATE_SERVER_FINAL; |
615 | 373 | ret = 1; |
616 | 373 | break; |
617 | 0 | case AUTH_SCRAM_SERVER_STATE_SERVER_FINAL: |
618 | 0 | i_unreached(); |
619 | 0 | case AUTH_SCRAM_SERVER_STATE_CLIENT_FINISH: |
620 | 0 | server->state = AUTH_SCRAM_SERVER_STATE_END; |
621 | 0 | ret = auth_scram_parse_client_finish(server, input, input_len, |
622 | 0 | error_r); |
623 | 0 | if (ret < 0) { |
624 | 0 | *error_code_r = |
625 | 0 | AUTH_SCRAM_SERVER_ERROR_PROTOCOL_VIOLATION; |
626 | 0 | server->state = AUTH_SCRAM_SERVER_STATE_ERROR; |
627 | 0 | } |
628 | 0 | break; |
629 | 0 | case AUTH_SCRAM_SERVER_STATE_END: |
630 | 0 | case AUTH_SCRAM_SERVER_STATE_ERROR: |
631 | 0 | i_unreached(); |
632 | 2.47k | } |
633 | | |
634 | 2.47k | return ret; |
635 | 2.47k | } |
636 | | |
637 | | bool auth_scram_server_output(struct auth_scram_server *server, |
638 | | const unsigned char **output_r, |
639 | | size_t *output_len_r) |
640 | 1.76k | { |
641 | 1.76k | struct auth_scram_key_data *kdata = &server->key_data; |
642 | 1.76k | string_t *output; |
643 | 1.76k | bool result = FALSE; |
644 | | |
645 | 1.76k | switch (server->state) { |
646 | 0 | case AUTH_SCRAM_SERVER_STATE_INIT: |
647 | 0 | *output_r = uchar_empty_ptr; |
648 | 0 | *output_len_r = 0; |
649 | 0 | server->state = AUTH_SCRAM_SERVER_STATE_CLIENT_FIRST; |
650 | 0 | break; |
651 | 0 | case AUTH_SCRAM_SERVER_STATE_CLIENT_FIRST: |
652 | 0 | i_unreached(); |
653 | 1.39k | case AUTH_SCRAM_SERVER_STATE_CREDENTIALS_LOOKUP: |
654 | 1.39k | i_assert(kdata->salt != NULL); |
655 | 1.39k | server->state = AUTH_SCRAM_SERVER_STATE_SERVER_FIRST; |
656 | | /* Fall through */ |
657 | 1.39k | case AUTH_SCRAM_SERVER_STATE_SERVER_FIRST: |
658 | | /* Compose server-first message */ |
659 | 1.39k | output = auth_scram_get_server_first(server); |
660 | 1.39k | *output_r = str_data(output); |
661 | 1.39k | *output_len_r = str_len(output); |
662 | 1.39k | server->state = AUTH_SCRAM_SERVER_STATE_CLIENT_FINAL; |
663 | 1.39k | break; |
664 | 0 | case AUTH_SCRAM_SERVER_STATE_CLIENT_FINAL: |
665 | 0 | i_unreached(); |
666 | 373 | case AUTH_SCRAM_SERVER_STATE_SERVER_FINAL: |
667 | | /* Compose server-final message */ |
668 | 373 | output = auth_scram_get_server_final(server); |
669 | 373 | *output_r = str_data(output); |
670 | 373 | *output_len_r = str_len(output); |
671 | 373 | server->state = AUTH_SCRAM_SERVER_STATE_CLIENT_FINISH; |
672 | 373 | result = TRUE; |
673 | 373 | break; |
674 | 0 | case AUTH_SCRAM_SERVER_STATE_CLIENT_FINISH: |
675 | 0 | case AUTH_SCRAM_SERVER_STATE_END: |
676 | 0 | case AUTH_SCRAM_SERVER_STATE_ERROR: |
677 | 0 | i_unreached(); |
678 | 1.76k | } |
679 | | |
680 | 1.76k | return result; |
681 | 1.76k | } |