BUG/MEDIUM: jwt: Properly process ecdsa signatures (concatenated R and S params)
When the JWT token signature is using ECDSA algorithm (ES256 for instance), the signature is a direct concatenation of the R and S parameters instead of OpenSSL's DER format (see section 3.4 of RFC7518). The code that verified the signatures wrongly assumed that they came in OpenSSL's format and it did not actually work. We now have the extra step of converting the signature into a complete ECDSA_SIG that can be fed into OpenSSL's digest verification functions. The ECDSA signatures in the regtest had to be recalculated and it was made via the PyJWT python library so that we don't end up checking signatures that we built ourselves anymore. This patch should fix GitHub issue #2001. It should be backported up to branch 2.5.
This commit is contained in:
parent
86aac23e6b
commit
5a8f02ae66
22
reg-tests/jwt/build_token.py
Executable file
22
reg-tests/jwt/build_token.py
Executable file
@ -0,0 +1,22 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
# JWT package can be installed via 'pip install pyjwt' command
|
||||
|
||||
import sys
|
||||
import jwt
|
||||
import json
|
||||
|
||||
if len(sys.argv) != 4:
|
||||
print(sys.argv[0],"<alg> <json_to_sign> <priv_key>")
|
||||
quit()
|
||||
|
||||
|
||||
alg=sys.argv[1]
|
||||
json_to_sign=sys.argv[2]
|
||||
priv_key_file=sys.argv[3]
|
||||
|
||||
with open(priv_key_file) as file:
|
||||
priv_key = file.read()
|
||||
|
||||
print(jwt.encode(json.loads(json_to_sign),priv_key,algorithm=alg))
|
||||
|
@ -220,9 +220,9 @@ client c9 -connect ${h1_mainfe_sock} {
|
||||
# Token content : {"alg":"ES256","typ":"JWT"}
|
||||
# {"sub":"1234567890","name":"John Doe","iat":1516239022}
|
||||
# Key gen process : openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out es256-private.pem; openssl ec -in es256-private.pem -pubout -out es256-public.pem
|
||||
# OpenSSL cmd : openssl dgst -sha256 -sign es256-private.pem data.txt | base64 | tr -d '=\n' | tr '/+' '_-'
|
||||
# Token creation : ./build_token.py ES256 '{"sub":"1234567890","name":"John Doe","iat":1516239022}' es256-private.pem
|
||||
|
||||
txreq -url "/es256" -hdr "Authorization: Bearer eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.MEYCIQCkHcfMhzhP3FvZqjaqEDW89_5QEhBwUvpXv535lAnRuQIhALc62LiFZz0oDuKeqI3ogto336D7kEg4Uat8qm_iW6ur"
|
||||
txreq -url "/es256" -hdr "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.pNI_c5mHE3mLV0YDpstlP4l3t5XARLl6OmcKLuvF5r60m-C63mbgfKWdPjmJPMTCmX_y50YW_v2SKw0ju0tJHw"
|
||||
rxresp
|
||||
expect resp.status == 200
|
||||
expect resp.http.x-jwt-alg == "ES256"
|
||||
@ -233,9 +233,9 @@ client c10 -connect ${h1_mainfe_sock} {
|
||||
# Token content : {"alg":"ES384","typ":"JWT"}
|
||||
# {"sub":"1234567890","name":"John Doe","iat":1516239022}
|
||||
# Key gen process : openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-384 -out es384-private.pem; openssl ec -in es384-private.pem -pubout -out es384-public.pem
|
||||
# OpenSSL cmd : openssl dgst -sha384 -sign es384-private.pem data.txt | base64 | tr -d '=\n' | tr '/+' '_-'
|
||||
# Token creation : ./build_token.py ES384 '{"sub":"1234567890","name":"John Doe","iat":1516239022}' es384-private.pem
|
||||
|
||||
txreq -url "/es384" -hdr "Authorization: Bearer eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.MGUCMQDQFs6fqnmoxbw3eIQCT6km0TnMakpGy2F-8ZgGu5G8nFQKzCAO-V-UTOD0OqxHUa8CMBqHfZ6pjqRaLK-PebsvbGSzneAG7Id3oN78n2wWGKcYCI_s0KSO88thboaR9AS4tA"
|
||||
txreq -url "/es384" -hdr "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.cs59CQiCI_Pl8J-PKQ2y73L5IJascZXkf7MfRXycO1HkT9pqDW2bFr1bh7pFyPA85GaML4BPYVH_zDhcmjSMn_EIvUV8cPDuuUu69Au7n9LYGVkVJ-k7qN4DAR5eLCiU"
|
||||
rxresp
|
||||
expect resp.status == 200
|
||||
expect resp.http.x-jwt-alg == "ES384"
|
||||
@ -246,9 +246,9 @@ client c11 -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
|
||||
# OpenSSL cmd : openssl dgst -sha512 -sign es512-private.pem data.txt | base64 | tr -d '=\n' | tr '/+' '_-'
|
||||
# Token creation : ./build_token.py ES512 '{"sub":"1234567890","name":"John Doe","iat":1516239022}' es512-private.pem
|
||||
|
||||
txreq -url "/es512" -hdr "Authorization: Bearer eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.MIGHAkEEPEgIrFKIDofBpFKX_mtya55QboGr09P6--v8uO85DwQWR0iKgMNSzYkL3K1lwyExG0Vtwfnife0lNe7Fn5TigAJCAY95NShiTn3tvleXVGCkkD0-HcribnMhd34QPGRc4rlwTkUg9umIUhxnEhPR--OohlmhJyIYGHuH8Ksm5fSIWfRa"
|
||||
txreq -url "/es512" -hdr "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzUxMiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.AJcyt0OYf2wg7SggJJVKYysLUkBQA0f0Zc0EbKgud2fQLeT65n42A9l9hhGje79VLWhEyisQmDpFXTpfFXeD_NiaAXyNnX5b8TbZALqxbjx8iIpbcObgUh_g5Gi81bKmRmfXUHW7L5iAwoNjYbUpXGipCpCD0N6-8zCrjcFD2UX01f0Y"
|
||||
rxresp
|
||||
expect resp.status == 200
|
||||
expect resp.http.x-jwt-alg == "ES512"
|
||||
@ -301,7 +301,7 @@ client c15 -connect ${h1_mainfe_sock} {
|
||||
rxresp
|
||||
expect resp.status == 200
|
||||
expect resp.http.x-jwt-alg == "ES512"
|
||||
# Unmanaged algorithm
|
||||
# Invalid token
|
||||
expect resp.http.x-jwt-verify == "-3"
|
||||
} -run
|
||||
|
||||
@ -313,7 +313,7 @@ client c16 -connect ${h1_mainfe_sock} {
|
||||
rxresp
|
||||
expect resp.status == 200
|
||||
expect resp.http.x-jwt-alg == "ES512"
|
||||
# Unmanaged algorithm
|
||||
# Invalid token
|
||||
expect resp.http.x-jwt-verify == "-3"
|
||||
} -run
|
||||
|
||||
@ -325,7 +325,7 @@ client c17 -connect ${h1_mainfe_sock} {
|
||||
rxresp
|
||||
expect resp.status == 200
|
||||
expect resp.http.x-jwt-alg == "ES512"
|
||||
# Unmanaged algorithm
|
||||
# Invalid token
|
||||
expect resp.http.x-jwt-verify == "-3"
|
||||
} -run
|
||||
|
||||
@ -340,7 +340,7 @@ client c18 -connect ${h1_mainfe_sock} {
|
||||
rxresp
|
||||
expect resp.status == 200
|
||||
expect resp.http.x-jwt-alg == "ES512"
|
||||
# Unmanaged algorithm
|
||||
# Unknown certificate
|
||||
expect resp.http.x-jwt-verify == "-5"
|
||||
} -run
|
||||
|
||||
|
86
src/jwt.c
86
src/jwt.c
@ -18,6 +18,7 @@
|
||||
#include <haproxy/openssl-compat.h>
|
||||
#include <haproxy/base64.h>
|
||||
#include <haproxy/jwt.h>
|
||||
#include <haproxy/buf.h>
|
||||
|
||||
|
||||
#ifdef USE_OPENSSL
|
||||
@ -213,32 +214,94 @@ jwt_jwsverify_hmac(const struct jwt_ctx *ctx, const struct buffer *decoded_signa
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert a JWT ECDSA signature (R and S parameters concatenatedi, see section
|
||||
* 3.4 of RFC7518) into an ECDSA_SIG that can be fed back into OpenSSL's digest
|
||||
* verification functions.
|
||||
* Returns 0 in case of success.
|
||||
*/
|
||||
static int convert_ecdsa_sig(const struct jwt_ctx *ctx, EVP_PKEY *pkey, struct buffer *signature)
|
||||
{
|
||||
int retval = 0;
|
||||
ECDSA_SIG *ecdsa_sig = NULL;
|
||||
BIGNUM *ec_R = NULL, *ec_S = NULL;
|
||||
unsigned int bignum_len;
|
||||
unsigned char *p;
|
||||
|
||||
ecdsa_sig = ECDSA_SIG_new();
|
||||
if (!ecdsa_sig) {
|
||||
retval = JWT_VRFY_OUT_OF_MEMORY;
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (b_data(signature) % 2) {
|
||||
retval = JWT_VRFY_INVALID_TOKEN;
|
||||
goto end;
|
||||
}
|
||||
|
||||
bignum_len = b_data(signature) / 2;
|
||||
|
||||
ec_R = BN_bin2bn((unsigned char*)b_orig(signature), bignum_len, NULL);
|
||||
ec_S = BN_bin2bn((unsigned char *)(b_orig(signature) + bignum_len), bignum_len, NULL);
|
||||
|
||||
if (!ec_R || !ec_S) {
|
||||
retval = JWT_VRFY_INVALID_TOKEN;
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Build ecdsa out of R and S values. */
|
||||
ECDSA_SIG_set0(ecdsa_sig, ec_R, ec_S);
|
||||
|
||||
p = (unsigned char*)signature->area;
|
||||
|
||||
signature->data = i2d_ECDSA_SIG(ecdsa_sig, &p);
|
||||
if (signature->data == 0) {
|
||||
retval = JWT_VRFY_INVALID_TOKEN;
|
||||
goto end;
|
||||
}
|
||||
|
||||
end:
|
||||
ECDSA_SIG_free(ecdsa_sig);
|
||||
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)
|
||||
jwt_jwsverify_rsa_ecdsa(const struct jwt_ctx *ctx, struct buffer *decoded_signature)
|
||||
{
|
||||
const EVP_MD *evp = NULL;
|
||||
EVP_MD_CTX *evp_md_ctx;
|
||||
enum jwt_vrfy_status retval = JWT_VRFY_KO;
|
||||
struct ebmb_node *eb;
|
||||
struct jwt_cert_tree_entry *entry = NULL;
|
||||
int is_ecdsa = 0;
|
||||
|
||||
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:
|
||||
evp = EVP_sha512();
|
||||
break;
|
||||
|
||||
case JWS_ALG_ES256:
|
||||
evp = EVP_sha256();
|
||||
is_ecdsa = 1;
|
||||
break;
|
||||
case JWS_ALG_ES384:
|
||||
evp = EVP_sha384();
|
||||
is_ecdsa = 1;
|
||||
break;
|
||||
case JWS_ALG_ES512:
|
||||
evp = EVP_sha512();
|
||||
is_ecdsa = 1;
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
@ -261,9 +324,22 @@ jwt_jwsverify_rsa_ecdsa(const struct jwt_ctx *ctx, const struct buffer *decoded_
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (EVP_DigestVerifyInit(evp_md_ctx, NULL, evp, NULL,entry-> pkey) == 1 &&
|
||||
/*
|
||||
* ECXXX signatures are a direct concatenation of the (R, S) pair and
|
||||
* need to be converted back to asn.1 in order for verify operations to
|
||||
* work with OpenSSL.
|
||||
*/
|
||||
if (is_ecdsa) {
|
||||
int conv_retval = convert_ecdsa_sig(ctx, entry->pkey, decoded_signature);
|
||||
if (retval != 0) {
|
||||
retval = conv_retval;
|
||||
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 &&
|
||||
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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user