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..39c2aba9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,21 +2,70 @@ # 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: + # CPython 2.7 + - python: 2.7 + env: TOXENV=py27-base + - python: 2.7 + env: TOXENV=py27-cryptography + - python: 2.7 + env: TOXENV=py27-pycryptodome + - python: 2.7 + env: TOXENV=py27-pycrypto + - python: 2.7 + env: TOXENV=py27-compatibility + # CPython 3.4 + - python: 3.4 + env: TOXENV=py34-base + - python: 3.4 + env: TOXENV=py34-cryptography + - python: 3.4 + env: TOXENV=py34-pycryptodome + - python: 3.4 + env: TOXENV=py34-pycrypto + - python: 3.4 + env: TOXENV=py34-compatibility + # CPython 3.5 + - python: 3.5 + env: TOXENV=py35-base + - python: 3.5 + env: TOXENV=py35-cryptography + - python: 3.5 + env: TOXENV=py35-pycryptodome + - python: 3.5 + env: TOXENV=py35-pycrypto + - python: 3.5 + env: TOXENV=py35-compatibility + # CPython 3.5 + - python: 3.5 + env: TOXENV=py35-base + - python: 3.5 + env: TOXENV=py35-cryptography + - python: 3.5 + env: TOXENV=py35-pycryptodome + - python: 3.5 + env: TOXENV=py35-pycrypto + - python: 3.5 + env: TOXENV=py35-compatibility + # PyPy 5.3.1 + - python: pypy-5.3.1 + env: TOXENV=pypy-base + - python: pypy-5.3.1 + env: TOXENV=pypy-cryptography + - python: pypy-5.3.1 + env: TOXENV=pypy-pycryptodome + - python: pypy-5.3.1 + env: TOXENV=pypy-pycrypto + - python: pypy-5.3.1 + env: TOXENV=pypy-compatibility # matrix: # include: # - python: 3.6 -# env: -# - TOX_ENV=flake8 -# script: tox -e $TOX_ENV \ No newline at end of file +# env: TOX_ENV=flake8 \ No newline at end of file diff --git a/jose/backends/cryptography_backend.py b/jose/backends/cryptography_backend.py index 797d7e0c..68047665 100644 --- a/jose/backends/cryptography_backend.py +++ b/jose/backends/cryptography_backend.py @@ -67,7 +67,7 @@ def __init__(self, key, algorithm, cryptography_backend=default_backend): def _process_jwk(self, jwk_dict): if not jwk_dict.get('kty') == 'EC': - raise JWKError("Incorrect key type. Expected: 'EC', Recieved: %s" % jwk_dict.get('kty')) + 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') @@ -212,7 +212,7 @@ def __init__(self, key, algorithm, cryptography_backend=default_backend): def _process_jwk(self, jwk_dict): if not jwk_dict.get('kty') == 'RSA': - raise JWKError("Incorrect key type. Expected: 'RSA', Recieved: %s" % jwk_dict.get('kty')) + raise JWKError("Incorrect key type. Expected: 'RSA', Received: %s" % jwk_dict.get('kty')) e = base64_to_long(jwk_dict.get('e', 256)) n = base64_to_long(jwk_dict.get('n')) diff --git a/setup.py b/setup.py index d21374d5..63116c23 100644 --- a/setup.py +++ b/setup.py @@ -63,7 +63,6 @@ def get_packages(package): 'pytest', 'pytest-cov', 'pytest-runner', - 'cryptography', ], install_requires=['six <2.0', 'ecdsa <1.0', 'rsa', 'future <1.0'] ) diff --git a/tests/algorithms/test_EC.py b/tests/algorithms/test_EC.py index d20d9c0b..2f9b72a2 100644 --- a/tests/algorithms/test_EC.py +++ b/tests/algorithms/test_EC.py @@ -2,10 +2,18 @@ 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 +except ImportError: + CryptographyECKey = None -import ecdsa import pytest private_key = """-----BEGIN EC PRIVATE KEY----- @@ -14,69 +22,78 @@ 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----- +""" + + +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 -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.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() + + +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 +104,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 +145,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..fd43c80d --- /dev/null +++ b/tests/algorithms/test_EC_compat.py @@ -0,0 +1,33 @@ +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 TestBackendRsaCompatibility(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) diff --git a/tests/algorithms/test_RSA.py b/tests/algorithms/test_RSA.py index eb957dfa..894e3aac 100644 --- a/tests/algorithms/test_RSA.py +++ b/tests/algorithms/test_RSA.py @@ -1,14 +1,20 @@ - 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 Crypto.PublicKey import RSA +except ImportError: + RSA = None + +try: + from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.primitives.asymmetric import rsa +except ImportError: + default_backend = rsa = 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. @@ -69,65 +75,80 @@ -----END RSA PRIVATE KEY-----""" -class TestRSAAlgorithm: +@pytest.mark.pycrypto +@pytest.mark.pycryptodome +@pytest.mark.skipif(RSA is None, reason="Pycrypto/dome backend not available") +def test_pycrypto_RSA_key_instance(): + key = RSA.construct((long( + 26057131595212989515105618545799160306093557851986992545257129318694524535510983041068168825614868056510242030438003863929818932202262132630250203397069801217463517914103389095129323580576852108653940669240896817348477800490303630912852266209307160550655497615975529276169196271699168537716821419779900117025818140018436554173242441334827711966499484119233207097432165756707507563413323850255548329534279691658369466534587631102538061857114141268972476680597988266772849780811214198186940677291891818952682545840788356616771009013059992237747149380197028452160324144544057074406611859615973035412993832273216732343819), + long(65537))) + RSAKey(key, ALGORITHMS.RS256) + +# TODO: Unclear why this test was marked as only for pycrypto +@pytest.mark.pycrypto +@pytest.mark.pycryptodome +def test_pycrypto_unencoded_cleartext(): + 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)) - @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) +@pytest.mark.cryptography +@pytest.mark.skipif( + None in (default_backend, rsa), + reason="Cryptography backend not available" +) +def test_cryptography_RSA_key_instance(): - def test_cryptography_RSA_key_instance(self): - from cryptography.hazmat.backends import default_backend - from cryptography.hazmat.primitives.asymmetric import rsa + key = rsa.RSAPublicNumbers( + long(65537), + long(26057131595212989515105618545799160306093557851986992545257129318694524535510983041068168825614868056510242030438003863929818932202262132630250203397069801217463517914103389095129323580576852108653940669240896817348477800490303630912852266209307160550655497615975529276169196271699168537716821419779900117025818140018436554173242441334827711966499484119233207097432165756707507563413323850255548329534279691658369466534587631102538061857114141268972476680597988266772849780811214198186940677291891818952682545840788356616771009013059992237747149380197028452160324144544057074406611859615973035412993832273216732343819), + ).public_key(default_backend()) - key = rsa.RSAPublicNumbers( - long(65537), - long(26057131595212989515105618545799160306093557851986992545257129318694524535510983041068168825614868056510242030438003863929818932202262132630250203397069801217463517914103389095129323580576852108653940669240896817348477800490303630912852266209307160550655497615975529276169196271699168537716821419779900117025818140018436554173242441334827711966499484119233207097432165756707507563413323850255548329534279691658369466534587631102538061857114141268972476680597988266772849780811214198186940677291891818952682545840788356616771009013059992237747149380197028452160324144544057074406611859615973035412993832273216732343819), - ).public_key(default_backend()) + pubkey = RSAKey(key, ALGORITHMS.RS256) + assert pubkey.is_public() - pubkey = CryptographyRSAKey(key, ALGORITHMS.RS256) - assert pubkey.is_public() + pem = pubkey.to_pem() + assert pem.startswith(b'-----BEGIN PUBLIC KEY-----') - pem = pubkey.to_pem() - assert pem.startswith(b'-----BEGIN PUBLIC KEY-----') - @pytest.mark.parametrize("Backend", [RSAKey, CryptographyRSAKey, PurePythonRSAKey]) - def test_string_secret(self, Backend): +class TestRSAAlgorithm: + def test_RSA_key(self): + assert not RSAKey(private_key, ALGORITHMS.RS256).is_public() + + 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, 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 +163,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,44 +177,26 @@ 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) + def test_get_public_key(self): + 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) + def test_to_pem(self): + key = RSAKey(private_key, ALGORITHMS.RS256) assert key.to_pem(pem_format='PKCS1').strip() == private_key.strip() pkcs8 = key.to_pem(pem_format='PKCS8').strip() assert pkcs8 != private_key.strip() - newkey = Backend(pkcs8, ALGORITHMS.RS256) + newkey = RSAKey(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 - - 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() - def assert_parameters(self, as_dict, private): assert isinstance(as_dict, dict) @@ -218,43 +221,15 @@ 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) + def test_to_dict(self): + 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..c2359c2d --- /dev/null +++ b/tests/algorithms/test_RSA_compat.py @@ -0,0 +1,50 @@ +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 +except ImportError: + PurePythonRSAKey = CryptographyRSAKey = RSAKey = None +from jose.constants import ALGORITHMS + +from .test_RSA import private_key + + +@pytest.mark.backend_compatibility +@pytest.mark.skipif( + None in (PurePythonRSAKey, CryptographyRSAKey, RSAKey), + reason="Multiple crypto backends not available for backend compatibility tests" +) +class TestBackendRsaCompatibility(object): + + @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("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 + + 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() 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..605869d3 100644 --- a/tox.ini +++ b/tox.ini @@ -1,20 +1,39 @@ [tox] -envlist = py{27,34,35,36,py},flake8 +envlist = py{27,34,35,36,py}-{base,cryptography,pycryptodome,pycrypto,compatibility},flake8 skip_missing_interpreters = True -[testenv] +[testenv:basecommand] commands = pip --version - py.test --cov-report term-missing --cov jose + py.test --cov-report term-missing --cov jose {posargs} + +[testenv:compatibility] +deps = + cryptography + pycrypto >=2.6.0, <2.7.0 + pycryptodome >=3.3.1, <4.0.0 + +[testenv] deps = - six - future - pycrypto - ecdsa pytest pytest-cov pytest-runner - cryptography + compatibility: {[testenv:compatibility]deps} +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 ; [testenv:flake8] ; commands = flake8 jose