PostgreSQL Source Code git master
fe-auth-oauth.c
Go to the documentation of this file.
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-2025, 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
31/* The exported OAuth callback mechanism. */
32static void *oauth_init(PGconn *conn, const char *password,
33 const char *sasl_mechanism);
34static SASLStatus oauth_exchange(void *opaq, bool final,
35 char *input, int inputlen,
36 char **output, int *outputlen);
37static bool oauth_channel_bound(void *opaq);
38static void oauth_free(void *opaq);
39
45};
46
47/*
48 * Initializes mechanism state for OAUTHBEARER.
49 *
50 * For a full description of the API, see libpq/fe-auth-sasl.h.
51 */
52static void *
54 const char *sasl_mechanism)
55{
57
58 /*
59 * We only support one SASL mechanism here; anything else is programmer
60 * error.
61 */
62 Assert(sasl_mechanism != NULL);
63 Assert(strcmp(sasl_mechanism, OAUTHBEARER_NAME) == 0);
64
65 state = calloc(1, sizeof(*state));
66 if (!state)
67 return NULL;
68
69 state->step = FE_OAUTH_INIT;
70 state->conn = conn;
71
72 return state;
73}
74
75/*
76 * Frees the state allocated by oauth_init().
77 *
78 * This handles only mechanism state tied to the connection lifetime; state
79 * stored in state->async_ctx is freed up either immediately after the
80 * authentication handshake succeeds, or before the mechanism is cleaned up on
81 * failure. See pg_fe_cleanup_oauth_flow() and cleanup_user_oauth_flow().
82 */
83static void
84oauth_free(void *opaq)
85{
86 fe_oauth_state *state = opaq;
87
88 /* Any async authentication state should have been cleaned up already. */
89 Assert(!state->async_ctx);
90
91 free(state);
92}
93
94#define kvsep "\x01"
95
96/*
97 * Constructs an OAUTHBEARER client initial response (RFC 7628, Sec. 3.1).
98 *
99 * If discover is true, the initial response will contain a request for the
100 * server's required OAuth parameters (Sec. 4.3). Otherwise, conn->token must
101 * be set; it will be sent as the connection's bearer token.
102 *
103 * Returns the response as a null-terminated string, or NULL on error.
104 */
105static char *
107{
108 static const char *const resp_format = "n,," kvsep "auth=%s%s" kvsep kvsep;
109
111 const char *authn_scheme;
112 char *response = NULL;
113 const char *token = conn->oauth_token;
114
115 if (discover)
116 {
117 /* Parameter discovery uses a completely empty auth value. */
118 authn_scheme = token = "";
119 }
120 else
121 {
122 /*
123 * Use a Bearer authentication scheme (RFC 6750, Sec. 2.1). A trailing
124 * space is used as a separator.
125 */
126 authn_scheme = "Bearer ";
127
128 /* conn->token must have been set in this case. */
129 if (!token)
130 {
131 Assert(false);
133 "internal error: no OAuth token was set for the connection");
134 return NULL;
135 }
136 }
137
139 appendPQExpBuffer(&buf, resp_format, authn_scheme, token);
140
142 response = strdup(buf.data);
144
145 if (!response)
146 libpq_append_conn_error(conn, "out of memory");
147
148 return response;
149}
150
151/*
152 * JSON Parser (for the OAUTHBEARER error result)
153 */
154
155/* Relevant JSON fields in the error result object. */
156#define ERROR_STATUS_FIELD "status"
157#define ERROR_SCOPE_FIELD "scope"
158#define ERROR_OPENID_CONFIGURATION_FIELD "openid-configuration"
159
160/*
161 * Limit the maximum number of nested objects/arrays. Because OAUTHBEARER
162 * doesn't have any defined extensions for its JSON yet, we can be much more
163 * conservative here than with libpq-oauth's MAX_OAUTH_NESTING_LEVEL; we expect
164 * a nesting level of 1 in practice.
165 */
166#define MAX_SASL_NESTING_LEVEL 8
167
169{
170 char *errmsg; /* any non-NULL value stops all processing */
171 PQExpBufferData errbuf; /* backing memory for errmsg */
172 int nested; /* nesting level (zero is the top) */
173
174 const char *target_field_name; /* points to a static allocation */
175 char **target_field; /* see below */
176
177 /* target_field, if set, points to one of the following: */
178 char *status;
179 char *scope;
181};
182
183#define oauth_json_has_error(ctx) \
184 (PQExpBufferDataBroken((ctx)->errbuf) || (ctx)->errmsg)
185
186#define oauth_json_set_error(ctx, ...) \
187 do { \
188 appendPQExpBuffer(&(ctx)->errbuf, __VA_ARGS__); \
189 (ctx)->errmsg = (ctx)->errbuf.data; \
190 } while (0)
191
194{
195 struct json_ctx *ctx = state;
196
197 if (ctx->target_field)
198 {
199 Assert(ctx->nested == 1);
200
202 libpq_gettext("field \"%s\" must be a string"),
203 ctx->target_field_name);
204 }
205
206 ++ctx->nested;
208 oauth_json_set_error(ctx, libpq_gettext("JSON is too deeply nested"));
209
211}
212
215{
216 struct json_ctx *ctx = state;
217
218 --ctx->nested;
219 return JSON_SUCCESS;
220}
221
223oauth_json_object_field_start(void *state, char *name, bool isnull)
224{
225 struct json_ctx *ctx = state;
226
227 /* Only top-level keys are considered. */
228 if (ctx->nested == 1)
229 {
230 if (strcmp(name, ERROR_STATUS_FIELD) == 0)
231 {
233 ctx->target_field = &ctx->status;
234 }
235 else if (strcmp(name, ERROR_SCOPE_FIELD) == 0)
236 {
238 ctx->target_field = &ctx->scope;
239 }
240 else if (strcmp(name, ERROR_OPENID_CONFIGURATION_FIELD) == 0)
241 {
243 ctx->target_field = &ctx->discovery_uri;
244 }
245 }
246
247 return JSON_SUCCESS;
248}
249
252{
253 struct json_ctx *ctx = state;
254
255 if (!ctx->nested)
256 {
257 ctx->errmsg = libpq_gettext("top-level element must be an object");
258 }
259 else if (ctx->target_field)
260 {
261 Assert(ctx->nested == 1);
262
264 libpq_gettext("field \"%s\" must be a string"),
265 ctx->target_field_name);
266 }
267
268 ++ctx->nested;
270 oauth_json_set_error(ctx, libpq_gettext("JSON is too deeply nested"));
271
273}
274
277{
278 struct json_ctx *ctx = state;
279
280 --ctx->nested;
281 return JSON_SUCCESS;
282}
283
286{
287 struct json_ctx *ctx = state;
288
289 if (!ctx->nested)
290 {
291 ctx->errmsg = libpq_gettext("top-level element must be an object");
293 }
294
295 if (ctx->target_field)
296 {
297 if (ctx->nested != 1)
298 {
299 /*
300 * ctx->target_field should not have been set for nested keys.
301 * Assert and don't continue any further for production builds.
302 */
303 Assert(false);
305 "internal error: target scalar found at nesting level %d during OAUTHBEARER parsing",
306 ctx->nested);
308 }
309
310 /*
311 * We don't allow duplicate field names; error out if the target has
312 * already been set.
313 */
314 if (*ctx->target_field)
315 {
317 libpq_gettext("field \"%s\" is duplicated"),
318 ctx->target_field_name);
320 }
321
322 /* The only fields we support are strings. */
323 if (type != JSON_TOKEN_STRING)
324 {
326 libpq_gettext("field \"%s\" must be a string"),
327 ctx->target_field_name);
329 }
330
331 *ctx->target_field = strdup(token);
332 if (!*ctx->target_field)
333 return JSON_OUT_OF_MEMORY;
334
335 ctx->target_field = NULL;
336 ctx->target_field_name = NULL;
337 }
338 else
339 {
340 /* otherwise we just ignore it */
341 }
342
343 return JSON_SUCCESS;
344}
345
346#define HTTPS_SCHEME "https://"
347#define HTTP_SCHEME "http://"
348
349/* We support both well-known suffixes defined by RFC 8414. */
350#define WK_PREFIX "/.well-known/"
351#define OPENID_WK_SUFFIX "openid-configuration"
352#define OAUTH_WK_SUFFIX "oauth-authorization-server"
353
354/*
355 * Derives an issuer identifier from one of our recognized .well-known URIs,
356 * using the rules in RFC 8414.
357 */
358static char *
360{
361 const char *authority_start = NULL;
362 const char *wk_start;
363 const char *wk_end;
364 char *issuer;
365 ptrdiff_t start_offset,
366 end_offset;
367 size_t end_len;
368
369 /*
370 * https:// is required for issuer identifiers (RFC 8414, Sec. 2; OIDC
371 * Discovery 1.0, Sec. 3). This is a case-insensitive comparison at this
372 * level (but issuer identifier comparison at the level above this is
373 * case-sensitive, so in practice it's probably moot).
374 */
375 if (pg_strncasecmp(wkuri, HTTPS_SCHEME, strlen(HTTPS_SCHEME)) == 0)
376 authority_start = wkuri + strlen(HTTPS_SCHEME);
377
378 if (!authority_start
380 && pg_strncasecmp(wkuri, HTTP_SCHEME, strlen(HTTP_SCHEME)) == 0)
381 {
382 /* Allow http:// for testing only. */
383 authority_start = wkuri + strlen(HTTP_SCHEME);
384 }
385
386 if (!authority_start)
387 {
389 "OAuth discovery URI \"%s\" must use HTTPS",
390 wkuri);
391 return NULL;
392 }
393
394 /*
395 * Well-known URIs in general may support queries and fragments, but the
396 * two types we support here do not. (They must be constructed from the
397 * components of issuer identifiers, which themselves may not contain any
398 * queries or fragments.)
399 *
400 * It's important to check this first, to avoid getting tricked later by a
401 * prefix buried inside a query or fragment.
402 */
403 if (strpbrk(authority_start, "?#") != NULL)
404 {
406 "OAuth discovery URI \"%s\" must not contain query or fragment components",
407 wkuri);
408 return NULL;
409 }
410
411 /*
412 * Find the start of the .well-known prefix. IETF rules (RFC 8615) state
413 * this must be at the beginning of the path component, but OIDC defined
414 * it at the end instead (OIDC Discovery 1.0, Sec. 4), so we have to
415 * search for it anywhere.
416 */
417 wk_start = strstr(authority_start, WK_PREFIX);
418 if (!wk_start)
419 {
421 "OAuth discovery URI \"%s\" is not a .well-known URI",
422 wkuri);
423 return NULL;
424 }
425
426 /*
427 * Now find the suffix type. We only support the two defined in OIDC
428 * Discovery 1.0 and RFC 8414.
429 */
430 wk_end = wk_start + strlen(WK_PREFIX);
431
432 if (strncmp(wk_end, OPENID_WK_SUFFIX, strlen(OPENID_WK_SUFFIX)) == 0)
433 wk_end += strlen(OPENID_WK_SUFFIX);
434 else if (strncmp(wk_end, OAUTH_WK_SUFFIX, strlen(OAUTH_WK_SUFFIX)) == 0)
435 wk_end += strlen(OAUTH_WK_SUFFIX);
436 else
437 wk_end = NULL;
438
439 /*
440 * Even if there's a match, we still need to check to make sure the suffix
441 * takes up the entire path segment, to weed out constructions like
442 * "/.well-known/openid-configuration-bad".
443 */
444 if (!wk_end || (*wk_end != '/' && *wk_end != '\0'))
445 {
447 "OAuth discovery URI \"%s\" uses an unsupported .well-known suffix",
448 wkuri);
449 return NULL;
450 }
451
452 /*
453 * Finally, make sure the .well-known components are provided either as a
454 * prefix (IETF style) or as a postfix (OIDC style). In other words,
455 * "https://localhost/a/.well-known/openid-configuration/b" is not allowed
456 * to claim association with "https://localhost/a/b".
457 */
458 if (*wk_end != '\0')
459 {
460 /*
461 * It's not at the end, so it's required to be at the beginning at the
462 * path. Find the starting slash.
463 */
464 const char *path_start;
465
466 path_start = strchr(authority_start, '/');
467 Assert(path_start); /* otherwise we wouldn't have found WK_PREFIX */
468
469 if (wk_start != path_start)
470 {
472 "OAuth discovery URI \"%s\" uses an invalid format",
473 wkuri);
474 return NULL;
475 }
476 }
477
478 /* Checks passed! Now build the issuer. */
479 issuer = strdup(wkuri);
480 if (!issuer)
481 {
482 libpq_append_conn_error(conn, "out of memory");
483 return NULL;
484 }
485
486 /*
487 * The .well-known components are from [wk_start, wk_end). Remove those to
488 * form the issuer ID, by shifting the path suffix (which may be empty)
489 * leftwards.
490 */
491 start_offset = wk_start - wkuri;
492 end_offset = wk_end - wkuri;
493 end_len = strlen(wk_end) + 1; /* move the NULL terminator too */
494
495 memmove(issuer + start_offset, issuer + end_offset, end_len);
496
497 return issuer;
498}
499
500/*
501 * Parses the server error result (RFC 7628, Sec. 3.2.2) contained in msg and
502 * stores any discovered openid_configuration and scope settings for the
503 * connection.
504 */
505static bool
506handle_oauth_sasl_error(PGconn *conn, const char *msg, int msglen)
507{
508 JsonLexContext *lex;
509 JsonSemAction sem = {0};
511 struct json_ctx ctx = {0};
512 char *errmsg = NULL;
513 bool success = false;
514
515 Assert(conn->oauth_issuer_id); /* ensured by setup_oauth_parameters() */
516
517 /* Sanity check. */
518 if (strlen(msg) != msglen)
519 {
521 "server's error message contained an embedded NULL, and was discarded");
522 return false;
523 }
524
525 /*
526 * pg_parse_json doesn't validate the incoming UTF-8, so we have to check
527 * that up front.
528 */
529 if (pg_encoding_verifymbstr(PG_UTF8, msg, msglen) != msglen)
530 {
532 "server's error response is not valid UTF-8");
533 return false;
534 }
535
536 lex = makeJsonLexContextCstringLen(NULL, msg, msglen, PG_UTF8, true);
537 setJsonLexContextOwnsTokens(lex, true); /* must not leak on error */
538
540 sem.semstate = &ctx;
541
548
549 err = pg_parse_json(lex, &sem);
550
552 {
554 errmsg = libpq_gettext("out of memory");
555 else if (ctx.errmsg)
556 errmsg = ctx.errmsg;
557 else
558 {
559 /*
560 * Developer error: one of the action callbacks didn't call
561 * oauth_json_set_error() before erroring out.
562 */
564 errmsg = "<unexpected empty error>";
565 }
566 }
567 else if (err != JSON_SUCCESS)
568 errmsg = json_errdetail(err, lex);
569
570 if (errmsg)
572 "failed to parse server's error response: %s",
573 errmsg);
574
575 /* Don't need the error buffer or the JSON lexer anymore. */
578
579 if (errmsg)
580 goto cleanup;
581
582 if (ctx.discovery_uri)
583 {
584 char *discovery_issuer;
585
586 /*
587 * The URI MUST correspond to our existing issuer, to avoid mix-ups.
588 *
589 * Issuer comparison is done byte-wise, rather than performing any URL
590 * normalization; this follows the suggestions for issuer comparison
591 * in RFC 9207 Sec. 2.4 (which requires simple string comparison) and
592 * vastly simplifies things. Since this is the key protection against
593 * a rogue server sending the client to an untrustworthy location,
594 * simpler is better.
595 */
596 discovery_issuer = issuer_from_well_known_uri(conn, ctx.discovery_uri);
597 if (!discovery_issuer)
598 goto cleanup; /* error message already set */
599
600 if (strcmp(conn->oauth_issuer_id, discovery_issuer) != 0)
601 {
603 "server's discovery document at %s (issuer \"%s\") is incompatible with oauth_issuer (%s)",
604 ctx.discovery_uri, discovery_issuer,
606
607 free(discovery_issuer);
608 goto cleanup;
609 }
610
611 free(discovery_issuer);
612
614 {
616 ctx.discovery_uri = NULL;
617 }
618 else
619 {
620 /* This must match the URI we'd previously determined. */
621 if (strcmp(conn->oauth_discovery_uri, ctx.discovery_uri) != 0)
622 {
624 "server's discovery document has moved to %s (previous location was %s)",
625 ctx.discovery_uri,
627 goto cleanup;
628 }
629 }
630 }
631
632 if (ctx.scope)
633 {
634 /* Servers may not override a previously set oauth_scope. */
635 if (!conn->oauth_scope)
636 {
637 conn->oauth_scope = ctx.scope;
638 ctx.scope = NULL;
639 }
640 }
641
642 if (!ctx.status)
643 {
645 "server sent error response without a status");
646 goto cleanup;
647 }
648
649 if (strcmp(ctx.status, "invalid_token") != 0)
650 {
651 /*
652 * invalid_token is the only error code we'll automatically retry for;
653 * otherwise, just bail out now.
654 */
656 "server rejected OAuth bearer token: %s",
657 ctx.status);
658 goto cleanup;
659 }
660
661 success = true;
662
663cleanup:
664 free(ctx.status);
665 free(ctx.scope);
666 free(ctx.discovery_uri);
667
668 return success;
669}
670
671/*
672 * Callback implementation of conn->async_auth() for a user-defined OAuth flow.
673 * Delegates the retrieval of the token to the application's async callback.
674 *
675 * This will be called multiple times as needed; the application is responsible
676 * for setting an altsock to signal and returning the correct PGRES_POLLING_*
677 * statuses for use by PQconnectPoll().
678 */
681{
683 PGoauthBearerRequest *request = state->async_ctx;
685
686 if (!request->async)
687 {
689 "user-defined OAuth flow provided neither a token nor an async callback");
691 }
692
693 status = request->async(conn, request, &conn->altsock);
695 {
696 libpq_append_conn_error(conn, "user-defined OAuth flow failed");
697 return status;
698 }
699 else if (status == PGRES_POLLING_OK)
700 {
701 /*
702 * We already have a token, so copy it into the conn. (We can't hold
703 * onto the original string, since it may not be safe for us to free()
704 * it.)
705 */
706 if (!request->token)
707 {
709 "user-defined OAuth flow did not provide a token");
711 }
712
713 conn->oauth_token = strdup(request->token);
714 if (!conn->oauth_token)
715 {
716 libpq_append_conn_error(conn, "out of memory");
718 }
719
720 return PGRES_POLLING_OK;
721 }
722
723 /* The hook wants the client to poll the altsock. Make sure it set one. */
725 {
727 "user-defined OAuth flow did not provide a socket for polling");
729 }
730
731 return status;
732}
733
734/*
735 * Cleanup callback for the async user flow. Delegates most of its job to the
736 * user-provided cleanup implementation, then disconnects the altsock.
737 */
738static void
740{
742 PGoauthBearerRequest *request = state->async_ctx;
743
744 Assert(request);
745
746 if (request->cleanup)
747 request->cleanup(conn, request);
749
750 free(request);
751 state->async_ctx = NULL;
752}
753
754/*-------------
755 * Builtin Flow
756 *
757 * There are three potential implementations of use_builtin_flow:
758 *
759 * 1) If the OAuth client is disabled at configuration time, return false.
760 * Dependent clients must provide their own flow.
761 * 2) If the OAuth client is enabled and USE_DYNAMIC_OAUTH is defined, dlopen()
762 * the libpq-oauth plugin and use its implementation.
763 * 3) Otherwise, use flow callbacks that are statically linked into the
764 * executable.
765 */
766
767#if !defined(USE_LIBCURL)
768
769/*
770 * This configuration doesn't support the builtin flow.
771 */
772
773bool
775{
776 return false;
777}
778
779#elif defined(USE_DYNAMIC_OAUTH)
780
781/*
782 * Use the builtin flow in the libpq-oauth plugin, which is loaded at runtime.
783 */
784
785typedef char *(*libpq_gettext_func) (const char *msgid);
786
787/*
788 * Define accessor/mutator shims to inject into libpq-oauth, so that it doesn't
789 * depend on the offsets within PGconn. (These have changed during minor version
790 * updates in the past.)
791 */
792
793#define DEFINE_GETTER(TYPE, MEMBER) \
794 typedef TYPE (*conn_ ## MEMBER ## _func) (PGconn *conn); \
795 static TYPE conn_ ## MEMBER(PGconn *conn) { return conn->MEMBER; }
796
797/* Like DEFINE_GETTER, but returns a pointer to the member. */
798#define DEFINE_GETTER_P(TYPE, MEMBER) \
799 typedef TYPE (*conn_ ## MEMBER ## _func) (PGconn *conn); \
800 static TYPE conn_ ## MEMBER(PGconn *conn) { return &conn->MEMBER; }
801
802#define DEFINE_SETTER(TYPE, MEMBER) \
803 typedef void (*set_conn_ ## MEMBER ## _func) (PGconn *conn, TYPE val); \
804 static void set_conn_ ## MEMBER(PGconn *conn, TYPE val) { conn->MEMBER = val; }
805
806DEFINE_GETTER_P(PQExpBuffer, errorMessage);
807DEFINE_GETTER(char *, oauth_client_id);
808DEFINE_GETTER(char *, oauth_client_secret);
809DEFINE_GETTER(char *, oauth_discovery_uri);
810DEFINE_GETTER(char *, oauth_issuer_id);
811DEFINE_GETTER(char *, oauth_scope);
812DEFINE_GETTER(fe_oauth_state *, sasl_state);
813
814DEFINE_SETTER(pgsocket, altsock);
815DEFINE_SETTER(char *, oauth_token);
816
817/*
818 * Loads the libpq-oauth plugin via dlopen(), initializes it, and plugs its
819 * callbacks into the connection's async auth handlers.
820 *
821 * Failure to load here results in a relatively quiet connection error, to
822 * handle the use case where the build supports loading a flow but a user does
823 * not want to install it. Troubleshooting of linker/loader failures can be done
824 * via PGOAUTHDEBUG.
825 */
826bool
828{
829 static bool initialized = false;
830 static pthread_mutex_t init_mutex = PTHREAD_MUTEX_INITIALIZER;
831 int lockerr;
832
833 void (*init) (pgthreadlock_t threadlock,
834 libpq_gettext_func gettext_impl,
835 conn_errorMessage_func errmsg_impl,
836 conn_oauth_client_id_func clientid_impl,
837 conn_oauth_client_secret_func clientsecret_impl,
838 conn_oauth_discovery_uri_func discoveryuri_impl,
839 conn_oauth_issuer_id_func issuerid_impl,
840 conn_oauth_scope_func scope_impl,
841 conn_sasl_state_func saslstate_impl,
842 set_conn_altsock_func setaltsock_impl,
843 set_conn_oauth_token_func settoken_impl);
845 void (*cleanup) (PGconn *conn);
846
847 /*
848 * On macOS only, load the module using its absolute install path; the
849 * standard search behavior is not very helpful for this use case. Unlike
850 * on other platforms, DYLD_LIBRARY_PATH is used as a fallback even with
851 * absolute paths (modulo SIP effects), so tests can continue to work.
852 *
853 * On the other platforms, load the module using only the basename, to
854 * rely on the runtime linker's standard search behavior.
855 */
856 const char *const module_name =
857#if defined(__darwin__)
858 LIBDIR "/libpq-oauth-" PG_MAJORVERSION DLSUFFIX;
859#else
860 "libpq-oauth-" PG_MAJORVERSION DLSUFFIX;
861#endif
862
863 state->builtin_flow = dlopen(module_name, RTLD_NOW | RTLD_LOCAL);
864 if (!state->builtin_flow)
865 {
866 /*
867 * For end users, this probably isn't an error condition, it just
868 * means the flow isn't installed. Developers and package maintainers
869 * may want to debug this via the PGOAUTHDEBUG envvar, though.
870 *
871 * Note that POSIX dlerror() isn't guaranteed to be threadsafe.
872 */
874 fprintf(stderr, "failed dlopen for libpq-oauth: %s\n", dlerror());
875
876 return false;
877 }
878
879 if ((init = dlsym(state->builtin_flow, "libpq_oauth_init")) == NULL
880 || (flow = dlsym(state->builtin_flow, "pg_fe_run_oauth_flow")) == NULL
881 || (cleanup = dlsym(state->builtin_flow, "pg_fe_cleanup_oauth_flow")) == NULL)
882 {
883 /*
884 * This is more of an error condition than the one above, but due to
885 * the dlerror() threadsafety issue, lock it behind PGOAUTHDEBUG too.
886 */
888 fprintf(stderr, "failed dlsym for libpq-oauth: %s\n", dlerror());
889
890 dlclose(state->builtin_flow);
891 return false;
892 }
893
894 /*
895 * Past this point, we do not unload the module. It stays in the process
896 * permanently.
897 */
898
899 /*
900 * We need to inject necessary function pointers into the module. This
901 * only needs to be done once -- even if the pointers are constant,
902 * assigning them while another thread is executing the flows feels like
903 * tempting fate.
904 */
905 if ((lockerr = pthread_mutex_lock(&init_mutex)) != 0)
906 {
907 /* Should not happen... but don't continue if it does. */
908 Assert(false);
909
910 libpq_append_conn_error(conn, "failed to lock mutex (%d)", lockerr);
911 return false;
912 }
913
914 if (!initialized)
915 {
917#ifdef ENABLE_NLS
919#else
920 NULL,
921#endif
931
932 initialized = true;
933 }
934
935 pthread_mutex_unlock(&init_mutex);
936
937 /* Set our asynchronous callbacks. */
938 conn->async_auth = flow;
940
941 return true;
942}
943
944#else
945
946/*
947 * Use the builtin flow in libpq-oauth.a (see libpq-oauth/oauth-curl.h).
948 */
949
952
953bool
955{
956 /* Set our asynchronous callbacks. */
959
960 return true;
961}
962
963#endif /* USE_LIBCURL */
964
965
966/*
967 * Chooses an OAuth client flow for the connection, which will retrieve a Bearer
968 * token for presentation to the server.
969 *
970 * If the application has registered a custom flow handler using
971 * PQAUTHDATA_OAUTH_BEARER_TOKEN, it may either return a token immediately (e.g.
972 * if it has one cached for immediate use), or set up for a series of
973 * asynchronous callbacks which will be managed by run_user_oauth_flow().
974 *
975 * If the default handler is used instead, a Device Authorization flow is used
976 * for the connection if support has been compiled in. (See
977 * fe-auth-oauth-curl.c for implementation details.)
978 *
979 * If neither a custom handler nor the builtin flow is available, the connection
980 * fails here.
981 */
982static bool
984{
985 int res;
986 PGoauthBearerRequest request = {
988 .scope = conn->oauth_scope,
989 };
990
992
993 /* The client may have overridden the OAuth flow. */
995 if (res > 0)
996 {
997 PGoauthBearerRequest *request_copy;
998
999 if (request.token)
1000 {
1001 /*
1002 * We already have a token, so copy it into the conn. (We can't
1003 * hold onto the original string, since it may not be safe for us
1004 * to free() it.)
1005 */
1006 conn->oauth_token = strdup(request.token);
1007 if (!conn->oauth_token)
1008 {
1009 libpq_append_conn_error(conn, "out of memory");
1010 goto fail;
1011 }
1012
1013 /* short-circuit */
1014 if (request.cleanup)
1015 request.cleanup(conn, &request);
1016 return true;
1017 }
1018
1019 request_copy = malloc(sizeof(*request_copy));
1020 if (!request_copy)
1021 {
1022 libpq_append_conn_error(conn, "out of memory");
1023 goto fail;
1024 }
1025
1026 *request_copy = request;
1027
1030 state->async_ctx = request_copy;
1031 }
1032 else if (res < 0)
1033 {
1034 libpq_append_conn_error(conn, "user-defined OAuth flow failed");
1035 goto fail;
1036 }
1037 else if (!use_builtin_flow(conn, state))
1038 {
1039 libpq_append_conn_error(conn, "no OAuth flows are available (try installing the libpq-oauth package)");
1040 goto fail;
1041 }
1042
1043 return true;
1044
1045fail:
1046 if (request.cleanup)
1047 request.cleanup(conn, &request);
1048 return false;
1049}
1050
1051/*
1052 * Fill in our issuer identifier (and discovery URI, if possible) using the
1053 * connection parameters. If conn->oauth_discovery_uri can't be populated in
1054 * this function, it will be requested from the server.
1055 */
1056static bool
1058{
1059 /*
1060 * This is the only function that sets conn->oauth_issuer_id. If a
1061 * previous connection attempt has already computed it, don't overwrite it
1062 * or the discovery URI. (There's no reason for them to change once
1063 * they're set, and handle_oauth_sasl_error() will fail the connection if
1064 * the server attempts to switch them on us later.)
1065 */
1066 if (conn->oauth_issuer_id)
1067 return true;
1068
1069 /*---
1070 * To talk to a server, we require the user to provide issuer and client
1071 * identifiers.
1072 *
1073 * While it's possible for an OAuth client to support multiple issuers, it
1074 * requires additional effort to make sure the flows in use are safe -- to
1075 * quote RFC 9207,
1076 *
1077 * OAuth clients that interact with only one authorization server are
1078 * not vulnerable to mix-up attacks. However, when such clients decide
1079 * to add support for a second authorization server in the future, they
1080 * become vulnerable and need to apply countermeasures to mix-up
1081 * attacks.
1082 *
1083 * For now, we allow only one.
1084 */
1086 {
1088 "server requires OAuth authentication, but oauth_issuer and oauth_client_id are not both set");
1089 return false;
1090 }
1091
1092 /*
1093 * oauth_issuer is interpreted differently if it's a well-known discovery
1094 * URI rather than just an issuer identifier.
1095 */
1096 if (strstr(conn->oauth_issuer, WK_PREFIX) != NULL)
1097 {
1098 /*
1099 * Convert the URI back to an issuer identifier. (This also performs
1100 * validation of the URI format.)
1101 */
1104 if (!conn->oauth_issuer_id)
1105 return false; /* error message already set */
1106
1109 {
1110 libpq_append_conn_error(conn, "out of memory");
1111 return false;
1112 }
1113 }
1114 else
1115 {
1116 /*
1117 * Treat oauth_issuer as an issuer identifier. We'll ask the server
1118 * for the discovery URI.
1119 */
1121 if (!conn->oauth_issuer_id)
1122 {
1123 libpq_append_conn_error(conn, "out of memory");
1124 return false;
1125 }
1126 }
1127
1128 return true;
1129}
1130
1131/*
1132 * Implements the OAUTHBEARER SASL exchange (RFC 7628, Sec. 3.2).
1133 *
1134 * If the necessary OAuth parameters are set up on the connection, this will run
1135 * the client flow asynchronously and present the resulting token to the server.
1136 * Otherwise, an empty discovery response will be sent and any parameters sent
1137 * back by the server will be stored for a second attempt.
1138 *
1139 * For a full description of the API, see libpq/sasl.h.
1140 */
1141static SASLStatus
1142oauth_exchange(void *opaq, bool final,
1143 char *input, int inputlen,
1144 char **output, int *outputlen)
1145{
1146 fe_oauth_state *state = opaq;
1147 PGconn *conn = state->conn;
1148 bool discover = false;
1149
1150 *output = NULL;
1151 *outputlen = 0;
1152
1153 switch (state->step)
1154 {
1155 case FE_OAUTH_INIT:
1156 /* We begin in the initial response phase. */
1157 Assert(inputlen == -1);
1158
1160 return SASL_FAILED;
1161
1162 if (conn->oauth_token)
1163 {
1164 /*
1165 * A previous connection already fetched the token; we'll use
1166 * it below.
1167 */
1168 }
1169 else if (conn->oauth_discovery_uri)
1170 {
1171 /*
1172 * We don't have a token, but we have a discovery URI already
1173 * stored. Decide whether we're using a user-provided OAuth
1174 * flow or the one we have built in.
1175 */
1177 return SASL_FAILED;
1178
1179 if (conn->oauth_token)
1180 {
1181 /*
1182 * A really smart user implementation may have already
1183 * given us the token (e.g. if there was an unexpired copy
1184 * already cached), and we can use it immediately.
1185 */
1186 }
1187 else
1188 {
1189 /*
1190 * Otherwise, we'll have to hand the connection over to
1191 * our OAuth implementation.
1192 *
1193 * This could take a while, since it generally involves a
1194 * user in the loop. To avoid consuming the server's
1195 * authentication timeout, we'll continue this handshake
1196 * to the end, so that the server can close its side of
1197 * the connection. We'll open a second connection later
1198 * once we've retrieved a token.
1199 */
1200 discover = true;
1201 }
1202 }
1203 else
1204 {
1205 /*
1206 * If we don't have a token, and we don't have a discovery URI
1207 * to be able to request a token, we ask the server for one
1208 * explicitly.
1209 */
1210 discover = true;
1211 }
1212
1213 /*
1214 * Generate an initial response. This either contains a token, if
1215 * we have one, or an empty discovery response which is doomed to
1216 * fail.
1217 */
1218 *output = client_initial_response(conn, discover);
1219 if (!*output)
1220 return SASL_FAILED;
1221
1222 *outputlen = strlen(*output);
1224
1225 if (conn->oauth_token)
1226 {
1227 /*
1228 * For the purposes of require_auth, our side of
1229 * authentication is done at this point; the server will
1230 * either accept the connection or send an error. Unlike
1231 * SCRAM, there is no additional server data to check upon
1232 * success.
1233 */
1234 conn->client_finished_auth = true;
1235 }
1236
1237 return SASL_CONTINUE;
1238
1240 if (final)
1241 {
1242 /*
1243 * OAUTHBEARER does not make use of additional data with a
1244 * successful SASL exchange, so we shouldn't get an
1245 * AuthenticationSASLFinal message.
1246 */
1248 "server sent unexpected additional OAuth data");
1249 return SASL_FAILED;
1250 }
1251
1252 /*
1253 * An error message was sent by the server. Respond with the
1254 * required dummy message (RFC 7628, sec. 3.2.3).
1255 */
1256 *output = strdup(kvsep);
1257 if (unlikely(!*output))
1258 {
1259 libpq_append_conn_error(conn, "out of memory");
1260 return SASL_FAILED;
1261 }
1262 *outputlen = strlen(*output); /* == 1 */
1263
1264 /* Grab the settings from discovery. */
1265 if (!handle_oauth_sasl_error(conn, input, inputlen))
1266 return SASL_FAILED;
1267
1268 if (conn->oauth_token)
1269 {
1270 /*
1271 * The server rejected our token. Continue onwards towards the
1272 * expected FATAL message, but mark our state to catch any
1273 * unexpected "success" from the server.
1274 */
1276 return SASL_CONTINUE;
1277 }
1278
1279 if (!conn->async_auth)
1280 {
1281 /*
1282 * No OAuth flow is set up yet. Did we get enough information
1283 * from the server to create one?
1284 */
1286 {
1288 "server requires OAuth authentication, but no discovery metadata was provided");
1289 return SASL_FAILED;
1290 }
1291
1292 /* Yes. Set up the flow now. */
1294 return SASL_FAILED;
1295
1296 if (conn->oauth_token)
1297 {
1298 /*
1299 * A token was available in a custom flow's cache. Skip
1300 * the asynchronous processing.
1301 */
1302 goto reconnect;
1303 }
1304 }
1305
1306 /*
1307 * Time to retrieve a token. This involves a number of HTTP
1308 * connections and timed waits, so we escape the synchronous auth
1309 * processing and tell PQconnectPoll to transfer control to our
1310 * async implementation.
1311 */
1312 Assert(conn->async_auth); /* should have been set already */
1314 return SASL_ASYNC;
1315
1317
1318 /*
1319 * We've returned successfully from token retrieval. Double-check
1320 * that we have what we need for the next connection.
1321 */
1322 if (!conn->oauth_token)
1323 {
1324 Assert(false); /* should have failed before this point! */
1326 "internal error: OAuth flow did not set a token");
1327 return SASL_FAILED;
1328 }
1329
1330 goto reconnect;
1331
1333
1334 /*
1335 * After an error, the server should send an error response to
1336 * fail the SASL handshake, which is handled in higher layers.
1337 *
1338 * If we get here, the server either sent *another* challenge
1339 * which isn't defined in the RFC, or completed the handshake
1340 * successfully after telling us it was going to fail. Neither is
1341 * acceptable.
1342 */
1344 "server sent additional OAuth data after error");
1345 return SASL_FAILED;
1346
1347 default:
1348 libpq_append_conn_error(conn, "invalid OAuth exchange state");
1349 break;
1350 }
1351
1352 Assert(false); /* should never get here */
1353 return SASL_FAILED;
1354
1355reconnect:
1356
1357 /*
1358 * Despite being a failure from the point of view of SASL, we have enough
1359 * information to restart with a new connection.
1360 */
1361 libpq_append_conn_error(conn, "retrying connection with new bearer token");
1362 conn->oauth_want_retry = true;
1363 return SASL_FAILED;
1364}
1365
1366static bool
1368{
1369 /* This mechanism does not support channel binding. */
1370 return false;
1371}
1372
1373/*
1374 * Fully clears out any stored OAuth token. This is done proactively upon
1375 * successful connection as well as during pqClosePGconn().
1376 */
1377void
1379{
1380 if (!conn->oauth_token)
1381 return;
1382
1385 conn->oauth_token = NULL;
1386}
1387
1388/*
1389 * Returns true if the PGOAUTHDEBUG=UNSAFE flag is set in the environment.
1390 */
1391bool
1393{
1394 const char *env = getenv("PGOAUTHDEBUG");
1395
1396 return (env && strcmp(env, "UNSAFE") == 0);
1397}
static void cleanup(void)
Definition: bootstrap.c:713
#define unlikely(x)
Definition: c.h:347
#define fprintf(file, fmt, msg)
Definition: cubescan.l:21
int errmsg(const char *fmt,...)
Definition: elog.c:1071
void err(int eval, const char *fmt,...)
Definition: err.c:43
#define HTTP_SCHEME
#define ERROR_SCOPE_FIELD
static bool setup_token_request(PGconn *conn, fe_oauth_state *state)
static JsonParseErrorType oauth_json_array_end(void *state)
static char * issuer_from_well_known_uri(PGconn *conn, const char *wkuri)
#define HTTPS_SCHEME
static bool handle_oauth_sasl_error(PGconn *conn, const char *msg, int msglen)
static void cleanup_user_oauth_flow(PGconn *conn)
static bool setup_oauth_parameters(PGconn *conn)
#define oauth_json_set_error(ctx,...)
#define WK_PREFIX
static JsonParseErrorType oauth_json_object_field_start(void *state, char *name, bool isnull)
static JsonParseErrorType oauth_json_scalar(void *state, char *token, JsonTokenType type)
const pg_fe_sasl_mech pg_oauth_mech
Definition: fe-auth-oauth.c:40
#define OPENID_WK_SUFFIX
static SASLStatus oauth_exchange(void *opaq, bool final, char *input, int inputlen, char **output, int *outputlen)
#define OAUTH_WK_SUFFIX
static bool oauth_channel_bound(void *opaq)
#define oauth_json_has_error(ctx)
static JsonParseErrorType oauth_json_array_start(void *state)
static JsonParseErrorType oauth_json_object_end(void *state)
static void oauth_free(void *opaq)
Definition: fe-auth-oauth.c:84
#define ERROR_OPENID_CONFIGURATION_FIELD
void pqClearOAuthToken(PGconn *conn)
static void * oauth_init(PGconn *conn, const char *password, const char *sasl_mechanism)
Definition: fe-auth-oauth.c:53
#define kvsep
Definition: fe-auth-oauth.c:94
static char * client_initial_response(PGconn *conn, bool discover)
bool use_builtin_flow(PGconn *conn, fe_oauth_state *state)
#define ERROR_STATUS_FIELD
#define MAX_SASL_NESTING_LEVEL
static JsonParseErrorType oauth_json_object_start(void *state)
static PostgresPollingStatusType run_user_oauth_flow(PGconn *conn)
bool oauth_unsafe_debugging_enabled(void)
@ FE_OAUTH_REQUESTING_TOKEN
Definition: fe-auth-oauth.h:26
@ FE_OAUTH_SERVER_ERROR
Definition: fe-auth-oauth.h:27
@ FE_OAUTH_INIT
Definition: fe-auth-oauth.h:24
@ FE_OAUTH_BEARER_SENT
Definition: fe-auth-oauth.h:25
SASLStatus
Definition: fe-auth-sasl.h:29
@ SASL_ASYNC
Definition: fe-auth-sasl.h:33
@ SASL_CONTINUE
Definition: fe-auth-sasl.h:32
@ SASL_FAILED
Definition: fe-auth-sasl.h:31
PQauthDataHook_type PQauthDataHook
Definition: fe-auth.c:1586
Assert(PointerIsAligned(start, uint64))
#define calloc(a, b)
Definition: header.h:55
#define free(a)
Definition: header.h:65
#define malloc(a)
Definition: header.h:50
FILE * input
FILE * output
static bool success
Definition: initdb.c:187
int init
Definition: isn.c:79
JsonParseErrorType pg_parse_json(JsonLexContext *lex, const JsonSemAction *sem)
Definition: jsonapi.c:744
JsonLexContext * makeJsonLexContextCstringLen(JsonLexContext *lex, const char *json, size_t len, int encoding, bool need_escapes)
Definition: jsonapi.c:392
void setJsonLexContextOwnsTokens(JsonLexContext *lex, bool owned_by_context)
Definition: jsonapi.c:542
char * json_errdetail(JsonParseErrorType error, JsonLexContext *lex)
Definition: jsonapi.c:2404
void freeJsonLexContext(JsonLexContext *lex)
Definition: jsonapi.c:687
JsonParseErrorType
Definition: jsonapi.h:35
@ JSON_OUT_OF_MEMORY
Definition: jsonapi.h:52
@ JSON_SEM_ACTION_FAILED
Definition: jsonapi.h:59
@ JSON_SUCCESS
Definition: jsonapi.h:36
JsonTokenType
Definition: jsonapi.h:18
@ JSON_TOKEN_STRING
Definition: jsonapi.h:20
void(* pgthreadlock_t)(int acquire)
Definition: libpq-fe.h:472
PostgresPollingStatusType
Definition: libpq-fe.h:114
@ PGRES_POLLING_OK
Definition: libpq-fe.h:118
@ PGRES_POLLING_FAILED
Definition: libpq-fe.h:115
@ PQAUTHDATA_OAUTH_BEARER_TOKEN
Definition: libpq-fe.h:196
#define OAUTHBEARER_NAME
Definition: oauth-common.h:17
PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn)
Definition: oauth-curl.c:2932
void pg_fe_cleanup_oauth_flow(PGconn *conn)
Definition: oauth-curl.c:354
set_conn_oauth_token_func set_conn_oauth_token
Definition: oauth-utils.c:47
conn_oauth_client_secret_func conn_oauth_client_secret
Definition: oauth-utils.c:40
conn_sasl_state_func conn_sasl_state
Definition: oauth-utils.c:44
conn_oauth_client_id_func conn_oauth_client_id
Definition: oauth-utils.c:39
conn_oauth_scope_func conn_oauth_scope
Definition: oauth-utils.c:43
pgthreadlock_t pg_g_threadlock
Definition: oauth-utils.c:35
conn_oauth_issuer_id_func conn_oauth_issuer_id
Definition: oauth-utils.c:42
set_conn_altsock_func set_conn_altsock
Definition: oauth-utils.c:46
conn_oauth_discovery_uri_func conn_oauth_discovery_uri
Definition: oauth-utils.c:41
void libpq_append_conn_error(PGconn *conn, const char *fmt,...)
Definition: oauth-utils.c:95
conn_errorMessage_func conn_errorMessage
Definition: oauth-utils.c:38
#define libpq_gettext(x)
Definition: oauth-utils.h:86
char *(* libpq_gettext_func)(const char *msgid)
Definition: oauth-utils.h:51
static char * buf
Definition: pg_test_fsync.c:72
@ PG_UTF8
Definition: pg_wchar.h:232
void explicit_bzero(void *buf, size_t len)
int pgsocket
Definition: port.h:29
#define PGINVALID_SOCKET
Definition: port.h:31
int pg_strncasecmp(const char *s1, const char *s2, size_t n)
Definition: pgstrcasecmp.c:69
void initPQExpBuffer(PQExpBuffer str)
Definition: pqexpbuffer.c:90
void appendPQExpBuffer(PQExpBuffer str, const char *fmt,...)
Definition: pqexpbuffer.c:265
void termPQExpBuffer(PQExpBuffer str)
Definition: pqexpbuffer.c:129
#define PQExpBufferDataBroken(buf)
Definition: pqexpbuffer.h:67
int pthread_mutex_unlock(pthread_mutex_t *mp)
Definition: pthread-win32.c:60
int pthread_mutex_lock(pthread_mutex_t *mp)
Definition: pthread-win32.c:42
#define PTHREAD_MUTEX_INITIALIZER
Definition: pthread-win32.h:16
static char * password
Definition: streamutil.c:51
PGconn * conn
Definition: streamutil.c:52
json_struct_action array_end
Definition: jsonapi.h:157
json_struct_action object_start
Definition: jsonapi.h:154
json_ofield_action object_field_start
Definition: jsonapi.h:158
json_scalar_action scalar
Definition: jsonapi.h:162
void * semstate
Definition: jsonapi.h:153
json_struct_action array_start
Definition: jsonapi.h:156
json_struct_action object_end
Definition: jsonapi.h:155
void(* cleanup)(PGconn *conn, struct PGoauthBearerRequest *request)
Definition: libpq-fe.h:787
const char * openid_configuration
Definition: libpq-fe.h:755
PostgresPollingStatusType(* async)(PGconn *conn, struct PGoauthBearerRequest *request, SOCKTYPE *altsock)
Definition: libpq-fe.h:776
char * discovery_uri
const char * target_field_name
char * status
char * scope
PQExpBufferData errbuf
char ** target_field
char * errmsg
char * oauth_discovery_uri
Definition: libpq-int.h:438
char * oauth_scope
Definition: libpq-int.h:442
void(* cleanup_async_auth)(PGconn *conn)
Definition: libpq-int.h:526
bool client_finished_auth
Definition: libpq-int.h:518
char * oauth_client_id
Definition: libpq-int.h:440
char * oauth_issuer
Definition: libpq-int.h:436
bool oauth_want_retry
Definition: libpq-int.h:444
char * oauth_token
Definition: libpq-int.h:443
char * oauth_issuer_id
Definition: libpq-int.h:437
pgsocket altsock
Definition: libpq-int.h:527
PostgresPollingStatusType(* async_auth)(PGconn *conn)
Definition: libpq-int.h:525
void * sasl_state
Definition: libpq-int.h:600
Definition: regguts.h:323
static JsonSemAction sem
const char * type
const char * name
int pg_encoding_verifymbstr(int encoding, const char *mbstr, int len)
Definition: wchar.c:2202
void * dlopen(const char *file, int mode)
Definition: win32dlopen.c:76
char * dlerror(void)
Definition: win32dlopen.c:40
void * dlsym(void *handle, const char *symbol)
Definition: win32dlopen.c:61
#define RTLD_NOW
Definition: win32_port.h:533
int dlclose(void *handle)
Definition: win32dlopen.c:49
static bool initialized
Definition: win32ntdll.c:36