LCOV - code coverage report
Current view: top level - src/interfaces/libpq - fe-auth-oauth.c (source / functions) Hit Total Coverage
Test: PostgreSQL 18devel Lines: 3 347 0.9 %
Date: 2025-02-21 17:14:59 Functions: 1 18 5.6 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*-------------------------------------------------------------------------
       2             :  *
       3             :  * fe-auth-oauth.c
       4             :  *     The front-end (client) implementation of OAuth/OIDC authentication
       5             :  *     using the SASL OAUTHBEARER mechanism.
       6             :  *
       7             :  * Portions Copyright (c) 1996-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             : #include "common/base64.h"
      19             : #include "common/hmac.h"
      20             : #include "common/jsonapi.h"
      21             : #include "common/oauth-common.h"
      22             : #include "fe-auth.h"
      23             : #include "fe-auth-oauth.h"
      24             : #include "mb/pg_wchar.h"
      25             : 
      26             : /* The exported OAuth callback mechanism. */
      27             : static void *oauth_init(PGconn *conn, const char *password,
      28             :                         const char *sasl_mechanism);
      29             : static SASLStatus oauth_exchange(void *opaq, bool final,
      30             :                                  char *input, int inputlen,
      31             :                                  char **output, int *outputlen);
      32             : static bool oauth_channel_bound(void *opaq);
      33             : static void oauth_free(void *opaq);
      34             : 
      35             : const pg_fe_sasl_mech pg_oauth_mech = {
      36             :     oauth_init,
      37             :     oauth_exchange,
      38             :     oauth_channel_bound,
      39             :     oauth_free,
      40             : };
      41             : 
      42             : /*
      43             :  * Initializes mechanism state for OAUTHBEARER.
      44             :  *
      45             :  * For a full description of the API, see libpq/fe-auth-sasl.h.
      46             :  */
      47             : static void *
      48           0 : oauth_init(PGconn *conn, const char *password,
      49             :            const char *sasl_mechanism)
      50             : {
      51             :     fe_oauth_state *state;
      52             : 
      53             :     /*
      54             :      * We only support one SASL mechanism here; anything else is programmer
      55             :      * error.
      56             :      */
      57             :     Assert(sasl_mechanism != NULL);
      58             :     Assert(strcmp(sasl_mechanism, OAUTHBEARER_NAME) == 0);
      59             : 
      60           0 :     state = calloc(1, sizeof(*state));
      61           0 :     if (!state)
      62           0 :         return NULL;
      63             : 
      64           0 :     state->step = FE_OAUTH_INIT;
      65           0 :     state->conn = conn;
      66             : 
      67           0 :     return state;
      68             : }
      69             : 
      70             : /*
      71             :  * Frees the state allocated by oauth_init().
      72             :  *
      73             :  * This handles only mechanism state tied to the connection lifetime; state
      74             :  * stored in state->async_ctx is freed up either immediately after the
      75             :  * authentication handshake succeeds, or before the mechanism is cleaned up on
      76             :  * failure. See pg_fe_cleanup_oauth_flow() and cleanup_user_oauth_flow().
      77             :  */
      78             : static void
      79           0 : oauth_free(void *opaq)
      80             : {
      81           0 :     fe_oauth_state *state = opaq;
      82             : 
      83             :     /* Any async authentication state should have been cleaned up already. */
      84             :     Assert(!state->async_ctx);
      85             : 
      86           0 :     free(state);
      87           0 : }
      88             : 
      89             : #define kvsep "\x01"
      90             : 
      91             : /*
      92             :  * Constructs an OAUTHBEARER client initial response (RFC 7628, Sec. 3.1).
      93             :  *
      94             :  * If discover is true, the initial response will contain a request for the
      95             :  * server's required OAuth parameters (Sec. 4.3). Otherwise, conn->token must
      96             :  * be set; it will be sent as the connection's bearer token.
      97             :  *
      98             :  * Returns the response as a null-terminated string, or NULL on error.
      99             :  */
     100             : static char *
     101           0 : client_initial_response(PGconn *conn, bool discover)
     102             : {
     103             :     static const char *const resp_format = "n,," kvsep "auth=%s%s" kvsep kvsep;
     104             : 
     105             :     PQExpBufferData buf;
     106             :     const char *authn_scheme;
     107           0 :     char       *response = NULL;
     108           0 :     const char *token = conn->oauth_token;
     109             : 
     110           0 :     if (discover)
     111             :     {
     112             :         /* Parameter discovery uses a completely empty auth value. */
     113           0 :         authn_scheme = token = "";
     114             :     }
     115             :     else
     116             :     {
     117             :         /*
     118             :          * Use a Bearer authentication scheme (RFC 6750, Sec. 2.1). A trailing
     119             :          * space is used as a separator.
     120             :          */
     121           0 :         authn_scheme = "Bearer ";
     122             : 
     123             :         /* conn->token must have been set in this case. */
     124           0 :         if (!token)
     125             :         {
     126             :             Assert(false);
     127           0 :             libpq_append_conn_error(conn,
     128             :                                     "internal error: no OAuth token was set for the connection");
     129           0 :             return NULL;
     130             :         }
     131             :     }
     132             : 
     133           0 :     initPQExpBuffer(&buf);
     134           0 :     appendPQExpBuffer(&buf, resp_format, authn_scheme, token);
     135             : 
     136           0 :     if (!PQExpBufferDataBroken(buf))
     137           0 :         response = strdup(buf.data);
     138           0 :     termPQExpBuffer(&buf);
     139             : 
     140           0 :     if (!response)
     141           0 :         libpq_append_conn_error(conn, "out of memory");
     142             : 
     143           0 :     return response;
     144             : }
     145             : 
     146             : /*
     147             :  * JSON Parser (for the OAUTHBEARER error result)
     148             :  */
     149             : 
     150             : /* Relevant JSON fields in the error result object. */
     151             : #define ERROR_STATUS_FIELD "status"
     152             : #define ERROR_SCOPE_FIELD "scope"
     153             : #define ERROR_OPENID_CONFIGURATION_FIELD "openid-configuration"
     154             : 
     155             : struct json_ctx
     156             : {
     157             :     char       *errmsg;         /* any non-NULL value stops all processing */
     158             :     PQExpBufferData errbuf;     /* backing memory for errmsg */
     159             :     int         nested;         /* nesting level (zero is the top) */
     160             : 
     161             :     const char *target_field_name;  /* points to a static allocation */
     162             :     char      **target_field;   /* see below */
     163             : 
     164             :     /* target_field, if set, points to one of the following: */
     165             :     char       *status;
     166             :     char       *scope;
     167             :     char       *discovery_uri;
     168             : };
     169             : 
     170             : #define oauth_json_has_error(ctx) \
     171             :     (PQExpBufferDataBroken((ctx)->errbuf) || (ctx)->errmsg)
     172             : 
     173             : #define oauth_json_set_error(ctx, ...) \
     174             :     do { \
     175             :         appendPQExpBuffer(&(ctx)->errbuf, __VA_ARGS__); \
     176             :         (ctx)->errmsg = (ctx)->errbuf.data; \
     177             :     } while (0)
     178             : 
     179             : static JsonParseErrorType
     180           0 : oauth_json_object_start(void *state)
     181             : {
     182           0 :     struct json_ctx *ctx = state;
     183             : 
     184           0 :     if (ctx->target_field)
     185             :     {
     186             :         Assert(ctx->nested == 1);
     187             : 
     188           0 :         oauth_json_set_error(ctx,
     189             :                              libpq_gettext("field \"%s\" must be a string"),
     190             :                              ctx->target_field_name);
     191             :     }
     192             : 
     193           0 :     ++ctx->nested;
     194           0 :     return oauth_json_has_error(ctx) ? JSON_SEM_ACTION_FAILED : JSON_SUCCESS;
     195             : }
     196             : 
     197             : static JsonParseErrorType
     198           0 : oauth_json_object_end(void *state)
     199             : {
     200           0 :     struct json_ctx *ctx = state;
     201             : 
     202           0 :     --ctx->nested;
     203           0 :     return JSON_SUCCESS;
     204             : }
     205             : 
     206             : static JsonParseErrorType
     207           0 : oauth_json_object_field_start(void *state, char *name, bool isnull)
     208             : {
     209           0 :     struct json_ctx *ctx = state;
     210             : 
     211             :     /* Only top-level keys are considered. */
     212           0 :     if (ctx->nested == 1)
     213             :     {
     214           0 :         if (strcmp(name, ERROR_STATUS_FIELD) == 0)
     215             :         {
     216           0 :             ctx->target_field_name = ERROR_STATUS_FIELD;
     217           0 :             ctx->target_field = &ctx->status;
     218             :         }
     219           0 :         else if (strcmp(name, ERROR_SCOPE_FIELD) == 0)
     220             :         {
     221           0 :             ctx->target_field_name = ERROR_SCOPE_FIELD;
     222           0 :             ctx->target_field = &ctx->scope;
     223             :         }
     224           0 :         else if (strcmp(name, ERROR_OPENID_CONFIGURATION_FIELD) == 0)
     225             :         {
     226           0 :             ctx->target_field_name = ERROR_OPENID_CONFIGURATION_FIELD;
     227           0 :             ctx->target_field = &ctx->discovery_uri;
     228             :         }
     229             :     }
     230             : 
     231           0 :     return JSON_SUCCESS;
     232             : }
     233             : 
     234             : static JsonParseErrorType
     235           0 : oauth_json_array_start(void *state)
     236             : {
     237           0 :     struct json_ctx *ctx = state;
     238             : 
     239           0 :     if (!ctx->nested)
     240             :     {
     241           0 :         ctx->errmsg = libpq_gettext("top-level element must be an object");
     242             :     }
     243           0 :     else if (ctx->target_field)
     244             :     {
     245             :         Assert(ctx->nested == 1);
     246             : 
     247           0 :         oauth_json_set_error(ctx,
     248             :                              libpq_gettext("field \"%s\" must be a string"),
     249             :                              ctx->target_field_name);
     250             :     }
     251             : 
     252           0 :     return oauth_json_has_error(ctx) ? JSON_SEM_ACTION_FAILED : JSON_SUCCESS;
     253             : }
     254             : 
     255             : static JsonParseErrorType
     256           0 : oauth_json_scalar(void *state, char *token, JsonTokenType type)
     257             : {
     258           0 :     struct json_ctx *ctx = state;
     259             : 
     260           0 :     if (!ctx->nested)
     261             :     {
     262           0 :         ctx->errmsg = libpq_gettext("top-level element must be an object");
     263           0 :         return JSON_SEM_ACTION_FAILED;
     264             :     }
     265             : 
     266           0 :     if (ctx->target_field)
     267             :     {
     268           0 :         if (ctx->nested != 1)
     269             :         {
     270             :             /*
     271             :              * ctx->target_field should not have been set for nested keys.
     272             :              * Assert and don't continue any further for production builds.
     273             :              */
     274             :             Assert(false);
     275           0 :             oauth_json_set_error(ctx,
     276             :                                  "internal error: target scalar found at nesting level %d during OAUTHBEARER parsing",
     277             :                                  ctx->nested);
     278           0 :             return JSON_SEM_ACTION_FAILED;
     279             :         }
     280             : 
     281             :         /*
     282             :          * We don't allow duplicate field names; error out if the target has
     283             :          * already been set.
     284             :          */
     285           0 :         if (*ctx->target_field)
     286             :         {
     287           0 :             oauth_json_set_error(ctx,
     288             :                                  libpq_gettext("field \"%s\" is duplicated"),
     289             :                                  ctx->target_field_name);
     290           0 :             return JSON_SEM_ACTION_FAILED;
     291             :         }
     292             : 
     293             :         /* The only fields we support are strings. */
     294           0 :         if (type != JSON_TOKEN_STRING)
     295             :         {
     296           0 :             oauth_json_set_error(ctx,
     297             :                                  libpq_gettext("field \"%s\" must be a string"),
     298             :                                  ctx->target_field_name);
     299           0 :             return JSON_SEM_ACTION_FAILED;
     300             :         }
     301             : 
     302           0 :         *ctx->target_field = strdup(token);
     303           0 :         if (!*ctx->target_field)
     304           0 :             return JSON_OUT_OF_MEMORY;
     305             : 
     306           0 :         ctx->target_field = NULL;
     307           0 :         ctx->target_field_name = NULL;
     308             :     }
     309             :     else
     310             :     {
     311             :         /* otherwise we just ignore it */
     312             :     }
     313             : 
     314           0 :     return JSON_SUCCESS;
     315             : }
     316             : 
     317             : #define HTTPS_SCHEME "https://"
     318             : #define HTTP_SCHEME "http://"
     319             : 
     320             : /* We support both well-known suffixes defined by RFC 8414. */
     321             : #define WK_PREFIX "/.well-known/"
     322             : #define OPENID_WK_SUFFIX "openid-configuration"
     323             : #define OAUTH_WK_SUFFIX "oauth-authorization-server"
     324             : 
     325             : /*
     326             :  * Derives an issuer identifier from one of our recognized .well-known URIs,
     327             :  * using the rules in RFC 8414.
     328             :  */
     329             : static char *
     330           0 : issuer_from_well_known_uri(PGconn *conn, const char *wkuri)
     331             : {
     332           0 :     const char *authority_start = NULL;
     333             :     const char *wk_start;
     334             :     const char *wk_end;
     335             :     char       *issuer;
     336             :     ptrdiff_t   start_offset,
     337             :                 end_offset;
     338             :     size_t      end_len;
     339             : 
     340             :     /*
     341             :      * https:// is required for issuer identifiers (RFC 8414, Sec. 2; OIDC
     342             :      * Discovery 1.0, Sec. 3). This is a case-insensitive comparison at this
     343             :      * level (but issuer identifier comparison at the level above this is
     344             :      * case-sensitive, so in practice it's probably moot).
     345             :      */
     346           0 :     if (pg_strncasecmp(wkuri, HTTPS_SCHEME, strlen(HTTPS_SCHEME)) == 0)
     347           0 :         authority_start = wkuri + strlen(HTTPS_SCHEME);
     348             : 
     349           0 :     if (!authority_start
     350           0 :         && oauth_unsafe_debugging_enabled()
     351           0 :         && pg_strncasecmp(wkuri, HTTP_SCHEME, strlen(HTTP_SCHEME)) == 0)
     352             :     {
     353             :         /* Allow http:// for testing only. */
     354           0 :         authority_start = wkuri + strlen(HTTP_SCHEME);
     355             :     }
     356             : 
     357           0 :     if (!authority_start)
     358             :     {
     359           0 :         libpq_append_conn_error(conn,
     360             :                                 "OAuth discovery URI \"%s\" must use HTTPS",
     361             :                                 wkuri);
     362           0 :         return NULL;
     363             :     }
     364             : 
     365             :     /*
     366             :      * Well-known URIs in general may support queries and fragments, but the
     367             :      * two types we support here do not. (They must be constructed from the
     368             :      * components of issuer identifiers, which themselves may not contain any
     369             :      * queries or fragments.)
     370             :      *
     371             :      * It's important to check this first, to avoid getting tricked later by a
     372             :      * prefix buried inside a query or fragment.
     373             :      */
     374           0 :     if (strpbrk(authority_start, "?#") != NULL)
     375             :     {
     376           0 :         libpq_append_conn_error(conn,
     377             :                                 "OAuth discovery URI \"%s\" must not contain query or fragment components",
     378             :                                 wkuri);
     379           0 :         return NULL;
     380             :     }
     381             : 
     382             :     /*
     383             :      * Find the start of the .well-known prefix. IETF rules (RFC 8615) state
     384             :      * this must be at the beginning of the path component, but OIDC defined
     385             :      * it at the end instead (OIDC Discovery 1.0, Sec. 4), so we have to
     386             :      * search for it anywhere.
     387             :      */
     388           0 :     wk_start = strstr(authority_start, WK_PREFIX);
     389           0 :     if (!wk_start)
     390             :     {
     391           0 :         libpq_append_conn_error(conn,
     392             :                                 "OAuth discovery URI \"%s\" is not a .well-known URI",
     393             :                                 wkuri);
     394           0 :         return NULL;
     395             :     }
     396             : 
     397             :     /*
     398             :      * Now find the suffix type. We only support the two defined in OIDC
     399             :      * Discovery 1.0 and RFC 8414.
     400             :      */
     401           0 :     wk_end = wk_start + strlen(WK_PREFIX);
     402             : 
     403           0 :     if (strncmp(wk_end, OPENID_WK_SUFFIX, strlen(OPENID_WK_SUFFIX)) == 0)
     404           0 :         wk_end += strlen(OPENID_WK_SUFFIX);
     405           0 :     else if (strncmp(wk_end, OAUTH_WK_SUFFIX, strlen(OAUTH_WK_SUFFIX)) == 0)
     406           0 :         wk_end += strlen(OAUTH_WK_SUFFIX);
     407             :     else
     408           0 :         wk_end = NULL;
     409             : 
     410             :     /*
     411             :      * Even if there's a match, we still need to check to make sure the suffix
     412             :      * takes up the entire path segment, to weed out constructions like
     413             :      * "/.well-known/openid-configuration-bad".
     414             :      */
     415           0 :     if (!wk_end || (*wk_end != '/' && *wk_end != '\0'))
     416             :     {
     417           0 :         libpq_append_conn_error(conn,
     418             :                                 "OAuth discovery URI \"%s\" uses an unsupported .well-known suffix",
     419             :                                 wkuri);
     420           0 :         return NULL;
     421             :     }
     422             : 
     423             :     /*
     424             :      * Finally, make sure the .well-known components are provided either as a
     425             :      * prefix (IETF style) or as a postfix (OIDC style). In other words,
     426             :      * "https://localhost/a/.well-known/openid-configuration/b" is not allowed
     427             :      * to claim association with "https://localhost/a/b".
     428             :      */
     429           0 :     if (*wk_end != '\0')
     430             :     {
     431             :         /*
     432             :          * It's not at the end, so it's required to be at the beginning at the
     433             :          * path. Find the starting slash.
     434             :          */
     435             :         const char *path_start;
     436             : 
     437           0 :         path_start = strchr(authority_start, '/');
     438             :         Assert(path_start);     /* otherwise we wouldn't have found WK_PREFIX */
     439             : 
     440           0 :         if (wk_start != path_start)
     441             :         {
     442           0 :             libpq_append_conn_error(conn,
     443             :                                     "OAuth discovery URI \"%s\" uses an invalid format",
     444             :                                     wkuri);
     445           0 :             return NULL;
     446             :         }
     447             :     }
     448             : 
     449             :     /* Checks passed! Now build the issuer. */
     450           0 :     issuer = strdup(wkuri);
     451           0 :     if (!issuer)
     452             :     {
     453           0 :         libpq_append_conn_error(conn, "out of memory");
     454           0 :         return NULL;
     455             :     }
     456             : 
     457             :     /*
     458             :      * The .well-known components are from [wk_start, wk_end). Remove those to
     459             :      * form the issuer ID, by shifting the path suffix (which may be empty)
     460             :      * leftwards.
     461             :      */
     462           0 :     start_offset = wk_start - wkuri;
     463           0 :     end_offset = wk_end - wkuri;
     464           0 :     end_len = strlen(wk_end) + 1;   /* move the NULL terminator too */
     465             : 
     466           0 :     memmove(issuer + start_offset, issuer + end_offset, end_len);
     467             : 
     468           0 :     return issuer;
     469             : }
     470             : 
     471             : /*
     472             :  * Parses the server error result (RFC 7628, Sec. 3.2.2) contained in msg and
     473             :  * stores any discovered openid_configuration and scope settings for the
     474             :  * connection.
     475             :  */
     476             : static bool
     477           0 : handle_oauth_sasl_error(PGconn *conn, const char *msg, int msglen)
     478             : {
     479           0 :     JsonLexContext lex = {0};
     480           0 :     JsonSemAction sem = {0};
     481             :     JsonParseErrorType err;
     482           0 :     struct json_ctx ctx = {0};
     483           0 :     char       *errmsg = NULL;
     484           0 :     bool        success = false;
     485             : 
     486             :     Assert(conn->oauth_issuer_id);   /* ensured by setup_oauth_parameters() */
     487             : 
     488             :     /* Sanity check. */
     489           0 :     if (strlen(msg) != msglen)
     490             :     {
     491           0 :         libpq_append_conn_error(conn,
     492             :                                 "server's error message contained an embedded NULL, and was discarded");
     493           0 :         return false;
     494             :     }
     495             : 
     496             :     /*
     497             :      * pg_parse_json doesn't validate the incoming UTF-8, so we have to check
     498             :      * that up front.
     499             :      */
     500           0 :     if (pg_encoding_verifymbstr(PG_UTF8, msg, msglen) != msglen)
     501             :     {
     502           0 :         libpq_append_conn_error(conn,
     503             :                                 "server's error response is not valid UTF-8");
     504           0 :         return false;
     505             :     }
     506             : 
     507           0 :     makeJsonLexContextCstringLen(&lex, msg, msglen, PG_UTF8, true);
     508           0 :     setJsonLexContextOwnsTokens(&lex, true);    /* must not leak on error */
     509             : 
     510           0 :     initPQExpBuffer(&ctx.errbuf);
     511           0 :     sem.semstate = &ctx;
     512             : 
     513           0 :     sem.object_start = oauth_json_object_start;
     514           0 :     sem.object_end = oauth_json_object_end;
     515           0 :     sem.object_field_start = oauth_json_object_field_start;
     516           0 :     sem.array_start = oauth_json_array_start;
     517           0 :     sem.scalar = oauth_json_scalar;
     518             : 
     519           0 :     err = pg_parse_json(&lex, &sem);
     520             : 
     521           0 :     if (err == JSON_SEM_ACTION_FAILED)
     522             :     {
     523           0 :         if (PQExpBufferDataBroken(ctx.errbuf))
     524           0 :             errmsg = libpq_gettext("out of memory");
     525           0 :         else if (ctx.errmsg)
     526           0 :             errmsg = ctx.errmsg;
     527             :         else
     528             :         {
     529             :             /*
     530             :              * Developer error: one of the action callbacks didn't call
     531             :              * oauth_json_set_error() before erroring out.
     532             :              */
     533             :             Assert(oauth_json_has_error(&ctx));
     534           0 :             errmsg = "<unexpected empty error>";
     535             :         }
     536             :     }
     537           0 :     else if (err != JSON_SUCCESS)
     538           0 :         errmsg = json_errdetail(err, &lex);
     539             : 
     540           0 :     if (errmsg)
     541           0 :         libpq_append_conn_error(conn,
     542             :                                 "failed to parse server's error response: %s",
     543             :                                 errmsg);
     544             : 
     545             :     /* Don't need the error buffer or the JSON lexer anymore. */
     546           0 :     termPQExpBuffer(&ctx.errbuf);
     547           0 :     freeJsonLexContext(&lex);
     548             : 
     549           0 :     if (errmsg)
     550           0 :         goto cleanup;
     551             : 
     552           0 :     if (ctx.discovery_uri)
     553             :     {
     554             :         char       *discovery_issuer;
     555             : 
     556             :         /*
     557             :          * The URI MUST correspond to our existing issuer, to avoid mix-ups.
     558             :          *
     559             :          * Issuer comparison is done byte-wise, rather than performing any URL
     560             :          * normalization; this follows the suggestions for issuer comparison
     561             :          * in RFC 9207 Sec. 2.4 (which requires simple string comparison) and
     562             :          * vastly simplifies things. Since this is the key protection against
     563             :          * a rogue server sending the client to an untrustworthy location,
     564             :          * simpler is better.
     565             :          */
     566           0 :         discovery_issuer = issuer_from_well_known_uri(conn, ctx.discovery_uri);
     567           0 :         if (!discovery_issuer)
     568           0 :             goto cleanup;       /* error message already set */
     569             : 
     570           0 :         if (strcmp(conn->oauth_issuer_id, discovery_issuer) != 0)
     571             :         {
     572           0 :             libpq_append_conn_error(conn,
     573             :                                     "server's discovery document at %s (issuer \"%s\") is incompatible with oauth_issuer (%s)",
     574             :                                     ctx.discovery_uri, discovery_issuer,
     575             :                                     conn->oauth_issuer_id);
     576             : 
     577           0 :             free(discovery_issuer);
     578           0 :             goto cleanup;
     579             :         }
     580             : 
     581           0 :         free(discovery_issuer);
     582             : 
     583           0 :         if (!conn->oauth_discovery_uri)
     584             :         {
     585           0 :             conn->oauth_discovery_uri = ctx.discovery_uri;
     586           0 :             ctx.discovery_uri = NULL;
     587             :         }
     588             :         else
     589             :         {
     590             :             /* This must match the URI we'd previously determined. */
     591           0 :             if (strcmp(conn->oauth_discovery_uri, ctx.discovery_uri) != 0)
     592             :             {
     593           0 :                 libpq_append_conn_error(conn,
     594             :                                         "server's discovery document has moved to %s (previous location was %s)",
     595             :                                         ctx.discovery_uri,
     596             :                                         conn->oauth_discovery_uri);
     597           0 :                 goto cleanup;
     598             :             }
     599             :         }
     600             :     }
     601             : 
     602           0 :     if (ctx.scope)
     603             :     {
     604             :         /* Servers may not override a previously set oauth_scope. */
     605           0 :         if (!conn->oauth_scope)
     606             :         {
     607           0 :             conn->oauth_scope = ctx.scope;
     608           0 :             ctx.scope = NULL;
     609             :         }
     610             :     }
     611             : 
     612           0 :     if (!ctx.status)
     613             :     {
     614           0 :         libpq_append_conn_error(conn,
     615             :                                 "server sent error response without a status");
     616           0 :         goto cleanup;
     617             :     }
     618             : 
     619           0 :     if (strcmp(ctx.status, "invalid_token") != 0)
     620             :     {
     621             :         /*
     622             :          * invalid_token is the only error code we'll automatically retry for;
     623             :          * otherwise, just bail out now.
     624             :          */
     625           0 :         libpq_append_conn_error(conn,
     626             :                                 "server rejected OAuth bearer token: %s",
     627             :                                 ctx.status);
     628           0 :         goto cleanup;
     629             :     }
     630             : 
     631           0 :     success = true;
     632             : 
     633           0 : cleanup:
     634           0 :     free(ctx.status);
     635           0 :     free(ctx.scope);
     636           0 :     free(ctx.discovery_uri);
     637             : 
     638           0 :     return success;
     639             : }
     640             : 
     641             : /*
     642             :  * Callback implementation of conn->async_auth() for a user-defined OAuth flow.
     643             :  * Delegates the retrieval of the token to the application's async callback.
     644             :  *
     645             :  * This will be called multiple times as needed; the application is responsible
     646             :  * for setting an altsock to signal and returning the correct PGRES_POLLING_*
     647             :  * statuses for use by PQconnectPoll().
     648             :  */
     649             : static PostgresPollingStatusType
     650           0 : run_user_oauth_flow(PGconn *conn)
     651             : {
     652           0 :     fe_oauth_state *state = conn->sasl_state;
     653           0 :     PGoauthBearerRequest *request = state->async_ctx;
     654             :     PostgresPollingStatusType status;
     655             : 
     656           0 :     if (!request->async)
     657             :     {
     658           0 :         libpq_append_conn_error(conn,
     659             :                                 "user-defined OAuth flow provided neither a token nor an async callback");
     660           0 :         return PGRES_POLLING_FAILED;
     661             :     }
     662             : 
     663           0 :     status = request->async(conn, request, &conn->altsock);
     664           0 :     if (status == PGRES_POLLING_FAILED)
     665             :     {
     666           0 :         libpq_append_conn_error(conn, "user-defined OAuth flow failed");
     667           0 :         return status;
     668             :     }
     669           0 :     else if (status == PGRES_POLLING_OK)
     670             :     {
     671             :         /*
     672             :          * We already have a token, so copy it into the conn. (We can't hold
     673             :          * onto the original string, since it may not be safe for us to free()
     674             :          * it.)
     675             :          */
     676           0 :         if (!request->token)
     677             :         {
     678           0 :             libpq_append_conn_error(conn,
     679             :                                     "user-defined OAuth flow did not provide a token");
     680           0 :             return PGRES_POLLING_FAILED;
     681             :         }
     682             : 
     683           0 :         conn->oauth_token = strdup(request->token);
     684           0 :         if (!conn->oauth_token)
     685             :         {
     686           0 :             libpq_append_conn_error(conn, "out of memory");
     687           0 :             return PGRES_POLLING_FAILED;
     688             :         }
     689             : 
     690           0 :         return PGRES_POLLING_OK;
     691             :     }
     692             : 
     693             :     /* The hook wants the client to poll the altsock. Make sure it set one. */
     694           0 :     if (conn->altsock == PGINVALID_SOCKET)
     695             :     {
     696           0 :         libpq_append_conn_error(conn,
     697             :                                 "user-defined OAuth flow did not provide a socket for polling");
     698           0 :         return PGRES_POLLING_FAILED;
     699             :     }
     700             : 
     701           0 :     return status;
     702             : }
     703             : 
     704             : /*
     705             :  * Cleanup callback for the async user flow. Delegates most of its job to the
     706             :  * user-provided cleanup implementation, then disconnects the altsock.
     707             :  */
     708             : static void
     709           0 : cleanup_user_oauth_flow(PGconn *conn)
     710             : {
     711           0 :     fe_oauth_state *state = conn->sasl_state;
     712           0 :     PGoauthBearerRequest *request = state->async_ctx;
     713             : 
     714             :     Assert(request);
     715             : 
     716           0 :     if (request->cleanup)
     717           0 :         request->cleanup(conn, request);
     718           0 :     conn->altsock = PGINVALID_SOCKET;
     719             : 
     720           0 :     free(request);
     721           0 :     state->async_ctx = NULL;
     722           0 : }
     723             : 
     724             : /*
     725             :  * Chooses an OAuth client flow for the connection, which will retrieve a Bearer
     726             :  * token for presentation to the server.
     727             :  *
     728             :  * If the application has registered a custom flow handler using
     729             :  * PQAUTHDATA_OAUTH_BEARER_TOKEN, it may either return a token immediately (e.g.
     730             :  * if it has one cached for immediate use), or set up for a series of
     731             :  * asynchronous callbacks which will be managed by run_user_oauth_flow().
     732             :  *
     733             :  * If the default handler is used instead, a Device Authorization flow is used
     734             :  * for the connection if support has been compiled in. (See
     735             :  * fe-auth-oauth-curl.c for implementation details.)
     736             :  *
     737             :  * If neither a custom handler nor the builtin flow is available, the connection
     738             :  * fails here.
     739             :  */
     740             : static bool
     741           0 : setup_token_request(PGconn *conn, fe_oauth_state *state)
     742             : {
     743             :     int         res;
     744           0 :     PGoauthBearerRequest request = {
     745           0 :         .openid_configuration = conn->oauth_discovery_uri,
     746           0 :         .scope = conn->oauth_scope,
     747             :     };
     748             : 
     749             :     Assert(request.openid_configuration);
     750             : 
     751             :     /* The client may have overridden the OAuth flow. */
     752           0 :     res = PQauthDataHook(PQAUTHDATA_OAUTH_BEARER_TOKEN, conn, &request);
     753           0 :     if (res > 0)
     754             :     {
     755             :         PGoauthBearerRequest *request_copy;
     756             : 
     757           0 :         if (request.token)
     758             :         {
     759             :             /*
     760             :              * We already have a token, so copy it into the conn. (We can't
     761             :              * hold onto the original string, since it may not be safe for us
     762             :              * to free() it.)
     763             :              */
     764           0 :             conn->oauth_token = strdup(request.token);
     765           0 :             if (!conn->oauth_token)
     766             :             {
     767           0 :                 libpq_append_conn_error(conn, "out of memory");
     768           0 :                 goto fail;
     769             :             }
     770             : 
     771             :             /* short-circuit */
     772           0 :             if (request.cleanup)
     773           0 :                 request.cleanup(conn, &request);
     774           0 :             return true;
     775             :         }
     776             : 
     777           0 :         request_copy = malloc(sizeof(*request_copy));
     778           0 :         if (!request_copy)
     779             :         {
     780           0 :             libpq_append_conn_error(conn, "out of memory");
     781           0 :             goto fail;
     782             :         }
     783             : 
     784           0 :         memcpy(request_copy, &request, sizeof(request));
     785             : 
     786           0 :         conn->async_auth = run_user_oauth_flow;
     787           0 :         conn->cleanup_async_auth = cleanup_user_oauth_flow;
     788           0 :         state->async_ctx = request_copy;
     789             :     }
     790           0 :     else if (res < 0)
     791             :     {
     792           0 :         libpq_append_conn_error(conn, "user-defined OAuth flow failed");
     793           0 :         goto fail;
     794             :     }
     795             :     else
     796             :     {
     797             : #if USE_LIBCURL
     798             :         /* Hand off to our built-in OAuth flow. */
     799             :         conn->async_auth = pg_fe_run_oauth_flow;
     800             :         conn->cleanup_async_auth = pg_fe_cleanup_oauth_flow;
     801             : 
     802             : #else
     803           0 :         libpq_append_conn_error(conn, "no custom OAuth flows are available, and libpq was not built with libcurl support");
     804           0 :         goto fail;
     805             : 
     806             : #endif
     807             :     }
     808             : 
     809           0 :     return true;
     810             : 
     811           0 : fail:
     812           0 :     if (request.cleanup)
     813           0 :         request.cleanup(conn, &request);
     814           0 :     return false;
     815             : }
     816             : 
     817             : /*
     818             :  * Fill in our issuer identifier (and discovery URI, if possible) using the
     819             :  * connection parameters. If conn->oauth_discovery_uri can't be populated in
     820             :  * this function, it will be requested from the server.
     821             :  */
     822             : static bool
     823           0 : setup_oauth_parameters(PGconn *conn)
     824             : {
     825             :     /*
     826             :      * This is the only function that sets conn->oauth_issuer_id. If a
     827             :      * previous connection attempt has already computed it, don't overwrite it
     828             :      * or the discovery URI. (There's no reason for them to change once
     829             :      * they're set, and handle_oauth_sasl_error() will fail the connection if
     830             :      * the server attempts to switch them on us later.)
     831             :      */
     832           0 :     if (conn->oauth_issuer_id)
     833           0 :         return true;
     834             : 
     835             :     /*---
     836             :      * To talk to a server, we require the user to provide issuer and client
     837             :      * identifiers.
     838             :      *
     839             :      * While it's possible for an OAuth client to support multiple issuers, it
     840             :      * requires additional effort to make sure the flows in use are safe -- to
     841             :      * quote RFC 9207,
     842             :      *
     843             :      *     OAuth clients that interact with only one authorization server are
     844             :      *     not vulnerable to mix-up attacks. However, when such clients decide
     845             :      *     to add support for a second authorization server in the future, they
     846             :      *     become vulnerable and need to apply countermeasures to mix-up
     847             :      *     attacks.
     848             :      *
     849             :      * For now, we allow only one.
     850             :      */
     851           0 :     if (!conn->oauth_issuer || !conn->oauth_client_id)
     852             :     {
     853           0 :         libpq_append_conn_error(conn,
     854             :                                 "server requires OAuth authentication, but oauth_issuer and oauth_client_id are not both set");
     855           0 :         return false;
     856             :     }
     857             : 
     858             :     /*
     859             :      * oauth_issuer is interpreted differently if it's a well-known discovery
     860             :      * URI rather than just an issuer identifier.
     861             :      */
     862           0 :     if (strstr(conn->oauth_issuer, WK_PREFIX) != NULL)
     863             :     {
     864             :         /*
     865             :          * Convert the URI back to an issuer identifier. (This also performs
     866             :          * validation of the URI format.)
     867             :          */
     868           0 :         conn->oauth_issuer_id = issuer_from_well_known_uri(conn,
     869           0 :                                                            conn->oauth_issuer);
     870           0 :         if (!conn->oauth_issuer_id)
     871           0 :             return false;       /* error message already set */
     872             : 
     873           0 :         conn->oauth_discovery_uri = strdup(conn->oauth_issuer);
     874           0 :         if (!conn->oauth_discovery_uri)
     875             :         {
     876           0 :             libpq_append_conn_error(conn, "out of memory");
     877           0 :             return false;
     878             :         }
     879             :     }
     880             :     else
     881             :     {
     882             :         /*
     883             :          * Treat oauth_issuer as an issuer identifier. We'll ask the server
     884             :          * for the discovery URI.
     885             :          */
     886           0 :         conn->oauth_issuer_id = strdup(conn->oauth_issuer);
     887           0 :         if (!conn->oauth_issuer_id)
     888             :         {
     889           0 :             libpq_append_conn_error(conn, "out of memory");
     890           0 :             return false;
     891             :         }
     892             :     }
     893             : 
     894           0 :     return true;
     895             : }
     896             : 
     897             : /*
     898             :  * Implements the OAUTHBEARER SASL exchange (RFC 7628, Sec. 3.2).
     899             :  *
     900             :  * If the necessary OAuth parameters are set up on the connection, this will run
     901             :  * the client flow asynchronously and present the resulting token to the server.
     902             :  * Otherwise, an empty discovery response will be sent and any parameters sent
     903             :  * back by the server will be stored for a second attempt.
     904             :  *
     905             :  * For a full description of the API, see libpq/sasl.h.
     906             :  */
     907             : static SASLStatus
     908           0 : oauth_exchange(void *opaq, bool final,
     909             :                char *input, int inputlen,
     910             :                char **output, int *outputlen)
     911             : {
     912           0 :     fe_oauth_state *state = opaq;
     913           0 :     PGconn     *conn = state->conn;
     914           0 :     bool        discover = false;
     915             : 
     916           0 :     *output = NULL;
     917           0 :     *outputlen = 0;
     918             : 
     919           0 :     switch (state->step)
     920             :     {
     921           0 :         case FE_OAUTH_INIT:
     922             :             /* We begin in the initial response phase. */
     923             :             Assert(inputlen == -1);
     924             : 
     925           0 :             if (!setup_oauth_parameters(conn))
     926           0 :                 return SASL_FAILED;
     927             : 
     928           0 :             if (conn->oauth_token)
     929             :             {
     930             :                 /*
     931             :                  * A previous connection already fetched the token; we'll use
     932             :                  * it below.
     933             :                  */
     934             :             }
     935           0 :             else if (conn->oauth_discovery_uri)
     936             :             {
     937             :                 /*
     938             :                  * We don't have a token, but we have a discovery URI already
     939             :                  * stored. Decide whether we're using a user-provided OAuth
     940             :                  * flow or the one we have built in.
     941             :                  */
     942           0 :                 if (!setup_token_request(conn, state))
     943           0 :                     return SASL_FAILED;
     944             : 
     945           0 :                 if (conn->oauth_token)
     946             :                 {
     947             :                     /*
     948             :                      * A really smart user implementation may have already
     949             :                      * given us the token (e.g. if there was an unexpired copy
     950             :                      * already cached), and we can use it immediately.
     951             :                      */
     952             :                 }
     953             :                 else
     954             :                 {
     955             :                     /*
     956             :                      * Otherwise, we'll have to hand the connection over to
     957             :                      * our OAuth implementation.
     958             :                      *
     959             :                      * This could take a while, since it generally involves a
     960             :                      * user in the loop. To avoid consuming the server's
     961             :                      * authentication timeout, we'll continue this handshake
     962             :                      * to the end, so that the server can close its side of
     963             :                      * the connection. We'll open a second connection later
     964             :                      * once we've retrieved a token.
     965             :                      */
     966           0 :                     discover = true;
     967             :                 }
     968             :             }
     969             :             else
     970             :             {
     971             :                 /*
     972             :                  * If we don't have a token, and we don't have a discovery URI
     973             :                  * to be able to request a token, we ask the server for one
     974             :                  * explicitly.
     975             :                  */
     976           0 :                 discover = true;
     977             :             }
     978             : 
     979             :             /*
     980             :              * Generate an initial response. This either contains a token, if
     981             :              * we have one, or an empty discovery response which is doomed to
     982             :              * fail.
     983             :              */
     984           0 :             *output = client_initial_response(conn, discover);
     985           0 :             if (!*output)
     986           0 :                 return SASL_FAILED;
     987             : 
     988           0 :             *outputlen = strlen(*output);
     989           0 :             state->step = FE_OAUTH_BEARER_SENT;
     990             : 
     991           0 :             if (conn->oauth_token)
     992             :             {
     993             :                 /*
     994             :                  * For the purposes of require_auth, our side of
     995             :                  * authentication is done at this point; the server will
     996             :                  * either accept the connection or send an error. Unlike
     997             :                  * SCRAM, there is no additional server data to check upon
     998             :                  * success.
     999             :                  */
    1000           0 :                 conn->client_finished_auth = true;
    1001             :             }
    1002             : 
    1003           0 :             return SASL_CONTINUE;
    1004             : 
    1005           0 :         case FE_OAUTH_BEARER_SENT:
    1006           0 :             if (final)
    1007             :             {
    1008             :                 /*
    1009             :                  * OAUTHBEARER does not make use of additional data with a
    1010             :                  * successful SASL exchange, so we shouldn't get an
    1011             :                  * AuthenticationSASLFinal message.
    1012             :                  */
    1013           0 :                 libpq_append_conn_error(conn,
    1014             :                                         "server sent unexpected additional OAuth data");
    1015           0 :                 return SASL_FAILED;
    1016             :             }
    1017             : 
    1018             :             /*
    1019             :              * An error message was sent by the server. Respond with the
    1020             :              * required dummy message (RFC 7628, sec. 3.2.3).
    1021             :              */
    1022           0 :             *output = strdup(kvsep);
    1023           0 :             if (unlikely(!*output))
    1024             :             {
    1025           0 :                 libpq_append_conn_error(conn, "out of memory");
    1026           0 :                 return SASL_FAILED;
    1027             :             }
    1028           0 :             *outputlen = strlen(*output);   /* == 1 */
    1029             : 
    1030             :             /* Grab the settings from discovery. */
    1031           0 :             if (!handle_oauth_sasl_error(conn, input, inputlen))
    1032           0 :                 return SASL_FAILED;
    1033             : 
    1034           0 :             if (conn->oauth_token)
    1035             :             {
    1036             :                 /*
    1037             :                  * The server rejected our token. Continue onwards towards the
    1038             :                  * expected FATAL message, but mark our state to catch any
    1039             :                  * unexpected "success" from the server.
    1040             :                  */
    1041           0 :                 state->step = FE_OAUTH_SERVER_ERROR;
    1042           0 :                 return SASL_CONTINUE;
    1043             :             }
    1044             : 
    1045           0 :             if (!conn->async_auth)
    1046             :             {
    1047             :                 /*
    1048             :                  * No OAuth flow is set up yet. Did we get enough information
    1049             :                  * from the server to create one?
    1050             :                  */
    1051           0 :                 if (!conn->oauth_discovery_uri)
    1052             :                 {
    1053           0 :                     libpq_append_conn_error(conn,
    1054             :                                             "server requires OAuth authentication, but no discovery metadata was provided");
    1055           0 :                     return SASL_FAILED;
    1056             :                 }
    1057             : 
    1058             :                 /* Yes. Set up the flow now. */
    1059           0 :                 if (!setup_token_request(conn, state))
    1060           0 :                     return SASL_FAILED;
    1061             : 
    1062           0 :                 if (conn->oauth_token)
    1063             :                 {
    1064             :                     /*
    1065             :                      * A token was available in a custom flow's cache. Skip
    1066             :                      * the asynchronous processing.
    1067             :                      */
    1068           0 :                     goto reconnect;
    1069             :                 }
    1070             :             }
    1071             : 
    1072             :             /*
    1073             :              * Time to retrieve a token. This involves a number of HTTP
    1074             :              * connections and timed waits, so we escape the synchronous auth
    1075             :              * processing and tell PQconnectPoll to transfer control to our
    1076             :              * async implementation.
    1077             :              */
    1078             :             Assert(conn->async_auth);    /* should have been set already */
    1079           0 :             state->step = FE_OAUTH_REQUESTING_TOKEN;
    1080           0 :             return SASL_ASYNC;
    1081             : 
    1082           0 :         case FE_OAUTH_REQUESTING_TOKEN:
    1083             : 
    1084             :             /*
    1085             :              * We've returned successfully from token retrieval. Double-check
    1086             :              * that we have what we need for the next connection.
    1087             :              */
    1088           0 :             if (!conn->oauth_token)
    1089             :             {
    1090             :                 Assert(false);  /* should have failed before this point! */
    1091           0 :                 libpq_append_conn_error(conn,
    1092             :                                         "internal error: OAuth flow did not set a token");
    1093           0 :                 return SASL_FAILED;
    1094             :             }
    1095             : 
    1096           0 :             goto reconnect;
    1097             : 
    1098           0 :         case FE_OAUTH_SERVER_ERROR:
    1099             : 
    1100             :             /*
    1101             :              * After an error, the server should send an error response to
    1102             :              * fail the SASL handshake, which is handled in higher layers.
    1103             :              *
    1104             :              * If we get here, the server either sent *another* challenge
    1105             :              * which isn't defined in the RFC, or completed the handshake
    1106             :              * successfully after telling us it was going to fail. Neither is
    1107             :              * acceptable.
    1108             :              */
    1109           0 :             libpq_append_conn_error(conn,
    1110             :                                     "server sent additional OAuth data after error");
    1111           0 :             return SASL_FAILED;
    1112             : 
    1113           0 :         default:
    1114           0 :             libpq_append_conn_error(conn, "invalid OAuth exchange state");
    1115           0 :             break;
    1116             :     }
    1117             : 
    1118             :     Assert(false);              /* should never get here */
    1119           0 :     return SASL_FAILED;
    1120             : 
    1121           0 : reconnect:
    1122             : 
    1123             :     /*
    1124             :      * Despite being a failure from the point of view of SASL, we have enough
    1125             :      * information to restart with a new connection.
    1126             :      */
    1127           0 :     libpq_append_conn_error(conn, "retrying connection with new bearer token");
    1128           0 :     conn->oauth_want_retry = true;
    1129           0 :     return SASL_FAILED;
    1130             : }
    1131             : 
    1132             : static bool
    1133           0 : oauth_channel_bound(void *opaq)
    1134             : {
    1135             :     /* This mechanism does not support channel binding. */
    1136           0 :     return false;
    1137             : }
    1138             : 
    1139             : /*
    1140             :  * Fully clears out any stored OAuth token. This is done proactively upon
    1141             :  * successful connection as well as during pqClosePGconn().
    1142             :  */
    1143             : void
    1144       53026 : pqClearOAuthToken(PGconn *conn)
    1145             : {
    1146       53026 :     if (!conn->oauth_token)
    1147       53026 :         return;
    1148             : 
    1149           0 :     explicit_bzero(conn->oauth_token, strlen(conn->oauth_token));
    1150           0 :     free(conn->oauth_token);
    1151           0 :     conn->oauth_token = NULL;
    1152             : }
    1153             : 
    1154             : /*
    1155             :  * Returns true if the PGOAUTHDEBUG=UNSAFE flag is set in the environment.
    1156             :  */
    1157             : bool
    1158           0 : oauth_unsafe_debugging_enabled(void)
    1159             : {
    1160           0 :     const char *env = getenv("PGOAUTHDEBUG");
    1161             : 
    1162           0 :     return (env && strcmp(env, "UNSAFE") == 0);
    1163             : }

Generated by: LCOV version 1.14