1/*-------------------------------------------------------------------------
4 * Server-side implementation of the SASL OAUTHBEARER mechanism.
6 * See the following RFC for more details:
7 * - RFC 7628: https://datatracker.ietf.org/doc/html/rfc7628
9 * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
10 * Portions Copyright (c) 1994, Regents of the University of California
12 * src/backend/libpq/auth-oauth.c
14 *-------------------------------------------------------------------------
37static void *
oauth_init(
Port *
port,
const char *selected_mech,
const char *shadow_pass);
39 char **
output,
int *outputlen,
const char **logdetail);
47/* Mechanism declaration */
56/* Valid states for the oauth_exchange() machine. */
64/* Mechanism callback state. */
78/* Constants seen in an OAUTHBEARER client initial response. */
79 #define KVSEP 0x01 /* separator byte for key/value pairs */
80 #define AUTH_KEY "auth" /* key containing the Authorization header */
81 #define BEARER_SCHEME "Bearer " /* required header scheme (case-insensitive!) */
84 * Retrieves the OAUTHBEARER mechanism list (currently a single item).
86 * For a full description of the API, see libpq/sasl.h.
91 /* Only OAUTHBEARER is supported. */
97 * Initializes mechanism state and loads the configured validator module.
99 * For a full description of the API, see libpq/sasl.h.
108 errcode(ERRCODE_PROTOCOL_VIOLATION),
109 errmsg(
"client selected an invalid SASL authentication mechanism"));
126 * Implements the OAUTHBEARER SASL exchange (RFC 7628, Sec. 3.2). This pulls
127 * apart the client initial response and validates the Bearer token. It also
128 * handles the dummy error response for a failed handshake, as described in
131 * For a full description of the API, see libpq/sasl.h.
135 char **
output,
int *outputlen,
const char **logdetail)
149 * If the client didn't include an "Initial Client Response" in the
150 * SASLInitialResponse message, send an empty challenge, to which the
151 * client will respond with the same data that usually comes in the
152 * Initial Client Response.
164 * Check that the input length agrees with the string length of the input.
168 errcode(ERRCODE_PROTOCOL_VIOLATION),
169 errmsg(
"malformed OAUTHBEARER message"),
171 if (inputlen != strlen(
input))
173 errcode(ERRCODE_PROTOCOL_VIOLATION),
174 errmsg(
"malformed OAUTHBEARER message"),
175 errdetail(
"Message length does not match input length."));
180 /* Handle this case below. */
186 * Only one response is valid for the client during authentication
187 * failure: a single kvsep.
191 errcode(ERRCODE_PROTOCOL_VIOLATION),
192 errmsg(
"malformed OAUTHBEARER message"),
193 errdetail(
"Client did not send a kvsep response."));
195 /* The (failed) handshake is now complete. */
200 elog(
ERROR,
"invalid OAUTHBEARER exchange state");
204 /* Handle the client's initial message. */
208 * OAUTHBEARER does not currently define a channel binding (so there is no
209 * OAUTHBEARER-PLUS, and we do not accept a 'p' specifier). We accept a
210 * 'y' specifier purely for the remote chance that a future specification
211 * could define one; then future clients can still interoperate with this
212 * server implementation. 'n' is the expected case.
219 errcode(ERRCODE_PROTOCOL_VIOLATION),
220 errmsg(
"malformed OAUTHBEARER message"),
221 errdetail(
"The server does not support channel binding for OAuth, but the client message includes channel binding data."));
224 case 'y':
/* fall through */
229 errcode(ERRCODE_PROTOCOL_VIOLATION),
230 errmsg(
"malformed OAUTHBEARER message"),
231 errdetail(
"Comma expected, but found character \"%s\".",
238 errcode(ERRCODE_PROTOCOL_VIOLATION),
239 errmsg(
"malformed OAUTHBEARER message"),
240 errdetail(
"Unexpected channel-binding flag \"%s\".",
245 * Forbid optional authzid (authorization identity). We don't support it.
249 errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
250 errmsg(
"client uses authorization identity, but it is not supported"));
253 errcode(ERRCODE_PROTOCOL_VIOLATION),
254 errmsg(
"malformed OAUTHBEARER message"),
255 errdetail(
"Unexpected attribute \"%s\" in client-first-message.",
259 /* All remaining fields are separated by the RFC's kvsep (\x01). */
262 errcode(ERRCODE_PROTOCOL_VIOLATION),
263 errmsg(
"malformed OAUTHBEARER message"),
264 errdetail(
"Key-value separator expected, but found character \"%s\".",
271 errcode(ERRCODE_PROTOCOL_VIOLATION),
272 errmsg(
"malformed OAUTHBEARER message"),
273 errdetail(
"Message does not contain an auth value."));
275 /* We should be at the end of our message. */
278 errcode(ERRCODE_PROTOCOL_VIOLATION),
279 errmsg(
"malformed OAUTHBEARER message"),
280 errdetail(
"Message contains additional data after the final terminator."));
295 /* Don't let extra copies of the bearer token hang around. */
302 * Convert an arbitrary byte to printable form. For error messages.
304 * If it's a printable ASCII character, print it as a single character.
305 * otherwise, print it in hex.
307 * The returned pointer points to a static buffer.
314 if (
c >= 0x21 &&
c <= 0x7E)
322 * Performs syntactic validation of a key and value from the initial client
323 * response. (Semantic validation of interesting values must be performed
333 static const char *key_allowed_set =
334 "abcdefghijklmnopqrstuvwxyz"
335 "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
341 errcode(ERRCODE_PROTOCOL_VIOLATION),
342 errmsg(
"malformed OAUTHBEARER message"),
343 errdetail(
"Message contains an empty key name."));
345 span = strspn(
key, key_allowed_set);
346 if (
key[span] !=
'0円')
348 errcode(ERRCODE_PROTOCOL_VIOLATION),
349 errmsg(
"malformed OAUTHBEARER message"),
350 errdetail(
"Message contains an invalid key name."));
354 * value = *(VCHAR / SP / HTAB / CR / LF )
356 * The VCHAR (visible character) class is large; a loop is more
357 * straightforward than strspn().
361 if (0x21 <= *
val && *
val <= 0x7E)
362 continue;
/* VCHAR */
370 continue;
/* SP, HTAB, CR, LF */
374 errcode(ERRCODE_PROTOCOL_VIOLATION),
375 errmsg(
"malformed OAUTHBEARER message"),
376 errdetail(
"Message contains an invalid value."));
382 * Consumes all kvpairs in an OAUTHBEARER exchange message. If the "auth" key is
383 * found, its value is returned.
392 * The relevant ABNF, from Sec. 3.1:
396 * value = *(VCHAR / SP / HTAB / CR / LF )
397 * kvpair = key "=" value kvsep
398 * ;;gs2-header = See RFC 5801
399 * client-resp = (gs2-header kvsep *kvpair kvsep) / kvsep
401 * By the time we reach this code, the gs2-header and initial kvsep have
402 * already been validated. We start at the beginning of the first kvpair.
413 * Find the end of this kvpair. Note that input is null-terminated by
414 * the SASL code, so the strchr() is bounded.
416 end = strchr(pos,
KVSEP);
419 errcode(ERRCODE_PROTOCOL_VIOLATION),
420 errmsg(
"malformed OAUTHBEARER message"),
421 errdetail(
"Message contains an unterminated key/value pair."));
426 /* Empty kvpair, signifying the end of the list. */
432 * Find the end of the key name.
434 sep = strchr(pos,
'=');
437 errcode(ERRCODE_PROTOCOL_VIOLATION),
438 errmsg(
"malformed OAUTHBEARER message"),
439 errdetail(
"Message contains a key without a value."));
442 /* Both key and value are now safely terminated. */
451 errcode(ERRCODE_PROTOCOL_VIOLATION),
452 errmsg(
"malformed OAUTHBEARER message"),
453 errdetail(
"Message contains multiple auth values."));
460 * The RFC also defines the host and port keys, but they are not
461 * required for OAUTHBEARER and we do not use them. Also, per Sec.
462 * 3.1, any key/value pairs we don't recognize must be ignored.
466 /* Move to the next pair. */
471 errcode(ERRCODE_PROTOCOL_VIOLATION),
472 errmsg(
"malformed OAUTHBEARER message"),
473 errdetail(
"Message did not contain a final terminator."));
480 * Builds the JSON response for failed authentication (RFC 7628, Sec. 3.2.2).
481 * This contains the required scopes for entry and a pointer to the OAuth/OpenID
482 * discovery document, which the client may use to conduct its OAuth flow.
491 * The admin needs to set an issuer and scope for OAuth to work. There's
492 * not really a way to hide this from the user, either, because we can't
493 * choose a "default" issuer, so be honest in the failure message. (In
494 * practice such configurations are rejected during HBA parsing.)
498 errcode(ERRCODE_INTERNAL_ERROR),
499 errmsg(
"OAuth is not properly configured for this user"),
500 errdetail_log(
"The issuer and scope parameters must be set in pg_hba.conf."));
503 * Build a default .well-known URI based on our issuer, unless the HBA has
504 * already provided one.
508 if (strstr(ctx->
issuer,
"/.well-known/") == NULL)
514 * Escaping the string here is belt-and-suspenders defensive programming
515 * since escapable characters aren't valid in either the issuer URI or the
516 * scope list, but the HBA doesn't enforce that yet.
530 *outputlen =
buf.len;
534 * Validates the provided Authorization header and returns the token from
535 * within it. NULL is returned on validation failure.
537 * Only Bearer tokens are accepted. The ABNF is defined in RFC 6750, Sec.
540 * b64token = 1*( ALPHA / DIGIT /
541 * "-" / "." / "_" / "~" / "+" / "/" ) *"="
542 * credentials = "Bearer" 1*SP b64token
544 * The "credentials" construction is what we receive in our auth value.
546 * Since that spec is subordinate to HTTP (i.e. the HTTP Authorization
547 * header format; RFC 9110 Sec. 11), the "Bearer" scheme string must be
548 * compared case-insensitively. (This is not mentioned in RFC 6750, but the
549 * OAUTHBEARER spec points it out: RFC 7628 Sec. 4.)
551 * Invalid formats are technically a protocol violation, but we shouldn't
552 * reflect any information about the sensitive Bearer token back to the
553 * client; log at COMMERROR instead.
560 static const char *
const b64token_allowed_set =
561 "abcdefghijklmnopqrstuvwxyz"
562 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
565 /* Missing auth headers should be handled by the caller. */
568 if (header[0] ==
'0円')
571 * A completely empty auth header represents a query for
572 * authentication parameters. The client expects it to fail; there's
573 * no need to make any extra noise in the logs.
575 * TODO: should we find a way to return STATUS_EOF at the top level,
576 * to suppress the authentication error entirely?
584 errcode(ERRCODE_PROTOCOL_VIOLATION),
585 errmsg(
"malformed OAuth bearer token"),
586 errdetail_log(
"Client response indicated a non-Bearer authentication scheme."));
590 /* Pull the bearer token out of the auth value. */
593 /* Swallow any additional spaces. */
594 while (*
token ==
' ')
597 /* Tokens must not be empty. */
601 errcode(ERRCODE_PROTOCOL_VIOLATION),
602 errmsg(
"malformed OAuth bearer token"),
608 * Make sure the token contains only allowed characters. Tokens may end
609 * with any number of '=' characters.
611 span = strspn(
token, b64token_allowed_set);
612 while (
token[span] ==
'=')
615 if (
token[span] !=
'0円')
618 * This error message could be more helpful by printing the
619 * problematic character(s), but that'd be a bit like printing a piece
620 * of someone's password into the logs.
623 errcode(ERRCODE_PROTOCOL_VIOLATION),
624 errmsg(
"malformed OAuth bearer token"),
625 errdetail_log(
"Bearer token is not in the correct format."));
633 * Checks that the "auth" kvpair in the client response contains a syntactically
634 * valid Bearer token, then passes it along to the loaded validator module for
635 * authorization. Returns true if validation succeeds.
645 /* Ensure that we have a correct token to validate */
650 * Ensure that we have a validation library loaded, this should always be
651 * the case and an error here is indicative of a bug.
655 errcode(ERRCODE_INTERNAL_ERROR),
656 errmsg(
"validation of OAuth token requested without a validator loaded"));
658 /* Call the validation function from the validator module */
661 port->user_name, ret))
664 errcode(ERRCODE_INTERNAL_ERROR),
665 errmsg(
"internal error in OAuth validator module"));
670 * Log any authentication results even if the token isn't authorized; it
671 * might be useful for auditing or troubleshooting.
679 errmsg(
"OAuth bearer authentication failed for user \"%s\"",
681 errdetail_log(
"Validator failed to authorize the provided token."));
687 if (
port->hba->oauth_skip_usermap)
690 * If the validator is our authorization authority, we're done.
691 * Authentication may or may not have been performed depending on the
692 * validator implementation; all that matters is that the validator
693 * says the user can log in with the target role.
699 /* Make sure the validator authenticated the user. */
703 errmsg(
"OAuth bearer authentication failed for user \"%s\"",
711 /* Finally, check the user map. */
719 * Clear and free the validation result from the validator module once
720 * we're done with it.
730 * load_validator_library
732 * Load the configured validator library in order to perform token validation.
733 * There is no built-in fallback since validation is implementation specific. If
734 * no validator library is configured, or if it fails to load, then error out
735 * since token validation won't be possible.
744 * The presence, and validity, of libname has already been established by
745 * check_oauth_validator so we don't need to perform more than Assert
746 * level checking here.
748 Assert(libname && *libname);
755 * The validator init function is required since it will set the callbacks
756 * for the validator library.
758 if (validator_init == NULL)
760 errmsg(
"%s module \"%s\" must define the symbol %s",
761 "OAuth validator", libname,
"_PG_oauth_validator_module_init"));
767 * Check the magic number, to protect against break-glass scenarios where
768 * the ABI must change within a major version. load_external_function()
769 * already checks for compatibility across major versions.
773 errmsg(
"%s module \"%s\": magic number mismatch",
774 "OAuth validator", libname),
775 errdetail(
"Server has magic number 0x%08X, module has 0x%08X.",
779 * Make sure all required callbacks are present in the ValidatorCallbacks
780 * structure. Right now only the validation callback is required.
784 errmsg(
"%s module \"%s\" must provide a %s callback",
785 "OAuth validator", libname,
"validate_cb"));
787 /* Allocate memory for validator library private state data */
794 /* Shut down the library before cleaning up its state. */
802 * Call the validator module's shutdown callback, if one is provided. This is
803 * invoked during memory context reset.
813 * Ensure an OAuth validator named in the HBA is permitted by the configuration.
815 * If the validator is currently unset and exactly one library is declared in
816 * oauth_validator_libraries, then that library will be used as the validator.
817 * Otherwise the name must be present in the list of oauth_validator_libraries.
832 errcode(ERRCODE_CONFIG_FILE_ERROR),
833 errmsg(
"oauth_validator_libraries must be set for authentication method %s",
835 errcontext(
"line %d of configuration file \"%s\"",
836 line_num, file_name));
837 *err_msg =
psprintf(
"oauth_validator_libraries must be set for authentication method %s",
842 /* SplitDirectoriesString needs a modifiable copy */
847 /* syntax error in list */
849 errcode(ERRCODE_CONFIG_FILE_ERROR),
850 errmsg(
"invalid list syntax in parameter \"%s\"",
851 "oauth_validator_libraries"));
852 *err_msg =
psprintf(
"invalid list syntax in parameter \"%s\"",
853 "oauth_validator_libraries");
859 if (elemlist->
length == 1)
866 errcode(ERRCODE_CONFIG_FILE_ERROR),
867 errmsg(
"authentication method \"oauth\" requires argument \"validator\" to be set when oauth_validator_libraries contains multiple options"),
868 errcontext(
"line %d of configuration file \"%s\"",
869 line_num, file_name));
870 *err_msg =
"authentication method \"oauth\" requires argument \"validator\" to be set when oauth_validator_libraries contains multiple options";
881 errcode(ERRCODE_INVALID_PARAMETER_VALUE),
882 errmsg(
"validator \"%s\" is not permitted by %s",
884 errcontext(
"line %d of configuration file \"%s\"",
885 line_num, file_name));
886 *err_msg =
psprintf(
"validator \"%s\" is not permitted by %s",
893 return (*err_msg == NULL);
static void shutdown_validator_library(void *arg)
static void generate_error_response(struct oauth_ctx *ctx, char **output, int *outputlen)
static bool validate(Port *port, const char *auth)
char * oauth_validator_libraries_string
static const OAuthValidatorCallbacks * ValidatorCallbacks
static void validate_kvpair(const char *key, const char *val)
bool check_oauth_validator(HbaLine *hbaline, int elevel, char **err_msg)
static void * oauth_init(Port *port, const char *selected_mech, const char *shadow_pass)
static const char * validate_token_format(const char *header)
static char * sanitize_char(char c)
static void oauth_get_mechanisms(Port *port, StringInfo buf)
static int oauth_exchange(void *opaq, const char *input, int inputlen, char **output, int *outputlen, const char **logdetail)
static char * parse_kvpairs_for_auth(char **input)
static void load_validator_library(const char *libname)
const pg_be_sasl_mech pg_be_oauth_mech
static ValidatorModuleState * validator_module_state
void set_authn_id(Port *port, const char *id)
#define PG_MAX_AUTH_TOKEN_LENGTH
static void cleanup(void)
void * load_external_function(const char *filename, const char *funcname, bool signalNotFound, void **filehandle)
int errdetail(const char *fmt,...)
int errcode(int sqlerrcode)
int errmsg(const char *fmt,...)
int errdetail_log(const char *fmt,...)
#define ereport(elevel,...)
Assert(PointerIsAligned(start, uint64))
int check_usermap(const char *usermap_name, const char *pg_user, const char *system_user, bool case_insensitive)
void escape_json(StringInfo buf, const char *str)
void list_free_deep(List *list)
char * pstrdup(const char *in)
void MemoryContextRegisterResetCallback(MemoryContext context, MemoryContextCallback *cb)
void pfree(void *pointer)
void * palloc0(Size size)
MemoryContext CurrentMemoryContext
ClientConnectionInfo MyClientConnectionInfo
#define PG_OAUTH_VALIDATOR_MAGIC
const OAuthValidatorCallbacks *(* OAuthValidatorModuleInit)(void)
#define foreach_ptr(type, var, lst)
void explicit_bzero(void *buf, size_t len)
int pg_strncasecmp(const char *s1, const char *s2, size_t n)
char * psprintf(const char *fmt,...)
#define PG_SASL_EXCHANGE_FAILURE
#define PG_SASL_EXCHANGE_CONTINUE
#define PG_SASL_EXCHANGE_SUCCESS
void appendStringInfoString(StringInfo str, const char *s)
void appendStringInfoChar(StringInfo str, char ch)
void initStringInfo(StringInfo str)
MemoryContextCallbackFunction func
ValidatorShutdownCB shutdown_cb
ValidatorValidateCB validate_cb
ValidatorStartupCB startup_cb
void(* get_mechanisms)(Port *port, StringInfo buf)
bool SplitDirectoriesString(char *rawstring, char separator, List **namelist)