MINOR: jwe: Add option to enable/disable algorithms or encryption algorithms for jwt_decrypt

Some users of the jwt_decrypt_XXX converters might want to reject JWT
tokens with a specific algorithm or encryption algorithm ("alg" or "enc"
field respectively) in order to avoid weak algorithms for instance.
This could be done from the configuration but would be tedious.

This patch adds the new 'jwt.decrypt_alg_list' and
'jwt.decrypt_enc_list' global options that can be used to define a
subset of accepted algorithms
This commit is contained in:
Remi Tricot-Le Breton 2026-05-07 17:05:16 +02:00 committed by Willy Tarreau
parent 00941af7b7
commit f82a242c8f
2 changed files with 242 additions and 27 deletions

View File

@ -1791,6 +1791,8 @@ The following keywords are supported in the "global" section :
- insecure-fork-wanted
- insecure-setuid-wanted
- issuers-chain-path
- jwt.decrypt_alg_list
- jwt.decrypt_enc_list
- key-base
- limited-quic
- localpeer
@ -2888,6 +2890,40 @@ issuers-chain-path <dir>
The OCSP features are able to use the completed chain when no .issuer was
used, or no chain was provided in the PEM.
jwt.decrypt_alg_list <list>
Set the list of algorithms allowed in the jwt_decrypt_XXX converters. JWT
tokens using an unsupported or disabled algorithms will never be decrypted.
The specified algorithms must have the same format as in section 4.1 of
RFC7518 and must be colon-separated. The special "ALL" name can be used to
enable all the supported algorithms (see "jwt_decrypt_jwk" converter for a
complete list) and a '!' can be appended to an algorithm name to explicitly
disable it.
Please note that unless "ALL" is specified, using this option will disable
any algorithm that is not explicitly mentioned in the provided list.
Examples:
# Enable all algorithms but the "ECDH-ES" one
jwt.decrypt_alg_list ALL:!ECDH-ES
# Only enable ECDH-ES algorithms
jwt.decrypt_alg_list ECDH-ES:ECDH-ES+A128KW:ECDH-ES+A192KW:ECDH-ES+A256KW
jwt.decrypt_enc_list <list>
Set the list of encryption algorithms allowed in the jwt_decrypt_XXX
converters. JWT tokens using an unsupported or disabled encryption algorithms
will never be decrypted.
The specified algorithms must have the same format as in section 5.1 of
RFC7518 and must be colon-separated. The special "ALL" name can be used to
enable all the supported algorithms (see "jwt_decrypt_jwk" converter for a
complete list) and a '!' can be appended to an algorithm name to explicitly
disable it.
Please note that unless "ALL" is specified, using this option will disable
any algorithm that is not explicitly mentioned in the provided list.
Examples:
# Enable only AES GCM encrypting algorithms
jwt.decrypt_enc_list A128GCM:A192GCM:A256GCM
key-base <dir>
Assigns a default directory to fetch SSL private keys from when a relative
path is used with "key" directives. Absolute locations specified prevail and
@ -21897,6 +21933,10 @@ jwt_decrypt_cert(<cert>)
the JOSE header) among the following: RSA1_5, RSA-OAEP, RSA-OAEP-256,
ECDH-ES, ECDH-ES+A128KW, ECDH-ES+A192KW or ECDH-ES+A256KW.
The supported algorithms and encryption algorithms ("alg" and "enc" fields of
the JOSE header respectively) can be modified thanks to the
'jwt.decrypt_alg_list' and 'jwt.decrypt_enc_list' global options.
The JWE token must be provided base64url-encoded and the output will be
provided "raw". If an error happens during token parsing, signature
verification or content decryption, an empty string will be returned.
@ -21932,6 +21972,10 @@ jwt_decrypt_jwk(<jwk>)
so the A128KW, A192KW, ECDH-ES+A128KW and ECDH-ES+A192KW algorithms won't
work.
The supported algorithms and encryption algorithms ("alg" and "enc" fields of
the JOSE header respectively) can be modified thanks to the
'jwt.decrypt_alg_list' and 'jwt.decrypt_enc_list' global options.
The JWE token must be provided base64url-encoded and the output will be
provided "raw". If an error happens during token parsing, signature
verification or content decryption, an empty string will be returned.

225
src/jwe.c
View File

@ -26,6 +26,7 @@
struct alg_enc {
const char *name;
int value;
int enabled;
};
/* https://datatracker.ietf.org/doc/html/rfc7518#section-4.1 */
@ -50,27 +51,34 @@ typedef enum {
// JWE_ALG_PBES2_HS512_A256KW,
} jwe_alg;
struct alg_enc jwe_algs[] = {
{ "RSA1_5", JWE_ALG_RSA1_5 },
{ "RSA-OAEP", JWE_ALG_RSA_OAEP },
{ "RSA-OAEP-256", JWE_ALG_RSA_OAEP_256 },
{ "A128KW", JWE_ALG_A128KW },
{ "A192KW", JWE_ALG_A192KW },
{ "A256KW", JWE_ALG_A256KW },
{ "dir", JWE_ALG_DIR },
{ "ECDH-ES", JWE_ALG_ECDH_ES },
{ "ECDH-ES+A128KW", JWE_ALG_ECDH_ES_A128KW },
{ "ECDH-ES+A192KW", JWE_ALG_ECDH_ES_A192KW },
{ "ECDH-ES+A256KW", JWE_ALG_ECDH_ES_A256KW },
{ "A128GCMKW", JWE_ALG_A128GCMKW },
{ "A192GCMKW", JWE_ALG_A192GCMKW },
{ "A256GCMKW", JWE_ALG_A256GCMKW },
{ "PBES2-HS256+A128KW", JWE_ALG_UNMANAGED },
{ "PBES2-HS384+A192KW", JWE_ALG_UNMANAGED },
{ "PBES2-HS512+A256KW", JWE_ALG_UNMANAGED },
{ NULL, JWE_ALG_UNMANAGED },
enum {
ALG_ENC_DISABLED = 0,
ALG_ENC_ENABLED = 1
};
struct alg_enc jwe_algs_dflt[] = {
{ "RSA1_5", JWE_ALG_RSA1_5, ALG_ENC_ENABLED },
{ "RSA-OAEP", JWE_ALG_RSA_OAEP, ALG_ENC_ENABLED },
{ "RSA-OAEP-256", JWE_ALG_RSA_OAEP_256, ALG_ENC_ENABLED },
{ "A128KW", JWE_ALG_A128KW, ALG_ENC_ENABLED },
{ "A192KW", JWE_ALG_A192KW, ALG_ENC_ENABLED },
{ "A256KW", JWE_ALG_A256KW, ALG_ENC_ENABLED },
{ "dir", JWE_ALG_DIR, ALG_ENC_ENABLED },
{ "ECDH-ES", JWE_ALG_ECDH_ES, ALG_ENC_ENABLED },
{ "ECDH-ES+A128KW", JWE_ALG_ECDH_ES_A128KW, ALG_ENC_ENABLED },
{ "ECDH-ES+A192KW", JWE_ALG_ECDH_ES_A192KW, ALG_ENC_ENABLED },
{ "ECDH-ES+A256KW", JWE_ALG_ECDH_ES_A256KW, ALG_ENC_ENABLED },
{ "A128GCMKW", JWE_ALG_A128GCMKW, ALG_ENC_ENABLED },
{ "A192GCMKW", JWE_ALG_A192GCMKW, ALG_ENC_ENABLED },
{ "A256GCMKW", JWE_ALG_A256GCMKW, ALG_ENC_ENABLED },
{ "PBES2-HS256+A128KW", JWE_ALG_UNMANAGED, ALG_ENC_DISABLED },
{ "PBES2-HS384+A192KW", JWE_ALG_UNMANAGED, ALG_ENC_DISABLED },
{ "PBES2-HS512+A256KW", JWE_ALG_UNMANAGED, ALG_ENC_DISABLED },
{ NULL, JWE_ALG_UNMANAGED, ALG_ENC_DISABLED },
};
struct alg_enc *jwe_algs = NULL;
/* https://datatracker.ietf.org/doc/html/rfc7518#section-5.1 */
typedef enum {
JWE_ENC_UNMANAGED = -1,
@ -82,16 +90,18 @@ typedef enum {
JWE_ENC_A256GCM,
} jwe_enc;
struct alg_enc jwe_encodings[] = {
{ "A128CBC-HS256", JWE_ENC_A128CBC_HS256 },
{ "A192CBC-HS384", JWE_ENC_A192CBC_HS384 },
{ "A256CBC-HS512", JWE_ENC_A256CBC_HS512 },
{ "A128GCM", JWE_ENC_A128GCM },
{ "A192GCM", JWE_ENC_A192GCM },
{ "A256GCM", JWE_ENC_A256GCM },
{ NULL, JWE_ENC_UNMANAGED },
struct alg_enc jwe_encodings_dflt[] = {
{ "A128CBC-HS256", JWE_ENC_A128CBC_HS256, ALG_ENC_ENABLED },
{ "A192CBC-HS384", JWE_ENC_A192CBC_HS384, ALG_ENC_ENABLED },
{ "A256CBC-HS512", JWE_ENC_A256CBC_HS512, ALG_ENC_ENABLED },
{ "A128GCM", JWE_ENC_A128GCM, ALG_ENC_ENABLED },
{ "A192GCM", JWE_ENC_A192GCM, ALG_ENC_ENABLED },
{ "A256GCM", JWE_ENC_A256GCM, ALG_ENC_ENABLED },
{ NULL, JWE_ENC_UNMANAGED, ALG_ENC_DISABLED },
};
struct alg_enc *jwe_encodings = NULL;
/*
* In the JWE Compact Serialization, a JWE is represented as the concatenation:
@ -131,6 +141,8 @@ static inline int parse_alg_enc(struct buffer *buf, struct alg_enc *array)
while (item->name) {
if (strncmp(item->name, b_orig(buf), (int)b_data(buf)) == 0) {
if (item->enabled == ALG_ENC_DISABLED)
return -1;
val = item->value;
break;
}
@ -2059,6 +2071,165 @@ end:
}
/*
* Duplicate algorithm and encoding arrays so that their enabled flags
* can be overwritten (when using the jwt.encrypt_alg/enc_list options).
* Return 0 in case of success, -1 otherwise.
*/
static int dup_alg_enc_arrays(void)
{
int ret = -1;
if (!jwe_algs) {
jwe_algs = malloc(sizeof(jwe_algs_dflt));
if (!jwe_algs) {
ha_alert("Unable to allocate the JWE algorithms array.\n");
goto end;
}
memcpy(jwe_algs, jwe_algs_dflt, sizeof(jwe_algs_dflt));
}
if (!jwe_encodings) {
jwe_encodings = malloc(sizeof(jwe_encodings_dflt));
if (!jwe_encodings) {
ha_alert("Unable to allocate the JWE encodings array.\n");
goto end;
}
memcpy(jwe_encodings, jwe_encodings_dflt, sizeof(jwe_encodings_dflt));
}
ret = 0;
end:
return ret;
}
static int jwe_init(void)
{
/* Duplicate algorithm and encoding arrays so that their enabled flags
* can be overwritten */
return dup_alg_enc_arrays();
}
REGISTER_PRE_CHECK(jwe_init);
static void jwe_deinit(void)
{
ha_free(&jwe_algs);
ha_free(&jwe_encodings);
}
REGISTER_POST_DEINIT(jwe_deinit);
/*
* Look for an entry named <algname> in array <arr> and set its 'enabled' field
* to <value>.
* Return 0 in case of success, 1 otherwise (item not found).
*/
static int set_alg_enc_flag(struct alg_enc *arr, char *algname, int algname_len, int value)
{
struct alg_enc *item = &arr[0];
while (item->name) {
if (algname_len == strlen(item->name) && strncmp(algname, item->name, algname_len) == 0) {
item->enabled = value;
return 0;
}
++item;
}
return 1;
}
/* Set the 'enabled' field to <val> for all members of <arr>. */
static void set_all_alg_enc_flags(struct alg_enc *arr, int val)
{
struct alg_enc *item = &arr[0];
while (item->name) {
item->enabled = val;
++item;
}
}
/*
* Parse algorithm list comprised of colon-separated algorithms having the same
* format as in jwe_algs_dflt or jwe_encodings_dflt arrays.
* When the option is used, all the algorithms are first disabled so any
* unspecified algo will not be enabled anymore.
*/
static int jwe_parse_global_alg_enc_list(char **args, int section_type, struct proxy *curpx,
const struct proxy *defpx, const char *file, int line,
char **err)
{
struct alg_enc *arr = NULL;
char *p, *arg = args[1];
char *arg_end = arg+strlen(arg);
char *alg, *alg_end;
int value = ALG_ENC_ENABLED;
int ret = -1;
if (dup_alg_enc_arrays())
goto end;
if (args[0][14] == 'a') {
/* "jwe.supported_algorithms" */
arr = jwe_algs;
} else {
/* "jwe.supported_encodings" */
arr = jwe_encodings;
}
/* Disable all algorithms/encodings */
set_all_alg_enc_flags(arr, ALG_ENC_DISABLED);
while (*arg) {
value = ALG_ENC_ENABLED;
if (*arg == '!') {
++arg;
value = ALG_ENC_DISABLED;
}
alg = arg;
alg_end = NULL;
p = strchr(arg, ':');
if (p) {
alg_end = p;
arg = p + 1;
} else {
alg_end = arg_end;
arg = arg_end;
}
/* If "ALL" is used all previously disabled algo will be
* reenabled */
if (strncmp(alg, "ALL", alg_end - alg) == 0) {
set_all_alg_enc_flags(arr, ALG_ENC_ENABLED);
} else {
if (set_alg_enc_flag(arr, alg, alg_end-alg, value) != 0) {
memprintf(err, "Unknown algorithm/encoding %.*s\n", (int)(alg_end-alg), alg);
goto end;
}
}
}
ret = 0;
end:
return ret;
}
static struct cfg_kw_list cfg_kws = {ILH, {
{ CFG_GLOBAL, "jwt.decrypt_alg_list", jwe_parse_global_alg_enc_list },
{ CFG_GLOBAL, "jwt.decrypt_enc_list", jwe_parse_global_alg_enc_list },
{ 0, NULL, NULL },
}};
INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);
static struct sample_conv_kw_list sample_conv_kws = {ILH, {
/* JSON Web Token converters */
{ "jwt_decrypt_secret", sample_conv_jwt_decrypt_secret, ARG1(1,STR), sample_conv_jwt_decrypt_secret_check, SMP_T_BIN, SMP_T_BIN },