pyjwt icon indicating copy to clipboard operation
pyjwt copied to clipboard

JWTs can be "successfully" encoded and decoded even when `algorithm` does not match the EC curve type

Open DavidBuchanan314 opened this issue 1 year ago • 4 comments

You can generate a SECP256K1 keypair and then tell pyjwt to sign a message using algorithm "ES256".

Expected Result

An exception should be raised, because the SECP256K1 curve is not compatible with the ES256 algorithm (it wants ES256K).

Actual Result

An invalid JWT is encoded (signature will not verify against declared algorithm).

Subsequently, the invalid JWT can be decoded "successfully" without error.

This is arguably a security issue, but it only arises if you use the API "wrong". Nonetheless, I think the API should try to guard against such incorrect uses.

Reproduction Steps

from cryptography.hazmat.primitives.asymmetric import ec
import jwt

#KEY_TYPE = ec.SECP256R1()
KEY_TYPE = ec.SECP256K1()

privkey = ec.generate_private_key(KEY_TYPE)

my_jwt = jwt.encode(
	{ "hello": "world" },
	privkey,
	algorithm="ES256", # nistp256 aka ec.SECP256R1()
)

print(my_jwt) # I think this should raise an exception!

decoded = jwt.decode(my_jwt, key=privkey.public_key(), algorithms=["ES256"])

print(decoded) # This should raise an exception even more so!

System Information

$ python -m jwt.help
{
  "cryptography": {
    "version": "41.0.7"
  },
  "implementation": {
    "name": "CPython",
    "version": "3.12.7"
  },
  "platform": {
    "release": "6.11.0-400.asahi.fc40.aarch64+16k",
    "system": "Linux"
  },
  "pyjwt": {
    "version": "2.10.0"
  }
}

DavidBuchanan314 avatar Dec 02 '24 04:12 DavidBuchanan314

p.s. it would be nice if I didn't have to pass algorithm explicitly, since the correct algorithm to use can be inferred from the passed key type, assuming the passed key is an object (and not just a string or whatever).

DavidBuchanan314 avatar Dec 02 '24 04:12 DavidBuchanan314

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 Feb 01 '25 01:02 github-actions[bot]

not stale

DavidBuchanan314 avatar Feb 01 '25 02:02 DavidBuchanan314

So the decoding part IMO is fine to succeed, since the payload was encoded using ES256 (if you had encoded using ES256K, then decoding will have thrown an error, as you would expect). That said, for encode(), I agree we should check if the key is a cryptography PrivateKey object - if it is, we should validate the algorithm type (or, more simply, deduce the algorithm from the PrivateKey and throw an exception if the algorithm is specified).

EDIT: PyJWT doesn't have a way yet to determine which algorithm maps to which cryptography Public/PrivateKey. This may probably be the bulk of the work for this ticket. I had suggested trying to tackle it in #985, but paused since it was beyond the scope of the issue; the "CryptoAlgorithm" with a more robust implementation of "check_crypto_key_type" may be the right path. Still thinking through it...

pachewise avatar Feb 23 '25 05:02 pachewise