diff --git a/.gitignore b/.gitignore index ba746605..8eef7586 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,12 @@ docs/_build/ # PyBuilder target/ + +# PyCharm +.idea/ + +# PyEnv +.python-version + +# PyTest +.pytest_cache/ diff --git a/.travis.yml b/.travis.yml index 716a5574..738b6f3d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,21 +2,114 @@ # detail: https://blog.travis-ci.com/2017-06-21-trusty-updates-2017-Q2-launch dist: precise language: python -python: - - "2.7" - - "3.4" - - "3.5" - - "3.6" - - "pypy-5.3.1" install: - pip install -U setuptools && pip install -U tox codecov tox-travis script: - tox after_success: - codecov -# matrix: -# include: -# - python: 3.6 -# env: -# - TOX_ENV=flake8 -# script: tox -e $TOX_ENV \ No newline at end of file +matrix: + include: + # CPython 2.7 + - python: 2.7 + env: TOXENV=py27-base + - python: 2.7 + env: TOXENV=py27-cryptography-only + - python: 2.7 + env: TOXENV=py27-pycryptodome-norsa + - python: 2.7 + env: TOXENV=py27-pycrypto-norsa + - python: 2.7 + env: TOXENV=py27-compatibility + # CPython 3.4 + - python: 3.4 + env: TOXENV=py34-base + - python: 3.4 + env: TOXENV=py34-cryptography-only + - python: 3.4 + env: TOXENV=py34-pycryptodome-norsa + - python: 3.4 + env: TOXENV=py34-pycrypto-norsa + - python: 3.4 + env: TOXENV=py34-compatibility + # CPython 3.5 + - python: 3.5 + env: TOXENV=py35-base + - python: 3.5 + env: TOXENV=py35-cryptography-only + - python: 3.5 + env: TOXENV=py35-pycryptodome-norsa + - python: 3.5 + env: TOXENV=py35-pycrypto-norsa + - python: 3.5 + env: TOXENV=py35-compatibility + # CPython 3.5 + - python: 3.5 + env: TOXENV=py35-base + - python: 3.5 + env: TOXENV=py35-cryptography-only + - python: 3.5 + env: TOXENV=py35-pycryptodome-norsa + - python: 3.5 + env: TOXENV=py35-pycrypto-norsa + - python: 3.5 + env: TOXENV=py35-compatibility + # CPython 3.6 + - python: 3.6 + env: TOXENV=py35-base + - python: 3.6 + env: TOXENV=py35-cryptography-only + - python: 3.6 + env: TOXENV=py35-pycryptodome-norsa + - python: 3.6 + env: TOXENV=py35-pycrypto-norsa + - python: 3.6 + env: TOXENV=py35-compatibility + # CPython 3.7 + # xenial + sudo are currently needed to get 3.7 + # https://github.com/travis-ci/travis-ci/issues/9815 + - python: 3.7 + env: TOXENV=py35-base + dist: xenial + sudo: true + - python: 3.7 + env: TOXENV=py35-cryptography-only + dist: xenial + sudo: true + - python: 3.7 + env: TOXENV=py35-pycryptodome-norsa + dist: xenial + sudo: true + - python: 3.7 + env: TOXENV=py35-pycrypto-norsa + dist: xenial + sudo: true + - python: 3.7 + env: TOXENV=py35-compatibility + dist: xenial + sudo: true + # PyPy 5.3.1 + - python: pypy-5.3.1 + env: TOXENV=pypy-base + - python: pypy-5.3.1 + env: TOXENV=pypy-cryptography-only + - python: pypy-5.3.1 + env: TOXENV=pypy-pycryptodome-norsa + - python: pypy-5.3.1 + env: TOXENV=pypy-pycrypto-norsa + - python: pypy-5.3.1 + env: TOXENV=pypy-compatibility + # PyPy 3.5 (5.10.1?) + - python: pypy3.5 + env: TOXENV=pypy-base + - python: pypy3.5 + env: TOXENV=pypy-cryptography-only + - python: pypy3.5 + env: TOXENV=pypy-pycryptodome-norsa + - python: pypy3.5 + env: TOXENV=pypy-pycrypto-norsa + - python: pypy3.5 + env: TOXENV=pypy-compatibility + # Linting + - python: 3.6 + env: TOX_ENV=flake8 diff --git a/README.rst b/README.rst index 8bc29f1f..b3a9de17 100644 --- a/README.rst +++ b/README.rst @@ -42,6 +42,23 @@ The crytography option is a good default. $ pip install python-jose[pycryptodome] $ pip install python-jose[pycrypto] +Due to complexities with setuptools, the ``python-rsa`` and ``python-ecdsa`` libraries are always installed. +If you use one of the custom backends and would like to clean up unneeded dependencies, +you can remove the following dependencies for each backend: + +* ``cryptography`` + + * ``pip uninstall rsa ecdsa pyasn1`` + +* ``pycrypto`` or ``pycryptodome`` + + * ``pip uninstall rsa`` + +.. warning:: + + Uninstall carefully. Make sure that nothing else in your environment needs these + libraries before uninstalling them. + Usage ----- diff --git a/jose/backends/__init__.py b/jose/backends/__init__.py index 732bd5b2..d1b9fa1a 100644 --- a/jose/backends/__init__.py +++ b/jose/backends/__init__.py @@ -1,9 +1,9 @@ try: - from jose.backends.pycrypto_backend import RSAKey # noqa: F401 + from jose.backends.cryptography_backend import CryptographyRSAKey as RSAKey # noqa: F401 except ImportError: try: - from jose.backends.cryptography_backend import CryptographyRSAKey as RSAKey # noqa: F401 + from jose.backends.pycrypto_backend import RSAKey # noqa: F401 except ImportError: from jose.backends.rsa_backend import RSAKey # noqa: F401 diff --git a/jose/backends/_asn1.py b/jose/backends/_asn1.py new file mode 100644 index 00000000..e252cc77 --- /dev/null +++ b/jose/backends/_asn1.py @@ -0,0 +1,82 @@ +"""ASN1 encoding helpers for converting between PKCS1 and PKCS8. + +Required by rsa_backend and pycrypto_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/cryptography_backend.py b/jose/backends/cryptography_backend.py index 68047665..b9bdc0dc 100644 --- a/jose/backends/cryptography_backend.py +++ b/jose/backends/cryptography_backend.py @@ -1,6 +1,13 @@ +from __future__ import division + +import math + import six -import ecdsa -from ecdsa.util import sigdecode_string, sigencode_string, sigdecode_der, sigencode_der + +try: + from ecdsa import SigningKey as EcdsaSigningKey, VerifyingKey as EcdsaVerifyingKey +except ImportError: + EcdsaSigningKey = EcdsaVerifyingKey = None from jose.backends.base import Key from jose.utils import base64_to_long, long_to_base64 @@ -11,7 +18,9 @@ from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import ec, rsa, padding +from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature, encode_dss_signature from cryptography.hazmat.primitives.serialization import load_pem_private_key, load_pem_public_key +from cryptography.utils import int_from_bytes, int_to_bytes from cryptography.x509 import load_pem_x509_certificate @@ -37,7 +46,7 @@ def __init__(self, key, algorithm, cryptography_backend=default_backend): self.prepared_key = key return - if isinstance(key, (ecdsa.SigningKey, ecdsa.VerifyingKey)): + if None not in (EcdsaSigningKey, EcdsaVerifyingKey) and isinstance(key, (EcdsaSigningKey, EcdsaVerifyingKey)): # convert to PEM and let cryptography below load it as PEM key = key.to_pem().decode('utf-8') @@ -90,19 +99,42 @@ def _process_jwk(self, jwk_dict): else: return public.public_key(self.cryptography_backend()) + def _sig_component_length(self): + """Determine the correct serialization length for an encoded signature component. + + This is the number of bytes required to encode the maximum key value. + """ + return int(math.ceil(self.prepared_key.key_size / 8.0)) + + def _der_to_raw(self, der_signature): + """Convert signature from DER encoding to RAW encoding.""" + r, s = decode_dss_signature(der_signature) + component_length = self._sig_component_length() + return int_to_bytes(r, component_length) + int_to_bytes(s, component_length) + + def _raw_to_der(self, raw_signature): + """Convert signature from RAW encoding to DER encoding.""" + component_length = self._sig_component_length() + if len(raw_signature) != int(2 * component_length): + raise ValueError("Invalid signature") + + r_bytes = raw_signature[:component_length] + s_bytes = raw_signature[component_length:] + r = int_from_bytes(r_bytes, "big") + s = int_from_bytes(s_bytes, "big") + return encode_dss_signature(r, s) + def sign(self, msg): if self.hash_alg.digest_size * 8 > self.prepared_key.curve.key_size: raise TypeError("this curve (%s) is too short " "for your digest (%d)" % (self.prepared_key.curve.name, 8 * self.hash_alg.digest_size)) signature = self.prepared_key.sign(msg, ec.ECDSA(self.hash_alg())) - order = (2 ** self.prepared_key.curve.key_size) - 1 - return sigencode_string(*sigdecode_der(signature, order), order=order) + return self._der_to_raw(signature) def verify(self, msg, sig): - order = (2 ** self.prepared_key.curve.key_size) - 1 - signature = sigencode_der(*sigdecode_string(sig, order), order=order) try: + signature = self._raw_to_der(sig) self.prepared_key.verify(signature, msg, ec.ECDSA(self.hash_alg())) return True except Exception: diff --git a/jose/backends/pycrypto_backend.py b/jose/backends/pycrypto_backend.py index 4fe596aa..a12e861c 100644 --- a/jose/backends/pycrypto_backend.py +++ b/jose/backends/pycrypto_backend.py @@ -1,3 +1,5 @@ +from base64 import b64encode + import six import Crypto.Hash.SHA256 @@ -9,7 +11,7 @@ from Crypto.Util.asn1 import DerSequence from jose.backends.base import Key -from jose.backends.rsa_backend import pem_to_spki +from jose.backends._asn1 import rsa_public_key_pkcs8_to_pkcs1 from jose.utils import base64_to_long, long_to_base64 from jose.constants import ALGORITHMS from jose.exceptions import JWKError @@ -25,6 +27,21 @@ _RSAKey = RSA._RSAobj +def _der_to_pem(der_key, marker): + """ + Perform a simple DER to PEM conversion. + """ + pem_key_chunks = [('-----BEGIN %s-----' % marker).encode('utf-8')] + + # Limit base64 output lines to 64 characters by limiting input lines to 48 characters. + for chunk_start in range(0, len(der_key), 48): + pem_key_chunks.append(b64encode(der_key[chunk_start:chunk_start + 48])) + + pem_key_chunks.append(('-----END %s-----' % marker).encode('utf-8')) + + return b'\n'.join(pem_key_chunks) + + class RSAKey(Key): """ Performs signing and verification operations using @@ -132,7 +149,7 @@ def sign(self, msg): def verify(self, msg, sig): try: return PKCS1_v1_5.new(self.prepared_key).verify(self.hash_alg.new(msg), sig) - except Exception as e: + except Exception: return False def is_public(self): @@ -152,11 +169,13 @@ def to_pem(self, pem_format='PKCS8'): raise ValueError("Invalid pem format specified: %r" % (pem_format,)) if self.is_public(): - pem = self.prepared_key.exportKey('PEM', pkcs=1) + # PyCrypto/dome always export public keys as PKCS8 if pkcs == 8: - pem = pem_to_spki(pem, fmt='PKCS8') + pem = self.prepared_key.exportKey('PEM') else: - pem = pem_to_spki(pem, fmt='PKCS1') + pkcs8_der = self.prepared_key.exportKey('DER') + pkcs1_der = rsa_public_key_pkcs8_to_pkcs1(pkcs8_der) + pem = _der_to_pem(pkcs1_der, 'RSA PUBLIC KEY') return pem else: pem = self.prepared_key.exportKey('PEM', pkcs=pkcs) diff --git a/jose/backends/rsa_backend.py b/jose/backends/rsa_backend.py index 4dbbf5ab..c1f5539d 100644 --- a/jose/backends/rsa_backend.py +++ b/jose/backends/rsa_backend.py @@ -1,18 +1,34 @@ +import binascii + import six -from pyasn1.codec.der import encoder -from pyasn1.type import univ +from pyasn1.error import PyAsn1Error import rsa as pyrsa import rsa.pem as pyrsa_pem -from rsa.asn1 import OpenSSLPubKey, AsnPubKey, PubKeyHeader from jose.backends.base import Key +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.constants import ALGORITHMS from jose.exceptions import JWKError from jose.utils import base64_to_long, long_to_base64 -PKCS8_RSA_HEADER = b'0\x82\x04\xbd\x02\x01\x000\r\x06\t*\x86H\x86\xf7\r\x01\x01\x01\x05\x00' +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. @@ -83,6 +99,23 @@ def pem_to_spki(pem, fmt='PKCS8'): 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' @@ -121,12 +154,15 @@ def __init__(self, key, algorithm): self._prepared_key = pyrsa.PrivateKey.load_pkcs1(key) except ValueError: try: - # python-rsa does not support PKCS8 yet so we have to manually remove OID der = pyrsa_pem.load_pem(key, b'PRIVATE KEY') - header, der = der[:22], der[22:] - if header != PKCS8_RSA_HEADER: - raise ValueError("Invalid PKCS8 header") - self._prepared_key = pyrsa.PrivateKey._load_pkcs1_der(der) + 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 @@ -183,26 +219,17 @@ 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': - pem = pyrsa_pem.save_pem(PKCS8_RSA_HEADER + der, pem_marker='PRIVATE KEY') + 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("Invalid pem format specified: %r" % (pem_format,)) else: if pem_format == 'PKCS8': - asn_key = AsnPubKey() - asn_key.setComponentByName('modulus', self._prepared_key.n) - asn_key.setComponentByName('publicExponent', self._prepared_key.e) - der = encoder.encode(asn_key) - - header = PubKeyHeader() - header['oid'] = univ.ObjectIdentifier('1.2.840.113549.1.1.1') - pub_key = OpenSSLPubKey() - pub_key['header'] = header - pub_key['key'] = univ.BitString.fromOctetString(der) - - der = encoder.encode(pub_key) - pem = pyrsa_pem.save_pem(der, pem_marker='PUBLIC KEY') + 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') diff --git a/requirements.txt b/requirements.txt index 5343103b..4083b222 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ six future rsa ecdsa +pyasn1 diff --git a/setup.py b/setup.py index d21374d5..ae32fa2c 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,8 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- import os +import platform + import jose from setuptools import setup @@ -21,11 +23,26 @@ def get_packages(package): ] +def _cryptography_version(): + # pyca/cryptography dropped support for PyPy < 5.4 in 2.5 + # https://cryptography.io/en/latest/changelog/#v2-5 + if platform.python_implementation() == 'PyPy' and platform.python_version() < '5.4': + return 'cryptography < 2.5' + + return 'cryptography' + + +pyasn1 = ['pyasn1'] extras_require = { - 'cryptography': ['cryptography'], - 'pycrypto': ['pycrypto >=2.6.0, <2.7.0'], - 'pycryptodome': ['pycryptodome >=3.3.1, <4.0.0'], + 'cryptography': [_cryptography_version()], + 'pycrypto': ['pycrypto >=2.6.0, <2.7.0'] + pyasn1, + 'pycryptodome': ['pycryptodome >=3.3.1, <4.0.0'] + pyasn1, } +legacy_backend_requires = ['ecdsa <1.0', 'rsa'] + pyasn1 +install_requires = ['six <2.0', 'future <1.0'] + +# TODO: work this into the extras selection instead. +install_requires += legacy_backend_requires setup( @@ -63,7 +80,6 @@ def get_packages(package): 'pytest', 'pytest-cov', 'pytest-runner', - 'cryptography', ], - install_requires=['six <2.0', 'ecdsa <1.0', 'rsa', 'future <1.0'] + install_requires=install_requires ) diff --git a/tests/algorithms/test_EC.py b/tests/algorithms/test_EC.py index d20d9c0b..7f012afb 100644 --- a/tests/algorithms/test_EC.py +++ b/tests/algorithms/test_EC.py @@ -2,10 +2,20 @@ from jose.constants import ALGORITHMS from jose.exceptions import JOSEError, JWKError -from jose.backends.ecdsa_backend import ECDSAECKey -from jose.backends.cryptography_backend import CryptographyECKey +from jose.backends import ECKey +try: + from jose.backends.ecdsa_backend import ECDSAECKey + import ecdsa +except ImportError: + ECDSAECKey = ecdsa = None + +try: + from jose.backends.cryptography_backend import CryptographyECKey + from cryptography.hazmat.primitives.asymmetric import ec as CryptographyEc + from cryptography.hazmat.backends import default_backend as CryptographyBackend +except ImportError: + CryptographyECKey = CryptographyEc = CryptographyBackend = None -import ecdsa import pytest private_key = """-----BEGIN EC PRIVATE KEY----- @@ -14,69 +24,125 @@ WkG0HJWIORlPbvXME+DRh6G/yVOKnTm88Q== -----END EC PRIVATE KEY-----""" +# Private key generated using NIST256p curve +TOO_SHORT_PRIVATE_KEY = b"""\ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIMlUyYGOpjV4bbW0C9FKS2zkspD0L/5vJLnr6sJoLdc+oAoGCCqGSM49 +AwEHoUQDQgAE6TDUNj5QXl+RKdZvBV+cg7Td6cJRB+Ta8XAhIuCAzonq0Ix//1+C +pNSsy11sIKmMl61YJzxvZ6WkNluBmkDPCQ== +-----END EC PRIVATE KEY----- +""" + +# ES256 signatures generated to test conversion logic +DER_SIGNATURE = ( + b"0F\x02!\x00\x89yG\x81W\x01\x11\x9b0\x08\xa4\xd0\xe3g([\x07\xb5\x01\xb3" + b"\x9d\xdf \xd1\xbc\xedK\x01\x87:}\xf2\x02!\x00\xb2shTA\x00\x1a\x13~\xba" + b"J\xdb\xeem\x12\x1e\xfeMO\x04\xb2[\x86A\xbd\xc6hu\x953X\x1e" +) +RAW_SIGNATURE = ( + b"\x89yG\x81W\x01\x11\x9b0\x08\xa4\xd0\xe3g([\x07\xb5\x01\xb3\x9d\xdf " + b"\xd1\xbc\xedK\x01\x87:}\xf2\xb2shTA\x00\x1a\x13~\xbaJ\xdb\xeem\x12\x1e" + b"\xfeMO\x04\xb2[\x86A\xbd\xc6hu\x953X\x1e" +) + + +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() + + +@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) +)) +def test_cryptography_sig_component_length(algorithm, expected_length): + # Put mapping inside here to avoid more complex handling for test runs that do not have pyca/cryptography + mapping = { + ALGORITHMS.ES256: CryptographyEc.SECP256R1, + ALGORITHMS.ES384: CryptographyEc.SECP384R1, + ALGORITHMS.ES512: CryptographyEc.SECP521R1, + } + key = CryptographyECKey( + CryptographyEc.generate_private_key(mapping[algorithm](), backend=CryptographyBackend()), + algorithm + ) + assert key._sig_component_length() == 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 -class TestECAlgorithm: - @pytest.mark.parametrize("Backend", [ECDSAECKey, CryptographyECKey]) - def test_key_from_pem(self, Backend): - assert not Backend(private_key, ALGORITHMS.ES256).is_public() +@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 + + +class TestECAlgorithm: - @pytest.mark.parametrize("Backend", [ECDSAECKey, CryptographyECKey]) - def test_key_from_ecdsa(self, Backend): - key = ecdsa.SigningKey.from_pem(private_key) - assert not Backend(key, ALGORITHMS.ES256).is_public() + def test_key_from_pem(self): + assert not ECKey(private_key, ALGORITHMS.ES256).is_public() - @pytest.mark.parametrize("Backend", [ECDSAECKey, CryptographyECKey]) - def test_to_pem(self, Backend): - key = Backend(private_key, ALGORITHMS.ES256) + def test_to_pem(self): + key = ECKey(private_key, ALGORITHMS.ES256) assert not key.is_public() assert key.to_pem().strip() == private_key.strip().encode('utf-8') public_pem = key.public_key().to_pem() - assert Backend(public_pem, ALGORITHMS.ES256).is_public() - - @pytest.mark.parametrize( - "Backend,ExceptionType", - [ - (ECDSAECKey, ecdsa.BadDigestError), - (CryptographyECKey, TypeError) - ] - ) + assert ECKey(public_pem, ALGORITHMS.ES256).is_public() + + @pytest.mark.parametrize("Backend,ExceptionType", _backend_exception_types()) def test_key_too_short(self, Backend, ExceptionType): - priv_key = ecdsa.SigningKey.generate(curve=ecdsa.NIST256p).to_pem() - key = Backend(priv_key, ALGORITHMS.ES512) + key = Backend(TOO_SHORT_PRIVATE_KEY, ALGORITHMS.ES512) with pytest.raises(ExceptionType): key.sign(b'foo') - @pytest.mark.parametrize("Backend", [ECDSAECKey, CryptographyECKey]) - def test_get_public_key(self, Backend): - key = Backend(private_key, ALGORITHMS.ES256) + def test_get_public_key(self): + key = ECKey(private_key, ALGORITHMS.ES256) pubkey = key.public_key() pubkey2 = pubkey.public_key() assert pubkey == pubkey2 - @pytest.mark.parametrize("Backend", [ECDSAECKey, CryptographyECKey]) - def test_string_secret(self, Backend): + def test_string_secret(self): key = 'secret' with pytest.raises(JOSEError): - Backend(key, ALGORITHMS.ES256) + ECKey(key, ALGORITHMS.ES256) - @pytest.mark.parametrize("Backend", [ECDSAECKey, CryptographyECKey]) - def test_object(self, Backend): + def test_object(self): key = object() with pytest.raises(JOSEError): - Backend(key, ALGORITHMS.ES256) + ECKey(key, ALGORITHMS.ES256) - @pytest.mark.parametrize("Backend", [ECDSAECKey, CryptographyECKey]) - def test_invalid_algorithm(self, Backend): + def test_invalid_algorithm(self): with pytest.raises(JWKError): - Backend(private_key, 'nonexistent') + ECKey(private_key, 'nonexistent') with pytest.raises(JWKError): - Backend({'kty': 'bla'}, ALGORITHMS.ES256) + ECKey({'kty': 'bla'}, ALGORITHMS.ES256) - @pytest.mark.parametrize("Backend", [ECDSAECKey, CryptographyECKey]) - def test_EC_jwk(self, Backend): + def test_EC_jwk(self): key = { "kty": "EC", "kid": "bilbo.baggins@hobbiton.example", @@ -87,22 +153,21 @@ def test_EC_jwk(self, Backend): "d": "AAhRON2r9cqXX1hg-RoI6R1tX5p2rUAYdmpHZoC1XNM56KtscrX6zbKipQrCW9CGZH3T4ubpnoTKLDYJ_fF3_rJt", } - assert not Backend(key, ALGORITHMS.ES512).is_public() + assert not ECKey(key, ALGORITHMS.ES512).is_public() del key['d'] # We are now dealing with a public key. - assert Backend(key, ALGORITHMS.ES512).is_public() + assert ECKey(key, ALGORITHMS.ES512).is_public() del key['x'] # This key is missing a required parameter. with pytest.raises(JWKError): - Backend(key, ALGORITHMS.ES512) + ECKey(key, ALGORITHMS.ES512) - @pytest.mark.parametrize("Backend", [ECDSAECKey]) - def test_verify(self, Backend): - key = Backend(private_key, ALGORITHMS.ES256) + def test_verify(self): + key = ECKey(private_key, ALGORITHMS.ES256) msg = b'test' signature = key.sign(msg) public_key = key.public_key() @@ -129,23 +194,7 @@ def assert_parameters(self, as_dict, private): # Private parameters should be absent assert 'd' not in as_dict - @pytest.mark.parametrize("Backend", [ECDSAECKey, CryptographyECKey]) - def test_to_dict(self, Backend): - key = Backend(private_key, ALGORITHMS.ES256) + def test_to_dict(self): + key = ECKey(private_key, ALGORITHMS.ES256) self.assert_parameters(key.to_dict(), private=True) self.assert_parameters(key.public_key().to_dict(), private=False) - - @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) diff --git a/tests/algorithms/test_EC_compat.py b/tests/algorithms/test_EC_compat.py new file mode 100644 index 00000000..1fc6dab3 --- /dev/null +++ b/tests/algorithms/test_EC_compat.py @@ -0,0 +1,72 @@ +import pytest + +try: + from jose.backends.ecdsa_backend import ECDSAECKey + from jose.backends.cryptography_backend import CryptographyECKey +except ImportError: + ECDSAECKey = CryptographyECKey = None +from jose.constants import ALGORITHMS + +from .test_EC import 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(object): + + @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 key.public_key().to_pem().strip() == key2.public_key().to_pem().strip() + + @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 key.to_pem().strip() == key2.to_pem().strip() + + @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 = pubkey.to_pem().strip() + + pub_target = BackendTo(pub_pem_source, ALGORITHMS.ES256) + + assert pub_pem_source == pub_target.to_pem().strip() + + @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 = key.to_pem().strip() + + target = BackendTo(pem_source, ALGORITHMS.ES256) + + assert pem_source == target.to_pem().strip() diff --git a/tests/algorithms/test_RSA.py b/tests/algorithms/test_RSA.py index eb957dfa..97aeb20e 100644 --- a/tests/algorithms/test_RSA.py +++ b/tests/algorithms/test_RSA.py @@ -1,14 +1,29 @@ - +import base64 import sys -from jose.backends.pycrypto_backend import RSAKey -from jose.backends.cryptography_backend import CryptographyRSAKey -from jose.backends.rsa_backend import RSAKey as PurePythonRSAKey +try: + from jose.backends.rsa_backend import RSAKey as PurePythonRSAKey + from jose.backends import rsa_backend +except ImportError: + PurePythonRSAKey = rsa_backend = None + +try: + from Crypto.PublicKey import RSA as PyCryptoRSA + from jose.backends.pycrypto_backend import RSAKey as PyCryptoRSAKey +except ImportError: + PyCryptoRSA = PyCryptoRSAKey = 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 + +from jose.backends import RSAKey from jose.constants import ALGORITHMS from jose.exceptions import JOSEError, JWKError -from Crypto.PublicKey import RSA - import pytest # Deal with integer compatibilities between Python 2 and 3. @@ -16,7 +31,7 @@ if sys.version_info > (3,): long = int -private_key = b"""-----BEGIN RSA PRIVATE KEY----- +private_key_4096_pkcs1 = b"""-----BEGIN RSA PRIVATE KEY----- MIIJKwIBAAKCAgEAtSKfSeI0fukRIX38AHlKB1YPpX8PUYN2JdvfM+XjNmLfU1M7 4N0VmdzIX95sneQGO9kC2xMIE+AIlt52Yf/KgBZggAlS9Y0Vx8DsSL2HvOjguAdX ir3vYLvAyyHin/mUisJOqccFKChHKjnk0uXy/38+1r17/cYTp76brKpU1I4kM20M @@ -67,67 +82,220 @@ bjJ/JfTO5060SsWftf4iw3jrhSn9RwTTYdq/kErGFWvDGJn2MiuhMe2onNfVzIGR mdUxHwi1ulkspAn/fmY7f0hZpskDwcHyZmbKZuk+NU/FJ8IAcmvk9y7m25nSSc8= -----END RSA PRIVATE KEY-----""" +private_key_2048_pkcs1 = b"""-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAom6GcUPchmHxBuV3zJ60EPC7y30WiiVxn1WXSPHmfqaj0q2U +xS03YugkYmX9lB/EQ6Z5bOY9VuL1oMudL6Dkb9aYYEBZHVgejV7vtYuYT19QMesn +AsmGq8etie7XyWHzfWTxljbF53yvxXJMixcFzebAov9pUiV9Hmy3hYVLw3J1NXVg +gPZpUT2oF+qAayhPsOi2b0CrIE3FvioDx7IiRXKFpV/1gah3NRSKxCrsxV6V+UGO ++trP1ViWiu4oXB5j25kZmkgI0lXG60p58DUUeCOnEemvurltf9T9IEs7LGBEzUYm +itGSY4ZOY3MabPypRfFRRotZEDyZjshq4xfXAwIDAQABAoIBAFclRzoTd4gdmevi +RwDgEKmaDpchGGurpScgC5eWONywWOpaOJwFI1cMRyEHqSHEXU8STMkxSa2I/NF1 +DHMWNhkOoBfbzjPhKBse2Sqkp2XGNEdj6z0ik/8rlR6QpvMjezhGZRr7bfhBPCiJ +pylkg7exWp7Yu0/YTyV4nImlNz23GvrYHFtzDzTtn9gW4fe46wI08s4PqH/TyBh8 +QkwkTwOKTk6n/xz2hND/shUOGjaoS0o6y4+8v3O1JYUWa7YZaIFofvF/dHR0yieg +2gQjc0c6+VeBm8dEbn3he+KnIBwQbWsiCuWL6Jq4XPtMbqutfovIYf9lRB+3q2PI +VSh3mwECgYEAzhOhG+usoxjJGk2wVJH5wnHL0zyH8gWF4SnnxwwdBOF4kdLB2eva +SJsi8rJQMT0TC4wZ6TsD2fJXGazIyM6OnD+52AViiUsLVS5MR7qEMNitdkWEtDx9 +Xve50NF9XkTrn6+cgqvfJ9ezE4cOaiD3Eov1u/HbHRx3K2Qf9IzvGoMCgYEAycgk +yOSYR0A3xKcfCQyOU0THEZWBBzd6zBAQ6VQNbcWHh1E8Ve0td6cvCxfydW1pIzXE +7b7J/BgMoC9oSQPfqQJQF2O0XESrdNgXjscfFpVgPfzbFQNgf7d0DSq4b/A5n5QR +HVMmWzVQoRQUwqTNeVxs0NpY6W6Meqv3i/KJqYECgYA/KyMyhM55fCqA5pmLgueV +Y/5/tMlTNcAxIgBLMnpeuaKUyI7ldveFVBClZmVQgpEo8/wpUw6+Kxvp4d32N+Ld +IGeeQSBQR3Gk3blCL3k/49tgKrUf7n7bsoIB8YVFdUjovRLzty2DcAoTjU2s2IgD +5mUgBGYPCV+6LEnjU6QjcwKBgGg+0FJBVzKoSKd+N5hzNixqwfWhqXFTBkvamQIS +fIWToTsVivhRekXwx2sRyh9EkSaxprW09aEZw5wWIehm6evk1//dcNaiW3oYEcOf +t73xGjGsKnsmrXoOCxSqV3LtRrfcxSLDTHOejbNKLpeIkOb8CvOzem/OvyC5K0DP +4rMBAoGBAJStRo5xQ2F9cyZW8vLd4eR3FHXxF/7Moxr6AyV3RLUjMhkwB8ZcFLUQ +dXI4NN9leDeIpNaGU6ozr+At3f50GtCWxdUppy9FDh5qDamBV8K4/+uNqFPiKFQ9 +uwNcJ8daMgVZ0QBrD3CBcSZQrfC484BlV6spJ3C16qDVSQPt7sAI +-----END RSA PRIVATE KEY-----""" +PRIVATE_KEYS = ( + pytest.param(private_key_2048_pkcs1, id="RSA_2048_PKCS1"), + pytest.param(private_key_4096_pkcs1, id="RSA_4096_PKCS1") +) + +LEGACY_INVALID_PRIVATE_KEY_PKCS8_PEM = b"""-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFADCCCSsCAQACggIBALUin0niNH7pESF9/AB5 +SgdWD6V/D1GDdiXb3zPl4zZi31NTO+DdFZncyF/ebJ3kBjvZAtsTCBPgCJbedmH/ +yoAWYIAJUvWNFcfA7Ei9h7zo4LgHV4q972C7wMsh4p/5lIrCTqnHBSgoRyo55NLl +8v9/Pta9e/3GE6e+m6yqVNSOJDNtDP/3W7ywVo388sPXobn6++GlcK/tMSX7AVa9 +qGkBcMP1xxs+vUO8hyug28WDuMOKtrCH3AuKU/F0zx6OCWdjO99xGvGux8bWUuet +/5oYUWS1OWsp0KcGlb9lPvgi+hLxrfE5TWTpHkb/MM/kbfAe9I86EaVSt+q0fqRy +pV4TBk+tfb/Ni53k3bKgNuVoti3f3NJ4rrpduAOvmmo9rvUlm8QPS5lbRZ7bzW0W +h1xNUi6Sz6CKfLqaRhdNjc9r95XfIAp001n6vwUPNEMvHtHKEUQARAma4yDMxxIO +jJaEQ8uJ2tKUUL+tVaIKkSg1Nq9/1XsxT0A293ImLGY1ga9x6TTpFI067y5hcjhP +UOUf6kBpnOgWLX5Oa5+4iH15ZCQGR14QcvhJQbogTPmEpBTO3R/drEiKGdOVeDD9 +PV3Kace5HcLCcu9krrLfR53fQe1d+WJ1Relu/dZVR53p4QiTs4kZpB+MSy2z5Gkk +9irNyBx+7VZbTOjbEZN4zXVTAgMBAAECggIBAK8ftCV4oAx7RWa+KWBD48DIAgSd +na/Pi/D6bQf+IPi6CvTCqkezOGkzvj6CCz1z8lr2av5nng2pMmS63HXPGndQKyhe +22gwaXhhG5EQPSX1eR4zav3muIMrwzAhqLvGT0kAp5EZq/CxUGyQ4JzOWWuQGK8B +L9mhIeuyK0x6ud1vN6zIqCLpgjYhvu00O3oBBomLEO+ORi6xAi2YSikU4Lf0/pNX +EpNSyyWsJnuV4CVMPtw/RnXSRHqb2KC/sGf4JztgA8j5z3UO6HNjT3BTF6ZiEH9v +fv4OxX5WrX0IZCL/ngumwedQ4XTItc8qdoTocyoOo5++IsVV/h7bNv3DIgD7tDWl +plPZ/IOx5l1nOPrkjv37LBoyNwy2CnecZ4a6uGt2TuaCS3jDvVO9fmJHLYNtz0x8 +DQBq7D2HYxnAZyRHbW12t9WoCOTwBuq4BlGN4kqFqhy4AU8a19OE1lby0IvMG8Ye +wRBdMmrDnmSsXRNiFmIO+cxrI5bsh5Tp6UWuZUn+imerYYfXnMxR2LUuA72AXGfY +WVv4PO4ntvEpARccZdG62LfbijWCb7mo1RvJE/NAAu1s4Nqu1pp6MMNyTSVGzbYb +OXaislEpTBTCAf7znjxRy6wOqg3wJ7EB4FG/Vh4VK2LB9tFyqakdjo5llNOW1eKU +jFvycBt+6PqcdcihAoIBAQDZ8MdHe5LBDLyY3jeaZQ8t3jC4nwSPuki/6OU1w6BT +OqycRftFD+9LqLmCatnSPgwkgOnPxLJ+1gDoq2Ne+2nSnAVfG4sALZih5xZwrP5M +AGavJTL0D7TTyf76tp+NJDuPIfH8W3hnJsgKhT2eGCksazwOHHWxyLhUrfVsmlHF +RZs3UxR1ZUT+9BF3Zlx7fOG8ljQeAh3qvM5qzcTzQIuuZxJFJLO4gUDLbr8ldvml +4wzkwTAneuiISIdomatTu5F1MIIdTh/YQmu6K4h8gHYJCnSglqHJmzzxDThE4I62 +gPcESF8DmEw29V+YTL4tZgh6PMYzI7uLMw9N8SVYu1q7AoIBAQDUxG5QpMRMoSam +jGRd1N3X9mK/2bR6rK06+JcAfzDipM5rkq0TsYXhJCiLMW4lfgMU77T0dRUK5am4 +UyUcMvqcOCjyez+H6tkf2Aar10jQQmpk7epdl3V6pwJNwPvnDhGdpVPbm34HXjdi +IfK4HZ9S1njkowlnxMZpKFOtf13usQDHlykEpRykp1/b6MbhNsSDR3lOmQCstZBp +qybRIlpyPG/AuLPH08g7VydS2rMrNIhztk89o1IX+CCcG/Oy5OyM3tjKyPqD81Q1 +5ZpG45AeLWMqIkU4/K5PKcAVvx1b9NNqYzi02uJM9ZEN5bGf9wLOM3zYu5lhIWnZ +hLACH2JJAoIBAQDTt/a/2Ko+ZFMq5mV51ccjNgB6ufBCeCOIW4Wf70Vm1U8uGUX6 +V3qOM4DT0117ws8k/x8kud71HIyRez3z3aV19h+5vxYPvDvUvJuuJkB8ML+QUkDn +nAJ85HSRtqvU/2fkqoNcNrgG7UPUBJBRbwNApYQX6UnkxitcCAqt0FSzoeUhn9H2 +IcUfMJdvOL+LL0xUWk6TAFdz3KtiUjeMYB3R9UtoZDk7ekUp25JRoPzxTFsQNyTC +lcIj8uGomfA4TbUG9XLRaT3CZvQkTXowCNOiAMg/4VWWdvqC6ebJ8qRxY2OUg4Ha +Ci+wDDsrxxHRJJgDt9qLf6EHnzi07Rjs1EVVAoIBAQC0seYmIuh7U9kpVM3gSmnl +gWA4IsH99SxhisFjMKHpuaF9BmJq+TcED9tG60HqIWyomTMK8WxfhtButF4t5rWj +eqZ72GQKIE8pliOESR+TjvQgp1WFCp5A/hkcw6qrfe1D/yaKuTF9PGy4sLAb4Txv +86lUM4pHUHxYzmDSVfsGPdi1qRCy2y7KP0NP1g8hMYwPGeJR9+r0wnXU5//dWNmL +bvxRpgs4yAmjK88/tHC5XrIL42bEqDGOHbJEIhEDexvSP2fKQIlRCpQX+djeH2FD +37P6EoTLcvzuSjzRuy9J61CpZ36/Sa0rQtpf/RSvD+6YBG4g+qG2NdRZYTDBfLnR +AoIBAQCCobzhbYqQ9Y6gzqYzqEfbCv5UUeW1VVkH2pjAzdQMJoyW1R0vIYbWFDP+ +LIdqddj+kYKDvHzg39bxHFhYd8cTWRNaTwj2iAg/YuVPjUbz89rwvdNB3K2i0a1B +Wkc8IajjpJ2CUgaxs1vgsd2EnmjgoJysiPAeYMOAtBtIi9XUrM9m0dTuBjTlX090 +eo6GRFwExaPynNi9GALwKQTVGL2NJG4yfyX0zudOtErFn7X+IsN464Up/UcE02Ha +v5BKxhVrwxiZ9jIroTHtqJzX1cyBkZnVMR8ItbpZLKQJ/35mO39IWabJA8HB8mZm +ymbpPjVPxSfCAHJr5Pcu5tuZ0knP +-----END PRIVATE KEY----- +""" + + +def _legacy_invalid_private_key_pkcs8_der(): + legacy_key = LEGACY_INVALID_PRIVATE_KEY_PKCS8_PEM.strip() + legacy_key = legacy_key[legacy_key.index(b"\n"):legacy_key.rindex(b"\n")] + return base64.b64decode(legacy_key) + + +def _actually_invalid_private_key_pkcs8_der(): + legacy_key = _legacy_invalid_private_key_pkcs8_der() + invalid_key = legacy_key[:len(rsa_backend.LEGACY_INVALID_PKCS8_RSA_HEADER)] + invalid_key += b"\x00" + invalid_key += legacy_key[len(rsa_backend.LEGACY_INVALID_PKCS8_RSA_HEADER):] + return invalid_key + + +def _actually_invalid_private_key_pkcs8_pem(): + invalid_key = b"-----BEGIN PRIVATE KEY-----\n" + invalid_key += base64.b64encode(_actually_invalid_private_key_pkcs8_der()) + invalid_key += b"\n-----END PRIVATE KEY-----\n" + return invalid_key + + +@pytest.mark.skipif(PurePythonRSAKey is None, reason="python-rsa backend not available") +class TestPurePythonRsa(object): + + def test_python_rsa_legacy_pem_read(self): + key = PurePythonRSAKey(LEGACY_INVALID_PRIVATE_KEY_PKCS8_PEM, ALGORITHMS.RS256) + new_pem = key.to_pem(pem_format="PKCS8") + assert new_pem != LEGACY_INVALID_PRIVATE_KEY_PKCS8_PEM + + def test_python_rsa_legacy_pem_invalid(self): + with pytest.raises(JWKError) as excinfo: + PurePythonRSAKey(_actually_invalid_private_key_pkcs8_pem(), ALGORITHMS.RS256) + + excinfo.match("Invalid private key encoding") + + def test_python_rsa_legacy_private_key_pkcs8_to_pkcs1(self): + legacy_key = _legacy_invalid_private_key_pkcs8_der() + legacy_pkcs1 = legacy_key[len(rsa_backend.LEGACY_INVALID_PKCS8_RSA_HEADER):] + + assert rsa_backend._legacy_private_key_pkcs8_to_pkcs1(legacy_key) == legacy_pkcs1 + + def test_python_rsa_legacy_private_key_pkcs8_to_pkcs1_invalid(self): + invalid_key = _actually_invalid_private_key_pkcs8_der() + + with pytest.raises(ValueError) as excinfo: + rsa_backend._legacy_private_key_pkcs8_to_pkcs1(invalid_key) + + excinfo.match("Invalid private key encoding") + + +@pytest.mark.pycrypto +@pytest.mark.pycryptodome +@pytest.mark.skipif(None in (PyCryptoRSA, PyCryptoRSAKey), reason="Pycrypto/dome backend not available") +def test_pycrypto_RSA_key_instance(): + key = PyCryptoRSA.construct((long( + 26057131595212989515105618545799160306093557851986992545257129318694524535510983041068168825614868056510242030438003863929818932202262132630250203397069801217463517914103389095129323580576852108653940669240896817348477800490303630912852266209307160550655497615975529276169196271699168537716821419779900117025818140018436554173242441334827711966499484119233207097432165756707507563413323850255548329534279691658369466534587631102538061857114141268972476680597988266772849780811214198186940677291891818952682545840788356616771009013059992237747149380197028452160324144544057074406611859615973035412993832273216732343819), + long(65537))) + PyCryptoRSAKey(key, ALGORITHMS.RS256) + + +# TODO: Unclear why this test was marked as only for pycrypto +@pytest.mark.pycrypto +@pytest.mark.pycryptodome +@pytest.mark.parametrize("private_key", PRIVATE_KEYS) +@pytest.mark.skipif(None in (PyCryptoRSA, PyCryptoRSAKey), reason="Pycrypto/dome backend not available") +def test_pycrypto_unencoded_cleartext(private_key): + key = PyCryptoRSAKey(private_key, ALGORITHMS.RS256) + msg = b'test' + signature = key.sign(msg) + public_key = key.public_key() + + assert bool(public_key.verify(msg, signature)) + assert not bool(public_key.verify(msg, 1)) + + +@pytest.mark.cryptography +@pytest.mark.skipif( + None in (default_backend, pyca_rsa, CryptographyRSAKey), + reason="Cryptography backend not available" +) +def test_cryptography_RSA_key_instance(): + key = pyca_rsa.RSAPublicNumbers( + long(65537), + long(26057131595212989515105618545799160306093557851986992545257129318694524535510983041068168825614868056510242030438003863929818932202262132630250203397069801217463517914103389095129323580576852108653940669240896817348477800490303630912852266209307160550655497615975529276169196271699168537716821419779900117025818140018436554173242441334827711966499484119233207097432165756707507563413323850255548329534279691658369466534587631102538061857114141268972476680597988266772849780811214198186940677291891818952682545840788356616771009013059992237747149380197028452160324144544057074406611859615973035412993832273216732343819), + ).public_key(default_backend()) -class TestRSAAlgorithm: - - @pytest.mark.parametrize("Backend", [RSAKey, CryptographyRSAKey, PurePythonRSAKey]) - def test_RSA_key(self, Backend): - assert not Backend(private_key, ALGORITHMS.RS256).is_public() - - def test_pycrypto_RSA_key_instance(self): - key = RSA.construct((long(26057131595212989515105618545799160306093557851986992545257129318694524535510983041068168825614868056510242030438003863929818932202262132630250203397069801217463517914103389095129323580576852108653940669240896817348477800490303630912852266209307160550655497615975529276169196271699168537716821419779900117025818140018436554173242441334827711966499484119233207097432165756707507563413323850255548329534279691658369466534587631102538061857114141268972476680597988266772849780811214198186940677291891818952682545840788356616771009013059992237747149380197028452160324144544057074406611859615973035412993832273216732343819), long(65537))) - RSAKey(key, ALGORITHMS.RS256) - - def test_cryptography_RSA_key_instance(self): - from cryptography.hazmat.backends import default_backend - from cryptography.hazmat.primitives.asymmetric import rsa + pubkey = CryptographyRSAKey(key, ALGORITHMS.RS256) + assert pubkey.is_public() - key = rsa.RSAPublicNumbers( - long(65537), - long(26057131595212989515105618545799160306093557851986992545257129318694524535510983041068168825614868056510242030438003863929818932202262132630250203397069801217463517914103389095129323580576852108653940669240896817348477800490303630912852266209307160550655497615975529276169196271699168537716821419779900117025818140018436554173242441334827711966499484119233207097432165756707507563413323850255548329534279691658369466534587631102538061857114141268972476680597988266772849780811214198186940677291891818952682545840788356616771009013059992237747149380197028452160324144544057074406611859615973035412993832273216732343819), - ).public_key(default_backend()) + pem = pubkey.to_pem() + assert pem.startswith(b'-----BEGIN PUBLIC KEY-----') - pubkey = CryptographyRSAKey(key, ALGORITHMS.RS256) - assert pubkey.is_public() - pem = pubkey.to_pem() - assert pem.startswith(b'-----BEGIN PUBLIC KEY-----') +class TestRSAAlgorithm: + def test_RSA_key(self): + assert not RSAKey(private_key_4096_pkcs1, ALGORITHMS.RS256).is_public() - @pytest.mark.parametrize("Backend", [RSAKey, CryptographyRSAKey, PurePythonRSAKey]) - def test_string_secret(self, Backend): + def test_string_secret(self): key = 'secret' with pytest.raises(JOSEError): - Backend(key, ALGORITHMS.RS256) + RSAKey(key, ALGORITHMS.RS256) - @pytest.mark.parametrize("Backend", [RSAKey, CryptographyRSAKey, PurePythonRSAKey]) - def test_object(self, Backend): + def test_object(self): key = object() with pytest.raises(JOSEError): - Backend(key, ALGORITHMS.RS256) + RSAKey(key, ALGORITHMS.RS256) - @pytest.mark.parametrize("Backend", [RSAKey, CryptographyRSAKey, PurePythonRSAKey]) - def test_bad_cert(self, Backend): + def test_bad_cert(self,): key = '-----BEGIN CERTIFICATE-----' with pytest.raises(JOSEError): - Backend(key, ALGORITHMS.RS256) + RSAKey(key, ALGORITHMS.RS256) - @pytest.mark.parametrize("Backend", [RSAKey, CryptographyRSAKey, PurePythonRSAKey]) - def test_invalid_algorithm(self, Backend): + def test_invalid_algorithm(self): with pytest.raises(JWKError): - Backend(private_key, ALGORITHMS.ES256) + RSAKey(private_key_4096_pkcs1, ALGORITHMS.ES256) with pytest.raises(JWKError): - Backend({'kty': 'bla'}, ALGORITHMS.RS256) + RSAKey({'kty': 'bla'}, ALGORITHMS.RS256) - @pytest.mark.parametrize("Backend", [RSAKey, CryptographyRSAKey, PurePythonRSAKey]) - def test_RSA_jwk(self, Backend): + def test_RSA_jwk(self): key = { "kty": "RSA", "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw", "e": "AQAB", } - assert Backend(key, ALGORITHMS.RS256).is_public() + assert RSAKey(key, ALGORITHMS.RS256).is_public() key = { "kty": "RSA", @@ -142,13 +310,13 @@ def test_RSA_jwk(self, Backend): "dq": "CLDmDGduhylc9o7r84rEUVn7pzQ6PF83Y-iBZx5NT-TpnOZKF1pErAMVeKzFEl41DlHHqqBLSM0W1sOFbwTxYWZDm6sI6og5iTbwQGIC3gnJKbi_7k_vJgGHwHxgPaX2PnvP-zyEkDERuf-ry4c_Z11Cq9AqC2yeL6kdKT1cYF8", "qi": "3PiqvXQN0zwMeE-sBvZgi289XP9XCQF3VWqPzMKnIgQp7_Tugo6-NZBKCQsMf3HaEGBjTVJs_jcK8-TRXvaKe-7ZMaQj8VfBdYkssbu0NKDDhjJ-GtiseaDVWt7dcH0cfwxgFUHpQh7FoCrjFJ6h6ZEpMF6xmujs4qMpPz8aaI4" } - assert not Backend(key, ALGORITHMS.RS256).is_public() + assert not RSAKey(key, ALGORITHMS.RS256).is_public() del key['p'] # Some but not all extra parameters are present with pytest.raises(JWKError): - Backend(key, ALGORITHMS.RS256) + RSAKey(key, ALGORITHMS.RS256) del key['q'] del key['dp'] @@ -156,43 +324,27 @@ def test_RSA_jwk(self, Backend): del key['qi'] # None of the extra parameters are present, but 'key' is still private. - assert not Backend(key, ALGORITHMS.RS256).is_public() + assert not RSAKey(key, ALGORITHMS.RS256).is_public() - @pytest.mark.parametrize("Backend", [RSAKey, CryptographyRSAKey, PurePythonRSAKey]) - def test_get_public_key(self, Backend): - key = Backend(private_key, ALGORITHMS.RS256) + @pytest.mark.parametrize("private_key", PRIVATE_KEYS) + def test_get_public_key(self, private_key): + key = RSAKey(private_key, ALGORITHMS.RS256) public_key = key.public_key() public_key2 = public_key.public_key() assert public_key.is_public() assert public_key2.is_public() assert public_key == public_key2 - @pytest.mark.parametrize("Backend", [RSAKey, CryptographyRSAKey, PurePythonRSAKey]) - def test_to_pem(self, Backend): - key = Backend(private_key, ALGORITHMS.RS256) - assert key.to_pem(pem_format='PKCS1').strip() == private_key.strip() + @pytest.mark.parametrize("pkey", PRIVATE_KEYS) + def test_to_pem(self, pkey): + key = RSAKey(pkey, ALGORITHMS.RS256) + assert key.to_pem(pem_format='PKCS1').strip() == pkey.strip() pkcs8 = key.to_pem(pem_format='PKCS8').strip() - assert pkcs8 != private_key.strip() - - newkey = Backend(pkcs8, ALGORITHMS.RS256) - assert newkey.to_pem(pem_format='PKCS1').strip() == private_key.strip() - - @pytest.mark.parametrize("BackendFrom", [RSAKey, CryptographyRSAKey, PurePythonRSAKey]) - @pytest.mark.parametrize("BackendTo", [RSAKey, CryptographyRSAKey, PurePythonRSAKey]) - def test_public_key_to_pem(self, BackendFrom, BackendTo): - key = BackendFrom(private_key, ALGORITHMS.RS256) - pubkey = key.public_key() - - pkcs1_pub = pubkey.to_pem(pem_format='PKCS1').strip() - pkcs8_pub = pubkey.to_pem(pem_format='PKCS8').strip() - assert pkcs1_pub != pkcs8_pub, BackendFrom + assert pkcs8 != pkey.strip() - pub1 = BackendTo(pkcs1_pub, ALGORITHMS.RS256) - pub8 = BackendTo(pkcs8_pub, ALGORITHMS.RS256) - - assert pkcs8_pub == pub1.to_pem(pem_format='PKCS8').strip() - assert pkcs1_pub == pub8.to_pem(pem_format='PKCS1').strip() + newkey = RSAKey(pkcs8, ALGORITHMS.RS256) + assert newkey.to_pem(pem_format='PKCS1').strip() == pkey.strip() def assert_parameters(self, as_dict, private): assert isinstance(as_dict, dict) @@ -218,43 +370,16 @@ def assert_parameters(self, as_dict, private): assert 'dq' not in as_dict assert 'qi' not in as_dict - def assert_roundtrip(self, key, Backend): - assert Backend( + def assert_roundtrip(self, key): + assert RSAKey( key.to_dict(), ALGORITHMS.RS256 ).to_dict() == key.to_dict() - @pytest.mark.parametrize("Backend", [RSAKey, CryptographyRSAKey, PurePythonRSAKey]) - def test_to_dict(self, Backend): - key = Backend(private_key, ALGORITHMS.RS256) + @pytest.mark.parametrize("private_key", PRIVATE_KEYS) + def test_to_dict(self, private_key): + key = RSAKey(private_key, ALGORITHMS.RS256) self.assert_parameters(key.to_dict(), private=True) self.assert_parameters(key.public_key().to_dict(), private=False) - self.assert_roundtrip(key, Backend) - self.assert_roundtrip(key.public_key(), Backend) - - @pytest.mark.parametrize("BackendSign", [RSAKey, CryptographyRSAKey, PurePythonRSAKey]) - @pytest.mark.parametrize("BackendVerify", [RSAKey, CryptographyRSAKey, PurePythonRSAKey]) - def test_signing_parity(self, BackendSign, BackendVerify): - 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("Backend", [RSAKey, CryptographyRSAKey, PurePythonRSAKey]) - def test_pycrypto_unencoded_cleartext(self, Backend): - key = Backend(private_key, ALGORITHMS.RS256) - - key = RSAKey(private_key, ALGORITHMS.RS256) - msg = b'test' - signature = key.sign(msg) - public_key = key.public_key() - - assert bool(public_key.verify(msg, signature)) - assert not bool(public_key.verify(msg, 1)) + self.assert_roundtrip(key) + self.assert_roundtrip(key.public_key()) diff --git a/tests/algorithms/test_RSA_compat.py b/tests/algorithms/test_RSA_compat.py new file mode 100644 index 00000000..0da03d50 --- /dev/null +++ b/tests/algorithms/test_RSA_compat.py @@ -0,0 +1,102 @@ +import pytest + +try: + from jose.backends.rsa_backend import RSAKey as PurePythonRSAKey + from jose.backends.cryptography_backend import CryptographyRSAKey + from jose.backends.pycrypto_backend import RSAKey as PyCryptoRSAKey +except ImportError: + PurePythonRSAKey = CryptographyRSAKey = PyCryptoRSAKey = None +from jose.constants import ALGORITHMS + +from .test_RSA import PRIVATE_KEYS + +CRYPTO_BACKENDS = ( + pytest.param(PurePythonRSAKey, id="python_rsa"), + pytest.param(CryptographyRSAKey, id="pyca/cryptography"), + pytest.param(PyCryptoRSAKey, id="pycrypto/dome") +) +ENCODINGS = ("PKCS1", "PKCS8") + + +@pytest.mark.backend_compatibility +@pytest.mark.skipif( + None in (PurePythonRSAKey, CryptographyRSAKey, PyCryptoRSAKey), + reason="Multiple crypto backends not available for backend compatibility tests" +) +class TestBackendRsaCompatibility(object): + + @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() diff --git a/tests/test_asn1.py b/tests/test_asn1.py new file mode 100644 index 00000000..4167ea9b --- /dev/null +++ b/tests/test_asn1.py @@ -0,0 +1,167 @@ +"""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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""" +PKCS8_PRIVATE_KEY = b"""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 +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 new file mode 100644 index 00000000..6e633a19 --- /dev/null +++ b/tests/test_backends.py @@ -0,0 +1,35 @@ +"""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 CryptographyRSAKey, CryptographyECKey +except ImportError: + CryptographyRSAKey = CryptographyECKey = None +try: + from jose.backends.pycrypto_backend import RSAKey as PyCryptoRSAKey +except ImportError: + PyCryptoRSAKey = None +try: + from jose.backends.ecdsa_backend import ECDSAECKey as PurePythonECDSAKey +except ImportError: + PurePythonRSAKey = None + +from jose.backends import ECKey, RSAKey + + +def test_default_ec_backend(): + if CryptographyECKey is not None: + assert ECKey is CryptographyECKey + else: + assert ECKey is PurePythonECDSAKey + + +def test_default_rsa_backend(): + if CryptographyRSAKey is not None: + assert RSAKey is CryptographyRSAKey + elif PyCryptoRSAKey is not None: + assert RSAKey is PyCryptoRSAKey + else: + assert RSAKey is PurePythonRSAKey diff --git a/tests/test_firebase.py b/tests/test_firebase.py index 041739bf..b31ac8fe 100644 --- a/tests/test_firebase.py +++ b/tests/test_firebase.py @@ -1,8 +1,16 @@ import json +import pytest + 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", @@ -14,6 +22,7 @@ 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): diff --git a/tests/test_jwk.py b/tests/test_jwk.py index a79dfa3c..6ea5a1b8 100644 --- a/tests/test_jwk.py +++ b/tests/test_jwk.py @@ -1,9 +1,7 @@ from jose import jwk from jose.exceptions import JWKError from jose.backends.base import Key -from jose.backends.pycrypto_backend import RSAKey -from jose.backends.cryptography_backend import CryptographyECKey -from jose.backends.ecdsa_backend import ECDSAECKey +from jose.backends import ECKey, RSAKey import pytest @@ -53,7 +51,7 @@ def test_invalid_hash_alg(self): key = RSAKey(rsa_key, 'HS512') with pytest.raises(JWKError): - key = ECDSAECKey(ec_key, 'RS512') # noqa: F841 + key = ECKey(ec_key, 'RS512') # noqa: F841 def test_invalid_jwk(self): @@ -64,7 +62,7 @@ def test_invalid_jwk(self): key = RSAKey(hmac_key, 'RS256') with pytest.raises(JWKError): - key = ECDSAECKey(rsa_key, 'ES256') # noqa: F841 + key = ECKey(rsa_key, 'ES256') # noqa: F841 def test_RSAKey_errors(self): @@ -104,9 +102,7 @@ def test_construct_from_jwk(self): assert isinstance(key, jwk.Key) def test_construct_EC_from_jwk(self): - key = CryptographyECKey(ec_key, algorithm='ES512') - assert isinstance(key, jwk.Key) - key = ECDSAECKey(ec_key, algorithm='ES512') + key = ECKey(ec_key, algorithm='ES512') assert isinstance(key, jwk.Key) def test_construct_from_jwk_missing_alg(self): diff --git a/tox.ini b/tox.ini index df48dd3f..37c57eaf 100644 --- a/tox.ini +++ b/tox.ini @@ -1,23 +1,50 @@ [tox] -envlist = py{27,34,35,36,py},flake8 +minversion = 3.4.0 +envlist = + py{27,34,35,36,py}-{base,cryptography-only,pycryptodome-norsa,pycrypto-norsa,compatibility}, + flake8 skip_missing_interpreters = True -[testenv] +[testenv:basecommand] commands = pip --version - py.test --cov-report term-missing --cov jose -deps = - six - future + py.test --cov-report term-missing --cov jose {posargs} + +[testenv:compatibility] +extras = + cryptography pycrypto - ecdsa + pycryptodome + +[testenv] +deps = pytest pytest-cov pytest-runner - cryptography +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 pycrypto or backend_compatibility)" + # Test the pyca/cryptography backend + cryptography: {[testenv:basecommand]commands} -m "not (pycryptodome or pycrypto or backend_compatibility)" + # Test the pycryptodome backend + pycryptodome: {[testenv:basecommand]commands} -m "not (cryptography or pycrypto or backend_compatibility)" + # Test the pycrypto backend + pycrypto: {[testenv:basecommand]commands} -m "not (cryptography or pycryptodome or backend_compatibility)" + # Test cross-backend compatibility and coexistence + compatibility: {[testenv:basecommand]commands} +extras = + cryptography: cryptography + pycryptodome: pycryptodome + pycrypto: pycrypto + compatibility: {[testenv:compatibility]extras} -; [testenv:flake8] -; commands = flake8 jose -; skip_install= True -; deps = -; flake8 \ No newline at end of file +[testenv:flake8] +skip_install= True +deps = + flake8 +commands = flake8 jose setup.py