MINOR: jwt: Add support for RSA-PSS signatures (PS256 algorithm)

This patch adds the support for the PS algorithms when verifying JWT
signatures (rsa-pss). It was not managed during the first implementation
and previously raised an "Unmanaged algorithm" error.
The tests use the same rsa signature as the plain rsa tests (RS256 ...)
and the implementation simply adds a call to
EVP_PKEY_CTX_set_rsa_padding in the function that manages rsa and ecdsa
signatures.
The signatures in the reg-test were built thanks to the PyJWT python
library once again.
This commit is contained in:
Remi Tricot-Le Breton 2023-03-07 17:43:57 +01:00 committed by William Lallemand
parent bce0c0c37a
commit 447a38f387
4 changed files with 95 additions and 50 deletions

View File

@ -17867,9 +17867,8 @@ jwt_verify(<alg>,<key>)
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.
processed (three dot-separated base64-url encoded strings). All the
algorithms mentioned in section 3.1 of RFC7518 are managed.
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
@ -17894,7 +17893,7 @@ jwt_verify(<alg>,<key>)
| 0 | "Verification failure" |
| 1 | "Verification success" |
| -1 | "Unknown algorithm (not mentioned in RFC7518)" |
| -2 | "Unmanaged algorithm (PSXXX algorithm family)" |
| -2 | "Unmanaged algorithm" |
| -3 | "Invalid token" |
| -4 | "Out of memory" |
| -5 | "Unknown certificate" |

View File

@ -16,7 +16,7 @@ feature cmd "$HAPROXY_PROGRAM -cc 'feature(OPENSSL)'"
feature cmd "command -v socat"
feature ignore_unknown_macro
server s1 -repeat 22 {
server s1 -repeat 24 {
rxreq
txresp
} -start
@ -39,6 +39,7 @@ haproxy h1 -conf {
use_backend hsXXX_be if { path_beg /hs }
use_backend rsXXX_be if { path_beg /rs }
use_backend esXXX_be if { path_beg /es }
use_backend psXXX_be if { path_beg /ps }
use_backend auth_bearer_be if { path /auth_bearer }
default_backend dflt_be
@ -85,6 +86,20 @@ haproxy h1 -conf {
http-response set-header x-jwt-verify-ES512 %[var(txn.bearer),jwt_verify(txn.jwt_alg,"${testdir}/es512-public.pem")] if { var(txn.jwt_alg) -m str "ES512" }
server s1 ${s1_addr}:${s1_port}
backend psXXX_be
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) -m beg "PS" }
http-response set-header x-jwt-token %[var(txn.bearer)]
http-response set-header x-jwt-alg %[var(txn.jwt_alg)]
http-response set-header x-jwt-verify-PS256 %[var(txn.bearer),jwt_verify(txn.jwt_alg,"${testdir}/rsa-public.pem")] if { var(txn.jwt_alg) -m str "PS256" }
http-response set-header x-jwt-verify-PS384 %[var(txn.bearer),jwt_verify(txn.jwt_alg,"${testdir}/rsa-public.pem")] if { var(txn.jwt_alg) -m str "PS384" }
http-response set-header x-jwt-verify-PS512 %[var(txn.bearer),jwt_verify(txn.jwt_alg,"${testdir}/rsa-public.pem")] if { var(txn.jwt_alg) -m str "PS512" }
server s1 ${s1_addr}:${s1_port}
# This backend will only be used to test the http_auth_bearer sample fetch.
# No jwt_verify will then be performed.
@ -255,8 +270,45 @@ client c11 -connect ${h1_mainfe_sock} {
expect resp.http.x-jwt-verify-ES512 == "1"
} -run
# The following token is invalid (too short)
client c12 -connect ${h1_mainfe_sock} {
# Token content : {"alg":"PS256","typ":"JWT"}
# {"sub":"1234567890","name":"John Doe","iat":1516239022}
# Token creation : ./build_token.py PS256 '{"sub":"1234567890","name":"John Doe","iat":1516239022}' rsa-private.pem
txreq -url "/ps256" -hdr "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJQUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.eXzN8m41ejgmbtJPhgifU_jMuYwVXL3HsLMOJ_ERipNcuqVQUmfHib1MWydSOYkgNBIm2lK9LjUmRCs1LvMUsbcqnokebFUNmO6IFdbMj3kD4cvqXHyK0yogQ7fdtJZf3_ukcJQ_-IdCG6mwowq6-OLjv-v2EflwPsT33EGmEDuE-7Z8AVTOVPiKUrqq1KqBi7NnnzdghqKfXn4b0yT7CnxQ_GK4F-ghBxBiMXK2J8M6pvS1vof7PyzVQmpeNzn2Rpbk-Ez88WeoTQXqZL1_BeW0z8FeyWXoIiqAzluRHSfZf2iUwrHuiH-tZ5BkAsJXHMDhMoL8_TKdD2hAnCWdVA9W9bQpzfaCbF5xv8lkGcy01ekrh-rN6ZOjItYeDj3BuaQgrKa5YAs_Grei_iSLqAu_YmDiVJxBfv5ahe1I8rwBQ7lIsZqv6p8BKqBFNylLzIFioAtmHJBF0HtItLoj0Mp_bUuU6RLIwf7C8ZWPQVTVsTgHMAlnZLNnQ3vhcxCjLm-r45M3AUFQfMEy1ajiqpFb3z2ElEwiOS9uLYJs3AOAoJDc-e62VJ7tRlw7KB-Vw0mvztvXgYdit48KOxdbn15HQ0lbBM_jJHvbYjDFC0iGUaizBPqmOJcTvObvKv5itEhPT6ffsv9XBnRSv9f3kW_rI7chrCyRZc0nFUvEJ9o"
rxresp
expect resp.status == 200
expect resp.http.x-jwt-alg == "PS256"
expect resp.http.x-jwt-verify-PS256 == "1"
} -run
client c13 -connect ${h1_mainfe_sock} {
# Token content : {"alg":"PS384","typ":"JWT"}
# {"sub":"1234567890","name":"John Doe","iat":1516239022}
# Token creation : ./build_token.py PS384 '{"sub":"1234567890","name":"John Doe","iat":1516239022}' rsa-private.pem
txreq -url "/ps384" -hdr "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJQUzM4NCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.f-il5pRvC_vYuJ5jI-q9zxgqStCzvICKJyJEmVSjK47uLtt24SNLsQ1V24gqGuDOkXAhxlVu9rUwvtbzOQbF6N1YFFKbCuJ7zbGG81j5r3IuFl_5y6v077PW3hSNn62WX1GDv8w_kGedAZqGwKhJR7D1CbPBE5v-b4PskVF1V7IrFx8PufS_LUeJq1Etei0iU7H9OWD0yVApE_nmeELy4Kz1cc1fQZTBzd-b6kB562JbUbENM14HoiuKpnZvDtQks93A7y_B14SZPrxDaiVI-fR1n8Ja10wyBqbw8mWzt4s7vkxQI8U0eTBcj6bpWcm6S947G_jjoum_Lu3ZSKXE4UxcZ2IIuM74PEUgWJUjr4f9klB8kplJS5AIXMUNG6QbgZhOdSfZmlfzZUmSt1CLI22rTadXjvn-5CG_VxWJUjcPF9hViFFKQ7qQw3Tcn73ZKf5toK3imQBay4vR11DYWP5flLscFtqPvFcV4qhNarG-kVTI2xO8wXDXEoKeIXvsr3GTmCmBbs-kxvtyI80GUTzKN2I9vp0W9Qo5GNa3DDU1-io3olVwtMFh_0qfhmdO1Rt-j11gGnYTz3S5zMMMG2Ihy8ho3ayNZlZf7MJvVBSPqbCpHdiRa8VgTyYdYvK81lgkSc3wE8CygFEBMEi9b181OKPODlpux6k-3AL_2Hs"
rxresp
expect resp.status == 200
expect resp.http.x-jwt-alg == "PS384"
expect resp.http.x-jwt-verify-PS384 == "1"
} -run
client c14 -connect ${h1_mainfe_sock} {
# Token content : {"alg":"PS512","typ":"JWT"}
# {"sub":"1234567890","name":"John Doe","iat":1516239022}
# Token creation : ./build_token.py PS512 '{"sub":"1234567890","name":"John Doe","iat":1516239022}' rsa-private.pem
txreq -url "/ps512" -hdr "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJQUzUxMiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.tJZQn0ksGx7vFpBzhNkP8vupyRiAAy5Rf6UdR2MEnO6-iwisbXOUrwwh8XQWngEe2O5FJabCxJRI_shSVEUuWY2Vz6kvRAQ6sWv_4uoPTUk9zjSXkS6C_nb_UY_6tUz39qA-OI80JKcLadvjB66CGWHI00C5Xz2gyWQuFgSItBIV6l0wI6Spf4NJa2Lefo7XbobQ7-u-yzgbIJ1BgXFOTWHYsgJ67n39gj7MDDsUjSaNbFlKfbvGJrdli5_PNNSdoNiF0pdsd6vldnucs5Rfysp4V-nbBzrORuJhl0_BlPG7_Wbap0sm6NCnzp1ks3D5_OWLZxJZNw_TJ2OuVHOX2PNj2MuHjMPDMKKxgxIXQJ8ry39-sk56ZrCJ8UqZofk8NX7Z4ypeWrK62BNSTLY8Le4WzF6dYcuawxiyt7xsC0MkaplXpRFLdmHrMhvyZz6S8BFhtlGD-PnRnEr8qZkThiZSs5kcEW8ryneKlN5TQ7E0H1HekUUii3_T9MtC5rNsT1vzyGr0XAn5TLxeal4Gvp3WyOHs4l7Q1EyQXPkAX8bWwODtLZ3DrREwdLb7Ex2k9wRDF52aww9EMpeLM3at6MQKggWQhNEClahN9AWBj7Vz-RqliWEIdUdNTL3d1JgLX41GZqXjOGZIwiVJwYpVRh1jKVhUn8pN8jCtoeiUxh8"
rxresp
expect resp.status == 200
expect resp.http.x-jwt-alg == "PS512"
expect resp.http.x-jwt-verify-PS512 == "1"
} -run
# The following token is invalid (too short)
client c15 -connect ${h1_mainfe_sock} {
# Token content : {"alg":"ES512","typ":"JWT"}
# {"sub":"1234567890","name":"John Doe","iat":1516239022}
# OpenSSL cmd : openssl dgst -sha512 -sign es512-private.pem data.txt | base64 | tr -d '=\n' | tr '/+' '_-'
@ -269,21 +321,8 @@ client c12 -connect ${h1_mainfe_sock} {
expect resp.http.x-jwt-verify-ES512 == "-3"
} -run
# Unmanaged algorithm
client c13 -connect ${h1_mainfe_sock} {
# Token content : {"alg":"PS512","typ":"JWT"}
# {"sub":"1234567890","name":"John Doe","iat":1516239022}
txreq -url "/errors" -hdr "Authorization: Bearer eyJhbGciOiJQUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.MIGHAkEEPEgIrFKIDofBpFKX_mtya55QboGr09P6--v8uO85DwQWR0iKgMNSzYkL3K1lwyExG0Vtwfnife0lNe7Fn5TigAJCAY95NShiTn3tvleXVGCkkD0-HcribnMhd34QPGRc4rlwTkUg9umIUhxnEhPR--OohlmhJyIYGHuH8Ksm5f"
rxresp
expect resp.status == 200
expect resp.http.x-jwt-alg == "PS512"
# Unmanaged algorithm
expect resp.http.x-jwt-verify == "-2"
} -run
# Unknown algorithm
client c14 -connect ${h1_mainfe_sock} {
client c16 -connect ${h1_mainfe_sock} {
# Token content : {"alg":"UNKNOWN_ALG","typ":"JWT"}
# {"sub":"1234567890","name":"John Doe","iat":1516239022}
txreq -url "/errors" -hdr "Authorization: Bearer eyJhbGciOiJVTktOT1dOX0FMRyIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.MIGHAkEEPEgIrFKIDofBpFKX_mtya55QboGr09P6--v8uO85DwQWR0iKgMNSzYkL3K1lwyExG0Vtwfnife0lNe7Fn5TigAJCAY95NShiTn3tvleXVGCkkD0-HcribnMhd34QPGRc4rlwTkUg9umIUhxnEhPR--OohlmhJyIYGHuH8Ksm5f"
@ -295,7 +334,7 @@ client c14 -connect ${h1_mainfe_sock} {
} -run
# Invalid token (not enough fields)
client c15 -connect ${h1_mainfe_sock} {
client c17 -connect ${h1_mainfe_sock} {
# Token content : {"alg":"ES512","typ":"JWT"}
# {"sub":"1234567890","name":"John Doe","iat":1516239022}
txreq -url "/errors" -hdr "Authorization: Bearer eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ"
@ -307,7 +346,7 @@ client c15 -connect ${h1_mainfe_sock} {
} -run
# Invalid token (too many fields)
client c16 -connect ${h1_mainfe_sock} {
client c18 -connect ${h1_mainfe_sock} {
# Token content : {"alg":"ES512","typ":"JWT"}
# {"sub":"1234567890","name":"John Doe","iat":1516239022}
txreq -url "/errors" -hdr "Authorization: Bearer eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.MIGHAkEEPEgIrFKIDofBpFKX_mtya55QboGr09P6--v8uO85DwQWR0iKgMNSzYkL3K1lwyExG0Vtwfnife0lNe7Fn5TigAJCAY95NShiTn3tvleXVGCkkD0-HcribnMhd34QPGRc4rlwTkUg9umIUhxnEhPR--OohlmhJyIYGHuH8Ksm5f.unexpectedextrafield"
@ -319,7 +358,7 @@ client c16 -connect ${h1_mainfe_sock} {
} -run
# Invalid token (empty signature)
client c17 -connect ${h1_mainfe_sock} {
client c19 -connect ${h1_mainfe_sock} {
# Token content : {"alg":"ES512","typ":"JWT"}
# {"sub":"1234567890","name":"John Doe","iat":1516239022}
txreq -url "/errors" -hdr "Authorization: Bearer eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ."
@ -331,7 +370,7 @@ client c17 -connect ${h1_mainfe_sock} {
} -run
# Unknown certificate
client c18 -connect ${h1_mainfe_sock} {
client c20 -connect ${h1_mainfe_sock} {
# Token content : {"alg":"ES512","typ":"JWT"}
# {"sub":"1234567890","name":"John Doe","iat":1516239022}
# Key gen process : openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-521 -out es512-private.pem; openssl ec -in es512-private.pem -pubout -out es512-public.pem
@ -347,7 +386,7 @@ client c18 -connect ${h1_mainfe_sock} {
# Test the http_auth_bearer special cases (other header than the default "Authorization" one)
client c19 -connect ${h1_mainfe_sock} {
client c21 -connect ${h1_mainfe_sock} {
txreq -url "/auth_bearer" -hdr "Custom-Authorization: Bearer random_value"
rxresp
expect resp.status == 200
@ -355,7 +394,7 @@ client c19 -connect ${h1_mainfe_sock} {
} -run
# Test the http_auth_bearer special cases (multiple spaces after the scheme)
client c20 -connect ${h1_mainfe_sock} {
client c22 -connect ${h1_mainfe_sock} {
txreq -url "/auth_bearer" -hdr "Custom-Authorization: Bearer random_value"
rxresp
expect resp.status == 200
@ -363,7 +402,7 @@ client c20 -connect ${h1_mainfe_sock} {
} -run
# Test the http_auth_bearer special cases (no value after the scheme)
client c21 -connect ${h1_mainfe_sock} {
client c23 -connect ${h1_mainfe_sock} {
txreq -url "/auth_bearer" -hdr "Custom-Authorization: Bearer "
rxresp
expect resp.status == 200
@ -371,7 +410,7 @@ client c21 -connect ${h1_mainfe_sock} {
} -run
# Test the http_auth_bearer special cases (no value after the scheme)
client c22 -connect ${h1_mainfe_sock} {
client c24 -connect ${h1_mainfe_sock} {
txreq -url "/errors" -hdr "Authorization: Bearer "
rxresp
expect resp.status == 200

View File

@ -275,10 +275,12 @@ jwt_jwsverify_rsa_ecdsa(const struct jwt_ctx *ctx, struct buffer *decoded_signat
{
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;
struct ebmb_node *eb;
struct jwt_cert_tree_entry *entry = NULL;
int is_ecdsa = 0;
int padding = RSA_PKCS1_PADDING;
switch(ctx->alg) {
case JWS_ALG_RS256:
@ -303,6 +305,19 @@ jwt_jwsverify_rsa_ecdsa(const struct jwt_ctx *ctx, struct buffer *decoded_signat
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;
}
@ -337,12 +352,15 @@ jwt_jwsverify_rsa_ecdsa(const struct jwt_ctx *ctx, struct buffer *decoded_signat
}
}
if (EVP_DigestVerifyInit(evp_md_ctx, NULL, evp, NULL, entry->pkey) == 1 &&
EVP_DigestVerifyUpdate(evp_md_ctx, (const unsigned char*)ctx->jose.start,
if (EVP_DigestVerifyInit(evp_md_ctx, &pkey_ctx, evp, NULL, entry->pkey) == 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);
@ -420,16 +438,15 @@ enum jwt_vrfy_status jwt_verify(const struct buffer *token, const struct buffer
case JWS_ALG_ES256:
case JWS_ALG_ES384:
case JWS_ALG_ES512:
/* RSASSA-PKCS1-v1_5 + SHA-XXX */
/* ECDSA using P-XXX and SHA-XXX */
retval = jwt_jwsverify_rsa_ecdsa(&ctx, decoded_sig);
break;
case JWS_ALG_PS256:
case JWS_ALG_PS384:
case JWS_ALG_PS512:
default:
/* 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;

View File

@ -3867,19 +3867,9 @@ static int sample_conv_jwt_verify_check(struct arg *args, struct sample_conv *co
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:
if (alg == JWT_ALG_DEFAULT) {
memprintf(err, "unknown JWT algorithm: %s", args[0].data.str.area);
return 0;
case JWS_ALG_PS256:
case JWS_ALG_PS384:
case JWS_ALG_PS512:
memprintf(err, "RSASSA-PSS JWS signing not managed yet");
return 0;
default:
break;
}
}