Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * fe-auth-scram.c
4 : : * The front-end (client) implementation of SCRAM authentication.
5 : : *
6 : : * Portions Copyright (c) 1996-2024, 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/hmac.h"
19 : : #include "common/saslprep.h"
20 : : #include "common/scram-common.h"
21 : : #include "fe-auth.h"
22 : :
23 : :
24 : : /* The exported SCRAM callback mechanism. */
25 : : static void *scram_init(PGconn *conn, const char *password,
26 : : const char *sasl_mechanism);
27 : : static SASLStatus scram_exchange(void *opaq, char *input, int inputlen,
28 : : char **output, int *outputlen);
29 : : static bool scram_channel_bound(void *opaq);
30 : : static void scram_free(void *opaq);
31 : :
32 : : const pg_fe_sasl_mech pg_scram_mech = {
33 : : scram_init,
34 : : scram_exchange,
35 : : scram_channel_bound,
36 : : scram_free
37 : : };
38 : :
39 : : /*
40 : : * Status of exchange messages used for SCRAM authentication via the
41 : : * SASL protocol.
42 : : */
43 : : typedef enum
44 : : {
45 : : FE_SCRAM_INIT,
46 : : FE_SCRAM_NONCE_SENT,
47 : : FE_SCRAM_PROOF_SENT,
48 : : FE_SCRAM_FINISHED,
49 : : } fe_scram_state_enum;
50 : :
51 : : typedef struct
52 : : {
53 : : fe_scram_state_enum state;
54 : :
55 : : /* These are supplied by the user */
56 : : PGconn *conn;
57 : : char *password;
58 : : char *sasl_mechanism;
59 : :
60 : : /* State data depending on the hash type */
61 : : pg_cryptohash_type hash_type;
62 : : int key_length;
63 : :
64 : : /* We construct these */
65 : : uint8 SaltedPassword[SCRAM_MAX_KEY_LEN];
66 : : char *client_nonce;
67 : : char *client_first_message_bare;
68 : : char *client_final_message_without_proof;
69 : :
70 : : /* These come from the server-first message */
71 : : char *server_first_message;
72 : : char *salt;
73 : : int saltlen;
74 : : int iterations;
75 : : char *nonce;
76 : :
77 : : /* These come from the server-final message */
78 : : char *server_final_message;
79 : : char ServerSignature[SCRAM_MAX_KEY_LEN];
80 : : } fe_scram_state;
81 : :
82 : : static bool read_server_first_message(fe_scram_state *state, char *input);
83 : : static bool read_server_final_message(fe_scram_state *state, char *input);
84 : : static char *build_client_first_message(fe_scram_state *state);
85 : : static char *build_client_final_message(fe_scram_state *state);
86 : : static bool verify_server_signature(fe_scram_state *state, bool *match,
87 : : const char **errstr);
88 : : static bool calculate_client_proof(fe_scram_state *state,
89 : : const char *client_final_message_without_proof,
90 : : uint8 *result, const char **errstr);
91 : :
92 : : /*
93 : : * Initialize SCRAM exchange status.
94 : : */
95 : : static void *
1012 michael@paquier.xyz 96 :CBC 43 : scram_init(PGconn *conn,
97 : : const char *password,
98 : : const char *sasl_mechanism)
99 : : {
100 : : fe_scram_state *state;
101 : : char *prep_password;
102 : : pg_saslprep_rc rc;
103 : :
2339 peter_e@gmx.net 104 [ - + ]: 43 : Assert(sasl_mechanism != NULL);
105 : :
2595 heikki.linnakangas@i 106 : 43 : state = (fe_scram_state *) malloc(sizeof(fe_scram_state));
107 [ - + ]: 43 : if (!state)
2595 heikki.linnakangas@i 108 :UBC 0 : return NULL;
2595 heikki.linnakangas@i 109 :CBC 43 : memset(state, 0, sizeof(fe_scram_state));
2292 peter_e@gmx.net 110 : 43 : state->conn = conn;
2595 heikki.linnakangas@i 111 : 43 : state->state = FE_SCRAM_INIT;
481 michael@paquier.xyz 112 : 43 : state->key_length = SCRAM_SHA_256_KEY_LEN;
113 : 43 : state->hash_type = PG_SHA256;
114 : :
115 : 43 : state->sasl_mechanism = strdup(sasl_mechanism);
2339 peter_e@gmx.net 116 [ - + ]: 43 : if (!state->sasl_mechanism)
117 : : {
2339 peter_e@gmx.net 118 :UBC 0 : free(state);
119 : 0 : return NULL;
120 : : }
121 : :
122 : : /* Normalize the password with SASLprep, if possible */
2564 heikki.linnakangas@i 123 :CBC 43 : rc = pg_saslprep(password, &prep_password);
124 [ - + ]: 43 : if (rc == SASLPREP_OOM)
125 : : {
2339 peter_e@gmx.net 126 :UBC 0 : free(state->sasl_mechanism);
2564 heikki.linnakangas@i 127 : 0 : free(state);
128 : 0 : return NULL;
129 : : }
2564 heikki.linnakangas@i 130 [ + + ]:CBC 43 : if (rc != SASLPREP_SUCCESS)
131 : : {
132 : 2 : prep_password = strdup(password);
133 [ - + ]: 2 : if (!prep_password)
134 : : {
2339 peter_e@gmx.net 135 :UBC 0 : free(state->sasl_mechanism);
2564 heikki.linnakangas@i 136 : 0 : free(state);
137 : 0 : return NULL;
138 : : }
139 : : }
2564 heikki.linnakangas@i 140 :CBC 43 : state->password = prep_password;
141 : :
2595 142 : 43 : return state;
143 : : }
144 : :
145 : : /*
146 : : * Return true if channel binding was employed and the SCRAM exchange
147 : : * completed. This should be used after a successful exchange to determine
148 : : * whether the server authenticated itself to the client.
149 : : *
150 : : * Note that the caller must also ensure that the exchange was actually
151 : : * successful.
152 : : */
153 : : static bool
1012 michael@paquier.xyz 154 : 3 : scram_channel_bound(void *opaq)
155 : : {
1665 jdavis@postgresql.or 156 : 3 : fe_scram_state *state = (fe_scram_state *) opaq;
157 : :
158 : : /* no SCRAM exchange done */
159 [ - + ]: 3 : if (state == NULL)
1665 jdavis@postgresql.or 160 :UBC 0 : return false;
161 : :
162 : : /* SCRAM exchange not completed */
1665 jdavis@postgresql.or 163 [ - + ]:CBC 3 : if (state->state != FE_SCRAM_FINISHED)
1665 jdavis@postgresql.or 164 :UBC 0 : return false;
165 : :
166 : : /* channel binding mechanism not used */
1665 jdavis@postgresql.or 167 [ - + ]:CBC 3 : if (strcmp(state->sasl_mechanism, SCRAM_SHA_256_PLUS_NAME) != 0)
1665 jdavis@postgresql.or 168 :UBC 0 : return false;
169 : :
170 : : /* all clear! */
1665 jdavis@postgresql.or 171 :CBC 3 : return true;
172 : : }
173 : :
174 : : /*
175 : : * Free SCRAM exchange status
176 : : */
177 : : static void
1012 michael@paquier.xyz 178 : 43 : scram_free(void *opaq)
179 : : {
2595 heikki.linnakangas@i 180 : 43 : fe_scram_state *state = (fe_scram_state *) opaq;
181 : :
668 peter@eisentraut.org 182 : 43 : free(state->password);
183 : 43 : free(state->sasl_mechanism);
184 : :
185 : : /* client messages */
186 : 43 : free(state->client_nonce);
187 : 43 : free(state->client_first_message_bare);
188 : 43 : free(state->client_final_message_without_proof);
189 : :
190 : : /* first message from server */
191 : 43 : free(state->server_first_message);
192 : 43 : free(state->salt);
193 : 43 : free(state->nonce);
194 : :
195 : : /* final message from server */
196 : 43 : free(state->server_final_message);
197 : :
2595 heikki.linnakangas@i 198 : 43 : free(state);
199 : 43 : }
200 : :
201 : : /*
202 : : * Exchange a SCRAM message with backend.
203 : : */
204 : : static SASLStatus
1012 michael@paquier.xyz 205 : 123 : scram_exchange(void *opaq, char *input, int inputlen,
206 : : char **output, int *outputlen)
207 : : {
2595 heikki.linnakangas@i 208 : 123 : fe_scram_state *state = (fe_scram_state *) opaq;
2292 peter_e@gmx.net 209 : 123 : PGconn *conn = state->conn;
822 michael@paquier.xyz 210 : 123 : const char *errstr = NULL;
211 : :
2595 heikki.linnakangas@i 212 : 123 : *output = NULL;
213 : 123 : *outputlen = 0;
214 : :
215 : : /*
216 : : * Check that the input length agrees with the string length of the input.
217 : : * We can ignore inputlen after this.
218 : : */
219 [ + + ]: 123 : if (state->state != FE_SCRAM_INIT)
220 : : {
221 [ - + ]: 80 : if (inputlen == 0)
222 : : {
516 peter@eisentraut.org 223 :UBC 0 : libpq_append_conn_error(conn, "malformed SCRAM message (empty message)");
24 dgustafsson@postgres 224 :UNC 0 : return SASL_FAILED;
225 : : }
2595 heikki.linnakangas@i 226 [ - + ]:CBC 80 : if (inputlen != strlen(input))
227 : : {
516 peter@eisentraut.org 228 :UBC 0 : libpq_append_conn_error(conn, "malformed SCRAM message (length mismatch)");
24 dgustafsson@postgres 229 :UNC 0 : return SASL_FAILED;
230 : : }
231 : : }
232 : :
2595 heikki.linnakangas@i 233 [ + + + - ]:CBC 123 : switch (state->state)
234 : : {
235 : 43 : case FE_SCRAM_INIT:
236 : : /* Begin the SCRAM handshake, by sending client nonce */
2292 peter_e@gmx.net 237 : 43 : *output = build_client_first_message(state);
2595 heikki.linnakangas@i 238 [ - + ]: 43 : if (*output == NULL)
24 dgustafsson@postgres 239 :UNC 0 : return SASL_FAILED;
240 : :
2595 heikki.linnakangas@i 241 :CBC 43 : *outputlen = strlen(*output);
242 : 43 : state->state = FE_SCRAM_NONCE_SENT;
24 dgustafsson@postgres 243 :GNC 43 : return SASL_CONTINUE;
244 : :
2595 heikki.linnakangas@i 245 :CBC 43 : case FE_SCRAM_NONCE_SENT:
246 : : /* Receive salt and server nonce, send response. */
2292 peter_e@gmx.net 247 [ - + ]: 43 : if (!read_server_first_message(state, input))
24 dgustafsson@postgres 248 :UNC 0 : return SASL_FAILED;
249 : :
2292 peter_e@gmx.net 250 :CBC 43 : *output = build_client_final_message(state);
2595 heikki.linnakangas@i 251 [ - + ]: 43 : if (*output == NULL)
24 dgustafsson@postgres 252 :UNC 0 : return SASL_FAILED;
253 : :
2595 heikki.linnakangas@i 254 :CBC 43 : *outputlen = strlen(*output);
255 : 43 : state->state = FE_SCRAM_PROOF_SENT;
24 dgustafsson@postgres 256 :GNC 43 : return SASL_CONTINUE;
257 : :
2595 heikki.linnakangas@i 258 :CBC 37 : case FE_SCRAM_PROOF_SENT:
259 : : {
260 : : bool match;
261 : :
262 : : /* Receive server signature */
24 dgustafsson@postgres 263 [ - + ]:GNC 37 : if (!read_server_final_message(state, input))
24 dgustafsson@postgres 264 :UNC 0 : return SASL_FAILED;
265 : :
266 : : /*
267 : : * Verify server signature, to make sure we're talking to the
268 : : * genuine server.
269 : : */
24 dgustafsson@postgres 270 [ - + ]:GNC 37 : if (!verify_server_signature(state, &match, &errstr))
271 : : {
24 dgustafsson@postgres 272 :UNC 0 : libpq_append_conn_error(conn, "could not verify server signature: %s", errstr);
273 : 0 : return SASL_FAILED;
274 : : }
275 : :
24 dgustafsson@postgres 276 [ - + ]:GNC 37 : if (!match)
277 : : {
24 dgustafsson@postgres 278 :UNC 0 : libpq_append_conn_error(conn, "incorrect server signature");
279 : : }
24 dgustafsson@postgres 280 :GNC 37 : state->state = FE_SCRAM_FINISHED;
281 : 37 : state->conn->client_finished_auth = true;
282 : 37 : return match ? SASL_COMPLETE : SASL_FAILED;
283 : : }
284 : :
2595 heikki.linnakangas@i 285 :UBC 0 : default:
286 : : /* shouldn't happen */
516 peter@eisentraut.org 287 : 0 : libpq_append_conn_error(conn, "invalid SCRAM exchange state");
24 dgustafsson@postgres 288 :UNC 0 : break;
289 : : }
290 : :
291 : 0 : return SASL_FAILED;
292 : : }
293 : :
294 : : /*
295 : : * Read value for an attribute part of a SCRAM message.
296 : : *
297 : : * The buffer at **input is destructively modified, and *input is
298 : : * advanced over the "attr=value" string and any following comma.
299 : : *
300 : : * On failure, append an error message to *errorMessage and return NULL.
301 : : */
302 : : static char *
2595 heikki.linnakangas@i 303 :CBC 166 : read_attr_value(char **input, char attr, PQExpBuffer errorMessage)
304 : : {
305 : 166 : char *begin = *input;
306 : : char *end;
307 : :
308 [ - + ]: 166 : if (*begin != attr)
309 : : {
516 peter@eisentraut.org 310 :UBC 0 : libpq_append_error(errorMessage,
311 : : "malformed SCRAM message (attribute \"%c\" expected)",
312 : : attr);
2595 heikki.linnakangas@i 313 : 0 : return NULL;
314 : : }
2595 heikki.linnakangas@i 315 :CBC 166 : begin++;
316 : :
317 [ - + ]: 166 : if (*begin != '=')
318 : : {
516 peter@eisentraut.org 319 :UBC 0 : libpq_append_error(errorMessage,
320 : : "malformed SCRAM message (expected character \"=\" for attribute \"%c\")",
321 : : attr);
2595 heikki.linnakangas@i 322 : 0 : return NULL;
323 : : }
2595 heikki.linnakangas@i 324 :CBC 166 : begin++;
325 : :
326 : 166 : end = begin;
327 [ + + + + ]: 5060 : while (*end && *end != ',')
328 : 4894 : end++;
329 : :
330 [ + + ]: 166 : if (*end)
331 : : {
332 : 86 : *end = '\0';
333 : 86 : *input = end + 1;
334 : : }
335 : : else
336 : 80 : *input = end;
337 : :
338 : 166 : return begin;
339 : : }
340 : :
341 : : /*
342 : : * Build the first exchange message sent by the client.
343 : : */
344 : : static char *
2292 peter_e@gmx.net 345 : 43 : build_client_first_message(fe_scram_state *state)
346 : : {
347 : 43 : PGconn *conn = state->conn;
348 : : char raw_nonce[SCRAM_RAW_NONCE_LEN + 1];
349 : : char *result;
350 : : int channel_info_len;
351 : : int encoded_len;
352 : : PQExpBufferData buf;
353 : :
354 : : /*
355 : : * Generate a "raw" nonce. This is converted to ASCII-printable form by
356 : : * base64-encoding it.
357 : : */
1930 michael@paquier.xyz 358 [ - + ]: 43 : if (!pg_strong_random(raw_nonce, SCRAM_RAW_NONCE_LEN))
359 : : {
516 peter@eisentraut.org 360 :UBC 0 : libpq_append_conn_error(conn, "could not generate nonce");
2595 heikki.linnakangas@i 361 : 0 : return NULL;
362 : : }
363 : :
1746 michael@paquier.xyz 364 :CBC 43 : encoded_len = pg_b64_enc_len(SCRAM_RAW_NONCE_LEN);
365 : : /* don't forget the zero-terminator */
366 : 43 : state->client_nonce = malloc(encoded_len + 1);
2595 heikki.linnakangas@i 367 [ - + ]: 43 : if (state->client_nonce == NULL)
368 : : {
516 peter@eisentraut.org 369 :UBC 0 : libpq_append_conn_error(conn, "out of memory");
2595 heikki.linnakangas@i 370 : 0 : return NULL;
371 : : }
1746 michael@paquier.xyz 372 :CBC 43 : encoded_len = pg_b64_encode(raw_nonce, SCRAM_RAW_NONCE_LEN,
373 : : state->client_nonce, encoded_len);
374 [ - + ]: 43 : if (encoded_len < 0)
375 : : {
516 peter@eisentraut.org 376 :UBC 0 : libpq_append_conn_error(conn, "could not encode nonce");
1746 michael@paquier.xyz 377 : 0 : return NULL;
378 : : }
2595 heikki.linnakangas@i 379 :CBC 43 : state->client_nonce[encoded_len] = '\0';
380 : :
381 : : /*
382 : : * Generate message. The username is left empty as the backend uses the
383 : : * value provided by the startup packet. Also, as this username is not
384 : : * prepared with SASLprep, the message parsing would fail if it includes
385 : : * '=' or ',' characters.
386 : : */
387 : :
2339 peter_e@gmx.net 388 : 43 : initPQExpBuffer(&buf);
389 : :
390 : : /*
391 : : * First build the gs2-header with channel binding information.
392 : : */
2266 393 [ + + ]: 43 : if (strcmp(state->sasl_mechanism, SCRAM_SHA_256_PLUS_NAME) == 0)
394 : : {
2292 395 [ - + ]: 5 : Assert(conn->ssl_in_use);
1746 drowley@postgresql.o 396 : 5 : appendPQExpBufferStr(&buf, "p=tls-server-end-point");
397 : : }
398 : : #ifdef USE_SSL
1665 jdavis@postgresql.or 399 [ + + ]: 38 : else if (conn->channel_binding[0] != 'd' && /* disable */
400 [ - + ]: 36 : conn->ssl_in_use)
401 : : {
402 : : /*
403 : : * Client supports channel binding, but thinks the server does not.
404 : : */
1746 drowley@postgresql.o 405 :UBC 0 : appendPQExpBufferChar(&buf, 'y');
406 : : }
407 : : #endif
408 : : else
409 : : {
410 : : /*
411 : : * Client does not support channel binding, or has disabled it.
412 : : */
1746 drowley@postgresql.o 413 :CBC 38 : appendPQExpBufferChar(&buf, 'n');
414 : : }
415 : :
2339 peter_e@gmx.net 416 [ - + ]: 43 : if (PQExpBufferDataBroken(buf))
2339 peter_e@gmx.net 417 :UBC 0 : goto oom_error;
418 : :
2339 peter_e@gmx.net 419 :CBC 43 : channel_info_len = buf.len;
420 : :
421 : 43 : appendPQExpBuffer(&buf, ",,n=,r=%s", state->client_nonce);
422 [ - + ]: 43 : if (PQExpBufferDataBroken(buf))
2339 peter_e@gmx.net 423 :UBC 0 : goto oom_error;
424 : :
425 : : /*
426 : : * The first message content needs to be saved without channel binding
427 : : * information.
428 : : */
2339 peter_e@gmx.net 429 :CBC 43 : state->client_first_message_bare = strdup(buf.data + channel_info_len + 2);
430 [ - + ]: 43 : if (!state->client_first_message_bare)
2339 peter_e@gmx.net 431 :UBC 0 : goto oom_error;
432 : :
2339 peter_e@gmx.net 433 :CBC 43 : result = strdup(buf.data);
434 [ - + ]: 43 : if (result == NULL)
2339 peter_e@gmx.net 435 :UBC 0 : goto oom_error;
436 : :
2339 peter_e@gmx.net 437 :CBC 43 : termPQExpBuffer(&buf);
438 : 43 : return result;
439 : :
2339 peter_e@gmx.net 440 :UBC 0 : oom_error:
441 : 0 : termPQExpBuffer(&buf);
516 peter@eisentraut.org 442 : 0 : libpq_append_conn_error(conn, "out of memory");
2339 peter_e@gmx.net 443 : 0 : return NULL;
444 : : }
445 : :
446 : : /*
447 : : * Build the final exchange message sent from the client.
448 : : */
449 : : static char *
2292 peter_e@gmx.net 450 :CBC 43 : build_client_final_message(fe_scram_state *state)
451 : : {
452 : : PQExpBufferData buf;
453 : 43 : PGconn *conn = state->conn;
454 : : uint8 client_proof[SCRAM_MAX_KEY_LEN];
455 : : char *result;
456 : : int encoded_len;
822 michael@paquier.xyz 457 : 43 : const char *errstr = NULL;
458 : :
2595 heikki.linnakangas@i 459 : 43 : initPQExpBuffer(&buf);
460 : :
461 : : /*
462 : : * Construct client-final-message-without-proof. We need to remember it
463 : : * for verifying the server proof in the final step of authentication.
464 : : *
465 : : * The channel binding flag handling (p/y/n) must be consistent with
466 : : * build_client_first_message(), because the server will check that it's
467 : : * the same flag both times.
468 : : */
2266 peter_e@gmx.net 469 [ + + ]: 43 : if (strcmp(state->sasl_mechanism, SCRAM_SHA_256_PLUS_NAME) == 0)
470 : : {
471 : : #ifdef USE_SSL
2292 472 : 5 : char *cbind_data = NULL;
473 : 5 : size_t cbind_data_len = 0;
474 : : size_t cbind_header_len;
475 : : char *cbind_input;
476 : : size_t cbind_input_len;
477 : : int encoded_cbind_len;
478 : :
479 : : /* Fetch hash data of server's SSL certificate */
480 : : cbind_data =
2079 heikki.linnakangas@i 481 : 5 : pgtls_get_peer_certificate_hash(state->conn,
482 : : &cbind_data_len);
483 [ - + ]: 5 : if (cbind_data == NULL)
484 : : {
485 : : /* error message is already set on error */
2339 peter_e@gmx.net 486 :UBC 0 : termPQExpBuffer(&buf);
487 : 0 : return NULL;
488 : : }
489 : :
1746 drowley@postgresql.o 490 :CBC 5 : appendPQExpBufferStr(&buf, "c=");
491 : :
492 : : /* p=type,, */
2079 heikki.linnakangas@i 493 : 5 : cbind_header_len = strlen("p=tls-server-end-point,,");
2339 peter_e@gmx.net 494 : 5 : cbind_input_len = cbind_header_len + cbind_data_len;
495 : 5 : cbind_input = malloc(cbind_input_len);
496 [ - + ]: 5 : if (!cbind_input)
497 : : {
2292 peter_e@gmx.net 498 :UBC 0 : free(cbind_data);
2339 499 : 0 : goto oom_error;
500 : : }
2079 heikki.linnakangas@i 501 :CBC 5 : memcpy(cbind_input, "p=tls-server-end-point,,", cbind_header_len);
2339 peter_e@gmx.net 502 : 5 : memcpy(cbind_input + cbind_header_len, cbind_data, cbind_data_len);
503 : :
1746 michael@paquier.xyz 504 : 5 : encoded_cbind_len = pg_b64_enc_len(cbind_input_len);
505 [ - + ]: 5 : if (!enlargePQExpBuffer(&buf, encoded_cbind_len))
506 : : {
2292 peter_e@gmx.net 507 :UBC 0 : free(cbind_data);
2339 508 : 0 : free(cbind_input);
509 : 0 : goto oom_error;
510 : : }
1746 michael@paquier.xyz 511 :CBC 5 : encoded_cbind_len = pg_b64_encode(cbind_input, cbind_input_len,
512 : 5 : buf.data + buf.len,
513 : : encoded_cbind_len);
514 [ - + ]: 5 : if (encoded_cbind_len < 0)
515 : : {
1746 michael@paquier.xyz 516 :UBC 0 : free(cbind_data);
517 : 0 : free(cbind_input);
518 : 0 : termPQExpBuffer(&buf);
1189 tgl@sss.pgh.pa.us 519 : 0 : appendPQExpBufferStr(&conn->errorMessage,
520 : : "could not encode cbind data for channel binding\n");
1746 michael@paquier.xyz 521 : 0 : return NULL;
522 : : }
1746 michael@paquier.xyz 523 :CBC 5 : buf.len += encoded_cbind_len;
2339 peter_e@gmx.net 524 : 5 : buf.data[buf.len] = '\0';
525 : :
2292 526 : 5 : free(cbind_data);
2339 527 : 5 : free(cbind_input);
528 : : #else
529 : : /*
530 : : * Chose channel binding, but the SSL library doesn't support it.
531 : : * Shouldn't happen.
532 : : */
533 : : termPQExpBuffer(&buf);
534 : : appendPQExpBufferStr(&conn->errorMessage,
535 : : "channel binding not supported by this build\n");
536 : : return NULL;
537 : : #endif /* USE_SSL */
538 : : }
539 : : #ifdef USE_SSL
1665 jdavis@postgresql.or 540 [ + + ]: 38 : else if (conn->channel_binding[0] != 'd' && /* disable */
541 [ - + ]: 36 : conn->ssl_in_use)
1746 drowley@postgresql.o 542 :UBC 0 : appendPQExpBufferStr(&buf, "c=eSws"); /* base64 of "y,," */
543 : : #endif
544 : : else
1746 drowley@postgresql.o 545 :CBC 38 : appendPQExpBufferStr(&buf, "c=biws"); /* base64 of "n,," */
546 : :
2339 peter_e@gmx.net 547 [ - + ]: 43 : if (PQExpBufferDataBroken(buf))
2339 peter_e@gmx.net 548 :UBC 0 : goto oom_error;
549 : :
2339 peter_e@gmx.net 550 :CBC 43 : appendPQExpBuffer(&buf, ",r=%s", state->nonce);
2595 heikki.linnakangas@i 551 [ - + ]: 43 : if (PQExpBufferDataBroken(buf))
2595 heikki.linnakangas@i 552 :UBC 0 : goto oom_error;
553 : :
2595 heikki.linnakangas@i 554 :CBC 43 : state->client_final_message_without_proof = strdup(buf.data);
555 [ - + ]: 43 : if (state->client_final_message_without_proof == NULL)
2595 heikki.linnakangas@i 556 :UBC 0 : goto oom_error;
557 : :
558 : : /* Append proof to it, to form client-final-message. */
1229 michael@paquier.xyz 559 [ - + ]:CBC 43 : if (!calculate_client_proof(state,
560 : 43 : state->client_final_message_without_proof,
561 : : client_proof, &errstr))
562 : : {
1229 michael@paquier.xyz 563 :UBC 0 : termPQExpBuffer(&buf);
516 peter@eisentraut.org 564 : 0 : libpq_append_conn_error(conn, "could not calculate client proof: %s", errstr);
1229 michael@paquier.xyz 565 : 0 : return NULL;
566 : : }
567 : :
1746 drowley@postgresql.o 568 :CBC 43 : appendPQExpBufferStr(&buf, ",p=");
481 michael@paquier.xyz 569 : 43 : encoded_len = pg_b64_enc_len(state->key_length);
1746 570 [ - + ]: 43 : if (!enlargePQExpBuffer(&buf, encoded_len))
2595 heikki.linnakangas@i 571 :UBC 0 : goto oom_error;
1746 michael@paquier.xyz 572 :CBC 43 : encoded_len = pg_b64_encode((char *) client_proof,
573 : : state->key_length,
574 : 43 : buf.data + buf.len,
575 : : encoded_len);
576 [ - + ]: 43 : if (encoded_len < 0)
577 : : {
1746 michael@paquier.xyz 578 :UBC 0 : termPQExpBuffer(&buf);
516 peter@eisentraut.org 579 : 0 : libpq_append_conn_error(conn, "could not encode client proof");
1746 michael@paquier.xyz 580 : 0 : return NULL;
581 : : }
1746 michael@paquier.xyz 582 :CBC 43 : buf.len += encoded_len;
2595 heikki.linnakangas@i 583 : 43 : buf.data[buf.len] = '\0';
584 : :
585 : 43 : result = strdup(buf.data);
586 [ - + ]: 43 : if (result == NULL)
2595 heikki.linnakangas@i 587 :UBC 0 : goto oom_error;
588 : :
2595 heikki.linnakangas@i 589 :CBC 43 : termPQExpBuffer(&buf);
590 : 43 : return result;
591 : :
2595 heikki.linnakangas@i 592 :UBC 0 : oom_error:
593 : 0 : termPQExpBuffer(&buf);
516 peter@eisentraut.org 594 : 0 : libpq_append_conn_error(conn, "out of memory");
2595 heikki.linnakangas@i 595 : 0 : return NULL;
596 : : }
597 : :
598 : : /*
599 : : * Read the first exchange message coming from the server.
600 : : */
601 : : static bool
2292 peter_e@gmx.net 602 :CBC 43 : read_server_first_message(fe_scram_state *state, char *input)
603 : : {
604 : 43 : PGconn *conn = state->conn;
605 : : char *iterations_str;
606 : : char *endptr;
607 : : char *encoded_salt;
608 : : char *nonce;
609 : : int decoded_salt_len;
610 : :
2595 heikki.linnakangas@i 611 : 43 : state->server_first_message = strdup(input);
612 [ - + ]: 43 : if (state->server_first_message == NULL)
613 : : {
516 peter@eisentraut.org 614 :UBC 0 : libpq_append_conn_error(conn, "out of memory");
2595 heikki.linnakangas@i 615 : 0 : return false;
616 : : }
617 : :
618 : : /* parse the message */
2292 peter_e@gmx.net 619 :CBC 43 : nonce = read_attr_value(&input, 'r',
620 : : &conn->errorMessage);
2595 heikki.linnakangas@i 621 [ - + ]: 43 : if (nonce == NULL)
622 : : {
623 : : /* read_attr_value() has appended an error string */
2595 heikki.linnakangas@i 624 :UBC 0 : return false;
625 : : }
626 : :
627 : : /* Verify immediately that the server used our part of the nonce */
2518 heikki.linnakangas@i 628 [ + - ]:CBC 43 : if (strlen(nonce) < strlen(state->client_nonce) ||
629 [ - + ]: 43 : memcmp(nonce, state->client_nonce, strlen(state->client_nonce)) != 0)
630 : : {
516 peter@eisentraut.org 631 :UBC 0 : libpq_append_conn_error(conn, "invalid SCRAM response (nonce mismatch)");
2595 heikki.linnakangas@i 632 : 0 : return false;
633 : : }
634 : :
2595 heikki.linnakangas@i 635 :CBC 43 : state->nonce = strdup(nonce);
636 [ - + ]: 43 : if (state->nonce == NULL)
637 : : {
516 peter@eisentraut.org 638 :UBC 0 : libpq_append_conn_error(conn, "out of memory");
2595 heikki.linnakangas@i 639 : 0 : return false;
640 : : }
641 : :
2292 peter_e@gmx.net 642 :CBC 43 : encoded_salt = read_attr_value(&input, 's', &conn->errorMessage);
2595 heikki.linnakangas@i 643 [ - + ]: 43 : if (encoded_salt == NULL)
644 : : {
645 : : /* read_attr_value() has appended an error string */
2595 heikki.linnakangas@i 646 :UBC 0 : return false;
647 : : }
1746 michael@paquier.xyz 648 :CBC 43 : decoded_salt_len = pg_b64_dec_len(strlen(encoded_salt));
649 : 43 : state->salt = malloc(decoded_salt_len);
2595 heikki.linnakangas@i 650 [ - + ]: 43 : if (state->salt == NULL)
651 : : {
516 peter@eisentraut.org 652 :UBC 0 : libpq_append_conn_error(conn, "out of memory");
2595 heikki.linnakangas@i 653 : 0 : return false;
654 : : }
2595 heikki.linnakangas@i 655 :CBC 86 : state->saltlen = pg_b64_decode(encoded_salt,
656 : 43 : strlen(encoded_salt),
657 : : state->salt,
658 : : decoded_salt_len);
1763 michael@paquier.xyz 659 [ - + ]: 43 : if (state->saltlen < 0)
660 : : {
516 peter@eisentraut.org 661 :UBC 0 : libpq_append_conn_error(conn, "malformed SCRAM message (invalid salt)");
1763 michael@paquier.xyz 662 : 0 : return false;
663 : : }
664 : :
2292 peter_e@gmx.net 665 :CBC 43 : iterations_str = read_attr_value(&input, 'i', &conn->errorMessage);
2595 heikki.linnakangas@i 666 [ - + ]: 43 : if (iterations_str == NULL)
667 : : {
668 : : /* read_attr_value() has appended an error string */
2595 heikki.linnakangas@i 669 :UBC 0 : return false;
670 : : }
2565 heikki.linnakangas@i 671 :CBC 43 : state->iterations = strtol(iterations_str, &endptr, 10);
2595 672 [ + - - + ]: 43 : if (*endptr != '\0' || state->iterations < 1)
673 : : {
516 peter@eisentraut.org 674 :UBC 0 : libpq_append_conn_error(conn, "malformed SCRAM message (invalid iteration count)");
2595 heikki.linnakangas@i 675 : 0 : return false;
676 : : }
677 : :
2595 heikki.linnakangas@i 678 [ - + ]:CBC 43 : if (*input != '\0')
516 peter@eisentraut.org 679 :UBC 0 : libpq_append_conn_error(conn, "malformed SCRAM message (garbage at end of server-first-message)");
680 : :
2595 heikki.linnakangas@i 681 :CBC 43 : return true;
682 : : }
683 : :
684 : : /*
685 : : * Read the final exchange message coming from the server.
686 : : */
687 : : static bool
2292 peter_e@gmx.net 688 : 37 : read_server_final_message(fe_scram_state *state, char *input)
689 : : {
690 : 37 : PGconn *conn = state->conn;
691 : : char *encoded_server_signature;
692 : : char *decoded_server_signature;
693 : : int server_signature_len;
694 : :
2595 heikki.linnakangas@i 695 : 37 : state->server_final_message = strdup(input);
696 [ - + ]: 37 : if (!state->server_final_message)
697 : : {
516 peter@eisentraut.org 698 :UBC 0 : libpq_append_conn_error(conn, "out of memory");
2595 heikki.linnakangas@i 699 : 0 : return false;
700 : : }
701 : :
702 : : /* Check for error result. */
2595 heikki.linnakangas@i 703 [ - + ]:CBC 37 : if (*input == 'e')
704 : : {
2292 peter_e@gmx.net 705 :UBC 0 : char *errmsg = read_attr_value(&input, 'e',
706 : : &conn->errorMessage);
707 : :
1189 tgl@sss.pgh.pa.us 708 [ # # ]: 0 : if (errmsg == NULL)
709 : : {
710 : : /* read_attr_value() has appended an error message */
711 : 0 : return false;
712 : : }
516 peter@eisentraut.org 713 : 0 : libpq_append_conn_error(conn, "error received from server in SCRAM exchange: %s",
714 : : errmsg);
2595 heikki.linnakangas@i 715 : 0 : return false;
716 : : }
717 : :
718 : : /* Parse the message. */
2292 peter_e@gmx.net 719 :CBC 37 : encoded_server_signature = read_attr_value(&input, 'v',
720 : : &conn->errorMessage);
2543 heikki.linnakangas@i 721 [ - + ]: 37 : if (encoded_server_signature == NULL)
722 : : {
723 : : /* read_attr_value() has appended an error message */
2595 heikki.linnakangas@i 724 :UBC 0 : return false;
725 : : }
726 : :
2595 heikki.linnakangas@i 727 [ - + ]:CBC 37 : if (*input != '\0')
516 peter@eisentraut.org 728 :UBC 0 : libpq_append_conn_error(conn, "malformed SCRAM message (garbage at end of server-final-message)");
729 : :
1763 michael@paquier.xyz 730 :CBC 37 : server_signature_len = pg_b64_dec_len(strlen(encoded_server_signature));
731 : 37 : decoded_server_signature = malloc(server_signature_len);
732 [ - + ]: 37 : if (!decoded_server_signature)
733 : : {
516 peter@eisentraut.org 734 :UBC 0 : libpq_append_conn_error(conn, "out of memory");
1763 michael@paquier.xyz 735 : 0 : return false;
736 : : }
737 : :
2543 heikki.linnakangas@i 738 :CBC 37 : server_signature_len = pg_b64_decode(encoded_server_signature,
739 : 37 : strlen(encoded_server_signature),
740 : : decoded_server_signature,
741 : : server_signature_len);
481 michael@paquier.xyz 742 [ - + ]: 37 : if (server_signature_len != state->key_length)
743 : : {
1763 michael@paquier.xyz 744 :UBC 0 : free(decoded_server_signature);
516 peter@eisentraut.org 745 : 0 : libpq_append_conn_error(conn, "malformed SCRAM message (invalid server signature)");
2595 heikki.linnakangas@i 746 : 0 : return false;
747 : : }
481 michael@paquier.xyz 748 :CBC 37 : memcpy(state->ServerSignature, decoded_server_signature,
749 : 37 : state->key_length);
1763 750 : 37 : free(decoded_server_signature);
751 : :
2595 heikki.linnakangas@i 752 : 37 : return true;
753 : : }
754 : :
755 : : /*
756 : : * Calculate the client proof, part of the final exchange message sent
757 : : * by the client. Returns true on success, false on failure with *errstr
758 : : * pointing to a message about the error details.
759 : : */
760 : : static bool
761 : 43 : calculate_client_proof(fe_scram_state *state,
762 : : const char *client_final_message_without_proof,
763 : : uint8 *result, const char **errstr)
764 : : {
765 : : uint8 StoredKey[SCRAM_MAX_KEY_LEN];
766 : : uint8 ClientKey[SCRAM_MAX_KEY_LEN];
767 : : uint8 ClientSignature[SCRAM_MAX_KEY_LEN];
768 : : int i;
769 : : pg_hmac_ctx *ctx;
770 : :
481 michael@paquier.xyz 771 : 43 : ctx = pg_hmac_create(state->hash_type);
1107 772 [ - + ]: 43 : if (ctx == NULL)
773 : : {
822 michael@paquier.xyz 774 :UBC 0 : *errstr = pg_hmac_error(NULL); /* returns OOM */
1107 775 : 0 : return false;
776 : : }
777 : :
778 : : /*
779 : : * Calculate SaltedPassword, and store it in 'state' so that we can reuse
780 : : * it later in verify_server_signature.
781 : : */
481 michael@paquier.xyz 782 [ + - ]:CBC 43 : if (scram_SaltedPassword(state->password, state->hash_type,
783 : 43 : state->key_length, state->salt, state->saltlen,
822 784 : 43 : state->iterations, state->SaltedPassword,
785 [ + - ]: 43 : errstr) < 0 ||
481 786 : 43 : scram_ClientKey(state->SaltedPassword, state->hash_type,
787 [ - + ]: 43 : state->key_length, ClientKey, errstr) < 0 ||
788 : 43 : scram_H(ClientKey, state->hash_type, state->key_length,
789 : : StoredKey, errstr) < 0)
790 : : {
791 : : /* errstr is already filled here */
822 michael@paquier.xyz 792 :UBC 0 : pg_hmac_free(ctx);
793 : 0 : return false;
794 : : }
795 : :
481 michael@paquier.xyz 796 [ + - + - ]:CBC 86 : if (pg_hmac_init(ctx, StoredKey, state->key_length) < 0 ||
1107 797 : 43 : pg_hmac_update(ctx,
798 : 43 : (uint8 *) state->client_first_message_bare,
799 [ + - ]: 86 : strlen(state->client_first_message_bare)) < 0 ||
800 [ + - ]: 86 : pg_hmac_update(ctx, (uint8 *) ",", 1) < 0 ||
801 : 43 : pg_hmac_update(ctx,
802 : 43 : (uint8 *) state->server_first_message,
803 [ + - ]: 86 : strlen(state->server_first_message)) < 0 ||
804 [ + - ]: 86 : pg_hmac_update(ctx, (uint8 *) ",", 1) < 0 ||
805 : 43 : pg_hmac_update(ctx,
806 : : (uint8 *) client_final_message_without_proof,
807 [ - + ]: 43 : strlen(client_final_message_without_proof)) < 0 ||
481 808 : 43 : pg_hmac_final(ctx, ClientSignature, state->key_length) < 0)
809 : : {
822 michael@paquier.xyz 810 :UBC 0 : *errstr = pg_hmac_error(ctx);
1107 811 : 0 : pg_hmac_free(ctx);
1229 812 : 0 : return false;
813 : : }
814 : :
481 michael@paquier.xyz 815 [ + + ]:CBC 1419 : for (i = 0; i < state->key_length; i++)
2595 heikki.linnakangas@i 816 : 1376 : result[i] = ClientKey[i] ^ ClientSignature[i];
817 : :
1107 michael@paquier.xyz 818 : 43 : pg_hmac_free(ctx);
1229 819 : 43 : return true;
820 : : }
821 : :
822 : : /*
823 : : * Validate the server signature, received as part of the final exchange
824 : : * message received from the server. *match tracks if the server signature
825 : : * matched or not. Returns true if the server signature got verified, and
826 : : * false for a processing error with *errstr pointing to a message about the
827 : : * error details.
828 : : */
829 : : static bool
822 830 : 37 : verify_server_signature(fe_scram_state *state, bool *match,
831 : : const char **errstr)
832 : : {
833 : : uint8 expected_ServerSignature[SCRAM_MAX_KEY_LEN];
834 : : uint8 ServerKey[SCRAM_MAX_KEY_LEN];
835 : : pg_hmac_ctx *ctx;
836 : :
481 837 : 37 : ctx = pg_hmac_create(state->hash_type);
1107 838 [ - + ]: 37 : if (ctx == NULL)
839 : : {
822 michael@paquier.xyz 840 :UBC 0 : *errstr = pg_hmac_error(NULL); /* returns OOM */
841 : 0 : return false;
842 : : }
843 : :
481 michael@paquier.xyz 844 [ - + ]:CBC 37 : if (scram_ServerKey(state->SaltedPassword, state->hash_type,
845 : : state->key_length, ServerKey, errstr) < 0)
846 : : {
847 : : /* errstr is filled already */
822 michael@paquier.xyz 848 :UBC 0 : pg_hmac_free(ctx);
1107 849 : 0 : return false;
850 : : }
851 : :
852 : : /* calculate ServerSignature */
481 michael@paquier.xyz 853 [ + - + - ]:CBC 74 : if (pg_hmac_init(ctx, ServerKey, state->key_length) < 0 ||
1107 854 : 37 : pg_hmac_update(ctx,
855 : 37 : (uint8 *) state->client_first_message_bare,
856 [ + - ]: 74 : strlen(state->client_first_message_bare)) < 0 ||
857 [ + - ]: 74 : pg_hmac_update(ctx, (uint8 *) ",", 1) < 0 ||
858 : 37 : pg_hmac_update(ctx,
859 : 37 : (uint8 *) state->server_first_message,
860 [ + - ]: 74 : strlen(state->server_first_message)) < 0 ||
861 [ + - ]: 74 : pg_hmac_update(ctx, (uint8 *) ",", 1) < 0 ||
862 : 37 : pg_hmac_update(ctx,
863 : 37 : (uint8 *) state->client_final_message_without_proof,
864 [ - + ]: 74 : strlen(state->client_final_message_without_proof)) < 0 ||
865 : 37 : pg_hmac_final(ctx, expected_ServerSignature,
481 866 : 37 : state->key_length) < 0)
867 : : {
822 michael@paquier.xyz 868 :UBC 0 : *errstr = pg_hmac_error(ctx);
1107 869 : 0 : pg_hmac_free(ctx);
1229 870 : 0 : return false;
871 : : }
872 : :
1107 michael@paquier.xyz 873 :CBC 37 : pg_hmac_free(ctx);
874 : :
875 : : /* signature processed, so now check after it */
481 876 : 37 : if (memcmp(expected_ServerSignature, state->ServerSignature,
877 [ - + ]: 37 : state->key_length) != 0)
1229 michael@paquier.xyz 878 :UBC 0 : *match = false;
879 : : else
1229 michael@paquier.xyz 880 :CBC 37 : *match = true;
881 : :
2595 heikki.linnakangas@i 882 : 37 : return true;
883 : : }
884 : :
885 : : /*
886 : : * Build a new SCRAM secret.
887 : : *
888 : : * On error, returns NULL and sets *errstr to point to a message about the
889 : : * error details.
890 : : */
891 : : char *
384 dgustafsson@postgres 892 : 1 : pg_fe_scram_build_secret(const char *password, int iterations, const char **errstr)
893 : : {
894 : : char *prep_password;
895 : : pg_saslprep_rc rc;
896 : : char saltbuf[SCRAM_DEFAULT_SALT_LEN];
897 : : char *result;
898 : :
899 : : /*
900 : : * Normalize the password with SASLprep. If that doesn't work, because
901 : : * the password isn't valid UTF-8 or contains prohibited characters, just
902 : : * proceed with the original password. (See comments at the top of
903 : : * auth-scram.c.)
904 : : */
2538 heikki.linnakangas@i 905 : 1 : rc = pg_saslprep(password, &prep_password);
906 [ - + ]: 1 : if (rc == SASLPREP_OOM)
907 : : {
598 peter@eisentraut.org 908 :UBC 0 : *errstr = libpq_gettext("out of memory");
2538 heikki.linnakangas@i 909 : 0 : return NULL;
910 : : }
2538 heikki.linnakangas@i 911 [ + - ]:CBC 1 : if (rc == SASLPREP_SUCCESS)
912 : 1 : password = (const char *) prep_password;
913 : :
914 : : /* Generate a random salt */
1930 michael@paquier.xyz 915 [ - + ]: 1 : if (!pg_strong_random(saltbuf, SCRAM_DEFAULT_SALT_LEN))
916 : : {
598 peter@eisentraut.org 917 :UBC 0 : *errstr = libpq_gettext("could not generate random salt");
668 918 : 0 : free(prep_password);
2538 heikki.linnakangas@i 919 : 0 : return NULL;
920 : : }
921 : :
481 michael@paquier.xyz 922 :CBC 1 : result = scram_build_secret(PG_SHA256, SCRAM_SHA_256_KEY_LEN, saltbuf,
923 : : SCRAM_DEFAULT_SALT_LEN,
924 : : iterations, password,
925 : : errstr);
926 : :
668 peter@eisentraut.org 927 : 1 : free(prep_password);
928 : :
2538 heikki.linnakangas@i 929 : 1 : return result;
930 : : }
|