Please note that this converter is only available when HAProxy has been
compiled with USE_OPENSSL.
+jwt_verify(<alg>,<key>)
+ Performs a signature verification for the JSON Web Token (JWT) given in input
+ by using the <alg> algorithm and the <key> parameter, which should either
+ hold a secret or a path to a public certificate. Returns 1 in cae of
+ verification success. See below for a full list of the possible return
+ values.
+ For now, only JWS tokens using the Compact Serialization format can be
+ processed (three dot-separated base64-url encoded strings). Among the
+ accepted algorithms for a JWS (see section 3.1 of RFC7518), the PSXXX ones
+ are not managed yet.
+ If the used algorithm is of the HMAC family, <key> should be the secret used
+ in the HMAC signature calculation. Otherwise, <key> should be the path to the
+ public certificate that can be used to validate the token's signature. All
+ the certificates that might be used to verify JWTs must be known during init
+ in order to be added into a dedicated certificate cache so that no disk
+ access is required during runtime. For this reason, any used certificate must
+ be mentioned explicitely at least once in a jwt_verify call. Passing an
+ intermediate variable as second parameter is then not advised.
+
+ This converter only verifies the signature of the token and does not perform
+ a full JWT validation as specified in section 7.2 of RFC7519. We do not
+ ensure that the header and payload contents are fully valid JSON's once
+ decoded for instance, and no checks are performed regarding their respective
+ contents.
+
+ The possible return values are the following :
+
+ +----+---------------------------------------------------------------------------+
+ | ID | message |
+ +----+---------------------------------------------------------------------------+
+ | 0 | "Verification failure" |
+ | 1 | "Verification sucess" |
+ | 2 | "Unknown algorithm (not mentioned in RFC7518)" |
+ | 3 | "Unmanaged algorithm (PSXXX algorithm family)" |
+ | 4 | "Invalid token" |
+ | 5 | "Out of memory" |
+ | 6 | "Unknown certificate" |
+ +----+---------------------------------------------------------------------------+
+
+ Please note that this converter is only available when HAProxy has been
+ compiled with USE_OPENSSL.
+
+ Example:
+ # Get a JWT from the authorization header, extract the "alg" field of its
+ # JOSE header and use a public certificate to verify a signature
+ http-request set-var(txn.bearer) http_auth_bearer
+ http-request set-var(txn.jwt_alg) var(txn.bearer),jwt_header_query('$.alg')
+ http-request deny unless { var(txn.jwt_alg) "RS256" }
+ http-request deny unless { var(txn.bearer),jwt_verify(txn.jwt_alg,"/path/to/crt.pem") 1 }
+
language(<value>[,<default>])
Returns the value with the highest q-factor from a list as extracted from the
"accept-language" header using "req.fhdr". Values with no q-factor have a
struct ebmb_node node;
char path[VAR_ARRAY];
};
+
+enum jwt_vrfy_status {
+ JWT_VRFY_KO = 0,
+ JWT_VRFY_OK = 1,
+ JWT_VRFY_UNKNOWN_ALG,
+ JWT_VRFY_UNMANAGED_ALG,
+ JWT_VRFY_INVALID_TOKEN,
+ JWT_VRFY_OUT_OF_MEMORY,
+ JWT_VRFY_UNKNOWN_CERT
+};
+
#endif /* USE_OPENSSL */
enum jwt_alg jwt_parse_alg(const char *alg_str, unsigned int alg_len);
int jwt_tokenize(const struct buffer *jwt, struct jwt_item *items, unsigned int *item_num);
int jwt_tree_load_cert(char *path, int pathlen, char **err);
+
+enum jwt_vrfy_status jwt_verify(const struct buffer *token, const struct buffer *alg,
+ const struct buffer *key);
#endif /* USE_OPENSSL */
#endif /* _HAPROXY_JWT_H */
BIO_free(bio);
return retval;
}
+
+/*
+ * 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 = NULL;
+ unsigned int signature_length = 0;
+ struct buffer *trash = NULL;
+ unsigned char *hmac_res = NULL;
+ enum jwt_vrfy_status retval = JWT_VRFY_KO;
+
+ trash = alloc_trash_chunk();
+ if (!trash)
+ return JWT_VRFY_OUT_OF_MEMORY;
+
+ signature = (unsigned char*)trash->area;
+ signature_length = trash->size;
+
+ 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 &&
+ (memcmp(decoded_signature->area, signature, signature_length) == 0))
+ retval = JWT_VRFY_OK;
+
+ free_trash_chunk(trash);
+
+ 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, const struct buffer *decoded_signature)
+{
+ const EVP_MD *evp = NULL;
+ EVP_MD_CTX *evp_md_ctx;
+ enum jwt_vrfy_status retval = JWT_VRFY_KO;
+ struct buffer *trash = NULL;
+ struct ebmb_node *eb;
+ struct jwt_cert_tree_entry *entry = NULL;
+
+ trash = alloc_trash_chunk();
+ if (!trash)
+ return JWT_VRFY_OUT_OF_MEMORY;
+
+ switch(ctx->alg) {
+ case JWS_ALG_RS256:
+ case JWS_ALG_ES256:
+ evp = EVP_sha256();
+ break;
+ case JWS_ALG_RS384:
+ case JWS_ALG_ES384:
+ evp = EVP_sha384();
+ break;
+ case JWS_ALG_RS512:
+ case JWS_ALG_ES512:
+ evp = EVP_sha512();
+ break;
+ default: break;
+ }
+
+ evp_md_ctx = EVP_MD_CTX_new();
+ if (!evp_md_ctx) {
+ free_trash_chunk(trash);
+ return JWT_VRFY_OUT_OF_MEMORY;
+ }
+
+ eb = ebst_lookup(&jwt_cert_tree, ctx->key);
+
+ if (!eb) {
+ retval = JWT_VRFY_UNKNOWN_CERT;
+ goto end;
+ }
+
+ entry = ebmb_entry(eb, struct jwt_cert_tree_entry, node);
+
+ if (!entry->pkey) {
+ retval = JWT_VRFY_UNKNOWN_CERT;
+ goto end;
+ }
+
+ if (EVP_DigestVerifyInit(evp_md_ctx, NULL, evp, NULL,entry-> pkey) == 1 &&
+ 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);
+ free_trash_chunk(trash);
+ 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;
+
+ 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;
+
+ decoded_sig->data = base64urldec(ctx.signature.start, ctx.signature.length,
+ decoded_sig->area, decoded_sig->size);
+ if (decoded_sig->data == (unsigned int)-1) {
+ retval = JWT_VRFY_INVALID_TOKEN;
+ goto end;
+ }
+
+ ctx.key = key->area;
+ ctx.key_length = key->data;
+
+ /* We have all three sections, signature calculation can begin. */
+
+ if (ctx.alg <= JWS_ALG_HS512) {
+ /* HMAC + SHA-XXX */
+ retval = jwt_jwsverify_hmac(&ctx, decoded_sig);
+ } else if (ctx.alg <= JWS_ALG_ES512) {
+ /* RSASSA-PKCS1-v1_5 + SHA-XXX */
+ /* ECDSA using P-XXX and SHA-XXX */
+ retval = jwt_jwsverify_rsa_ecdsa(&ctx, decoded_sig);
+ } else if (ctx.alg <= JWS_ALG_PS512) {
+ /* RSASSA-PSS using SHA-XXX and MGF1 with SHA-XXX */
+
+ /* Not managed yet */
+ retval = JWT_VRFY_UNMANAGED_ALG;
+ }
+
+end:
+ free_trash_chunk(decoded_sig);
+
+ return retval;
+}
+
#endif /* USE_OPENSSL */
}
#ifdef USE_OPENSSL
+static int sample_conv_jwt_verify_check(struct arg *args, struct sample_conv *conv,
+ const char *file, int line, char **err)
+{
+ vars_check_arg(&args[0], NULL);
+ vars_check_arg(&args[1], NULL);
+
+ if (args[0].type == ARGT_STR) {
+ enum jwt_alg alg = jwt_parse_alg(args[0].data.str.area, args[0].data.str.data);
+
+ switch(alg) {
+ case JWT_ALG_DEFAULT:
+ memprintf(err, "unknown JWT algorithm : %s", *err);
+ break;
+
+ case JWS_ALG_PS256:
+ case JWS_ALG_PS384:
+ case JWS_ALG_PS512:
+ memprintf(err, "RSASSA-PSS JWS signing not managed yet");
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if (args[1].type == ARGT_STR) {
+ jwt_tree_load_cert(args[1].data.str.area, args[1].data.str.data, err);
+ }
+
+ return 1;
+}
+
+/* Check that a JWT's signature is correct */
+static int sample_conv_jwt_verify(const struct arg *args, struct sample *smp, void *private)
+{
+ struct sample alg_smp, key_smp;
+
+ smp->data.type = SMP_T_SINT;
+ smp->data.u.sint = 0;
+
+ smp_set_owner(&alg_smp, smp->px, smp->sess, smp->strm, smp->opt);
+ smp_set_owner(&key_smp, smp->px, smp->sess, smp->strm, smp->opt);
+ if (!sample_conv_var2smp_str(&args[0], &alg_smp))
+ return 0;
+ if (!sample_conv_var2smp_str(&args[1], &key_smp))
+ return 0;
+
+ smp->data.u.sint = jwt_verify(&smp->data.u.str, &alg_smp.data.u.str,
+ &key_smp.data.u.str);
+
+ return 1;
+}
+
+
/*
* Returns the decoded header or payload of a JWT if no parameter is given, or
* the value of the specified field of the corresponding JWT subpart if a
/* JSON Web Token converters */
{ "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 },
#endif
{ NULL, NULL, 0, 0, 0 },
}};