diff options
-rw-r--r-- | docs/faq.rst | 8 | ||||
-rw-r--r-- | docs/installation.rst | 34 | ||||
-rw-r--r-- | jwt/algorithms.py | 79 | ||||
-rw-r--r-- | jwt/contrib/__init__.py | 0 | ||||
-rw-r--r-- | jwt/contrib/algorithms/__init__.py | 0 | ||||
-rw-r--r-- | jwt/contrib/algorithms/py_ecdsa.py | 68 | ||||
-rw-r--r-- | jwt/contrib/algorithms/py_ed25519.py | 79 | ||||
-rw-r--r-- | jwt/contrib/algorithms/pycrypto.py | 46 | ||||
-rw-r--r-- | jwt/help.py | 5 | ||||
-rw-r--r-- | pyproject.toml | 2 | ||||
-rw-r--r-- | setup.cfg | 4 | ||||
-rw-r--r-- | tests/contrib/__init__.py | 0 | ||||
-rw-r--r-- | tests/contrib/test_algorithms.py | 299 | ||||
-rw-r--r-- | tests/test_algorithms.py | 85 |
14 files changed, 157 insertions, 552 deletions
diff --git a/docs/faq.rst b/docs/faq.rst index a5eb139..e8fb177 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -28,11 +28,3 @@ extract the public or private keys from a x509 certificate in PEM format. cert_obj = load_pem_x509_certificate(cert_str, default_backend()) public_key = cert_obj.public_key() private_key = cert_obj.private_key() - - -I'm using Google App Engine and can't install `cryptography`, what can I do? ----------------------------------------------------------------------------- - -Some platforms like Google App Engine don't allow you to install libraries -that require C extensions to be built (like `cryptography`). If you're deploying -to one of those environments, you should check out :ref:`legacy-deps` diff --git a/docs/installation.rst b/docs/installation.rst index e423cfb..6da2ccd 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -23,39 +23,5 @@ The ``pyjwt[crypto]`` format is recommended in requirements files in projects using ``PyJWT``, as a separate ``cryptography`` requirement line may later be mistaken for an unused requirement and removed. -.. _legacy-deps: - -Legacy Dependencies -------------------- - -Some environments, most notably Google App Engine, do not allow the installation -of Python packages that require compilation of C extensions and therefore -cannot install ``cryptography``. If you can install ``cryptography``, you -should disregard this section. - -If you are deploying an application to one of these environments, you may -need to use the legacy implementations of the digital signature algorithms: - -.. code-block:: console - - $ pip install pycrypto ecdsa - -Once you have installed ``pycrypto`` and ``ecdcsa``, you can tell PyJWT to use -the legacy implementations with ``jwt.register_algorithm()``. The following -example code shows how to configure PyJWT to use the legacy implementations -for RSA with SHA256 and EC with SHA256 signatures. - -.. code-block:: python - - import jwt - from jwt.contrib.algorithms.pycrypto import RSAAlgorithm - from jwt.contrib.algorithms.py_ecdsa import ECAlgorithm - - jwt.unregister_algorithm('RS256') - jwt.unregister_algorithm('ES256') - - jwt.register_algorithm('RS256', RSAAlgorithm(RSAAlgorithm.SHA256)) - jwt.register_algorithm('ES256', ECAlgorithm(ECAlgorithm.SHA256)) - .. _`cryptography`: https://cryptography.io diff --git a/jwt/algorithms.py b/jwt/algorithms.py index be41ff1..5723984 100644 --- a/jwt/algorithms.py +++ b/jwt/algorithms.py @@ -39,10 +39,15 @@ try: from cryptography.hazmat.backends import default_backend from cryptography.exceptions import InvalidSignature + import cryptography.exceptions + from cryptography.hazmat.primitives.asymmetric.ed25519 import ( + Ed25519PrivateKey, + Ed25519PublicKey, + ) + has_crypto = True except ImportError: has_crypto = False - has_ed25519 = False requires_cryptography = { "RS256", @@ -85,16 +90,9 @@ def get_default_algorithms(): "PS256": RSAPSSAlgorithm(RSAPSSAlgorithm.SHA256), "PS384": RSAPSSAlgorithm(RSAPSSAlgorithm.SHA384), "PS512": RSAPSSAlgorithm(RSAPSSAlgorithm.SHA512), + "EdDSA": Ed25519Algorithm(), } ) - # Older versions of the `cryptography` libraries may not have Ed25519 available. - # Needs a minimum of version 2.6 - try: - from jwt.contrib.algorithms.py_ed25519 import Ed25519Algorithm - - default_algorithms.update({"EdDSA": Ed25519Algorithm()}) - except ImportError: - pass return default_algorithms @@ -470,3 +468,66 @@ if has_crypto: # noqa: C901 return True except InvalidSignature: return False + + class Ed25519Algorithm(Algorithm): + """ + Performs signing and verification operations using Ed25519 + + This class requires ``cryptography>=2.6`` to be installed. + """ + + def __init__(self, **kwargs): + pass + + def prepare_key(self, key): + + if isinstance(key, (Ed25519PrivateKey, Ed25519PublicKey)): + return key + + if isinstance(key, (bytes, str)): + if isinstance(key, str): + key = key.encode("utf-8") + str_key = key.decode("utf-8") + + if "-----BEGIN PUBLIC" in str_key: + return load_pem_public_key(key, backend=default_backend()) + if "-----BEGIN PRIVATE" in str_key: + return load_pem_private_key( + key, password=None, backend=default_backend() + ) + if str_key[0:4] == "ssh-": + return load_ssh_public_key(key, backend=default_backend()) + + raise TypeError("Expecting a PEM-formatted or OpenSSH key.") + + def sign(self, msg, key): + """ + Sign a message ``msg`` using the Ed25519 private key ``key`` + :param str|bytes msg: Message to sign + :param Ed25519PrivateKey key: A :class:`.Ed25519PrivateKey` instance + :return bytes signature: The signature, as bytes + """ + msg = bytes(msg, "utf-8") if type(msg) is not bytes else msg + return key.sign(msg) + + def verify(self, msg, key, sig): + """ + Verify a given ``msg`` against a signature ``sig`` using the Ed25519 key ``key`` + + :param str|bytes sig: Ed25519 signature to check ``msg`` against + :param str|bytes msg: Message to sign + :param Ed25519PrivateKey|Ed25519PublicKey key: A private or public Ed25519 key instance + :return bool verified: True if signature is valid, False if not. + """ + try: + msg = bytes(msg, "utf-8") if type(msg) is not bytes else msg + sig = bytes(sig, "utf-8") if type(sig) is not bytes else sig + + if isinstance(key, Ed25519PrivateKey): + key = key.public_key() + key.verify(sig, msg) + return ( + True # If no exception was raised, the signature is valid. + ) + except cryptography.exceptions.InvalidSignature: + return False diff --git a/jwt/contrib/__init__.py b/jwt/contrib/__init__.py deleted file mode 100644 index e69de29..0000000 --- a/jwt/contrib/__init__.py +++ /dev/null diff --git a/jwt/contrib/algorithms/__init__.py b/jwt/contrib/algorithms/__init__.py deleted file mode 100644 index e69de29..0000000 --- a/jwt/contrib/algorithms/__init__.py +++ /dev/null diff --git a/jwt/contrib/algorithms/py_ecdsa.py b/jwt/contrib/algorithms/py_ecdsa.py deleted file mode 100644 index 3f5fa76..0000000 --- a/jwt/contrib/algorithms/py_ecdsa.py +++ /dev/null @@ -1,68 +0,0 @@ -# Note: This file is named py_ecdsa.py because import behavior in Python 2 -# would cause ecdsa.py to squash the ecdsa library that it depends upon. - -import hashlib - -import ecdsa - -from jwt.algorithms import Algorithm - - -class ECAlgorithm(Algorithm): - """ - Performs signing and verification operations using - ECDSA and the specified hash function - - This class requires the ecdsa package to be installed. - - This is based off of the implementation in PyJWT 0.3.2 - """ - - SHA256 = hashlib.sha256 - SHA384 = hashlib.sha384 - SHA512 = hashlib.sha512 - - def __init__(self, hash_alg): - self.hash_alg = hash_alg - - def prepare_key(self, key): - - if isinstance(key, ecdsa.SigningKey) or isinstance( - key, ecdsa.VerifyingKey - ): - return key - - if isinstance(key, (bytes, str)): - if isinstance(key, str): - key = key.encode("utf-8") - - # Attempt to load key. We don't know if it's - # a Signing Key or a Verifying Key, so we try - # the Verifying Key first. - try: - key = ecdsa.VerifyingKey.from_pem(key) - except ecdsa.der.UnexpectedDER: - key = ecdsa.SigningKey.from_pem(key) - - else: - raise TypeError("Expecting a PEM-formatted key.") - - return key - - def sign(self, msg, key): - return key.sign( - msg, hashfunc=self.hash_alg, sigencode=ecdsa.util.sigencode_string - ) - - def verify(self, msg, key, sig): - try: - return key.verify( - sig, - msg, - hashfunc=self.hash_alg, - sigdecode=ecdsa.util.sigdecode_string, - ) - # ecdsa <= 0.13.2 raises AssertionError on too long signatures, - # ecdsa >= 0.13.3 raises BadSignatureError for verification errors. - except (AssertionError, ecdsa.BadSignatureError): - return False diff --git a/jwt/contrib/algorithms/py_ed25519.py b/jwt/contrib/algorithms/py_ed25519.py deleted file mode 100644 index a86000a..0000000 --- a/jwt/contrib/algorithms/py_ed25519.py +++ /dev/null @@ -1,79 +0,0 @@ -""" -Implementation of Ed25519 using ``cryptography`` (as of Version 2.6 released in February 2019) -""" - -import cryptography.exceptions -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives.asymmetric.ed25519 import ( - Ed25519PrivateKey, - Ed25519PublicKey, -) -from cryptography.hazmat.primitives.serialization import ( - load_pem_private_key, - load_pem_public_key, - load_ssh_public_key, -) - -from jwt.algorithms import Algorithm - - -class Ed25519Algorithm(Algorithm): - """ - Performs signing and verification operations using Ed25519 - - This class requires ``cryptography>=2.6`` to be installed. - """ - - def __init__(self, **kwargs): - pass - - def prepare_key(self, key): - - if isinstance(key, (Ed25519PrivateKey, Ed25519PublicKey)): - return key - - if isinstance(key, (bytes, str)): - if isinstance(key, str): - key = key.encode("utf-8") - str_key = key.decode("utf-8") - - if "-----BEGIN PUBLIC" in str_key: - return load_pem_public_key(key, backend=default_backend()) - if "-----BEGIN PRIVATE" in str_key: - return load_pem_private_key( - key, password=None, backend=default_backend() - ) - if str_key[0:4] == "ssh-": - return load_ssh_public_key(key, backend=default_backend()) - - raise TypeError("Expecting a PEM-formatted or OpenSSH key.") - - def sign(self, msg, key): - """ - Sign a message ``msg`` using the Ed25519 private key ``key`` - :param str|bytes msg: Message to sign - :param Ed25519PrivateKey key: A :class:`.Ed25519PrivateKey` instance - :return bytes signature: The signature, as bytes - """ - msg = bytes(msg, "utf-8") if type(msg) is not bytes else msg - return key.sign(msg) - - def verify(self, msg, key, sig): - """ - Verify a given ``msg`` against a signature ``sig`` using the Ed25519 key ``key`` - - :param str|bytes sig: Ed25519 signature to check ``msg`` against - :param str|bytes msg: Message to sign - :param Ed25519PrivateKey|Ed25519PublicKey key: A private or public Ed25519 key instance - :return bool verified: True if signature is valid, False if not. - """ - try: - msg = bytes(msg, "utf-8") if type(msg) is not bytes else msg - sig = bytes(sig, "utf-8") if type(sig) is not bytes else sig - - if isinstance(key, Ed25519PrivateKey): - key = key.public_key() - key.verify(sig, msg) - return True # If no exception was raised, the signature is valid. - except cryptography.exceptions.InvalidSignature: - return False diff --git a/jwt/contrib/algorithms/pycrypto.py b/jwt/contrib/algorithms/pycrypto.py deleted file mode 100644 index 0fb5871..0000000 --- a/jwt/contrib/algorithms/pycrypto.py +++ /dev/null @@ -1,46 +0,0 @@ -import Crypto.Hash.SHA256 -import Crypto.Hash.SHA384 -import Crypto.Hash.SHA512 -from Crypto.PublicKey import RSA -from Crypto.Signature import PKCS1_v1_5 - -from jwt.algorithms import Algorithm - - -class RSAAlgorithm(Algorithm): - """ - Performs signing and verification operations using - RSASSA-PKCS-v1_5 and the specified hash function. - - This class requires PyCrypto package to be installed. - - This is based off of the implementation in PyJWT 0.3.2 - """ - - SHA256 = Crypto.Hash.SHA256 - SHA384 = Crypto.Hash.SHA384 - SHA512 = Crypto.Hash.SHA512 - - def __init__(self, hash_alg): - self.hash_alg = hash_alg - - def prepare_key(self, key): - - if isinstance(key, RSA._RSAobj): - return key - - if isinstance(key, (bytes, str)): - if isinstance(key, str): - key = key.encode("utf-8") - - key = RSA.importKey(key) - else: - raise TypeError("Expecting a PEM- or RSA-formatted key.") - - return key - - def sign(self, msg, key): - return PKCS1_v1_5.new(key).sign(self.hash_alg.new(msg)) - - def verify(self, msg, key, sig): - return PKCS1_v1_5.new(key).verify(self.hash_alg.new(msg), sig) diff --git a/jwt/help.py b/jwt/help.py index 9b42a4d..510be0a 100644 --- a/jwt/help.py +++ b/jwt/help.py @@ -9,11 +9,6 @@ try: except ImportError: cryptography = None # type: ignore -try: - import ecdsa # type: ignore -except ImportError: - ecdsa = None # type: ignore - def info(): """ diff --git a/pyproject.toml b/pyproject.toml index 0552d2b..1830eff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,4 +28,4 @@ use_parentheses=true combine_as_imports=true known_first_party="jwt" -known_third_party=["Crypto", "cryptography", "ecdsa", "pytest", "requests_mock", "setuptools", "sphinx_rtd_theme"] +known_third_party=["pytest", "requests_mock", "setuptools", "sphinx_rtd_theme"] @@ -38,7 +38,7 @@ docs = sphinx-rtd-theme zope.interface crypto = - cryptography>=1.4 + cryptography>=2.6,<3.0.0 tests = pytest>=6.0.0,<7.0.0 coverage[toml]==5.0.4 @@ -47,7 +47,7 @@ dev = sphinx sphinx-rtd-theme zope.interface - cryptography>=1.4 + cryptography>=2.6,<3.0.0 pytest>=6.0.0,<7.0.0 coverage[toml]==5.0.4 requests diff --git a/tests/contrib/__init__.py b/tests/contrib/__init__.py deleted file mode 100644 index e69de29..0000000 --- a/tests/contrib/__init__.py +++ /dev/null diff --git a/tests/contrib/test_algorithms.py b/tests/contrib/test_algorithms.py deleted file mode 100644 index f0cf394..0000000 --- a/tests/contrib/test_algorithms.py +++ /dev/null @@ -1,299 +0,0 @@ -import base64 - -import pytest - -from jwt.utils import force_bytes, force_unicode - -from ..utils import key_path - -try: - from jwt.contrib.algorithms.pycrypto import RSAAlgorithm - - has_pycrypto = True -except ImportError: - has_pycrypto = False - -try: - from jwt.contrib.algorithms.py_ecdsa import ECAlgorithm - - has_ecdsa = True -except ImportError: - has_ecdsa = False - -try: - from jwt.contrib.algorithms.py_ed25519 import Ed25519Algorithm - - has_ed25519 = True -except ImportError: - has_ed25519 = False - - -@pytest.mark.skipif( - not has_pycrypto, reason="Not supported without PyCrypto library" -) -class TestPycryptoAlgorithms: - def test_rsa_should_parse_pem_public_key(self): - algo = RSAAlgorithm(RSAAlgorithm.SHA256) - - with open(key_path("testkey2_rsa.pub.pem")) as pem_key: - algo.prepare_key(pem_key.read()) - - def test_rsa_should_accept_unicode_key(self): - algo = RSAAlgorithm(RSAAlgorithm.SHA256) - - with open(key_path("testkey_rsa")) as rsa_key: - algo.prepare_key(force_unicode(rsa_key.read())) - - def test_rsa_should_reject_non_string_key(self): - algo = RSAAlgorithm(RSAAlgorithm.SHA256) - - with pytest.raises(TypeError): - algo.prepare_key(None) - - def test_rsa_sign_should_generate_correct_signature_value(self): - algo = RSAAlgorithm(RSAAlgorithm.SHA256) - - jwt_message = force_bytes("Hello World!") - - expected_sig = base64.b64decode( - force_bytes( - "yS6zk9DBkuGTtcBzLUzSpo9gGJxJFOGvUqN01iLhWHrzBQ9ZEz3+Ae38AXp" - "10RWwscp42ySC85Z6zoN67yGkLNWnfmCZSEv+xqELGEvBJvciOKsrhiObUl" - "2mveSc1oeO/2ujkGDkkkJ2epn0YliacVjZF5+/uDmImUfAAj8lzjnHlzYix" - "sn5jGz1H07jYYbi9diixN8IUhXeTafwFg02IcONhum29V40Wu6O5tAKWlJX" - "fHJnNUzAEUOXS0WahHVb57D30pcgIji9z923q90p5c7E2cU8V+E1qe8NdCA" - "APCDzZZ9zQ/dgcMVaBrGrgimrcLbPjueOKFgSO+SSjIElKA==" - ) - ) - - with open(key_path("testkey_rsa")) as keyfile: - jwt_key = algo.prepare_key(keyfile.read()) - - with open(key_path("testkey_rsa.pub")) as keyfile: - jwt_pub_key = algo.prepare_key(keyfile.read()) - - algo.sign(jwt_message, jwt_key) - result = algo.verify(jwt_message, jwt_pub_key, expected_sig) - assert result - - def test_rsa_verify_should_return_false_if_signature_invalid(self): - algo = RSAAlgorithm(RSAAlgorithm.SHA256) - - jwt_message = force_bytes("Hello World!") - - jwt_sig = base64.b64decode( - force_bytes( - "yS6zk9DBkuGTtcBzLUzSpo9gGJxJFOGvUqN01iLhWHrzBQ9ZEz3+Ae38AXp" - "10RWwscp42ySC85Z6zoN67yGkLNWnfmCZSEv+xqELGEvBJvciOKsrhiObUl" - "2mveSc1oeO/2ujkGDkkkJ2epn0YliacVjZF5+/uDmImUfAAj8lzjnHlzYix" - "sn5jGz1H07jYYbi9diixN8IUhXeTafwFg02IcONhum29V40Wu6O5tAKWlJX" - "fHJnNUzAEUOXS0WahHVb57D30pcgIji9z923q90p5c7E2cU8V+E1qe8NdCA" - "APCDzZZ9zQ/dgcMVaBrGrgimrcLbPjueOKFgSO+SSjIElKA==" - ) - ) - - jwt_sig += force_bytes("123") # Signature is now invalid - - with open(key_path("testkey_rsa.pub")) as keyfile: - jwt_pub_key = algo.prepare_key(keyfile.read()) - - result = algo.verify(jwt_message, jwt_pub_key, jwt_sig) - assert not result - - def test_rsa_verify_should_return_true_if_signature_valid(self): - algo = RSAAlgorithm(RSAAlgorithm.SHA256) - - jwt_message = force_bytes("Hello World!") - - jwt_sig = base64.b64decode( - force_bytes( - "yS6zk9DBkuGTtcBzLUzSpo9gGJxJFOGvUqN01iLhWHrzBQ9ZEz3+Ae38AXp" - "10RWwscp42ySC85Z6zoN67yGkLNWnfmCZSEv+xqELGEvBJvciOKsrhiObUl" - "2mveSc1oeO/2ujkGDkkkJ2epn0YliacVjZF5+/uDmImUfAAj8lzjnHlzYix" - "sn5jGz1H07jYYbi9diixN8IUhXeTafwFg02IcONhum29V40Wu6O5tAKWlJX" - "fHJnNUzAEUOXS0WahHVb57D30pcgIji9z923q90p5c7E2cU8V+E1qe8NdCA" - "APCDzZZ9zQ/dgcMVaBrGrgimrcLbPjueOKFgSO+SSjIElKA==" - ) - ) - - with open(key_path("testkey_rsa.pub")) as keyfile: - jwt_pub_key = algo.prepare_key(keyfile.read()) - - result = algo.verify(jwt_message, jwt_pub_key, jwt_sig) - assert result - - def test_rsa_prepare_key_should_be_idempotent(self): - algo = RSAAlgorithm(RSAAlgorithm.SHA256) - - with open(key_path("testkey_rsa.pub")) as keyfile: - jwt_pub_key_first = algo.prepare_key(keyfile.read()) - jwt_pub_key_second = algo.prepare_key(jwt_pub_key_first) - - assert jwt_pub_key_first == jwt_pub_key_second - - -@pytest.mark.skipif( - not has_ecdsa, reason="Not supported without ecdsa library" -) -class TestEcdsaAlgorithms: - def test_ec_should_reject_non_string_key(self): - algo = ECAlgorithm(ECAlgorithm.SHA256) - - with pytest.raises(TypeError): - algo.prepare_key(None) - - def test_ec_should_accept_unicode_key(self): - algo = ECAlgorithm(ECAlgorithm.SHA256) - - with open(key_path("testkey_ec")) as ec_key: - algo.prepare_key(force_unicode(ec_key.read())) - - def test_ec_sign_should_generate_correct_signature_value(self): - algo = ECAlgorithm(ECAlgorithm.SHA256) - - jwt_message = force_bytes("Hello World!") - - expected_sig = base64.b64decode( - force_bytes( - "AC+m4Jf/xI3guAC6w0w37t5zRpSCF6F4udEz5LiMiTIjCS4vcVe6dDOxK+M" - "mvkF8PxJuvqxP2CO3TR3okDPCl/NjATTO1jE+qBZ966CRQSSzcCM+tzcHzw" - "LZS5kbvKu0Acd/K6Ol2/W3B1NeV5F/gjvZn/jOwaLgWEUYsg0o4XVrAg65" - ) - ) - - with open(key_path("testkey_ec")) as keyfile: - jwt_key = algo.prepare_key(keyfile.read()) - - with open(key_path("testkey_ec.pub")) as keyfile: - jwt_pub_key = algo.prepare_key(keyfile.read()) - - algo.sign(jwt_message, jwt_key) - result = algo.verify(jwt_message, jwt_pub_key, expected_sig) - assert result - - def test_ec_verify_should_return_false_if_signature_invalid(self): - algo = ECAlgorithm(ECAlgorithm.SHA256) - - jwt_message = force_bytes("Hello World!") - - jwt_sig = base64.b64decode( - force_bytes( - "AC+m4Jf/xI3guAC6w0w37t5zRpSCF6F4udEz5LiMiTIjCS4vcVe6dDOxK+M" - "mvkF8PxJuvqxP2CO3TR3okDPCl/NjATTO1jE+qBZ966CRQSSzcCM+tzcHzw" - "LZS5kbvKu0Acd/K6Ol2/W3B1NeV5F/gjvZn/jOwaLgWEUYsg0o4XVrAg65" - ) - ) - - jwt_sig += force_bytes("123") # Signature is now invalid - - with open(key_path("testkey_ec.pub")) as keyfile: - jwt_pub_key = algo.prepare_key(keyfile.read()) - - result = algo.verify(jwt_message, jwt_pub_key, jwt_sig) - assert not result - - def test_ec_verify_should_return_true_if_signature_valid(self): - algo = ECAlgorithm(ECAlgorithm.SHA256) - - jwt_message = force_bytes("Hello World!") - - jwt_sig = base64.b64decode( - force_bytes( - "AC+m4Jf/xI3guAC6w0w37t5zRpSCF6F4udEz5LiMiTIjCS4vcVe6dDOxK+M" - "mvkF8PxJuvqxP2CO3TR3okDPCl/NjATTO1jE+qBZ966CRQSSzcCM+tzcHzw" - "LZS5kbvKu0Acd/K6Ol2/W3B1NeV5F/gjvZn/jOwaLgWEUYsg0o4XVrAg65" - ) - ) - - with open(key_path("testkey_ec.pub")) as keyfile: - jwt_pub_key = algo.prepare_key(keyfile.read()) - - result = algo.verify(jwt_message, jwt_pub_key, jwt_sig) - assert result - - def test_ec_prepare_key_should_be_idempotent(self): - algo = ECAlgorithm(ECAlgorithm.SHA256) - - with open(key_path("testkey_ec.pub")) as keyfile: - jwt_pub_key_first = algo.prepare_key(keyfile.read()) - jwt_pub_key_second = algo.prepare_key(jwt_pub_key_first) - - assert jwt_pub_key_first == jwt_pub_key_second - - -@pytest.mark.skipif( - not has_ed25519, reason="Not supported without cryptography>=2.6 library" -) -class TestEd25519Algorithms: - hello_world_sig = "Qxa47mk/azzUgmY2StAOguAd4P7YBLpyCfU3JdbaiWnXM4o4WibXwmIHvNYgN3frtE2fcyd8OYEaOiD/KiwkCg==" - hello_world = force_bytes("Hello World!") - - def test_ed25519_should_reject_non_string_key(self): - algo = Ed25519Algorithm() - - with pytest.raises(TypeError): - algo.prepare_key(None) - - with open(key_path("testkey_ed25519")) as keyfile: - algo.prepare_key(keyfile.read()) - - with open(key_path("testkey_ed25519.pub")) as keyfile: - algo.prepare_key(keyfile.read()) - - def test_ed25519_should_accept_unicode_key(self): - algo = Ed25519Algorithm() - - with open(key_path("testkey_ed25519")) as ec_key: - algo.prepare_key(force_unicode(ec_key.read())) - - def test_ed25519_sign_should_generate_correct_signature_value(self): - algo = Ed25519Algorithm() - - jwt_message = self.hello_world - - expected_sig = base64.b64decode(force_bytes(self.hello_world_sig)) - - with open(key_path("testkey_ed25519")) as keyfile: - jwt_key = algo.prepare_key(keyfile.read()) - - with open(key_path("testkey_ed25519.pub")) as keyfile: - jwt_pub_key = algo.prepare_key(keyfile.read()) - - algo.sign(jwt_message, jwt_key) - result = algo.verify(jwt_message, jwt_pub_key, expected_sig) - assert result - - def test_ed25519_verify_should_return_false_if_signature_invalid(self): - algo = Ed25519Algorithm() - - jwt_message = self.hello_world - jwt_sig = base64.b64decode(force_bytes(self.hello_world_sig)) - - jwt_sig += force_bytes("123") # Signature is now invalid - - with open(key_path("testkey_ed25519.pub")) as keyfile: - jwt_pub_key = algo.prepare_key(keyfile.read()) - - result = algo.verify(jwt_message, jwt_pub_key, jwt_sig) - assert not result - - def test_ed25519_verify_should_return_true_if_signature_valid(self): - algo = Ed25519Algorithm() - - jwt_message = self.hello_world - jwt_sig = base64.b64decode(force_bytes(self.hello_world_sig)) - - with open(key_path("testkey_ed25519.pub")) as keyfile: - jwt_pub_key = algo.prepare_key(keyfile.read()) - - result = algo.verify(jwt_message, jwt_pub_key, jwt_sig) - assert result - - def test_ed25519_prepare_key_should_be_idempotent(self): - algo = Ed25519Algorithm() - - with open(key_path("testkey_ed25519.pub")) as keyfile: - jwt_pub_key_first = algo.prepare_key(keyfile.read()) - jwt_pub_key_second = algo.prepare_key(jwt_pub_key_first) - - assert jwt_pub_key_first == jwt_pub_key_second diff --git a/tests/test_algorithms.py b/tests/test_algorithms.py index 18c8e7b..4af4e6e 100644 --- a/tests/test_algorithms.py +++ b/tests/test_algorithms.py @@ -11,7 +11,12 @@ from .keys import load_hmac_key from .utils import key_path try: - from jwt.algorithms import RSAAlgorithm, ECAlgorithm, RSAPSSAlgorithm + from jwt.algorithms import ( + RSAAlgorithm, + ECAlgorithm, + RSAPSSAlgorithm, + Ed25519Algorithm, + ) from .keys import load_rsa_pub_key, load_ec_pub_key has_crypto = True @@ -669,3 +674,81 @@ class TestAlgorithmsRFC7520: result = algo.verify(signing_input, key, signature) assert result + + +@pytest.mark.skipif( + not has_crypto, reason="Not supported without cryptography>=2.6 library" +) +class TestEd25519Algorithms: + hello_world_sig = "Qxa47mk/azzUgmY2StAOguAd4P7YBLpyCfU3JdbaiWnXM4o4WibXwmIHvNYgN3frtE2fcyd8OYEaOiD/KiwkCg==" + hello_world = force_bytes("Hello World!") + + def test_ed25519_should_reject_non_string_key(self): + algo = Ed25519Algorithm() + + with pytest.raises(TypeError): + algo.prepare_key(None) + + with open(key_path("testkey_ed25519")) as keyfile: + algo.prepare_key(keyfile.read()) + + with open(key_path("testkey_ed25519.pub")) as keyfile: + algo.prepare_key(keyfile.read()) + + def test_ed25519_should_accept_unicode_key(self): + algo = Ed25519Algorithm() + + with open(key_path("testkey_ed25519")) as ec_key: + algo.prepare_key(force_unicode(ec_key.read())) + + def test_ed25519_sign_should_generate_correct_signature_value(self): + algo = Ed25519Algorithm() + + jwt_message = self.hello_world + + expected_sig = base64.b64decode(force_bytes(self.hello_world_sig)) + + with open(key_path("testkey_ed25519")) as keyfile: + jwt_key = algo.prepare_key(keyfile.read()) + + with open(key_path("testkey_ed25519.pub")) as keyfile: + jwt_pub_key = algo.prepare_key(keyfile.read()) + + algo.sign(jwt_message, jwt_key) + result = algo.verify(jwt_message, jwt_pub_key, expected_sig) + assert result + + def test_ed25519_verify_should_return_false_if_signature_invalid(self): + algo = Ed25519Algorithm() + + jwt_message = self.hello_world + jwt_sig = base64.b64decode(force_bytes(self.hello_world_sig)) + + jwt_sig += force_bytes("123") # Signature is now invalid + + with open(key_path("testkey_ed25519.pub")) as keyfile: + jwt_pub_key = algo.prepare_key(keyfile.read()) + + result = algo.verify(jwt_message, jwt_pub_key, jwt_sig) + assert not result + + def test_ed25519_verify_should_return_true_if_signature_valid(self): + algo = Ed25519Algorithm() + + jwt_message = self.hello_world + jwt_sig = base64.b64decode(force_bytes(self.hello_world_sig)) + + with open(key_path("testkey_ed25519.pub")) as keyfile: + jwt_pub_key = algo.prepare_key(keyfile.read()) + + result = algo.verify(jwt_message, jwt_pub_key, jwt_sig) + assert result + + def test_ed25519_prepare_key_should_be_idempotent(self): + algo = Ed25519Algorithm() + + with open(key_path("testkey_ed25519.pub")) as keyfile: + jwt_pub_key_first = algo.prepare_key(keyfile.read()) + jwt_pub_key_second = algo.prepare_key(jwt_pub_key_first) + + assert jwt_pub_key_first == jwt_pub_key_second |