diff --git a/CHANGELOG.md b/CHANGELOG.md index 892b5c2..3b5a402 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog # +## 4.0.0 ## + +* Remove dependencies on ecdsa, pyasn1, rsa. + cryptography is now a required dependency. + Remove non-cryptography backends. + ## 3.5.0 -- 2025-05-28 ## ### News ### diff --git a/README.rst b/README.rst index 9b9d03d..f0bd88c 100644 --- a/README.rst +++ b/README.rst @@ -21,50 +21,20 @@ Installation :: - $ pip install python-jose[cryptography] + $ pip install python-jose Cryptographic Backends ---------------------- -As of 3.3.0, python-jose implements three different cryptographic backends. -The backend must be selected as an extra when installing python-jose. -If you do not select a backend, the native-python backend will be installed. - -Unless otherwise noted, all backends support all operations. - -Due to complexities with setuptools, the native-python backend is always installed, -even if you select a different backend on install. -We recommend that you remove unnecessary dependencies in production. +python-jose used to have various cryptographic backends, but due to deprecation +of ecdsa & rsa libraries, the only supported backend as of 4.0.0 is based on +the cryptography lib. #. cryptography * This backend uses `pyca/cryptography`_ for all cryptographic operations. - This is the recommended backend and is selected over all other backends if any others are present. - * Installation: ``pip install python-jose[cryptography]`` - * Unused dependencies: - - * ``rsa`` - * ``ecdsa`` - * ``pyasn1`` - -#. pycryptodome - - * This backend uses `pycryptodome`_ for all cryptographic operations. - * Installation: ``pip install python-jose[pycryptodome]`` - * Unused dependencies: - - * ``rsa`` - -#. native-python - - * This backend uses `python-rsa`_ and `python-ecdsa`_ for all cryptographic operations. - This backend is always installed but any other backend will take precedence if one is installed. - * Installation: ``pip install python-jose`` - - .. note:: - - The native-python backend cannot process certificates. + This is the **required** backend and is selected over all other backends. Usage ----- @@ -99,8 +69,6 @@ This library was originally based heavily on the work of the folks over at PyJWT .. _pyca/cryptography: http://cryptography.io/ .. _pycryptodome: https://pycryptodome.readthedocs.io/en/latest/ .. _pycrypto: https://www.dlitz.net/software/pycrypto/ -.. _python-ecdsa: https://github.com/warner/python-ecdsa -.. _python-rsa: https://stuvel.eu/rsa .. |style| image:: https://img.shields.io/badge/code%20style-black-000000.svg :target: https://github.com/psf/black :alt: Code style: black diff --git a/TODO.md b/TODO.md index 779a15a..2631b6d 100644 --- a/TODO.md +++ b/TODO.md @@ -1,6 +1,5 @@ # TODO -* Gracefully fail if ECDSA is used with ecdsa installed * Refactor JWS json decode and load * Implement RSA PSS signing * Implement JWE diff --git a/jose/__init__.py b/jose/__init__.py index 7e53b60..88372b1 100644 --- a/jose/__init__.py +++ b/jose/__init__.py @@ -1,4 +1,4 @@ -__version__ = "3.5.0" +__version__ = "4.0.0" __author__ = "Michael Davis" __license__ = "MIT" __copyright__ = "Copyright 2016 Michael Davis" diff --git a/jose/backends/__init__.py b/jose/backends/__init__.py index 9918969..975d157 100644 --- a/jose/backends/__init__.py +++ b/jose/backends/__init__.py @@ -1,26 +1,7 @@ +from jose.backends.cryptography_backend import CryptographyAESKey as AESKey # noqa: F401 +from jose.backends.cryptography_backend import CryptographyECKey as ECKey # noqa: F401 +from jose.backends.cryptography_backend import CryptographyHMACKey as HMACKey # noqa: F401 +from jose.backends.cryptography_backend import CryptographyRSAKey as RSAKey # noqa: F401 from jose.backends.native import get_random_bytes # noqa: F401 -try: - from jose.backends.cryptography_backend import CryptographyRSAKey as RSAKey # noqa: F401 -except ImportError: - try: - from jose.backends.rsa_backend import RSAKey # noqa: F401 - except ImportError: - RSAKey = None - -try: - from jose.backends.cryptography_backend import CryptographyECKey as ECKey # noqa: F401 -except ImportError: - from jose.backends.ecdsa_backend import ECDSAECKey as ECKey # noqa: F401 - -try: - from jose.backends.cryptography_backend import CryptographyAESKey as AESKey # noqa: F401 -except ImportError: - AESKey = None - -try: - from jose.backends.cryptography_backend import CryptographyHMACKey as HMACKey # noqa: F401 -except ImportError: - from jose.backends.native import HMACKey # noqa: F401 - from .base import DIRKey # noqa: F401 diff --git a/jose/backends/_asn1.py b/jose/backends/_asn1.py deleted file mode 100644 index 87e3df1..0000000 --- a/jose/backends/_asn1.py +++ /dev/null @@ -1,84 +0,0 @@ -"""ASN1 encoding helpers for converting between PKCS1 and PKCS8. - -Required by rsa_backend but not cryptography_backend. -""" - -from pyasn1.codec.der import decoder, encoder -from pyasn1.type import namedtype, univ - -RSA_ENCRYPTION_ASN1_OID = "1.2.840.113549.1.1.1" - - -class RsaAlgorithmIdentifier(univ.Sequence): - """ASN1 structure for recording RSA PrivateKeyAlgorithm identifiers.""" - - componentType = namedtype.NamedTypes( - namedtype.NamedType("rsaEncryption", univ.ObjectIdentifier()), namedtype.NamedType("parameters", univ.Null()) - ) - - -class PKCS8PrivateKey(univ.Sequence): - """ASN1 structure for recording PKCS8 private keys.""" - - componentType = namedtype.NamedTypes( - namedtype.NamedType("version", univ.Integer()), - namedtype.NamedType("privateKeyAlgorithm", RsaAlgorithmIdentifier()), - namedtype.NamedType("privateKey", univ.OctetString()), - ) - - -class PublicKeyInfo(univ.Sequence): - """ASN1 structure for recording PKCS8 public keys.""" - - componentType = namedtype.NamedTypes( - namedtype.NamedType("algorithm", RsaAlgorithmIdentifier()), namedtype.NamedType("publicKey", univ.BitString()) - ) - - -def rsa_private_key_pkcs8_to_pkcs1(pkcs8_key): - """Convert a PKCS8-encoded RSA private key to PKCS1.""" - decoded_values = decoder.decode(pkcs8_key, asn1Spec=PKCS8PrivateKey()) - - try: - decoded_key = decoded_values[0] - except IndexError: - raise ValueError("Invalid private key encoding") - - return decoded_key["privateKey"] - - -def rsa_private_key_pkcs1_to_pkcs8(pkcs1_key): - """Convert a PKCS1-encoded RSA private key to PKCS8.""" - algorithm = RsaAlgorithmIdentifier() - algorithm["rsaEncryption"] = RSA_ENCRYPTION_ASN1_OID - - pkcs8_key = PKCS8PrivateKey() - pkcs8_key["version"] = 0 - pkcs8_key["privateKeyAlgorithm"] = algorithm - pkcs8_key["privateKey"] = pkcs1_key - - return encoder.encode(pkcs8_key) - - -def rsa_public_key_pkcs1_to_pkcs8(pkcs1_key): - """Convert a PKCS1-encoded RSA private key to PKCS8.""" - algorithm = RsaAlgorithmIdentifier() - algorithm["rsaEncryption"] = RSA_ENCRYPTION_ASN1_OID - - pkcs8_key = PublicKeyInfo() - pkcs8_key["algorithm"] = algorithm - pkcs8_key["publicKey"] = univ.BitString.fromOctetString(pkcs1_key) - - return encoder.encode(pkcs8_key) - - -def rsa_public_key_pkcs8_to_pkcs1(pkcs8_key): - """Convert a PKCS8-encoded RSA private key to PKCS1.""" - decoded_values = decoder.decode(pkcs8_key, asn1Spec=PublicKeyInfo()) - - try: - decoded_key = decoded_values[0] - except IndexError: - raise ValueError("Invalid public key encoding.") - - return decoded_key["publicKey"].asOctets() diff --git a/jose/backends/ecdsa_backend.py b/jose/backends/ecdsa_backend.py deleted file mode 100644 index 756c7ea..0000000 --- a/jose/backends/ecdsa_backend.py +++ /dev/null @@ -1,150 +0,0 @@ -import hashlib - -import ecdsa - -from jose.backends.base import Key -from jose.constants import ALGORITHMS -from jose.exceptions import JWKError -from jose.utils import base64_to_long, long_to_base64 - - -class ECDSAECKey(Key): - """ - 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 - - CURVE_MAP = { - SHA256: ecdsa.curves.NIST256p, - SHA384: ecdsa.curves.NIST384p, - SHA512: ecdsa.curves.NIST521p, - } - CURVE_NAMES = ( - (ecdsa.curves.NIST256p, "P-256"), - (ecdsa.curves.NIST384p, "P-384"), - (ecdsa.curves.NIST521p, "P-521"), - ) - - def __init__(self, key, algorithm): - if algorithm not in ALGORITHMS.EC: - raise JWKError("hash_alg: %s is not a valid hash algorithm" % algorithm) - - self.hash_alg = { - ALGORITHMS.ES256: self.SHA256, - ALGORITHMS.ES384: self.SHA384, - ALGORITHMS.ES512: self.SHA512, - }.get(algorithm) - self._algorithm = algorithm - - self.curve = self.CURVE_MAP.get(self.hash_alg) - - if isinstance(key, (ecdsa.SigningKey, ecdsa.VerifyingKey)): - self.prepared_key = key - return - - if isinstance(key, dict): - self.prepared_key = self._process_jwk(key) - return - - if isinstance(key, str): - key = key.encode("utf-8") - - if isinstance(key, bytes): - # 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) - except Exception as e: - raise JWKError(e) - - self.prepared_key = key - return - - raise JWKError("Unable to parse an ECKey from key: %s" % key) - - def _process_jwk(self, jwk_dict): - if not jwk_dict.get("kty") == "EC": - raise JWKError("Incorrect key type. Expected: 'EC', Received: %s" % jwk_dict.get("kty")) - - if not all(k in jwk_dict for k in ["x", "y", "crv"]): - raise JWKError("Mandatory parameters are missing") - - if "d" in jwk_dict: - # We are dealing with a private key; the secret exponent is enough - # to create an ecdsa key. - d = base64_to_long(jwk_dict.get("d")) - return ecdsa.keys.SigningKey.from_secret_exponent(d, self.curve) - else: - x = base64_to_long(jwk_dict.get("x")) - y = base64_to_long(jwk_dict.get("y")) - - if not ecdsa.ecdsa.point_is_valid(self.curve.generator, x, y): - raise JWKError(f"Point: {x}, {y} is not a valid point") - - point = ecdsa.ellipticcurve.Point(self.curve.curve, x, y, self.curve.order) - return ecdsa.keys.VerifyingKey.from_public_point(point, self.curve) - - def sign(self, msg): - return self.prepared_key.sign( - msg, hashfunc=self.hash_alg, sigencode=ecdsa.util.sigencode_string, allow_truncate=False - ) - - def verify(self, msg, sig): - try: - return self.prepared_key.verify( - sig, msg, hashfunc=self.hash_alg, sigdecode=ecdsa.util.sigdecode_string, allow_truncate=False - ) - except Exception: - return False - - def is_public(self): - return isinstance(self.prepared_key, ecdsa.VerifyingKey) - - def public_key(self): - if self.is_public(): - return self - return self.__class__(self.prepared_key.get_verifying_key(), self._algorithm) - - def to_pem(self): - return self.prepared_key.to_pem() - - def to_dict(self): - if not self.is_public(): - public_key = self.prepared_key.get_verifying_key() - else: - public_key = self.prepared_key - crv = None - for key, value in self.CURVE_NAMES: - if key == self.prepared_key.curve: - crv = value - if not crv: - raise KeyError(f"Can't match {self.prepared_key.curve}") - - # Calculate the key size in bytes. Section 6.2.1.2 and 6.2.1.3 of - # RFC7518 prescribes that the 'x', 'y' and 'd' parameters of the curve - # points must be encoded as octed-strings of this length. - key_size = self.prepared_key.curve.baselen - - data = { - "alg": self._algorithm, - "kty": "EC", - "crv": crv, - "x": long_to_base64(public_key.pubkey.point.x(), size=key_size).decode("ASCII"), - "y": long_to_base64(public_key.pubkey.point.y(), size=key_size).decode("ASCII"), - } - - if not self.is_public(): - data["d"] = long_to_base64(self.prepared_key.privkey.secret_multiplier, size=key_size).decode("ASCII") - - return data diff --git a/jose/backends/native.py b/jose/backends/native.py index 8cc77da..7e3110c 100644 --- a/jose/backends/native.py +++ b/jose/backends/native.py @@ -1,69 +1,5 @@ -import hashlib -import hmac import os -from jose.backends.base import Key -from jose.constants import ALGORITHMS -from jose.exceptions import JWKError -from jose.utils import base64url_decode, base64url_encode, is_pem_format, is_ssh_key - def get_random_bytes(num_bytes): return bytes(os.urandom(num_bytes)) - - -class HMACKey(Key): - """ - Performs signing and verification operations using HMAC - and the specified hash function. - """ - - HASHES = {ALGORITHMS.HS256: hashlib.sha256, ALGORITHMS.HS384: hashlib.sha384, ALGORITHMS.HS512: hashlib.sha512} - - def __init__(self, key, algorithm): - if algorithm not in ALGORITHMS.HMAC: - raise JWKError("hash_alg: %s is not a valid hash algorithm" % algorithm) - self._algorithm = algorithm - self._hash_alg = self.HASHES.get(algorithm) - - if isinstance(key, dict): - self.prepared_key = self._process_jwk(key) - return - - if not isinstance(key, str) and not isinstance(key, bytes): - raise JWKError("Expecting a string- or bytes-formatted key.") - - if isinstance(key, str): - key = key.encode("utf-8") - - if is_pem_format(key) or is_ssh_key(key): - raise JWKError( - "The specified key is an asymmetric key or x509 certificate and" - " should not be used as an HMAC secret." - ) - - self.prepared_key = key - - def _process_jwk(self, jwk_dict): - if not jwk_dict.get("kty") == "oct": - raise JWKError("Incorrect key type. Expected: 'oct', Received: %s" % jwk_dict.get("kty")) - - k = jwk_dict.get("k") - k = k.encode("utf-8") - k = bytes(k) - k = base64url_decode(k) - - return k - - def sign(self, msg): - return hmac.new(self.prepared_key, msg, self._hash_alg).digest() - - def verify(self, msg, sig): - return hmac.compare_digest(sig, self.sign(msg)) - - def to_dict(self): - return { - "alg": self._algorithm, - "kty": "oct", - "k": base64url_encode(self.prepared_key).decode("ASCII"), - } diff --git a/jose/backends/rsa_backend.py b/jose/backends/rsa_backend.py deleted file mode 100644 index 8139d69..0000000 --- a/jose/backends/rsa_backend.py +++ /dev/null @@ -1,283 +0,0 @@ -import binascii -import warnings - -import rsa as pyrsa -import rsa.pem as pyrsa_pem -from pyasn1.error import PyAsn1Error -from rsa import DecryptionError - -from jose.backends._asn1 import ( - rsa_private_key_pkcs1_to_pkcs8, - rsa_private_key_pkcs8_to_pkcs1, - rsa_public_key_pkcs1_to_pkcs8, -) -from jose.backends.base import Key -from jose.constants import ALGORITHMS -from jose.exceptions import JWEError, JWKError -from jose.utils import base64_to_long, long_to_base64 - -ALGORITHMS.SUPPORTED.remove(ALGORITHMS.RSA_OAEP) # RSA OAEP not supported - -LEGACY_INVALID_PKCS8_RSA_HEADER = binascii.unhexlify( - "30" # sequence - "8204BD" # DER-encoded sequence contents length of 1213 bytes -- INCORRECT STATIC LENGTH - "020100" # integer: 0 -- Version - "30" # sequence - "0D" # DER-encoded sequence contents length of 13 bytes -- PrivateKeyAlgorithmIdentifier - "06092A864886F70D010101" # OID -- rsaEncryption - "0500" # NULL -- parameters -) -ASN1_SEQUENCE_ID = binascii.unhexlify("30") -RSA_ENCRYPTION_ASN1_OID = "1.2.840.113549.1.1.1" - -# Functions gcd and rsa_recover_prime_factors were copied from cryptography 1.9 -# to enable pure python rsa module to be in compliance with section 6.3.1 of RFC7518 -# which requires only private exponent (d) for private key. - - -def _gcd(a, b): - """Calculate the Greatest Common Divisor of a and b. - - Unless b==0, the result will have the same sign as b (so that when - b is divided by it, the result comes out positive). - """ - while b: - a, b = b, (a % b) - return a - - -# Controls the number of iterations rsa_recover_prime_factors will perform -# to obtain the prime factors. Each iteration increments by 2 so the actual -# maximum attempts is half this number. -_MAX_RECOVERY_ATTEMPTS = 1000 - - -def _rsa_recover_prime_factors(n, e, d): - """ - Compute factors p and q from the private exponent d. We assume that n has - no more than two factors. This function is adapted from code in PyCrypto. - """ - # See 8.2.2(i) in Handbook of Applied Cryptography. - ktot = d * e - 1 - # The quantity d*e-1 is a multiple of phi(n), even, - # and can be represented as t*2^s. - t = ktot - while t % 2 == 0: - t = t // 2 - # Cycle through all multiplicative inverses in Zn. - # The algorithm is non-deterministic, but there is a 50% chance - # any candidate a leads to successful factoring. - # See "Digitalized Signatures and Public Key Functions as Intractable - # as Factorization", M. Rabin, 1979 - spotted = False - a = 2 - while not spotted and a < _MAX_RECOVERY_ATTEMPTS: - k = t - # Cycle through all values a^{t*2^i}=a^k - while k < ktot: - cand = pow(a, k, n) - # Check if a^k is a non-trivial root of unity (mod n) - if cand != 1 and cand != (n - 1) and pow(cand, 2, n) == 1: - # We have found a number such that (cand-1)(cand+1)=0 (mod n). - # Either of the terms divides n. - p = _gcd(cand + 1, n) - spotted = True - break - k *= 2 - # This value was not any good... let's try another! - a += 2 - if not spotted: - raise ValueError("Unable to compute factors p and q from exponent d.") - # Found ! - q, r = divmod(n, p) - assert r == 0 - p, q = sorted((p, q), reverse=True) - return (p, q) - - -def pem_to_spki(pem, fmt="PKCS8"): - key = RSAKey(pem, ALGORITHMS.RS256) - return key.to_pem(fmt) - - -def _legacy_private_key_pkcs8_to_pkcs1(pkcs8_key): - """Legacy RSA private key PKCS8-to-PKCS1 conversion. - - .. warning:: - - This is incorrect parsing and only works because the legacy PKCS1-to-PKCS8 - encoding was also incorrect. - """ - # Only allow this processing if the prefix matches - # AND the following byte indicates an ASN1 sequence, - # as we would expect with the legacy encoding. - if not pkcs8_key.startswith(LEGACY_INVALID_PKCS8_RSA_HEADER + ASN1_SEQUENCE_ID): - raise ValueError("Invalid private key encoding") - - return pkcs8_key[len(LEGACY_INVALID_PKCS8_RSA_HEADER) :] - - -class RSAKey(Key): - SHA256 = "SHA-256" - SHA384 = "SHA-384" - SHA512 = "SHA-512" - - def __init__(self, key, algorithm): - if algorithm not in ALGORITHMS.RSA: - raise JWKError("hash_alg: %s is not a valid hash algorithm" % algorithm) - - if algorithm in ALGORITHMS.RSA_KW and algorithm != ALGORITHMS.RSA1_5: - raise JWKError("alg: %s is not supported by the RSA backend" % algorithm) - - self.hash_alg = { - ALGORITHMS.RS256: self.SHA256, - ALGORITHMS.RS384: self.SHA384, - ALGORITHMS.RS512: self.SHA512, - }.get(algorithm) - self._algorithm = algorithm - - if isinstance(key, dict): - self._prepared_key = self._process_jwk(key) - return - - if isinstance(key, (pyrsa.PublicKey, pyrsa.PrivateKey)): - self._prepared_key = key - return - - if isinstance(key, str): - key = key.encode("utf-8") - - if isinstance(key, bytes): - try: - self._prepared_key = pyrsa.PublicKey.load_pkcs1(key) - except ValueError: - try: - self._prepared_key = pyrsa.PublicKey.load_pkcs1_openssl_pem(key) - except ValueError: - try: - self._prepared_key = pyrsa.PrivateKey.load_pkcs1(key) - except ValueError: - try: - der = pyrsa_pem.load_pem(key, b"PRIVATE KEY") - try: - pkcs1_key = rsa_private_key_pkcs8_to_pkcs1(der) - except PyAsn1Error: - # If the key was encoded using the old, invalid, - # encoding then pyasn1 will throw an error attempting - # to parse the key. - pkcs1_key = _legacy_private_key_pkcs8_to_pkcs1(der) - self._prepared_key = pyrsa.PrivateKey.load_pkcs1(pkcs1_key, format="DER") - except ValueError as e: - raise JWKError(e) - return - raise JWKError("Unable to parse an RSA_JWK from key: %s" % key) - - def _process_jwk(self, jwk_dict): - if not jwk_dict.get("kty") == "RSA": - raise JWKError("Incorrect key type. Expected: 'RSA', Received: %s" % jwk_dict.get("kty")) - - e = base64_to_long(jwk_dict.get("e")) - n = base64_to_long(jwk_dict.get("n")) - - if "d" not in jwk_dict: - return pyrsa.PublicKey(e=e, n=n) - else: - d = base64_to_long(jwk_dict.get("d")) - extra_params = ["p", "q", "dp", "dq", "qi"] - - if any(k in jwk_dict for k in extra_params): - # Precomputed private key parameters are available. - if not all(k in jwk_dict for k in extra_params): - # These values must be present when 'p' is according to - # Section 6.3.2 of RFC7518, so if they are not we raise - # an error. - raise JWKError("Precomputed private key parameters are incomplete.") - - p = base64_to_long(jwk_dict["p"]) - q = base64_to_long(jwk_dict["q"]) - return pyrsa.PrivateKey(e=e, n=n, d=d, p=p, q=q) - else: - p, q = _rsa_recover_prime_factors(n, e, d) - return pyrsa.PrivateKey(n=n, e=e, d=d, p=p, q=q) - - def sign(self, msg): - return pyrsa.sign(msg, self._prepared_key, self.hash_alg) - - def verify(self, msg, sig): - if not self.is_public(): - warnings.warn("Attempting to verify a message with a private key. " "This is not recommended.") - try: - pyrsa.verify(msg, sig, self._prepared_key) - return True - except pyrsa.pkcs1.VerificationError: - return False - - def is_public(self): - return isinstance(self._prepared_key, pyrsa.PublicKey) - - def public_key(self): - if isinstance(self._prepared_key, pyrsa.PublicKey): - return self - return self.__class__(pyrsa.PublicKey(n=self._prepared_key.n, e=self._prepared_key.e), self._algorithm) - - def to_pem(self, pem_format="PKCS8"): - if isinstance(self._prepared_key, pyrsa.PrivateKey): - der = self._prepared_key.save_pkcs1(format="DER") - if pem_format == "PKCS8": - pkcs8_der = rsa_private_key_pkcs1_to_pkcs8(der) - pem = pyrsa_pem.save_pem(pkcs8_der, pem_marker="PRIVATE KEY") - elif pem_format == "PKCS1": - pem = pyrsa_pem.save_pem(der, pem_marker="RSA PRIVATE KEY") - else: - raise ValueError(f"Invalid pem format specified: {pem_format!r}") - else: - if pem_format == "PKCS8": - pkcs1_der = self._prepared_key.save_pkcs1(format="DER") - pkcs8_der = rsa_public_key_pkcs1_to_pkcs8(pkcs1_der) - pem = pyrsa_pem.save_pem(pkcs8_der, pem_marker="PUBLIC KEY") - elif pem_format == "PKCS1": - der = self._prepared_key.save_pkcs1(format="DER") - pem = pyrsa_pem.save_pem(der, pem_marker="RSA PUBLIC KEY") - else: - raise ValueError(f"Invalid pem format specified: {pem_format!r}") - return pem - - def to_dict(self): - if not self.is_public(): - public_key = self.public_key()._prepared_key - else: - public_key = self._prepared_key - - data = { - "alg": self._algorithm, - "kty": "RSA", - "n": long_to_base64(public_key.n).decode("ASCII"), - "e": long_to_base64(public_key.e).decode("ASCII"), - } - - if not self.is_public(): - data.update( - { - "d": long_to_base64(self._prepared_key.d).decode("ASCII"), - "p": long_to_base64(self._prepared_key.p).decode("ASCII"), - "q": long_to_base64(self._prepared_key.q).decode("ASCII"), - "dp": long_to_base64(self._prepared_key.exp1).decode("ASCII"), - "dq": long_to_base64(self._prepared_key.exp2).decode("ASCII"), - "qi": long_to_base64(self._prepared_key.coef).decode("ASCII"), - } - ) - - return data - - def wrap_key(self, key_data): - if not self.is_public(): - warnings.warn("Attempting to encrypt a message with a private key." " This is not recommended.") - wrapped_key = pyrsa.encrypt(key_data, self._prepared_key) - return wrapped_key - - def unwrap_key(self, wrapped_key): - try: - unwrapped_key = pyrsa.decrypt(wrapped_key, self._prepared_key) - except DecryptionError as e: - raise JWEError(e) - return unwrapped_key diff --git a/jose/jws.py b/jose/jws.py index 27f6b79..5337eb9 100644 --- a/jose/jws.py +++ b/jose/jws.py @@ -1,17 +1,17 @@ import binascii import json -try: - from collections.abc import Iterable, Mapping -except ImportError: - from collections import Mapping, Iterable - from jose import jwk from jose.backends.base import Key from jose.constants import ALGORITHMS from jose.exceptions import JWSError, JWSSignatureError from jose.utils import base64url_decode, base64url_encode +try: + from collections.abc import Iterable, Mapping +except ImportError: + from collections import Iterable, Mapping + def sign(payload, key, headers=None, algorithm=ALGORITHMS.HS256): """Signs a claims set and returns a JWS string. diff --git a/jose/jwt.py b/jose/jwt.py index f47e4dd..ff0d19e 100644 --- a/jose/jwt.py +++ b/jose/jwt.py @@ -2,6 +2,12 @@ from calendar import timegm from datetime import datetime, timedelta +from jose import jws + +from .constants import ALGORITHMS +from .exceptions import ExpiredSignatureError, JWSError, JWTClaimsError, JWTError +from .utils import calculate_at_hash, timedelta_total_seconds + try: from collections.abc import Mapping except ImportError: @@ -14,12 +20,6 @@ UTC = timezone.utc # Preferred in Python 3.12 and below -from jose import jws - -from .constants import ALGORITHMS -from .exceptions import ExpiredSignatureError, JWSError, JWTClaimsError, JWTError -from .utils import calculate_at_hash, timedelta_total_seconds - def encode(claims, key, algorithm=ALGORITHMS.HS256, headers=None, access_token=None): """Encodes a claims set and returns a JWT string. diff --git a/jose/utils.py b/jose/utils.py index d62cafb..943001b 100644 --- a/jose/utils.py +++ b/jose/utils.py @@ -2,25 +2,13 @@ import re import struct -# Piggyback of the backends implementation of the function that converts a long -# to a bytes stream. Some plumbing is necessary to have the signatures match. -try: - from cryptography.utils import int_to_bytes as _long_to_bytes - - def long_to_bytes(n, blocksize=0): - return _long_to_bytes(n, blocksize or None) - -except ImportError: - from ecdsa.ecdsa import int_to_string as _long_to_bytes - - def long_to_bytes(n, blocksize=0): - ret = _long_to_bytes(n) - if blocksize == 0: - return ret - else: - assert len(ret) <= blocksize - padding = blocksize - len(ret) - return b"\x00" * padding + ret +from cryptography.utils import int_to_bytes as _long_to_bytes + + +def long_to_bytes(n, blocksize=0): + # Piggyback of the backends implementation of the function that converts a long + # to a bytes stream. Some plumbing is necessary to have the signatures match. + return _long_to_bytes(n, blocksize or None) def long_to_base64(data, size=0): diff --git a/pytest.ini b/pytest.ini index 03589cf..86138b5 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,7 +1,5 @@ [pytest] markers = pycrypto: marks tests as applicable with PyCrypto backend - pycryptodome: marks tests as applicable with PyCryptodome backend - ecdsa: marks tests as applicable with ecdsa backend cryptography: marks tests as applicable with cryptography backend backend_compatibility: mark tests as testing compatibility between backends diff --git a/requirements-dev.txt b/requirements-dev.txt index 1520217..c500c99 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -2,8 +2,7 @@ PyYAML==5.4.1 cov-core==1.15.0 coverage==7.8.2 coveralls==4.0.1 -cryptography==45.0.3 docopt==0.6.2 pytest==8.3.5 pytest-cov==6.1.1 --r requirements.txt \ No newline at end of file +-r requirements.txt diff --git a/requirements.txt b/requirements.txt index 7bc375f..0d38bc5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1 @@ -pycryptodome -rsa -ecdsa != 0.15 -pyasn1 +cryptography diff --git a/setup.cfg b/setup.cfg index e4e3d19..3b66ce2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -34,20 +34,14 @@ classifiers = packages = find: python_requires = >=3.9 install_requires = - ecdsa != 0.15 - rsa >=4.0, <5.0, !=4.4, !=4.1.1 - pyasn1 >=0.5.0 + cryptography >=3.4.0 [options.extras_require] test = pytest pytest-cov -cryptography = - cryptography >=3.4.0 pycrypto = pycrypto >=2.6.0, <2.7.0 -pycryptodome = - pycryptodome >=3.3.1, <4.0.0 [options.packages.find] exclude = diff --git a/tests/algorithms/test_AES_compat.py b/tests/algorithms/test_AES_compat.py index 4f05bed..5ed6052 100644 --- a/tests/algorithms/test_AES_compat.py +++ b/tests/algorithms/test_AES_compat.py @@ -1,12 +1,13 @@ import pytest +from jose.constants import ALGORITHMS +from jose.exceptions import JWEError + try: from jose.backends.cryptography_backend import CryptographyAESKey except ImportError: CryptographyAESKey = None -from jose.constants import ALGORITHMS -from jose.exceptions import JWEError CRYPTO_BACKENDS = (pytest.param(CryptographyAESKey, id="pyca/cryptography"),) diff --git a/tests/algorithms/test_EC.py b/tests/algorithms/test_EC.py index d8602a2..64977d3 100644 --- a/tests/algorithms/test_EC.py +++ b/tests/algorithms/test_EC.py @@ -2,30 +2,17 @@ import json import re +import pytest +from cryptography.hazmat.backends import default_backend as CryptographyBackend +from cryptography.hazmat.primitives import hashes, hmac, serialization +from cryptography.hazmat.primitives.asymmetric import ec as CryptographyEc + from jose import jwt from jose.backends import ECKey +from jose.backends.cryptography_backend import CryptographyECKey from jose.constants import ALGORITHMS from jose.exceptions import JOSEError, JWKError -try: - import ecdsa - - from jose.backends.ecdsa_backend import ECDSAECKey -except ImportError: - ECDSAECKey = ecdsa = None - -try: - from cryptography.hazmat.backends import default_backend as CryptographyBackend - from cryptography.hazmat.primitives import hashes, hmac, serialization - from cryptography.hazmat.primitives.asymmetric import ec as CryptographyEc - - from jose.backends.cryptography_backend import CryptographyECKey - -except ImportError: - CryptographyECKey = CryptographyEc = CryptographyBackend = None - -import pytest - private_key = """-----BEGIN EC PRIVATE KEY----- MHcCAQEEIOiSs10XnBlfykk5zsJRmzYybKdMlGniSJcssDvUcF6DoAoGCCqGSM49 AwEHoUQDQgAE7gb4edKJ7ul9IgomCdcOebQTZ8qktqtBfRKboa71CfEKzBruUi+D @@ -81,22 +68,10 @@ def normalize_pem(key_pem_str): def _backend_exception_types(): """Build the backend exception types based on available backends.""" - if None not in (ECDSAECKey, ecdsa): - yield ECDSAECKey, ecdsa.BadDigestError - - if CryptographyECKey is not None: - yield CryptographyECKey, TypeError - - -@pytest.mark.ecdsa -@pytest.mark.skipif(None in (ECDSAECKey, ecdsa), reason="python-ecdsa backend not available") -def test_key_from_ecdsa(): - key = ecdsa.SigningKey.from_pem(private_key) - assert not ECKey(key, ALGORITHMS.ES256).is_public() + yield CryptographyECKey, TypeError @pytest.mark.cryptography -@pytest.mark.skipif(CryptographyECKey is None, reason="pyca/cryptography backend not available") @pytest.mark.parametrize( "algorithm, expected_length", ((ALGORITHMS.ES256, 32), (ALGORITHMS.ES384, 48), (ALGORITHMS.ES512, 66)) ) @@ -114,14 +89,12 @@ def test_cryptography_sig_component_length(algorithm, expected_length): @pytest.mark.cryptography -@pytest.mark.skipif(CryptographyECKey is None, reason="pyca/cryptography backend not available") def test_cryptograhy_der_to_raw(): key = CryptographyECKey(private_key, ALGORITHMS.ES256) assert key._der_to_raw(DER_SIGNATURE) == RAW_SIGNATURE @pytest.mark.cryptography -@pytest.mark.skipif(CryptographyECKey is None, reason="pyca/cryptography backend not available") def test_cryptograhy_raw_to_der(): key = CryptographyECKey(private_key, ALGORITHMS.ES256) assert key._raw_to_der(RAW_SIGNATURE) == DER_SIGNATURE @@ -230,12 +203,11 @@ def test_to_dict(self): @pytest.mark.cryptography -@pytest.mark.skipif(CryptographyECKey is None, reason="pyca/cryptography backend not available") def test_incorrect_public_key_hmac_signing(): def b64(x): return base64.urlsafe_b64encode(x).replace(b"=", b"") - KEY = CryptographyEc.generate_private_key(CryptographyEc.SECP256R1) + KEY = CryptographyEc.generate_private_key(CryptographyEc.SECP256R1()) PUBKEY = KEY.public_key().public_bytes( encoding=serialization.Encoding.OpenSSH, format=serialization.PublicFormat.OpenSSH, diff --git a/tests/algorithms/test_EC_compat.py b/tests/algorithms/test_EC_compat.py deleted file mode 100644 index 1bb7373..0000000 --- a/tests/algorithms/test_EC_compat.py +++ /dev/null @@ -1,71 +0,0 @@ -import pytest - -try: - from jose.backends.cryptography_backend import CryptographyECKey - from jose.backends.ecdsa_backend import ECDSAECKey -except ImportError: - ECDSAECKey = CryptographyECKey = None -from jose.constants import ALGORITHMS - -from .test_EC import get_pem_for_key, normalize_pem, private_key - - -@pytest.mark.backend_compatibility -@pytest.mark.skipif( - None in (ECDSAECKey, CryptographyECKey), - reason="Multiple crypto backends not available for backend compatibility tests", -) -class TestBackendEcdsaCompatibility: - @pytest.mark.parametrize("BackendSign", [ECDSAECKey, CryptographyECKey]) - @pytest.mark.parametrize("BackendVerify", [ECDSAECKey, CryptographyECKey]) - def test_signing_parity(self, BackendSign, BackendVerify): - key_sign = BackendSign(private_key, ALGORITHMS.ES256) - key_verify = BackendVerify(private_key, ALGORITHMS.ES256).public_key() - - msg = b"test" - sig = key_sign.sign(msg) - - # valid signature - assert key_verify.verify(msg, sig) - - # invalid signature - assert not key_verify.verify(msg, b"n" * 64) - - @pytest.mark.parametrize("BackendFrom", [ECDSAECKey, CryptographyECKey]) - @pytest.mark.parametrize("BackendTo", [ECDSAECKey, CryptographyECKey]) - def test_public_key_to_pem(self, BackendFrom, BackendTo): - key = BackendFrom(private_key, ALGORITHMS.ES256) - key2 = BackendTo(private_key, ALGORITHMS.ES256) - - assert normalize_pem(get_pem_for_key(key.public_key())) == normalize_pem(get_pem_for_key(key2.public_key())) - - @pytest.mark.parametrize("BackendFrom", [ECDSAECKey, CryptographyECKey]) - @pytest.mark.parametrize("BackendTo", [ECDSAECKey, CryptographyECKey]) - def test_private_key_to_pem(self, BackendFrom, BackendTo): - key = BackendFrom(private_key, ALGORITHMS.ES256) - key2 = BackendTo(private_key, ALGORITHMS.ES256) - - assert normalize_pem(get_pem_for_key(key)) == normalize_pem(get_pem_for_key(key2)) - - @pytest.mark.parametrize("BackendFrom", [ECDSAECKey, CryptographyECKey]) - @pytest.mark.parametrize("BackendTo", [ECDSAECKey, CryptographyECKey]) - def test_public_key_load_cycle(self, BackendFrom, BackendTo): - key = BackendFrom(private_key, ALGORITHMS.ES256) - pubkey = key.public_key() - - pub_pem_source = normalize_pem(get_pem_for_key(pubkey)) - - pub_target = BackendTo(pub_pem_source, ALGORITHMS.ES256) - - assert pub_pem_source == normalize_pem(get_pem_for_key(pub_target)) - - @pytest.mark.parametrize("BackendFrom", [ECDSAECKey, CryptographyECKey]) - @pytest.mark.parametrize("BackendTo", [ECDSAECKey, CryptographyECKey]) - def test_private_key_load_cycle(self, BackendFrom, BackendTo): - key = BackendFrom(private_key, ALGORITHMS.ES256) - - pem_source = normalize_pem(get_pem_for_key(key)) - - target = BackendTo(pem_source, ALGORITHMS.ES256) - - assert pem_source == normalize_pem(get_pem_for_key(target)) diff --git a/tests/algorithms/test_HMAC.py b/tests/algorithms/test_HMAC.py deleted file mode 100644 index 2b0859e..0000000 --- a/tests/algorithms/test_HMAC.py +++ /dev/null @@ -1,48 +0,0 @@ -import json - -import pytest - -from jose.backends.native import HMACKey -from jose.constants import ALGORITHMS -from jose.exceptions import JOSEError - - -class TestHMACAlgorithm: - def test_non_string_key(self): - with pytest.raises(JOSEError): - HMACKey(object(), ALGORITHMS.HS256) - - def test_RSA_key(self): - key = "-----BEGIN PUBLIC KEY-----" - with pytest.raises(JOSEError): - HMACKey(key, ALGORITHMS.HS256) - - key = "-----BEGIN RSA PUBLIC KEY-----" - with pytest.raises(JOSEError): - HMACKey(key, ALGORITHMS.HS256) - - key = "-----BEGIN CERTIFICATE-----" - with pytest.raises(JOSEError): - HMACKey(key, ALGORITHMS.HS256) - - key = "ssh-rsa" - with pytest.raises(JOSEError): - HMACKey(key, ALGORITHMS.HS256) - - def test_to_dict(self): - passphrase = "The quick brown fox jumps over the lazy dog" - encoded = "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw" - key = HMACKey(passphrase, ALGORITHMS.HS256) - - as_dict = key.to_dict() - assert "alg" in as_dict - assert as_dict["alg"] == ALGORITHMS.HS256 - - assert "kty" in as_dict - assert as_dict["kty"] == "oct" - - assert "k" in as_dict - assert as_dict["k"] == encoded - - # as_dict should be serializable to JSON - json.dumps(as_dict) diff --git a/tests/algorithms/test_HMAC_compat.py b/tests/algorithms/test_HMAC_compat.py index f2fb899..30bdbfc 100644 --- a/tests/algorithms/test_HMAC_compat.py +++ b/tests/algorithms/test_HMAC_compat.py @@ -1,17 +1,14 @@ import pytest +from jose.constants import ALGORITHMS + try: from jose.backends.cryptography_backend import CryptographyHMACKey except ImportError: CryptographyHMACKey = None -from jose.backends.native import HMACKey -from jose.constants import ALGORITHMS -CRYPTO_BACKENDS = ( - pytest.param(CryptographyHMACKey, id="pyca/cryptography"), - pytest.param(HMACKey, id="native"), -) +CRYPTO_BACKENDS = (pytest.param(CryptographyHMACKey, id="pyca/cryptography"),) SUPPORTED_ALGORITHMS = ALGORITHMS.HMAC diff --git a/tests/algorithms/test_RSA.py b/tests/algorithms/test_RSA.py index b181993..58d4929 100644 --- a/tests/algorithms/test_RSA.py +++ b/tests/algorithms/test_RSA.py @@ -1,31 +1,19 @@ import base64 import json -try: - from jose.backends import rsa_backend - from jose.backends.rsa_backend import RSAKey as PurePythonRSAKey -except ImportError: - PurePythonRSAKey = rsa_backend = None - -try: - from Crypto.PublicKey import RSA as PyCryptoRSA -except ImportError: - PyCryptoRSA = None - -try: - from cryptography.hazmat.backends import default_backend - from cryptography.hazmat.primitives.asymmetric import rsa as pyca_rsa - - from jose.backends.cryptography_backend import CryptographyRSAKey -except ImportError: - default_backend = pyca_rsa = CryptographyRSAKey = None - import pytest +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives.asymmetric import rsa as pyca_rsa from jose.backends import RSAKey +from jose.backends.cryptography_backend import CryptographyRSAKey from jose.constants import ALGORITHMS from jose.exceptions import JOSEError, JWKError +PurePythonRSAKey = rsa_backend = None +PyCryptoRSA = None + + private_key_4096_pkcs1 = b"""-----BEGIN RSA PRIVATE KEY----- MIIJKwIBAAKCAgEAtSKfSeI0fukRIX38AHlKB1YPpX8PUYN2JdvfM+XjNmLfU1M7 4N0VmdzIX95sneQGO9kC2xMIE+AIlt52Yf/KgBZggAlS9Y0Vx8DsSL2HvOjguAdX diff --git a/tests/algorithms/test_RSA_compat.py b/tests/algorithms/test_RSA_compat.py deleted file mode 100644 index 02eb3f6..0000000 --- a/tests/algorithms/test_RSA_compat.py +++ /dev/null @@ -1,124 +0,0 @@ -import pytest - -try: - from jose.backends.cryptography_backend import CryptographyRSAKey - from jose.backends.rsa_backend import RSAKey as PurePythonRSAKey -except ImportError: - PurePythonRSAKey = CryptographyRSAKey = None -from jose.constants import ALGORITHMS -from jose.exceptions import JWEError - -from .test_RSA import PRIVATE_KEYS - -CRYPTO_BACKENDS = ( - pytest.param(PurePythonRSAKey, id="python_rsa"), - pytest.param(CryptographyRSAKey, id="pyca/cryptography"), -) -ENCODINGS = ("PKCS1", "PKCS8") - - -@pytest.mark.backend_compatibility -@pytest.mark.skipif( - None in (PurePythonRSAKey, CryptographyRSAKey), - reason="Multiple crypto backends not available for backend compatibility tests", -) -class TestBackendRsaCompatibility: - @pytest.mark.parametrize("BackendSign", CRYPTO_BACKENDS) - @pytest.mark.parametrize("BackendVerify", CRYPTO_BACKENDS) - @pytest.mark.parametrize("private_key", PRIVATE_KEYS) - def test_signing_parity(self, BackendSign, BackendVerify, private_key): - key_sign = BackendSign(private_key, ALGORITHMS.RS256) - key_verify = BackendVerify(private_key, ALGORITHMS.RS256).public_key() - - msg = b"test" - sig = key_sign.sign(msg) - - # valid signature - assert key_verify.verify(msg, sig) - - # invalid signature - assert not key_verify.verify(msg, b"n" * 64) - - @pytest.mark.parametrize("encoding", ENCODINGS) - @pytest.mark.parametrize("BackendFrom", CRYPTO_BACKENDS) - @pytest.mark.parametrize("BackendTo", CRYPTO_BACKENDS) - @pytest.mark.parametrize("private_key", PRIVATE_KEYS) - def test_public_key_to_pem(self, BackendFrom, BackendTo, encoding, private_key): - key = BackendFrom(private_key, ALGORITHMS.RS256) - key2 = BackendTo(private_key, ALGORITHMS.RS256) - - key1_pem = key.public_key().to_pem(pem_format=encoding).strip() - key2_pem = key2.public_key().to_pem(pem_format=encoding).strip() - assert key1_pem == key2_pem - - @pytest.mark.parametrize("encoding", ENCODINGS) - @pytest.mark.parametrize("BackendFrom", CRYPTO_BACKENDS) - @pytest.mark.parametrize("BackendTo", CRYPTO_BACKENDS) - @pytest.mark.parametrize("private_key", PRIVATE_KEYS) - def test_private_key_to_pem(self, BackendFrom, BackendTo, encoding, private_key): - key = BackendFrom(private_key, ALGORITHMS.RS256) - key2 = BackendTo(private_key, ALGORITHMS.RS256) - - key1_pem = key.to_pem(pem_format=encoding).strip() - key2_pem = key2.to_pem(pem_format=encoding).strip() - - import base64 - - a = base64.b64decode(key1_pem[key1_pem.index(b"\n") : key1_pem.rindex(b"\n")]) - b = base64.b64decode(key2_pem[key2_pem.index(b"\n") : key2_pem.rindex(b"\n")]) - assert a == b - - assert key1_pem == key2_pem - - @pytest.mark.parametrize("encoding_save", ENCODINGS) - @pytest.mark.parametrize("encoding_load", ENCODINGS) - @pytest.mark.parametrize("BackendFrom", CRYPTO_BACKENDS) - @pytest.mark.parametrize("BackendTo", CRYPTO_BACKENDS) - @pytest.mark.parametrize("private_key", PRIVATE_KEYS) - def test_public_key_load_cycle(self, BackendFrom, BackendTo, encoding_save, encoding_load, private_key): - key = BackendFrom(private_key, ALGORITHMS.RS256) - - pem_pub_reference = key.public_key().to_pem(pem_format=encoding_save).strip() - pem_pub_load = key.public_key().to_pem(pem_format=encoding_load).strip() - - pubkey_2 = BackendTo(pem_pub_load, ALGORITHMS.RS256) - - assert pem_pub_reference == pubkey_2.to_pem(encoding_save).strip() - - @pytest.mark.parametrize("encoding_save", ENCODINGS) - @pytest.mark.parametrize("encoding_load", ENCODINGS) - @pytest.mark.parametrize("BackendFrom", CRYPTO_BACKENDS) - @pytest.mark.parametrize("BackendTo", CRYPTO_BACKENDS) - @pytest.mark.parametrize("private_key", PRIVATE_KEYS) - def test_private_key_load_cycle(self, BackendFrom, BackendTo, encoding_save, encoding_load, private_key): - key = BackendFrom(private_key, ALGORITHMS.RS256) - - pem_reference = key.to_pem(pem_format=encoding_save).strip() - pem_load = key.to_pem(pem_format=encoding_load).strip() - - key_2 = BackendTo(pem_load, ALGORITHMS.RS256) - - assert pem_reference == key_2.to_pem(encoding_save).strip() - - @pytest.mark.parametrize("backend_wrap", CRYPTO_BACKENDS) - @pytest.mark.parametrize("backend_unwrap", CRYPTO_BACKENDS) - @pytest.mark.parametrize("algorithm", filter(lambda x: x in ALGORITHMS.SUPPORTED, ALGORITHMS.RSA_KW)) - @pytest.mark.parametrize("private_key", PRIVATE_KEYS) - def test_key_wrap_parity(self, backend_wrap, backend_unwrap, private_key, algorithm): - if algorithm in (ALGORITHMS.RSA_OAEP, ALGORITHMS.RSA_OAEP_256) and PurePythonRSAKey in ( - backend_wrap, - backend_unwrap, - ): - pytest.skip("Pure RSA does not support OAEP") - key_wrap = backend_wrap(private_key, algorithm).public_key() - key_unwrap = backend_unwrap(private_key, algorithm) - - unwrapped_key = b"test" - wrapped_key = key_wrap.wrap_key(unwrapped_key) - - # verify unwrap to original key - actual = key_unwrap.unwrap_key(wrapped_key) - assert actual == unwrapped_key - - with pytest.raises(JWEError): - key_unwrap.unwrap_key(b"n" * 64) diff --git a/tests/test_asn1.py b/tests/test_asn1.py deleted file mode 100644 index 6e1b103..0000000 --- a/tests/test_asn1.py +++ /dev/null @@ -1,168 +0,0 @@ -"""Tests for ``jose.backends._asn1``.""" - -import base64 - -import pytest - -try: - from jose.backends import _asn1 -except ImportError: - _asn1 = None - -pytestmark = [ - pytest.mark.pycrypto, - pytest.mark.pycryptodome, - pytest.mark.skipif(_asn1 is None, reason="ASN1 backend not available"), -] - -PKCS1_PRIVATE_KEY = b"""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 -pskDwcHyZmbKZuk+NU/FJ8IAcmvk9y7m25nSSc8=""" -PKCS8_PRIVATE_KEY = b"""MIIJRQIBADANBgkqhkiG9w0BAQEFAASCCS8wggkrAg -EAAoICAQC1Ip9J4jR+6REhffwAeUoHVg+lfw9Rg3Yl298z5eM2Yt9TUzvg3RWZ3Mh -f3myd5AY72QLbEwgT4AiW3nZh/8qAFmCACVL1jRXHwOxIvYe86OC4B1eKve9gu8DL -IeKf+ZSKwk6pxwUoKEcqOeTS5fL/fz7WvXv9xhOnvpusqlTUjiQzbQz/91u8sFaN/ -PLD16G5+vvhpXCv7TEl+wFWvahpAXDD9ccbPr1DvIcroNvFg7jDirawh9wLilPxdM -8ejglnYzvfcRrxrsfG1lLnrf+aGFFktTlrKdCnBpW/ZT74IvoS8a3xOU1k6R5G/zD -P5G3wHvSPOhGlUrfqtH6kcqVeEwZPrX2/zYud5N2yoDblaLYt39zSeK66XbgDr5pq -Pa71JZvED0uZW0We281tFodcTVIuks+giny6mkYXTY3Pa/eV3yAKdNNZ+r8FDzRDL -x7RyhFEAEQJmuMgzMcSDoyWhEPLidrSlFC/rVWiCpEoNTavf9V7MU9ANvdyJixmNY -Gvcek06RSNOu8uYXI4T1DlH+pAaZzoFi1+TmufuIh9eWQkBkdeEHL4SUG6IEz5hKQ -Uzt0f3axIihnTlXgw/T1dymnHuR3CwnLvZK6y30ed30HtXflidUXpbv3WVUed6eEI -k7OJGaQfjEsts+RpJPYqzcgcfu1WW0zo2xGTeM11UwIDAQABAoICAQCvH7QleKAMe -0VmvilgQ+PAyAIEnZ2vz4vw+m0H/iD4ugr0wqpHszhpM74+ggs9c/Ja9mr+Z54NqT -Jkutx1zxp3UCsoXttoMGl4YRuRED0l9XkeM2r95riDK8MwIai7xk9JAKeRGavwsVB -skOCczllrkBivAS/ZoSHrsitMerndbzesyKgi6YI2Ib7tNDt6AQaJixDvjkYusQIt -mEopFOC39P6TVxKTUsslrCZ7leAlTD7cP0Z10kR6m9igv7Bn+Cc7YAPI+c91DuhzY -09wUxemYhB/b37+DsV+Vq19CGQi/54LpsHnUOF0yLXPKnaE6HMqDqOfviLFVf4e2z -b9wyIA+7Q1paZT2fyDseZdZzj65I79+ywaMjcMtgp3nGeGurhrdk7mgkt4w71TvX5 -iRy2Dbc9MfA0Aauw9h2MZwGckR21tdrfVqAjk8AbquAZRjeJKhaocuAFPGtfThNZW -8tCLzBvGHsEQXTJqw55krF0TYhZiDvnMayOW7IeU6elFrmVJ/opnq2GH15zMUdi1L -gO9gFxn2Flb+DzuJ7bxKQEXHGXRuti324o1gm+5qNUbyRPzQALtbODartaaejDDck -0lRs22Gzl2orJRKUwUwgH+8548UcusDqoN8CexAeBRv1YeFStiwfbRcqmpHY6OZZT -TltXilIxb8nAbfuj6nHXIoQKCAQEA2fDHR3uSwQy8mN43mmUPLd4wuJ8Ej7pIv+jl -NcOgUzqsnEX7RQ/vS6i5gmrZ0j4MJIDpz8SyftYA6KtjXvtp0pwFXxuLAC2YoecWc -Kz+TABmryUy9A+008n++rafjSQ7jyHx/Ft4ZybICoU9nhgpLGs8Dhx1sci4VK31bJ -pRxUWbN1MUdWVE/vQRd2Zce3zhvJY0HgId6rzOas3E80CLrmcSRSSzuIFAy26/JXb -5peMM5MEwJ3roiEiHaJmrU7uRdTCCHU4f2EJruiuIfIB2CQp0oJahyZs88Q04ROCO -toD3BEhfA5hMNvVfmEy+LWYIejzGMyO7izMPTfElWLtauwKCAQEA1MRuUKTETKEmp -oxkXdTd1/Ziv9m0eqytOviXAH8w4qTOa5KtE7GF4SQoizFuJX4DFO+09HUVCuWpuF -MlHDL6nDgo8ns/h+rZH9gGq9dI0EJqZO3qXZd1eqcCTcD75w4RnaVT25t+B143YiH -yuB2fUtZ45KMJZ8TGaShTrX9d7rEAx5cpBKUcpKdf2+jG4TbEg0d5TpkArLWQaasm -0SJacjxvwLizx9PIO1cnUtqzKzSIc7ZPPaNSF/ggnBvzsuTsjN7Yysj6g/NUNeWaR -uOQHi1jKiJFOPyuTynAFb8dW/TTamM4tNriTPWRDeWxn/cCzjN82LuZYSFp2YSwAh -9iSQKCAQEA07f2v9iqPmRTKuZledXHIzYAernwQngjiFuFn+9FZtVPLhlF+ld6jjO -A09Nde8LPJP8fJLne9RyMkXs9892ldfYfub8WD7w71LybriZAfDC/kFJA55wCfOR0 -kbar1P9n5KqDXDa4Bu1D1ASQUW8DQKWEF+lJ5MYrXAgKrdBUs6HlIZ/R9iHFHzCXb -zi/iy9MVFpOkwBXc9yrYlI3jGAd0fVLaGQ5O3pFKduSUaD88UxbEDckwpXCI/LhqJ -nwOE21BvVy0Wk9wmb0JE16MAjTogDIP+FVlnb6gunmyfKkcWNjlIOB2govsAw7K8c -R0SSYA7fai3+hB584tO0Y7NRFVQKCAQEAtLHmJiLoe1PZKVTN4Epp5YFgOCLB/fUs -YYrBYzCh6bmhfQZiavk3BA/bRutB6iFsqJkzCvFsX4bQbrReLea1o3qme9hkCiBPK -ZYjhEkfk470IKdVhQqeQP4ZHMOqq33tQ/8mirkxfTxsuLCwG+E8b/OpVDOKR1B8WM -5g0lX7Bj3YtakQstsuyj9DT9YPITGMDxniUffq9MJ11Of/3VjZi278UaYLOMgJoyv -PP7RwuV6yC+NmxKgxjh2yRCIRA3sb0j9nykCJUQqUF/nY3h9hQ9+z+hKEy3L87ko8 -0bsvSetQqWd+v0mtK0LaX/0Urw/umARuIPqhtjXUWWEwwXy50QKCAQEAgqG84W2Kk -PWOoM6mM6hH2wr+VFHltVVZB9qYwM3UDCaMltUdLyGG1hQz/iyHanXY/pGCg7x84N -/W8RxYWHfHE1kTWk8I9ogIP2LlT41G8/Pa8L3TQdytotGtQVpHPCGo46SdglIGsbN -b4LHdhJ5o4KCcrIjwHmDDgLQbSIvV1KzPZtHU7gY05V9PdHqOhkRcBMWj8pzYvRgC -8CkE1Ri9jSRuMn8l9M7nTrRKxZ+1/iLDeOuFKf1HBNNh2r+QSsYVa8MYmfYyK6Ex7 -aic19XMgZGZ1TEfCLW6WSykCf9+Zjt/SFmmyQPBwfJmZspm6T41T8UnwgBya+T3Lu -bbmdJJzw==""" -PKCS1_PUBLIC_KEY = b"""MIICCgKCAgEAtSKfSeI0fukRIX38AHlKB1YPpX8PUY -N2JdvfM+XjNmLfU1M74N0VmdzIX95sneQGO9kC2xMIE+AIlt52Yf/KgBZggAlS9Y0 -Vx8DsSL2HvOjguAdXir3vYLvAyyHin/mUisJOqccFKChHKjnk0uXy/38+1r17/cYT -p76brKpU1I4kM20M//dbvLBWjfzyw9ehufr74aVwr+0xJfsBVr2oaQFww/XHGz69Q -7yHK6DbxYO4w4q2sIfcC4pT8XTPHo4JZ2M733Ea8a7HxtZS563/mhhRZLU5aynQpw -aVv2U++CL6EvGt8TlNZOkeRv8wz+Rt8B70jzoRpVK36rR+pHKlXhMGT619v82LneT -dsqA25Wi2Ld/c0niuul24A6+aaj2u9SWbxA9LmVtFntvNbRaHXE1SLpLPoIp8uppG -F02Nz2v3ld8gCnTTWfq/BQ80Qy8e0coRRABECZrjIMzHEg6MloRDy4na0pRQv61Vo -gqRKDU2r3/VezFPQDb3ciYsZjWBr3HpNOkUjTrvLmFyOE9Q5R/qQGmc6BYtfk5rn7 -iIfXlkJAZHXhBy+ElBuiBM+YSkFM7dH92sSIoZ05V4MP09Xcppx7kdwsJy72Sust9 -Hnd9B7V35YnVF6W791lVHnenhCJOziRmkH4xLLbPkaST2Ks3IHH7tVltM6NsRk3jN -dVMCAwEAAQ==""" -PKCS8_PUBLIC_KEY = b"""MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAg -EAtSKfSeI0fukRIX38AHlKB1YPpX8PUYN2JdvfM+XjNmLfU1M74N0VmdzIX95sneQ -GO9kC2xMIE+AIlt52Yf/KgBZggAlS9Y0Vx8DsSL2HvOjguAdXir3vYLvAyyHin/mU -isJOqccFKChHKjnk0uXy/38+1r17/cYTp76brKpU1I4kM20M//dbvLBWjfzyw9ehu -fr74aVwr+0xJfsBVr2oaQFww/XHGz69Q7yHK6DbxYO4w4q2sIfcC4pT8XTPHo4JZ2 -M733Ea8a7HxtZS563/mhhRZLU5aynQpwaVv2U++CL6EvGt8TlNZOkeRv8wz+Rt8B7 -0jzoRpVK36rR+pHKlXhMGT619v82LneTdsqA25Wi2Ld/c0niuul24A6+aaj2u9SWb -xA9LmVtFntvNbRaHXE1SLpLPoIp8uppGF02Nz2v3ld8gCnTTWfq/BQ80Qy8e0coRR -ABECZrjIMzHEg6MloRDy4na0pRQv61VogqRKDU2r3/VezFPQDb3ciYsZjWBr3HpNO -kUjTrvLmFyOE9Q5R/qQGmc6BYtfk5rn7iIfXlkJAZHXhBy+ElBuiBM+YSkFM7dH92 -sSIoZ05V4MP09Xcppx7kdwsJy72Sust9Hnd9B7V35YnVF6W791lVHnenhCJOziRmk -H4xLLbPkaST2Ks3IHH7tVltM6NsRk3jNdVMCAwEAAQ==""" - - -def test_rsa_private_key_pkcs1_to_pkcs8(): - pkcs1 = base64.b64decode(PKCS1_PRIVATE_KEY) - pkcs8 = base64.b64decode(PKCS8_PRIVATE_KEY) - - assert _asn1.rsa_private_key_pkcs1_to_pkcs8(pkcs1) == pkcs8 - - -def test_rsa_private_key_pkcs8_to_pkcs1(): - pkcs1 = base64.b64decode(PKCS1_PRIVATE_KEY) - pkcs8 = base64.b64decode(PKCS8_PRIVATE_KEY) - - assert _asn1.rsa_private_key_pkcs8_to_pkcs1(pkcs8) == pkcs1 - - -def test_rsa_public_key_pkcs1_to_pkcs8(): - pkcs1 = base64.b64decode(PKCS1_PUBLIC_KEY) - pkcs8 = base64.b64decode(PKCS8_PUBLIC_KEY) - - assert _asn1.rsa_public_key_pkcs1_to_pkcs8(pkcs1) == pkcs8 - - -def test_rsa_public_key_pkcs8_to_pkcs1(): - pkcs1 = base64.b64decode(PKCS1_PUBLIC_KEY) - pkcs8 = base64.b64decode(PKCS8_PUBLIC_KEY) - - assert _asn1.rsa_public_key_pkcs8_to_pkcs1(pkcs8) == pkcs1 diff --git a/tests/test_backends.py b/tests/test_backends.py index 4ce71a7..15ff3d2 100644 --- a/tests/test_backends.py +++ b/tests/test_backends.py @@ -1,59 +1,25 @@ """Test the default import handling.""" -try: - from jose.backends.rsa_backend import RSAKey as PurePythonRSAKey -except ImportError: - PurePythonRSAKey = None -try: - from jose.backends.cryptography_backend import CryptographyECKey, CryptographyRSAKey -except ImportError: - CryptographyRSAKey = CryptographyECKey = None -try: - from jose.backends.ecdsa_backend import ECDSAECKey as PurePythonECDSAKey -except ImportError: - PurePythonRSAKey = None - -try: - from jose.backends.cryptography_backend import CryptographyAESKey -except ImportError: - CryptographyAESKey = None -try: - from jose.backends.cryptography_backend import CryptographyHMACKey -except ImportError: - CryptographyHMACKey = None - -from jose.backends import ECKey, HMACKey, RSAKey -from jose.backends.native import HMACKey as NativeHMACKey - -try: - from jose.backends import AESKey -except ImportError: - AESKey = None +from jose.backends import AESKey, ECKey, HMACKey, RSAKey +from jose.backends.cryptography_backend import ( + CryptographyAESKey, + CryptographyECKey, + CryptographyHMACKey, + CryptographyRSAKey, +) def test_default_ec_backend(): - if CryptographyECKey is not None: - assert ECKey is CryptographyECKey - else: - assert ECKey is PurePythonECDSAKey + assert ECKey is CryptographyECKey def test_default_rsa_backend(): - if CryptographyRSAKey is not None: - assert RSAKey is CryptographyRSAKey - else: - assert RSAKey is PurePythonRSAKey + assert RSAKey is CryptographyRSAKey def test_default_aes_backend(): - if CryptographyAESKey is not None: - assert AESKey is CryptographyAESKey - else: - assert AESKey is None + assert AESKey is CryptographyAESKey def test_default_hmac_backend(): - if CryptographyHMACKey is not None: - assert HMACKey is CryptographyHMACKey - else: - assert HMACKey is NativeHMACKey + assert HMACKey is CryptographyHMACKey diff --git a/tests/test_firebase.py b/tests/test_firebase.py index 1096591..f7ed29f 100644 --- a/tests/test_firebase.py +++ b/tests/test_firebase.py @@ -5,11 +5,6 @@ from jose import jwt from jose.backends import RSAKey -try: - from jose.backends.rsa_backend import RSAKey as RsaRSAKey -except ImportError: - RsaRSAKey = None - firebase_certs = { "6f83ab6e516e718fba9ddeb6647fd5fb752a151b": "-----BEGIN CERTIFICATE-----\nMIIDHDCCAgSgAwIBAgIIP5V2bjX2bXUwDQYJKoZIhvcNAQEFBQAwMTEvMC0GA1UE\nAxMmc2VjdXJldG9rZW4uc3lzdGVtLmdzZXJ2aWNlYWNjb3VudC5jb20wHhcNMTYw\nODMxMDA0NTI2WhcNMTYwOTAzMDExNTI2WjAxMS8wLQYDVQQDEyZzZWN1cmV0b2tl\nbi5zeXN0ZW0uZ3NlcnZpY2VhY2NvdW50LmNvbTCCASIwDQYJKoZIhvcNAQEBBQAD\nggEPADCCAQoCggEBAKHHtOMXBD+0YTtZHuzFrERiiwa+D6Ybq4SUHlicgRPV3Uk2\nvnTOqg1EhxshEXqjkAQbbRop9hhHTc+p8rBxgYGuLcZsBhGrnRqU6FnTTiWB1x5V\nvOfCkPE60W07gi8p+HyB8cqw1Tz2LnRUw/15888CrspVeumtNUkhXSRKzeS2BI4l\nkuOMkqmsMSu1yB5IZm5meMyta1uhJnP93jKmdar19RkZXOlFcT+fsSY2FPuqvDvX\nssChgZgNV5qtk0CIzexmFJaUFzpKE/RxqdIJooB1H83fUBGVK+9v3Ko+BI+GEvUc\nxIGAEWu2KrbjwPNzzC3/UV9aSfHEOJxQoutPviECAwEAAaM4MDYwDAYDVR0TAQH/\nBAIwADAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwIwDQYJ\nKoZIhvcNAQEFBQADggEBAIHOiqxXm1IcuXE87ELyKYDG0/gZPzCHz98h/x0LExrs\nd0bOYOIA08rt6qllmP24oT3hQt86HmDb932pm/fjaLL68x81TjYq6cFO0JxOzts+\nY+9XxkdP8Qu7UJ8Dx+rRvDN1MUxLTvBVXdamhkhDusx7PB5kK1ixWtf91qrl/J9e\nUYQBnJ4E9wI8U5HVkW3IBWvsFt/+gMO1EcoNBdB2cY/4N3l3oxm5PSNDS4DTEs2f\nAYZDqo6PJt2tTRGSmvLBKSCqcT7eWBbIwBht3Uw8CvOMbVYGBWjbFeua3Q3fe+p7\n7UbFOLIvSGR516kyZqxy9pLoA9+2TvbpYwWu6mLCZtg=\n-----END CERTIFICATE-----\n", "fc2da7fa53d92e3bcba8a17e74b34da9dd585065": "-----BEGIN CERTIFICATE-----\nMIIDHDCCAgSgAwIBAgIINfZYQW9uekMwDQYJKoZIhvcNAQEFBQAwMTEvMC0GA1UE\nAxMmc2VjdXJldG9rZW4uc3lzdGVtLmdzZXJ2aWNlYWNjb3VudC5jb20wHhcNMTYw\nODI5MDA0NTI2WhcNMTYwOTAxMDExNTI2WjAxMS8wLQYDVQQDEyZzZWN1cmV0b2tl\nbi5zeXN0ZW0uZ3NlcnZpY2VhY2NvdW50LmNvbTCCASIwDQYJKoZIhvcNAQEBBQAD\nggEPADCCAQoCggEBAMvfJ5DY7lV4txW0zn9ayMxwAp5BzUhyIbuZkmsmMLRrNl+i\nid4lawojB846YtcTPZLD/5QpXRumAAUI5NA023fxaUdriM25zewpSnZWs6eUf0O6\nONES8Xk4WD2fbyPz6cgnsFEfMslNd3NypRiB9fVG6LFj6TFHC64o/YEeQB2dwkJZ\nXknKSEkFJSRC83TiHUlWzaRjmTdGRrvGEWHxr+xJltP8tPPlJUKu2VadgMbGlkKU\n5dBRhvWwZZW0zJupuKzd27O2lPkxfbx9vrUbsfqZcN4OY5Xg+ijQJVTv0/qcplsd\nPZ9Uui0QsBOPbrIO+5/Tq9FIBqxzUlpWwetv6pMCAwEAAaM4MDYwDAYDVR0TAQH/\nBAIwADAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwIwDQYJ\nKoZIhvcNAQEFBQADggEBALqWwzIQSK94hxTmxlA+RoyMvb8fyTcECM2qY+n+PDb5\nMvt8zqM6AwGjK1hvcUg08BEsnqqRqC81dkSEReS9KCoTY/oQ0sCCpwL3QP3puoxp\nfZU9CSwvnrFTJjC2Q/b8BlWta4CSDwpxpy/K3wm6tRn5ED4rPcP4FRqWU5jyHiug\nRrNkKiG7TeBBvQ3ZlF9K4JSx1yn9g7EvPBcmygop5FIKI1uS+URxeyavtlwfnTTs\nDtRVV/x0LDkHoJ2Agy7l2MqT7eoRKh5VNucQONLrcZT1AY02eZi/WVSjgpzC48eP\nV9xlcgIaRbS/JDULYgW5h0uVdRNqSVGJ6yBLXT2uaBA=\n-----END CERTIFICATE-----\n", @@ -21,7 +16,6 @@ firebase_token = "eyJhbGciOiJSUzI1NiIsImtpZCI6ImY0YjBhNWM3M2FkODVhNWRhMDlmMGU3Zjc2NDYzNjMxMzM5ZTBiYmYifQ.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vd2Vkb3RyYW5zZmVyLTIwMTYiLCJhdWQiOiJ3ZWRvdHJhbnNmZXItMjAxNiIsImF1dGhfdGltZSI6MTQ2NzM0NjI3MCwidXNlcl9pZCI6IjRjemVXVllIekNNVnN0WEZOYldHVXBKYmJTZzEiLCJzdWIiOiI0Y3plV1ZZSHpDTVZzdFhGTmJXR1VwSmJiU2cxIiwiaWF0IjoxNDY3MzQ2MjcwLCJleHAiOjE0NjczNDk4NzAsImVtYWlsIjoic2V1bkBjbXUuY29tIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJmaXJlYmFzZSI6eyJpZGVudGl0aWVzIjp7InBhc3N3b3JkIjpbInNldW5AY211LmNvbSJdLCJlbWFpbCI6WyJzZXVuQGNtdS5jb20iXX19fQ.U-fYjx8rMm5tYV24r0uEcNQtIe3UKULxsHecLdGzTbi1v-VKzKDk_QPL26SPDoU8JUMY3nJQ1hOE9AapBrQck8NVUZSKFMD49XdtsyoN2kKdinpFR1hSxIE0L2dRStS7OZ8sGiX866lNa52Cr6TXSsnMD6N2P0OtVE5EeD1Nf-AiJ-gsaLrP4tBnmj1MNYhEYVHb6sAUrT3nEI9gWmeKcPWPfn76FGTdGWZ2mjdaeAG4RbuFL4cHdOISA_0HVLGJxuNyEHAHybDX8mVdNW_F4yzL3H-SmPFY5Kv3tCdBzpzhUKfNOnFFmf2ggFOJnDsqMp-TZaIPk6ce_ltqhQ0dnQ" -@pytest.mark.skipif(RSAKey is RsaRSAKey, reason="python-rsa backend does not support certificates") class TestFirebase: def test_individual_cert(self): jwt.decode( diff --git a/tests/test_jwe.py b/tests/test_jwe.py index 6ab9971..9977b57 100644 --- a/tests/test_jwe.py +++ b/tests/test_jwe.py @@ -3,6 +3,7 @@ import pytest import jose.backends +import jose.backends.cryptography_backend # noqa E402 from jose import jwe from jose.constants import ALGORITHMS, ZIPS from jose.exceptions import JWEError, JWEParseError @@ -10,21 +11,8 @@ from jose.utils import base64url_decode backends = [] -try: - import jose.backends.cryptography_backend # noqa E402 - backends.append(jose.backends.cryptography_backend) -except ImportError: - pass - -import jose.backends.native # noqa E402 - -try: - from jose.backends.rsa_backend import RSAKey as RSABackendRSAKey -except ImportError: - RSABackendRSAKey = None - -backends.append(jose.backends.native) +backends.append(jose.backends.cryptography_backend) PRIVATE_KEY_PEM = """-----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEA3AyQGW/Q8AKJH2Mfjv1c67iYcwIn+Z2tpqHDQQV9CfSx9CMs @@ -134,7 +122,6 @@ def test_wrong_auth_tag_is_ignored(self): @pytest.mark.skipif(AESKey is None, reason="Test requires AES Backend") -@pytest.mark.skipif(RSAKey is RSABackendRSAKey, reason="RSA Backend does not support all modes") class TestDecrypt: JWE_RSA_PACKAGES = ( pytest.param( diff --git a/tests/test_jwt.py b/tests/test_jwt.py index f9d54cd..10f1f26 100644 --- a/tests/test_jwt.py +++ b/tests/test_jwt.py @@ -2,6 +2,11 @@ import json from datetime import datetime, timedelta +import pytest + +from jose import jws, jwt +from jose.exceptions import JWTError + try: from datetime import UTC # Preferred in Python 3.13+ except ImportError: @@ -9,11 +14,6 @@ UTC = timezone.utc -import pytest - -from jose import jws, jwt -from jose.exceptions import JWTError - @pytest.fixture def claims(): diff --git a/tox.ini b/tox.ini index 8f68f76..bbf7922 100644 --- a/tox.ini +++ b/tox.ini @@ -1,18 +1,18 @@ [tox] min_version = 4.4 envlist = - py{39,310,311,312,313,py3}-{base,cryptography-only,pycryptodome-norsa,compatibility}, + py{39,310,311,312,313,py3}-{base,cryptography-only}, lint skip_missing_interpreters = True [gh-actions] python = - 3.9: py39-{base,cryptography-only,pycryptodome-norsa,compatibility} - 3.10: py310-{base,cryptography-only,pycryptodome-norsa,compatibility} - 3.11: py311-{base,cryptography-only,pycryptodome-norsa,compatibility} - 3.12: py312-{base,cryptography-only,pycryptodome-norsa,compatibility} - 3.13: py313-{base,cryptography-only,pycryptodome-norsa,compatibility} - pypy-3.9: pypy3-{base,cryptography-only,pycryptodome-norsa,compatibility} + 3.9: py39-{base,cryptography-only} + 3.10: py310-{base,cryptography-only} + 3.11: py311-{base,cryptography-only} + 3.12: py312-{base,cryptography-only} + 3.13: py313-{base,cryptography-only} + pypy-3.9: pypy3-{base,cryptography-only} [testenv:basecommand] commands = @@ -26,34 +26,14 @@ commands = pip --version pytest -[testenv:compatibility] -extras = - cryptography - pycryptodome - [testenv] deps = pytest pytest-cov -commands_pre = - # Remove the python-rsa and python-ecdsa backends - only: pip uninstall -y ecdsa rsa - # Remove just the python-rsa backend - norsa: pip uninstall -y rsa commands = - # Test the python-rsa backend - base: {[testenv:basecommand]commands} -m "not (cryptography or pycryptodome or backend_compatibility)" # Test the pyca/cryptography backend - cryptography: {[testenv:basecommand]commands} -m "not (pycryptodome or backend_compatibility)" - # Test the pycryptodome backend - pycryptodome: {[testenv:basecommand]commands} -m "not (cryptography or backend_compatibility)" - # Test cross-backend compatibility and coexistence - compatibility: {[testenv:basecommand]commands} -extras = - cryptography: cryptography - pycryptodome: pycryptodome - compatibility: {[testenv:compatibility]extras} + cryptography: {[testenv:basecommand]commands} [testenv:lint] basepython = python3.12