From 0a72f5ee7c2a61bdb379436461269315c776b50a Mon Sep 17 00:00:00 2001 From: Remi Tricot-Le Breton Date: Fri, 1 Oct 2021 15:36:57 +0200 Subject: [PATCH] MINOR: jwt: jwt_header_query and jwt_payload_query converters Those converters allow to extract a JSON value out of a JSON Web Token's header part or payload part (the two first dot-separated base64url encoded parts of a JWS in the Compact Serialization format). They act as a json_query call on the corresponding decoded subpart when given parameters, and they return the decoded JSON subpart when no parameter is given. --- doc/configuration.txt | 20 ++++++++++ src/jwt.c | 1 - src/sample.c | 92 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 112 insertions(+), 1 deletion(-) diff --git a/doc/configuration.txt b/doc/configuration.txt index 5466487ea..465e326db 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -16617,6 +16617,26 @@ json_query(,[]) # get the value of the key 'iss' from a JWT Bearer token http-request set-var(txn.token_payload) req.hdr(Authorization),word(2,.),ub64dec,json_query('$.iss') +jwt_header_query([],[]) + When given a JSON Web Token (JWT) in input, either returns the decoded header + part of the token (the first base64-url encoded part of the JWT) if no + parameter is given, or performs a json_query on the decoded header part of + the token. See "json_query" converter for details about the accepted + json_path and output_type parameters. + + Please note that this converter is only available when HAProxy has been + compiled with USE_OPENSSL. + +jwt_payload_query([],[]) + When given a JSON Web Token (JWT) in input, either returns the decoded + payload part of the token (the second base64-url encoded part of the JWT) if + no parameter is given, or performs a json_query on the decoded payload part + of the token. See "json_query" converter for details about the accepted + json_path and output_type parameters. + + Please note that this converter is only available when HAProxy has been + compiled with USE_OPENSSL. + language([,]) Returns the value with the highest q-factor from a list as extracted from the "accept-language" header using "req.fhdr". Values with no q-factor have a diff --git a/src/jwt.c b/src/jwt.c index 210308364..0f2e00c49 100644 --- a/src/jwt.c +++ b/src/jwt.c @@ -81,7 +81,6 @@ enum jwt_alg jwt_parse_alg(const char *alg_str, unsigned int alg_len) return alg; } - /* * Split a JWT into its separate dot-separated parts. * Since only JWS following the Compact Serialization format are managed for diff --git a/src/sample.c b/src/sample.c index 970dabb5b..7b7843304 100644 --- a/src/sample.c +++ b/src/sample.c @@ -44,6 +44,7 @@ #include #include #include +#include /* sample type names */ const char *smp_to_type[SMP_TYPES] = { @@ -3493,6 +3494,91 @@ static int sample_conv_json_query(const struct arg *args, struct sample *smp, vo return 0; } +#ifdef USE_OPENSSL +/* + * Returns the decoded header or payload of a JWT if no parameter is given, or + * the value of the specified field of the corresponding JWT subpart if a + * parameter is given. + */ +static int sample_conv_jwt_member_query(const struct arg *args, struct sample *smp, + void *private, enum jwt_elt member) +{ + struct jwt_item items[JWT_ELT_MAX] = { { 0 } }; + unsigned int item_num = member + 1; /* We don't need to tokenize the full token */ + struct buffer *decoded_header = get_trash_chunk(); + int retval = 0; + + jwt_tokenize(&smp->data.u.str, items, &item_num); + + if (item_num < member + 1) + goto end; + + decoded_header = alloc_trash_chunk(); + if (!decoded_header) + goto end; + + decoded_header->data = base64urldec(items[member].start, items[member].length, + decoded_header->area, decoded_header->size); + + if (decoded_header->data == (unsigned int)-1) + goto end; + + if (args[0].type != ARGT_STR) { + smp->data.u.str = *decoded_header; + smp->data.type = SMP_T_STR; + goto end; + } + + /* We look for a specific field of the header or payload part of the JWT */ + smp->data.u.str = *decoded_header; + + retval = sample_conv_json_query(args, smp, private); + +end: + return retval; +} + +/* This function checks the "jwt_header_query" and "jwt_payload_query" converters' arguments. + * It is based on the "json_query" converter's check with the only difference + * being that the jwt converters can take 0 parameters as well. + */ +static int sample_conv_jwt_query_check(struct arg *arg, struct sample_conv *conv, + const char *file, int line, char **err) +{ + if (arg[1].data.str.data != 0) { + if (strcmp(arg[1].data.str.area, "int") != 0) { + memprintf(err, "output_type only supports \"int\" as argument"); + return 0; + } else { + arg[1].type = ARGT_SINT; + arg[1].data.sint = 0; + } + } + return 1; +} + +/* + * If no parameter is given, return the decoded header part of a JWT (the first + * base64 encoded part, corresponding to the JOSE header). + * If a parameter is given, this converter acts as a "json_query" on this + * decoded JSON. + */ +static int sample_conv_jwt_header_query(const struct arg *args, struct sample *smp, void *private) +{ + return sample_conv_jwt_member_query(args, smp, private, JWT_ELT_JOSE); +} + +/* + * If no parameter is given, return the decoded payload part of a JWT (the + * second base64 encoded part, which contains all the claims). If a parameter + * is given, this converter acts as a "json_query" on this decoded JSON. + */ +static int sample_conv_jwt_payload_query(const struct arg *args, struct sample *smp, void *private) +{ + return sample_conv_jwt_member_query(args, smp, private, JWT_ELT_CLAIMS); +} + +#endif /* USE_OPENSSL */ /************************************************************************/ /* All supported sample fetch functions must be declared here */ @@ -4000,6 +4086,12 @@ static struct sample_conv_kw_list sample_conv_kws = {ILH, { { "ltrim", sample_conv_ltrim, ARG1(1,STR), NULL, SMP_T_STR, SMP_T_STR }, { "rtrim", sample_conv_rtrim, ARG1(1,STR), NULL, SMP_T_STR, SMP_T_STR }, { "json_query", sample_conv_json_query, ARG2(1,STR,STR), sample_check_json_query , SMP_T_STR, SMP_T_ANY }, + +#ifdef USE_OPENSSL + /* JSON Web Token converters */ + { "jwt_header_query", sample_conv_jwt_header_query, ARG2(0,STR,STR), sample_conv_jwt_query_check, SMP_T_BIN, SMP_T_ANY }, + { "jwt_payload_query", sample_conv_jwt_payload_query, ARG2(0,STR,STR), sample_conv_jwt_query_check, SMP_T_BIN, SMP_T_ANY }, +#endif { NULL, NULL, 0, 0, 0 }, }};