diff --git a/tests/test_client.py b/tests/test_client.py index 10132aaec..a0a321ce0 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -41,13 +41,17 @@ from paramiko.pkey import PublicBlob from paramiko.ssh_exception import SSHException, AuthenticationException -from .util import _support, slow +from .util import _support, sha1_signing_unsupported, slow requires_gss_auth = unittest.skipUnless( paramiko.GSS_AUTH_AVAILABLE, "GSS auth not available" ) +requires_sha1_signing = unittest.skipIf( + sha1_signing_unsupported(), "SHA-1 signing not supported" +) + FINGERPRINTS = { "ssh-dss": b"\x44\x78\xf0\xb9\xa2\x3c\xc5\x18\x20\x09\xff\x75\x5b\xc1\xd2\x6c", # noqa "ssh-rsa": b"\x60\x73\x38\x44\xcb\x51\x86\x65\x7f\xde\xda\xa2\x2b\x5a\x57\xd5", # noqa @@ -235,33 +239,39 @@ def _test_connection(self, **kwargs): class SSHClientTest(ClientTest): + @requires_sha1_signing def test_client(self): """ verify that the SSHClient stuff works too. """ self._test_connection(password="pygmalion") + @requires_sha1_signing def test_client_dsa(self): """ verify that SSHClient works with a DSA key. """ self._test_connection(key_filename=_support("test_dss.key")) + @requires_sha1_signing def test_client_rsa(self): """ verify that SSHClient works with an RSA key. """ self._test_connection(key_filename=_support("test_rsa.key")) + @requires_sha1_signing def test_client_ecdsa(self): """ verify that SSHClient works with an ECDSA key. """ self._test_connection(key_filename=_support("test_ecdsa_256.key")) + @requires_sha1_signing def test_client_ed25519(self): self._test_connection(key_filename=_support("test_ed25519.key")) + @requires_sha1_signing def test_multiple_key_files(self): """ verify that SSHClient accepts and tries multiple key files. @@ -293,6 +303,7 @@ def test_multiple_key_files(self): self.tearDown() self.setUp() + @requires_sha1_signing def test_multiple_key_files_failure(self): """ Expect failure when multiple keys in play and none are accepted @@ -306,6 +317,7 @@ def test_multiple_key_files_failure(self): allowed_keys=["ecdsa-sha2-nistp256"], ) + @requires_sha1_signing def test_certs_allowed_as_key_filename_values(self): # NOTE: giving cert path here, not key path. (Key path test is below. # They're similar except for which path is given; the expected auth and @@ -319,6 +331,7 @@ def test_certs_allowed_as_key_filename_values(self): public_blob=PublicBlob.from_file(cert_path), ) + @requires_sha1_signing def test_certs_implicitly_loaded_alongside_key_filename_keys(self): # NOTE: a regular test_connection() w/ test_rsa.key would incidentally # test this (because test_xxx.key-cert.pub exists) but incidental tests @@ -469,6 +482,7 @@ def test_banner_timeout(self): kwargs = dict(self.connect_kwargs, banner_timeout=0.5) self.assertRaises(paramiko.SSHException, self.tc.connect, **kwargs) + @requires_sha1_signing def test_auth_trickledown(self): """ Failed key auth doesn't prevent subsequent pw auth from succeeding @@ -489,6 +503,7 @@ def test_auth_trickledown(self): ) self._test_connection(**kwargs) + @requires_sha1_signing @slow def test_auth_timeout(self): """ @@ -591,6 +606,7 @@ def test_host_key_negotiation_1(self): host_key = paramiko.ECDSAKey.generate() self._client_host_key_bad(host_key) + @requires_sha1_signing def test_host_key_negotiation_2(self): host_key = paramiko.RSAKey.generate(2048) self._client_host_key_bad(host_key) @@ -598,6 +614,7 @@ def test_host_key_negotiation_2(self): def test_host_key_negotiation_3(self): self._client_host_key_good(paramiko.ECDSAKey, "test_ecdsa_256.key") + @requires_sha1_signing def test_host_key_negotiation_4(self): self._client_host_key_good(paramiko.RSAKey, "test_rsa.key") @@ -681,6 +698,7 @@ class PasswordPassphraseTests(ClientTest): # instead of suffering a real connection cycle. # TODO: in that case, move the below to be part of an integration suite? + @requires_sha1_signing def test_password_kwarg_works_for_password_auth(self): # Straightforward / duplicate of earlier basic password test. self._test_connection(password="pygmalion") @@ -688,10 +706,12 @@ def test_password_kwarg_works_for_password_auth(self): # TODO: more granular exception pending #387; should be signaling "no auth # methods available" because no key and no password @raises(SSHException) + @requires_sha1_signing def test_passphrase_kwarg_not_used_for_password_auth(self): # Using the "right" password in the "wrong" field shouldn't work. self._test_connection(passphrase="pygmalion") + @requires_sha1_signing def test_passphrase_kwarg_used_for_key_passphrase(self): # Straightforward again, with new passphrase kwarg. self._test_connection( @@ -699,6 +719,7 @@ def test_passphrase_kwarg_used_for_key_passphrase(self): passphrase="television", ) + @requires_sha1_signing def test_password_kwarg_used_for_passphrase_when_no_passphrase_kwarg_given( self ): # noqa @@ -709,6 +730,7 @@ def test_password_kwarg_used_for_passphrase_when_no_passphrase_kwarg_given( ) @raises(AuthenticationException) # TODO: more granular + @requires_sha1_signing def test_password_kwarg_not_used_for_passphrase_when_passphrase_kwarg_given( # noqa self ): diff --git a/tests/test_pkey.py b/tests/test_pkey.py index ed62da21c..e0d1b0ac5 100644 --- a/tests/test_pkey.py +++ b/tests/test_pkey.py @@ -44,9 +44,13 @@ from mock import patch, Mock import pytest -from .util import _support, is_low_entropy +from .util import _support, is_low_entropy, sha1_signing_unsupported +requires_sha1_signing = unittest.skipIf( + sha1_signing_unsupported(), "SHA-1 signing not supported" +) + # from openssh's ssh-keygen PUB_RSA = "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEA049W6geFpmsljTwfvI1UmKWWJPNFI74+vNKTk4dmzkQY2yAMs6FhlvhlI8ysU4oj71ZsRYMecHbBbxdN79+JRFVYTKaLqjwGENeTd+yv4q+V2PvZv3fLnzApI3l7EJCqhWwJUHJ1jAkZzqDx0tyOL4uoZpww3nmE0kb3y21tH4c=" # noqa PUB_DSS = "ssh-dss AAAAB3NzaC1kc3MAAACBAOeBpgNnfRzr/twmAQRu2XwWAp3CFtrVnug6s6fgwj/oLjYbVtjAy6pl/h0EKCWx2rf1IetyNsTxWrniA9I6HeDj65X1FyDkg6g8tvCnaNB8Xp/UUhuzHuGsMIipRxBxw9LF608EqZcj1E3ytktoW5B5OcjrkEoz3xG7C+rpIjYvAAAAFQDwz4UnmsGiSNu5iqjn3uTzwUpshwAAAIEAkxfFeY8P2wZpDjX0MimZl5wkoFQDL25cPzGBuB4OnB8NoUk/yjAHIIpEShw8V+LzouMK5CTJQo5+Ngw3qIch/WgRmMHy4kBq1SsXMjQCte1So6HBMvBPIW5SiMTmjCfZZiw4AYHK+B/JaOwaG9yRg2Ejg4Ok10+XFDxlqZo8Y+wAAACARmR7CCPjodxASvRbIyzaVpZoJ/Z6x7dAumV+ysrV1BVYd0lYukmnjO1kKBWApqpH1ve9XDQYN8zgxM4b16L21kpoWQnZtXrY3GZ4/it9kUgyB7+NwacIBlXa8cMDL7Q/69o0d54U0X/NeX5QxuYR6OMJlrkQB7oiW/P/1mwjQgE=" # noqa @@ -256,6 +260,7 @@ def _sign_and_verify_rsa(self, algorithm, saved_sig): pub = RSAKey(data=key.asbytes()) self.assertTrue(pub.verify_ssh_sig(b"ice weasels", msg)) + @requires_sha1_signing def test_sign_and_verify_ssh_rsa(self): self._sign_and_verify_rsa("ssh-rsa", SIGNED_RSA) @@ -280,6 +285,7 @@ def test_sign_dss(self): pub = DSSKey(data=key.asbytes()) self.assertTrue(pub.verify_ssh_sig(b"ice weasels", msg)) + @requires_sha1_signing def test_generate_rsa(self): key = RSAKey.generate(1024) msg = key.sign_ssh_data(b"jerri blank") diff --git a/tests/test_transport.py b/tests/test_transport.py index a9262f3df..b9daec604 100644 --- a/tests/test_transport.py +++ b/tests/test_transport.py @@ -61,7 +61,7 @@ from paramiko.py3compat import bytes, byte_chr from paramiko.message import Message -from .util import needs_builtin, _support, slow +from .util import needs_builtin, _support, sha1_signing_unsupported, slow from .loop import LoopSocket @@ -77,6 +77,10 @@ Maybe. """ +requires_sha1_signing = unittest.skipIf( + sha1_signing_unsupported(), "SHA-1 signing not supported" +) + class NullServer(ServerInterface): paranoid_did_password = False @@ -1283,6 +1287,7 @@ class TestSHA2SignatureKeyExchange(unittest.TestCase): # are new tests in test_pkey.py which use known signature blobs to prove # the SHA2 family was in fact used! + @requires_sha1_signing def test_base_case_ssh_rsa_still_used_as_fallback(self): # Prove that ssh-rsa is used if either, or both, participants have SHA2 # algorithms disabled @@ -1405,6 +1410,7 @@ def test_client_sha1_disabled_server_sha2_disabled_no_match(self): ) as (tc, ts, err): assert isinstance(err, AuthenticationException) + @requires_sha1_signing def test_ssh_rsa_still_used_when_sha2_disabled(self): privkey = RSAKey.from_private_key_file(_support("test_rsa.key")) # NOTE: this works because key obj comparison uses public bytes diff --git a/tests/util.py b/tests/util.py index 1355ce8a8..4bf73949b 100644 --- a/tests/util.py +++ b/tests/util.py @@ -9,6 +9,10 @@ from paramiko.py3compat import builtins, PY2 from paramiko.ssh_gss import GSS_AUTH_AVAILABLE +from cryptography.exceptions import UnsupportedAlgorithm, _Reasons +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import padding, rsa tests_dir = dirname(realpath(__file__)) @@ -144,3 +148,26 @@ def is_low_entropy(): # I don't see a way to tell internally if the hash seed was set this # way, but env should be plenty sufficient, this is only for testing. return is_32bit and os.environ.get("PYTHONHASHSEED", None) == "0" + + +def sha1_signing_unsupported(): + """ + This is used to skip tests in environments where SHA-1 signing is + not supported by the backend. + """ + private_key = rsa.generate_private_key( + public_exponent=65537, key_size=2048, backend=default_backend() + ) + message = b"Some dummy text" + try: + private_key.sign( + message, + padding.PSS( + mgf=padding.MGF1(hashes.SHA1()), + salt_length=padding.PSS.MAX_LENGTH, + ), + hashes.SHA1(), + ) + return False + except UnsupportedAlgorithm as e: + return e._reason is _Reasons.UNSUPPORTED_HASH