1 | /*------------------------------------------------------------------------- |
2 | * |
3 | * fe-auth-scram.c |
4 | * The front-end (client) implementation of SCRAM authentication. |
5 | * |
6 | * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group |
7 | * Portions Copyright (c) 1994, Regents of the University of California |
8 | * |
9 | * IDENTIFICATION |
10 | * src/interfaces/libpq/fe-auth-scram.c |
11 | * |
12 | *------------------------------------------------------------------------- |
13 | */ |
14 | |
15 | #include "postgres_fe.h" |
16 | |
17 | #include "common/base64.h" |
18 | #include "common/saslprep.h" |
19 | #include "common/scram-common.h" |
20 | #include "fe-auth.h" |
21 | |
22 | |
23 | /* |
24 | * Status of exchange messages used for SCRAM authentication via the |
25 | * SASL protocol. |
26 | */ |
27 | typedef enum |
28 | { |
29 | FE_SCRAM_INIT, |
30 | FE_SCRAM_NONCE_SENT, |
31 | FE_SCRAM_PROOF_SENT, |
32 | FE_SCRAM_FINISHED |
33 | } fe_scram_state_enum; |
34 | |
35 | typedef struct |
36 | { |
37 | fe_scram_state_enum state; |
38 | |
39 | /* These are supplied by the user */ |
40 | PGconn *conn; |
41 | char *password; |
42 | char *sasl_mechanism; |
43 | |
44 | /* We construct these */ |
45 | uint8 SaltedPassword[SCRAM_KEY_LEN]; |
46 | char *client_nonce; |
47 | char *client_first_message_bare; |
48 | char *client_final_message_without_proof; |
49 | |
50 | /* These come from the server-first message */ |
51 | char *server_first_message; |
52 | char *salt; |
53 | int saltlen; |
54 | int iterations; |
55 | char *nonce; |
56 | |
57 | /* These come from the server-final message */ |
58 | char *server_final_message; |
59 | char ServerSignature[SCRAM_KEY_LEN]; |
60 | } fe_scram_state; |
61 | |
62 | static bool read_server_first_message(fe_scram_state *state, char *input); |
63 | static bool read_server_final_message(fe_scram_state *state, char *input); |
64 | static char *build_client_first_message(fe_scram_state *state); |
65 | static char *build_client_final_message(fe_scram_state *state); |
66 | static bool verify_server_signature(fe_scram_state *state); |
67 | static void calculate_client_proof(fe_scram_state *state, |
68 | const char *client_final_message_without_proof, |
69 | uint8 *result); |
70 | |
71 | /* |
72 | * Initialize SCRAM exchange status. |
73 | */ |
74 | void * |
75 | pg_fe_scram_init(PGconn *conn, |
76 | const char *password, |
77 | const char *sasl_mechanism) |
78 | { |
79 | fe_scram_state *state; |
80 | char *prep_password; |
81 | pg_saslprep_rc rc; |
82 | |
83 | Assert(sasl_mechanism != NULL); |
84 | |
85 | state = (fe_scram_state *) malloc(sizeof(fe_scram_state)); |
86 | if (!state) |
87 | return NULL; |
88 | memset(state, 0, sizeof(fe_scram_state)); |
89 | state->conn = conn; |
90 | state->state = FE_SCRAM_INIT; |
91 | state->sasl_mechanism = strdup(sasl_mechanism); |
92 | |
93 | if (!state->sasl_mechanism) |
94 | { |
95 | free(state); |
96 | return NULL; |
97 | } |
98 | |
99 | /* Normalize the password with SASLprep, if possible */ |
100 | rc = pg_saslprep(password, &prep_password); |
101 | if (rc == SASLPREP_OOM) |
102 | { |
103 | free(state->sasl_mechanism); |
104 | free(state); |
105 | return NULL; |
106 | } |
107 | if (rc != SASLPREP_SUCCESS) |
108 | { |
109 | prep_password = strdup(password); |
110 | if (!prep_password) |
111 | { |
112 | free(state->sasl_mechanism); |
113 | free(state); |
114 | return NULL; |
115 | } |
116 | } |
117 | state->password = prep_password; |
118 | |
119 | return state; |
120 | } |
121 | |
122 | /* |
123 | * Free SCRAM exchange status |
124 | */ |
125 | void |
126 | pg_fe_scram_free(void *opaq) |
127 | { |
128 | fe_scram_state *state = (fe_scram_state *) opaq; |
129 | |
130 | if (state->password) |
131 | free(state->password); |
132 | if (state->sasl_mechanism) |
133 | free(state->sasl_mechanism); |
134 | |
135 | /* client messages */ |
136 | if (state->client_nonce) |
137 | free(state->client_nonce); |
138 | if (state->client_first_message_bare) |
139 | free(state->client_first_message_bare); |
140 | if (state->client_final_message_without_proof) |
141 | free(state->client_final_message_without_proof); |
142 | |
143 | /* first message from server */ |
144 | if (state->server_first_message) |
145 | free(state->server_first_message); |
146 | if (state->salt) |
147 | free(state->salt); |
148 | if (state->nonce) |
149 | free(state->nonce); |
150 | |
151 | /* final message from server */ |
152 | if (state->server_final_message) |
153 | free(state->server_final_message); |
154 | |
155 | free(state); |
156 | } |
157 | |
158 | /* |
159 | * Exchange a SCRAM message with backend. |
160 | */ |
161 | void |
162 | pg_fe_scram_exchange(void *opaq, char *input, int inputlen, |
163 | char **output, int *outputlen, |
164 | bool *done, bool *success) |
165 | { |
166 | fe_scram_state *state = (fe_scram_state *) opaq; |
167 | PGconn *conn = state->conn; |
168 | |
169 | *done = false; |
170 | *success = false; |
171 | *output = NULL; |
172 | *outputlen = 0; |
173 | |
174 | /* |
175 | * Check that the input length agrees with the string length of the input. |
176 | * We can ignore inputlen after this. |
177 | */ |
178 | if (state->state != FE_SCRAM_INIT) |
179 | { |
180 | if (inputlen == 0) |
181 | { |
182 | printfPQExpBuffer(&conn->errorMessage, |
183 | libpq_gettext("malformed SCRAM message (empty message)\n" )); |
184 | goto error; |
185 | } |
186 | if (inputlen != strlen(input)) |
187 | { |
188 | printfPQExpBuffer(&conn->errorMessage, |
189 | libpq_gettext("malformed SCRAM message (length mismatch)\n" )); |
190 | goto error; |
191 | } |
192 | } |
193 | |
194 | switch (state->state) |
195 | { |
196 | case FE_SCRAM_INIT: |
197 | /* Begin the SCRAM handshake, by sending client nonce */ |
198 | *output = build_client_first_message(state); |
199 | if (*output == NULL) |
200 | goto error; |
201 | |
202 | *outputlen = strlen(*output); |
203 | *done = false; |
204 | state->state = FE_SCRAM_NONCE_SENT; |
205 | break; |
206 | |
207 | case FE_SCRAM_NONCE_SENT: |
208 | /* Receive salt and server nonce, send response. */ |
209 | if (!read_server_first_message(state, input)) |
210 | goto error; |
211 | |
212 | *output = build_client_final_message(state); |
213 | if (*output == NULL) |
214 | goto error; |
215 | |
216 | *outputlen = strlen(*output); |
217 | *done = false; |
218 | state->state = FE_SCRAM_PROOF_SENT; |
219 | break; |
220 | |
221 | case FE_SCRAM_PROOF_SENT: |
222 | /* Receive server signature */ |
223 | if (!read_server_final_message(state, input)) |
224 | goto error; |
225 | |
226 | /* |
227 | * Verify server signature, to make sure we're talking to the |
228 | * genuine server. XXX: A fake server could simply not require |
229 | * authentication, though. There is currently no option in libpq |
230 | * to reject a connection, if SCRAM authentication did not happen. |
231 | */ |
232 | if (verify_server_signature(state)) |
233 | *success = true; |
234 | else |
235 | { |
236 | *success = false; |
237 | printfPQExpBuffer(&conn->errorMessage, |
238 | libpq_gettext("incorrect server signature\n" )); |
239 | } |
240 | *done = true; |
241 | state->state = FE_SCRAM_FINISHED; |
242 | break; |
243 | |
244 | default: |
245 | /* shouldn't happen */ |
246 | printfPQExpBuffer(&conn->errorMessage, |
247 | libpq_gettext("invalid SCRAM exchange state\n" )); |
248 | goto error; |
249 | } |
250 | return; |
251 | |
252 | error: |
253 | *done = true; |
254 | *success = false; |
255 | return; |
256 | } |
257 | |
258 | /* |
259 | * Read value for an attribute part of a SCRAM message. |
260 | */ |
261 | static char * |
262 | read_attr_value(char **input, char attr, PQExpBuffer errorMessage) |
263 | { |
264 | char *begin = *input; |
265 | char *end; |
266 | |
267 | if (*begin != attr) |
268 | { |
269 | printfPQExpBuffer(errorMessage, |
270 | libpq_gettext("malformed SCRAM message (attribute \"%c\" expected)\n" ), |
271 | attr); |
272 | return NULL; |
273 | } |
274 | begin++; |
275 | |
276 | if (*begin != '=') |
277 | { |
278 | printfPQExpBuffer(errorMessage, |
279 | libpq_gettext("malformed SCRAM message (expected character \"=\" for attribute \"%c\")\n" ), |
280 | attr); |
281 | return NULL; |
282 | } |
283 | begin++; |
284 | |
285 | end = begin; |
286 | while (*end && *end != ',') |
287 | end++; |
288 | |
289 | if (*end) |
290 | { |
291 | *end = '\0'; |
292 | *input = end + 1; |
293 | } |
294 | else |
295 | *input = end; |
296 | |
297 | return begin; |
298 | } |
299 | |
300 | /* |
301 | * Build the first exchange message sent by the client. |
302 | */ |
303 | static char * |
304 | build_client_first_message(fe_scram_state *state) |
305 | { |
306 | PGconn *conn = state->conn; |
307 | char raw_nonce[SCRAM_RAW_NONCE_LEN + 1]; |
308 | char *result; |
309 | int channel_info_len; |
310 | int encoded_len; |
311 | PQExpBufferData buf; |
312 | |
313 | /* |
314 | * Generate a "raw" nonce. This is converted to ASCII-printable form by |
315 | * base64-encoding it. |
316 | */ |
317 | if (!pg_strong_random(raw_nonce, SCRAM_RAW_NONCE_LEN)) |
318 | { |
319 | printfPQExpBuffer(&conn->errorMessage, |
320 | libpq_gettext("could not generate nonce\n" )); |
321 | return NULL; |
322 | } |
323 | |
324 | state->client_nonce = malloc(pg_b64_enc_len(SCRAM_RAW_NONCE_LEN) + 1); |
325 | if (state->client_nonce == NULL) |
326 | { |
327 | printfPQExpBuffer(&conn->errorMessage, |
328 | libpq_gettext("out of memory\n" )); |
329 | return NULL; |
330 | } |
331 | encoded_len = pg_b64_encode(raw_nonce, SCRAM_RAW_NONCE_LEN, state->client_nonce); |
332 | state->client_nonce[encoded_len] = '\0'; |
333 | |
334 | /* |
335 | * Generate message. The username is left empty as the backend uses the |
336 | * value provided by the startup packet. Also, as this username is not |
337 | * prepared with SASLprep, the message parsing would fail if it includes |
338 | * '=' or ',' characters. |
339 | */ |
340 | |
341 | initPQExpBuffer(&buf); |
342 | |
343 | /* |
344 | * First build the gs2-header with channel binding information. |
345 | */ |
346 | if (strcmp(state->sasl_mechanism, SCRAM_SHA_256_PLUS_NAME) == 0) |
347 | { |
348 | Assert(conn->ssl_in_use); |
349 | appendPQExpBuffer(&buf, "p=tls-server-end-point" ); |
350 | } |
351 | #ifdef HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH |
352 | else if (conn->ssl_in_use) |
353 | { |
354 | /* |
355 | * Client supports channel binding, but thinks the server does not. |
356 | */ |
357 | appendPQExpBuffer(&buf, "y" ); |
358 | } |
359 | #endif |
360 | else |
361 | { |
362 | /* |
363 | * Client does not support channel binding. |
364 | */ |
365 | appendPQExpBuffer(&buf, "n" ); |
366 | } |
367 | |
368 | if (PQExpBufferDataBroken(buf)) |
369 | goto oom_error; |
370 | |
371 | channel_info_len = buf.len; |
372 | |
373 | appendPQExpBuffer(&buf, ",,n=,r=%s" , state->client_nonce); |
374 | if (PQExpBufferDataBroken(buf)) |
375 | goto oom_error; |
376 | |
377 | /* |
378 | * The first message content needs to be saved without channel binding |
379 | * information. |
380 | */ |
381 | state->client_first_message_bare = strdup(buf.data + channel_info_len + 2); |
382 | if (!state->client_first_message_bare) |
383 | goto oom_error; |
384 | |
385 | result = strdup(buf.data); |
386 | if (result == NULL) |
387 | goto oom_error; |
388 | |
389 | termPQExpBuffer(&buf); |
390 | return result; |
391 | |
392 | oom_error: |
393 | termPQExpBuffer(&buf); |
394 | printfPQExpBuffer(&conn->errorMessage, |
395 | libpq_gettext("out of memory\n" )); |
396 | return NULL; |
397 | } |
398 | |
399 | /* |
400 | * Build the final exchange message sent from the client. |
401 | */ |
402 | static char * |
403 | build_client_final_message(fe_scram_state *state) |
404 | { |
405 | PQExpBufferData buf; |
406 | PGconn *conn = state->conn; |
407 | uint8 client_proof[SCRAM_KEY_LEN]; |
408 | char *result; |
409 | |
410 | initPQExpBuffer(&buf); |
411 | |
412 | /* |
413 | * Construct client-final-message-without-proof. We need to remember it |
414 | * for verifying the server proof in the final step of authentication. |
415 | * |
416 | * The channel binding flag handling (p/y/n) must be consistent with |
417 | * build_client_first_message(), because the server will check that it's |
418 | * the same flag both times. |
419 | */ |
420 | if (strcmp(state->sasl_mechanism, SCRAM_SHA_256_PLUS_NAME) == 0) |
421 | { |
422 | #ifdef HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH |
423 | char *cbind_data = NULL; |
424 | size_t cbind_data_len = 0; |
425 | size_t cbind_header_len; |
426 | char *cbind_input; |
427 | size_t cbind_input_len; |
428 | |
429 | /* Fetch hash data of server's SSL certificate */ |
430 | cbind_data = |
431 | pgtls_get_peer_certificate_hash(state->conn, |
432 | &cbind_data_len); |
433 | if (cbind_data == NULL) |
434 | { |
435 | /* error message is already set on error */ |
436 | termPQExpBuffer(&buf); |
437 | return NULL; |
438 | } |
439 | |
440 | appendPQExpBuffer(&buf, "c=" ); |
441 | |
442 | /* p=type,, */ |
443 | cbind_header_len = strlen("p=tls-server-end-point,," ); |
444 | cbind_input_len = cbind_header_len + cbind_data_len; |
445 | cbind_input = malloc(cbind_input_len); |
446 | if (!cbind_input) |
447 | { |
448 | free(cbind_data); |
449 | goto oom_error; |
450 | } |
451 | memcpy(cbind_input, "p=tls-server-end-point,," , cbind_header_len); |
452 | memcpy(cbind_input + cbind_header_len, cbind_data, cbind_data_len); |
453 | |
454 | if (!enlargePQExpBuffer(&buf, pg_b64_enc_len(cbind_input_len))) |
455 | { |
456 | free(cbind_data); |
457 | free(cbind_input); |
458 | goto oom_error; |
459 | } |
460 | buf.len += pg_b64_encode(cbind_input, cbind_input_len, buf.data + buf.len); |
461 | buf.data[buf.len] = '\0'; |
462 | |
463 | free(cbind_data); |
464 | free(cbind_input); |
465 | #else |
466 | /* |
467 | * Chose channel binding, but the SSL library doesn't support it. |
468 | * Shouldn't happen. |
469 | */ |
470 | termPQExpBuffer(&buf); |
471 | printfPQExpBuffer(&conn->errorMessage, |
472 | "channel binding not supported by this build\n" ); |
473 | return NULL; |
474 | #endif /* HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH */ |
475 | } |
476 | #ifdef HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH |
477 | else if (conn->ssl_in_use) |
478 | appendPQExpBuffer(&buf, "c=eSws" ); /* base64 of "y,," */ |
479 | #endif |
480 | else |
481 | appendPQExpBuffer(&buf, "c=biws" ); /* base64 of "n,," */ |
482 | |
483 | if (PQExpBufferDataBroken(buf)) |
484 | goto oom_error; |
485 | |
486 | appendPQExpBuffer(&buf, ",r=%s" , state->nonce); |
487 | if (PQExpBufferDataBroken(buf)) |
488 | goto oom_error; |
489 | |
490 | state->client_final_message_without_proof = strdup(buf.data); |
491 | if (state->client_final_message_without_proof == NULL) |
492 | goto oom_error; |
493 | |
494 | /* Append proof to it, to form client-final-message. */ |
495 | calculate_client_proof(state, |
496 | state->client_final_message_without_proof, |
497 | client_proof); |
498 | |
499 | appendPQExpBuffer(&buf, ",p=" ); |
500 | if (!enlargePQExpBuffer(&buf, pg_b64_enc_len(SCRAM_KEY_LEN))) |
501 | goto oom_error; |
502 | buf.len += pg_b64_encode((char *) client_proof, |
503 | SCRAM_KEY_LEN, |
504 | buf.data + buf.len); |
505 | buf.data[buf.len] = '\0'; |
506 | |
507 | result = strdup(buf.data); |
508 | if (result == NULL) |
509 | goto oom_error; |
510 | |
511 | termPQExpBuffer(&buf); |
512 | return result; |
513 | |
514 | oom_error: |
515 | termPQExpBuffer(&buf); |
516 | printfPQExpBuffer(&conn->errorMessage, |
517 | libpq_gettext("out of memory\n" )); |
518 | return NULL; |
519 | } |
520 | |
521 | /* |
522 | * Read the first exchange message coming from the server. |
523 | */ |
524 | static bool |
525 | read_server_first_message(fe_scram_state *state, char *input) |
526 | { |
527 | PGconn *conn = state->conn; |
528 | char *iterations_str; |
529 | char *endptr; |
530 | char *encoded_salt; |
531 | char *nonce; |
532 | |
533 | state->server_first_message = strdup(input); |
534 | if (state->server_first_message == NULL) |
535 | { |
536 | printfPQExpBuffer(&conn->errorMessage, |
537 | libpq_gettext("out of memory\n" )); |
538 | return false; |
539 | } |
540 | |
541 | /* parse the message */ |
542 | nonce = read_attr_value(&input, 'r', |
543 | &conn->errorMessage); |
544 | if (nonce == NULL) |
545 | { |
546 | /* read_attr_value() has generated an error string */ |
547 | return false; |
548 | } |
549 | |
550 | /* Verify immediately that the server used our part of the nonce */ |
551 | if (strlen(nonce) < strlen(state->client_nonce) || |
552 | memcmp(nonce, state->client_nonce, strlen(state->client_nonce)) != 0) |
553 | { |
554 | printfPQExpBuffer(&conn->errorMessage, |
555 | libpq_gettext("invalid SCRAM response (nonce mismatch)\n" )); |
556 | return false; |
557 | } |
558 | |
559 | state->nonce = strdup(nonce); |
560 | if (state->nonce == NULL) |
561 | { |
562 | printfPQExpBuffer(&conn->errorMessage, |
563 | libpq_gettext("out of memory\n" )); |
564 | return false; |
565 | } |
566 | |
567 | encoded_salt = read_attr_value(&input, 's', &conn->errorMessage); |
568 | if (encoded_salt == NULL) |
569 | { |
570 | /* read_attr_value() has generated an error string */ |
571 | return false; |
572 | } |
573 | state->salt = malloc(pg_b64_dec_len(strlen(encoded_salt))); |
574 | if (state->salt == NULL) |
575 | { |
576 | printfPQExpBuffer(&conn->errorMessage, |
577 | libpq_gettext("out of memory\n" )); |
578 | return false; |
579 | } |
580 | state->saltlen = pg_b64_decode(encoded_salt, |
581 | strlen(encoded_salt), |
582 | state->salt); |
583 | if (state->saltlen < 0) |
584 | { |
585 | printfPQExpBuffer(&conn->errorMessage, |
586 | libpq_gettext("malformed SCRAM message (invalid salt)\n" )); |
587 | return false; |
588 | } |
589 | |
590 | iterations_str = read_attr_value(&input, 'i', &conn->errorMessage); |
591 | if (iterations_str == NULL) |
592 | { |
593 | /* read_attr_value() has generated an error string */ |
594 | return false; |
595 | } |
596 | state->iterations = strtol(iterations_str, &endptr, 10); |
597 | if (*endptr != '\0' || state->iterations < 1) |
598 | { |
599 | printfPQExpBuffer(&conn->errorMessage, |
600 | libpq_gettext("malformed SCRAM message (invalid iteration count)\n" )); |
601 | return false; |
602 | } |
603 | |
604 | if (*input != '\0') |
605 | printfPQExpBuffer(&conn->errorMessage, |
606 | libpq_gettext("malformed SCRAM message (garbage at end of server-first-message)\n" )); |
607 | |
608 | return true; |
609 | } |
610 | |
611 | /* |
612 | * Read the final exchange message coming from the server. |
613 | */ |
614 | static bool |
615 | read_server_final_message(fe_scram_state *state, char *input) |
616 | { |
617 | PGconn *conn = state->conn; |
618 | char *encoded_server_signature; |
619 | char *decoded_server_signature; |
620 | int server_signature_len; |
621 | |
622 | state->server_final_message = strdup(input); |
623 | if (!state->server_final_message) |
624 | { |
625 | printfPQExpBuffer(&conn->errorMessage, |
626 | libpq_gettext("out of memory\n" )); |
627 | return false; |
628 | } |
629 | |
630 | /* Check for error result. */ |
631 | if (*input == 'e') |
632 | { |
633 | char *errmsg = read_attr_value(&input, 'e', |
634 | &conn->errorMessage); |
635 | |
636 | printfPQExpBuffer(&conn->errorMessage, |
637 | libpq_gettext("error received from server in SCRAM exchange: %s\n" ), |
638 | errmsg); |
639 | return false; |
640 | } |
641 | |
642 | /* Parse the message. */ |
643 | encoded_server_signature = read_attr_value(&input, 'v', |
644 | &conn->errorMessage); |
645 | if (encoded_server_signature == NULL) |
646 | { |
647 | /* read_attr_value() has generated an error message */ |
648 | return false; |
649 | } |
650 | |
651 | if (*input != '\0') |
652 | printfPQExpBuffer(&conn->errorMessage, |
653 | libpq_gettext("malformed SCRAM message (garbage at end of server-final-message)\n" )); |
654 | |
655 | server_signature_len = pg_b64_dec_len(strlen(encoded_server_signature)); |
656 | decoded_server_signature = malloc(server_signature_len); |
657 | if (!decoded_server_signature) |
658 | { |
659 | printfPQExpBuffer(&conn->errorMessage, |
660 | libpq_gettext("out of memory\n" )); |
661 | return false; |
662 | } |
663 | |
664 | server_signature_len = pg_b64_decode(encoded_server_signature, |
665 | strlen(encoded_server_signature), |
666 | decoded_server_signature); |
667 | if (server_signature_len != SCRAM_KEY_LEN) |
668 | { |
669 | free(decoded_server_signature); |
670 | printfPQExpBuffer(&conn->errorMessage, |
671 | libpq_gettext("malformed SCRAM message (invalid server signature)\n" )); |
672 | return false; |
673 | } |
674 | memcpy(state->ServerSignature, decoded_server_signature, SCRAM_KEY_LEN); |
675 | free(decoded_server_signature); |
676 | |
677 | return true; |
678 | } |
679 | |
680 | /* |
681 | * Calculate the client proof, part of the final exchange message sent |
682 | * by the client. |
683 | */ |
684 | static void |
685 | calculate_client_proof(fe_scram_state *state, |
686 | const char *client_final_message_without_proof, |
687 | uint8 *result) |
688 | { |
689 | uint8 StoredKey[SCRAM_KEY_LEN]; |
690 | uint8 ClientKey[SCRAM_KEY_LEN]; |
691 | uint8 ClientSignature[SCRAM_KEY_LEN]; |
692 | int i; |
693 | scram_HMAC_ctx ctx; |
694 | |
695 | /* |
696 | * Calculate SaltedPassword, and store it in 'state' so that we can reuse |
697 | * it later in verify_server_signature. |
698 | */ |
699 | scram_SaltedPassword(state->password, state->salt, state->saltlen, |
700 | state->iterations, state->SaltedPassword); |
701 | |
702 | scram_ClientKey(state->SaltedPassword, ClientKey); |
703 | scram_H(ClientKey, SCRAM_KEY_LEN, StoredKey); |
704 | |
705 | scram_HMAC_init(&ctx, StoredKey, SCRAM_KEY_LEN); |
706 | scram_HMAC_update(&ctx, |
707 | state->client_first_message_bare, |
708 | strlen(state->client_first_message_bare)); |
709 | scram_HMAC_update(&ctx, "," , 1); |
710 | scram_HMAC_update(&ctx, |
711 | state->server_first_message, |
712 | strlen(state->server_first_message)); |
713 | scram_HMAC_update(&ctx, "," , 1); |
714 | scram_HMAC_update(&ctx, |
715 | client_final_message_without_proof, |
716 | strlen(client_final_message_without_proof)); |
717 | scram_HMAC_final(ClientSignature, &ctx); |
718 | |
719 | for (i = 0; i < SCRAM_KEY_LEN; i++) |
720 | result[i] = ClientKey[i] ^ ClientSignature[i]; |
721 | } |
722 | |
723 | /* |
724 | * Validate the server signature, received as part of the final exchange |
725 | * message received from the server. |
726 | */ |
727 | static bool |
728 | verify_server_signature(fe_scram_state *state) |
729 | { |
730 | uint8 expected_ServerSignature[SCRAM_KEY_LEN]; |
731 | uint8 ServerKey[SCRAM_KEY_LEN]; |
732 | scram_HMAC_ctx ctx; |
733 | |
734 | scram_ServerKey(state->SaltedPassword, ServerKey); |
735 | |
736 | /* calculate ServerSignature */ |
737 | scram_HMAC_init(&ctx, ServerKey, SCRAM_KEY_LEN); |
738 | scram_HMAC_update(&ctx, |
739 | state->client_first_message_bare, |
740 | strlen(state->client_first_message_bare)); |
741 | scram_HMAC_update(&ctx, "," , 1); |
742 | scram_HMAC_update(&ctx, |
743 | state->server_first_message, |
744 | strlen(state->server_first_message)); |
745 | scram_HMAC_update(&ctx, "," , 1); |
746 | scram_HMAC_update(&ctx, |
747 | state->client_final_message_without_proof, |
748 | strlen(state->client_final_message_without_proof)); |
749 | scram_HMAC_final(expected_ServerSignature, &ctx); |
750 | |
751 | if (memcmp(expected_ServerSignature, state->ServerSignature, SCRAM_KEY_LEN) != 0) |
752 | return false; |
753 | |
754 | return true; |
755 | } |
756 | |
757 | /* |
758 | * Build a new SCRAM verifier. |
759 | */ |
760 | char * |
761 | pg_fe_scram_build_verifier(const char *password) |
762 | { |
763 | char *prep_password; |
764 | pg_saslprep_rc rc; |
765 | char saltbuf[SCRAM_DEFAULT_SALT_LEN]; |
766 | char *result; |
767 | |
768 | /* |
769 | * Normalize the password with SASLprep. If that doesn't work, because |
770 | * the password isn't valid UTF-8 or contains prohibited characters, just |
771 | * proceed with the original password. (See comments at top of file.) |
772 | */ |
773 | rc = pg_saslprep(password, &prep_password); |
774 | if (rc == SASLPREP_OOM) |
775 | return NULL; |
776 | if (rc == SASLPREP_SUCCESS) |
777 | password = (const char *) prep_password; |
778 | |
779 | /* Generate a random salt */ |
780 | if (!pg_strong_random(saltbuf, SCRAM_DEFAULT_SALT_LEN)) |
781 | { |
782 | if (prep_password) |
783 | free(prep_password); |
784 | return NULL; |
785 | } |
786 | |
787 | result = scram_build_verifier(saltbuf, SCRAM_DEFAULT_SALT_LEN, |
788 | SCRAM_DEFAULT_ITERATIONS, password); |
789 | |
790 | if (prep_password) |
791 | free(prep_password); |
792 | |
793 | return result; |
794 | } |
795 | |