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