diff --git a/doc/configuration.txt b/doc/configuration.txt index de877ab59..ac94b324f 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -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 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 + 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 + 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 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() 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() 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. diff --git a/src/jwe.c b/src/jwe.c index e36b7b0d8..a022e33ab 100644 --- a/src/jwe.c +++ b/src/jwe.c @@ -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 in array and set its 'enabled' field + * to . + * 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 for all members of . */ +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 },