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