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.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index d437946..2d57689 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -17867,9 +17867,8 @@
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 @@
| 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" |
diff --git a/reg-tests/jwt/jws_verify.vtc b/reg-tests/jwt/jws_verify.vtc
index d9a6328..43d37c7 100644
--- a/reg-tests/jwt/jws_verify.vtc
+++ b/reg-tests/jwt/jws_verify.vtc
@@ -16,7 +16,7 @@
feature cmd "command -v socat"
feature ignore_unknown_macro
-server s1 -repeat 22 {
+server s1 -repeat 24 {
rxreq
txresp
} -start
@@ -39,6 +39,7 @@
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 @@
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,35 +270,59 @@
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":"ES512","typ":"JWT"}
+ # Token content : {"alg":"PS256","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 '/+' '_-'
-
- txreq -url "/es512" -hdr "Authorization: Bearer eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.MIGHAkEEPEgIrFKIDofBpFKX_mtya55QboGr09P6--v8uO85DwQWR0iKgMNSzYkL3K1lwyExG0Vtwfnife0lNe7Fn5TigAJCAY95NShiTn3tvleXVGCkkD0-HcribnMhd34QPGRc4rlwTkUg9umIUhxnEhPR--OohlmhJyIYGHuH8Ksm5f"
+ # 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 == "ES512"
- # Invalid token
- expect resp.http.x-jwt-verify-ES512 == "-3"
+ expect resp.http.x-jwt-alg == "PS256"
+ expect resp.http.x-jwt-verify-PS256 == "1"
} -run
-
-# Unmanaged algorithm
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}
- txreq -url "/errors" -hdr "Authorization: Bearer eyJhbGciOiJQUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.MIGHAkEEPEgIrFKIDofBpFKX_mtya55QboGr09P6--v8uO85DwQWR0iKgMNSzYkL3K1lwyExG0Vtwfnife0lNe7Fn5TigAJCAY95NShiTn3tvleXVGCkkD0-HcribnMhd34QPGRc4rlwTkUg9umIUhxnEhPR--OohlmhJyIYGHuH8Ksm5f"
+ # 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"
- # Unmanaged algorithm
- expect resp.http.x-jwt-verify == "-2"
+ 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 '/+' '_-'
+
+ txreq -url "/es512" -hdr "Authorization: Bearer eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.MIGHAkEEPEgIrFKIDofBpFKX_mtya55QboGr09P6--v8uO85DwQWR0iKgMNSzYkL3K1lwyExG0Vtwfnife0lNe7Fn5TigAJCAY95NShiTn3tvleXVGCkkD0-HcribnMhd34QPGRc4rlwTkUg9umIUhxnEhPR--OohlmhJyIYGHuH8Ksm5f"
+ rxresp
+ expect resp.status == 200
+ expect resp.http.x-jwt-alg == "ES512"
+ # Invalid token
+ expect resp.http.x-jwt-verify-ES512 == "-3"
+} -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 @@
} -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 @@
} -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 @@
} -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 @@
} -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 @@
# 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 @@
} -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 @@
} -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 @@
} -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
diff --git a/src/jwt.c b/src/jwt.c
index b901588..6c4cbd3 100644
--- a/src/jwt.c
+++ b/src/jwt.c
@@ -275,10 +275,12 @@
{
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 @@
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,11 +352,14 @@
}
}
- 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;
+ 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:
@@ -420,16 +438,15 @@
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;
- case JWS_ALG_PS256:
- case JWS_ALG_PS384:
- case JWS_ALG_PS512:
default:
- /* RSASSA-PSS using SHA-XXX and MGF1 with SHA-XXX */
-
/* Not managed yet */
retval = JWT_VRFY_UNMANAGED_ALG;
break;
diff --git a/src/sample.c b/src/sample.c
index 2e04c74..ce01b0f 100644
--- a/src/sample.c
+++ b/src/sample.c
@@ -3867,19 +3867,9 @@
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;
}
}