mirror of
https://git.haproxy.org/git/haproxy.git/
synced 2026-02-03 16:31:08 +01:00
MINOR: jwt: Add new jwt_verify_cert converter
This converter will be in charge of performing the same operation as the 'jwt_verify' one except that it takes a full-on pem certificate path instead of a public key path as parameter. The certificate path can be either provided directly as a string or via a variable. This allows to use certificates that are not known during init to perform token validation.
This commit is contained in:
parent
c3c0597a34
commit
f5632fd481
@ -55,6 +55,7 @@ struct jwt_ctx {
|
||||
struct jwt_item signature;
|
||||
char *key;
|
||||
unsigned int key_length;
|
||||
int is_x509; /* 1 if 'key' field is a certificate, 0 otherwise */
|
||||
};
|
||||
|
||||
enum jwt_elt {
|
||||
|
||||
@ -31,7 +31,7 @@ int jwt_tokenize(const struct buffer *jwt, struct jwt_item *items, unsigned int
|
||||
int jwt_tree_load_cert(char *path, int pathlen, const char *file, int line, char **err);
|
||||
|
||||
enum jwt_vrfy_status jwt_verify(const struct buffer *token, const struct buffer *alg,
|
||||
const struct buffer *key);
|
||||
const struct buffer *key, int is_x509);
|
||||
|
||||
#endif /* USE_OPENSSL */
|
||||
|
||||
|
||||
@ -80,17 +80,17 @@ haproxy h1 -conf {
|
||||
http-response set-header x-jwt-verify-RS512 %[var(txn.bearer),jwt_verify(txn.jwt_alg,"${testdir}/rsa-public.pem")] if { var(txn.jwt_alg) -m str "RS512" }
|
||||
|
||||
# Pure certificate (not predefined in crt-store)
|
||||
http-response set-header x-jwt-verify-RS256-cert %[var(txn.bearer),jwt_verify(txn.jwt_alg,"${testdir}/cert.rsa.pem")] if { var(txn.jwt_alg) -m str "RS256" }
|
||||
http-response set-header x-jwt-verify-RS256-cert %[var(txn.bearer),jwt_verify_cert(txn.jwt_alg,"${testdir}/cert.rsa.pem")] if { var(txn.jwt_alg) -m str "RS256" }
|
||||
# Named crt-store
|
||||
http-response set-header x-jwt-verify-RS256-cert-named %[var(txn.bearer),jwt_verify(txn.jwt_alg,"@named_store${testdir}/cert.rsa.pem")] if { var(txn.jwt_alg) -m str "RS256" }
|
||||
http-response set-header x-jwt-verify-RS256-cert-named %[var(txn.bearer),jwt_verify_cert(txn.jwt_alg,"@named_store${testdir}/cert.rsa.pem")] if { var(txn.jwt_alg) -m str "RS256" }
|
||||
|
||||
# Variables
|
||||
# This first case only works because the certificate
|
||||
# is already explicitly used in a previous jwt_verify call.
|
||||
http-response set-var(txn.cert) str("${testdir}/cert.rsa.pem")
|
||||
http-response set-header x-jwt-verify-RS256-var1 %[var(txn.bearer),jwt_verify(txn.jwt_alg,txn.cert)] if { var(txn.jwt_alg) -m str "RS256" }
|
||||
http-response set-header x-jwt-verify-RS256-var1 %[var(txn.bearer),jwt_verify_cert(txn.jwt_alg,txn.cert)] if { var(txn.jwt_alg) -m str "RS256" }
|
||||
http-response set-var(txn.cert) str("@named_store${testdir}/cert.rsa.pem")
|
||||
http-response set-header x-jwt-verify-RS256-var2 %[var(txn.bearer),jwt_verify(txn.jwt_alg,txn.cert)] if { var(txn.jwt_alg) -m str "RS256" }
|
||||
http-response set-header x-jwt-verify-RS256-var2 %[var(txn.bearer),jwt_verify_cert(txn.jwt_alg,txn.cert)] if { var(txn.jwt_alg) -m str "RS256" }
|
||||
|
||||
server s1 ${s1_addr}:${s1_port}
|
||||
|
||||
@ -109,7 +109,7 @@ haproxy h1 -conf {
|
||||
|
||||
# Variables and real certificate
|
||||
http-response set-var(txn.cert) str("${testdir}/cert.ecdsa.pem")
|
||||
http-response set-header x-jwt-verify-ES256-var %[var(txn.bearer),jwt_verify(txn.jwt_alg,txn.cert)] if { var(txn.jwt_alg) -m str "ES256" }
|
||||
http-response set-header x-jwt-verify-ES256-var %[var(txn.bearer),jwt_verify_cert(txn.jwt_alg,txn.cert)] if { var(txn.jwt_alg) -m str "ES256" }
|
||||
|
||||
server s1 ${s1_addr}:${s1_port}
|
||||
|
||||
|
||||
84
src/jwt.c
84
src/jwt.c
@ -141,6 +141,7 @@ int jwt_tree_load_cert(char *path, int pathlen, const char *file, int line, char
|
||||
BIO *bio = NULL;
|
||||
struct stat buf;
|
||||
struct ebmb_node *eb = NULL;
|
||||
struct ckch_store *store = NULL;
|
||||
|
||||
eb = ebst_lookup(&jwt_cert_tree, path);
|
||||
|
||||
@ -181,6 +182,52 @@ int jwt_tree_load_cert(char *path, int pathlen, const char *file, int line, char
|
||||
}
|
||||
}
|
||||
|
||||
/* 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;
|
||||
|
||||
err_code = ckch_store_load_files(&conf, store, 0, file, line, err);
|
||||
if (err_code & ERR_FATAL) {
|
||||
ckch_store_free(store);
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
if (ebst_insert(&ckchs_tree, &store->node) != &store->node) {
|
||||
ckch_store_free(store);
|
||||
HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
|
||||
retval = 0;
|
||||
|
||||
HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
|
||||
|
||||
end:
|
||||
if (retval) {
|
||||
/* Some error happened during pubkey parsing, remove the already
|
||||
@ -209,6 +256,10 @@ jwt_jwsverify_hmac(const struct jwt_ctx *ctx, const struct buffer *decoded_signa
|
||||
unsigned char *hmac_res = NULL;
|
||||
enum jwt_vrfy_status retval = JWT_VRFY_KO;
|
||||
|
||||
if (ctx->is_x509) {
|
||||
return JWT_VRFY_UNMANAGED_ALG;
|
||||
}
|
||||
|
||||
switch(ctx->alg) {
|
||||
case JWS_ALG_HS256:
|
||||
evp = EVP_sha256();
|
||||
@ -344,15 +395,29 @@ jwt_jwsverify_rsa_ecdsa(const struct jwt_ctx *ctx, struct buffer *decoded_signat
|
||||
if (!evp_md_ctx)
|
||||
return JWT_VRFY_OUT_OF_MEMORY;
|
||||
|
||||
/* Look for a public key in the JWT tree */
|
||||
eb = ebst_lookup(&jwt_cert_tree, ctx->key);
|
||||
if (ctx->is_x509) {
|
||||
struct ckch_store *store = NULL;
|
||||
if (!HA_SPIN_TRYLOCK(CKCH_LOCK, &ckch_lock)) {
|
||||
|
||||
if (eb) {
|
||||
entry = ebmb_entry(eb, struct jwt_cert_tree_entry, node);
|
||||
store = ckchs_lookup(ctx->key);
|
||||
if (store) {
|
||||
pubkey = X509_get_pubkey(store->data->cert);
|
||||
if (pubkey)
|
||||
EVP_PKEY_up_ref(pubkey);
|
||||
}
|
||||
HA_SPIN_UNLOCK(CKCH_LOCK, &ckch_lock);
|
||||
}
|
||||
} else {
|
||||
/* Look for a public key in the JWT tree */
|
||||
eb = ebst_lookup(&jwt_cert_tree, ctx->key);
|
||||
|
||||
pubkey = entry->pubkey;
|
||||
if (pubkey)
|
||||
EVP_PKEY_up_ref(pubkey);
|
||||
if (eb) {
|
||||
entry = ebmb_entry(eb, struct jwt_cert_tree_entry, node);
|
||||
|
||||
pubkey = entry->pubkey;
|
||||
if (pubkey)
|
||||
EVP_PKEY_up_ref(pubkey);
|
||||
}
|
||||
}
|
||||
|
||||
if (!pubkey) {
|
||||
@ -400,10 +465,12 @@ end:
|
||||
* 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.
|
||||
* <key> is either a HMAC secret or a public key path if <is_509_path> is 0,
|
||||
* otherwise <key> is an X509 certificate path.
|
||||
* Returns 1 in case of success.
|
||||
*/
|
||||
enum jwt_vrfy_status jwt_verify(const struct buffer *token, const struct buffer *alg,
|
||||
const struct buffer *key)
|
||||
const struct buffer *key, int is_x509_path)
|
||||
{
|
||||
struct jwt_item items[JWT_ELT_MAX] = { { 0 } };
|
||||
unsigned int item_num = JWT_ELT_MAX;
|
||||
@ -450,6 +517,7 @@ enum jwt_vrfy_status jwt_verify(const struct buffer *token, const struct buffer
|
||||
decoded_sig->data = ret;
|
||||
ctx.key = key->area;
|
||||
ctx.key_length = key->data;
|
||||
ctx.is_x509 = is_x509_path;
|
||||
|
||||
/* We have all three sections, signature calculation can begin. */
|
||||
|
||||
|
||||
103
src/sample.c
103
src/sample.c
@ -4524,6 +4524,53 @@ static int sample_conv_jwt_verify_check(struct arg *args, struct sample_conv *co
|
||||
/* don't try to load a file with HMAC algorithms */
|
||||
retval = 1;
|
||||
break;
|
||||
default:
|
||||
retval = (jwt_tree_load_cert(args[1].data.str.area, args[1].data.str.data,
|
||||
file, line, err) == 0);
|
||||
/* The second arg might be an HMAC secret but
|
||||
* the 'alg' is stored in a var */
|
||||
if (!retval && args[0].type == ARGT_VAR)
|
||||
retval = 1;
|
||||
break;
|
||||
}
|
||||
} else if (args[1].type == ARGT_VAR) {
|
||||
/* We will try to resolve the var during runtime because the
|
||||
* processing might work if it actually points to an already
|
||||
* existing ckch_store.
|
||||
*/
|
||||
retval = 1;
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int sample_conv_jwt_verify_cert_check(struct arg *args, struct sample_conv *conv,
|
||||
const char *file, int line, char **err)
|
||||
{
|
||||
enum jwt_alg alg = JWT_ALG_DEFAULT;
|
||||
int retval = 0;
|
||||
|
||||
vars_check_arg(&args[0], NULL);
|
||||
vars_check_arg(&args[1], NULL);
|
||||
|
||||
if (args[0].type == ARGT_STR) {
|
||||
alg = jwt_parse_alg(args[0].data.str.area, args[0].data.str.data);
|
||||
|
||||
if (alg == JWT_ALG_DEFAULT) {
|
||||
memprintf(err, "unknown JWT algorithm: %s", args[0].data.str.area);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (args[1].type == ARGT_STR) {
|
||||
switch (alg) {
|
||||
case JWS_ALG_HS256:
|
||||
case JWS_ALG_HS384:
|
||||
case JWS_ALG_HS512:
|
||||
/* We can't have a certificate as second parameter for
|
||||
* HMAC-signed JWT tokens */
|
||||
memprintf(err, "HMAC-signed tokens can't be processed by this converter");
|
||||
break;
|
||||
default:
|
||||
retval = (jwt_tree_load_cert(args[1].data.str.area, args[1].data.str.data,
|
||||
file, line, err) == 0);
|
||||
@ -4580,7 +4627,60 @@ static int sample_conv_jwt_verify(const struct arg *args, struct sample *smp, vo
|
||||
if (chunk_printf(key, "%.*s", (int)b_data(&key_smp.data.u.str), b_orig(&key_smp.data.u.str)) <= 0)
|
||||
goto end;
|
||||
|
||||
ret = jwt_verify(input, alg, key);
|
||||
ret = jwt_verify(input, alg, key, 0);
|
||||
|
||||
smp->data.type = SMP_T_SINT;
|
||||
smp->data.u.sint = ret;
|
||||
|
||||
retval = 1;
|
||||
|
||||
end:
|
||||
free_trash_chunk(input);
|
||||
free_trash_chunk(alg);
|
||||
free_trash_chunk(key);
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int sample_conv_jwt_verify_cert(const struct arg *args, struct sample *smp, void *private)
|
||||
{
|
||||
struct sample alg_smp, key_smp;
|
||||
enum jwt_vrfy_status ret;
|
||||
struct buffer *input = NULL;
|
||||
struct buffer *alg = NULL;
|
||||
struct buffer *key = NULL;
|
||||
int retval = 0;
|
||||
|
||||
/* The two following calls to 'sample_conv_var2smp_str' will both make
|
||||
* use of the preallocated trash buffer (via get_trash_chunk call in
|
||||
* smp_dup) which would end up erasing the contents of the 'smp' input
|
||||
* buffer.
|
||||
*/
|
||||
input = alloc_trash_chunk();
|
||||
if (!input)
|
||||
return 0;
|
||||
alg = alloc_trash_chunk();
|
||||
if (!alg)
|
||||
goto end;
|
||||
key = alloc_trash_chunk();
|
||||
if (!key)
|
||||
goto end;
|
||||
|
||||
if (!chunk_cpy(input, &smp->data.u.str))
|
||||
goto end;
|
||||
|
||||
smp_set_owner(&alg_smp, smp->px, smp->sess, smp->strm, smp->opt);
|
||||
if (!sample_conv_var2smp_str(&args[0], &alg_smp))
|
||||
goto end;
|
||||
if (chunk_printf(alg, "%.*s", (int)b_data(&alg_smp.data.u.str), b_orig(&alg_smp.data.u.str)) <= 0)
|
||||
goto end;
|
||||
|
||||
smp_set_owner(&key_smp, smp->px, smp->sess, smp->strm, smp->opt);
|
||||
if (!sample_conv_var2smp_str(&args[1], &key_smp))
|
||||
goto end;
|
||||
if (chunk_printf(key, "%.*s", (int)b_data(&key_smp.data.u.str), b_orig(&key_smp.data.u.str)) <= 0)
|
||||
goto end;
|
||||
|
||||
ret = jwt_verify(input, alg, key, 1);
|
||||
|
||||
smp->data.type = SMP_T_SINT;
|
||||
smp->data.u.sint = ret;
|
||||
@ -5532,6 +5632,7 @@ static struct sample_conv_kw_list sample_conv_kws = {ILH, {
|
||||
{ "jwt_header_query", sample_conv_jwt_header_query, ARG2(0,STR,STR), sample_conv_jwt_query_check, SMP_T_BIN, SMP_T_ANY },
|
||||
{ "jwt_payload_query", sample_conv_jwt_payload_query, ARG2(0,STR,STR), sample_conv_jwt_query_check, SMP_T_BIN, SMP_T_ANY },
|
||||
{ "jwt_verify", sample_conv_jwt_verify, ARG2(2,STR,STR), sample_conv_jwt_verify_check, SMP_T_BIN, SMP_T_SINT },
|
||||
{ "jwt_verify_cert", sample_conv_jwt_verify_cert, ARG2(2,STR,STR), sample_conv_jwt_verify_cert_check, SMP_T_BIN, SMP_T_SINT },
|
||||
#endif
|
||||
{ "when", sample_conv_when, ARG3(1,STR,STR,STR), check_when_cond, SMP_T_ANY, SMP_T_ANY },
|
||||
{ NULL, NULL, 0, 0, 0 },
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user