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 {
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 */
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}
# 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}
BIO *bio = NULL;
struct stat buf;
struct ebmb_node *eb = NULL;
+ struct ckch_store *store = NULL;
eb = ebst_lookup(&jwt_cert_tree, path);
}
}
+ /* 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
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();
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)) {
+
+ 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);
- if (eb) {
- entry = ebmb_entry(eb, struct jwt_cert_tree_entry, node);
+ if (eb) {
+ entry = ebmb_entry(eb, struct jwt_cert_tree_entry, node);
- pubkey = entry->pubkey;
- if (pubkey)
- EVP_PKEY_up_ref(pubkey);
+ pubkey = entry->pubkey;
+ if (pubkey)
+ EVP_PKEY_up_ref(pubkey);
+ }
}
if (!pubkey) {
* 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;
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. */
/* 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);
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;
{ "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 },