diff --git a/reg-tests/jwt/build_token.py b/reg-tests/jwt/build_token.py new file mode 100755 index 000000000..2f368abf9 --- /dev/null +++ b/reg-tests/jwt/build_token.py @@ -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]," ") + 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)) + diff --git a/reg-tests/jwt/jws_verify.vtc b/reg-tests/jwt/jws_verify.vtc index d8afcae5b..3aaf8d8b7 100644 --- a/reg-tests/jwt/jws_verify.vtc +++ b/reg-tests/jwt/jws_verify.vtc @@ -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 diff --git a/src/jwt.c b/src/jwt.c index 7f20e374b..a17af1847 100644 --- a/src/jwt.c +++ b/src/jwt.c @@ -18,6 +18,7 @@ #include #include #include +#include #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; }