haproxy/src/jwt.c

679 lines
17 KiB
C
Raw Normal View History

/*
* JSON Web Token (JWT) processing
*
* Copyright 2021 HAProxy Technologies
* Remi Tricot-Le Breton <rlebreton@haproxy.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/
MAJOR: jwt: Allow certificate instead of public key in jwt_verify converter The 'jwt_verify' converter could only be passed public keys as second parameter instead of full-on public certificates. This patch allows proper certificates to be used. Those certificates can be loaded in ckch_stores like any other certificate which means that all the certificate-related operations that can be made via the CLI can now benefit JWT validation as well. We now have two ways JWT validation can work, the legacy one which only relies on public keys which could not be stored in ckch_stores without some in depth changes in the way the ckch_stores are built. In this legacy way, the public keys are fully stored in a cache dedicated to JWT only which does not have any CLI commands and any way to update them during runtime. It also requires that all the public keys used are passed at least once explicitely to the 'jwt_verify' converter so that they can be loaded during init. The new way uses actual certificates, either already stored in the ckch_store tree (if predefined in a crt-store or already used previously in the configuration) or loaded in the ckch_store tree during init if they are explicitely used in the configuration like so: var(txn.bearer),jwt_verify(txn.jwt_alg,"cert.pem") When using a variable (or any other way that can only be resolved during runtime) in place of the converter's <key> parameter, the first time we encounter a new value (for which we don't have any entry in the jwt tree) we will lock the ckch_store tree and try to perform a lookup in it. If the lookup fails, an entry will still be inserted into the jwt tree so that any following call with this value avoids performing the ckch_store tree lookup.
2025-06-30 10:56:26 -04:00
#include <sys/stat.h>
#include <import/ebmbtree.h>
#include <import/ebsttree.h>
#include <haproxy/api.h>
#include <haproxy/tools.h>
#include <haproxy/openssl-compat.h>
#include <haproxy/base64.h>
#include <haproxy/jwt.h>
#include <haproxy/buf.h>
MAJOR: jwt: Allow certificate instead of public key in jwt_verify converter The 'jwt_verify' converter could only be passed public keys as second parameter instead of full-on public certificates. This patch allows proper certificates to be used. Those certificates can be loaded in ckch_stores like any other certificate which means that all the certificate-related operations that can be made via the CLI can now benefit JWT validation as well. We now have two ways JWT validation can work, the legacy one which only relies on public keys which could not be stored in ckch_stores without some in depth changes in the way the ckch_stores are built. In this legacy way, the public keys are fully stored in a cache dedicated to JWT only which does not have any CLI commands and any way to update them during runtime. It also requires that all the public keys used are passed at least once explicitely to the 'jwt_verify' converter so that they can be loaded during init. The new way uses actual certificates, either already stored in the ckch_store tree (if predefined in a crt-store or already used previously in the configuration) or loaded in the ckch_store tree during init if they are explicitely used in the configuration like so: var(txn.bearer),jwt_verify(txn.jwt_alg,"cert.pem") When using a variable (or any other way that can only be resolved during runtime) in place of the converter's <key> parameter, the first time we encounter a new value (for which we don't have any entry in the jwt tree) we will lock the ckch_store tree and try to perform a lookup in it. If the lookup fails, an entry will still be inserted into the jwt tree so that any following call with this value avoids performing the ckch_store tree lookup.
2025-06-30 10:56:26 -04:00
#include <haproxy/ssl_ckch.h>
#include <haproxy/ssl_sock.h>
#ifdef USE_OPENSSL
MAJOR: jwt: Allow certificate instead of public key in jwt_verify converter The 'jwt_verify' converter could only be passed public keys as second parameter instead of full-on public certificates. This patch allows proper certificates to be used. Those certificates can be loaded in ckch_stores like any other certificate which means that all the certificate-related operations that can be made via the CLI can now benefit JWT validation as well. We now have two ways JWT validation can work, the legacy one which only relies on public keys which could not be stored in ckch_stores without some in depth changes in the way the ckch_stores are built. In this legacy way, the public keys are fully stored in a cache dedicated to JWT only which does not have any CLI commands and any way to update them during runtime. It also requires that all the public keys used are passed at least once explicitely to the 'jwt_verify' converter so that they can be loaded during init. The new way uses actual certificates, either already stored in the ckch_store tree (if predefined in a crt-store or already used previously in the configuration) or loaded in the ckch_store tree during init if they are explicitely used in the configuration like so: var(txn.bearer),jwt_verify(txn.jwt_alg,"cert.pem") When using a variable (or any other way that can only be resolved during runtime) in place of the converter's <key> parameter, the first time we encounter a new value (for which we don't have any entry in the jwt tree) we will lock the ckch_store tree and try to perform a lookup in it. If the lookup fails, an entry will still be inserted into the jwt tree so that any following call with this value avoids performing the ckch_store tree lookup.
2025-06-30 10:56:26 -04:00
/* Tree into which the public certificates used to validate JWTs will be stored. */
MAJOR: jwt: Allow certificate instead of public key in jwt_verify converter The 'jwt_verify' converter could only be passed public keys as second parameter instead of full-on public certificates. This patch allows proper certificates to be used. Those certificates can be loaded in ckch_stores like any other certificate which means that all the certificate-related operations that can be made via the CLI can now benefit JWT validation as well. We now have two ways JWT validation can work, the legacy one which only relies on public keys which could not be stored in ckch_stores without some in depth changes in the way the ckch_stores are built. In this legacy way, the public keys are fully stored in a cache dedicated to JWT only which does not have any CLI commands and any way to update them during runtime. It also requires that all the public keys used are passed at least once explicitely to the 'jwt_verify' converter so that they can be loaded during init. The new way uses actual certificates, either already stored in the ckch_store tree (if predefined in a crt-store or already used previously in the configuration) or loaded in the ckch_store tree during init if they are explicitely used in the configuration like so: var(txn.bearer),jwt_verify(txn.jwt_alg,"cert.pem") When using a variable (or any other way that can only be resolved during runtime) in place of the converter's <key> parameter, the first time we encounter a new value (for which we don't have any entry in the jwt tree) we will lock the ckch_store tree and try to perform a lookup in it. If the lookup fails, an entry will still be inserted into the jwt tree so that any following call with this value avoids performing the ckch_store tree lookup.
2025-06-30 10:56:26 -04:00
struct eb_root jwt_cert_tree = EB_ROOT_UNIQUE;
__decl_rwlock(jwt_tree_lock);
/*
* The possible algorithm strings that can be found in a JWS's JOSE header are
* defined in section 3.1 of RFC7518.
*/
enum jwt_alg jwt_parse_alg(const char *alg_str, unsigned int alg_len)
{
enum jwt_alg alg = JWT_ALG_DEFAULT;
/* Algorithms are all 5 characters long apart from "none". */
if (alg_len < sizeof("HS256")-1) {
if (alg_len == sizeof("none")-1 && strcmp("none", alg_str) == 0)
alg = JWS_ALG_NONE;
return alg;
}
if (alg == JWT_ALG_DEFAULT) {
switch(*alg_str++) {
case 'H':
if (strncmp(alg_str, "S256", alg_len-1) == 0)
alg = JWS_ALG_HS256;
else if (strncmp(alg_str, "S384", alg_len-1) == 0)
alg = JWS_ALG_HS384;
else if (strncmp(alg_str, "S512", alg_len-1) == 0)
alg = JWS_ALG_HS512;
break;
case 'R':
if (strncmp(alg_str, "S256", alg_len-1) == 0)
alg = JWS_ALG_RS256;
else if (strncmp(alg_str, "S384", alg_len-1) == 0)
alg = JWS_ALG_RS384;
else if (strncmp(alg_str, "S512", alg_len-1) == 0)
alg = JWS_ALG_RS512;
break;
case 'E':
if (strncmp(alg_str, "S256", alg_len-1) == 0)
alg = JWS_ALG_ES256;
else if (strncmp(alg_str, "S384", alg_len-1) == 0)
alg = JWS_ALG_ES384;
else if (strncmp(alg_str, "S512", alg_len-1) == 0)
alg = JWS_ALG_ES512;
break;
case 'P':
if (strncmp(alg_str, "S256", alg_len-1) == 0)
alg = JWS_ALG_PS256;
else if (strncmp(alg_str, "S384", alg_len-1) == 0)
alg = JWS_ALG_PS384;
else if (strncmp(alg_str, "S512", alg_len-1) == 0)
alg = JWS_ALG_PS512;
break;
default:
break;
}
}
return alg;
}
/*
* Split a JWT into its separate dot-separated parts.
* Since only JWS following the Compact Serialization format are managed for
* 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.
*/
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;
}
while (index < *item_num && ptr < jwt_end) {
if (*ptr++ == '.') {
items[index++].length = length;
if (index == *item_num)
return -1;
items[index].start = ptr;
items[index].length = 0;
length = 0;
} else
++length;
}
if (index < *item_num)
items[index].length = length;
*item_num = (index+1);
return (ptr != jwt_end);
}
/*
* Parse a public certificate and insert it into the jwt_cert_tree.
MAJOR: jwt: Allow certificate instead of public key in jwt_verify converter The 'jwt_verify' converter could only be passed public keys as second parameter instead of full-on public certificates. This patch allows proper certificates to be used. Those certificates can be loaded in ckch_stores like any other certificate which means that all the certificate-related operations that can be made via the CLI can now benefit JWT validation as well. We now have two ways JWT validation can work, the legacy one which only relies on public keys which could not be stored in ckch_stores without some in depth changes in the way the ckch_stores are built. In this legacy way, the public keys are fully stored in a cache dedicated to JWT only which does not have any CLI commands and any way to update them during runtime. It also requires that all the public keys used are passed at least once explicitely to the 'jwt_verify' converter so that they can be loaded during init. The new way uses actual certificates, either already stored in the ckch_store tree (if predefined in a crt-store or already used previously in the configuration) or loaded in the ckch_store tree during init if they are explicitely used in the configuration like so: var(txn.bearer),jwt_verify(txn.jwt_alg,"cert.pem") When using a variable (or any other way that can only be resolved during runtime) in place of the converter's <key> parameter, the first time we encounter a new value (for which we don't have any entry in the jwt tree) we will lock the ckch_store tree and try to perform a lookup in it. If the lookup fails, an entry will still be inserted into the jwt tree so that any following call with this value avoids performing the ckch_store tree lookup.
2025-06-30 10:56:26 -04:00
* This function can only be called during configuration parsing so we do not
* need to lock the jwt certificate tree.
* Returns 0 in case of success.
*/
MAJOR: jwt: Allow certificate instead of public key in jwt_verify converter The 'jwt_verify' converter could only be passed public keys as second parameter instead of full-on public certificates. This patch allows proper certificates to be used. Those certificates can be loaded in ckch_stores like any other certificate which means that all the certificate-related operations that can be made via the CLI can now benefit JWT validation as well. We now have two ways JWT validation can work, the legacy one which only relies on public keys which could not be stored in ckch_stores without some in depth changes in the way the ckch_stores are built. In this legacy way, the public keys are fully stored in a cache dedicated to JWT only which does not have any CLI commands and any way to update them during runtime. It also requires that all the public keys used are passed at least once explicitely to the 'jwt_verify' converter so that they can be loaded during init. The new way uses actual certificates, either already stored in the ckch_store tree (if predefined in a crt-store or already used previously in the configuration) or loaded in the ckch_store tree during init if they are explicitely used in the configuration like so: var(txn.bearer),jwt_verify(txn.jwt_alg,"cert.pem") When using a variable (or any other way that can only be resolved during runtime) in place of the converter's <key> parameter, the first time we encounter a new value (for which we don't have any entry in the jwt tree) we will lock the ckch_store tree and try to perform a lookup in it. If the lookup fails, an entry will still be inserted into the jwt tree so that any following call with this value avoids performing the ckch_store tree lookup.
2025-06-30 10:56:26 -04:00
int jwt_tree_load_cert(char *path, int pathlen, const char *file, int line, char **err)
{
int retval = -1;
struct jwt_cert_tree_entry *entry = NULL;
EVP_PKEY *pubkey = NULL;
BIO *bio = NULL;
MAJOR: jwt: Allow certificate instead of public key in jwt_verify converter The 'jwt_verify' converter could only be passed public keys as second parameter instead of full-on public certificates. This patch allows proper certificates to be used. Those certificates can be loaded in ckch_stores like any other certificate which means that all the certificate-related operations that can be made via the CLI can now benefit JWT validation as well. We now have two ways JWT validation can work, the legacy one which only relies on public keys which could not be stored in ckch_stores without some in depth changes in the way the ckch_stores are built. In this legacy way, the public keys are fully stored in a cache dedicated to JWT only which does not have any CLI commands and any way to update them during runtime. It also requires that all the public keys used are passed at least once explicitely to the 'jwt_verify' converter so that they can be loaded during init. The new way uses actual certificates, either already stored in the ckch_store tree (if predefined in a crt-store or already used previously in the configuration) or loaded in the ckch_store tree during init if they are explicitely used in the configuration like so: var(txn.bearer),jwt_verify(txn.jwt_alg,"cert.pem") When using a variable (or any other way that can only be resolved during runtime) in place of the converter's <key> parameter, the first time we encounter a new value (for which we don't have any entry in the jwt tree) we will lock the ckch_store tree and try to perform a lookup in it. If the lookup fails, an entry will still be inserted into the jwt tree so that any following call with this value avoids performing the ckch_store tree lookup.
2025-06-30 10:56:26 -04:00
struct stat buf;
struct ebmb_node *eb = NULL;
struct ckch_store *store = NULL;
eb = ebst_lookup(&jwt_cert_tree, path);
if (eb)
return 0; /* Entry already in the tree, nothing to do. */
entry = calloc(1, sizeof(*entry) + pathlen + 1);
if (!entry) {
memprintf(err, "%sunable to allocate memory (jwt_cert_tree_entry).\n", err && *err ? *err : "");
return -1;
}
memcpy(entry->path, path, pathlen + 1);
MAJOR: jwt: Allow certificate instead of public key in jwt_verify converter The 'jwt_verify' converter could only be passed public keys as second parameter instead of full-on public certificates. This patch allows proper certificates to be used. Those certificates can be loaded in ckch_stores like any other certificate which means that all the certificate-related operations that can be made via the CLI can now benefit JWT validation as well. We now have two ways JWT validation can work, the legacy one which only relies on public keys which could not be stored in ckch_stores without some in depth changes in the way the ckch_stores are built. In this legacy way, the public keys are fully stored in a cache dedicated to JWT only which does not have any CLI commands and any way to update them during runtime. It also requires that all the public keys used are passed at least once explicitely to the 'jwt_verify' converter so that they can be loaded during init. The new way uses actual certificates, either already stored in the ckch_store tree (if predefined in a crt-store or already used previously in the configuration) or loaded in the ckch_store tree during init if they are explicitely used in the configuration like so: var(txn.bearer),jwt_verify(txn.jwt_alg,"cert.pem") When using a variable (or any other way that can only be resolved during runtime) in place of the converter's <key> parameter, the first time we encounter a new value (for which we don't have any entry in the jwt tree) we will lock the ckch_store tree and try to perform a lookup in it. If the lookup fails, an entry will still be inserted into the jwt tree so that any following call with this value avoids performing the ckch_store tree lookup.
2025-06-30 10:56:26 -04:00
entry->type = JWT_ENTRY_DFLT;
if (ebst_insert(&jwt_cert_tree, &entry->node) != &entry->node) {
MAJOR: jwt: Allow certificate instead of public key in jwt_verify converter The 'jwt_verify' converter could only be passed public keys as second parameter instead of full-on public certificates. This patch allows proper certificates to be used. Those certificates can be loaded in ckch_stores like any other certificate which means that all the certificate-related operations that can be made via the CLI can now benefit JWT validation as well. We now have two ways JWT validation can work, the legacy one which only relies on public keys which could not be stored in ckch_stores without some in depth changes in the way the ckch_stores are built. In this legacy way, the public keys are fully stored in a cache dedicated to JWT only which does not have any CLI commands and any way to update them during runtime. It also requires that all the public keys used are passed at least once explicitely to the 'jwt_verify' converter so that they can be loaded during init. The new way uses actual certificates, either already stored in the ckch_store tree (if predefined in a crt-store or already used previously in the configuration) or loaded in the ckch_store tree during init if they are explicitely used in the configuration like so: var(txn.bearer),jwt_verify(txn.jwt_alg,"cert.pem") When using a variable (or any other way that can only be resolved during runtime) in place of the converter's <key> parameter, the first time we encounter a new value (for which we don't have any entry in the jwt tree) we will lock the ckch_store tree and try to perform a lookup in it. If the lookup fails, an entry will still be inserted into the jwt tree so that any following call with this value avoids performing the ckch_store tree lookup.
2025-06-30 10:56:26 -04:00
/* Should never happen since we checked if the entry already
* existed previously.
*/
free(entry);
MAJOR: jwt: Allow certificate instead of public key in jwt_verify converter The 'jwt_verify' converter could only be passed public keys as second parameter instead of full-on public certificates. This patch allows proper certificates to be used. Those certificates can be loaded in ckch_stores like any other certificate which means that all the certificate-related operations that can be made via the CLI can now benefit JWT validation as well. We now have two ways JWT validation can work, the legacy one which only relies on public keys which could not be stored in ckch_stores without some in depth changes in the way the ckch_stores are built. In this legacy way, the public keys are fully stored in a cache dedicated to JWT only which does not have any CLI commands and any way to update them during runtime. It also requires that all the public keys used are passed at least once explicitely to the 'jwt_verify' converter so that they can be loaded during init. The new way uses actual certificates, either already stored in the ckch_store tree (if predefined in a crt-store or already used previously in the configuration) or loaded in the ckch_store tree during init if they are explicitely used in the configuration like so: var(txn.bearer),jwt_verify(txn.jwt_alg,"cert.pem") When using a variable (or any other way that can only be resolved during runtime) in place of the converter's <key> parameter, the first time we encounter a new value (for which we don't have any entry in the jwt tree) we will lock the ckch_store tree and try to perform a lookup in it. If the lookup fails, an entry will still be inserted into the jwt tree so that any following call with this value avoids performing the ckch_store tree lookup.
2025-06-30 10:56:26 -04:00
return 0;
}
MAJOR: jwt: Allow certificate instead of public key in jwt_verify converter The 'jwt_verify' converter could only be passed public keys as second parameter instead of full-on public certificates. This patch allows proper certificates to be used. Those certificates can be loaded in ckch_stores like any other certificate which means that all the certificate-related operations that can be made via the CLI can now benefit JWT validation as well. We now have two ways JWT validation can work, the legacy one which only relies on public keys which could not be stored in ckch_stores without some in depth changes in the way the ckch_stores are built. In this legacy way, the public keys are fully stored in a cache dedicated to JWT only which does not have any CLI commands and any way to update them during runtime. It also requires that all the public keys used are passed at least once explicitely to the 'jwt_verify' converter so that they can be loaded during init. The new way uses actual certificates, either already stored in the ckch_store tree (if predefined in a crt-store or already used previously in the configuration) or loaded in the ckch_store tree during init if they are explicitely used in the configuration like so: var(txn.bearer),jwt_verify(txn.jwt_alg,"cert.pem") When using a variable (or any other way that can only be resolved during runtime) in place of the converter's <key> parameter, the first time we encounter a new value (for which we don't have any entry in the jwt tree) we will lock the ckch_store tree and try to perform a lookup in it. If the lookup fails, an entry will still be inserted into the jwt tree so that any following call with this value avoids performing the ckch_store tree lookup.
2025-06-30 10:56:26 -04:00
if (stat(path, &buf) == 0) {
bio = BIO_new(BIO_s_file());
if (!bio) {
memprintf(err, "%sunable to allocate memory (BIO).\n", err && *err ? *err : "");
goto end;
}
if (BIO_read_filename(bio, path) == 1) {
pubkey = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL);
/* The file might exist but not contain a public key if
* we were given an actual certificate path (or a
* named crt-store).
*/
if (pubkey) {
entry->type = JWT_ENTRY_PKEY;
entry->pubkey = pubkey;
retval = 0;
goto end;
}
}
}
MAJOR: jwt: Allow certificate instead of public key in jwt_verify converter The 'jwt_verify' converter could only be passed public keys as second parameter instead of full-on public certificates. This patch allows proper certificates to be used. Those certificates can be loaded in ckch_stores like any other certificate which means that all the certificate-related operations that can be made via the CLI can now benefit JWT validation as well. We now have two ways JWT validation can work, the legacy one which only relies on public keys which could not be stored in ckch_stores without some in depth changes in the way the ckch_stores are built. In this legacy way, the public keys are fully stored in a cache dedicated to JWT only which does not have any CLI commands and any way to update them during runtime. It also requires that all the public keys used are passed at least once explicitely to the 'jwt_verify' converter so that they can be loaded during init. The new way uses actual certificates, either already stored in the ckch_store tree (if predefined in a crt-store or already used previously in the configuration) or loaded in the ckch_store tree during init if they are explicitely used in the configuration like so: var(txn.bearer),jwt_verify(txn.jwt_alg,"cert.pem") When using a variable (or any other way that can only be resolved during runtime) in place of the converter's <key> parameter, the first time we encounter a new value (for which we don't have any entry in the jwt tree) we will lock the ckch_store tree and try to perform a lookup in it. If the lookup fails, an entry will still be inserted into the jwt tree so that any following call with this value avoids performing the ckch_store tree lookup.
2025-06-30 10:56:26 -04:00
/* Look for an actual certificate or crt-store with the given name.
* If the path corresponds to an actual certificate that was not loaded
* yet we will create the corresponding ckch_store. */
if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
goto end;
store = ckchs_lookup(path);
if (!store) {
struct ckch_conf conf = {};
int err_code = 0;
/* Create a new store with the given path */
store = ckch_store_new(path);
if (!store) {
HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
goto end;
}
conf.crt = path;
MAJOR: jwt: Allow certificate instead of public key in jwt_verify converter The 'jwt_verify' converter could only be passed public keys as second parameter instead of full-on public certificates. This patch allows proper certificates to be used. Those certificates can be loaded in ckch_stores like any other certificate which means that all the certificate-related operations that can be made via the CLI can now benefit JWT validation as well. We now have two ways JWT validation can work, the legacy one which only relies on public keys which could not be stored in ckch_stores without some in depth changes in the way the ckch_stores are built. In this legacy way, the public keys are fully stored in a cache dedicated to JWT only which does not have any CLI commands and any way to update them during runtime. It also requires that all the public keys used are passed at least once explicitely to the 'jwt_verify' converter so that they can be loaded during init. The new way uses actual certificates, either already stored in the ckch_store tree (if predefined in a crt-store or already used previously in the configuration) or loaded in the ckch_store tree during init if they are explicitely used in the configuration like so: var(txn.bearer),jwt_verify(txn.jwt_alg,"cert.pem") When using a variable (or any other way that can only be resolved during runtime) in place of the converter's <key> parameter, the first time we encounter a new value (for which we don't have any entry in the jwt tree) we will lock the ckch_store tree and try to perform a lookup in it. If the lookup fails, an entry will still be inserted into the jwt tree so that any following call with this value avoids performing the ckch_store tree lookup.
2025-06-30 10:56:26 -04:00
err_code = ckch_store_load_files(&conf, store, 0, file, line, err);
if (err_code & ERR_FATAL) {
ckch_store_free(store);
MAJOR: jwt: Allow certificate instead of public key in jwt_verify converter The 'jwt_verify' converter could only be passed public keys as second parameter instead of full-on public certificates. This patch allows proper certificates to be used. Those certificates can be loaded in ckch_stores like any other certificate which means that all the certificate-related operations that can be made via the CLI can now benefit JWT validation as well. We now have two ways JWT validation can work, the legacy one which only relies on public keys which could not be stored in ckch_stores without some in depth changes in the way the ckch_stores are built. In this legacy way, the public keys are fully stored in a cache dedicated to JWT only which does not have any CLI commands and any way to update them during runtime. It also requires that all the public keys used are passed at least once explicitely to the 'jwt_verify' converter so that they can be loaded during init. The new way uses actual certificates, either already stored in the ckch_store tree (if predefined in a crt-store or already used previously in the configuration) or loaded in the ckch_store tree during init if they are explicitely used in the configuration like so: var(txn.bearer),jwt_verify(txn.jwt_alg,"cert.pem") When using a variable (or any other way that can only be resolved during runtime) in place of the converter's <key> parameter, the first time we encounter a new value (for which we don't have any entry in the jwt tree) we will lock the ckch_store tree and try to perform a lookup in it. If the lookup fails, an entry will still be inserted into the jwt tree so that any following call with this value avoids performing the ckch_store tree lookup.
2025-06-30 10:56:26 -04:00
/* If we are in this case we are in the conf
* parsing phase and this case might happen if
* we were provided an HMAC secret or a variable
* name.
*/
retval = 0;
ha_free(err);
HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
goto end;
}
MAJOR: jwt: Allow certificate instead of public key in jwt_verify converter The 'jwt_verify' converter could only be passed public keys as second parameter instead of full-on public certificates. This patch allows proper certificates to be used. Those certificates can be loaded in ckch_stores like any other certificate which means that all the certificate-related operations that can be made via the CLI can now benefit JWT validation as well. We now have two ways JWT validation can work, the legacy one which only relies on public keys which could not be stored in ckch_stores without some in depth changes in the way the ckch_stores are built. In this legacy way, the public keys are fully stored in a cache dedicated to JWT only which does not have any CLI commands and any way to update them during runtime. It also requires that all the public keys used are passed at least once explicitely to the 'jwt_verify' converter so that they can be loaded during init. The new way uses actual certificates, either already stored in the ckch_store tree (if predefined in a crt-store or already used previously in the configuration) or loaded in the ckch_store tree during init if they are explicitely used in the configuration like so: var(txn.bearer),jwt_verify(txn.jwt_alg,"cert.pem") When using a variable (or any other way that can only be resolved during runtime) in place of the converter's <key> parameter, the first time we encounter a new value (for which we don't have any entry in the jwt tree) we will lock the ckch_store tree and try to perform a lookup in it. If the lookup fails, an entry will still be inserted into the jwt tree so that any following call with this value avoids performing the ckch_store tree lookup.
2025-06-30 10:56:26 -04:00
if (ebst_insert(&ckchs_tree, &store->node) != &store->node) {
ckch_store_free(store);
HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
goto end;
}
}
MAJOR: jwt: Allow certificate instead of public key in jwt_verify converter The 'jwt_verify' converter could only be passed public keys as second parameter instead of full-on public certificates. This patch allows proper certificates to be used. Those certificates can be loaded in ckch_stores like any other certificate which means that all the certificate-related operations that can be made via the CLI can now benefit JWT validation as well. We now have two ways JWT validation can work, the legacy one which only relies on public keys which could not be stored in ckch_stores without some in depth changes in the way the ckch_stores are built. In this legacy way, the public keys are fully stored in a cache dedicated to JWT only which does not have any CLI commands and any way to update them during runtime. It also requires that all the public keys used are passed at least once explicitely to the 'jwt_verify' converter so that they can be loaded during init. The new way uses actual certificates, either already stored in the ckch_store tree (if predefined in a crt-store or already used previously in the configuration) or loaded in the ckch_store tree during init if they are explicitely used in the configuration like so: var(txn.bearer),jwt_verify(txn.jwt_alg,"cert.pem") When using a variable (or any other way that can only be resolved during runtime) in place of the converter's <key> parameter, the first time we encounter a new value (for which we don't have any entry in the jwt tree) we will lock the ckch_store tree and try to perform a lookup in it. If the lookup fails, an entry will still be inserted into the jwt tree so that any following call with this value avoids performing the ckch_store tree lookup.
2025-06-30 10:56:26 -04:00
retval = 0;
BUG_ON(store->jwt_entry != NULL);
entry->type = JWT_ENTRY_STORE;
entry->ckch_store = store;
entry->pubkey = X509_get_pubkey(store->data->cert);
store->jwt_entry = entry;
HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
end:
if (retval) {
/* Some error happened during pubkey parsing, remove the already
* inserted node from the tree and free it.
*/
ebmb_delete(&entry->node);
free(entry);
}
BIO_free(bio);
MAJOR: jwt: Allow certificate instead of public key in jwt_verify converter The 'jwt_verify' converter could only be passed public keys as second parameter instead of full-on public certificates. This patch allows proper certificates to be used. Those certificates can be loaded in ckch_stores like any other certificate which means that all the certificate-related operations that can be made via the CLI can now benefit JWT validation as well. We now have two ways JWT validation can work, the legacy one which only relies on public keys which could not be stored in ckch_stores without some in depth changes in the way the ckch_stores are built. In this legacy way, the public keys are fully stored in a cache dedicated to JWT only which does not have any CLI commands and any way to update them during runtime. It also requires that all the public keys used are passed at least once explicitely to the 'jwt_verify' converter so that they can be loaded during init. The new way uses actual certificates, either already stored in the ckch_store tree (if predefined in a crt-store or already used previously in the configuration) or loaded in the ckch_store tree during init if they are explicitely used in the configuration like so: var(txn.bearer),jwt_verify(txn.jwt_alg,"cert.pem") When using a variable (or any other way that can only be resolved during runtime) in place of the converter's <key> parameter, the first time we encounter a new value (for which we don't have any entry in the jwt tree) we will lock the ckch_store tree and try to perform a lookup in it. If the lookup fails, an entry will still be inserted into the jwt tree so that any following call with this value avoids performing the ckch_store tree lookup.
2025-06-30 10:56:26 -04:00
return retval;
}
MAJOR: jwt: Allow certificate instead of public key in jwt_verify converter The 'jwt_verify' converter could only be passed public keys as second parameter instead of full-on public certificates. This patch allows proper certificates to be used. Those certificates can be loaded in ckch_stores like any other certificate which means that all the certificate-related operations that can be made via the CLI can now benefit JWT validation as well. We now have two ways JWT validation can work, the legacy one which only relies on public keys which could not be stored in ckch_stores without some in depth changes in the way the ckch_stores are built. In this legacy way, the public keys are fully stored in a cache dedicated to JWT only which does not have any CLI commands and any way to update them during runtime. It also requires that all the public keys used are passed at least once explicitely to the 'jwt_verify' converter so that they can be loaded during init. The new way uses actual certificates, either already stored in the ckch_store tree (if predefined in a crt-store or already used previously in the configuration) or loaded in the ckch_store tree during init if they are explicitely used in the configuration like so: var(txn.bearer),jwt_verify(txn.jwt_alg,"cert.pem") When using a variable (or any other way that can only be resolved during runtime) in place of the converter's <key> parameter, the first time we encounter a new value (for which we don't have any entry in the jwt tree) we will lock the ckch_store tree and try to perform a lookup in it. If the lookup fails, an entry will still be inserted into the jwt tree so that any following call with this value avoids performing the ckch_store tree lookup.
2025-06-30 10:56:26 -04:00
/* Try to look for an already existing ckch_store in the store tree with the
* path found in the jwt_entry. Keep a reference to its pubkey if it exists.
* Return 0 in case of success.
*/
static int jwt_tree_tryload_store(struct jwt_cert_tree_entry *jwt_entry)
{
struct ckch_store *store = NULL;
int retval = 1;
if (!jwt_entry)
return 1;
/* We might have been given a 'crt-store' name */
if (HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock))
return 1;
store = ckchs_lookup(jwt_entry->path);
if (!store || store->jwt_entry)
goto end;
store->jwt_entry = jwt_entry;
BUG_ON(jwt_entry->pubkey != NULL);
jwt_entry->pubkey = X509_get_pubkey(store->data->cert);
retval = 0;
end:
HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
return retval;
}
/* Update the ckch_store and public key reference of a jwt_entry. This is only
* useful when updating a certificate from the CLI if it was being used for JWT
MAJOR: jwt: Allow certificate instead of public key in jwt_verify converter The 'jwt_verify' converter could only be passed public keys as second parameter instead of full-on public certificates. This patch allows proper certificates to be used. Those certificates can be loaded in ckch_stores like any other certificate which means that all the certificate-related operations that can be made via the CLI can now benefit JWT validation as well. We now have two ways JWT validation can work, the legacy one which only relies on public keys which could not be stored in ckch_stores without some in depth changes in the way the ckch_stores are built. In this legacy way, the public keys are fully stored in a cache dedicated to JWT only which does not have any CLI commands and any way to update them during runtime. It also requires that all the public keys used are passed at least once explicitely to the 'jwt_verify' converter so that they can be loaded during init. The new way uses actual certificates, either already stored in the ckch_store tree (if predefined in a crt-store or already used previously in the configuration) or loaded in the ckch_store tree during init if they are explicitely used in the configuration like so: var(txn.bearer),jwt_verify(txn.jwt_alg,"cert.pem") When using a variable (or any other way that can only be resolved during runtime) in place of the converter's <key> parameter, the first time we encounter a new value (for which we don't have any entry in the jwt tree) we will lock the ckch_store tree and try to perform a lookup in it. If the lookup fails, an entry will still be inserted into the jwt tree so that any following call with this value avoids performing the ckch_store tree lookup.
2025-06-30 10:56:26 -04:00
* validation.
*/
void jwt_replace_ckch_store(struct ckch_store *old_ckchs, struct ckch_store *new_ckchs)
{
struct jwt_cert_tree_entry *entry = old_ckchs->jwt_entry;
HA_RWLOCK_WRLOCK(JWT_LOCK, &jwt_tree_lock);
if (entry == NULL)
goto end;
old_ckchs->jwt_entry->ckch_store = new_ckchs;
new_ckchs->jwt_entry = old_ckchs->jwt_entry;
EVP_PKEY_free(entry->pubkey);
entry->pubkey = X509_get_pubkey(new_ckchs->data->cert);
end:
HA_RWLOCK_WRUNLOCK(JWT_LOCK, &jwt_tree_lock);
}
/*
* Calculate the HMAC signature of a specific JWT and check that it matches the
* one included in the token.
* Returns 1 in case of success.
*/
static enum jwt_vrfy_status
jwt_jwsverify_hmac(const struct jwt_ctx *ctx, const struct buffer *decoded_signature)
{
const EVP_MD *evp = NULL;
unsigned char signature[EVP_MAX_MD_SIZE];
unsigned int signature_length = 0;
unsigned char *hmac_res = NULL;
enum jwt_vrfy_status retval = JWT_VRFY_KO;
switch(ctx->alg) {
case JWS_ALG_HS256:
evp = EVP_sha256();
break;
case JWS_ALG_HS384:
evp = EVP_sha384();
break;
case JWS_ALG_HS512:
evp = EVP_sha512();
break;
default: break;
}
hmac_res = HMAC(evp, ctx->key, ctx->key_length, (const unsigned char*)ctx->jose.start,
ctx->jose.length + ctx->claims.length + 1, signature, &signature_length);
if (hmac_res && signature_length == decoded_signature->data &&
(CRYPTO_memcmp(decoded_signature->area, signature, signature_length) == 0))
retval = JWT_VRFY_OK;
return retval;
}
/*
* Convert a JWT ECDSA signature (R and S parameters concatenatedi, see section
* 3.4 of RFC7518) into an ECDSA_SIG that can be fed back into OpenSSL's digest
* verification functions.
* Returns 0 in case of success.
*/
static int convert_ecdsa_sig(const struct jwt_ctx *ctx, struct buffer *signature)
{
int retval = 0;
ECDSA_SIG *ecdsa_sig = NULL;
BIGNUM *ec_R = NULL, *ec_S = NULL;
unsigned int bignum_len;
unsigned char *p;
ecdsa_sig = ECDSA_SIG_new();
if (!ecdsa_sig) {
retval = JWT_VRFY_OUT_OF_MEMORY;
goto end;
}
if (b_data(signature) % 2) {
retval = JWT_VRFY_INVALID_TOKEN;
goto end;
}
bignum_len = b_data(signature) / 2;
ec_R = BN_bin2bn((unsigned char*)b_orig(signature), bignum_len, NULL);
ec_S = BN_bin2bn((unsigned char *)(b_orig(signature) + bignum_len), bignum_len, NULL);
if (!ec_R || !ec_S) {
retval = JWT_VRFY_INVALID_TOKEN;
goto end;
}
/* Build ecdsa out of R and S values. */
ECDSA_SIG_set0(ecdsa_sig, ec_R, ec_S);
p = (unsigned char*)signature->area;
signature->data = i2d_ECDSA_SIG(ecdsa_sig, &p);
if (signature->data == 0) {
retval = JWT_VRFY_INVALID_TOKEN;
goto end;
}
end:
ECDSA_SIG_free(ecdsa_sig);
return retval;
}
/*
* Check that the signature included in a JWT signed via RSA or ECDSA is valid
* and can be verified thanks to a given public certificate.
* Returns 1 in case of success.
*/
static enum jwt_vrfy_status
jwt_jwsverify_rsa_ecdsa(const struct jwt_ctx *ctx, struct buffer *decoded_signature)
{
const EVP_MD *evp = NULL;
EVP_MD_CTX *evp_md_ctx;
EVP_PKEY_CTX *pkey_ctx = NULL;
enum jwt_vrfy_status retval = JWT_VRFY_KO;
MAJOR: jwt: Allow certificate instead of public key in jwt_verify converter The 'jwt_verify' converter could only be passed public keys as second parameter instead of full-on public certificates. This patch allows proper certificates to be used. Those certificates can be loaded in ckch_stores like any other certificate which means that all the certificate-related operations that can be made via the CLI can now benefit JWT validation as well. We now have two ways JWT validation can work, the legacy one which only relies on public keys which could not be stored in ckch_stores without some in depth changes in the way the ckch_stores are built. In this legacy way, the public keys are fully stored in a cache dedicated to JWT only which does not have any CLI commands and any way to update them during runtime. It also requires that all the public keys used are passed at least once explicitely to the 'jwt_verify' converter so that they can be loaded during init. The new way uses actual certificates, either already stored in the ckch_store tree (if predefined in a crt-store or already used previously in the configuration) or loaded in the ckch_store tree during init if they are explicitely used in the configuration like so: var(txn.bearer),jwt_verify(txn.jwt_alg,"cert.pem") When using a variable (or any other way that can only be resolved during runtime) in place of the converter's <key> parameter, the first time we encounter a new value (for which we don't have any entry in the jwt tree) we will lock the ckch_store tree and try to perform a lookup in it. If the lookup fails, an entry will still be inserted into the jwt tree so that any following call with this value avoids performing the ckch_store tree lookup.
2025-06-30 10:56:26 -04:00
struct ebmb_node *eb = NULL;
struct jwt_cert_tree_entry *entry = NULL;
int is_ecdsa = 0;
int padding = RSA_PKCS1_PADDING;
MAJOR: jwt: Allow certificate instead of public key in jwt_verify converter The 'jwt_verify' converter could only be passed public keys as second parameter instead of full-on public certificates. This patch allows proper certificates to be used. Those certificates can be loaded in ckch_stores like any other certificate which means that all the certificate-related operations that can be made via the CLI can now benefit JWT validation as well. We now have two ways JWT validation can work, the legacy one which only relies on public keys which could not be stored in ckch_stores without some in depth changes in the way the ckch_stores are built. In this legacy way, the public keys are fully stored in a cache dedicated to JWT only which does not have any CLI commands and any way to update them during runtime. It also requires that all the public keys used are passed at least once explicitely to the 'jwt_verify' converter so that they can be loaded during init. The new way uses actual certificates, either already stored in the ckch_store tree (if predefined in a crt-store or already used previously in the configuration) or loaded in the ckch_store tree during init if they are explicitely used in the configuration like so: var(txn.bearer),jwt_verify(txn.jwt_alg,"cert.pem") When using a variable (or any other way that can only be resolved during runtime) in place of the converter's <key> parameter, the first time we encounter a new value (for which we don't have any entry in the jwt tree) we will lock the ckch_store tree and try to perform a lookup in it. If the lookup fails, an entry will still be inserted into the jwt tree so that any following call with this value avoids performing the ckch_store tree lookup.
2025-06-30 10:56:26 -04:00
EVP_PKEY *pubkey = NULL;
int lock_iswrlock = 0;
switch(ctx->alg) {
case JWS_ALG_RS256:
evp = EVP_sha256();
break;
case JWS_ALG_RS384:
evp = EVP_sha384();
break;
case JWS_ALG_RS512:
evp = EVP_sha512();
break;
case JWS_ALG_ES256:
evp = EVP_sha256();
is_ecdsa = 1;
break;
case JWS_ALG_ES384:
evp = EVP_sha384();
is_ecdsa = 1;
break;
case JWS_ALG_ES512:
evp = EVP_sha512();
is_ecdsa = 1;
break;
case JWS_ALG_PS256:
evp = EVP_sha256();
padding = RSA_PKCS1_PSS_PADDING;
break;
case JWS_ALG_PS384:
evp = EVP_sha384();
padding = RSA_PKCS1_PSS_PADDING;
break;
case JWS_ALG_PS512:
evp = EVP_sha512();
padding = RSA_PKCS1_PSS_PADDING;
break;
default: break;
}
evp_md_ctx = EVP_MD_CTX_new();
if (!evp_md_ctx)
return JWT_VRFY_OUT_OF_MEMORY;
MAJOR: jwt: Allow certificate instead of public key in jwt_verify converter The 'jwt_verify' converter could only be passed public keys as second parameter instead of full-on public certificates. This patch allows proper certificates to be used. Those certificates can be loaded in ckch_stores like any other certificate which means that all the certificate-related operations that can be made via the CLI can now benefit JWT validation as well. We now have two ways JWT validation can work, the legacy one which only relies on public keys which could not be stored in ckch_stores without some in depth changes in the way the ckch_stores are built. In this legacy way, the public keys are fully stored in a cache dedicated to JWT only which does not have any CLI commands and any way to update them during runtime. It also requires that all the public keys used are passed at least once explicitely to the 'jwt_verify' converter so that they can be loaded during init. The new way uses actual certificates, either already stored in the ckch_store tree (if predefined in a crt-store or already used previously in the configuration) or loaded in the ckch_store tree during init if they are explicitely used in the configuration like so: var(txn.bearer),jwt_verify(txn.jwt_alg,"cert.pem") When using a variable (or any other way that can only be resolved during runtime) in place of the converter's <key> parameter, the first time we encounter a new value (for which we don't have any entry in the jwt tree) we will lock the ckch_store tree and try to perform a lookup in it. If the lookup fails, an entry will still be inserted into the jwt tree so that any following call with this value avoids performing the ckch_store tree lookup.
2025-06-30 10:56:26 -04:00
HA_RWLOCK_RDLOCK(JWT_LOCK, &jwt_tree_lock);
eb = ebst_lookup(&jwt_cert_tree, ctx->key);
if (!eb) {
MAJOR: jwt: Allow certificate instead of public key in jwt_verify converter The 'jwt_verify' converter could only be passed public keys as second parameter instead of full-on public certificates. This patch allows proper certificates to be used. Those certificates can be loaded in ckch_stores like any other certificate which means that all the certificate-related operations that can be made via the CLI can now benefit JWT validation as well. We now have two ways JWT validation can work, the legacy one which only relies on public keys which could not be stored in ckch_stores without some in depth changes in the way the ckch_stores are built. In this legacy way, the public keys are fully stored in a cache dedicated to JWT only which does not have any CLI commands and any way to update them during runtime. It also requires that all the public keys used are passed at least once explicitely to the 'jwt_verify' converter so that they can be loaded during init. The new way uses actual certificates, either already stored in the ckch_store tree (if predefined in a crt-store or already used previously in the configuration) or loaded in the ckch_store tree during init if they are explicitely used in the configuration like so: var(txn.bearer),jwt_verify(txn.jwt_alg,"cert.pem") When using a variable (or any other way that can only be resolved during runtime) in place of the converter's <key> parameter, the first time we encounter a new value (for which we don't have any entry in the jwt tree) we will lock the ckch_store tree and try to perform a lookup in it. If the lookup fails, an entry will still be inserted into the jwt tree so that any following call with this value avoids performing the ckch_store tree lookup.
2025-06-30 10:56:26 -04:00
HA_RWLOCK_RDUNLOCK(JWT_LOCK, &jwt_tree_lock);
/* Create new entry and insert it in the jwt cert tree if we
* could find a corresponding ckch_store.
*/
entry = calloc(1, sizeof(*entry) + ctx->key_length + 1);
if (!entry) {
retval = JWT_VRFY_OUT_OF_MEMORY;
goto end;
}
memcpy(entry->path, ctx->key, ctx->key_length);
/* The ckch_lock will be taken in jwt_tree_tryload_store so we
* can't hold the lock on the jwt_cert_tree here because the lock
* order is different when updating a certificate from the CLI,
* where the ckch_lock is taken first and then the JWT one is
* taken in jwt_replace_ckch_store.
* If no corresponding ckch_store was found, we still try to
* insert the entry in the tree so that next calls to jwt_verify
* with the same 'key' path do not perform the lookup in the
* ckch_store anymore. */
entry->type = (jwt_tree_tryload_store(entry) == 0) ? JWT_ENTRY_STORE : JWT_ENTRY_INVALID;
HA_RWLOCK_WRLOCK(JWT_LOCK, &jwt_tree_lock);
if (ebst_insert(&jwt_cert_tree, &entry->node) != &entry->node) {
/* This rather unlikely case can only happen if the tree was
* modified between the previous read unlock and here.
*/
retval = JWT_VRFY_INTERNAL_ERR;
free(entry);
entry = NULL;
HA_RWLOCK_WRUNLOCK(JWT_LOCK, &jwt_tree_lock);
goto end;
}
lock_iswrlock = 1;
} else {
entry = ebmb_entry(eb, struct jwt_cert_tree_entry, node);
}
/* We tried looking for a ckch_store but could not find it */
switch (entry->type) {
case JWT_ENTRY_PKEY:
case JWT_ENTRY_STORE:
pubkey = entry->pubkey;
if (pubkey)
EVP_PKEY_up_ref(pubkey);
break;
case JWT_ENTRY_DFLT:
case JWT_ENTRY_INVALID:
retval = JWT_VRFY_UNKNOWN_CERT;
MAJOR: jwt: Allow certificate instead of public key in jwt_verify converter The 'jwt_verify' converter could only be passed public keys as second parameter instead of full-on public certificates. This patch allows proper certificates to be used. Those certificates can be loaded in ckch_stores like any other certificate which means that all the certificate-related operations that can be made via the CLI can now benefit JWT validation as well. We now have two ways JWT validation can work, the legacy one which only relies on public keys which could not be stored in ckch_stores without some in depth changes in the way the ckch_stores are built. In this legacy way, the public keys are fully stored in a cache dedicated to JWT only which does not have any CLI commands and any way to update them during runtime. It also requires that all the public keys used are passed at least once explicitely to the 'jwt_verify' converter so that they can be loaded during init. The new way uses actual certificates, either already stored in the ckch_store tree (if predefined in a crt-store or already used previously in the configuration) or loaded in the ckch_store tree during init if they are explicitely used in the configuration like so: var(txn.bearer),jwt_verify(txn.jwt_alg,"cert.pem") When using a variable (or any other way that can only be resolved during runtime) in place of the converter's <key> parameter, the first time we encounter a new value (for which we don't have any entry in the jwt tree) we will lock the ckch_store tree and try to perform a lookup in it. If the lookup fails, an entry will still be inserted into the jwt tree so that any following call with this value avoids performing the ckch_store tree lookup.
2025-06-30 10:56:26 -04:00
break;
}
MAJOR: jwt: Allow certificate instead of public key in jwt_verify converter The 'jwt_verify' converter could only be passed public keys as second parameter instead of full-on public certificates. This patch allows proper certificates to be used. Those certificates can be loaded in ckch_stores like any other certificate which means that all the certificate-related operations that can be made via the CLI can now benefit JWT validation as well. We now have two ways JWT validation can work, the legacy one which only relies on public keys which could not be stored in ckch_stores without some in depth changes in the way the ckch_stores are built. In this legacy way, the public keys are fully stored in a cache dedicated to JWT only which does not have any CLI commands and any way to update them during runtime. It also requires that all the public keys used are passed at least once explicitely to the 'jwt_verify' converter so that they can be loaded during init. The new way uses actual certificates, either already stored in the ckch_store tree (if predefined in a crt-store or already used previously in the configuration) or loaded in the ckch_store tree during init if they are explicitely used in the configuration like so: var(txn.bearer),jwt_verify(txn.jwt_alg,"cert.pem") When using a variable (or any other way that can only be resolved during runtime) in place of the converter's <key> parameter, the first time we encounter a new value (for which we don't have any entry in the jwt tree) we will lock the ckch_store tree and try to perform a lookup in it. If the lookup fails, an entry will still be inserted into the jwt tree so that any following call with this value avoids performing the ckch_store tree lookup.
2025-06-30 10:56:26 -04:00
if (lock_iswrlock)
HA_RWLOCK_WRUNLOCK(JWT_LOCK, &jwt_tree_lock);
else
HA_RWLOCK_RDUNLOCK(JWT_LOCK, &jwt_tree_lock);
MAJOR: jwt: Allow certificate instead of public key in jwt_verify converter The 'jwt_verify' converter could only be passed public keys as second parameter instead of full-on public certificates. This patch allows proper certificates to be used. Those certificates can be loaded in ckch_stores like any other certificate which means that all the certificate-related operations that can be made via the CLI can now benefit JWT validation as well. We now have two ways JWT validation can work, the legacy one which only relies on public keys which could not be stored in ckch_stores without some in depth changes in the way the ckch_stores are built. In this legacy way, the public keys are fully stored in a cache dedicated to JWT only which does not have any CLI commands and any way to update them during runtime. It also requires that all the public keys used are passed at least once explicitely to the 'jwt_verify' converter so that they can be loaded during init. The new way uses actual certificates, either already stored in the ckch_store tree (if predefined in a crt-store or already used previously in the configuration) or loaded in the ckch_store tree during init if they are explicitely used in the configuration like so: var(txn.bearer),jwt_verify(txn.jwt_alg,"cert.pem") When using a variable (or any other way that can only be resolved during runtime) in place of the converter's <key> parameter, the first time we encounter a new value (for which we don't have any entry in the jwt tree) we will lock the ckch_store tree and try to perform a lookup in it. If the lookup fails, an entry will still be inserted into the jwt tree so that any following call with this value avoids performing the ckch_store tree lookup.
2025-06-30 10:56:26 -04:00
if (!pubkey)
goto end;
/*
* ECXXX signatures are a direct concatenation of the (R, S) pair and
* need to be converted back to asn.1 in order for verify operations to
* work with OpenSSL.
*/
if (is_ecdsa) {
int conv_retval = convert_ecdsa_sig(ctx, decoded_signature);
if (conv_retval != 0) {
retval = conv_retval;
goto end;
}
}
MAJOR: jwt: Allow certificate instead of public key in jwt_verify converter The 'jwt_verify' converter could only be passed public keys as second parameter instead of full-on public certificates. This patch allows proper certificates to be used. Those certificates can be loaded in ckch_stores like any other certificate which means that all the certificate-related operations that can be made via the CLI can now benefit JWT validation as well. We now have two ways JWT validation can work, the legacy one which only relies on public keys which could not be stored in ckch_stores without some in depth changes in the way the ckch_stores are built. In this legacy way, the public keys are fully stored in a cache dedicated to JWT only which does not have any CLI commands and any way to update them during runtime. It also requires that all the public keys used are passed at least once explicitely to the 'jwt_verify' converter so that they can be loaded during init. The new way uses actual certificates, either already stored in the ckch_store tree (if predefined in a crt-store or already used previously in the configuration) or loaded in the ckch_store tree during init if they are explicitely used in the configuration like so: var(txn.bearer),jwt_verify(txn.jwt_alg,"cert.pem") When using a variable (or any other way that can only be resolved during runtime) in place of the converter's <key> parameter, the first time we encounter a new value (for which we don't have any entry in the jwt tree) we will lock the ckch_store tree and try to perform a lookup in it. If the lookup fails, an entry will still be inserted into the jwt tree so that any following call with this value avoids performing the ckch_store tree lookup.
2025-06-30 10:56:26 -04:00
if (EVP_DigestVerifyInit(evp_md_ctx, &pkey_ctx, evp, NULL, pubkey) == 1) {
if (is_ecdsa || EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, padding) > 0) {
if (EVP_DigestVerifyUpdate(evp_md_ctx, (const unsigned char*)ctx->jose.start,
ctx->jose.length + ctx->claims.length + 1) == 1 &&
EVP_DigestVerifyFinal(evp_md_ctx, (const unsigned char*)decoded_signature->area, decoded_signature->data) == 1) {
retval = JWT_VRFY_OK;
}
}
}
end:
EVP_MD_CTX_free(evp_md_ctx);
if (retval != JWT_VRFY_OK) {
/* Don't forget to remove SSL errors to be sure they cannot be
* caught elsewhere. The error queue is cleared because it seems
* at least 2 errors are produced.
*/
ERR_clear_error();
}
MAJOR: jwt: Allow certificate instead of public key in jwt_verify converter The 'jwt_verify' converter could only be passed public keys as second parameter instead of full-on public certificates. This patch allows proper certificates to be used. Those certificates can be loaded in ckch_stores like any other certificate which means that all the certificate-related operations that can be made via the CLI can now benefit JWT validation as well. We now have two ways JWT validation can work, the legacy one which only relies on public keys which could not be stored in ckch_stores without some in depth changes in the way the ckch_stores are built. In this legacy way, the public keys are fully stored in a cache dedicated to JWT only which does not have any CLI commands and any way to update them during runtime. It also requires that all the public keys used are passed at least once explicitely to the 'jwt_verify' converter so that they can be loaded during init. The new way uses actual certificates, either already stored in the ckch_store tree (if predefined in a crt-store or already used previously in the configuration) or loaded in the ckch_store tree during init if they are explicitely used in the configuration like so: var(txn.bearer),jwt_verify(txn.jwt_alg,"cert.pem") When using a variable (or any other way that can only be resolved during runtime) in place of the converter's <key> parameter, the first time we encounter a new value (for which we don't have any entry in the jwt tree) we will lock the ckch_store tree and try to perform a lookup in it. If the lookup fails, an entry will still be inserted into the jwt tree so that any following call with this value avoids performing the ckch_store tree lookup.
2025-06-30 10:56:26 -04:00
EVP_PKEY_free(pubkey);
return retval;
}
/*
* Check that the <token> that was signed via algorithm <alg> using the <key>
* (either an HMAC secret or the path to a public certificate) has a valid
* signature.
* Returns 1 in case of success.
*/
enum jwt_vrfy_status jwt_verify(const struct buffer *token, const struct buffer *alg,
const struct buffer *key)
{
struct jwt_item items[JWT_ELT_MAX] = { { 0 } };
unsigned int item_num = JWT_ELT_MAX;
struct buffer *decoded_sig = NULL;
struct jwt_ctx ctx = {};
enum jwt_vrfy_status retval = JWT_VRFY_KO;
int ret;
ctx.alg = jwt_parse_alg(alg->area, alg->data);
if (ctx.alg == JWT_ALG_DEFAULT)
return JWT_VRFY_UNKNOWN_ALG;
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];
/* "alg" is "none", the signature must be empty for the JWS to be valid. */
if (ctx.alg == JWS_ALG_NONE) {
return (ctx.signature.length == 0) ? JWT_VRFY_OK : JWT_VRFY_KO;
}
if (ctx.signature.length == 0)
return JWT_VRFY_INVALID_TOKEN;
decoded_sig = alloc_trash_chunk();
if (!decoded_sig)
return JWT_VRFY_OUT_OF_MEMORY;
ret = base64urldec(ctx.signature.start, ctx.signature.length,
decoded_sig->area, decoded_sig->size);
if (ret == -1) {
retval = JWT_VRFY_INVALID_TOKEN;
goto end;
}
decoded_sig->data = ret;
ctx.key = key->area;
ctx.key_length = key->data;
/* We have all three sections, signature calculation can begin. */
switch(ctx.alg) {
case JWS_ALG_HS256:
case JWS_ALG_HS384:
case JWS_ALG_HS512:
/* HMAC + SHA-XXX */
retval = jwt_jwsverify_hmac(&ctx, decoded_sig);
break;
case JWS_ALG_RS256:
case JWS_ALG_RS384:
case JWS_ALG_RS512:
case JWS_ALG_ES256:
case JWS_ALG_ES384:
case JWS_ALG_ES512:
case JWS_ALG_PS256:
case JWS_ALG_PS384:
case JWS_ALG_PS512:
/* RSASSA-PKCS1-v1_5 + SHA-XXX */
/* ECDSA using P-XXX and SHA-XXX */
/* RSASSA-PSS using SHA-XXX and MGF1 with SHA-XXX */
retval = jwt_jwsverify_rsa_ecdsa(&ctx, decoded_sig);
break;
default:
/* Not managed yet */
retval = JWT_VRFY_UNMANAGED_ALG;
break;
}
end:
free_trash_chunk(decoded_sig);
return retval;
}
static void jwt_deinit(void)
{
struct ebmb_node *node = NULL;
struct jwt_cert_tree_entry *entry = NULL;
MAJOR: jwt: Allow certificate instead of public key in jwt_verify converter The 'jwt_verify' converter could only be passed public keys as second parameter instead of full-on public certificates. This patch allows proper certificates to be used. Those certificates can be loaded in ckch_stores like any other certificate which means that all the certificate-related operations that can be made via the CLI can now benefit JWT validation as well. We now have two ways JWT validation can work, the legacy one which only relies on public keys which could not be stored in ckch_stores without some in depth changes in the way the ckch_stores are built. In this legacy way, the public keys are fully stored in a cache dedicated to JWT only which does not have any CLI commands and any way to update them during runtime. It also requires that all the public keys used are passed at least once explicitely to the 'jwt_verify' converter so that they can be loaded during init. The new way uses actual certificates, either already stored in the ckch_store tree (if predefined in a crt-store or already used previously in the configuration) or loaded in the ckch_store tree during init if they are explicitely used in the configuration like so: var(txn.bearer),jwt_verify(txn.jwt_alg,"cert.pem") When using a variable (or any other way that can only be resolved during runtime) in place of the converter's <key> parameter, the first time we encounter a new value (for which we don't have any entry in the jwt tree) we will lock the ckch_store tree and try to perform a lookup in it. If the lookup fails, an entry will still be inserted into the jwt tree so that any following call with this value avoids performing the ckch_store tree lookup.
2025-06-30 10:56:26 -04:00
HA_RWLOCK_WRLOCK(JWT_LOCK, &jwt_tree_lock);
node = ebmb_first(&jwt_cert_tree);
while (node) {
entry = ebmb_entry(node, struct jwt_cert_tree_entry, node);
ebmb_delete(node);
EVP_PKEY_free(entry->pubkey);
ha_free(&entry);
node = ebmb_first(&jwt_cert_tree);
}
MAJOR: jwt: Allow certificate instead of public key in jwt_verify converter The 'jwt_verify' converter could only be passed public keys as second parameter instead of full-on public certificates. This patch allows proper certificates to be used. Those certificates can be loaded in ckch_stores like any other certificate which means that all the certificate-related operations that can be made via the CLI can now benefit JWT validation as well. We now have two ways JWT validation can work, the legacy one which only relies on public keys which could not be stored in ckch_stores without some in depth changes in the way the ckch_stores are built. In this legacy way, the public keys are fully stored in a cache dedicated to JWT only which does not have any CLI commands and any way to update them during runtime. It also requires that all the public keys used are passed at least once explicitely to the 'jwt_verify' converter so that they can be loaded during init. The new way uses actual certificates, either already stored in the ckch_store tree (if predefined in a crt-store or already used previously in the configuration) or loaded in the ckch_store tree during init if they are explicitely used in the configuration like so: var(txn.bearer),jwt_verify(txn.jwt_alg,"cert.pem") When using a variable (or any other way that can only be resolved during runtime) in place of the converter's <key> parameter, the first time we encounter a new value (for which we don't have any entry in the jwt tree) we will lock the ckch_store tree and try to perform a lookup in it. If the lookup fails, an entry will still be inserted into the jwt tree so that any following call with this value avoids performing the ckch_store tree lookup.
2025-06-30 10:56:26 -04:00
HA_RWLOCK_WRUNLOCK(JWT_LOCK, &jwt_tree_lock);
}
REGISTER_POST_DEINIT(jwt_deinit);
#endif /* USE_OPENSSL */