pyjwt icon indicating copy to clipboard operation
pyjwt copied to clipboard

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

Open AFlowOfCode opened this issue 2 years ago • 4 comments

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.

AFlowOfCode avatar Jan 16 '23 22:01 AFlowOfCode

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

github-actions[bot] avatar Mar 18 '23 01:03 github-actions[bot]

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)

jaferrando avatar Sep 04 '23 11:09 jaferrando

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.

AFlowOfCode avatar Sep 04 '23 18:09 AFlowOfCode

I am open to review any contribution which fix this

auvipy avatar Nov 08 '23 04:11 auvipy