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
161{
162 char *errmsg; /* any non-NULL value stops all processing */
163 PQExpBufferData errbuf; /* backing memory for errmsg */
164 int nested; /* nesting level (zero is the top) */
165
166 const char *target_field_name; /* points to a static allocation */
167 char **target_field; /* see below */
168
169 /* target_field, if set, points to one of the following: */
170 char *status;
171 char *scope;
173};
174
175#define oauth_json_has_error(ctx) \
176 (PQExpBufferDataBroken((ctx)->errbuf) || (ctx)->errmsg)
177
178#define oauth_json_set_error(ctx, ...) \
179 do { \
180 appendPQExpBuffer(&(ctx)->errbuf, __VA_ARGS__); \
181 (ctx)->errmsg = (ctx)->errbuf.data; \
182 } while (0)
183
186{
187 struct json_ctx *ctx = state;
188
189 if (ctx->target_field)
190 {
191 Assert(ctx->nested == 1);
192
194 libpq_gettext("field \"%s\" must be a string"),
195 ctx->target_field_name);
196 }
197
198 ++ctx->nested;
200}
201
204{
205 struct json_ctx *ctx = state;
206
207 --ctx->nested;
208 return JSON_SUCCESS;
209}
210
212oauth_json_object_field_start(void *state, char *name, bool isnull)
213{
214 struct json_ctx *ctx = state;
215
216 /* Only top-level keys are considered. */
217 if (ctx->nested == 1)
218 {
219 if (strcmp(name, ERROR_STATUS_FIELD) == 0)
220 {
222 ctx->target_field = &ctx->status;
223 }
224 else if (strcmp(name, ERROR_SCOPE_FIELD) == 0)
225 {
227 ctx->target_field = &ctx->scope;
228 }
229 else if (strcmp(name, ERROR_OPENID_CONFIGURATION_FIELD) == 0)
230 {
232 ctx->target_field = &ctx->discovery_uri;
233 }
234 }
235
236 return JSON_SUCCESS;
237}
238
241{
242 struct json_ctx *ctx = state;
243
244 if (!ctx->nested)
245 {
246 ctx->errmsg = libpq_gettext("top-level element must be an object");
247 }
248 else if (ctx->target_field)
249 {
250 Assert(ctx->nested == 1);
251
253 libpq_gettext("field \"%s\" must be a string"),
254 ctx->target_field_name);
255 }
256
258}
259
262{
263 struct json_ctx *ctx = state;
264
265 if (!ctx->nested)
266 {
267 ctx->errmsg = libpq_gettext("top-level element must be an object");
269 }
270
271 if (ctx->target_field)
272 {
273 if (ctx->nested != 1)
274 {
275 /*
276 * ctx->target_field should not have been set for nested keys.
277 * Assert and don't continue any further for production builds.
278 */
279 Assert(false);
281 "internal error: target scalar found at nesting level %d during OAUTHBEARER parsing",
282 ctx->nested);
284 }
285
286 /*
287 * We don't allow duplicate field names; error out if the target has
288 * already been set.
289 */
290 if (*ctx->target_field)
291 {
293 libpq_gettext("field \"%s\" is duplicated"),
294 ctx->target_field_name);
296 }
297
298 /* The only fields we support are strings. */
299 if (type != JSON_TOKEN_STRING)
300 {
302 libpq_gettext("field \"%s\" must be a string"),
303 ctx->target_field_name);
305 }
306
307 *ctx->target_field = strdup(token);
308 if (!*ctx->target_field)
309 return JSON_OUT_OF_MEMORY;
310
311 ctx->target_field = NULL;
312 ctx->target_field_name = NULL;
313 }
314 else
315 {
316 /* otherwise we just ignore it */
317 }
318
319 return JSON_SUCCESS;
320}
321
322#define HTTPS_SCHEME "https://"
323#define HTTP_SCHEME "http://"
324
325/* We support both well-known suffixes defined by RFC 8414. */
326#define WK_PREFIX "/.well-known/"
327#define OPENID_WK_SUFFIX "openid-configuration"
328#define OAUTH_WK_SUFFIX "oauth-authorization-server"
329
330/*
331 * Derives an issuer identifier from one of our recognized .well-known URIs,
332 * using the rules in RFC 8414.
333 */
334static char *
336{
337 const char *authority_start = NULL;
338 const char *wk_start;
339 const char *wk_end;
340 char *issuer;
341 ptrdiff_t start_offset,
342 end_offset;
343 size_t end_len;
344
345 /*
346 * https:// is required for issuer identifiers (RFC 8414, Sec. 2; OIDC
347 * Discovery 1.0, Sec. 3). This is a case-insensitive comparison at this
348 * level (but issuer identifier comparison at the level above this is
349 * case-sensitive, so in practice it's probably moot).
350 */
351 if (pg_strncasecmp(wkuri, HTTPS_SCHEME, strlen(HTTPS_SCHEME)) == 0)
352 authority_start = wkuri + strlen(HTTPS_SCHEME);
353
354 if (!authority_start
356 && pg_strncasecmp(wkuri, HTTP_SCHEME, strlen(HTTP_SCHEME)) == 0)
357 {
358 /* Allow http:// for testing only. */
359 authority_start = wkuri + strlen(HTTP_SCHEME);
360 }
361
362 if (!authority_start)
363 {
365 "OAuth discovery URI \"%s\" must use HTTPS",
366 wkuri);
367 return NULL;
368 }
369
370 /*
371 * Well-known URIs in general may support queries and fragments, but the
372 * two types we support here do not. (They must be constructed from the
373 * components of issuer identifiers, which themselves may not contain any
374 * queries or fragments.)
375 *
376 * It's important to check this first, to avoid getting tricked later by a
377 * prefix buried inside a query or fragment.
378 */
379 if (strpbrk(authority_start, "?#") != NULL)
380 {
382 "OAuth discovery URI \"%s\" must not contain query or fragment components",
383 wkuri);
384 return NULL;
385 }
386
387 /*
388 * Find the start of the .well-known prefix. IETF rules (RFC 8615) state
389 * this must be at the beginning of the path component, but OIDC defined
390 * it at the end instead (OIDC Discovery 1.0, Sec. 4), so we have to
391 * search for it anywhere.
392 */
393 wk_start = strstr(authority_start, WK_PREFIX);
394 if (!wk_start)
395 {
397 "OAuth discovery URI \"%s\" is not a .well-known URI",
398 wkuri);
399 return NULL;
400 }
401
402 /*
403 * Now find the suffix type. We only support the two defined in OIDC
404 * Discovery 1.0 and RFC 8414.
405 */
406 wk_end = wk_start + strlen(WK_PREFIX);
407
408 if (strncmp(wk_end, OPENID_WK_SUFFIX, strlen(OPENID_WK_SUFFIX)) == 0)
409 wk_end += strlen(OPENID_WK_SUFFIX);
410 else if (strncmp(wk_end, OAUTH_WK_SUFFIX, strlen(OAUTH_WK_SUFFIX)) == 0)
411 wk_end += strlen(OAUTH_WK_SUFFIX);
412 else
413 wk_end = NULL;
414
415 /*
416 * Even if there's a match, we still need to check to make sure the suffix
417 * takes up the entire path segment, to weed out constructions like
418 * "/.well-known/openid-configuration-bad".
419 */
420 if (!wk_end || (*wk_end != '/' && *wk_end != '\0'))
421 {
423 "OAuth discovery URI \"%s\" uses an unsupported .well-known suffix",
424 wkuri);
425 return NULL;
426 }
427
428 /*
429 * Finally, make sure the .well-known components are provided either as a
430 * prefix (IETF style) or as a postfix (OIDC style). In other words,
431 * "https://localhost/a/.well-known/openid-configuration/b" is not allowed
432 * to claim association with "https://localhost/a/b".
433 */
434 if (*wk_end != '\0')
435 {
436 /*
437 * It's not at the end, so it's required to be at the beginning at the
438 * path. Find the starting slash.
439 */
440 const char *path_start;
441
442 path_start = strchr(authority_start, '/');
443 Assert(path_start); /* otherwise we wouldn't have found WK_PREFIX */
444
445 if (wk_start != path_start)
446 {
448 "OAuth discovery URI \"%s\" uses an invalid format",
449 wkuri);
450 return NULL;
451 }
452 }
453
454 /* Checks passed! Now build the issuer. */
455 issuer = strdup(wkuri);
456 if (!issuer)
457 {
458 libpq_append_conn_error(conn, "out of memory");
459 return NULL;
460 }
461
462 /*
463 * The .well-known components are from [wk_start, wk_end). Remove those to
464 * form the issuer ID, by shifting the path suffix (which may be empty)
465 * leftwards.
466 */
467 start_offset = wk_start - wkuri;
468 end_offset = wk_end - wkuri;
469 end_len = strlen(wk_end) + 1; /* move the NULL terminator too */
470
471 memmove(issuer + start_offset, issuer + end_offset, end_len);
472
473 return issuer;
474}
475
476/*
477 * Parses the server error result (RFC 7628, Sec. 3.2.2) contained in msg and
478 * stores any discovered openid_configuration and scope settings for the
479 * connection.
480 */
481static bool
482handle_oauth_sasl_error(PGconn *conn, const char *msg, int msglen)
483{
484 JsonLexContext *lex;
485 JsonSemAction sem = {0};
487 struct json_ctx ctx = {0};
488 char *errmsg = NULL;
489 bool success = false;
490
491 Assert(conn->oauth_issuer_id); /* ensured by setup_oauth_parameters() */
492
493 /* Sanity check. */
494 if (strlen(msg) != msglen)
495 {
497 "server's error message contained an embedded NULL, and was discarded");
498 return false;
499 }
500
501 /*
502 * pg_parse_json doesn't validate the incoming UTF-8, so we have to check
503 * that up front.
504 */
505 if (pg_encoding_verifymbstr(PG_UTF8, msg, msglen) != msglen)
506 {
508 "server's error response is not valid UTF-8");
509 return false;
510 }
511
512 lex = makeJsonLexContextCstringLen(NULL, msg, msglen, PG_UTF8, true);
513 setJsonLexContextOwnsTokens(lex, true); /* must not leak on error */
514
516 sem.semstate = &ctx;
517
523
524 err = pg_parse_json(lex, &sem);
525
527 {
529 errmsg = libpq_gettext("out of memory");
530 else if (ctx.errmsg)
531 errmsg = ctx.errmsg;
532 else
533 {
534 /*
535 * Developer error: one of the action callbacks didn't call
536 * oauth_json_set_error() before erroring out.
537 */
539 errmsg = "<unexpected empty error>";
540 }
541 }
542 else if (err != JSON_SUCCESS)
543 errmsg = json_errdetail(err, lex);
544
545 if (errmsg)
547 "failed to parse server's error response: %s",
548 errmsg);
549
550 /* Don't need the error buffer or the JSON lexer anymore. */
553
554 if (errmsg)
555 goto cleanup;
556
557 if (ctx.discovery_uri)
558 {
559 char *discovery_issuer;
560
561 /*
562 * The URI MUST correspond to our existing issuer, to avoid mix-ups.
563 *
564 * Issuer comparison is done byte-wise, rather than performing any URL
565 * normalization; this follows the suggestions for issuer comparison
566 * in RFC 9207 Sec. 2.4 (which requires simple string comparison) and
567 * vastly simplifies things. Since this is the key protection against
568 * a rogue server sending the client to an untrustworthy location,
569 * simpler is better.
570 */
571 discovery_issuer = issuer_from_well_known_uri(conn, ctx.discovery_uri);
572 if (!discovery_issuer)
573 goto cleanup; /* error message already set */
574
575 if (strcmp(conn->oauth_issuer_id, discovery_issuer) != 0)
576 {
578 "server's discovery document at %s (issuer \"%s\") is incompatible with oauth_issuer (%s)",
579 ctx.discovery_uri, discovery_issuer,
581
582 free(discovery_issuer);
583 goto cleanup;
584 }
585
586 free(discovery_issuer);
587
589 {
591 ctx.discovery_uri = NULL;
592 }
593 else
594 {
595 /* This must match the URI we'd previously determined. */
596 if (strcmp(conn->oauth_discovery_uri, ctx.discovery_uri) != 0)
597 {
599 "server's discovery document has moved to %s (previous location was %s)",
600 ctx.discovery_uri,
602 goto cleanup;
603 }
604 }
605 }
606
607 if (ctx.scope)
608 {
609 /* Servers may not override a previously set oauth_scope. */
610 if (!conn->oauth_scope)
611 {
612 conn->oauth_scope = ctx.scope;
613 ctx.scope = NULL;
614 }
615 }
616
617 if (!ctx.status)
618 {
620 "server sent error response without a status");
621 goto cleanup;
622 }
623
624 if (strcmp(ctx.status, "invalid_token") != 0)
625 {
626 /*
627 * invalid_token is the only error code we'll automatically retry for;
628 * otherwise, just bail out now.
629 */
631 "server rejected OAuth bearer token: %s",
632 ctx.status);
633 goto cleanup;
634 }
635
636 success = true;
637
638cleanup:
639 free(ctx.status);
640 free(ctx.scope);
641 free(ctx.discovery_uri);
642
643 return success;
644}
645
646/*
647 * Callback implementation of conn->async_auth() for a user-defined OAuth flow.
648 * Delegates the retrieval of the token to the application's async callback.
649 *
650 * This will be called multiple times as needed; the application is responsible
651 * for setting an altsock to signal and returning the correct PGRES_POLLING_*
652 * statuses for use by PQconnectPoll().
653 */
656{
658 PGoauthBearerRequest *request = state->async_ctx;
660
661 if (!request->async)
662 {
664 "user-defined OAuth flow provided neither a token nor an async callback");
666 }
667
668 status = request->async(conn, request, &conn->altsock);
670 {
671 libpq_append_conn_error(conn, "user-defined OAuth flow failed");
672 return status;
673 }
674 else if (status == PGRES_POLLING_OK)
675 {
676 /*
677 * We already have a token, so copy it into the conn. (We can't hold
678 * onto the original string, since it may not be safe for us to free()
679 * it.)
680 */
681 if (!request->token)
682 {
684 "user-defined OAuth flow did not provide a token");
686 }
687
688 conn->oauth_token = strdup(request->token);
689 if (!conn->oauth_token)
690 {
691 libpq_append_conn_error(conn, "out of memory");
693 }
694
695 return PGRES_POLLING_OK;
696 }
697
698 /* The hook wants the client to poll the altsock. Make sure it set one. */
700 {
702 "user-defined OAuth flow did not provide a socket for polling");
704 }
705
706 return status;
707}
708
709/*
710 * Cleanup callback for the async user flow. Delegates most of its job to the
711 * user-provided cleanup implementation, then disconnects the altsock.
712 */
713static void
715{
717 PGoauthBearerRequest *request = state->async_ctx;
718
719 Assert(request);
720
721 if (request->cleanup)
722 request->cleanup(conn, request);
724
725 free(request);
726 state->async_ctx = NULL;
727}
728
729/*-------------
730 * Builtin Flow
731 *
732 * There are three potential implementations of use_builtin_flow:
733 *
734 * 1) If the OAuth client is disabled at configuration time, return false.
735 * Dependent clients must provide their own flow.
736 * 2) If the OAuth client is enabled and USE_DYNAMIC_OAUTH is defined, dlopen()
737 * the libpq-oauth plugin and use its implementation.
738 * 3) Otherwise, use flow callbacks that are statically linked into the
739 * executable.
740 */
741
742#if !defined(USE_LIBCURL)
743
744/*
745 * This configuration doesn't support the builtin flow.
746 */
747
748bool
750{
751 return false;
752}
753
754#elif defined(USE_DYNAMIC_OAUTH)
755
756/*
757 * Use the builtin flow in the libpq-oauth plugin, which is loaded at runtime.
758 */
759
760typedef char *(*libpq_gettext_func) (const char *msgid);
761
762/*
763 * Define accessor/mutator shims to inject into libpq-oauth, so that it doesn't
764 * depend on the offsets within PGconn. (These have changed during minor version
765 * updates in the past.)
766 */
767
768#define DEFINE_GETTER(TYPE, MEMBER) \
769 typedef TYPE (*conn_ ## MEMBER ## _func) (PGconn *conn); \
770 static TYPE conn_ ## MEMBER(PGconn *conn) { return conn->MEMBER; }
771
772/* Like DEFINE_GETTER, but returns a pointer to the member. */
773#define DEFINE_GETTER_P(TYPE, MEMBER) \
774 typedef TYPE (*conn_ ## MEMBER ## _func) (PGconn *conn); \
775 static TYPE conn_ ## MEMBER(PGconn *conn) { return &conn->MEMBER; }
776
777#define DEFINE_SETTER(TYPE, MEMBER) \
778 typedef void (*set_conn_ ## MEMBER ## _func) (PGconn *conn, TYPE val); \
779 static void set_conn_ ## MEMBER(PGconn *conn, TYPE val) { conn->MEMBER = val; }
780
781DEFINE_GETTER_P(PQExpBuffer, errorMessage);
782DEFINE_GETTER(char *, oauth_client_id);
783DEFINE_GETTER(char *, oauth_client_secret);
784DEFINE_GETTER(char *, oauth_discovery_uri);
785DEFINE_GETTER(char *, oauth_issuer_id);
786DEFINE_GETTER(char *, oauth_scope);
787DEFINE_GETTER(fe_oauth_state *, sasl_state);
788
789DEFINE_SETTER(pgsocket, altsock);
790DEFINE_SETTER(char *, oauth_token);
791
792/*
793 * Loads the libpq-oauth plugin via dlopen(), initializes it, and plugs its
794 * callbacks into the connection's async auth handlers.
795 *
796 * Failure to load here results in a relatively quiet connection error, to
797 * handle the use case where the build supports loading a flow but a user does
798 * not want to install it. Troubleshooting of linker/loader failures can be done
799 * via PGOAUTHDEBUG.
800 */
801bool
803{
804 static bool initialized = false;
805 static pthread_mutex_t init_mutex = PTHREAD_MUTEX_INITIALIZER;
806 int lockerr;
807
808 void (*init) (pgthreadlock_t threadlock,
809 libpq_gettext_func gettext_impl,
810 conn_errorMessage_func errmsg_impl,
811 conn_oauth_client_id_func clientid_impl,
812 conn_oauth_client_secret_func clientsecret_impl,
813 conn_oauth_discovery_uri_func discoveryuri_impl,
814 conn_oauth_issuer_id_func issuerid_impl,
815 conn_oauth_scope_func scope_impl,
816 conn_sasl_state_func saslstate_impl,
817 set_conn_altsock_func setaltsock_impl,
818 set_conn_oauth_token_func settoken_impl);
820 void (*cleanup) (PGconn *conn);
821
822 /*
823 * On macOS only, load the module using its absolute install path; the
824 * standard search behavior is not very helpful for this use case. Unlike
825 * on other platforms, DYLD_LIBRARY_PATH is used as a fallback even with
826 * absolute paths (modulo SIP effects), so tests can continue to work.
827 *
828 * On the other platforms, load the module using only the basename, to
829 * rely on the runtime linker's standard search behavior.
830 */
831 const char *const module_name =
832#if defined(__darwin__)
833 LIBDIR "/libpq-oauth-" PG_MAJORVERSION DLSUFFIX;
834#else
835 "libpq-oauth-" PG_MAJORVERSION DLSUFFIX;
836#endif
837
838 state->builtin_flow = dlopen(module_name, RTLD_NOW | RTLD_LOCAL);
839 if (!state->builtin_flow)
840 {
841 /*
842 * For end users, this probably isn't an error condition, it just
843 * means the flow isn't installed. Developers and package maintainers
844 * may want to debug this via the PGOAUTHDEBUG envvar, though.
845 *
846 * Note that POSIX dlerror() isn't guaranteed to be threadsafe.
847 */
849 fprintf(stderr, "failed dlopen for libpq-oauth: %s\n", dlerror());
850
851 return false;
852 }
853
854 if ((init = dlsym(state->builtin_flow, "libpq_oauth_init")) == NULL
855 || (flow = dlsym(state->builtin_flow, "pg_fe_run_oauth_flow")) == NULL
856 || (cleanup = dlsym(state->builtin_flow, "pg_fe_cleanup_oauth_flow")) == NULL)
857 {
858 /*
859 * This is more of an error condition than the one above, but due to
860 * the dlerror() threadsafety issue, lock it behind PGOAUTHDEBUG too.
861 */
863 fprintf(stderr, "failed dlsym for libpq-oauth: %s\n", dlerror());
864
865 dlclose(state->builtin_flow);
866 return false;
867 }
868
869 /*
870 * Past this point, we do not unload the module. It stays in the process
871 * permanently.
872 */
873
874 /*
875 * We need to inject necessary function pointers into the module. This
876 * only needs to be done once -- even if the pointers are constant,
877 * assigning them while another thread is executing the flows feels like
878 * tempting fate.
879 */
880 if ((lockerr = pthread_mutex_lock(&init_mutex)) != 0)
881 {
882 /* Should not happen... but don't continue if it does. */
883 Assert(false);
884
885 libpq_append_conn_error(conn, "failed to lock mutex (%d)", lockerr);
886 return false;
887 }
888
889 if (!initialized)
890 {
892#ifdef ENABLE_NLS
894#else
895 NULL,
896#endif
906
907 initialized = true;
908 }
909
910 pthread_mutex_unlock(&init_mutex);
911
912 /* Set our asynchronous callbacks. */
913 conn->async_auth = flow;
915
916 return true;
917}
918
919#else
920
921/*
922 * Use the builtin flow in libpq-oauth.a (see libpq-oauth/oauth-curl.h).
923 */
924
927
928bool
930{
931 /* Set our asynchronous callbacks. */
934
935 return true;
936}
937
938#endif /* USE_LIBCURL */
939
940
941/*
942 * Chooses an OAuth client flow for the connection, which will retrieve a Bearer
943 * token for presentation to the server.
944 *
945 * If the application has registered a custom flow handler using
946 * PQAUTHDATA_OAUTH_BEARER_TOKEN, it may either return a token immediately (e.g.
947 * if it has one cached for immediate use), or set up for a series of
948 * asynchronous callbacks which will be managed by run_user_oauth_flow().
949 *
950 * If the default handler is used instead, a Device Authorization flow is used
951 * for the connection if support has been compiled in. (See
952 * fe-auth-oauth-curl.c for implementation details.)
953 *
954 * If neither a custom handler nor the builtin flow is available, the connection
955 * fails here.
956 */
957static bool
959{
960 int res;
961 PGoauthBearerRequest request = {
963 .scope = conn->oauth_scope,
964 };
965
967
968 /* The client may have overridden the OAuth flow. */
970 if (res > 0)
971 {
972 PGoauthBearerRequest *request_copy;
973
974 if (request.token)
975 {
976 /*
977 * We already have a token, so copy it into the conn. (We can't
978 * hold onto the original string, since it may not be safe for us
979 * to free() it.)
980 */
981 conn->oauth_token = strdup(request.token);
982 if (!conn->oauth_token)
983 {
984 libpq_append_conn_error(conn, "out of memory");
985 goto fail;
986 }
987
988 /* short-circuit */
989 if (request.cleanup)
990 request.cleanup(conn, &request);
991 return true;
992 }
993
994 request_copy = malloc(sizeof(*request_copy));
995 if (!request_copy)
996 {
997 libpq_append_conn_error(conn, "out of memory");
998 goto fail;
999 }
1000
1001 *request_copy = request;
1002
1005 state->async_ctx = request_copy;
1006 }
1007 else if (res < 0)
1008 {
1009 libpq_append_conn_error(conn, "user-defined OAuth flow failed");
1010 goto fail;
1011 }
1012 else if (!use_builtin_flow(conn, state))
1013 {
1014 libpq_append_conn_error(conn, "no OAuth flows are available (try installing the libpq-oauth package)");
1015 goto fail;
1016 }
1017
1018 return true;
1019
1020fail:
1021 if (request.cleanup)
1022 request.cleanup(conn, &request);
1023 return false;
1024}
1025
1026/*
1027 * Fill in our issuer identifier (and discovery URI, if possible) using the
1028 * connection parameters. If conn->oauth_discovery_uri can't be populated in
1029 * this function, it will be requested from the server.
1030 */
1031static bool
1033{
1034 /*
1035 * This is the only function that sets conn->oauth_issuer_id. If a
1036 * previous connection attempt has already computed it, don't overwrite it
1037 * or the discovery URI. (There's no reason for them to change once
1038 * they're set, and handle_oauth_sasl_error() will fail the connection if
1039 * the server attempts to switch them on us later.)
1040 */
1041 if (conn->oauth_issuer_id)
1042 return true;
1043
1044 /*---
1045 * To talk to a server, we require the user to provide issuer and client
1046 * identifiers.
1047 *
1048 * While it's possible for an OAuth client to support multiple issuers, it
1049 * requires additional effort to make sure the flows in use are safe -- to
1050 * quote RFC 9207,
1051 *
1052 * OAuth clients that interact with only one authorization server are
1053 * not vulnerable to mix-up attacks. However, when such clients decide
1054 * to add support for a second authorization server in the future, they
1055 * become vulnerable and need to apply countermeasures to mix-up
1056 * attacks.
1057 *
1058 * For now, we allow only one.
1059 */
1061 {
1063 "server requires OAuth authentication, but oauth_issuer and oauth_client_id are not both set");
1064 return false;
1065 }
1066
1067 /*
1068 * oauth_issuer is interpreted differently if it's a well-known discovery
1069 * URI rather than just an issuer identifier.
1070 */
1071 if (strstr(conn->oauth_issuer, WK_PREFIX) != NULL)
1072 {
1073 /*
1074 * Convert the URI back to an issuer identifier. (This also performs
1075 * validation of the URI format.)
1076 */
1079 if (!conn->oauth_issuer_id)
1080 return false; /* error message already set */
1081
1084 {
1085 libpq_append_conn_error(conn, "out of memory");
1086 return false;
1087 }
1088 }
1089 else
1090 {
1091 /*
1092 * Treat oauth_issuer as an issuer identifier. We'll ask the server
1093 * for the discovery URI.
1094 */
1096 if (!conn->oauth_issuer_id)
1097 {
1098 libpq_append_conn_error(conn, "out of memory");
1099 return false;
1100 }
1101 }
1102
1103 return true;
1104}
1105
1106/*
1107 * Implements the OAUTHBEARER SASL exchange (RFC 7628, Sec. 3.2).
1108 *
1109 * If the necessary OAuth parameters are set up on the connection, this will run
1110 * the client flow asynchronously and present the resulting token to the server.
1111 * Otherwise, an empty discovery response will be sent and any parameters sent
1112 * back by the server will be stored for a second attempt.
1113 *
1114 * For a full description of the API, see libpq/sasl.h.
1115 */
1116static SASLStatus
1117oauth_exchange(void *opaq, bool final,
1118 char *input, int inputlen,
1119 char **output, int *outputlen)
1120{
1121 fe_oauth_state *state = opaq;
1122 PGconn *conn = state->conn;
1123 bool discover = false;
1124
1125 *output = NULL;
1126 *outputlen = 0;
1127
1128 switch (state->step)
1129 {
1130 case FE_OAUTH_INIT:
1131 /* We begin in the initial response phase. */
1132 Assert(inputlen == -1);
1133
1135 return SASL_FAILED;
1136
1137 if (conn->oauth_token)
1138 {
1139 /*
1140 * A previous connection already fetched the token; we'll use
1141 * it below.
1142 */
1143 }
1144 else if (conn->oauth_discovery_uri)
1145 {
1146 /*
1147 * We don't have a token, but we have a discovery URI already
1148 * stored. Decide whether we're using a user-provided OAuth
1149 * flow or the one we have built in.
1150 */
1152 return SASL_FAILED;
1153
1154 if (conn->oauth_token)
1155 {
1156 /*
1157 * A really smart user implementation may have already
1158 * given us the token (e.g. if there was an unexpired copy
1159 * already cached), and we can use it immediately.
1160 */
1161 }
1162 else
1163 {
1164 /*
1165 * Otherwise, we'll have to hand the connection over to
1166 * our OAuth implementation.
1167 *
1168 * This could take a while, since it generally involves a
1169 * user in the loop. To avoid consuming the server's
1170 * authentication timeout, we'll continue this handshake
1171 * to the end, so that the server can close its side of
1172 * the connection. We'll open a second connection later
1173 * once we've retrieved a token.
1174 */
1175 discover = true;
1176 }
1177 }
1178 else
1179 {
1180 /*
1181 * If we don't have a token, and we don't have a discovery URI
1182 * to be able to request a token, we ask the server for one
1183 * explicitly.
1184 */
1185 discover = true;
1186 }
1187
1188 /*
1189 * Generate an initial response. This either contains a token, if
1190 * we have one, or an empty discovery response which is doomed to
1191 * fail.
1192 */
1193 *output = client_initial_response(conn, discover);
1194 if (!*output)
1195 return SASL_FAILED;
1196
1197 *outputlen = strlen(*output);
1199
1200 if (conn->oauth_token)
1201 {
1202 /*
1203 * For the purposes of require_auth, our side of
1204 * authentication is done at this point; the server will
1205 * either accept the connection or send an error. Unlike
1206 * SCRAM, there is no additional server data to check upon
1207 * success.
1208 */
1209 conn->client_finished_auth = true;
1210 }
1211
1212 return SASL_CONTINUE;
1213
1215 if (final)
1216 {
1217 /*
1218 * OAUTHBEARER does not make use of additional data with a
1219 * successful SASL exchange, so we shouldn't get an
1220 * AuthenticationSASLFinal message.
1221 */
1223 "server sent unexpected additional OAuth data");
1224 return SASL_FAILED;
1225 }
1226
1227 /*
1228 * An error message was sent by the server. Respond with the
1229 * required dummy message (RFC 7628, sec. 3.2.3).
1230 */
1231 *output = strdup(kvsep);
1232 if (unlikely(!*output))
1233 {
1234 libpq_append_conn_error(conn, "out of memory");
1235 return SASL_FAILED;
1236 }
1237 *outputlen = strlen(*output); /* == 1 */
1238
1239 /* Grab the settings from discovery. */
1240 if (!handle_oauth_sasl_error(conn, input, inputlen))
1241 return SASL_FAILED;
1242
1243 if (conn->oauth_token)
1244 {
1245 /*
1246 * The server rejected our token. Continue onwards towards the
1247 * expected FATAL message, but mark our state to catch any
1248 * unexpected "success" from the server.
1249 */
1251 return SASL_CONTINUE;
1252 }
1253
1254 if (!conn->async_auth)
1255 {
1256 /*
1257 * No OAuth flow is set up yet. Did we get enough information
1258 * from the server to create one?
1259 */
1261 {
1263 "server requires OAuth authentication, but no discovery metadata was provided");
1264 return SASL_FAILED;
1265 }
1266
1267 /* Yes. Set up the flow now. */
1269 return SASL_FAILED;
1270
1271 if (conn->oauth_token)
1272 {
1273 /*
1274 * A token was available in a custom flow's cache. Skip
1275 * the asynchronous processing.
1276 */
1277 goto reconnect;
1278 }
1279 }
1280
1281 /*
1282 * Time to retrieve a token. This involves a number of HTTP
1283 * connections and timed waits, so we escape the synchronous auth
1284 * processing and tell PQconnectPoll to transfer control to our
1285 * async implementation.
1286 */
1287 Assert(conn->async_auth); /* should have been set already */
1289 return SASL_ASYNC;
1290
1292
1293 /*
1294 * We've returned successfully from token retrieval. Double-check
1295 * that we have what we need for the next connection.
1296 */
1297 if (!conn->oauth_token)
1298 {
1299 Assert(false); /* should have failed before this point! */
1301 "internal error: OAuth flow did not set a token");
1302 return SASL_FAILED;
1303 }
1304
1305 goto reconnect;
1306
1308
1309 /*
1310 * After an error, the server should send an error response to
1311 * fail the SASL handshake, which is handled in higher layers.
1312 *
1313 * If we get here, the server either sent *another* challenge
1314 * which isn't defined in the RFC, or completed the handshake
1315 * successfully after telling us it was going to fail. Neither is
1316 * acceptable.
1317 */
1319 "server sent additional OAuth data after error");
1320 return SASL_FAILED;
1321
1322 default:
1323 libpq_append_conn_error(conn, "invalid OAuth exchange state");
1324 break;
1325 }
1326
1327 Assert(false); /* should never get here */
1328 return SASL_FAILED;
1329
1330reconnect:
1331
1332 /*
1333 * Despite being a failure from the point of view of SASL, we have enough
1334 * information to restart with a new connection.
1335 */
1336 libpq_append_conn_error(conn, "retrying connection with new bearer token");
1337 conn->oauth_want_retry = true;
1338 return SASL_FAILED;
1339}
1340
1341static bool
1343{
1344 /* This mechanism does not support channel binding. */
1345 return false;
1346}
1347
1348/*
1349 * Fully clears out any stored OAuth token. This is done proactively upon
1350 * successful connection as well as during pqClosePGconn().
1351 */
1352void
1354{
1355 if (!conn->oauth_token)
1356 return;
1357
1360 conn->oauth_token = NULL;
1361}
1362
1363/*
1364 * Returns true if the PGOAUTHDEBUG=UNSAFE flag is set in the environment.
1365 */
1366bool
1368{
1369 const char *env = getenv("PGOAUTHDEBUG");
1370
1371 return (env && strcmp(env, "UNSAFE") == 0);
1372}
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 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
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:2401
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:2906
void pg_fe_cleanup_oauth_flow(PGconn *conn)
Definition: oauth-curl.c:340
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 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:2163
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