Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * fe-auth-oauth.c
4 : * The front-end (client) implementation of OAuth/OIDC authentication
5 : * using the SASL OAUTHBEARER mechanism.
6 : *
7 : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
8 : * Portions Copyright (c) 1994, Regents of the University of California
9 : *
10 : * IDENTIFICATION
11 : * src/interfaces/libpq/fe-auth-oauth.c
12 : *
13 : *-------------------------------------------------------------------------
14 : */
15 :
16 : #include "postgres_fe.h"
17 :
18 : #ifdef USE_DYNAMIC_OAUTH
19 : #include <dlfcn.h>
20 : #endif
21 :
22 : #include "common/base64.h"
23 : #include "common/hmac.h"
24 : #include "common/jsonapi.h"
25 : #include "common/oauth-common.h"
26 : #include "fe-auth.h"
27 : #include "fe-auth-oauth.h"
28 : #include "mb/pg_wchar.h"
29 : #include "pg_config_paths.h"
30 : #include "utils/memdebug.h"
31 :
32 : static PostgresPollingStatusType do_async(fe_oauth_state *state,
33 : PGoauthBearerRequestV2 *request);
34 : static void do_cleanup(fe_oauth_state *state, PGoauthBearerRequestV2 *request);
35 : static void poison_req_v2(PGoauthBearerRequestV2 *request, bool poison);
36 :
37 : /* The exported OAuth callback mechanism. */
38 : static void *oauth_init(PGconn *conn, const char *password,
39 : const char *sasl_mechanism);
40 : static SASLStatus oauth_exchange(void *opaq, bool final,
41 : char *input, int inputlen,
42 : char **output, int *outputlen);
43 : static bool oauth_channel_bound(void *opaq);
44 : static void oauth_free(void *opaq);
45 :
46 : const pg_fe_sasl_mech pg_oauth_mech = {
47 : oauth_init,
48 : oauth_exchange,
49 : oauth_channel_bound,
50 : oauth_free,
51 : };
52 :
53 : /*
54 : * Initializes mechanism state for OAUTHBEARER.
55 : *
56 : * For a full description of the API, see libpq/fe-auth-sasl.h.
57 : */
58 : static void *
59 0 : oauth_init(PGconn *conn, const char *password,
60 : const char *sasl_mechanism)
61 : {
62 : fe_oauth_state *state;
63 :
64 : /*
65 : * We only support one SASL mechanism here; anything else is programmer
66 : * error.
67 : */
68 : Assert(sasl_mechanism != NULL);
69 : Assert(strcmp(sasl_mechanism, OAUTHBEARER_NAME) == 0);
70 :
71 0 : state = calloc(1, sizeof(*state));
72 0 : if (!state)
73 0 : return NULL;
74 :
75 0 : state->step = FE_OAUTH_INIT;
76 0 : state->conn = conn;
77 :
78 0 : return state;
79 : }
80 :
81 : /*
82 : * Frees the state allocated by oauth_init().
83 : *
84 : * This handles only mechanism state tied to the connection lifetime; state
85 : * stored in state->async_ctx is freed up either immediately after the
86 : * authentication handshake succeeds, or before the mechanism is cleaned up on
87 : * failure. See pg_fe_cleanup_oauth_flow() and cleanup_oauth_flow().
88 : */
89 : static void
90 0 : oauth_free(void *opaq)
91 : {
92 0 : fe_oauth_state *state = opaq;
93 :
94 : /* Any async authentication state should have been cleaned up already. */
95 : Assert(!state->async_ctx);
96 :
97 0 : free(state);
98 0 : }
99 :
100 : #define kvsep "\x01"
101 :
102 : /*
103 : * Constructs an OAUTHBEARER client initial response (RFC 7628, Sec. 3.1).
104 : *
105 : * If discover is true, the initial response will contain a request for the
106 : * server's required OAuth parameters (Sec. 4.3). Otherwise, conn->token must
107 : * be set; it will be sent as the connection's bearer token.
108 : *
109 : * Returns the response as a null-terminated string, or NULL on error.
110 : */
111 : static char *
112 0 : client_initial_response(PGconn *conn, bool discover)
113 : {
114 : static const char *const resp_format = "n,," kvsep "auth=%s%s" kvsep kvsep;
115 :
116 : PQExpBufferData buf;
117 : const char *authn_scheme;
118 0 : char *response = NULL;
119 0 : const char *token = conn->oauth_token;
120 :
121 0 : if (discover)
122 : {
123 : /* Parameter discovery uses a completely empty auth value. */
124 0 : authn_scheme = token = "";
125 : }
126 : else
127 : {
128 : /*
129 : * Use a Bearer authentication scheme (RFC 6750, Sec. 2.1). A trailing
130 : * space is used as a separator.
131 : */
132 0 : authn_scheme = "Bearer ";
133 :
134 : /* conn->token must have been set in this case. */
135 0 : if (!token)
136 : {
137 : Assert(false);
138 0 : libpq_append_conn_error(conn,
139 : "internal error: no OAuth token was set for the connection");
140 0 : return NULL;
141 : }
142 : }
143 :
144 0 : initPQExpBuffer(&buf);
145 0 : appendPQExpBuffer(&buf, resp_format, authn_scheme, token);
146 :
147 0 : if (!PQExpBufferDataBroken(buf))
148 0 : response = strdup(buf.data);
149 0 : termPQExpBuffer(&buf);
150 :
151 0 : if (!response)
152 0 : libpq_append_conn_error(conn, "out of memory");
153 :
154 0 : return response;
155 : }
156 :
157 : /*
158 : * JSON Parser (for the OAUTHBEARER error result)
159 : */
160 :
161 : /* Relevant JSON fields in the error result object. */
162 : #define ERROR_STATUS_FIELD "status"
163 : #define ERROR_SCOPE_FIELD "scope"
164 : #define ERROR_OPENID_CONFIGURATION_FIELD "openid-configuration"
165 :
166 : /*
167 : * Limit the maximum number of nested objects/arrays. Because OAUTHBEARER
168 : * doesn't have any defined extensions for its JSON yet, we can be much more
169 : * conservative here than with libpq-oauth's MAX_OAUTH_NESTING_LEVEL; we expect
170 : * a nesting level of 1 in practice.
171 : */
172 : #define MAX_SASL_NESTING_LEVEL 8
173 :
174 : struct json_ctx
175 : {
176 : char *errmsg; /* any non-NULL value stops all processing */
177 : PQExpBufferData errbuf; /* backing memory for errmsg */
178 : int nested; /* nesting level (zero is the top) */
179 :
180 : const char *target_field_name; /* points to a static allocation */
181 : char **target_field; /* see below */
182 :
183 : /* target_field, if set, points to one of the following: */
184 : char *status;
185 : char *scope;
186 : char *discovery_uri;
187 : };
188 :
189 : #define oauth_json_has_error(ctx) \
190 : (PQExpBufferDataBroken((ctx)->errbuf) || (ctx)->errmsg)
191 :
192 : #define oauth_json_set_error(ctx, fmt, ...) \
193 : do { \
194 : appendPQExpBuffer(&(ctx)->errbuf, libpq_gettext(fmt), ##__VA_ARGS__); \
195 : (ctx)->errmsg = (ctx)->errbuf.data; \
196 : } while (0)
197 :
198 : /* An untranslated version of oauth_json_set_error(). */
199 : #define oauth_json_set_error_internal(ctx, ...) \
200 : do { \
201 : appendPQExpBuffer(&(ctx)->errbuf, __VA_ARGS__); \
202 : (ctx)->errmsg = (ctx)->errbuf.data; \
203 : } while (0)
204 :
205 : static JsonParseErrorType
206 0 : oauth_json_object_start(void *state)
207 : {
208 0 : struct json_ctx *ctx = state;
209 :
210 0 : if (ctx->target_field)
211 : {
212 : Assert(ctx->nested == 1);
213 :
214 0 : oauth_json_set_error(ctx,
215 : "field \"%s\" must be a string",
216 : ctx->target_field_name);
217 : }
218 :
219 0 : ++ctx->nested;
220 0 : if (ctx->nested > MAX_SASL_NESTING_LEVEL)
221 0 : oauth_json_set_error(ctx, "JSON is too deeply nested");
222 :
223 0 : return oauth_json_has_error(ctx) ? JSON_SEM_ACTION_FAILED : JSON_SUCCESS;
224 : }
225 :
226 : static JsonParseErrorType
227 0 : oauth_json_object_end(void *state)
228 : {
229 0 : struct json_ctx *ctx = state;
230 :
231 0 : --ctx->nested;
232 0 : return JSON_SUCCESS;
233 : }
234 :
235 : static JsonParseErrorType
236 0 : oauth_json_object_field_start(void *state, char *name, bool isnull)
237 : {
238 0 : struct json_ctx *ctx = state;
239 :
240 : /* Only top-level keys are considered. */
241 0 : if (ctx->nested == 1)
242 : {
243 0 : if (strcmp(name, ERROR_STATUS_FIELD) == 0)
244 : {
245 0 : ctx->target_field_name = ERROR_STATUS_FIELD;
246 0 : ctx->target_field = &ctx->status;
247 : }
248 0 : else if (strcmp(name, ERROR_SCOPE_FIELD) == 0)
249 : {
250 0 : ctx->target_field_name = ERROR_SCOPE_FIELD;
251 0 : ctx->target_field = &ctx->scope;
252 : }
253 0 : else if (strcmp(name, ERROR_OPENID_CONFIGURATION_FIELD) == 0)
254 : {
255 0 : ctx->target_field_name = ERROR_OPENID_CONFIGURATION_FIELD;
256 0 : ctx->target_field = &ctx->discovery_uri;
257 : }
258 : }
259 :
260 0 : return JSON_SUCCESS;
261 : }
262 :
263 : static JsonParseErrorType
264 0 : oauth_json_array_start(void *state)
265 : {
266 0 : struct json_ctx *ctx = state;
267 :
268 0 : if (!ctx->nested)
269 : {
270 0 : oauth_json_set_error(ctx, "top-level element must be an object");
271 : }
272 0 : else if (ctx->target_field)
273 : {
274 : Assert(ctx->nested == 1);
275 :
276 0 : oauth_json_set_error(ctx,
277 : "field \"%s\" must be a string",
278 : ctx->target_field_name);
279 : }
280 :
281 0 : ++ctx->nested;
282 0 : if (ctx->nested > MAX_SASL_NESTING_LEVEL)
283 0 : oauth_json_set_error(ctx, "JSON is too deeply nested");
284 :
285 0 : return oauth_json_has_error(ctx) ? JSON_SEM_ACTION_FAILED : JSON_SUCCESS;
286 : }
287 :
288 : static JsonParseErrorType
289 0 : oauth_json_array_end(void *state)
290 : {
291 0 : struct json_ctx *ctx = state;
292 :
293 0 : --ctx->nested;
294 0 : return JSON_SUCCESS;
295 : }
296 :
297 : static JsonParseErrorType
298 0 : oauth_json_scalar(void *state, char *token, JsonTokenType type)
299 : {
300 0 : struct json_ctx *ctx = state;
301 :
302 0 : if (!ctx->nested)
303 : {
304 0 : oauth_json_set_error(ctx, "top-level element must be an object");
305 0 : return JSON_SEM_ACTION_FAILED;
306 : }
307 :
308 0 : if (ctx->target_field)
309 : {
310 0 : if (ctx->nested != 1)
311 : {
312 : /*
313 : * ctx->target_field should not have been set for nested keys.
314 : * Assert and don't continue any further for production builds.
315 : */
316 : Assert(false);
317 0 : oauth_json_set_error_internal(ctx,
318 : "internal error: target scalar found at nesting level %d during OAUTHBEARER parsing",
319 : ctx->nested);
320 0 : return JSON_SEM_ACTION_FAILED;
321 : }
322 :
323 : /*
324 : * We don't allow duplicate field names; error out if the target has
325 : * already been set.
326 : */
327 0 : if (*ctx->target_field)
328 : {
329 0 : oauth_json_set_error(ctx,
330 : "field \"%s\" is duplicated",
331 : ctx->target_field_name);
332 0 : return JSON_SEM_ACTION_FAILED;
333 : }
334 :
335 : /* The only fields we support are strings. */
336 0 : if (type != JSON_TOKEN_STRING)
337 : {
338 0 : oauth_json_set_error(ctx,
339 : "field \"%s\" must be a string",
340 : ctx->target_field_name);
341 0 : return JSON_SEM_ACTION_FAILED;
342 : }
343 :
344 0 : *ctx->target_field = strdup(token);
345 0 : if (!*ctx->target_field)
346 0 : return JSON_OUT_OF_MEMORY;
347 :
348 0 : ctx->target_field = NULL;
349 0 : ctx->target_field_name = NULL;
350 : }
351 : else
352 : {
353 : /* otherwise we just ignore it */
354 : }
355 :
356 0 : return JSON_SUCCESS;
357 : }
358 :
359 : #define HTTPS_SCHEME "https://"
360 : #define HTTP_SCHEME "http://"
361 :
362 : /* We support both well-known suffixes defined by RFC 8414. */
363 : #define WK_PREFIX "/.well-known/"
364 : #define OPENID_WK_SUFFIX "openid-configuration"
365 : #define OAUTH_WK_SUFFIX "oauth-authorization-server"
366 :
367 : /*
368 : * Derives an issuer identifier from one of our recognized .well-known URIs,
369 : * using the rules in RFC 8414.
370 : */
371 : static char *
372 0 : issuer_from_well_known_uri(PGconn *conn, const char *wkuri)
373 : {
374 0 : const char *authority_start = NULL;
375 : const char *wk_start;
376 : const char *wk_end;
377 : char *issuer;
378 : ptrdiff_t start_offset,
379 : end_offset;
380 : size_t end_len;
381 :
382 : /*
383 : * https:// is required for issuer identifiers (RFC 8414, Sec. 2; OIDC
384 : * Discovery 1.0, Sec. 3). This is a case-insensitive comparison at this
385 : * level (but issuer identifier comparison at the level above this is
386 : * case-sensitive, so in practice it's probably moot).
387 : */
388 0 : if (pg_strncasecmp(wkuri, HTTPS_SCHEME, strlen(HTTPS_SCHEME)) == 0)
389 0 : authority_start = wkuri + strlen(HTTPS_SCHEME);
390 :
391 0 : if (!authority_start
392 0 : && oauth_unsafe_debugging_enabled()
393 0 : && pg_strncasecmp(wkuri, HTTP_SCHEME, strlen(HTTP_SCHEME)) == 0)
394 : {
395 : /* Allow http:// for testing only. */
396 0 : authority_start = wkuri + strlen(HTTP_SCHEME);
397 : }
398 :
399 0 : if (!authority_start)
400 : {
401 0 : libpq_append_conn_error(conn,
402 : "OAuth discovery URI \"%s\" must use HTTPS",
403 : wkuri);
404 0 : return NULL;
405 : }
406 :
407 : /*
408 : * Well-known URIs in general may support queries and fragments, but the
409 : * two types we support here do not. (They must be constructed from the
410 : * components of issuer identifiers, which themselves may not contain any
411 : * queries or fragments.)
412 : *
413 : * It's important to check this first, to avoid getting tricked later by a
414 : * prefix buried inside a query or fragment.
415 : */
416 0 : if (strpbrk(authority_start, "?#") != NULL)
417 : {
418 0 : libpq_append_conn_error(conn,
419 : "OAuth discovery URI \"%s\" must not contain query or fragment components",
420 : wkuri);
421 0 : return NULL;
422 : }
423 :
424 : /*
425 : * Find the start of the .well-known prefix. IETF rules (RFC 8615) state
426 : * this must be at the beginning of the path component, but OIDC defined
427 : * it at the end instead (OIDC Discovery 1.0, Sec. 4), so we have to
428 : * search for it anywhere.
429 : */
430 0 : wk_start = strstr(authority_start, WK_PREFIX);
431 0 : if (!wk_start)
432 : {
433 0 : libpq_append_conn_error(conn,
434 : "OAuth discovery URI \"%s\" is not a .well-known URI",
435 : wkuri);
436 0 : return NULL;
437 : }
438 :
439 : /*
440 : * Now find the suffix type. We only support the two defined in OIDC
441 : * Discovery 1.0 and RFC 8414.
442 : */
443 0 : wk_end = wk_start + strlen(WK_PREFIX);
444 :
445 0 : if (strncmp(wk_end, OPENID_WK_SUFFIX, strlen(OPENID_WK_SUFFIX)) == 0)
446 0 : wk_end += strlen(OPENID_WK_SUFFIX);
447 0 : else if (strncmp(wk_end, OAUTH_WK_SUFFIX, strlen(OAUTH_WK_SUFFIX)) == 0)
448 0 : wk_end += strlen(OAUTH_WK_SUFFIX);
449 : else
450 0 : wk_end = NULL;
451 :
452 : /*
453 : * Even if there's a match, we still need to check to make sure the suffix
454 : * takes up the entire path segment, to weed out constructions like
455 : * "/.well-known/openid-configuration-bad".
456 : */
457 0 : if (!wk_end || (*wk_end != '/' && *wk_end != '\0'))
458 : {
459 0 : libpq_append_conn_error(conn,
460 : "OAuth discovery URI \"%s\" uses an unsupported .well-known suffix",
461 : wkuri);
462 0 : return NULL;
463 : }
464 :
465 : /*
466 : * Finally, make sure the .well-known components are provided either as a
467 : * prefix (IETF style) or as a postfix (OIDC style). In other words,
468 : * "https://localhost/a/.well-known/openid-configuration/b" is not allowed
469 : * to claim association with "https://localhost/a/b".
470 : */
471 0 : if (*wk_end != '\0')
472 : {
473 : /*
474 : * It's not at the end, so it's required to be at the beginning at the
475 : * path. Find the starting slash.
476 : */
477 : const char *path_start;
478 :
479 0 : path_start = strchr(authority_start, '/');
480 : Assert(path_start); /* otherwise we wouldn't have found WK_PREFIX */
481 :
482 0 : if (wk_start != path_start)
483 : {
484 0 : libpq_append_conn_error(conn,
485 : "OAuth discovery URI \"%s\" uses an invalid format",
486 : wkuri);
487 0 : return NULL;
488 : }
489 : }
490 :
491 : /* Checks passed! Now build the issuer. */
492 0 : issuer = strdup(wkuri);
493 0 : if (!issuer)
494 : {
495 0 : libpq_append_conn_error(conn, "out of memory");
496 0 : return NULL;
497 : }
498 :
499 : /*
500 : * The .well-known components are from [wk_start, wk_end). Remove those to
501 : * form the issuer ID, by shifting the path suffix (which may be empty)
502 : * leftwards.
503 : */
504 0 : start_offset = wk_start - wkuri;
505 0 : end_offset = wk_end - wkuri;
506 0 : end_len = strlen(wk_end) + 1; /* move the NULL terminator too */
507 :
508 0 : memmove(issuer + start_offset, issuer + end_offset, end_len);
509 :
510 0 : return issuer;
511 : }
512 :
513 : /*
514 : * Parses the server error result (RFC 7628, Sec. 3.2.2) contained in msg and
515 : * stores any discovered openid_configuration and scope settings for the
516 : * connection.
517 : */
518 : static bool
519 0 : handle_oauth_sasl_error(PGconn *conn, const char *msg, int msglen)
520 : {
521 : JsonLexContext *lex;
522 0 : JsonSemAction sem = {0};
523 : JsonParseErrorType err;
524 0 : struct json_ctx ctx = {0};
525 0 : char *errmsg = NULL;
526 0 : bool success = false;
527 :
528 : Assert(conn->oauth_issuer_id); /* ensured by setup_oauth_parameters() */
529 :
530 : /* Sanity check. */
531 0 : if (strlen(msg) != msglen)
532 : {
533 0 : libpq_append_conn_error(conn,
534 : "server's error message contained an embedded NULL, and was discarded");
535 0 : return false;
536 : }
537 :
538 : /*
539 : * pg_parse_json doesn't validate the incoming UTF-8, so we have to check
540 : * that up front.
541 : */
542 0 : if (pg_encoding_verifymbstr(PG_UTF8, msg, msglen) != msglen)
543 : {
544 0 : libpq_append_conn_error(conn,
545 : "server's error response is not valid UTF-8");
546 0 : return false;
547 : }
548 :
549 0 : lex = makeJsonLexContextCstringLen(NULL, msg, msglen, PG_UTF8, true);
550 0 : setJsonLexContextOwnsTokens(lex, true); /* must not leak on error */
551 :
552 0 : initPQExpBuffer(&ctx.errbuf);
553 0 : sem.semstate = &ctx;
554 :
555 0 : sem.object_start = oauth_json_object_start;
556 0 : sem.object_end = oauth_json_object_end;
557 0 : sem.object_field_start = oauth_json_object_field_start;
558 0 : sem.array_start = oauth_json_array_start;
559 0 : sem.array_end = oauth_json_array_end;
560 0 : sem.scalar = oauth_json_scalar;
561 :
562 0 : err = pg_parse_json(lex, &sem);
563 :
564 0 : if (err == JSON_SEM_ACTION_FAILED)
565 : {
566 0 : if (PQExpBufferDataBroken(ctx.errbuf))
567 0 : errmsg = libpq_gettext("out of memory");
568 0 : else if (ctx.errmsg)
569 0 : errmsg = ctx.errmsg;
570 : else
571 : {
572 : /*
573 : * Developer error: one of the action callbacks didn't call
574 : * oauth_json_set_error() before erroring out.
575 : */
576 : Assert(oauth_json_has_error(&ctx));
577 0 : errmsg = "<unexpected empty error>";
578 : }
579 : }
580 0 : else if (err != JSON_SUCCESS)
581 0 : errmsg = json_errdetail(err, lex);
582 :
583 0 : if (errmsg)
584 0 : libpq_append_conn_error(conn,
585 : "failed to parse server's error response: %s",
586 : errmsg);
587 :
588 : /* Don't need the error buffer or the JSON lexer anymore. */
589 0 : termPQExpBuffer(&ctx.errbuf);
590 0 : freeJsonLexContext(lex);
591 :
592 0 : if (errmsg)
593 0 : goto cleanup;
594 :
595 0 : if (ctx.discovery_uri)
596 : {
597 : char *discovery_issuer;
598 :
599 : /*
600 : * The URI MUST correspond to our existing issuer, to avoid mix-ups.
601 : *
602 : * Issuer comparison is done byte-wise, rather than performing any URL
603 : * normalization; this follows the suggestions for issuer comparison
604 : * in RFC 9207 Sec. 2.4 (which requires simple string comparison) and
605 : * vastly simplifies things. Since this is the key protection against
606 : * a rogue server sending the client to an untrustworthy location,
607 : * simpler is better.
608 : */
609 0 : discovery_issuer = issuer_from_well_known_uri(conn, ctx.discovery_uri);
610 0 : if (!discovery_issuer)
611 0 : goto cleanup; /* error message already set */
612 :
613 0 : if (strcmp(conn->oauth_issuer_id, discovery_issuer) != 0)
614 : {
615 0 : libpq_append_conn_error(conn,
616 : "server's discovery document at %s (issuer \"%s\") is incompatible with oauth_issuer (%s)",
617 : ctx.discovery_uri, discovery_issuer,
618 : conn->oauth_issuer_id);
619 :
620 0 : free(discovery_issuer);
621 0 : goto cleanup;
622 : }
623 :
624 0 : free(discovery_issuer);
625 :
626 0 : if (!conn->oauth_discovery_uri)
627 : {
628 0 : conn->oauth_discovery_uri = ctx.discovery_uri;
629 0 : ctx.discovery_uri = NULL;
630 : }
631 : else
632 : {
633 : /* This must match the URI we'd previously determined. */
634 0 : if (strcmp(conn->oauth_discovery_uri, ctx.discovery_uri) != 0)
635 : {
636 0 : libpq_append_conn_error(conn,
637 : "server's discovery document has moved to %s (previous location was %s)",
638 : ctx.discovery_uri,
639 : conn->oauth_discovery_uri);
640 0 : goto cleanup;
641 : }
642 : }
643 : }
644 :
645 0 : if (ctx.scope)
646 : {
647 : /* Servers may not override a previously set oauth_scope. */
648 0 : if (!conn->oauth_scope)
649 : {
650 0 : conn->oauth_scope = ctx.scope;
651 0 : ctx.scope = NULL;
652 : }
653 : }
654 :
655 0 : if (!ctx.status)
656 : {
657 0 : libpq_append_conn_error(conn,
658 : "server sent error response without a status");
659 0 : goto cleanup;
660 : }
661 :
662 0 : if (strcmp(ctx.status, "invalid_token") != 0)
663 : {
664 : /*
665 : * invalid_token is the only error code we'll automatically retry for;
666 : * otherwise, just bail out now.
667 : */
668 0 : libpq_append_conn_error(conn,
669 : "server rejected OAuth bearer token: %s",
670 : ctx.status);
671 0 : goto cleanup;
672 : }
673 :
674 0 : success = true;
675 :
676 0 : cleanup:
677 0 : free(ctx.status);
678 0 : free(ctx.scope);
679 0 : free(ctx.discovery_uri);
680 :
681 0 : return success;
682 : }
683 :
684 : /*
685 : * Helper for handling flow failures. If anything was put into request->error,
686 : * it's added to conn->errorMessage here.
687 : */
688 : static void
689 0 : report_flow_error(PGconn *conn, const PGoauthBearerRequestV2 *request)
690 : {
691 0 : fe_oauth_state *state = conn->sasl_state;
692 0 : const char *errmsg = request->error;
693 :
694 : /*
695 : * User-defined flows are called out explicitly so that the user knows who
696 : * to blame. Builtin flows don't need that extra message length; we expect
697 : * them to always fill in request->error on failure anyway.
698 : */
699 0 : if (state->builtin)
700 : {
701 0 : if (!errmsg)
702 : {
703 : /*
704 : * Don't turn a bug here into a crash in production, but don't
705 : * bother translating either.
706 : */
707 : Assert(false);
708 0 : errmsg = "builtin flow failed but did not provide an error message";
709 : }
710 :
711 0 : appendPQExpBufferStr(&conn->errorMessage, errmsg);
712 : }
713 : else
714 : {
715 0 : appendPQExpBufferStr(&conn->errorMessage,
716 0 : libpq_gettext("user-defined OAuth flow failed"));
717 0 : if (errmsg)
718 : {
719 0 : appendPQExpBufferStr(&conn->errorMessage, ": ");
720 0 : appendPQExpBufferStr(&conn->errorMessage, errmsg);
721 : }
722 : }
723 :
724 0 : appendPQExpBufferChar(&conn->errorMessage, '\n');
725 0 : }
726 :
727 : /*
728 : * Callback implementation of conn->async_auth() for OAuth flows. Delegates the
729 : * retrieval of the token to the PGoauthBearerRequestV2.async() callback.
730 : *
731 : * This will be called multiple times as needed; the callback is responsible for
732 : * setting an altsock to signal and returning the correct PGRES_POLLING_*
733 : * statuses for use by PQconnectPoll().
734 : */
735 : static PostgresPollingStatusType
736 0 : run_oauth_flow(PGconn *conn)
737 : {
738 0 : fe_oauth_state *state = conn->sasl_state;
739 0 : PGoauthBearerRequestV2 *request = state->async_ctx;
740 : PostgresPollingStatusType status;
741 :
742 0 : if (!request->v1.async)
743 : {
744 : Assert(!state->builtin); /* be very noisy if our code does this */
745 0 : libpq_append_conn_error(conn,
746 : "user-defined OAuth flow provided neither a token nor an async callback");
747 0 : return PGRES_POLLING_FAILED;
748 : }
749 :
750 0 : status = do_async(state, request);
751 :
752 0 : if (status == PGRES_POLLING_FAILED)
753 : {
754 0 : report_flow_error(conn, request);
755 0 : return status;
756 : }
757 0 : else if (status == PGRES_POLLING_OK)
758 : {
759 : /*
760 : * We already have a token, so copy it into the conn. (We can't hold
761 : * onto the original string, since it may not be safe for us to free()
762 : * it.)
763 : */
764 0 : if (!request->v1.token)
765 : {
766 : Assert(!state->builtin);
767 0 : libpq_append_conn_error(conn,
768 : "user-defined OAuth flow did not provide a token");
769 0 : return PGRES_POLLING_FAILED;
770 : }
771 :
772 0 : conn->oauth_token = strdup(request->v1.token);
773 0 : if (!conn->oauth_token)
774 : {
775 0 : libpq_append_conn_error(conn, "out of memory");
776 0 : return PGRES_POLLING_FAILED;
777 : }
778 :
779 0 : return PGRES_POLLING_OK;
780 : }
781 :
782 : /* The hook wants the client to poll the altsock. Make sure it set one. */
783 0 : if (conn->altsock == PGINVALID_SOCKET)
784 : {
785 : Assert(!state->builtin);
786 0 : libpq_append_conn_error(conn,
787 : "user-defined OAuth flow did not provide a socket for polling");
788 0 : return PGRES_POLLING_FAILED;
789 : }
790 :
791 0 : return status;
792 : }
793 :
794 : /*
795 : * Cleanup callback for the async flow. Delegates most of its job to
796 : * PGoauthBearerRequest.cleanup(), then disconnects the altsock and frees the
797 : * request itself.
798 : *
799 : * This is called either at the end of a successful authentication, or during
800 : * pqDropConnection(), so we won't leak resources even if PQconnectPoll() never
801 : * calls us back.
802 : */
803 : static void
804 0 : cleanup_oauth_flow(PGconn *conn)
805 : {
806 0 : fe_oauth_state *state = conn->sasl_state;
807 0 : PGoauthBearerRequestV2 *request = state->async_ctx;
808 :
809 : Assert(request);
810 :
811 0 : do_cleanup(state, request);
812 0 : conn->altsock = PGINVALID_SOCKET;
813 :
814 0 : free(request);
815 0 : state->async_ctx = NULL;
816 0 : }
817 :
818 : /*-------------
819 : * Builtin Flow
820 : *
821 : * There are three potential implementations of use_builtin_flow:
822 : *
823 : * 1) If the OAuth client is disabled at configuration time, return zero.
824 : * Dependent clients must provide their own flow.
825 : * 2) If the OAuth client is enabled and USE_DYNAMIC_OAUTH is defined, dlopen()
826 : * the libpq-oauth plugin and use its implementation.
827 : * 3) Otherwise, use flow callbacks that are statically linked into the
828 : * executable.
829 : *
830 : * For caller convenience, the return value follows the convention of
831 : * PQauthDataHook: zero means no implementation is provided, negative indicates
832 : * failure, and positive indicates success.
833 : */
834 :
835 : #if !defined(USE_LIBCURL)
836 :
837 : /*
838 : * This configuration doesn't support the builtin flow.
839 : */
840 :
841 : static int
842 0 : use_builtin_flow(PGconn *conn, fe_oauth_state *state, PGoauthBearerRequestV2 *request)
843 : {
844 0 : return 0;
845 : }
846 :
847 : #elif defined(USE_DYNAMIC_OAUTH)
848 :
849 : /*
850 : * Use the builtin flow in the libpq-oauth plugin, which is loaded at runtime.
851 : */
852 :
853 : typedef char *(*libpq_gettext_func) (const char *msgid);
854 :
855 : /*
856 : * Loads the libpq-oauth plugin via dlopen(), initializes it, and plugs its
857 : * callbacks into the connection's async auth handlers.
858 : *
859 : * Failure to load here results in a relatively quiet connection error, to
860 : * handle the use case where the build supports loading a flow but a user does
861 : * not want to install it. Troubleshooting of linker/loader failures can be done
862 : * via PGOAUTHDEBUG.
863 : *
864 : * The lifetime of *request ends shortly after this call, so it must be copied
865 : * to longer-lived storage.
866 : */
867 : static int
868 : use_builtin_flow(PGconn *conn, fe_oauth_state *state, PGoauthBearerRequestV2 *request)
869 : {
870 : static bool initialized = false;
871 : static pthread_mutex_t init_mutex = PTHREAD_MUTEX_INITIALIZER;
872 : int lockerr;
873 :
874 : void (*init) (libpq_gettext_func gettext_impl);
875 : int (*start_flow) (PGconn *conn, PGoauthBearerRequestV2 *request);
876 :
877 : /*
878 : * On macOS only, load the module using its absolute install path; the
879 : * standard search behavior is not very helpful for this use case. Unlike
880 : * on other platforms, DYLD_LIBRARY_PATH is used as a fallback even with
881 : * absolute paths (modulo SIP effects), so tests can continue to work.
882 : *
883 : * On the other platforms, load the module using only the basename, to
884 : * rely on the runtime linker's standard search behavior.
885 : */
886 : const char *const module_name =
887 : #if defined(__darwin__)
888 : LIBDIR "/libpq-oauth" DLSUFFIX;
889 : #else
890 : "libpq-oauth" DLSUFFIX;
891 : #endif
892 :
893 : state->flow_module = dlopen(module_name, RTLD_NOW | RTLD_LOCAL);
894 : if (!state->flow_module)
895 : {
896 : /*
897 : * For end users, this probably isn't an error condition, it just
898 : * means the flow isn't installed. Developers and package maintainers
899 : * may want to debug this via the PGOAUTHDEBUG envvar, though.
900 : *
901 : * Note that POSIX dlerror() isn't guaranteed to be threadsafe.
902 : */
903 : if (oauth_unsafe_debugging_enabled())
904 : fprintf(stderr, "failed dlopen for libpq-oauth: %s\n", dlerror());
905 :
906 : return 0;
907 : }
908 :
909 : /*
910 : * Our libpq-oauth.so provides a special initialization function for libpq
911 : * integration. If we don't find this, assume that a custom module is in
912 : * use instead.
913 : */
914 : init = dlsym(state->flow_module, "libpq_oauth_init");
915 : if (!init)
916 : state->builtin = false; /* adjust our error messages */
917 :
918 : if ((start_flow = dlsym(state->flow_module, "pg_start_oauthbearer")) == NULL)
919 : {
920 : /*
921 : * This is more of an error condition than the one above, but the
922 : * cause is still locked behind PGOAUTHDEBUG due to the dlerror()
923 : * threadsafety issue.
924 : */
925 : if (oauth_unsafe_debugging_enabled())
926 : fprintf(stderr, "failed dlsym for libpq-oauth: %s\n", dlerror());
927 :
928 : dlclose(state->flow_module);
929 : state->flow_module = NULL;
930 :
931 : request->error = libpq_gettext("could not find entry point for libpq-oauth");
932 : return -1;
933 : }
934 :
935 : /*
936 : * Past this point, we do not unload the module. It stays in the process
937 : * permanently.
938 : */
939 :
940 : if (init)
941 : {
942 : /*
943 : * We need to inject necessary function pointers into the module. This
944 : * only needs to be done once -- even if the pointers are constant,
945 : * assigning them while another thread is executing the flows feels
946 : * like tempting fate.
947 : */
948 : if ((lockerr = pthread_mutex_lock(&init_mutex)) != 0)
949 : {
950 : /* Should not happen... but don't continue if it does. */
951 : Assert(false);
952 :
953 : appendPQExpBuffer(&conn->errorMessage,
954 : "use_builtin_flow: failed to lock mutex (%d)\n",
955 : lockerr);
956 :
957 : request->error = ""; /* satisfy report_flow_error() */
958 : return -1;
959 : }
960 :
961 : if (!initialized)
962 : {
963 : init(
964 : #ifdef ENABLE_NLS
965 : libpq_gettext
966 : #else
967 : NULL
968 : #endif
969 : );
970 :
971 : initialized = true;
972 : }
973 :
974 : pthread_mutex_unlock(&init_mutex);
975 : }
976 :
977 : return (start_flow(conn, request) == 0) ? 1 : -1;
978 : }
979 :
980 : #else
981 :
982 : /*
983 : * For static builds, we can just call pg_start_oauthbearer() directly. It's
984 : * provided by libpq-oauth.a.
985 : */
986 :
987 : extern int pg_start_oauthbearer(PGconn *conn, PGoauthBearerRequestV2 *request);
988 :
989 : static int
990 : use_builtin_flow(PGconn *conn, fe_oauth_state *state, PGoauthBearerRequestV2 *request)
991 : {
992 : return (pg_start_oauthbearer(conn, request) == 0) ? 1 : -1;
993 : }
994 :
995 : #endif /* USE_LIBCURL */
996 :
997 :
998 : /*
999 : * Chooses an OAuth client flow for the connection, which will retrieve a Bearer
1000 : * token for presentation to the server.
1001 : *
1002 : * If the application has registered a custom flow handler using
1003 : * PQAUTHDATA_OAUTH_BEARER_TOKEN[_V2], it may either return a token immediately
1004 : * (e.g. if it has one cached for immediate use), or set up for a series of
1005 : * asynchronous callbacks which will be managed by run_oauth_flow().
1006 : *
1007 : * If the default handler is used instead, a Device Authorization flow is used
1008 : * for the connection if support has been compiled in. (See oauth-curl.c for
1009 : * implementation details.)
1010 : *
1011 : * If neither a custom handler nor the builtin flow is available, the connection
1012 : * fails here.
1013 : */
1014 : static bool
1015 0 : setup_token_request(PGconn *conn, fe_oauth_state *state)
1016 : {
1017 : int res;
1018 0 : PGoauthBearerRequestV2 request = {
1019 : .v1 = {
1020 0 : .openid_configuration = conn->oauth_discovery_uri,
1021 0 : .scope = conn->oauth_scope,
1022 : },
1023 0 : .issuer = conn->oauth_issuer_id,
1024 : };
1025 :
1026 : Assert(request.v1.openid_configuration);
1027 : Assert(request.issuer);
1028 :
1029 : /*
1030 : * The client may have overridden the OAuth flow. Try the v2 hook first,
1031 : * then fall back to the v1 implementation. If neither is available, try
1032 : * the builtin flow.
1033 : */
1034 0 : res = PQauthDataHook(PQAUTHDATA_OAUTH_BEARER_TOKEN_V2, conn, &request);
1035 0 : if (res == 0)
1036 : {
1037 0 : poison_req_v2(&request, true);
1038 :
1039 0 : res = PQauthDataHook(PQAUTHDATA_OAUTH_BEARER_TOKEN, conn, &request);
1040 0 : state->v1 = (res != 0);
1041 :
1042 0 : poison_req_v2(&request, false);
1043 : }
1044 0 : if (res == 0)
1045 : {
1046 0 : state->builtin = true;
1047 0 : res = use_builtin_flow(conn, state, &request);
1048 : }
1049 :
1050 0 : if (res > 0)
1051 : {
1052 : PGoauthBearerRequestV2 *request_copy;
1053 :
1054 0 : if (request.v1.token)
1055 : {
1056 : /*
1057 : * We already have a token, so copy it into the conn. (We can't
1058 : * hold onto the original string, since it may not be safe for us
1059 : * to free() it.)
1060 : */
1061 0 : conn->oauth_token = strdup(request.v1.token);
1062 0 : if (!conn->oauth_token)
1063 : {
1064 0 : libpq_append_conn_error(conn, "out of memory");
1065 0 : goto fail;
1066 : }
1067 :
1068 : /* short-circuit */
1069 0 : do_cleanup(state, &request);
1070 0 : return true;
1071 : }
1072 :
1073 0 : request_copy = malloc(sizeof(*request_copy));
1074 0 : if (!request_copy)
1075 : {
1076 0 : libpq_append_conn_error(conn, "out of memory");
1077 0 : goto fail;
1078 : }
1079 :
1080 0 : *request_copy = request;
1081 :
1082 0 : conn->async_auth = run_oauth_flow;
1083 0 : conn->cleanup_async_auth = cleanup_oauth_flow;
1084 0 : state->async_ctx = request_copy;
1085 :
1086 0 : return true;
1087 : }
1088 :
1089 : /*
1090 : * Failure cases: either we tried to set up a flow and failed, or there
1091 : * was no flow to try.
1092 : */
1093 0 : if (res < 0)
1094 0 : report_flow_error(conn, &request);
1095 : else
1096 0 : libpq_append_conn_error(conn, "no OAuth flows are available (try installing the libpq-oauth package)");
1097 :
1098 0 : fail:
1099 0 : do_cleanup(state, &request);
1100 0 : return false;
1101 : }
1102 :
1103 : /*
1104 : * Fill in our issuer identifier (and discovery URI, if possible) using the
1105 : * connection parameters. If conn->oauth_discovery_uri can't be populated in
1106 : * this function, it will be requested from the server.
1107 : */
1108 : static bool
1109 0 : setup_oauth_parameters(PGconn *conn)
1110 : {
1111 : /*
1112 : * This is the only function that sets conn->oauth_issuer_id. If a
1113 : * previous connection attempt has already computed it, don't overwrite it
1114 : * or the discovery URI. (There's no reason for them to change once
1115 : * they're set, and handle_oauth_sasl_error() will fail the connection if
1116 : * the server attempts to switch them on us later.)
1117 : */
1118 0 : if (conn->oauth_issuer_id)
1119 0 : return true;
1120 :
1121 : /*---
1122 : * To talk to a server, we require the user to provide issuer and client
1123 : * identifiers.
1124 : *
1125 : * While it's possible for an OAuth client to support multiple issuers, it
1126 : * requires additional effort to make sure the flows in use are safe -- to
1127 : * quote RFC 9207,
1128 : *
1129 : * OAuth clients that interact with only one authorization server are
1130 : * not vulnerable to mix-up attacks. However, when such clients decide
1131 : * to add support for a second authorization server in the future, they
1132 : * become vulnerable and need to apply countermeasures to mix-up
1133 : * attacks.
1134 : *
1135 : * For now, we allow only one.
1136 : */
1137 0 : if (!conn->oauth_issuer || !conn->oauth_client_id)
1138 : {
1139 0 : libpq_append_conn_error(conn,
1140 : "server requires OAuth authentication, but oauth_issuer and oauth_client_id are not both set");
1141 0 : return false;
1142 : }
1143 :
1144 : /*
1145 : * oauth_issuer is interpreted differently if it's a well-known discovery
1146 : * URI rather than just an issuer identifier.
1147 : */
1148 0 : if (strstr(conn->oauth_issuer, WK_PREFIX) != NULL)
1149 : {
1150 : /*
1151 : * Convert the URI back to an issuer identifier. (This also performs
1152 : * validation of the URI format.)
1153 : */
1154 0 : conn->oauth_issuer_id = issuer_from_well_known_uri(conn,
1155 0 : conn->oauth_issuer);
1156 0 : if (!conn->oauth_issuer_id)
1157 0 : return false; /* error message already set */
1158 :
1159 0 : conn->oauth_discovery_uri = strdup(conn->oauth_issuer);
1160 0 : if (!conn->oauth_discovery_uri)
1161 : {
1162 0 : libpq_append_conn_error(conn, "out of memory");
1163 0 : return false;
1164 : }
1165 : }
1166 : else
1167 : {
1168 : /*
1169 : * Treat oauth_issuer as an issuer identifier. We'll ask the server
1170 : * for the discovery URI.
1171 : */
1172 0 : conn->oauth_issuer_id = strdup(conn->oauth_issuer);
1173 0 : if (!conn->oauth_issuer_id)
1174 : {
1175 0 : libpq_append_conn_error(conn, "out of memory");
1176 0 : return false;
1177 : }
1178 : }
1179 :
1180 0 : return true;
1181 : }
1182 :
1183 : /*
1184 : * Implements the OAUTHBEARER SASL exchange (RFC 7628, Sec. 3.2).
1185 : *
1186 : * If the necessary OAuth parameters are set up on the connection, this will run
1187 : * the client flow asynchronously and present the resulting token to the server.
1188 : * Otherwise, an empty discovery response will be sent and any parameters sent
1189 : * back by the server will be stored for a second attempt.
1190 : *
1191 : * For a full description of the API, see libpq/sasl.h.
1192 : */
1193 : static SASLStatus
1194 0 : oauth_exchange(void *opaq, bool final,
1195 : char *input, int inputlen,
1196 : char **output, int *outputlen)
1197 : {
1198 0 : fe_oauth_state *state = opaq;
1199 0 : PGconn *conn = state->conn;
1200 0 : bool discover = false;
1201 :
1202 0 : *output = NULL;
1203 0 : *outputlen = 0;
1204 :
1205 0 : switch (state->step)
1206 : {
1207 0 : case FE_OAUTH_INIT:
1208 : /* We begin in the initial response phase. */
1209 : Assert(inputlen == -1);
1210 :
1211 0 : if (!setup_oauth_parameters(conn))
1212 0 : return SASL_FAILED;
1213 :
1214 0 : if (conn->oauth_token)
1215 : {
1216 : /*
1217 : * A previous connection already fetched the token; we'll use
1218 : * it below.
1219 : */
1220 : }
1221 0 : else if (conn->oauth_discovery_uri)
1222 : {
1223 : /*
1224 : * We don't have a token, but we have a discovery URI already
1225 : * stored. Decide whether we're using a user-provided OAuth
1226 : * flow or the one we have built in.
1227 : */
1228 0 : if (!setup_token_request(conn, state))
1229 0 : return SASL_FAILED;
1230 :
1231 0 : if (conn->oauth_token)
1232 : {
1233 : /*
1234 : * A really smart user implementation may have already
1235 : * given us the token (e.g. if there was an unexpired copy
1236 : * already cached), and we can use it immediately.
1237 : */
1238 : }
1239 : else
1240 : {
1241 : /*
1242 : * Otherwise, we'll have to hand the connection over to
1243 : * our OAuth implementation.
1244 : *
1245 : * This could take a while, since it generally involves a
1246 : * user in the loop. To avoid consuming the server's
1247 : * authentication timeout, we'll continue this handshake
1248 : * to the end, so that the server can close its side of
1249 : * the connection. We'll open a second connection later
1250 : * once we've retrieved a token.
1251 : */
1252 0 : discover = true;
1253 : }
1254 : }
1255 : else
1256 : {
1257 : /*
1258 : * If we don't have a token, and we don't have a discovery URI
1259 : * to be able to request a token, we ask the server for one
1260 : * explicitly.
1261 : */
1262 0 : discover = true;
1263 : }
1264 :
1265 : /*
1266 : * Generate an initial response. This either contains a token, if
1267 : * we have one, or an empty discovery response which is doomed to
1268 : * fail.
1269 : */
1270 0 : *output = client_initial_response(conn, discover);
1271 0 : if (!*output)
1272 0 : return SASL_FAILED;
1273 :
1274 0 : *outputlen = strlen(*output);
1275 0 : state->step = FE_OAUTH_BEARER_SENT;
1276 :
1277 0 : if (conn->oauth_token)
1278 : {
1279 : /*
1280 : * For the purposes of require_auth, our side of
1281 : * authentication is done at this point; the server will
1282 : * either accept the connection or send an error. Unlike
1283 : * SCRAM, there is no additional server data to check upon
1284 : * success.
1285 : */
1286 0 : conn->client_finished_auth = true;
1287 : }
1288 :
1289 0 : return SASL_CONTINUE;
1290 :
1291 0 : case FE_OAUTH_BEARER_SENT:
1292 0 : if (final)
1293 : {
1294 : /*
1295 : * OAUTHBEARER does not make use of additional data with a
1296 : * successful SASL exchange, so we shouldn't get an
1297 : * AuthenticationSASLFinal message.
1298 : */
1299 0 : libpq_append_conn_error(conn,
1300 : "server sent unexpected additional OAuth data");
1301 0 : return SASL_FAILED;
1302 : }
1303 :
1304 : /*
1305 : * An error message was sent by the server. Respond with the
1306 : * required dummy message (RFC 7628, sec. 3.2.3).
1307 : */
1308 0 : *output = strdup(kvsep);
1309 0 : if (unlikely(!*output))
1310 : {
1311 0 : libpq_append_conn_error(conn, "out of memory");
1312 0 : return SASL_FAILED;
1313 : }
1314 0 : *outputlen = strlen(*output); /* == 1 */
1315 :
1316 : /* Grab the settings from discovery. */
1317 0 : if (!handle_oauth_sasl_error(conn, input, inputlen))
1318 0 : return SASL_FAILED;
1319 :
1320 0 : if (conn->oauth_token)
1321 : {
1322 : /*
1323 : * The server rejected our token. Continue onwards towards the
1324 : * expected FATAL message, but mark our state to catch any
1325 : * unexpected "success" from the server.
1326 : */
1327 0 : state->step = FE_OAUTH_SERVER_ERROR;
1328 0 : return SASL_CONTINUE;
1329 : }
1330 :
1331 0 : if (!conn->async_auth)
1332 : {
1333 : /*
1334 : * No OAuth flow is set up yet. Did we get enough information
1335 : * from the server to create one?
1336 : */
1337 0 : if (!conn->oauth_discovery_uri)
1338 : {
1339 0 : libpq_append_conn_error(conn,
1340 : "server requires OAuth authentication, but no discovery metadata was provided");
1341 0 : return SASL_FAILED;
1342 : }
1343 :
1344 : /* Yes. Set up the flow now. */
1345 0 : if (!setup_token_request(conn, state))
1346 0 : return SASL_FAILED;
1347 :
1348 0 : if (conn->oauth_token)
1349 : {
1350 : /*
1351 : * A token was available in a custom flow's cache. Skip
1352 : * the asynchronous processing.
1353 : */
1354 0 : goto reconnect;
1355 : }
1356 : }
1357 :
1358 : /*
1359 : * Time to retrieve a token. This involves a number of HTTP
1360 : * connections and timed waits, so we escape the synchronous auth
1361 : * processing and tell PQconnectPoll to transfer control to our
1362 : * async implementation.
1363 : */
1364 : Assert(conn->async_auth); /* should have been set already */
1365 0 : state->step = FE_OAUTH_REQUESTING_TOKEN;
1366 0 : return SASL_ASYNC;
1367 :
1368 0 : case FE_OAUTH_REQUESTING_TOKEN:
1369 :
1370 : /*
1371 : * We've returned successfully from token retrieval. Double-check
1372 : * that we have what we need for the next connection.
1373 : */
1374 0 : if (!conn->oauth_token)
1375 : {
1376 : Assert(false); /* should have failed before this point! */
1377 0 : libpq_append_conn_error(conn,
1378 : "internal error: OAuth flow did not set a token");
1379 0 : return SASL_FAILED;
1380 : }
1381 :
1382 0 : goto reconnect;
1383 :
1384 0 : case FE_OAUTH_SERVER_ERROR:
1385 :
1386 : /*
1387 : * After an error, the server should send an error response to
1388 : * fail the SASL handshake, which is handled in higher layers.
1389 : *
1390 : * If we get here, the server either sent *another* challenge
1391 : * which isn't defined in the RFC, or completed the handshake
1392 : * successfully after telling us it was going to fail. Neither is
1393 : * acceptable.
1394 : */
1395 0 : libpq_append_conn_error(conn,
1396 : "server sent additional OAuth data after error");
1397 0 : return SASL_FAILED;
1398 :
1399 0 : default:
1400 0 : libpq_append_conn_error(conn, "invalid OAuth exchange state");
1401 0 : break;
1402 : }
1403 :
1404 : Assert(false); /* should never get here */
1405 0 : return SASL_FAILED;
1406 :
1407 0 : reconnect:
1408 :
1409 : /*
1410 : * Despite being a failure from the point of view of SASL, we have enough
1411 : * information to restart with a new connection.
1412 : */
1413 0 : libpq_append_conn_error(conn, "retrying connection with new bearer token");
1414 0 : conn->oauth_want_retry = true;
1415 0 : return SASL_FAILED;
1416 : }
1417 :
1418 : static bool
1419 0 : oauth_channel_bound(void *opaq)
1420 : {
1421 : /* This mechanism does not support channel binding. */
1422 0 : return false;
1423 : }
1424 :
1425 : /*
1426 : * Fully clears out any stored OAuth token. This is done proactively upon
1427 : * successful connection as well as during pqClosePGconn().
1428 : */
1429 : void
1430 30353 : pqClearOAuthToken(PGconn *conn)
1431 : {
1432 30353 : if (!conn->oauth_token)
1433 30353 : return;
1434 :
1435 0 : explicit_bzero(conn->oauth_token, strlen(conn->oauth_token));
1436 0 : free(conn->oauth_token);
1437 0 : conn->oauth_token = NULL;
1438 : }
1439 :
1440 : /*
1441 : * Returns true if the PGOAUTHDEBUG=UNSAFE flag is set in the environment.
1442 : */
1443 : bool
1444 0 : oauth_unsafe_debugging_enabled(void)
1445 : {
1446 0 : const char *env = getenv("PGOAUTHDEBUG");
1447 :
1448 0 : return (env && strcmp(env, "UNSAFE") == 0);
1449 : }
1450 :
1451 : /*
1452 : * Hook v1 Poisoning
1453 : *
1454 : * Try to catch misuses of the v1 PQAUTHDATA_OAUTH_BEARER_TOKEN hook and its
1455 : * callbacks, which are not allowed to downcast their request argument to
1456 : * PGoauthBearerRequestV2. (Such clients may crash or worse when speaking to
1457 : * libpq 18.)
1458 : *
1459 : * This attempts to use Valgrind hooks, if present, to mark the extra members as
1460 : * inaccessible. For uninstrumented builds, it also munges request->issuer to
1461 : * try to crash clients that perform string operations, and it aborts if
1462 : * request->error is set.
1463 : */
1464 :
1465 : #define MASK_BITS ((uintptr_t) 0x55aa55aa55aa55aa)
1466 : #define POISON_MASK(ptr) ((void *) (((uintptr_t) ptr) ^ MASK_BITS))
1467 :
1468 : /*
1469 : * Workhorse for v2 request poisoning. This must be called exactly twice: once
1470 : * to poison, once to unpoison.
1471 : *
1472 : * NB: Unpoisoning must restore the request to its original state, because we
1473 : * might still switch back to a v2 implementation internally. Don't do anything
1474 : * destructive during the poison operation.
1475 : */
1476 : static void
1477 0 : poison_req_v2(PGoauthBearerRequestV2 *request, bool poison)
1478 : {
1479 : #ifdef USE_VALGRIND
1480 : void *const base = (char *) request + sizeof(request->v1);
1481 : const size_t len = sizeof(*request) - sizeof(request->v1);
1482 : #endif
1483 :
1484 0 : if (poison)
1485 : {
1486 : /* Poison request->issuer with a mask to help uninstrumented builds. */
1487 0 : request->issuer = POISON_MASK(request->issuer);
1488 :
1489 : /*
1490 : * We'll check to make sure request->error wasn't assigned when
1491 : * unpoisoning, so it had better not be assigned now.
1492 : */
1493 : Assert(!request->error);
1494 :
1495 : VALGRIND_MAKE_MEM_NOACCESS(base, len);
1496 : }
1497 : else
1498 : {
1499 : /*
1500 : * XXX Using DEFINED here is technically too lax; we might catch
1501 : * struct padding in the blast radius. But since this API has to
1502 : * poison stack addresses, and Valgrind can't track/manage undefined
1503 : * stack regions, we can't be any stricter without tracking the
1504 : * original state of the memory.
1505 : */
1506 : VALGRIND_MAKE_MEM_DEFINED(base, len);
1507 :
1508 : /* Undo our mask. */
1509 0 : request->issuer = POISON_MASK(request->issuer);
1510 :
1511 : /*
1512 : * For uninstrumented builds, make sure request->error wasn't touched.
1513 : */
1514 0 : if (request->error)
1515 : {
1516 0 : fprintf(stderr,
1517 : "abort! out-of-bounds write to PGoauthBearerRequest by PQAUTHDATA_OAUTH_BEARER_TOKEN hook\n");
1518 0 : abort();
1519 : }
1520 : }
1521 0 : }
1522 :
1523 : /*
1524 : * Wrapper around PGoauthBearerRequest.async() which applies poison during the
1525 : * callback when necessary.
1526 : */
1527 : static PostgresPollingStatusType
1528 0 : do_async(fe_oauth_state *state, PGoauthBearerRequestV2 *request)
1529 : {
1530 : PostgresPollingStatusType ret;
1531 0 : PGconn *conn = state->conn;
1532 :
1533 : Assert(request->v1.async);
1534 :
1535 0 : if (state->v1)
1536 0 : poison_req_v2(request, true);
1537 :
1538 0 : ret = request->v1.async(conn,
1539 : (PGoauthBearerRequest *) request,
1540 0 : &conn->altsock);
1541 :
1542 0 : if (state->v1)
1543 0 : poison_req_v2(request, false);
1544 :
1545 0 : return ret;
1546 : }
1547 :
1548 : /*
1549 : * Similar wrapper for the optional PGoauthBearerRequest.cleanup() callback.
1550 : * Does nothing if one is not defined.
1551 : */
1552 : static void
1553 0 : do_cleanup(fe_oauth_state *state, PGoauthBearerRequestV2 *request)
1554 : {
1555 0 : if (!request->v1.cleanup)
1556 0 : return;
1557 :
1558 0 : if (state->v1)
1559 0 : poison_req_v2(request, true);
1560 :
1561 0 : request->v1.cleanup(state->conn, (PGoauthBearerRequest *) request);
1562 :
1563 0 : if (state->v1)
1564 0 : poison_req_v2(request, false);
1565 : }
|