pyjwt icon indicating copy to clipboard operation
pyjwt copied to clipboard

Error `TypeError: ECPublicKey.verify() takes 3 positional arguments but 4 were given` while using `jwt.decode`

Open cemysf opened this issue 1 year ago • 6 comments

Getting this error TypeError: ECPublicKey.verify() takes 3 positional arguments but 4 were given while trying to decode a token with RS256 algorithm.

Expected Result

Decode some token via jwt.decode

Actual Result

... (omitted) ...
File /opt/venv/lib/python3.12/site-packages/jwt/api_jwt.py:210, in PyJWT.decode(self, jwt, key, algorithms, options, verify, detached_payload, audience, issuer, leeway, **kwargs)
    203 if kwargs:
    204     warnings.warn(
    205         "passing additional kwargs to decode() is deprecated "
    206         "and will be removed in pyjwt version 3. "
    207         f"Unsupported kwargs: {tuple(kwargs.keys())}",
    208         RemovedInPyjwt3Warning,
    209     )
--> 210 decoded = self.decode_complete(
    211     jwt,
    212     key,
    213     algorithms,
    214     options,
    215     verify=verify,
    216     detached_payload=detached_payload,
    217     audience=audience,
    218     issuer=issuer,
    219     leeway=leeway,
    220 )
    221 return decoded["payload"]

File /opt/venv/lib/python3.12/site-packages/jwt/api_jwt.py:151, in PyJWT.decode_complete(self, jwt, key, algorithms, options, verify, detached_payload, audience, issuer, leeway, **kwargs)
    146 if options["verify_signature"] and not algorithms:
    147     raise DecodeError(
    148         'It is required that you pass in a value for the "algorithms" argument when calling decode().'
    149     )
--> 151 decoded = api_jws.decode_complete(
    152     jwt,
    153     key=key,
    154     algorithms=algorithms,
    155     options=options,
    156     detached_payload=detached_payload,
    157 )
    159 payload = self._decode_payload(decoded)
    161 merged_options = {**self.options, **options}

File /opt/venv/lib/python3.12/site-packages/jwt/api_jws.py:209, in PyJWS.decode_complete(self, jwt, key, algorithms, options, detached_payload, **kwargs)
    206     signing_input = b".".join([signing_input.rsplit(b".", 1)[0], payload])
    208 if verify_signature:
--> 209     self._verify_signature(signing_input, header, signature, key, algorithms)
    211 return {
    212     "payload": payload,
    213     "header": header,
    214     "signature": signature,
    215 }

File /opt/venv/lib/python3.12/site-packages/jwt/api_jws.py:309, in PyJWS._verify_signature(self, signing_input, header, signature, key, algorithms)
    306     raise InvalidAlgorithmError("Algorithm not supported") from e
    307 prepared_key = alg_obj.prepare_key(key)
--> 309 if not alg_obj.verify(signing_input, prepared_key, signature):
    310     raise InvalidSignatureError("Signature verification failed")

File /opt/venv/lib/python3.12/site-packages/jwt/algorithms.py:483, in RSAAlgorithm.verify(self, msg, key, sig)
    481 def verify(self, msg: bytes, key: RSAPublicKey, sig: bytes) -> bool:
    482     try:
--> 483         key.verify(sig, msg, padding.PKCS1v15(), self.hash_alg())
    484         return True
    485     except InvalidSignature:

TypeError: ECPublicKey.verify() takes 3 positional arguments but 4 were given

Reproduction Steps

import jwt

token = "..."
key = "..."
jwt.decode(
  jwt=token,
  key=key
  algorithms=["RS256",]
)

System Information

$ python -m jwt.help
{
  "cryptography": {
    "version": "42.0.5"
  },
  "implementation": {
    "name": "CPython",
    "version": "3.12.4"
  },
  "platform": {
    "release": "5.15.0-113-generic",
    "system": "Linux"
  },
  "pyjwt": {
    "version": "2.8.0"
  }
}

cemysf avatar Jul 12 '24 14:07 cemysf

Exact same problem in 2.9.0.

{
  "cryptography": {
    "version": "43.0.1"
  },
  "implementation": {
    "name": "CPython",
    "version": "3.12.4"
  },
  "platform": {
    "release": "11",
    "system": "Windows"
  },
  "pyjwt": {
    "version": "2.9.0"
  }
}

RbnRncn avatar Sep 05 '24 11:09 RbnRncn

Can you check what type(load_pem_public_key(your_key.encode("utf-8"))is? It seems to be returning an ElliptiveCurvePublicKey.

>>> from cryptography.hazmat.primitives.serialization import load_pem_public_key
>>> your_key = "..."  # unicode string, or you if you already have it as bytes skip the encode() below
>>> type(load_pem_public_key(your_key.encode("utf-8")))

(if load_pem_public_key throws an error for you, try load_ssh_public_key - that is, make sure you are using the loader relevant to how the key is formatted)

pachewise avatar Sep 10 '24 20:09 pachewise

Yes, my problem was caused because I was taking the wrong key from the JWKS endpoint of my openid-configuration. I was taking the first one that it is in fact an elliptic curve key ({"kty": "EC"}).

Using a PyJWTClient and the method get_signing_key_from_jwt as it is showed in the example https://pyjwt.readthedocs.io/en/latest/usage.html#oidc-login-flow it took the correct key and decoded without issues.

RbnRncn avatar Sep 11 '24 18:09 RbnRncn

I do feel like we should catch these issues where the key passed in is from the wrong algo-family (and, ideally, down to ensuring the same algo was used). I can attempt a PR this week if that's the approach we want to take.

pachewise avatar Sep 11 '24 21:09 pachewise

Yes, maybe it should warn you (or throw you) in some way that you are trying to verify with an ECPublicKey when the algorithm you specified is RS256.

But if we are already detecting the key algorithm and we know the algorithm passed by the user, we could catch the error and be more specific.

Only my two cents as an user, I'm by no means an expert in this. I'm glad to help if needed.

RbnRncn avatar Sep 12 '24 18:09 RbnRncn

Draft PR, so we have something to look at while proposing a "fix" for this.

pachewise avatar Sep 12 '24 20:09 pachewise

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 Nov 12 '24 01:11 github-actions[bot]