From e34b633be3f0ae092b078b6c6f096b5f67b1e8ea Mon Sep 17 00:00:00 2001 From: Remi Tricot-Le Breton Date: Tue, 10 Mar 2026 14:11:55 +0100 Subject: [PATCH] MINOR: jwt: Improve 'jwt_tokenize' function The 'jwt_tokenize' function that can be used to split a JWT token into its subparts can either fully process the token (from beginning to end) when we need to check its signature, or only partially when using the jwt_header_query or jwt_member_query converters. In this case we relied on the fact that the return value of the 'jwt_tokenize' function was not checked because a '-1' was returned (which was not actually an error). In order to make this logic more explicit, the 'jwt_tokenize' function now has a way to warn the caller that the token was invalid (less subparts than the specified 'item_num') or that the token was not processed in full (enough subparts found without parsing the token all the way). The function will now only return 0 if we found strictly the same number of subparts as 'item_num'. --- include/haproxy/jwt.h | 2 +- reg-tests/jwt/jws_verify.vtc | 43 +++++++++++++++++++++++++++++++++++- src/jwe.c | 6 ++--- src/jwt.c | 35 +++++++++++++++-------------- src/sample.c | 7 +++--- 5 files changed, 68 insertions(+), 25 deletions(-) diff --git a/include/haproxy/jwt.h b/include/haproxy/jwt.h index 10e928cef..cf8410b94 100644 --- a/include/haproxy/jwt.h +++ b/include/haproxy/jwt.h @@ -27,7 +27,7 @@ #ifdef USE_OPENSSL enum jwt_alg jwt_parse_alg(const char *alg_str, unsigned int alg_len); -int jwt_tokenize(const struct buffer *jwt, struct jwt_item *items, unsigned int *item_num); +int jwt_tokenize(const struct buffer *jwt, struct jwt_item *items, unsigned int item_num); int jwt_tree_load_cert(char *path, int pathlen, int tryload_cert, const char *file, int line, char **err); enum jwt_vrfy_status jwt_verify(const struct buffer *token, const struct buffer *alg, diff --git a/reg-tests/jwt/jws_verify.vtc b/reg-tests/jwt/jws_verify.vtc index 38017db8d..64587a7bc 100644 --- a/reg-tests/jwt/jws_verify.vtc +++ b/reg-tests/jwt/jws_verify.vtc @@ -16,7 +16,7 @@ feature cmd "$HAPROXY_PROGRAM -cc 'feature(OPENSSL)'" feature cmd "command -v socat" feature ignore_unknown_macro -server s1 -repeat 27 { +server s1 -repeat 40 { rxreq txresp } -start @@ -542,3 +542,44 @@ client c27 -connect ${h1_mainfe_sock} { expect resp.http.x-jwt-verify-RS256-var2 == "1" } -run + +client c28 -connect ${h1_mainfe_sock} { + # Token content : {"alg":"none"} + # {"iss":"joe", "exp":1300819380, "http://example.com/is_root":true} + txreq -url "/none" -hdr "Authorization: Bearer eyJhbGciOiJub25lIn0.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ." + rxresp + expect resp.status == 200 + expect resp.http.x-jwt-alg == "none" + expect resp.http.x-jwt-verify == "1" +} -run + +client c29 -connect ${h1_mainfe_sock} { + # Invalid Token : too many subparts + txreq -url "/errors" -hdr "Authorization: Bearer eyJhbGciOiJub25lIn0.aa.aa.aa" + rxresp + expect resp.status == 200 + expect resp.http.x-jwt-alg == "none" + expect resp.http.x-jwt-verify == "-3" + + # Invalid Token : too many subparts + txreq -url "/errors" -hdr "Authorization: Bearer eyJhbGciOiJub25lIn0.aa.aa." + rxresp + expect resp.status == 200 + expect resp.http.x-jwt-alg == "none" + expect resp.http.x-jwt-verify == "-3" + + # Invalid Token : too few subparts + txreq -url "/errors" -hdr "Authorization: Bearer eyJhbGciOiJub25lIn0.aa" + rxresp + expect resp.status == 200 + expect resp.http.x-jwt-alg == "none" + expect resp.http.x-jwt-verify == "-3" + + # Invalid Token : no signature but alg different than "none" + txreq -url "/errors" -hdr "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ." + rxresp + expect resp.status == 200 + expect resp.http.x-jwt-alg == "RS256" + expect resp.http.x-jwt-verify == "-3" +} -run + diff --git a/src/jwe.c b/src/jwe.c index bfa73d901..91050a2c0 100644 --- a/src/jwe.c +++ b/src/jwe.c @@ -534,7 +534,7 @@ static int sample_conv_jwt_decrypt_secret(const struct arg *args, struct sample if (!chunk_cpy(input, &smp->data.u.str)) goto end; - if (jwt_tokenize(input, items, &item_num) || item_num != JWE_ELT_MAX) + if (jwt_tokenize(input, items, item_num)) goto end; alg_tag = alloc_trash_chunk(); @@ -789,7 +789,7 @@ static int sample_conv_jwt_decrypt_cert(const struct arg *args, struct sample *s if (!chunk_cpy(input, &smp->data.u.str)) goto end; - if (jwt_tokenize(input, items, &item_num) || item_num != JWE_ELT_MAX) + if (jwt_tokenize(input, items, item_num)) goto end; /* Base64Url decode the JOSE header */ @@ -1302,7 +1302,7 @@ static int sample_conv_jwt_decrypt_jwk(const struct arg *args, struct sample *sm if (!chunk_cpy(input, &smp->data.u.str)) goto end; - if (jwt_tokenize(input, items, &item_num) || item_num != JWE_ELT_MAX) + if (jwt_tokenize(input, items, item_num)) goto end; alg_tag = alloc_trash_chunk(); diff --git a/src/jwt.c b/src/jwt.c index f8c33c5b3..3dc04b1fb 100644 --- a/src/jwt.c +++ b/src/jwt.c @@ -94,26 +94,30 @@ enum jwt_alg jwt_parse_alg(const char *alg_str, unsigned int alg_len) * now, we don't need to manage more than three subparts in the tokens. * See section 3.1 of RFC7515 for more information about JWS Compact * Serialization. - * Returns 0 in case of success. + * Returns -1 in case of error, 0 if the token has exactly parts, a + * positive value otherwise. */ -int jwt_tokenize(const struct buffer *jwt, struct jwt_item *items, unsigned int *item_num) +int jwt_tokenize(const struct buffer *jwt, struct jwt_item *items, unsigned int item_num) { char *ptr = jwt->area; char *jwt_end = jwt->area + jwt->data; unsigned int index = 0; unsigned int length = 0; - if (index < *item_num) { - items[index].start = ptr; - items[index].length = 0; - } + if (item_num == 0) + return -1; - while (index < *item_num && ptr < jwt_end) { + items[index].start = ptr; + items[index].length = 0; + + while (ptr < jwt_end) { if (*ptr++ == '.') { items[index++].length = length; + /* We found enough items, no need to keep looking for + * separators. */ + if (index == item_num) + return 1; - if (index == *item_num) - return -1; items[index].start = ptr; items[index].length = 0; length = 0; @@ -121,10 +125,11 @@ int jwt_tokenize(const struct buffer *jwt, struct jwt_item *items, unsigned int ++length; } - if (index < *item_num) - items[index].length = length; + /* We might not have found enough items */ + if (index < item_num - 1) + return -1; - *item_num = (index+1); + items[index].length = length; return (ptr != jwt_end); } @@ -493,13 +498,9 @@ enum jwt_vrfy_status jwt_verify(const struct buffer *token, const struct buffer if (ctx.alg == JWT_ALG_DEFAULT) return JWT_VRFY_UNKNOWN_ALG; - if (jwt_tokenize(token, items, &item_num)) + if (jwt_tokenize(token, items, item_num)) return JWT_VRFY_INVALID_TOKEN; - if (item_num != JWT_ELT_MAX) - if (ctx.alg != JWS_ALG_NONE || item_num != JWT_ELT_SIG) - return JWT_VRFY_INVALID_TOKEN; - ctx.jose = items[JWT_ELT_JOSE]; ctx.claims = items[JWT_ELT_CLAIMS]; ctx.signature = items[JWT_ELT_SIG]; diff --git a/src/sample.c b/src/sample.c index f8150eaa5..b29f6f3d3 100644 --- a/src/sample.c +++ b/src/sample.c @@ -4795,9 +4795,10 @@ static int sample_conv_jwt_member_query(const struct arg *args, struct sample *s int retval = 0; int ret; - jwt_tokenize(&smp->data.u.str, items, &item_num); - - if (item_num < member + 1) + /* We don't need to extract all the parts from the token, we only need a + * specific one. + */ + if (jwt_tokenize(&smp->data.u.str, items, item_num) < 0) goto end; decoded_header = get_trash_chunk_sz(items[member].length);