From 0861744f3de92130f3f94c1e4155469b666838d5 Mon Sep 17 00:00:00 2001 From: mattsb42-aws Date: Sat, 25 Aug 2018 13:13:50 -0700 Subject: [PATCH 01/43] Initial work to enable testing of isolated cryptographic backends. NOTE: This identified a few bugs with the python-rsa and pyca/cryptography backends. Those will fail for now. --- setup.py | 1 - tests/algorithms/test_EC.py | 128 ++++++++++---------- tests/algorithms/test_EC_compat.py | 33 ++++++ tests/algorithms/test_RSA.py | 175 ++++++++++++---------------- tests/algorithms/test_RSA_compat.py | 50 ++++++++ tests/test_jwk.py | 12 +- tox.ini | 35 ++++-- 7 files changed, 253 insertions(+), 181 deletions(-) create mode 100644 tests/algorithms/test_EC_compat.py create mode 100644 tests/algorithms/test_RSA_compat.py 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..12a9314a 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,pyca,dome,pycrypto},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 From c9a36977d46d8edf9a74c3421724de4bf4f0fc3c Mon Sep 17 00:00:00 2001 From: mattsb42-aws Date: Sat, 25 Aug 2018 13:16:28 -0700 Subject: [PATCH 02/43] add git ignores for PyCharm, PyEnv, and PyTest metadata --- .gitignore | 9 +++++++++ 1 file changed, 9 insertions(+) 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/ From e63c6c97517893bd1a4a99635ad40b2f2077a3ec Mon Sep 17 00:00:00 2001 From: mattsb42-aws Date: Fri, 7 Sep 2018 15:33:06 -0700 Subject: [PATCH 03/43] update tox envlist with the new testenv names --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 12a9314a..605869d3 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py{27,34,35,36,py}-{base,pyca,dome,pycrypto},flake8 +envlist = py{27,34,35,36,py}-{base,cryptography,pycryptodome,pycrypto,compatibility},flake8 skip_missing_interpreters = True [testenv:basecommand] From 2954bacddbe241c5de47964d6b909e970c793aee Mon Sep 17 00:00:00 2001 From: mattsb42-aws Date: Mon, 10 Sep 2018 01:53:05 -0700 Subject: [PATCH 04/43] update Travis config to run the explicit backend tests --- .travis.yml | 67 ++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 58 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 716a5574..1ff95c8e 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-pycryptodome + - 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-pycryptodome + - 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-pycryptodome + - 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-pycryptodome + - 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-pycryptodome + - 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 From 4d7b59833223c630ec28035029a0212499aeb1e9 Mon Sep 17 00:00:00 2001 From: mattsb42-aws Date: Mon, 10 Sep 2018 02:09:44 -0700 Subject: [PATCH 05/43] fix typo that duplicated pycryptodome tests in Travis --- .travis.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1ff95c8e..39c2aba9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,7 +18,7 @@ matrix: - python: 2.7 env: TOXENV=py27-pycryptodome - python: 2.7 - env: TOXENV=py27-pycryptodome + env: TOXENV=py27-pycrypto - python: 2.7 env: TOXENV=py27-compatibility # CPython 3.4 @@ -29,7 +29,7 @@ matrix: - python: 3.4 env: TOXENV=py34-pycryptodome - python: 3.4 - env: TOXENV=py34-pycryptodome + env: TOXENV=py34-pycrypto - python: 3.4 env: TOXENV=py34-compatibility # CPython 3.5 @@ -40,7 +40,7 @@ matrix: - python: 3.5 env: TOXENV=py35-pycryptodome - python: 3.5 - env: TOXENV=py35-pycryptodome + env: TOXENV=py35-pycrypto - python: 3.5 env: TOXENV=py35-compatibility # CPython 3.5 @@ -51,7 +51,7 @@ matrix: - python: 3.5 env: TOXENV=py35-pycryptodome - python: 3.5 - env: TOXENV=py35-pycryptodome + env: TOXENV=py35-pycrypto - python: 3.5 env: TOXENV=py35-compatibility # PyPy 5.3.1 @@ -62,7 +62,7 @@ matrix: - python: pypy-5.3.1 env: TOXENV=pypy-pycryptodome - python: pypy-5.3.1 - env: TOXENV=pypy-pycryptodome + env: TOXENV=pypy-pycrypto - python: pypy-5.3.1 env: TOXENV=pypy-compatibility # matrix: From d6d2b30cddb6ea628f4d4dbe9b521af52947dc0c Mon Sep 17 00:00:00 2001 From: mattsb42-aws Date: Mon, 24 Dec 2018 18:43:50 -0800 Subject: [PATCH 06/43] import rsa backends with backend-explicit naming to avoid confusion --- tests/algorithms/test_RSA.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/algorithms/test_RSA.py b/tests/algorithms/test_RSA.py index 894e3aac..4af2e61e 100644 --- a/tests/algorithms/test_RSA.py +++ b/tests/algorithms/test_RSA.py @@ -1,15 +1,15 @@ import sys try: - from Crypto.PublicKey import RSA + from Crypto.PublicKey import RSA as PyCryptoRSA except ImportError: - RSA = None + PyCryptoRSA = None try: from cryptography.hazmat.backends import default_backend - from cryptography.hazmat.primitives.asymmetric import rsa + from cryptography.hazmat.primitives.asymmetric import rsa as pyca_rsa except ImportError: - default_backend = rsa = None + default_backend = pyca_rsa = None from jose.backends import RSAKey from jose.constants import ALGORITHMS @@ -77,9 +77,9 @@ @pytest.mark.pycrypto @pytest.mark.pycryptodome -@pytest.mark.skipif(RSA is None, reason="Pycrypto/dome backend not available") +@pytest.mark.skipif(PyCryptoRSA is None, reason="Pycrypto/dome backend not available") def test_pycrypto_RSA_key_instance(): - key = RSA.construct((long( + key = PyCryptoRSA.construct((long( 26057131595212989515105618545799160306093557851986992545257129318694524535510983041068168825614868056510242030438003863929818932202262132630250203397069801217463517914103389095129323580576852108653940669240896817348477800490303630912852266209307160550655497615975529276169196271699168537716821419779900117025818140018436554173242441334827711966499484119233207097432165756707507563413323850255548329534279691658369466534587631102538061857114141268972476680597988266772849780811214198186940677291891818952682545840788356616771009013059992237747149380197028452160324144544057074406611859615973035412993832273216732343819), long(65537))) RSAKey(key, ALGORITHMS.RS256) @@ -99,12 +99,12 @@ def test_pycrypto_unencoded_cleartext(): @pytest.mark.cryptography @pytest.mark.skipif( - None in (default_backend, rsa), + None in (default_backend, pyca_rsa), reason="Cryptography backend not available" ) def test_cryptography_RSA_key_instance(): - key = rsa.RSAPublicNumbers( + key = pyca_rsa.RSAPublicNumbers( long(65537), long(26057131595212989515105618545799160306093557851986992545257129318694524535510983041068168825614868056510242030438003863929818932202262132630250203397069801217463517914103389095129323580576852108653940669240896817348477800490303630912852266209307160550655497615975529276169196271699168537716821419779900117025818140018436554173242441334827711966499484119233207097432165756707507563413323850255548329534279691658369466534587631102538061857114141268972476680597988266772849780811214198186940677291891818952682545840788356616771009013059992237747149380197028452160324144544057074406611859615973035412993832273216732343819), ).public_key(default_backend()) From 5ee69d8776aeb062f38df4f50310d61d107d2efb Mon Sep 17 00:00:00 2001 From: mattsb42-aws Date: Mon, 24 Dec 2018 18:57:06 -0800 Subject: [PATCH 07/43] make backend-explicit tests use backend-explicit keys test_pycrypto_RSA_key_instance and test_cryptography_RSA_key_instance both test compatibility of the backend-explicit RSAKey classes with the backend-native RSA key structures. As such, these tests must use the backend-explicit RSAKey classes rather than the default loaded from jose.backends to avoid breaking when multiple backends are present. --- tests/algorithms/test_RSA.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/tests/algorithms/test_RSA.py b/tests/algorithms/test_RSA.py index 4af2e61e..ad1cef68 100644 --- a/tests/algorithms/test_RSA.py +++ b/tests/algorithms/test_RSA.py @@ -2,14 +2,16 @@ try: from Crypto.PublicKey import RSA as PyCryptoRSA + from jose.backends.pycrypto_backend import RSAKey as PyCryptoRSAKey except ImportError: - PyCryptoRSA = None + 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 = None + default_backend = pyca_rsa = CryptographyRSAKey = None from jose.backends import RSAKey from jose.constants import ALGORITHMS @@ -77,12 +79,12 @@ @pytest.mark.pycrypto @pytest.mark.pycryptodome -@pytest.mark.skipif(PyCryptoRSA is None, reason="Pycrypto/dome backend not available") +@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))) - RSAKey(key, ALGORITHMS.RS256) + PyCryptoRSAKey(key, ALGORITHMS.RS256) # TODO: Unclear why this test was marked as only for pycrypto @pytest.mark.pycrypto @@ -99,7 +101,7 @@ def test_pycrypto_unencoded_cleartext(): @pytest.mark.cryptography @pytest.mark.skipif( - None in (default_backend, pyca_rsa), + None in (default_backend, pyca_rsa, CryptographyRSAKey), reason="Cryptography backend not available" ) def test_cryptography_RSA_key_instance(): @@ -109,7 +111,7 @@ def test_cryptography_RSA_key_instance(): long(26057131595212989515105618545799160306093557851986992545257129318694524535510983041068168825614868056510242030438003863929818932202262132630250203397069801217463517914103389095129323580576852108653940669240896817348477800490303630912852266209307160550655497615975529276169196271699168537716821419779900117025818140018436554173242441334827711966499484119233207097432165756707507563413323850255548329534279691658369466534587631102538061857114141268972476680597988266772849780811214198186940677291891818952682545840788356616771009013059992237747149380197028452160324144544057074406611859615973035412993832273216732343819), ).public_key(default_backend()) - pubkey = RSAKey(key, ALGORITHMS.RS256) + pubkey = CryptographyRSAKey(key, ALGORITHMS.RS256) assert pubkey.is_public() pem = pubkey.to_pem() From 9b6fa7ab18702cdf6fab30093b2480f06ba50c55 Mon Sep 17 00:00:00 2001 From: mattsb42-aws Date: Mon, 24 Dec 2018 22:18:14 -0800 Subject: [PATCH 08/43] add ability to remove base crypto backend for tests commands_pre requires tox >= 3.4.0 --- tox.ini | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 605869d3..14bca5bc 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,8 @@ [tox] -envlist = py{27,34,35,36,py}-{base,cryptography,pycryptodome,pycrypto,compatibility},flake8 +minversion = 3.4.0 +envlist = + py{27,34,35,36,py}-{base,cryptography-only,pycryptodome,pycrypto,compatibility}, + flake8 skip_missing_interpreters = True [testenv:basecommand] @@ -19,6 +22,9 @@ deps = pytest-cov pytest-runner compatibility: {[testenv:compatibility]deps} +commands_pre = + # Remove the python-rsa backend + only: pip uninstall -y ecdsa rsa commands = # Test the python-rsa backend base: {[testenv:basecommand]commands} -m "not (cryptography or pycryptodome or pycrypto or backend_compatibility)" From 28dcc70df19046964d4964c4a8d6df20a754c4b5 Mon Sep 17 00:00:00 2001 From: mattsb42-aws Date: Mon, 24 Dec 2018 22:20:02 -0800 Subject: [PATCH 09/43] update Travis CI to run cryptography-only --- .travis.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 39c2aba9..7dc5fb2b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,7 @@ matrix: - python: 2.7 env: TOXENV=py27-base - python: 2.7 - env: TOXENV=py27-cryptography + env: TOXENV=py27-cryptography-only - python: 2.7 env: TOXENV=py27-pycryptodome - python: 2.7 @@ -25,7 +25,7 @@ matrix: - python: 3.4 env: TOXENV=py34-base - python: 3.4 - env: TOXENV=py34-cryptography + env: TOXENV=py34-cryptography-only - python: 3.4 env: TOXENV=py34-pycryptodome - python: 3.4 @@ -36,7 +36,7 @@ matrix: - python: 3.5 env: TOXENV=py35-base - python: 3.5 - env: TOXENV=py35-cryptography + env: TOXENV=py35-cryptography-only - python: 3.5 env: TOXENV=py35-pycryptodome - python: 3.5 @@ -47,7 +47,7 @@ matrix: - python: 3.5 env: TOXENV=py35-base - python: 3.5 - env: TOXENV=py35-cryptography + env: TOXENV=py35-cryptography-only - python: 3.5 env: TOXENV=py35-pycryptodome - python: 3.5 @@ -58,7 +58,7 @@ matrix: - python: pypy-5.3.1 env: TOXENV=pypy-base - python: pypy-5.3.1 - env: TOXENV=pypy-cryptography + env: TOXENV=pypy-cryptography-only - python: pypy-5.3.1 env: TOXENV=pypy-pycryptodome - python: pypy-5.3.1 From 92226735c475397c13e18ebfcf61d3442dad4824 Mon Sep 17 00:00:00 2001 From: mattsb42-aws Date: Mon, 24 Dec 2018 22:45:36 -0800 Subject: [PATCH 10/43] isolate ecdsa key class imports to allow for missing library --- jose/backends/cryptography_backend.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/jose/backends/cryptography_backend.py b/jose/backends/cryptography_backend.py index 68047665..371ead7f 100644 --- a/jose/backends/cryptography_backend.py +++ b/jose/backends/cryptography_backend.py @@ -1,5 +1,9 @@ import six -import ecdsa + +try: + from ecdsa import SigningKey as EcdsaSigningKey, VerifyingKey as EcdsaVerifyingKey +except ImportError: + SigningKey = VerifyingKey = None from ecdsa.util import sigdecode_string, sigencode_string, sigdecode_der, sigencode_der from jose.backends.base import Key @@ -37,7 +41,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') From 04ef8b580953892c73fef9e3139aa3eac7619ffb Mon Sep 17 00:00:00 2001 From: mattsb42-aws Date: Tue, 25 Dec 2018 10:46:19 -0800 Subject: [PATCH 11/43] isolate asn1/der signature transformation to simplify testing --- jose/backends/cryptography_backend.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/jose/backends/cryptography_backend.py b/jose/backends/cryptography_backend.py index 371ead7f..ee367ca8 100644 --- a/jose/backends/cryptography_backend.py +++ b/jose/backends/cryptography_backend.py @@ -94,19 +94,27 @@ def _process_jwk(self, jwk_dict): else: return public.public_key(self.cryptography_backend()) + def _der_to_asn1(self, der_signature): + """Convert signature from DER encoding to ASN1 encoding.""" + order = (2 ** self.prepared_key.curve.key_size) - 1 + return sigencode_string(*sigdecode_der(der_signature, order), order=order) + + def _asn1_to_der(self, asn1_signature): + """Convert signature from ASN1 encoding to DER encoding.""" + order = (2 ** self.prepared_key.curve.key_size) - 1 + return sigencode_der(*sigdecode_string(asn1_signature, order), order=order) + 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_asn1(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._asn1_to_der(sig) self.prepared_key.verify(signature, msg, ec.ECDSA(self.hash_alg())) return True except Exception: From cea6ae82f31c0fea863f2bc28a17ba31ef3e61cb Mon Sep 17 00:00:00 2001 From: mattsb42-aws Date: Tue, 25 Dec 2018 10:57:55 -0800 Subject: [PATCH 12/43] add tests to verify der/asn1 conversions --- tests/algorithms/test_EC.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/algorithms/test_EC.py b/tests/algorithms/test_EC.py index 2f9b72a2..9fa6f0e6 100644 --- a/tests/algorithms/test_EC.py +++ b/tests/algorithms/test_EC.py @@ -31,6 +31,18 @@ -----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" +) +ASN1_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.""" @@ -51,6 +63,20 @@ def test_key_from_ecdsa(): assert not ECKey(key, ALGORITHMS.ES256).is_public() +@pytest.mark.cryptography +@pytest.mark.skipif(CryptographyECKey is None, reason="pyca/cryptography backend not available") +def test_cryptograhy_der_to_asn1(): + key = CryptographyECKey(private_key, ALGORITHMS.ES256) + assert key._der_to_asn1(DER_SIGNATURE) == ASN1_SIGNATURE + + +@pytest.mark.cryptography +@pytest.mark.skipif(CryptographyECKey is None, reason="pyca/cryptography backend not available") +def test_cryptograhy_asn1_to_der(): + key = CryptographyECKey(private_key, ALGORITHMS.ES256) + assert key._asn1_to_der(ASN1_SIGNATURE) == DER_SIGNATURE + + class TestECAlgorithm: def test_key_from_pem(self): From e786a0e6ed9058fae97c6e3d33b87e790eeecdc1 Mon Sep 17 00:00:00 2001 From: mattsb42-aws Date: Tue, 25 Dec 2018 12:05:33 -0800 Subject: [PATCH 13/43] remove dependency of pyca/cryptography backend on python-ecdsa --- jose/backends/cryptography_backend.py | 32 ++++++++++++++++++++++----- tests/algorithms/test_EC.py | 25 ++++++++++++++++++++- 2 files changed, 50 insertions(+), 7 deletions(-) diff --git a/jose/backends/cryptography_backend.py b/jose/backends/cryptography_backend.py index ee367ca8..daf0b5b6 100644 --- a/jose/backends/cryptography_backend.py +++ b/jose/backends/cryptography_backend.py @@ -1,10 +1,13 @@ +from __future__ import division + +import math + import six try: from ecdsa import SigningKey as EcdsaSigningKey, VerifyingKey as EcdsaVerifyingKey except ImportError: - SigningKey = VerifyingKey = None -from ecdsa.util import sigdecode_string, sigencode_string, sigdecode_der, sigencode_der + EcdsaSigningKey = EcdsaVerifyingKey = None from jose.backends.base import Key from jose.utils import base64_to_long, long_to_base64 @@ -15,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 @@ -94,15 +99,30 @@ 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 math.ceil(self.prepared_key.key_size / 8.0) + def _der_to_asn1(self, der_signature): """Convert signature from DER encoding to ASN1 encoding.""" - order = (2 ** self.prepared_key.curve.key_size) - 1 - return sigencode_string(*sigdecode_der(der_signature, order), order=order) + 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 _asn1_to_der(self, asn1_signature): """Convert signature from ASN1 encoding to DER encoding.""" - order = (2 ** self.prepared_key.curve.key_size) - 1 - return sigencode_der(*sigdecode_string(asn1_signature, order), order=order) + component_length = self._sig_component_length() + if len(asn1_signature) != int(2 * component_length): + raise ValueError("Invalid signature") + + r_bytes = asn1_signature[:component_length] + s_bytes = asn1_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: diff --git a/tests/algorithms/test_EC.py b/tests/algorithms/test_EC.py index 9fa6f0e6..afa6b295 100644 --- a/tests/algorithms/test_EC.py +++ b/tests/algorithms/test_EC.py @@ -11,8 +11,10 @@ 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 = None + CryptographyECKey = CryptographyEc = CryptographyBackend = None import pytest @@ -63,6 +65,27 @@ def test_key_from_ecdsa(): 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_asn1(): From 93a3c0a2bef7c9fee6fddf8e3cb4cbc9abd94326 Mon Sep 17 00:00:00 2001 From: mattsb42-aws Date: Tue, 25 Dec 2018 12:17:59 -0800 Subject: [PATCH 14/43] JOSE ECDSA signature encoding is raw encoding, not ASN1 --- jose/backends/cryptography_backend.py | 18 +++++++++--------- tests/algorithms/test_EC.py | 10 +++++----- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/jose/backends/cryptography_backend.py b/jose/backends/cryptography_backend.py index daf0b5b6..b97f68d2 100644 --- a/jose/backends/cryptography_backend.py +++ b/jose/backends/cryptography_backend.py @@ -106,20 +106,20 @@ def _sig_component_length(self): """ return math.ceil(self.prepared_key.key_size / 8.0) - def _der_to_asn1(self, der_signature): - """Convert signature from DER encoding to ASN1 encoding.""" + 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 _asn1_to_der(self, asn1_signature): - """Convert signature from ASN1 encoding to DER encoding.""" + def _raw_to_der(self, raw_signature): + """Convert signature from RAW encoding to DER encoding.""" component_length = self._sig_component_length() - if len(asn1_signature) != int(2 * component_length): + if len(raw_signature) != int(2 * component_length): raise ValueError("Invalid signature") - r_bytes = asn1_signature[:component_length] - s_bytes = asn1_signature[component_length:] + 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) @@ -130,11 +130,11 @@ def sign(self, msg): "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())) - return self._der_to_asn1(signature) + return self._der_to_raw(signature) def verify(self, msg, sig): try: - signature = self._asn1_to_der(sig) + signature = self._raw_to_der(sig) self.prepared_key.verify(signature, msg, ec.ECDSA(self.hash_alg())) return True except Exception: diff --git a/tests/algorithms/test_EC.py b/tests/algorithms/test_EC.py index afa6b295..7f012afb 100644 --- a/tests/algorithms/test_EC.py +++ b/tests/algorithms/test_EC.py @@ -39,7 +39,7 @@ 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" ) -ASN1_SIGNATURE = ( +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" @@ -88,16 +88,16 @@ def test_cryptography_sig_component_length(algorithm, expected_length): @pytest.mark.cryptography @pytest.mark.skipif(CryptographyECKey is None, reason="pyca/cryptography backend not available") -def test_cryptograhy_der_to_asn1(): +def test_cryptograhy_der_to_raw(): key = CryptographyECKey(private_key, ALGORITHMS.ES256) - assert key._der_to_asn1(DER_SIGNATURE) == ASN1_SIGNATURE + assert key._der_to_raw(DER_SIGNATURE) == RAW_SIGNATURE @pytest.mark.cryptography @pytest.mark.skipif(CryptographyECKey is None, reason="pyca/cryptography backend not available") -def test_cryptograhy_asn1_to_der(): +def test_cryptograhy_raw_to_der(): key = CryptographyECKey(private_key, ALGORITHMS.ES256) - assert key._asn1_to_der(ASN1_SIGNATURE) == DER_SIGNATURE + assert key._raw_to_der(RAW_SIGNATURE) == DER_SIGNATURE class TestECAlgorithm: From e2189cb766de250644716006f75da8035d3523ed Mon Sep 17 00:00:00 2001 From: mattsb42-aws Date: Tue, 25 Dec 2018 12:18:38 -0800 Subject: [PATCH 15/43] math.ceil returns a float in Python 2 --- jose/backends/cryptography_backend.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jose/backends/cryptography_backend.py b/jose/backends/cryptography_backend.py index b97f68d2..b9bdc0dc 100644 --- a/jose/backends/cryptography_backend.py +++ b/jose/backends/cryptography_backend.py @@ -104,7 +104,7 @@ def _sig_component_length(self): This is the number of bytes required to encode the maximum key value. """ - return math.ceil(self.prepared_key.key_size / 8.0) + 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.""" From 26e42c0f681985791c46879537e8b1c221ae6bb5 Mon Sep 17 00:00:00 2001 From: mattsb42-aws Date: Tue, 25 Dec 2018 13:13:19 -0800 Subject: [PATCH 16/43] disable Firebase tests on python-rsa backend Python-rsa does not support certificates. --- tests/test_firebase.py | 9 +++++++++ 1 file changed, 9 insertions(+) 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): From eaa63837d50d86ac38a03c0cfd4ad38ea412482b Mon Sep 17 00:00:00 2001 From: mattsb42-aws Date: Wed, 26 Dec 2018 13:16:54 -0800 Subject: [PATCH 17/43] expand RSA and ECDSA compatibility tests to validate serialization compatibility between backends --- tests/algorithms/test_EC_compat.py | 41 ++++++++++++++- tests/algorithms/test_RSA_compat.py | 79 +++++++++++++++++++++++------ 2 files changed, 103 insertions(+), 17 deletions(-) diff --git a/tests/algorithms/test_EC_compat.py b/tests/algorithms/test_EC_compat.py index fd43c80d..1fc6dab3 100644 --- a/tests/algorithms/test_EC_compat.py +++ b/tests/algorithms/test_EC_compat.py @@ -15,7 +15,7 @@ None in (ECDSAECKey, CryptographyECKey), reason="Multiple crypto backends not available for backend compatibility tests" ) -class TestBackendRsaCompatibility(object): +class TestBackendEcdsaCompatibility(object): @pytest.mark.parametrize("BackendSign", [ECDSAECKey, CryptographyECKey]) @pytest.mark.parametrize("BackendVerify", [ECDSAECKey, CryptographyECKey]) @@ -31,3 +31,42 @@ def test_signing_parity(self, BackendSign, BackendVerify): # 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_compat.py b/tests/algorithms/test_RSA_compat.py index c2359c2d..2882f700 100644 --- a/tests/algorithms/test_RSA_compat.py +++ b/tests/algorithms/test_RSA_compat.py @@ -3,23 +3,30 @@ try: from jose.backends.rsa_backend import RSAKey as PurePythonRSAKey from jose.backends.cryptography_backend import CryptographyRSAKey - from jose.backends.pycrypto_backend import RSAKey + from jose.backends.pycrypto_backend import RSAKey as PyCryptoRSAKey except ImportError: - PurePythonRSAKey = CryptographyRSAKey = RSAKey = None + PurePythonRSAKey = CryptographyRSAKey = PyCryptoRSAKey = None from jose.constants import ALGORITHMS from .test_RSA import private_key +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, RSAKey), + None in (PurePythonRSAKey, CryptographyRSAKey, PyCryptoRSAKey), 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]) + @pytest.mark.parametrize("BackendSign", CRYPTO_BACKENDS) + @pytest.mark.parametrize("BackendVerify", CRYPTO_BACKENDS) def test_signing_parity(self, BackendSign, BackendVerify): key_sign = BackendSign(private_key, ALGORITHMS.RS256) key_verify = BackendVerify(private_key, ALGORITHMS.RS256).public_key() @@ -33,18 +40,58 @@ def test_signing_parity(self, BackendSign, BackendVerify): # 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): + @pytest.mark.parametrize("encoding", ENCODINGS) + @pytest.mark.parametrize("BackendFrom", CRYPTO_BACKENDS) + @pytest.mark.parametrize("BackendTo", CRYPTO_BACKENDS) + def test_public_key_to_pem(self, BackendFrom, BackendTo, encoding): + 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) + def test_private_key_to_pem(self, BackendFrom, BackendTo, encoding): + 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) + def test_public_key_load_cycle(self, BackendFrom, BackendTo, encoding_save, encoding_load): + 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) + def test_private_key_load_cycle(self, BackendFrom, BackendTo, encoding_save, encoding_load): 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 + pem_reference = key.to_pem(pem_format=encoding_save).strip() + pem_load = key.to_pem(pem_format=encoding_load).strip() - pub1 = BackendTo(pkcs1_pub, ALGORITHMS.RS256) - pub8 = BackendTo(pkcs8_pub, ALGORITHMS.RS256) + key_2 = BackendTo(pem_load, ALGORITHMS.RS256) - assert pkcs8_pub == pub1.to_pem(pem_format='PKCS8').strip() - assert pkcs1_pub == pub8.to_pem(pem_format='PKCS1').strip() + assert pem_reference == key_2.to_pem(encoding_save).strip() From 130c383fd064185c14a3fbcf32ccb95d9ae5b69b Mon Sep 17 00:00:00 2001 From: mattsb42-aws Date: Wed, 26 Dec 2018 13:20:49 -0800 Subject: [PATCH 18/43] add 2048-bit RSA private key for tests --- tests/algorithms/test_RSA.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/algorithms/test_RSA.py b/tests/algorithms/test_RSA.py index 894e3aac..7cc25e8c 100644 --- a/tests/algorithms/test_RSA.py +++ b/tests/algorithms/test_RSA.py @@ -73,6 +73,35 @@ bjJ/JfTO5060SsWftf4iw3jrhSn9RwTTYdq/kErGFWvDGJn2MiuhMe2onNfVzIGR mdUxHwi1ulkspAn/fmY7f0hZpskDwcHyZmbKZuk+NU/FJ8IAcmvk9y7m25nSSc8= -----END RSA PRIVATE KEY-----""" +private_key_2048 = 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_key_4096 = private_key +PRIVATE_KEYS = (private_key_2048, private_key_4096) @pytest.mark.pycrypto From 939950d7e2734b3dbc5a9fbe4b818b5776232d85 Mon Sep 17 00:00:00 2001 From: mattsb42-aws Date: Wed, 26 Dec 2018 13:36:36 -0800 Subject: [PATCH 19/43] expand all tests that use an RSA private key to test with both sizes --- tests/algorithms/test_RSA.py | 33 +++++++++++++++++------------ tests/algorithms/test_RSA_compat.py | 17 +++++++++------ 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/tests/algorithms/test_RSA.py b/tests/algorithms/test_RSA.py index 7cc25e8c..57144cc4 100644 --- a/tests/algorithms/test_RSA.py +++ b/tests/algorithms/test_RSA.py @@ -22,7 +22,7 @@ if sys.version_info > (3,): long = int -private_key = b"""-----BEGIN RSA PRIVATE KEY----- +private_key_4096 = b"""-----BEGIN RSA PRIVATE KEY----- MIIJKwIBAAKCAgEAtSKfSeI0fukRIX38AHlKB1YPpX8PUYN2JdvfM+XjNmLfU1M7 4N0VmdzIX95sneQGO9kC2xMIE+AIlt52Yf/KgBZggAlS9Y0Vx8DsSL2HvOjguAdX ir3vYLvAyyHin/mUisJOqccFKChHKjnk0uXy/38+1r17/cYTp76brKpU1I4kM20M @@ -100,8 +100,10 @@ dXI4NN9leDeIpNaGU6ozr+At3f50GtCWxdUppy9FDh5qDamBV8K4/+uNqFPiKFQ9 uwNcJ8daMgVZ0QBrD3CBcSZQrfC484BlV6spJ3C16qDVSQPt7sAI -----END RSA PRIVATE KEY-----""" -private_key_4096 = private_key -PRIVATE_KEYS = (private_key_2048, private_key_4096) +PRIVATE_KEYS = ( + pytest.param(private_key_2048, id="RSA-2048"), + pytest.param(private_key_4096, id="RSA-4096") +) @pytest.mark.pycrypto @@ -113,10 +115,12 @@ def test_pycrypto_RSA_key_instance(): 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(): +@pytest.mark.parametrize("private_key", PRIVATE_KEYS) +def test_pycrypto_unencoded_cleartext(private_key): key = RSAKey(private_key, ALGORITHMS.RS256) msg = b'test' signature = key.sign(msg) @@ -147,7 +151,7 @@ def test_cryptography_RSA_key_instance(): class TestRSAAlgorithm: def test_RSA_key(self): - assert not RSAKey(private_key, ALGORITHMS.RS256).is_public() + assert not RSAKey(private_key_4096, ALGORITHMS.RS256).is_public() def test_string_secret(self): key = 'secret' @@ -166,7 +170,7 @@ def test_bad_cert(self,): def test_invalid_algorithm(self): with pytest.raises(JWKError): - RSAKey(private_key, ALGORITHMS.ES256) + RSAKey(private_key_4096, ALGORITHMS.ES256) with pytest.raises(JWKError): RSAKey({'kty': 'bla'}, ALGORITHMS.RS256) @@ -208,7 +212,8 @@ def test_RSA_jwk(self): # None of the extra parameters are present, but 'key' is still private. assert not RSAKey(key, ALGORITHMS.RS256).is_public() - def test_get_public_key(self): + @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() @@ -216,15 +221,16 @@ def test_get_public_key(self): assert public_key2.is_public() assert public_key == public_key2 - def test_to_pem(self): - key = RSAKey(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() + assert pkcs8 != pkey.strip() newkey = RSAKey(pkcs8, ALGORITHMS.RS256) - assert newkey.to_pem(pem_format='PKCS1').strip() == private_key.strip() + assert newkey.to_pem(pem_format='PKCS1').strip() == pkey.strip() def assert_parameters(self, as_dict, private): assert isinstance(as_dict, dict) @@ -256,7 +262,8 @@ def assert_roundtrip(self, key): ALGORITHMS.RS256 ).to_dict() == key.to_dict() - def test_to_dict(self): + @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) diff --git a/tests/algorithms/test_RSA_compat.py b/tests/algorithms/test_RSA_compat.py index 2882f700..0da03d50 100644 --- a/tests/algorithms/test_RSA_compat.py +++ b/tests/algorithms/test_RSA_compat.py @@ -8,7 +8,7 @@ PurePythonRSAKey = CryptographyRSAKey = PyCryptoRSAKey = None from jose.constants import ALGORITHMS -from .test_RSA import private_key +from .test_RSA import PRIVATE_KEYS CRYPTO_BACKENDS = ( pytest.param(PurePythonRSAKey, id="python_rsa"), @@ -27,7 +27,8 @@ class TestBackendRsaCompatibility(object): @pytest.mark.parametrize("BackendSign", CRYPTO_BACKENDS) @pytest.mark.parametrize("BackendVerify", CRYPTO_BACKENDS) - def test_signing_parity(self, BackendSign, BackendVerify): + @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() @@ -43,7 +44,8 @@ def test_signing_parity(self, BackendSign, BackendVerify): @pytest.mark.parametrize("encoding", ENCODINGS) @pytest.mark.parametrize("BackendFrom", CRYPTO_BACKENDS) @pytest.mark.parametrize("BackendTo", CRYPTO_BACKENDS) - def test_public_key_to_pem(self, BackendFrom, BackendTo, encoding): + @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) @@ -54,7 +56,8 @@ def test_public_key_to_pem(self, BackendFrom, BackendTo, encoding): @pytest.mark.parametrize("encoding", ENCODINGS) @pytest.mark.parametrize("BackendFrom", CRYPTO_BACKENDS) @pytest.mark.parametrize("BackendTo", CRYPTO_BACKENDS) - def test_private_key_to_pem(self, BackendFrom, BackendTo, encoding): + @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) @@ -72,7 +75,8 @@ def test_private_key_to_pem(self, BackendFrom, BackendTo, encoding): @pytest.mark.parametrize("encoding_load", ENCODINGS) @pytest.mark.parametrize("BackendFrom", CRYPTO_BACKENDS) @pytest.mark.parametrize("BackendTo", CRYPTO_BACKENDS) - def test_public_key_load_cycle(self, BackendFrom, BackendTo, encoding_save, encoding_load): + @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() @@ -86,7 +90,8 @@ def test_public_key_load_cycle(self, BackendFrom, BackendTo, encoding_save, enco @pytest.mark.parametrize("encoding_load", ENCODINGS) @pytest.mark.parametrize("BackendFrom", CRYPTO_BACKENDS) @pytest.mark.parametrize("BackendTo", CRYPTO_BACKENDS) - def test_private_key_load_cycle(self, BackendFrom, BackendTo, encoding_save, encoding_load): + @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() From 404ddc92b8252a3b21db1de3d0da77447773f705 Mon Sep 17 00:00:00 2001 From: mattsb42-aws Date: Wed, 26 Dec 2018 13:48:09 -0800 Subject: [PATCH 20/43] add legacy read compatibility test for rsa_backend --- tests/algorithms/test_RSA.py | 87 +++++++++++++++++++++++++++++++----- 1 file changed, 77 insertions(+), 10 deletions(-) diff --git a/tests/algorithms/test_RSA.py b/tests/algorithms/test_RSA.py index 57144cc4..2b8439d4 100644 --- a/tests/algorithms/test_RSA.py +++ b/tests/algorithms/test_RSA.py @@ -1,9 +1,15 @@ import sys try: - from Crypto.PublicKey import RSA + from jose.backends.rsa_backend import RSAKey as PurePythonRSAKey except ImportError: - RSA = None + PurePythonRSAKey = None + +try: + from Crypto.PublicKey import RSA as PyCryptoRSA + from jose.backends.pycrypto_backend import RSAKey as PyCryptoRSAKey +except ImportError: + PyCryptoRSA = None try: from cryptography.hazmat.backends import default_backend @@ -22,7 +28,7 @@ if sys.version_info > (3,): long = int -private_key_4096 = b"""-----BEGIN RSA PRIVATE KEY----- +private_key_4096_pkcs1 = b"""-----BEGIN RSA PRIVATE KEY----- MIIJKwIBAAKCAgEAtSKfSeI0fukRIX38AHlKB1YPpX8PUYN2JdvfM+XjNmLfU1M7 4N0VmdzIX95sneQGO9kC2xMIE+AIlt52Yf/KgBZggAlS9Y0Vx8DsSL2HvOjguAdX ir3vYLvAyyHin/mUisJOqccFKChHKjnk0uXy/38+1r17/cYTp76brKpU1I4kM20M @@ -73,7 +79,7 @@ bjJ/JfTO5060SsWftf4iw3jrhSn9RwTTYdq/kErGFWvDGJn2MiuhMe2onNfVzIGR mdUxHwi1ulkspAn/fmY7f0hZpskDwcHyZmbKZuk+NU/FJ8IAcmvk9y7m25nSSc8= -----END RSA PRIVATE KEY-----""" -private_key_2048 = b"""-----BEGIN RSA PRIVATE KEY----- +private_key_2048_pkcs1 = b"""-----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEAom6GcUPchmHxBuV3zJ60EPC7y30WiiVxn1WXSPHmfqaj0q2U xS03YugkYmX9lB/EQ6Z5bOY9VuL1oMudL6Dkb9aYYEBZHVgejV7vtYuYT19QMesn AsmGq8etie7XyWHzfWTxljbF53yvxXJMixcFzebAov9pUiV9Hmy3hYVLw3J1NXVg @@ -101,16 +107,77 @@ uwNcJ8daMgVZ0QBrD3CBcSZQrfC484BlV6spJ3C16qDVSQPt7sAI -----END RSA PRIVATE KEY-----""" PRIVATE_KEYS = ( - pytest.param(private_key_2048, id="RSA-2048"), - pytest.param(private_key_4096, id="RSA-4096") + 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----- +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 +ymbpPjVPxSfCAHJr5Pcu5tuZ0knP +-----END PRIVATE KEY----- +""" + + +@pytest.mark.skipif(PurePythonRSAKey is None, reason="python-rsa backend not available") +def test_python_rsa_legacy_pem_read(): + 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 + @pytest.mark.pycrypto @pytest.mark.pycryptodome -@pytest.mark.skipif(RSA is None, reason="Pycrypto/dome backend not available") +@pytest.mark.skipif(PyCryptoRSA is None, reason="Pycrypto/dome backend not available") def test_pycrypto_RSA_key_instance(): - key = RSA.construct((long( + key = PyCryptoRSA.construct((long( 26057131595212989515105618545799160306093557851986992545257129318694524535510983041068168825614868056510242030438003863929818932202262132630250203397069801217463517914103389095129323580576852108653940669240896817348477800490303630912852266209307160550655497615975529276169196271699168537716821419779900117025818140018436554173242441334827711966499484119233207097432165756707507563413323850255548329534279691658369466534587631102538061857114141268972476680597988266772849780811214198186940677291891818952682545840788356616771009013059992237747149380197028452160324144544057074406611859615973035412993832273216732343819), long(65537))) RSAKey(key, ALGORITHMS.RS256) @@ -151,7 +218,7 @@ def test_cryptography_RSA_key_instance(): class TestRSAAlgorithm: def test_RSA_key(self): - assert not RSAKey(private_key_4096, ALGORITHMS.RS256).is_public() + assert not RSAKey(private_key_4096_pkcs1, ALGORITHMS.RS256).is_public() def test_string_secret(self): key = 'secret' @@ -170,7 +237,7 @@ def test_bad_cert(self,): def test_invalid_algorithm(self): with pytest.raises(JWKError): - RSAKey(private_key_4096, ALGORITHMS.ES256) + RSAKey(private_key_4096_pkcs1, ALGORITHMS.ES256) with pytest.raises(JWKError): RSAKey({'kty': 'bla'}, ALGORITHMS.RS256) From 419b7d09c06baade864fca69d4b8c33449112680 Mon Sep 17 00:00:00 2001 From: mattsb42-aws Date: Wed, 26 Dec 2018 15:12:42 -0800 Subject: [PATCH 21/43] rsa_backend uses pyasn1 directly so it needs to be a direct dependency --- requirements.txt | 1 + setup.py | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) 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 63116c23..9397c06e 100644 --- a/setup.py +++ b/setup.py @@ -26,6 +26,11 @@ def get_packages(package): 'pycrypto': ['pycrypto >=2.6.0, <2.7.0'], 'pycryptodome': ['pycryptodome >=3.3.1, <4.0.0'], } +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( @@ -64,5 +69,5 @@ def get_packages(package): 'pytest-cov', 'pytest-runner', ], - install_requires=['six <2.0', 'ecdsa <1.0', 'rsa', 'future <1.0'] + install_requires=install_requires ) From 5dd8cbcd1142401b91fb6f0a77e5e28efa713810 Mon Sep 17 00:00:00 2001 From: mattsb42-aws Date: Wed, 26 Dec 2018 15:13:20 -0800 Subject: [PATCH 22/43] add tests got PKCS1-PKCS8 RSA private key encoding --- tests/algorithms/test_RSA.py | 125 +++++++++++++++++++++++++++++++++-- 1 file changed, 120 insertions(+), 5 deletions(-) diff --git a/tests/algorithms/test_RSA.py b/tests/algorithms/test_RSA.py index 2b8439d4..5bc96c38 100644 --- a/tests/algorithms/test_RSA.py +++ b/tests/algorithms/test_RSA.py @@ -1,9 +1,11 @@ +import base64 import sys try: from jose.backends.rsa_backend import RSAKey as PurePythonRSAKey + from jose.backends import rsa_backend except ImportError: - PurePythonRSAKey = None + PurePythonRSAKey = rsa_backend = None try: from Crypto.PublicKey import RSA as PyCryptoRSA @@ -165,12 +167,125 @@ -----END PRIVATE KEY----- """ +PKCS8_PRIVATE_KEY = b"""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 +bbmdJJzw==""" +PKCS1_PRIVATE_KEY = b"""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""" + @pytest.mark.skipif(PurePythonRSAKey is None, reason="python-rsa backend not available") -def test_python_rsa_legacy_pem_read(): - 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 +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_private_key_pkcs1_to_pkcs8(self): + pkcs1 = base64.b64decode(PKCS1_PRIVATE_KEY) + pkcs8 = base64.b64decode(PKCS8_PRIVATE_KEY) + + assert rsa_backend._private_key_pkcs1_to_pkcs8(pkcs1) == pkcs8 + + def test_python_rsa_private_key_pkcs8_to_pkcs1(self): + pkcs1 = base64.b64decode(PKCS1_PRIVATE_KEY) + pkcs8 = base64.b64decode(PKCS8_PRIVATE_KEY) + + assert rsa_backend._private_key_pkcs8_to_pkcs1(pkcs8) == pkcs1 @pytest.mark.pycrypto From 9a4ec3114ef04fae857748543475bb8dd3535e89 Mon Sep 17 00:00:00 2001 From: mattsb42-aws Date: Wed, 26 Dec 2018 15:13:47 -0800 Subject: [PATCH 23/43] fix rsa_backend RSA private key PKCS8 encoding --- jose/backends/rsa_backend.py | 80 +++++++++++++++++++++++++++++++----- 1 file changed, 70 insertions(+), 10 deletions(-) diff --git a/jose/backends/rsa_backend.py b/jose/backends/rsa_backend.py index 4dbbf5ab..3632e11b 100644 --- a/jose/backends/rsa_backend.py +++ b/jose/backends/rsa_backend.py @@ -1,6 +1,7 @@ import six -from pyasn1.codec.der import encoder -from pyasn1.type import univ +from pyasn1.codec.der import decoder, encoder +from pyasn1.error import PyAsn1Error +from pyasn1.type import namedtype, univ import rsa as pyrsa import rsa.pem as pyrsa_pem @@ -12,7 +13,9 @@ 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 = b'0\x82\x04\xbd\x02\x01\x000\r\x06\t*\x86H\x86\xf7\r\x01\x01\x01\x05\x00' +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 +86,59 @@ def pem_to_spki(pem, fmt='PKCS8'): return key.to_pem(fmt) +def _legacy_private_key_pkcs8_to_pkcs1(der_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. + """ + return der_key[len(LEGACY_INVALID_PKCS8_RSA_HEADER):] + + +class PKCS8RsaPrivateKeyAlgorithm(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", PKCS8RsaPrivateKeyAlgorithm()), + namedtype.NamedType("privateKey", univ.OctetString()) + ) + + +def _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 _private_key_pkcs1_to_pkcs8(pkcs1_key): + """Convert a PKCS1-encoded RSA private key to PKCS8.""" + algorithm = PKCS8RsaPrivateKeyAlgorithm() + 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) + + class RSAKey(Key): SHA256 = 'SHA-256' SHA384 = 'SHA-384' @@ -121,12 +177,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 = _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,7 +242,8 @@ 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 = _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: @@ -196,7 +256,7 @@ def to_pem(self, pem_format='PKCS8'): der = encoder.encode(asn_key) header = PubKeyHeader() - header['oid'] = univ.ObjectIdentifier('1.2.840.113549.1.1.1') + header['oid'] = univ.ObjectIdentifier(RSA_ENCRYPTION_ASN1_OID) pub_key = OpenSSLPubKey() pub_key['header'] = header pub_key['key'] = univ.BitString.fromOctetString(der) From edc443868a073a0fdb2a1b639060fb857fdbbc8e Mon Sep 17 00:00:00 2001 From: mattsb42-aws Date: Wed, 26 Dec 2018 16:52:07 -0800 Subject: [PATCH 24/43] convert ASN1 check values to hex for better readability and add check for known legacy prefix in legacy parsing --- jose/backends/rsa_backend.py | 23 +++++++++++++++++--- tests/algorithms/test_RSA.py | 42 ++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 3 deletions(-) diff --git a/jose/backends/rsa_backend.py b/jose/backends/rsa_backend.py index 3632e11b..495a3336 100644 --- a/jose/backends/rsa_backend.py +++ b/jose/backends/rsa_backend.py @@ -1,3 +1,5 @@ +import binascii + import six from pyasn1.codec.der import decoder, encoder from pyasn1.error import PyAsn1Error @@ -13,7 +15,16 @@ from jose.utils import base64_to_long, long_to_base64 -LEGACY_INVALID_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 @@ -86,7 +97,7 @@ def pem_to_spki(pem, fmt='PKCS8'): return key.to_pem(fmt) -def _legacy_private_key_pkcs8_to_pkcs1(der_key): +def _legacy_private_key_pkcs8_to_pkcs1(pkcs8_key): """Legacy RSA private key PKCS8-to-PKCS1 conversion. .. warning:: @@ -94,7 +105,13 @@ def _legacy_private_key_pkcs8_to_pkcs1(der_key): This is incorrect parsing and only works because the legacy PKCS1-to-PKCS8 encoding was also incorrect. """ - return der_key[len(LEGACY_INVALID_PKCS8_RSA_HEADER):] + # 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 PKCS8RsaPrivateKeyAlgorithm(univ.Sequence): diff --git a/tests/algorithms/test_RSA.py b/tests/algorithms/test_RSA.py index 5bc96c38..4958d07a 100644 --- a/tests/algorithms/test_RSA.py +++ b/tests/algorithms/test_RSA.py @@ -268,13 +268,55 @@ pskDwcHyZmbKZuk+NU/FJ8IAcmvk9y7m25nSSc8=""" +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") + def test_python_rsa_private_key_pkcs1_to_pkcs8(self): pkcs1 = base64.b64decode(PKCS1_PRIVATE_KEY) pkcs8 = base64.b64decode(PKCS8_PRIVATE_KEY) From 8e783021dfd720bd065704924ad64061ee930e8c Mon Sep 17 00:00:00 2001 From: mattsb42-aws Date: Thu, 27 Dec 2018 15:15:49 -0800 Subject: [PATCH 25/43] move asn1 private key processing into separate module --- jose/backends/_asn1.py | 51 +++++++++++++++++++++++++++++++ jose/backends/pycrypto_backend.py | 3 +- jose/backends/rsa_backend.py | 51 +++---------------------------- setup.py | 7 +++-- tests/algorithms/test_RSA.py | 12 -------- tests/test_asn1.py | 31 +++++++++++++++++++ 6 files changed, 93 insertions(+), 62 deletions(-) create mode 100644 jose/backends/_asn1.py create mode 100644 tests/test_asn1.py diff --git a/jose/backends/_asn1.py b/jose/backends/_asn1.py new file mode 100644 index 00000000..61dd5f10 --- /dev/null +++ b/jose/backends/_asn1.py @@ -0,0 +1,51 @@ +"""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.error import PyAsn1Error +from pyasn1.type import namedtype, univ + +RSA_ENCRYPTION_ASN1_OID = "1.2.840.113549.1.1.1" + + +class PKCS8RsaPrivateKeyAlgorithm(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", PKCS8RsaPrivateKeyAlgorithm()), + namedtype.NamedType("privateKey", univ.OctetString()) + ) + + +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 = PKCS8RsaPrivateKeyAlgorithm() + 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) diff --git a/jose/backends/pycrypto_backend.py b/jose/backends/pycrypto_backend.py index 4fe596aa..f19e411a 100644 --- a/jose/backends/pycrypto_backend.py +++ b/jose/backends/pycrypto_backend.py @@ -152,7 +152,8 @@ 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 + pem = self.prepared_key.exportKey('PEM') if pkcs == 8: pem = pem_to_spki(pem, fmt='PKCS8') else: diff --git a/jose/backends/rsa_backend.py b/jose/backends/rsa_backend.py index 495a3336..fe403bad 100644 --- a/jose/backends/rsa_backend.py +++ b/jose/backends/rsa_backend.py @@ -1,15 +1,16 @@ import binascii import six -from pyasn1.codec.der import decoder, encoder +from pyasn1.codec.der import encoder from pyasn1.error import PyAsn1Error -from pyasn1.type import namedtype, univ +from pyasn1.type import univ 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 from jose.constants import ALGORITHMS from jose.exceptions import JWKError from jose.utils import base64_to_long, long_to_base64 @@ -114,48 +115,6 @@ def _legacy_private_key_pkcs8_to_pkcs1(pkcs8_key): return pkcs8_key[len(LEGACY_INVALID_PKCS8_RSA_HEADER):] -class PKCS8RsaPrivateKeyAlgorithm(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", PKCS8RsaPrivateKeyAlgorithm()), - namedtype.NamedType("privateKey", univ.OctetString()) - ) - - -def _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 _private_key_pkcs1_to_pkcs8(pkcs1_key): - """Convert a PKCS1-encoded RSA private key to PKCS8.""" - algorithm = PKCS8RsaPrivateKeyAlgorithm() - 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) - - class RSAKey(Key): SHA256 = 'SHA-256' SHA384 = 'SHA-384' @@ -196,7 +155,7 @@ def __init__(self, key, algorithm): try: der = pyrsa_pem.load_pem(key, b'PRIVATE KEY') try: - pkcs1_key = _private_key_pkcs8_to_pkcs1(der) + 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 @@ -259,7 +218,7 @@ def to_pem(self, pem_format='PKCS8'): if isinstance(self._prepared_key, pyrsa.PrivateKey): der = self._prepared_key.save_pkcs1(format='DER') if pem_format == 'PKCS8': - pkcs8_der = _private_key_pkcs1_to_pkcs8(der) + 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') diff --git a/setup.py b/setup.py index 9397c06e..1430d3fd 100644 --- a/setup.py +++ b/setup.py @@ -21,12 +21,13 @@ def get_packages(package): ] +pyasn1 = ['pyasn1'] extras_require = { 'cryptography': ['cryptography'], - 'pycrypto': ['pycrypto >=2.6.0, <2.7.0'], - 'pycryptodome': ['pycryptodome >=3.3.1, <4.0.0'], + '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'] +legacy_backend_requires = ['ecdsa <1.0', 'rsa'] + pyasn1 install_requires = ['six <2.0', 'future <1.0'] # TODO: work this into the extras selection instead. diff --git a/tests/algorithms/test_RSA.py b/tests/algorithms/test_RSA.py index 944b9945..76474d11 100644 --- a/tests/algorithms/test_RSA.py +++ b/tests/algorithms/test_RSA.py @@ -318,18 +318,6 @@ def test_python_rsa_legacy_private_key_pkcs8_to_pkcs1_invalid(self): excinfo.match("Invalid private key encoding") - def test_python_rsa_private_key_pkcs1_to_pkcs8(self): - pkcs1 = base64.b64decode(PKCS1_PRIVATE_KEY) - pkcs8 = base64.b64decode(PKCS8_PRIVATE_KEY) - - assert rsa_backend._private_key_pkcs1_to_pkcs8(pkcs1) == pkcs8 - - def test_python_rsa_private_key_pkcs8_to_pkcs1(self): - pkcs1 = base64.b64decode(PKCS1_PRIVATE_KEY) - pkcs8 = base64.b64decode(PKCS8_PRIVATE_KEY) - - assert rsa_backend._private_key_pkcs8_to_pkcs1(pkcs8) == pkcs1 - @pytest.mark.pycrypto @pytest.mark.pycryptodome diff --git a/tests/test_asn1.py b/tests/test_asn1.py new file mode 100644 index 00000000..58bf063e --- /dev/null +++ b/tests/test_asn1.py @@ -0,0 +1,31 @@ +"""Tests for ``jose.backends._asn1``.""" +import base64 + +import pytest + +try: + from jose.backends import _asn1 +except ImportError: + _asn1 = None + +from .algorithms.test_RSA import PKCS1_PRIVATE_KEY, PKCS8_PRIVATE_KEY + +pytestmark = [ + pytest.mark.pycrypto, + pytest.mark.pycryptodome, + pytest.mark.skipif(_asn1 is None, reason="ASN1 backend not available") +] + + +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 From 6eb982f2e3c21a6650a30ea9963376bb2f4f07bb Mon Sep 17 00:00:00 2001 From: mattsb42-aws Date: Thu, 27 Dec 2018 15:10:09 -0800 Subject: [PATCH 26/43] add public key encoding conversion functions --- jose/backends/_asn1.py | 38 +++++++++- tests/algorithms/test_RSA.py | 100 ------------------------- tests/test_asn1.py | 140 ++++++++++++++++++++++++++++++++++- 3 files changed, 173 insertions(+), 105 deletions(-) diff --git a/jose/backends/_asn1.py b/jose/backends/_asn1.py index 61dd5f10..99954e17 100644 --- a/jose/backends/_asn1.py +++ b/jose/backends/_asn1.py @@ -9,7 +9,7 @@ RSA_ENCRYPTION_ASN1_OID = "1.2.840.113549.1.1.1" -class PKCS8RsaPrivateKeyAlgorithm(univ.Sequence): +class RsaAlgorithmIdentifier(univ.Sequence): """ASN1 structure for recording RSA PrivateKeyAlgorithm identifiers.""" componentType = namedtype.NamedTypes( namedtype.NamedType("rsaEncryption", univ.ObjectIdentifier()), @@ -21,11 +21,19 @@ class PKCS8PrivateKey(univ.Sequence): """ASN1 structure for recording PKCS8 private keys.""" componentType = namedtype.NamedTypes( namedtype.NamedType("version", univ.Integer()), - namedtype.NamedType("privateKeyAlgorithm", PKCS8RsaPrivateKeyAlgorithm()), + 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()) @@ -40,7 +48,7 @@ def rsa_private_key_pkcs8_to_pkcs1(pkcs8_key): def rsa_private_key_pkcs1_to_pkcs8(pkcs1_key): """Convert a PKCS1-encoded RSA private key to PKCS8.""" - algorithm = PKCS8RsaPrivateKeyAlgorithm() + algorithm = RsaAlgorithmIdentifier() algorithm["rsaEncryption"] = RSA_ENCRYPTION_ASN1_OID pkcs8_key = PKCS8PrivateKey() @@ -49,3 +57,27 @@ def rsa_private_key_pkcs1_to_pkcs8(pkcs1_key): 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/tests/algorithms/test_RSA.py b/tests/algorithms/test_RSA.py index 76474d11..af40e988 100644 --- a/tests/algorithms/test_RSA.py +++ b/tests/algorithms/test_RSA.py @@ -168,106 +168,6 @@ -----END PRIVATE KEY----- """ -PKCS8_PRIVATE_KEY = b"""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 -bbmdJJzw==""" -PKCS1_PRIVATE_KEY = b"""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 -pskDwcHyZmbKZuk+NU/FJ8IAcmvk9y7m25nSSc8=""" - def _legacy_invalid_private_key_pkcs8_der(): legacy_key = LEGACY_INVALID_PRIVATE_KEY_PKCS8_PEM.strip() diff --git a/tests/test_asn1.py b/tests/test_asn1.py index 58bf063e..4167ea9b 100644 --- a/tests/test_asn1.py +++ b/tests/test_asn1.py @@ -8,14 +8,136 @@ except ImportError: _asn1 = None -from .algorithms.test_RSA import PKCS1_PRIVATE_KEY, PKCS8_PRIVATE_KEY - 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) @@ -29,3 +151,17 @@ def test_rsa_private_key_pkcs8_to_pkcs1(): 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 From 42c3e813c56f3aa0f9144e2963c0c99a850bd43d Mon Sep 17 00:00:00 2001 From: mattsb42-aws Date: Thu, 27 Dec 2018 15:10:54 -0800 Subject: [PATCH 27/43] move pycrypto_backend PKCS8<->PKCS1 handling to _asn module --- jose/backends/pycrypto_backend.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/jose/backends/pycrypto_backend.py b/jose/backends/pycrypto_backend.py index f19e411a..ddf1b78e 100644 --- a/jose/backends/pycrypto_backend.py +++ b/jose/backends/pycrypto_backend.py @@ -4,12 +4,13 @@ import Crypto.Hash.SHA384 import Crypto.Hash.SHA512 +from Crypto.IO import PEM from Crypto.PublicKey import RSA from Crypto.Signature import PKCS1_v1_5 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 @@ -153,11 +154,12 @@ def to_pem(self, pem_format='PKCS8'): if self.is_public(): # PyCrypto/dome always export public keys as PKCS8 - pem = self.prepared_key.exportKey('PEM') 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 = PEM.encode(pkcs1_der, 'RSA PUBLIC KEY').encode('utf-8') return pem else: pem = self.prepared_key.exportKey('PEM', pkcs=pkcs) From a61a392ed9d8492356457acb23dbfd756a0c1397 Mon Sep 17 00:00:00 2001 From: mattsb42-aws Date: Thu, 27 Dec 2018 15:17:40 -0800 Subject: [PATCH 28/43] move rsa_backend PKCS8<->PKCS1 handling to _asn module --- jose/backends/rsa_backend.py | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/jose/backends/rsa_backend.py b/jose/backends/rsa_backend.py index fe403bad..c1f5539d 100644 --- a/jose/backends/rsa_backend.py +++ b/jose/backends/rsa_backend.py @@ -1,16 +1,17 @@ import binascii import six -from pyasn1.codec.der import encoder from pyasn1.error import PyAsn1Error -from pyasn1.type import univ 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 +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 @@ -226,19 +227,9 @@ def to_pem(self, pem_format='PKCS8'): 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(RSA_ENCRYPTION_ASN1_OID) - 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') From 76e342854103b1478d344e88e0637a97516af635 Mon Sep 17 00:00:00 2001 From: mattsb42-aws Date: Thu, 27 Dec 2018 15:47:29 -0800 Subject: [PATCH 29/43] update tox and Travis configs to use -norsa modes for pycrypto/dome test runs --- .travis.yml | 20 ++++++++++---------- tox.ini | 6 ++++-- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7dc5fb2b..33bb2a9a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,9 +16,9 @@ matrix: - python: 2.7 env: TOXENV=py27-cryptography-only - python: 2.7 - env: TOXENV=py27-pycryptodome + env: TOXENV=py27-pycryptodome-norsa - python: 2.7 - env: TOXENV=py27-pycrypto + env: TOXENV=py27-pycrypto-norsa - python: 2.7 env: TOXENV=py27-compatibility # CPython 3.4 @@ -27,9 +27,9 @@ matrix: - python: 3.4 env: TOXENV=py34-cryptography-only - python: 3.4 - env: TOXENV=py34-pycryptodome + env: TOXENV=py34-pycryptodome-norsa - python: 3.4 - env: TOXENV=py34-pycrypto + env: TOXENV=py34-pycrypto-norsa - python: 3.4 env: TOXENV=py34-compatibility # CPython 3.5 @@ -38,9 +38,9 @@ matrix: - python: 3.5 env: TOXENV=py35-cryptography-only - python: 3.5 - env: TOXENV=py35-pycryptodome + env: TOXENV=py35-pycryptodome-norsa - python: 3.5 - env: TOXENV=py35-pycrypto + env: TOXENV=py35-pycrypto-norsa - python: 3.5 env: TOXENV=py35-compatibility # CPython 3.5 @@ -49,9 +49,9 @@ matrix: - python: 3.5 env: TOXENV=py35-cryptography-only - python: 3.5 - env: TOXENV=py35-pycryptodome + env: TOXENV=py35-pycryptodome-norsa - python: 3.5 - env: TOXENV=py35-pycrypto + env: TOXENV=py35-pycrypto-norsa - python: 3.5 env: TOXENV=py35-compatibility # PyPy 5.3.1 @@ -60,9 +60,9 @@ matrix: - python: pypy-5.3.1 env: TOXENV=pypy-cryptography-only - python: pypy-5.3.1 - env: TOXENV=pypy-pycryptodome + env: TOXENV=pypy-pycryptodome-norsa - python: pypy-5.3.1 - env: TOXENV=pypy-pycrypto + env: TOXENV=pypy-pycrypto-norsa - python: pypy-5.3.1 env: TOXENV=pypy-compatibility # matrix: diff --git a/tox.ini b/tox.ini index 14bca5bc..176aa8f6 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,7 @@ [tox] minversion = 3.4.0 envlist = - py{27,34,35,36,py}-{base,cryptography-only,pycryptodome,pycrypto,compatibility}, + py{27,34,35,36,py}-{base,cryptography-only,pycryptodome-norsa,pycrypto-norsa,compatibility}, flake8 skip_missing_interpreters = True @@ -23,8 +23,10 @@ deps = pytest-runner compatibility: {[testenv:compatibility]deps} commands_pre = - # Remove the python-rsa backend + # Remove the python-rsa and python-ecdsa backends only: pip uninstall -y ecdsa rsa + # Remove just the python-rsa backend + nosra: pip uninstall -y rsa commands = # Test the python-rsa backend base: {[testenv:basecommand]commands} -m "not (cryptography or pycryptodome or pycrypto or backend_compatibility)" From 555648f4543d7375e939ab01472379dfc53ef230 Mon Sep 17 00:00:00 2001 From: mattsb42-aws Date: Thu, 27 Dec 2018 15:48:08 -0800 Subject: [PATCH 30/43] add note to readme about dependencies that can be cleaned up depending on the backend used --- README.rst | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) 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 ----- From 267f5ce0a5a7dfaa9553b9fab6cce2a70a8284bf Mon Sep 17 00:00:00 2001 From: mattsb42-aws Date: Thu, 27 Dec 2018 16:10:45 -0800 Subject: [PATCH 31/43] make cryptography_backend the default for RSAKey --- jose/backends/__init__.py | 4 ++-- tests/test_backends.py | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 tests/test_backends.py 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/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 From f96daecd92a1130d48a4a56d95fafbec7a880c61 Mon Sep 17 00:00:00 2001 From: mattsb42-aws Date: Thu, 27 Dec 2018 16:19:44 -0800 Subject: [PATCH 32/43] isolate pycrypto/dome-explict test and explicitly use pycrypto/dome backend --- tests/algorithms/test_RSA.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/algorithms/test_RSA.py b/tests/algorithms/test_RSA.py index 944b9945..20a59422 100644 --- a/tests/algorithms/test_RSA.py +++ b/tests/algorithms/test_RSA.py @@ -345,8 +345,9 @@ def test_pycrypto_RSA_key_instance(): @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 = RSAKey(private_key, ALGORITHMS.RS256) + key = PyCryptoRSAKey(private_key, ALGORITHMS.RS256) msg = b'test' signature = key.sign(msg) public_key = key.public_key() From 228621d9af39629840cba381fb97a47c7048b81e Mon Sep 17 00:00:00 2001 From: mattsb42-aws Date: Tue, 2 Apr 2019 12:15:04 -0700 Subject: [PATCH 33/43] fix tox norsa typo --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 176aa8f6..4b45379b 100644 --- a/tox.ini +++ b/tox.ini @@ -26,7 +26,7 @@ commands_pre = # Remove the python-rsa and python-ecdsa backends only: pip uninstall -y ecdsa rsa # Remove just the python-rsa backend - nosra: pip uninstall -y rsa + 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)" From ddcfbf9b4c82cfadd30841bc2331b17eeeb5bf5f Mon Sep 17 00:00:00 2001 From: mattsb42-aws Date: Tue, 2 Apr 2019 12:15:20 -0700 Subject: [PATCH 34/43] remove unused import --- jose/backends/_asn1.py | 1 - 1 file changed, 1 deletion(-) diff --git a/jose/backends/_asn1.py b/jose/backends/_asn1.py index 99954e17..e252cc77 100644 --- a/jose/backends/_asn1.py +++ b/jose/backends/_asn1.py @@ -3,7 +3,6 @@ Required by rsa_backend and pycrypto_backend but not cryptography_backend. """ from pyasn1.codec.der import decoder, encoder -from pyasn1.error import PyAsn1Error from pyasn1.type import namedtype, univ RSA_ENCRYPTION_ASN1_OID = "1.2.840.113549.1.1.1" From a40e3b74e1aacf9b1e71618581fb5bb408f3137c Mon Sep 17 00:00:00 2001 From: mattsb42-aws Date: Tue, 2 Apr 2019 16:10:44 -0700 Subject: [PATCH 35/43] add PEM->DER conversion function to remove pycrypto_backend dependency on Crypto.IO module --- jose/backends/pycrypto_backend.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/jose/backends/pycrypto_backend.py b/jose/backends/pycrypto_backend.py index ddf1b78e..01213264 100644 --- a/jose/backends/pycrypto_backend.py +++ b/jose/backends/pycrypto_backend.py @@ -1,10 +1,11 @@ +from base64 import b64encode + import six import Crypto.Hash.SHA256 import Crypto.Hash.SHA384 import Crypto.Hash.SHA512 -from Crypto.IO import PEM from Crypto.PublicKey import RSA from Crypto.Signature import PKCS1_v1_5 from Crypto.Util.asn1 import DerSequence @@ -26,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 @@ -159,7 +175,7 @@ def to_pem(self, pem_format='PKCS8'): else: pkcs8_der = self.prepared_key.exportKey('DER') pkcs1_der = rsa_public_key_pkcs8_to_pkcs1(pkcs8_der) - pem = PEM.encode(pkcs1_der, 'RSA PUBLIC KEY').encode('utf-8') + pem = _der_to_pem(pkcs1_der, 'RSA PUBLIC KEY') return pem else: pem = self.prepared_key.exportKey('PEM', pkcs=pkcs) From c25693392bb3d234792e5df0dceae8327c90909b Mon Sep 17 00:00:00 2001 From: mattsb42-aws Date: Tue, 2 Apr 2019 16:40:10 -0700 Subject: [PATCH 36/43] add cryptography version constraint for PyPy < 5.4 --- setup.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 1430d3fd..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,9 +23,18 @@ 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'], + 'cryptography': [_cryptography_version()], 'pycrypto': ['pycrypto >=2.6.0, <2.7.0'] + pyasn1, 'pycryptodome': ['pycryptodome >=3.3.1, <4.0.0'] + pyasn1, } From 0a5e07e1d6f6c472b31a7752dde396bf1238d0c1 Mon Sep 17 00:00:00 2001 From: mattsb42-aws Date: Tue, 2 Apr 2019 16:42:37 -0700 Subject: [PATCH 37/43] add Travis testing for PyPy 5.7.1 --- .travis.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.travis.yml b/.travis.yml index 33bb2a9a..6838fb37 100644 --- a/.travis.yml +++ b/.travis.yml @@ -65,6 +65,17 @@ matrix: env: TOXENV=pypy-pycrypto-norsa - python: pypy-5.3.1 env: TOXENV=pypy-compatibility + # PyPy 5.7.1 + - python: pypy-5.7.1 + env: TOXENV=pypy-base + - python: pypy-5.7.1 + env: TOXENV=pypy-cryptography-only + - python: pypy-5.7.1 + env: TOXENV=pypy-pycryptodome-norsa + - python: pypy-5.7.1 + env: TOXENV=pypy-pycrypto-norsa + - python: pypy-5.7.1 + env: TOXENV=pypy-compatibility # matrix: # include: # - python: 3.6 From 84fd4b2ff80ce4bb48b959b6f5f7c91ee20ac935 Mon Sep 17 00:00:00 2001 From: mattsb42-aws Date: Tue, 2 Apr 2019 16:48:32 -0700 Subject: [PATCH 38/43] add Travis testing for CPython 3.6 --- .travis.yml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 6838fb37..29c0a8da 100644 --- a/.travis.yml +++ b/.travis.yml @@ -54,6 +54,17 @@ matrix: 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 # PyPy 5.3.1 - python: pypy-5.3.1 env: TOXENV=pypy-base @@ -79,4 +90,4 @@ matrix: # matrix: # include: # - python: 3.6 -# env: TOX_ENV=flake8 \ No newline at end of file +# env: TOX_ENV=flake8 From 03c85835906b3b180baf2b87ec68769f51a72c08 Mon Sep 17 00:00:00 2001 From: mattsb42-aws Date: Tue, 2 Apr 2019 16:50:33 -0700 Subject: [PATCH 39/43] add Travis testing for CPython 3.7 --- .travis.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/.travis.yml b/.travis.yml index 29c0a8da..2ca3b354 100644 --- a/.travis.yml +++ b/.travis.yml @@ -65,6 +65,29 @@ matrix: 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 From 49c3059ba7d2bf15541bbef638cf0ed6a6506a71 Mon Sep 17 00:00:00 2001 From: mattsb42-aws Date: Tue, 2 Apr 2019 16:56:11 -0700 Subject: [PATCH 40/43] remove unused reference --- jose/backends/pycrypto_backend.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jose/backends/pycrypto_backend.py b/jose/backends/pycrypto_backend.py index 01213264..a12e861c 100644 --- a/jose/backends/pycrypto_backend.py +++ b/jose/backends/pycrypto_backend.py @@ -149,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): From 93bb9a734321a099d0528afb95f95a757bb91e0a Mon Sep 17 00:00:00 2001 From: mattsb42-aws Date: Tue, 2 Apr 2019 23:18:43 -0700 Subject: [PATCH 41/43] changing Travis run from pypy-3.5.1 to pypy3.5 per https://travis-ci.community/t/confusing-pypy-versions/2505 --- .travis.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2ca3b354..0c34b95c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -99,16 +99,16 @@ matrix: env: TOXENV=pypy-pycrypto-norsa - python: pypy-5.3.1 env: TOXENV=pypy-compatibility - # PyPy 5.7.1 - - python: pypy-5.7.1 + # PyPy 3.5 (5.10.1?) + - python: pypy3.5 env: TOXENV=pypy-base - - python: pypy-5.7.1 + - python: pypy3.5 env: TOXENV=pypy-cryptography-only - - python: pypy-5.7.1 + - python: pypy3.5 env: TOXENV=pypy-pycryptodome-norsa - - python: pypy-5.7.1 + - python: pypy3.5 env: TOXENV=pypy-pycrypto-norsa - - python: pypy-5.7.1 + - python: pypy3.5 env: TOXENV=pypy-compatibility # matrix: # include: From 69a57d931e41ebb34fa00c3cd9077be2c73bf081 Mon Sep 17 00:00:00 2001 From: mattsb42-aws Date: Wed, 3 Apr 2019 11:36:52 -0700 Subject: [PATCH 42/43] change tox compatibility installs to install extras rather than installing dependencies directly --- tox.ini | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tox.ini b/tox.ini index 4b45379b..c1f408cb 100644 --- a/tox.ini +++ b/tox.ini @@ -11,17 +11,16 @@ commands = py.test --cov-report term-missing --cov jose {posargs} [testenv:compatibility] -deps = +extras = cryptography - pycrypto >=2.6.0, <2.7.0 - pycryptodome >=3.3.1, <4.0.0 + pycrypto + pycryptodome [testenv] deps = pytest pytest-cov pytest-runner - compatibility: {[testenv:compatibility]deps} commands_pre = # Remove the python-rsa and python-ecdsa backends only: pip uninstall -y ecdsa rsa @@ -42,6 +41,7 @@ extras = cryptography: cryptography pycryptodome: pycryptodome pycrypto: pycrypto + compatibility: {[testenv:compatibility]extras} ; [testenv:flake8] ; commands = flake8 jose From b16995988f229c1c5741d7122d13bc559e2c4ab7 Mon Sep 17 00:00:00 2001 From: mattsb42-aws Date: Wed, 3 Apr 2019 11:42:21 -0700 Subject: [PATCH 43/43] enable flake8 in tox and Travis configs --- .travis.yml | 7 +++---- tox.ini | 10 +++++----- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0c34b95c..738b6f3d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,7 +110,6 @@ matrix: env: TOXENV=pypy-pycrypto-norsa - python: pypy3.5 env: TOXENV=pypy-compatibility -# matrix: -# include: -# - python: 3.6 -# env: TOX_ENV=flake8 + # Linting + - python: 3.6 + env: TOX_ENV=flake8 diff --git a/tox.ini b/tox.ini index c1f408cb..37c57eaf 100644 --- a/tox.ini +++ b/tox.ini @@ -43,8 +43,8 @@ extras = 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