jpadilla / pyjwt

JSON Web Token implementation in Python
https://pyjwt.readthedocs.io
MIT License
5.05k stars 676 forks source link

Can't verify detached payload JWS with JWK from its header #851

Open AFlowOfCode opened 1 year ago

AFlowOfCode commented 1 year ago

I am trying to verify the signature of a JWS with the JWK that is included in its header. I believe the fact that it has a detached payload is only incidental to the primary issue of not recognizing the correct form of a JWS's key. The JWK is normal and valid, for example:

{'crv': 'P-256', 'kty': 'EC', 'x': 'PY5pUvmWTEz5mCVir-Tyfi1M0q07_qaZSU_UAN3HBSI', 'y': 'aH9ZAGpTidZjxNu2zKXeX9koNQX_BAtIBCa-h7YC_B0'}

I can get a jwt.api_jwk.PyJWK object if I do api_jwk.PyJWK(jws_jwk, algorithm='ES256'), proving there is no issue with the JWK itself. However when I try to use it to verify the signature of a JWS in the manner below, I receive the error message Expecting a PEM-formatted key.

payload = api_jws.decode(
    jws_compact, verification_jwk, algorithms=['ES256'], 
    options={'verify_signature': True},
    detached_payload=payload)

It's clear that this is because the prepare_key method of the ECAlgorithm class expects either a key of type EllipticCurvePublicKey or a PEM string. However this is not how one typically receives the verification key in a JWS. They are always in JWK form, and I can't find any clear way to convert a JWK to a EllipticCurvePublicKey object nor a PEM.

Is this intended? Am I missing something obvious here? This seems like a bug or an oversight to me, so I appreciate any clarification on the proper verification of a JWS using its own key material.

Expected Result

To verify a standard JWS using the included key material, wherein it passes or fails depending upon the validity of the included signature as verified by the standard JWK included in a JWS protected header.

Actual Result

I am asked for a PEM-formatted key, which is not how keys are sent with a JWS.

Reproduction Steps

Use the JWK in any JWS and pass it into api_jws.decode along with the JWS as shown above.

github-actions[bot] commented 1 year ago

This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days

jaferrando commented 11 months ago

As a workaround I'd say you could load the JWK into a PyJWK object and then access the key atribute of the resulting object.

(untested code):

jwk_data = {'crv': 'P-256', 'kty': 'EC', 'x': 'PY5pUvmWTEz5mCVir-Tyfi1M0q07_qaZSU_UAN3HBSI', 'y': 'aH9ZAGpTidZjxNu2zKXeX9koNQX_BAtIBCa-h7YC_B0'}
jwk = PyJWK(jwk_data)
payload = api_jws.decode(
    jws_compact, jwk.key, algorithms=['ES256'], 
    options={'verify_signature': True},
    detached_payload=payload)
AFlowOfCode commented 11 months ago

Nice, that does work. In that case it seems like updating the api_jws.decode method to handle actual JWKs could be implemented pretty easily. The conversion performed by PyJWK.key could be called on the verification material argument when the type is a dict instead of a PEM string or EllipticCurvePublicKey instance.

auvipy commented 9 months ago

I am open to review any contribution which fix this