From 39244216e4b8b1e0ef684473b9387dca7256bc37 Mon Sep 17 00:00:00 2001 From: Alex Orange Date: Mon, 25 Apr 2016 13:53:06 -0600 Subject: Add support for ECDSA key sizes 384 and 521 alongside the existing 256. Previously only 256-bit was handled and in certain cases (private key reading) 384- and 521-bit keys were treated as 256-bit keys causing silent errors. Tests have been added to specifically test the 384 and 521 keysizes. As RFC 5656 defines 256, 384, and 521 as the required keysizes this seems a good set to test. Also, this will cover the branches at ecdsakey.py:55. Test keys were renamed and test_client.py was modified as a result. This also fixes two bugs in ecdsakey.py. First, when calculating bytes needed to store a key, the assumption was made that the key size (in bits) was divisible by 8 (see line 137). This has been fixed by rounding up (wasn't an issue as only 256-bit keys were used before). Another bug was that the key padding in asbytes was being done backwards (was padding on current_length - needed_length bytes). --- tests/test_client.py | 6 +- tests/test_ecdsa.key | 5 - tests/test_ecdsa_256.key | 5 + tests/test_ecdsa_384.key | 6 ++ tests/test_ecdsa_521.key | 7 ++ tests/test_ecdsa_password.key | 8 -- tests/test_ecdsa_password_256.key | 8 ++ tests/test_ecdsa_password_384.key | 9 ++ tests/test_ecdsa_password_521.key | 10 ++ tests/test_pkey.py | 190 ++++++++++++++++++++++++++++++++++---- 10 files changed, 221 insertions(+), 33 deletions(-) delete mode 100644 tests/test_ecdsa.key create mode 100644 tests/test_ecdsa_256.key create mode 100644 tests/test_ecdsa_384.key create mode 100644 tests/test_ecdsa_521.key delete mode 100644 tests/test_ecdsa_password.key create mode 100644 tests/test_ecdsa_password_256.key create mode 100644 tests/test_ecdsa_password_384.key create mode 100644 tests/test_ecdsa_password_521.key (limited to 'tests') diff --git a/tests/test_client.py b/tests/test_client.py index f42d79d9..63ff9297 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -182,7 +182,7 @@ class SSHClientTest (unittest.TestCase): """ verify that SSHClient works with an ECDSA key. """ - self._test_connection(key_filename=test_path('test_ecdsa.key')) + self._test_connection(key_filename=test_path('test_ecdsa_256.key')) def test_3_multiple_key_files(self): """ @@ -199,8 +199,8 @@ class SSHClientTest (unittest.TestCase): for attempt, accept in ( (['rsa', 'dss'], ['dss']), # Original test #3 (['dss', 'rsa'], ['dss']), # Ordering matters sometimes, sadly - (['dss', 'rsa', 'ecdsa'], ['dss']), # Try ECDSA but fail - (['rsa', 'ecdsa'], ['ecdsa']), # ECDSA success + (['dss', 'rsa', 'ecdsa_256'], ['dss']), # Try ECDSA but fail + (['rsa', 'ecdsa_256'], ['ecdsa']), # ECDSA success ): try: self._test_connection( diff --git a/tests/test_ecdsa.key b/tests/test_ecdsa.key deleted file mode 100644 index 42d44734..00000000 --- a/tests/test_ecdsa.key +++ /dev/null @@ -1,5 +0,0 @@ ------BEGIN EC PRIVATE KEY----- -MHcCAQEEIKB6ty3yVyKEnfF/zprx0qwC76MsMlHY4HXCnqho2eKioAoGCCqGSM49 -AwEHoUQDQgAElI9mbdlaS+T9nHxY/59lFnn80EEecZDBHq4gLpccY8Mge5ZTMiMD -ADRvOqQ5R98Sxst765CAqXmRtz8vwoD96g== ------END EC PRIVATE KEY----- diff --git a/tests/test_ecdsa_256.key b/tests/test_ecdsa_256.key new file mode 100644 index 00000000..42d44734 --- /dev/null +++ b/tests/test_ecdsa_256.key @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIKB6ty3yVyKEnfF/zprx0qwC76MsMlHY4HXCnqho2eKioAoGCCqGSM49 +AwEHoUQDQgAElI9mbdlaS+T9nHxY/59lFnn80EEecZDBHq4gLpccY8Mge5ZTMiMD +ADRvOqQ5R98Sxst765CAqXmRtz8vwoD96g== +-----END EC PRIVATE KEY----- diff --git a/tests/test_ecdsa_384.key b/tests/test_ecdsa_384.key new file mode 100644 index 00000000..796bf417 --- /dev/null +++ b/tests/test_ecdsa_384.key @@ -0,0 +1,6 @@ +-----BEGIN EC PRIVATE KEY----- +MIGkAgEBBDBDdO8IXvlLJgM7+sNtPl7tI7FM5kzuEUEEPRjXIPQM7mISciwJPBt+ +y43EuG8nL4mgBwYFK4EEACKhZANiAAQWxom0C1vQAGYhjdoREMVmGKBWlisDdzyk +mgyUjKpiJ9WfbIEVLsPGP8OdNjhr1y/8BZNIts+dJd6VmYw+4HzB+4F+U1Igs8K0 +JEvh59VNkvWheViadDXCM2MV8Nq+DNg= +-----END EC PRIVATE KEY----- diff --git a/tests/test_ecdsa_521.key b/tests/test_ecdsa_521.key new file mode 100644 index 00000000..b87dc90f --- /dev/null +++ b/tests/test_ecdsa_521.key @@ -0,0 +1,7 @@ +-----BEGIN EC PRIVATE KEY----- +MIHcAgEBBEIAprQtAS3OF6iVUkT8IowTHWicHzShGgk86EtuEXvfQnhZFKsWm6Jo +iqAr1yEaiuI9LfB3Xs8cjuhgEEfbduYr/f6gBwYFK4EEACOhgYkDgYYABACaOaFL +ZGuxa5AW16qj6VLypFbLrEWrt9AZUloCMefxO8bNLjK/O5g0rAVasar1TnyHE9qj +4NwzANZASWjQNbc4MAG8vzqezFwLIn/kNyNTsXNfqEko9OgHZknlj2Z79dwTJcRA +L4QLcT5aND0EHZLB2fAUDXiWIb2j4rg1mwPlBMiBXA== +-----END EC PRIVATE KEY----- diff --git a/tests/test_ecdsa_password.key b/tests/test_ecdsa_password.key deleted file mode 100644 index eb7910ed..00000000 --- a/tests/test_ecdsa_password.key +++ /dev/null @@ -1,8 +0,0 @@ ------BEGIN EC PRIVATE KEY----- -Proc-Type: 4,ENCRYPTED -DEK-Info: AES-128-CBC,EEB56BC745EDB2DE04FC3FE1F8DA387E - -wdt7QTCa6ahTJLaEPH7NhHyBcxhzrzf93d4UwQOuAhkM6//jKD4lF9fErHBW0f3B -ExberCU3UxfEF3xX2thXiLw47JgeOCeQUlqRFx92p36k6YmfNGX6W8CsZ3d+XodF -Z+pb6m285CiSX+W95NenFMexXFsIpntiCvTifTKJ8os= ------END EC PRIVATE KEY----- diff --git a/tests/test_ecdsa_password_256.key b/tests/test_ecdsa_password_256.key new file mode 100644 index 00000000..eb7910ed --- /dev/null +++ b/tests/test_ecdsa_password_256.key @@ -0,0 +1,8 @@ +-----BEGIN EC PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-128-CBC,EEB56BC745EDB2DE04FC3FE1F8DA387E + +wdt7QTCa6ahTJLaEPH7NhHyBcxhzrzf93d4UwQOuAhkM6//jKD4lF9fErHBW0f3B +ExberCU3UxfEF3xX2thXiLw47JgeOCeQUlqRFx92p36k6YmfNGX6W8CsZ3d+XodF +Z+pb6m285CiSX+W95NenFMexXFsIpntiCvTifTKJ8os= +-----END EC PRIVATE KEY----- diff --git a/tests/test_ecdsa_password_384.key b/tests/test_ecdsa_password_384.key new file mode 100644 index 00000000..eba33c14 --- /dev/null +++ b/tests/test_ecdsa_password_384.key @@ -0,0 +1,9 @@ +-----BEGIN EC PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-128-CBC,7F7B5DBE4CE040D822441AFE7A023A1D + +y/d6tGonAXYgJniQoFCdto+CuT1y1s41qzwNLN9YdNq/+R/dtQvZAaOuGtHJRFE6 +wWabhY1bSjavVPT2z1Zw1jhDJX5HGrf9LDoyORKtUWtUJoUvGdYLHbcg8Q+//WRf +R0A01YuSw1SJX0a225S1aRcsDAk1k5F8EMb8QzSSDgjAOI8ldQF35JI+ofNSGjgS +BPOlorQXTJxDOGmokw/Wql6MbhajXKPO39H2Z53W88U= +-----END EC PRIVATE KEY----- diff --git a/tests/test_ecdsa_password_521.key b/tests/test_ecdsa_password_521.key new file mode 100644 index 00000000..5986b930 --- /dev/null +++ b/tests/test_ecdsa_password_521.key @@ -0,0 +1,10 @@ +-----BEGIN EC PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-128-CBC,AEB2DE62C65D1A88C4940A3476B2F10A + +5kNk/FFPbHa0402QTrgpIT28uirJ4Amvb2/ryOEyOCe0NPbTLCqlQekj2RFYH2Un +pgCLUDkelKQv4pyuK8qWS7R+cFjE/gHHCPUWkK3djZUC8DKuA9lUKeQIE+V1vBHc +L5G+MpoYrPgaydcGx/Uqnc/kVuZx1DXLwrGGtgwNROVBtmjXC9EdfeXHLL1y0wvH +paNgacJpUtgqJEmiehf7eL/eiReegG553rZK3jjfboGkREUaKR5XOgamiKUtgKoc +sMpImVYCsRKd/9RI+VOqErZaEvy/9j0Ye3iH32wGOaA= +-----END EC PRIVATE KEY----- diff --git a/tests/test_pkey.py b/tests/test_pkey.py index ec128140..59b3bb43 100644 --- a/tests/test_pkey.py +++ b/tests/test_pkey.py @@ -24,6 +24,7 @@ import unittest import os from binascii import hexlify from hashlib import md5 +import base64 from paramiko import RSAKey, DSSKey, ECDSAKey, Message, util from paramiko.py3compat import StringIO, byte_chr, b, bytes @@ -33,11 +34,15 @@ from tests.util import test_path # from openssh's ssh-keygen PUB_RSA = 'ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEA049W6geFpmsljTwfvI1UmKWWJPNFI74+vNKTk4dmzkQY2yAMs6FhlvhlI8ysU4oj71ZsRYMecHbBbxdN79+JRFVYTKaLqjwGENeTd+yv4q+V2PvZv3fLnzApI3l7EJCqhWwJUHJ1jAkZzqDx0tyOL4uoZpww3nmE0kb3y21tH4c=' 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=' -PUB_ECDSA = 'ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJSPZm3ZWkvk/Zx8WP+fZRZ5/NBBHnGQwR6uIC6XHGPDIHuWUzIjAwA0bzqkOUffEsbLe+uQgKl5kbc/L8KA/eo=' +PUB_ECDSA_256 = 'ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJSPZm3ZWkvk/Zx8WP+fZRZ5/NBBHnGQwR6uIC6XHGPDIHuWUzIjAwA0bzqkOUffEsbLe+uQgKl5kbc/L8KA/eo=' +PUB_ECDSA_384 = 'ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBBbGibQLW9AAZiGN2hEQxWYYoFaWKwN3PKSaDJSMqmIn1Z9sgRUuw8Y/w502OGvXL/wFk0i2z50l3pWZjD7gfMH7gX5TUiCzwrQkS+Hn1U2S9aF5WJp0NcIzYxXw2r4M2A==' +PUB_ECDSA_521 = 'ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBACaOaFLZGuxa5AW16qj6VLypFbLrEWrt9AZUloCMefxO8bNLjK/O5g0rAVasar1TnyHE9qj4NwzANZASWjQNbc4MAG8vzqezFwLIn/kNyNTsXNfqEko9OgHZknlj2Z79dwTJcRAL4QLcT5aND0EHZLB2fAUDXiWIb2j4rg1mwPlBMiBXA==' FINGER_RSA = '1024 60:73:38:44:cb:51:86:65:7f:de:da:a2:2b:5a:57:d5' FINGER_DSS = '1024 44:78:f0:b9:a2:3c:c5:18:20:09:ff:75:5b:c1:d2:6c' -FINGER_ECDSA = '256 25:19:eb:55:e6:a1:47:ff:4f:38:d2:75:6f:a5:d5:60' +FINGER_ECDSA_256 = '256 25:19:eb:55:e6:a1:47:ff:4f:38:d2:75:6f:a5:d5:60' +FINGER_ECDSA_384 = '384 c1:8d:a0:59:09:47:41:8e:a8:a6:07:01:29:23:b4:65' +FINGER_ECDSA_521 = '521 44:58:22:52:12:33:16:0e:ce:0e:be:2c:7c:7e:cc:1e' SIGNED_RSA = '20:d7:8a:31:21:cb:f7:92:12:f2:a4:89:37:f5:78:af:e6:16:b6:25:b9:97:3d:a2:cd:5f:ca:20:21:73:4c:ad:34:73:8f:20:77:28:e2:94:15:08:d8:91:40:7a:85:83:bf:18:37:95:dc:54:1a:9b:88:29:6c:73:ca:38:b4:04:f1:56:b9:f2:42:9d:52:1b:29:29:b4:4f:fd:c9:2d:af:47:d2:40:76:30:f3:63:45:0c:d9:1d:43:86:0f:1c:70:e2:93:12:34:f3:ac:c5:0a:2f:14:50:66:59:f1:88:ee:c1:4a:e9:d1:9c:4e:46:f0:0e:47:6f:38:74:f1:44:a8' RSA_PRIVATE_OUT = """\ @@ -73,7 +78,7 @@ h9pT9XHqn+1rZ4bK+QGA -----END DSA PRIVATE KEY----- """ -ECDSA_PRIVATE_OUT = """\ +ECDSA_PRIVATE_OUT_256 = """\ -----BEGIN EC PRIVATE KEY----- MHcCAQEEIKB6ty3yVyKEnfF/zprx0qwC76MsMlHY4HXCnqho2eKioAoGCCqGSM49 AwEHoUQDQgAElI9mbdlaS+T9nHxY/59lFnn80EEecZDBHq4gLpccY8Mge5ZTMiMD @@ -81,6 +86,25 @@ ADRvOqQ5R98Sxst765CAqXmRtz8vwoD96g== -----END EC PRIVATE KEY----- """ +ECDSA_PRIVATE_OUT_384 = """\ +-----BEGIN EC PRIVATE KEY----- +MIGkAgEBBDBDdO8IXvlLJgM7+sNtPl7tI7FM5kzuEUEEPRjXIPQM7mISciwJPBt+ +y43EuG8nL4mgBwYFK4EEACKhZANiAAQWxom0C1vQAGYhjdoREMVmGKBWlisDdzyk +mgyUjKpiJ9WfbIEVLsPGP8OdNjhr1y/8BZNIts+dJd6VmYw+4HzB+4F+U1Igs8K0 +JEvh59VNkvWheViadDXCM2MV8Nq+DNg= +-----END EC PRIVATE KEY----- +""" + +ECDSA_PRIVATE_OUT_521 = """\ +-----BEGIN EC PRIVATE KEY----- +MIHcAgEBBEIAprQtAS3OF6iVUkT8IowTHWicHzShGgk86EtuEXvfQnhZFKsWm6Jo +iqAr1yEaiuI9LfB3Xs8cjuhgEEfbduYr/f6gBwYFK4EEACOhgYkDgYYABACaOaFL +ZGuxa5AW16qj6VLypFbLrEWrt9AZUloCMefxO8bNLjK/O5g0rAVasar1TnyHE9qj +4NwzANZASWjQNbc4MAG8vzqezFwLIn/kNyNTsXNfqEko9OgHZknlj2Z79dwTJcRA +L4QLcT5aND0EHZLB2fAUDXiWIb2j4rg1mwPlBMiBXA== +-----END EC PRIVATE KEY----- +""" + x1234 = b'\x01\x02\x03\x04' @@ -121,7 +145,7 @@ class KeyTest (unittest.TestCase): self.assertEqual(exp_rsa, my_rsa) self.assertEqual(PUB_RSA.split()[1], key.get_base64()) self.assertEqual(1024, key.get_bits()) - + def test_4_load_dss(self): key = DSSKey.from_private_key_file(test_path('test_dss.key')) self.assertEqual('ssh-dss', key.get_name()) @@ -205,43 +229,72 @@ class KeyTest (unittest.TestCase): msg.rewind() self.assertTrue(key.verify_ssh_sig(b'jerri blank', msg)) - def test_10_load_ecdsa(self): - key = ECDSAKey.from_private_key_file(test_path('test_ecdsa.key')) + def test_C_generate_ecdsa(self): + key = ECDSAKey.generate() + msg = key.sign_ssh_data(b'jerri blank') + msg.rewind() + self.assertTrue(key.verify_ssh_sig(b'jerri blank', msg)) + self.assertEqual(key.get_bits(), 256) + self.assertEqual(key.get_name(), 'ecdsa-sha2-nistp256') + + key = ECDSAKey.generate(bits=256) + msg = key.sign_ssh_data(b'jerri blank') + msg.rewind() + self.assertTrue(key.verify_ssh_sig(b'jerri blank', msg)) + self.assertEqual(key.get_bits(), 256) + self.assertEqual(key.get_name(), 'ecdsa-sha2-nistp256') + + key = ECDSAKey.generate(bits=384) + msg = key.sign_ssh_data(b'jerri blank') + msg.rewind() + self.assertTrue(key.verify_ssh_sig(b'jerri blank', msg)) + self.assertEqual(key.get_bits(), 384) + self.assertEqual(key.get_name(), 'ecdsa-sha2-nistp384') + + key = ECDSAKey.generate(bits=521) + msg = key.sign_ssh_data(b'jerri blank') + msg.rewind() + self.assertTrue(key.verify_ssh_sig(b'jerri blank', msg)) + self.assertEqual(key.get_bits(), 521) + self.assertEqual(key.get_name(), 'ecdsa-sha2-nistp521') + + def test_10_load_ecdsa_256(self): + key = ECDSAKey.from_private_key_file(test_path('test_ecdsa_256.key')) self.assertEqual('ecdsa-sha2-nistp256', key.get_name()) - exp_ecdsa = b(FINGER_ECDSA.split()[1].replace(':', '')) + exp_ecdsa = b(FINGER_ECDSA_256.split()[1].replace(':', '')) my_ecdsa = hexlify(key.get_fingerprint()) self.assertEqual(exp_ecdsa, my_ecdsa) - self.assertEqual(PUB_ECDSA.split()[1], key.get_base64()) + self.assertEqual(PUB_ECDSA_256.split()[1], key.get_base64()) self.assertEqual(256, key.get_bits()) s = StringIO() key.write_private_key(s) - self.assertEqual(ECDSA_PRIVATE_OUT, s.getvalue()) + self.assertEqual(ECDSA_PRIVATE_OUT_256, s.getvalue()) s.seek(0) key2 = ECDSAKey.from_private_key(s) self.assertEqual(key, key2) - def test_11_load_ecdsa_password(self): - key = ECDSAKey.from_private_key_file(test_path('test_ecdsa_password.key'), b'television') + def test_11_load_ecdsa_password_256(self): + key = ECDSAKey.from_private_key_file(test_path('test_ecdsa_password_256.key'), b'television') self.assertEqual('ecdsa-sha2-nistp256', key.get_name()) - exp_ecdsa = b(FINGER_ECDSA.split()[1].replace(':', '')) + exp_ecdsa = b(FINGER_ECDSA_256.split()[1].replace(':', '')) my_ecdsa = hexlify(key.get_fingerprint()) self.assertEqual(exp_ecdsa, my_ecdsa) - self.assertEqual(PUB_ECDSA.split()[1], key.get_base64()) + self.assertEqual(PUB_ECDSA_256.split()[1], key.get_base64()) self.assertEqual(256, key.get_bits()) - def test_12_compare_ecdsa(self): + def test_12_compare_ecdsa_256(self): # verify that the private & public keys compare equal - key = ECDSAKey.from_private_key_file(test_path('test_ecdsa.key')) + key = ECDSAKey.from_private_key_file(test_path('test_ecdsa_256.key')) self.assertEqual(key, key) pub = ECDSAKey(data=key.asbytes()) self.assertTrue(key.can_sign()) self.assertTrue(not pub.can_sign()) self.assertEqual(key, pub) - def test_13_sign_ecdsa(self): + def test_13_sign_ecdsa_256(self): # verify that the rsa private key can sign and verify - key = ECDSAKey.from_private_key_file(test_path('test_ecdsa.key')) + key = ECDSAKey.from_private_key_file(test_path('test_ecdsa_256.key')) msg = key.sign_ssh_data(b'ice weasels') self.assertTrue(type(msg) is Message) msg.rewind() @@ -255,6 +308,109 @@ class KeyTest (unittest.TestCase): pub = ECDSAKey(data=key.asbytes()) self.assertTrue(pub.verify_ssh_sig(b'ice weasels', msg)) + def test_14_load_ecdsa_384(self): + key = ECDSAKey.from_private_key_file(test_path('test_ecdsa_384.key')) + self.assertEqual('ecdsa-sha2-nistp384', key.get_name()) + exp_ecdsa = b(FINGER_ECDSA_384.split()[1].replace(':', '')) + my_ecdsa = hexlify(key.get_fingerprint()) + self.assertEqual(exp_ecdsa, my_ecdsa) + self.assertEqual(PUB_ECDSA_384.split()[1], key.get_base64()) + self.assertEqual(384, key.get_bits()) + + s = StringIO() + key.write_private_key(s) + self.assertEqual(ECDSA_PRIVATE_OUT_384, s.getvalue()) + s.seek(0) + key2 = ECDSAKey.from_private_key(s) + self.assertEqual(key, key2) + + def test_15_load_ecdsa_password_384(self): + key = ECDSAKey.from_private_key_file(test_path('test_ecdsa_password_384.key'), b'television') + self.assertEqual('ecdsa-sha2-nistp384', key.get_name()) + exp_ecdsa = b(FINGER_ECDSA_384.split()[1].replace(':', '')) + my_ecdsa = hexlify(key.get_fingerprint()) + self.assertEqual(exp_ecdsa, my_ecdsa) + self.assertEqual(PUB_ECDSA_384.split()[1], key.get_base64()) + self.assertEqual(384, key.get_bits()) + + def test_16_compare_ecdsa_384(self): + # verify that the private & public keys compare equal + key = ECDSAKey.from_private_key_file(test_path('test_ecdsa_384.key')) + self.assertEqual(key, key) + pub = ECDSAKey(data=key.asbytes()) + self.assertTrue(key.can_sign()) + self.assertTrue(not pub.can_sign()) + self.assertEqual(key, pub) + + def test_17_sign_ecdsa_384(self): + # verify that the rsa private key can sign and verify + key = ECDSAKey.from_private_key_file(test_path('test_ecdsa_384.key')) + msg = key.sign_ssh_data(b'ice weasels') + self.assertTrue(type(msg) is Message) + msg.rewind() + self.assertEqual('ecdsa-sha2-nistp384', msg.get_text()) + # ECDSA signatures, like DSS signatures, tend to be different + # each time, so we can't compare against a "known correct" + # signature. + # Even the length of the signature can change. + + msg.rewind() + pub = ECDSAKey(data=key.asbytes()) + self.assertTrue(pub.verify_ssh_sig(b'ice weasels', msg)) + + def test_18_load_ecdsa_521(self): + key = ECDSAKey.from_private_key_file(test_path('test_ecdsa_521.key')) + self.assertEqual('ecdsa-sha2-nistp521', key.get_name()) + exp_ecdsa = b(FINGER_ECDSA_521.split()[1].replace(':', '')) + my_ecdsa = hexlify(key.get_fingerprint()) + self.assertEqual(exp_ecdsa, my_ecdsa) + self.assertEqual(PUB_ECDSA_521.split()[1], key.get_base64()) + self.assertEqual(521, key.get_bits()) + + s = StringIO() + key.write_private_key(s) + # Different versions of OpenSSL (SSLeay versions 0x1000100f and + # 0x1000207f for instance) use different apparently valid (as far as + # ssh-keygen is concerned) padding. So we can't check the actual value + # of the pem encoded key. + s.seek(0) + key2 = ECDSAKey.from_private_key(s) + self.assertEqual(key, key2) + + def test_19_load_ecdsa_password_521(self): + key = ECDSAKey.from_private_key_file(test_path('test_ecdsa_password_521.key'), b'television') + self.assertEqual('ecdsa-sha2-nistp521', key.get_name()) + exp_ecdsa = b(FINGER_ECDSA_521.split()[1].replace(':', '')) + my_ecdsa = hexlify(key.get_fingerprint()) + self.assertEqual(exp_ecdsa, my_ecdsa) + self.assertEqual(PUB_ECDSA_521.split()[1], key.get_base64()) + self.assertEqual(521, key.get_bits()) + + def test_20_compare_ecdsa_521(self): + # verify that the private & public keys compare equal + key = ECDSAKey.from_private_key_file(test_path('test_ecdsa_521.key')) + self.assertEqual(key, key) + pub = ECDSAKey(data=key.asbytes()) + self.assertTrue(key.can_sign()) + self.assertTrue(not pub.can_sign()) + self.assertEqual(key, pub) + + def test_21_sign_ecdsa_521(self): + # verify that the rsa private key can sign and verify + key = ECDSAKey.from_private_key_file(test_path('test_ecdsa_521.key')) + msg = key.sign_ssh_data(b'ice weasels') + self.assertTrue(type(msg) is Message) + msg.rewind() + self.assertEqual('ecdsa-sha2-nistp521', msg.get_text()) + # ECDSA signatures, like DSS signatures, tend to be different + # each time, so we can't compare against a "known correct" + # signature. + # Even the length of the signature can change. + + msg.rewind() + pub = ECDSAKey(data=key.asbytes()) + self.assertTrue(pub.verify_ssh_sig(b'ice weasels', msg)) + def test_salt_size(self): # Read an existing encrypted private key file_ = test_path('test_rsa_password.key') -- cgit v1.2.3 From 1a92ef5cf9a97a5dcdf96fdfa69ad0900b9dec87 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Mon, 25 Apr 2016 18:33:33 -0700 Subject: Test & implementation for part 1 re: #670 --- paramiko/config.py | 19 +++++++++++++------ tests/test_util.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 6 deletions(-) (limited to 'tests') diff --git a/paramiko/config.py b/paramiko/config.py index e18fa4bf..7374eb1a 100644 --- a/paramiko/config.py +++ b/paramiko/config.py @@ -76,15 +76,17 @@ class SSHConfig (object): 'config': {} } elif key == 'proxycommand' and value.lower() == 'none': - # Proxycommands of none should not be added as an actual value. (Issue #415) - continue + # Store 'none' as None; prior to 3.x, it will get stripped out + # at the end (for compatibility with issue #415). After 3.x, it + # will simply not get stripped, leaving a nice explicit marker. + host['config'][key] = None else: if value.startswith('"') and value.endswith('"'): value = value[1:-1] - #identityfile, localforward, remoteforward keys are special cases, since they are allowed to be - # specified multiple times and they should be tried in order - # of specification. + # identityfile, localforward, remoteforward keys are special + # cases, since they are allowed to be specified multiple times + # and they should be tried in order of specification. if key in ['identityfile', 'localforward', 'remoteforward']: if key in host['config']: host['config'][key].append(value) @@ -127,10 +129,13 @@ class SSHConfig (object): # else it will reference the original list # in self._config and update that value too # when the extend() is being called. - ret[key] = value[:] + ret[key] = value[:] if value is not None else value elif key == 'identityfile': ret[key].extend(value) ret = self._expand_variables(ret, hostname) + # TODO: remove in 3.x re #670 + if 'proxycommand' in ret and ret['proxycommand'] is None: + del ret['proxycommand'] return ret def get_hostnames(self): @@ -211,6 +216,8 @@ class SSHConfig (object): } for k in config: + if config[k] is None: + continue if k in replacements: for find, replace in replacements[k]: if isinstance(config[k], list): diff --git a/tests/test_util.py b/tests/test_util.py index a6a2c30b..e25f0563 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -485,3 +485,33 @@ Host proxycommand-with-equals-none paramiko.util.lookup_ssh_host_config(host, config), values ) + + def test_proxycommand_none_masking(self): + # Re: https://github.com/paramiko/paramiko/issues/670 + source_config = """ +Host specific-host + ProxyCommand none + +Host other-host + ProxyCommand other-proxy + +Host * + ProxyCommand default-proxy +""" + config = paramiko.SSHConfig() + config.parse(StringIO(source_config)) + # When bug is present, the full stripping-out of specific-host's + # ProxyCommand means it actually appears to pick up the default + # ProxyCommand value instead, due to cascading. It should (for + # backwards compatibility reasons in 1.x/2.x) appear completely blank, + # as if the host had no ProxyCommand whatsoever. + # Threw another unrelated host in there just for sanity reasons. + self.assertFalse('proxycommand' in config.lookup('specific-host')) + self.assertEqual( + config.lookup('other-host')['proxycommand'], + 'other-proxy' + ) + self.assertEqual( + config.lookup('some-random-host')['proxycommand'], + 'default-proxy' + ) -- cgit v1.2.3 From fdfbdbb6cc64927fe1e41592728d35eddecc08de Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Mon, 25 Apr 2016 18:58:25 -0700 Subject: Formatting tweaks re #731 --- paramiko/ecdsakey.py | 9 +++++---- paramiko/transport.py | 2 +- tests/test_pkey.py | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) (limited to 'tests') diff --git a/paramiko/ecdsakey.py b/paramiko/ecdsakey.py index 6663baa2..0af60a15 100644 --- a/paramiko/ecdsakey.py +++ b/paramiko/ecdsakey.py @@ -39,10 +39,11 @@ from paramiko.util import deflate_long, inflate_long class _ECDSACurve(object): """ - Object for representing a specific ECDSA Curve (i.e. nistp256, nistp384, - etc.). Handles the generation of the key format identifier and the - selection of the proper hash function. Also grabs the proper curve from the - ecdsa package. + Represents a specific ECDSA Curve (nistp256, nistp384, etc). + + Handles the generation of the key format identifier and the selection of + the proper hash function. Also grabs the proper curve from the 'ecdsa' + package. """ def __init__(self, curve_class, nist_name): self.nist_name = nist_name diff --git a/paramiko/transport.py b/paramiko/transport.py index a314847e..d362ea64 100644 --- a/paramiko/transport.py +++ b/paramiko/transport.py @@ -121,7 +121,7 @@ class Transport (threading.Thread, ClosingContextManager): _preferred_keys = ( 'ssh-rsa', 'ssh-dss', - )+tuple(ECDSAKey.supported_key_format_identifiers()) + ) + tuple(ECDSAKey.supported_key_format_identifiers()) _preferred_kex = ( 'diffie-hellman-group1-sha1', 'diffie-hellman-group14-sha1', diff --git a/tests/test_pkey.py b/tests/test_pkey.py index 59b3bb43..2181dd91 100644 --- a/tests/test_pkey.py +++ b/tests/test_pkey.py @@ -145,7 +145,7 @@ class KeyTest (unittest.TestCase): self.assertEqual(exp_rsa, my_rsa) self.assertEqual(PUB_RSA.split()[1], key.get_base64()) self.assertEqual(1024, key.get_bits()) - + def test_4_load_dss(self): key = DSSKey.from_private_key_file(test_path('test_dss.key')) self.assertEqual('ssh-dss', key.get_name()) -- cgit v1.2.3 From caa842bf5d5b59002e8277f55ccd29c531aea08e Mon Sep 17 00:00:00 2001 From: Krzysztof Rusek Date: Thu, 11 Feb 2016 16:51:19 +0100 Subject: Issue #537 reproduction test and fix --- paramiko/buffered_pipe.py | 14 +++++++++----- tests/test_transport.py | 18 ++++++++++++++++++ 2 files changed, 27 insertions(+), 5 deletions(-) (limited to 'tests') diff --git a/paramiko/buffered_pipe.py b/paramiko/buffered_pipe.py index d5fe164e..e81e9b3d 100644 --- a/paramiko/buffered_pipe.py +++ b/paramiko/buffered_pipe.py @@ -70,11 +70,15 @@ class BufferedPipe (object): :param threading.Event event: the event to set/clear """ - self._event = event - if len(self._buffer) > 0: - event.set() - else: - event.clear() + self._lock.acquire() + try: + self._event = event + if self._closed or len(self._buffer) > 0: + event.set() + else: + event.clear() + finally: + self._lock.release() def feed(self, data): """ diff --git a/tests/test_transport.py b/tests/test_transport.py index 5069e5b0..d81ad8f3 100644 --- a/tests/test_transport.py +++ b/tests/test_transport.py @@ -828,3 +828,21 @@ class TransportTest(unittest.TestCase): hostkey=public_host_key, username='slowdive', password='pygmalion') + + def test_M_select_after_close(self): + """ + verify that select works when a channel is already closed. + """ + self.setup_test_server() + chan = self.tc.open_session() + chan.invoke_shell() + schan = self.ts.accept(1.0) + schan.close() + + # give client a moment to receive close notification + time.sleep(0.1) + + r, w, e = select.select([chan], [], [], 0.1) + self.assertEqual([chan], r) + self.assertEqual([], w) + self.assertEqual([], e) -- cgit v1.2.3 From f336a34fafef3b128dc0d81ab5ece74c31ec2016 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Sun, 12 Jun 2016 11:17:43 -0700 Subject: Update fake test socket objects to exhibit Python 3 socket-closed flag Re #520 --- tests/loop.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'tests') diff --git a/tests/loop.py b/tests/loop.py index 4f5dc163..e805ad96 100644 --- a/tests/loop.py +++ b/tests/loop.py @@ -37,9 +37,11 @@ class LoopSocket (object): self.__cv = threading.Condition(self.__lock) self.__timeout = None self.__mate = None + self._closed = False def close(self): self.__unlink() + self._closed = True try: self.__lock.acquire() self.__in_buffer = bytes() -- cgit v1.2.3 From 28c8be18c25e9d10f8e4759e489949f1e22d6346 Mon Sep 17 00:00:00 2001 From: Philip Lorenz Date: Sun, 21 Sep 2014 12:31:40 +0200 Subject: Support transmission of environment variables The SSH protocol allows the client to transmit environment variables to the server. This is particularly useful if the user wants to modify the environment of an executed command without having to reexecute the actual command from a shell. This patch extends the Client and Channel interface to allow the transmission of environment variables to the server side. In order to use this feature the SSH server must accept environment variables from the client (e.g. the AcceptEnv configuration directive of OpenSSH). FROM BITPROPHET: backport cherry-pick to 1.x line --- paramiko/channel.py | 41 +++++++++++++++++++++++++++++++++++++++++ paramiko/client.py | 9 +++++++-- tests/test_client.py | 45 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 2 deletions(-) (limited to 'tests') diff --git a/paramiko/channel.py b/paramiko/channel.py index 3a05bdc4..7735e1f1 100644 --- a/paramiko/channel.py +++ b/paramiko/channel.py @@ -283,6 +283,47 @@ class Channel (ClosingContextManager): m.add_int(height_pixels) self.transport._send_user_message(m) + @open_only + def update_environment_variables(self, environment): + """ + Updates this channel's environment. This operation is additive - i.e. + the current environment is not reset before the given environment + variables are set. + + :param dict environment: a dictionary containing the name and respective + values to set + :raises SSHException: + if any of the environment variables was rejected by the server or + the channel was closed + """ + for name, value in environment.items(): + try: + self.set_environment_variable(name, value) + except SSHException as e: + raise SSHException("Failed to set environment variable \"%s\"." % name, e) + + @open_only + def set_environment_variable(self, name, value): + """ + Set the value of an environment variable. + + :param str name: name of the environment variable + :param str value: value of the environment variable + + :raises SSHException: + if the request was rejected or the channel was closed + """ + m = Message() + m.add_byte(cMSG_CHANNEL_REQUEST) + m.add_int(self.remote_chanid) + m.add_string('env') + m.add_boolean(True) + m.add_string(name) + m.add_string(value) + self._event_pending() + self.transport._send_user_message(m) + self._wait_for_event() + def exit_status_ready(self): """ Return true if the remote process has exited and returned an exit diff --git a/paramiko/client.py b/paramiko/client.py index ebf21b08..681760cf 100644 --- a/paramiko/client.py +++ b/paramiko/client.py @@ -398,7 +398,8 @@ class SSHClient (ClosingContextManager): self._agent.close() self._agent = None - def exec_command(self, command, bufsize=-1, timeout=None, get_pty=False): + def exec_command(self, command, bufsize=-1, timeout=None, get_pty=False, + environment=None): """ Execute a command on the SSH server. A new `.Channel` is opened and the requested command is executed. The command's input and output @@ -411,6 +412,7 @@ class SSHClient (ClosingContextManager): Python :param int timeout: set command's channel timeout. See `Channel.settimeout`.settimeout + :param dict environment: the command's environment :return: the stdin, stdout, and stderr of the executing command, as a 3-tuple @@ -421,6 +423,7 @@ class SSHClient (ClosingContextManager): if get_pty: chan.get_pty() chan.settimeout(timeout) + chan.update_environment_variables(environment or {}) chan.exec_command(command) stdin = chan.makefile('wb', bufsize) stdout = chan.makefile('r', bufsize) @@ -428,7 +431,7 @@ class SSHClient (ClosingContextManager): return stdin, stdout, stderr def invoke_shell(self, term='vt100', width=80, height=24, width_pixels=0, - height_pixels=0): + height_pixels=0, environment=None): """ Start an interactive shell session on the SSH server. A new `.Channel` is opened and connected to a pseudo-terminal using the requested @@ -440,12 +443,14 @@ class SSHClient (ClosingContextManager): :param int height: the height (in characters) of the terminal window :param int width_pixels: the width (in pixels) of the terminal window :param int height_pixels: the height (in pixels) of the terminal window + :param dict environment: the command's environment :return: a new `.Channel` connected to the remote shell :raises SSHException: if the server fails to invoke a shell """ chan = self._transport.open_session() chan.get_pty(term, width, height, width_pixels, height_pixels) + chan.update_environment_variables(environment or {}) chan.invoke_shell() return chan diff --git a/tests/test_client.py b/tests/test_client.py index d39febac..e7ebbc6a 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -79,6 +79,16 @@ class NullServer (paramiko.ServerInterface): return False return True + def check_channel_env_request(self, channel, name, value): + if name == 'INVALID_ENV': + return False + + if not hasattr(channel, 'env'): + setattr(channel, 'env', {}) + + channel.env[name] = value + return True + class SSHClientTest (unittest.TestCase): @@ -373,3 +383,38 @@ class SSHClientTest (unittest.TestCase): password='pygmalion', ) self._test_connection(**kwargs) + + def test_update_environment(self): + """ + Verify that environment variables can be set by the client. + """ + threading.Thread(target=self._run).start() + + self.tc = paramiko.SSHClient() + self.tc.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + self.assertEqual(0, len(self.tc.get_host_keys())) + self.tc.connect(self.addr, self.port, username='slowdive', password='pygmalion') + + self.event.wait(1.0) + self.assertTrue(self.event.isSet()) + self.assertTrue(self.ts.is_active()) + + target_env = {b'A': b'B', b'C': b'd'} + + self.tc.exec_command('yes', environment=target_env) + schan = self.ts.accept(1.0) + self.assertEqual(target_env, getattr(schan, 'env', {})) + schan.close() + + # Cannot use assertRaises in context manager mode as it is not supported + # in Python 2.6. + try: + # Verify that a rejection by the server can be detected + self.tc.exec_command('yes', environment={b'INVALID_ENV': b''}) + except SSHException as e: + self.assertTrue('INVALID_ENV' in str(e), + 'Expected variable name in error message') + self.assertTrue(isinstance(e.args[1], SSHException), + 'Expected original SSHException in exception') + else: + self.assertFalse(False, 'SSHException was not thrown.') -- cgit v1.2.3 From b788056982e415d4763d930a650fac437bd076ce Mon Sep 17 00:00:00 2001 From: qqo Date: Wed, 20 Jul 2016 16:06:22 +0300 Subject: Add tests for ~ expansion inside proxycommand --- tests/test_util.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'tests') diff --git a/tests/test_util.py b/tests/test_util.py index e25f0563..87624711 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -248,6 +248,19 @@ Host * val ) + def test_11_proxycommand_tilde_expansion(self): + """ + Tilde (~) should be expanded inside ProxyCommand + """ + config = paramiko.util.parse_ssh_config(StringIO(""" +Host test + ProxyCommand ssh -F ~/.ssh/test_config bastion nc %h %p +""")) + self.assertEqual( + 'ssh -F %s/.ssh/test_config bastion nc test 22' % os.path.expanduser('~'), + host_config('test', config)['proxycommand'] + ) + def test_11_host_config_test_negation(self): test_config_file = """ Host www13.* !*.example.com -- cgit v1.2.3 From 2b06f22ed73906d780a1baa4f16c5f04007e97aa Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Mon, 5 Dec 2016 19:02:24 -0800 Subject: Just get rid of the frickin' numbers. Every other merge screws them up anyway. --- tests/test_util.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) (limited to 'tests') diff --git a/tests/test_util.py b/tests/test_util.py index 87624711..a31e4507 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -66,7 +66,7 @@ from paramiko import * class UtilTest(unittest.TestCase): - def test_1_import(self): + def test_import(self): """ verify that all the classes can be imported from paramiko. """ @@ -104,7 +104,7 @@ class UtilTest(unittest.TestCase): self.assertTrue('SSHConfig' in symbols) self.assertTrue('util' in symbols) - def test_2_parse_config(self): + def test_parse_config(self): global test_config_file f = StringIO(test_config_file) config = paramiko.util.parse_ssh_config(f) @@ -114,7 +114,7 @@ class UtilTest(unittest.TestCase): {'host': ['*'], 'config': {'crazy': 'something dumb'}}, {'host': ['spoo.example.com'], 'config': {'crazy': 'something else'}}]) - def test_3_host_config(self): + def test_host_config(self): global test_config_file f = StringIO(test_config_file) config = paramiko.util.parse_ssh_config(f) @@ -141,12 +141,12 @@ class UtilTest(unittest.TestCase): values ) - def test_4_generate_key_bytes(self): + def test_generate_key_bytes(self): x = paramiko.util.generate_key_bytes(sha1, b'ABCDEFGH', 'This is my secret passphrase.', 64) hex = ''.join(['%02x' % byte_ord(c) for c in x]) self.assertEqual(hex, '9110e2f6793b69363e58173e9436b13a5a4b339005741d5c680e505f57d871347b4239f14fb5c46e857d5e100424873ba849ac699cea98d729e57b3e84378e8b') - def test_5_host_keys(self): + def test_host_keys(self): with open('hostfile.temp', 'w') as f: f.write(test_hosts_file) try: @@ -159,7 +159,7 @@ class UtilTest(unittest.TestCase): finally: os.unlink('hostfile.temp') - def test_7_host_config_expose_issue_33(self): + def test_host_config_expose_issue_33(self): test_config_file = """ Host www13.* Port 22 @@ -178,7 +178,7 @@ Host * {'hostname': host, 'port': '22'} ) - def test_8_eintr_retry(self): + def test_eintr_retry(self): self.assertEqual('foo', paramiko.util.retry_on_signal(lambda: 'foo')) # Variables that are set by raises_intr @@ -203,7 +203,7 @@ Host * self.assertRaises(AssertionError, lambda: paramiko.util.retry_on_signal(raises_other_exception)) - def test_9_proxycommand_config_equals_parsing(self): + def test_proxycommand_config_equals_parsing(self): """ ProxyCommand should not split on equals signs within the value. """ @@ -222,7 +222,7 @@ Host equals-delimited 'foo bar=biz baz' ) - def test_10_proxycommand_interpolation(self): + def test_proxycommand_interpolation(self): """ ProxyCommand should perform interpolation on the value """ @@ -248,7 +248,7 @@ Host * val ) - def test_11_proxycommand_tilde_expansion(self): + def test_proxycommand_tilde_expansion(self): """ Tilde (~) should be expanded inside ProxyCommand """ @@ -261,7 +261,7 @@ Host test host_config('test', config)['proxycommand'] ) - def test_11_host_config_test_negation(self): + def test_host_config_test_negation(self): test_config_file = """ Host www13.* !*.example.com Port 22 @@ -283,7 +283,7 @@ Host * {'hostname': host, 'port': '8080'} ) - def test_12_host_config_test_proxycommand(self): + def test_host_config_test_proxycommand(self): test_config_file = """ Host proxy-with-equal-divisor-and-space ProxyCommand = foo=bar @@ -311,7 +311,7 @@ ProxyCommand foo=bar:%h-%p values ) - def test_11_host_config_test_identityfile(self): + def test_host_config_test_identityfile(self): test_config_file = """ IdentityFile id_dsa0 @@ -341,7 +341,7 @@ IdentityFile id_dsa22 values ) - def test_12_config_addressfamily_and_lazy_fqdn(self): + def test_config_addressfamily_and_lazy_fqdn(self): """ Ensure the code path honoring non-'all' AddressFamily doesn't asplode """ @@ -357,13 +357,13 @@ IdentityFile something_%l_using_fqdn self.assertEqual(32767, paramiko.util.clamp_value(32767, 32765, 32769)) self.assertEqual(32769, paramiko.util.clamp_value(32767, 32770, 32769)) - def test_13_config_dos_crlf_succeeds(self): + def test_config_dos_crlf_succeeds(self): config_file = StringIO("host abcqwerty\r\nHostName 127.0.0.1\r\n") config = paramiko.SSHConfig() config.parse(config_file) self.assertEqual(config.lookup("abcqwerty")["hostname"], "127.0.0.1") - def test_14_get_hostnames(self): + def test_get_hostnames(self): f = StringIO(test_config_file) config = paramiko.util.parse_ssh_config(f) self.assertEqual(config.get_hostnames(), set(['*', '*.example.com', 'spoo.example.com'])) -- cgit v1.2.3 From be200e712b5514387ee578e886ec36ae640800e1 Mon Sep 17 00:00:00 2001 From: Gabi Davar Date: Wed, 24 Aug 2016 09:08:20 +0300 Subject: skip test on windows - no SIGALRM. --- tests/test_packetizer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'tests') diff --git a/tests/test_packetizer.py b/tests/test_packetizer.py index 8faec03c..5fa54e4a 100644 --- a/tests/test_packetizer.py +++ b/tests/test_packetizer.py @@ -20,6 +20,7 @@ Some unit tests for the ssh2 protocol in Transport. """ +import sys import unittest from hashlib import sha1 @@ -33,7 +34,6 @@ from paramiko.common import byte_chr, zero_byte x55 = byte_chr(0x55) x1f = byte_chr(0x1f) - class PacketizerTest (unittest.TestCase): def test_1_write(self): @@ -75,6 +75,7 @@ class PacketizerTest (unittest.TestCase): self.assertEqual(1, m.get_int()) self.assertEqual(900, m.get_int()) + @unittest.skipIf(sys.platform.startswith("win"), 'no SIGALRM on windows') def test_3_closed(self): rsock = LoopSocket() wsock = LoopSocket() -- cgit v1.2.3 From c6b209dd5690807d8e8e55518c409b164ac2e383 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Mon, 5 Dec 2016 20:56:24 -0800 Subject: Looks like skipIf was added in 2.7? Dies on 2.6 --- tests/test_packetizer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'tests') diff --git a/tests/test_packetizer.py b/tests/test_packetizer.py index 5fa54e4a..15a7c93f 100644 --- a/tests/test_packetizer.py +++ b/tests/test_packetizer.py @@ -75,8 +75,9 @@ class PacketizerTest (unittest.TestCase): self.assertEqual(1, m.get_int()) self.assertEqual(900, m.get_int()) - @unittest.skipIf(sys.platform.startswith("win"), 'no SIGALRM on windows') def test_3_closed(self): + if sys.platform.startswith("win"): # no SIGALRM on windows + return rsock = LoopSocket() wsock = LoopSocket() rsock.link(wsock) -- cgit v1.2.3 From d4a5806d23e95cc386d88a361956d0e0c1df9fb6 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Mon, 12 Dec 2016 15:33:01 -0800 Subject: Remove code re #398 from 2.0 branch, as it's feature work --- paramiko/channel.py | 52 ------------------------------------------------- paramiko/client.py | 23 ++-------------------- sites/www/changelog.rst | 10 ---------- tests/test_client.py | 45 ------------------------------------------ 4 files changed, 2 insertions(+), 128 deletions(-) (limited to 'tests') diff --git a/paramiko/channel.py b/paramiko/channel.py index 52b5d849..3a05bdc4 100644 --- a/paramiko/channel.py +++ b/paramiko/channel.py @@ -283,58 +283,6 @@ class Channel (ClosingContextManager): m.add_int(height_pixels) self.transport._send_user_message(m) - @open_only - def update_environment(self, environment): - """ - Updates this channel's remote shell environment. - - .. note:: - This operation is additive - i.e. the current environment is not - reset before the given environment variables are set. - - .. warning:: - Servers may silently reject some environment variables; see the - warning in `set_environment_variable` for details. - - :param dict environment: - a dictionary containing the name and respective values to set - :raises SSHException: - if any of the environment variables was rejected by the server or - the channel was closed - """ - for name, value in environment.items(): - try: - self.set_environment_variable(name, value) - except SSHException as e: - err = "Failed to set environment variable \"{0}\"." - raise SSHException(err.format(name), e) - - @open_only - def set_environment_variable(self, name, value): - """ - Set the value of an environment variable. - - .. warning:: - The server may reject this request depending on its ``AcceptEnv`` - setting; such rejections will fail silently (which is common client - practice for this particular request type). Make sure you - understand your server's configuration before using! - - :param str name: name of the environment variable - :param str value: value of the environment variable - - :raises SSHException: - if the request was rejected or the channel was closed - """ - m = Message() - m.add_byte(cMSG_CHANNEL_REQUEST) - m.add_int(self.remote_chanid) - m.add_string('env') - m.add_boolean(False) - m.add_string(name) - m.add_string(value) - self.transport._send_user_message(m) - def exit_status_ready(self): """ Return true if the remote process has exited and returned an exit diff --git a/paramiko/client.py b/paramiko/client.py index 40cd5cf2..ebf21b08 100644 --- a/paramiko/client.py +++ b/paramiko/client.py @@ -398,14 +398,7 @@ class SSHClient (ClosingContextManager): self._agent.close() self._agent = None - def exec_command( - self, - command, - bufsize=-1, - timeout=None, - get_pty=False, - environment=None, - ): + def exec_command(self, command, bufsize=-1, timeout=None, get_pty=False): """ Execute a command on the SSH server. A new `.Channel` is opened and the requested command is executed. The command's input and output @@ -418,14 +411,6 @@ class SSHClient (ClosingContextManager): Python :param int timeout: set command's channel timeout. See `Channel.settimeout`.settimeout - :param dict environment: - a dict of shell environment variables, to be merged into the - default environment that the remote command executes within. - - .. warning:: - Servers may silently reject some environment variables; see the - warning in `.Channel.set_environment_variable` for details. - :return: the stdin, stdout, and stderr of the executing command, as a 3-tuple @@ -436,8 +421,6 @@ class SSHClient (ClosingContextManager): if get_pty: chan.get_pty() chan.settimeout(timeout) - if environment: - chan.update_environment(environment) chan.exec_command(command) stdin = chan.makefile('wb', bufsize) stdout = chan.makefile('r', bufsize) @@ -445,7 +428,7 @@ class SSHClient (ClosingContextManager): return stdin, stdout, stderr def invoke_shell(self, term='vt100', width=80, height=24, width_pixels=0, - height_pixels=0, environment=None): + height_pixels=0): """ Start an interactive shell session on the SSH server. A new `.Channel` is opened and connected to a pseudo-terminal using the requested @@ -457,14 +440,12 @@ class SSHClient (ClosingContextManager): :param int height: the height (in characters) of the terminal window :param int width_pixels: the width (in pixels) of the terminal window :param int height_pixels: the height (in pixels) of the terminal window - :param dict environment: the command's environment :return: a new `.Channel` connected to the remote shell :raises SSHException: if the server fails to invoke a shell """ chan = self._transport.open_session() chan.get_pty(term, width, height, width_pixels, height_pixels) - chan.update_environment_variables(environment or {}) chan.invoke_shell() return chan diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index 36460eff..72ae1548 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -45,16 +45,6 @@ Changelog signature. Caught by ``@Score_Under``. * :bug:`681` Fix a Python3-specific bug re: the handling of read buffers when using ``ProxyCommand``. Thanks to Paul Kapp for catch & patch. -* :feature:`398 (1.18+)` Add an ``environment`` dict argument to - `Client.exec_command ` (plus the - lower level `Channel.update_environment - ` and - `Channel.set_environment_variable - ` methods) which - implements the ``env`` SSH message type. This means the remote shell - environment can be set without the use of ``VARNAME=value`` shell tricks, - provided the server's ``AcceptEnv`` lists the variables you need to set. - Thanks to Philip Lorenz for the pull request. * :support:`819 backported (>=1.15,<2.0)` Document how lacking ``gmp`` headers at install time can cause a significant performance hit if you build PyCrypto from source. (Most system-distributed packages already have this enabled.) diff --git a/tests/test_client.py b/tests/test_client.py index 32d9ac60..63ff9297 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -82,16 +82,6 @@ class NullServer (paramiko.ServerInterface): return False return True - def check_channel_env_request(self, channel, name, value): - if name == 'INVALID_ENV': - return False - - if not hasattr(channel, 'env'): - setattr(channel, 'env', {}) - - channel.env[name] = value - return True - class SSHClientTest (unittest.TestCase): @@ -379,38 +369,3 @@ class SSHClientTest (unittest.TestCase): password='pygmalion', ) self._test_connection(**kwargs) - - def test_update_environment(self): - """ - Verify that environment variables can be set by the client. - """ - threading.Thread(target=self._run).start() - - self.tc = paramiko.SSHClient() - self.tc.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - self.assertEqual(0, len(self.tc.get_host_keys())) - self.tc.connect(self.addr, self.port, username='slowdive', password='pygmalion') - - self.event.wait(1.0) - self.assertTrue(self.event.isSet()) - self.assertTrue(self.ts.is_active()) - - target_env = {b'A': b'B', b'C': b'd'} - - self.tc.exec_command('yes', environment=target_env) - schan = self.ts.accept(1.0) - self.assertEqual(target_env, getattr(schan, 'env', {})) - schan.close() - - # Cannot use assertRaises in context manager mode as it is not supported - # in Python 2.6. - try: - # Verify that a rejection by the server can be detected - self.tc.exec_command('yes', environment={b'INVALID_ENV': b''}) - except SSHException as e: - self.assertTrue('INVALID_ENV' in str(e), - 'Expected variable name in error message') - self.assertTrue(isinstance(e.args[1], SSHException), - 'Expected original SSHException in exception') - else: - self.assertFalse(False, 'SSHException was not thrown.') -- cgit v1.2.3 From 208922af5a2be81d7bae2b9e2e4a3475b535593c Mon Sep 17 00:00:00 2001 From: james mike dupont Date: Thu, 19 Jan 2017 03:59:30 -0500 Subject: untie agian! --- paramiko/agent.py | 2 +- paramiko/kex_gss.py | 4 ++-- paramiko/server.py | 2 +- paramiko/sftp_client.py | 4 ++-- paramiko/sftp_handle.py | 2 +- paramiko/sftp_si.py | 2 +- paramiko/transport.py | 4 ++-- tests/stub_sftp.py | 2 +- tests/test_client.py | 2 +- tests/test_sftp.py | 2 +- 10 files changed, 13 insertions(+), 13 deletions(-) (limited to 'tests') diff --git a/paramiko/agent.py b/paramiko/agent.py index 6a8e7fb4..c13810bb 100644 --- a/paramiko/agent.py +++ b/paramiko/agent.py @@ -331,7 +331,7 @@ class Agent(AgentSSH): """ Client interface for using private keys from an SSH agent running on the local machine. If an SSH agent is running, this class can be used to - connect to it and retreive `.PKey` objects which can be used when + connect to it and retrieve `.PKey` objects which can be used when attempting to authenticate to remote SSH servers. Upon initialization, a session with the local machine's SSH agent is diff --git a/paramiko/kex_gss.py b/paramiko/kex_gss.py index 69969f8a..e21d55b9 100644 --- a/paramiko/kex_gss.py +++ b/paramiko/kex_gss.py @@ -104,7 +104,7 @@ class KexGSSGroup1(object): """ Parse the next packet. - :param char ptype: The type of the incomming packet + :param char ptype: The type of the incoming packet :param `.Message` m: The paket content """ if self.transport.server_mode and (ptype == MSG_KEXGSS_INIT): @@ -335,7 +335,7 @@ class KexGSSGex(object): """ Parse the next packet. - :param char ptype: The type of the incomming packet + :param char ptype: The type of the incoming packet :param `.Message` m: The paket content """ if ptype == MSG_KEXGSS_GROUPREQ: diff --git a/paramiko/server.py b/paramiko/server.py index f79a1748..bc4ac071 100644 --- a/paramiko/server.py +++ b/paramiko/server.py @@ -385,7 +385,7 @@ class ServerInterface (object): :param int pixelheight: height of screen in pixels, if known (may be ``0`` if unknown). :return: - ``True`` if the psuedo-terminal has been allocated; ``False`` + ``True`` if the pseudo-terminal has been allocated; ``False`` otherwise. """ return False diff --git a/paramiko/sftp_client.py b/paramiko/sftp_client.py index 0df94389..12a9506f 100644 --- a/paramiko/sftp_client.py +++ b/paramiko/sftp_client.py @@ -223,7 +223,7 @@ class SFTPClient(BaseSFTP, ClosingContextManager): ``read_aheads``, an integer controlling how many ``SSH_FXP_READDIR`` requests are made to the server. The default of 50 should suffice for most file listings as each request/response cycle - may contain multiple files (dependant on server implementation.) + may contain multiple files (dependent on server implementation.) .. versionadded:: 1.15 """ @@ -828,6 +828,6 @@ class SFTPClient(BaseSFTP, ClosingContextManager): class SFTP(SFTPClient): """ - An alias for `.SFTPClient` for backwards compatability. + An alias for `.SFTPClient` for backwards compatibility. """ pass diff --git a/paramiko/sftp_handle.py b/paramiko/sftp_handle.py index edceb5ad..05b5e904 100644 --- a/paramiko/sftp_handle.py +++ b/paramiko/sftp_handle.py @@ -179,7 +179,7 @@ class SFTPHandle (ClosingContextManager): def _get_next_files(self): """ - Used by the SFTP server code to retreive a cached directory + Used by the SFTP server code to retrieve a cached directory listing. """ fnlist = self.__files[:16] diff --git a/paramiko/sftp_si.py b/paramiko/sftp_si.py index 61db956c..7ab00ad7 100644 --- a/paramiko/sftp_si.py +++ b/paramiko/sftp_si.py @@ -208,7 +208,7 @@ class SFTPServerInterface (object): The ``attr`` object will contain only those fields provided by the client in its request, so you should use ``hasattr`` to check for - the presense of fields before using them. In some cases, the ``attr`` + the presence of fields before using them. In some cases, the ``attr`` object may be completely empty. :param str path: diff --git a/paramiko/transport.py b/paramiko/transport.py index 71d5109e..f1d590ec 100644 --- a/paramiko/transport.py +++ b/paramiko/transport.py @@ -507,7 +507,7 @@ class Transport (threading.Thread, ClosingContextManager): be triggered. On failure, `is_active` will return ``False``. (Since 1.4) If ``event`` is ``None``, this method will not return until - negotation is done. On success, the method returns normally. + negotiation is done. On success, the method returns normally. Otherwise an SSHException is raised. After a successful negotiation, the client will need to authenticate. @@ -2291,7 +2291,7 @@ class Transport (threading.Thread, ClosingContextManager): finally: self.lock.release() if kind == 'direct-tcpip': - # handle direct-tcpip requests comming from the client + # handle direct-tcpip requests coming from the client dest_addr = m.get_text() dest_port = m.get_int() origin_addr = m.get_text() diff --git a/tests/stub_sftp.py b/tests/stub_sftp.py index 24380ba1..5fcca386 100644 --- a/tests/stub_sftp.py +++ b/tests/stub_sftp.py @@ -55,7 +55,7 @@ class StubSFTPHandle (SFTPHandle): class StubSFTPServer (SFTPServerInterface): # assume current folder is a fine root - # (the tests always create and eventualy delete a subfolder, so there shouldn't be any mess) + # (the tests always create and eventually delete a subfolder, so there shouldn't be any mess) ROOT = os.getcwd() def _realpath(self, path): diff --git a/tests/test_client.py b/tests/test_client.py index 63ff9297..9c5761d6 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -357,7 +357,7 @@ class SSHClientTest (unittest.TestCase): # NOTE: re #387, re #394 # If pkey module used within Client._auth isn't correctly handling auth # errors (e.g. if it allows things like ValueError to bubble up as per - # midway thru #394) client.connect() will fail (at key load step) + # midway through #394) client.connect() will fail (at key load step) # instead of succeeding (at password step) kwargs = dict( # Password-protected key whose passphrase is not 'pygmalion' (it's diff --git a/tests/test_sftp.py b/tests/test_sftp.py index e4c2c3a3..d3064fff 100755 --- a/tests/test_sftp.py +++ b/tests/test_sftp.py @@ -413,7 +413,7 @@ class SFTPTest (unittest.TestCase): def test_A_readline_seek(self): """ create a text file and write a bunch of text into it. then count the lines - in the file, and seek around to retreive particular lines. this should + in the file, and seek around to retrieve particular lines. this should verify that read buffering and 'tell' work well together, and that read buffering is reset on 'seek'. """ -- cgit v1.2.3 From 3c9537d506568272f0a8469f8336bd944c6841bf Mon Sep 17 00:00:00 2001 From: Sofian Brabez Date: Wed, 14 Dec 2016 20:57:55 +0100 Subject: Avoid PacketizerTest.test_closed_3 to fail on platforms where errno.ETIME is not defined This changes define the proper Timer expired error message instead of raising AttributeError when errno.ETIME is not available on the platform. fixes #862 --- tests/test_packetizer.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'tests') diff --git a/tests/test_packetizer.py b/tests/test_packetizer.py index a1ea398c..02173292 100644 --- a/tests/test_packetizer.py +++ b/tests/test_packetizer.py @@ -114,9 +114,13 @@ class PacketizerTest (unittest.TestCase): import signal class TimeoutError(Exception): - pass + def __init__(self, error_message): + if hasattr(errno, 'ETIME'): + self.message = os.sterror(errno.ETIME) + else: + self.messaage = error_message - def timeout(seconds=1, error_message=os.strerror(errno.ETIME)): + def timeout(seconds=1, error_message='Timer expired'): def decorator(func): def _handle_timeout(signum, frame): raise TimeoutError(error_message) -- cgit v1.2.3 From dd7679bd89c072e1cea7147db1eeb790c8967150 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Mon, 20 Feb 2017 18:05:34 -0800 Subject: Test proving #853, fails on Python 3 but not 2 --- tests/test_pkey.py | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'tests') diff --git a/tests/test_pkey.py b/tests/test_pkey.py index 2181dd91..56b0bd62 100644 --- a/tests/test_pkey.py +++ b/tests/test_pkey.py @@ -107,6 +107,8 @@ L4QLcT5aND0EHZLB2fAUDXiWIb2j4rg1mwPlBMiBXA== x1234 = b'\x01\x02\x03\x04' +TEST_KEY_BYTES = b'\x00\x00\x00\x07ssh-rsa\x00\x00\x00\x01#\x00\x00\x00\x81\x00\xd3\x8fV\xea\x07\x85\xa6k%\x8d<\x1f\xbc\x8dT\x98\xa5\x96$\xf3E#\xbe>\xbc\xd2\x93\x93\x87f\xceD\x18\xdb \x0c\xb3\xa1a\x96\xf8e#\xcc\xacS\x8a#\xefVlE\x83\x1epv\xc1o\x17M\xef\xdf\x89DUXL\xa6\x8b\xaa<\x06\x10\xd7\x93w\xec\xaf\xe2\xaf\x95\xd8\xfb\xd9\xbfw\xcb\x9f0)#y{\x10\x90\xaa\x85l\tPru\x8c\t\x19\xce\xa0\xf1\xd2\xdc\x8e/\x8b\xa8f\x9c0\xdey\x84\xd2F\xf7\xcbmm\x1f\x87' + class KeyTest (unittest.TestCase): @@ -427,3 +429,7 @@ class KeyTest (unittest.TestCase): self.assertEqual(key, key2) finally: os.remove(newfile) + + def test_stringification(self): + key = RSAKey.from_private_key_file(test_path('test_rsa.key')) + self.assertEqual(str(key), TEST_KEY_BYTES) -- cgit v1.2.3 From c0f4238eda90e884d52717ef0cb0abbc0f4ed871 Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Mon, 20 Feb 2017 20:57:59 -0800 Subject: This is awful and I hate my life --- paramiko/rsakey.py | 12 +++++++++++- tests/test_pkey.py | 9 ++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) (limited to 'tests') diff --git a/paramiko/rsakey.py b/paramiko/rsakey.py index bc9053f5..fa2b4c44 100644 --- a/paramiko/rsakey.py +++ b/paramiko/rsakey.py @@ -27,6 +27,7 @@ from cryptography.hazmat.primitives.asymmetric import rsa, padding from paramiko.message import Message from paramiko.pkey import PKey +from paramiko.py3compat import PY2 from paramiko.ssh_exception import SSHException @@ -76,7 +77,16 @@ class RSAKey(PKey): return m.asbytes() def __str__(self): - return self.asbytes() + # NOTE: as per inane commentary in #853, this appears to be the least + # crummy way to get a representation that prints identical to Python + # 2's previous behavior, on both interpreters. + # TODO: replace with a nice clean fingerprint display or something + if PY2: + # Can't just return the .decode below for Py2 because stuff still + # tries stuffing it into ASCII for whatever godforsaken reason + return self.asbytes() + else: + return self.asbytes().decode('utf8', errors='ignore') def __hash__(self): h = hash(self.get_name()) diff --git a/tests/test_pkey.py b/tests/test_pkey.py index 56b0bd62..24d78c3e 100644 --- a/tests/test_pkey.py +++ b/tests/test_pkey.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright (C) 2003-2009 Robey Pointer # # This file is part of paramiko. @@ -27,7 +28,7 @@ from hashlib import md5 import base64 from paramiko import RSAKey, DSSKey, ECDSAKey, Message, util -from paramiko.py3compat import StringIO, byte_chr, b, bytes +from paramiko.py3compat import StringIO, byte_chr, b, bytes, PY2 from tests.util import test_path @@ -107,7 +108,8 @@ L4QLcT5aND0EHZLB2fAUDXiWIb2j4rg1mwPlBMiBXA== x1234 = b'\x01\x02\x03\x04' -TEST_KEY_BYTES = b'\x00\x00\x00\x07ssh-rsa\x00\x00\x00\x01#\x00\x00\x00\x81\x00\xd3\x8fV\xea\x07\x85\xa6k%\x8d<\x1f\xbc\x8dT\x98\xa5\x96$\xf3E#\xbe>\xbc\xd2\x93\x93\x87f\xceD\x18\xdb \x0c\xb3\xa1a\x96\xf8e#\xcc\xacS\x8a#\xefVlE\x83\x1epv\xc1o\x17M\xef\xdf\x89DUXL\xa6\x8b\xaa<\x06\x10\xd7\x93w\xec\xaf\xe2\xaf\x95\xd8\xfb\xd9\xbfw\xcb\x9f0)#y{\x10\x90\xaa\x85l\tPru\x8c\t\x19\xce\xa0\xf1\xd2\xdc\x8e/\x8b\xa8f\x9c0\xdey\x84\xd2F\xf7\xcbmm\x1f\x87' +TEST_KEY_BYTESTR_2 = '\x00\x00\x00\x07ssh-rsa\x00\x00\x00\x01#\x00\x00\x00\x81\x00\xd3\x8fV\xea\x07\x85\xa6k%\x8d<\x1f\xbc\x8dT\x98\xa5\x96$\xf3E#\xbe>\xbc\xd2\x93\x93\x87f\xceD\x18\xdb \x0c\xb3\xa1a\x96\xf8e#\xcc\xacS\x8a#\xefVlE\x83\x1epv\xc1o\x17M\xef\xdf\x89DUXL\xa6\x8b\xaa<\x06\x10\xd7\x93w\xec\xaf\xe2\xaf\x95\xd8\xfb\xd9\xbfw\xcb\x9f0)#y{\x10\x90\xaa\x85l\tPru\x8c\t\x19\xce\xa0\xf1\xd2\xdc\x8e/\x8b\xa8f\x9c0\xdey\x84\xd2F\xf7\xcbmm\x1f\x87' +TEST_KEY_BYTESTR_3 = '\x00\x00\x00\x07ssh-rsa\x00\x00\x00\x01#\x00\x00\x00\x00ӏV\x07k%<\x1fT$E#>ғfD\x18 \x0cae#̬S#VlE\x1epvo\x17M߉DUXL<\x06\x10דw\u2bd5ٿw˟0)#y{\x10l\tPru\t\x19Π\u070e/f0yFmm\x1f' class KeyTest (unittest.TestCase): @@ -432,4 +434,5 @@ class KeyTest (unittest.TestCase): def test_stringification(self): key = RSAKey.from_private_key_file(test_path('test_rsa.key')) - self.assertEqual(str(key), TEST_KEY_BYTES) + comparable = TEST_KEY_BYTESTR_2 if PY2 else TEST_KEY_BYTESTR_3 + self.assertEqual(str(key), comparable) -- cgit v1.2.3 From 9d5760cf45619ce5aacb567fdc42849d678d93eb Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Wed, 31 May 2017 17:30:17 -0700 Subject: Additional house style formatting tweaks, mostly re: removal of line continuations --- paramiko/__init__.py | 31 +++++++++++++++++++------------ paramiko/auth_handler.py | 34 ++++++++++++++++++---------------- paramiko/channel.py | 17 +++++++++++------ paramiko/file.py | 5 +++-- paramiko/packet.py | 6 ++++-- paramiko/sftp_client.py | 20 +++++++++++--------- paramiko/sftp_file.py | 6 ++++-- paramiko/sftp_server.py | 22 ++++++++++++---------- paramiko/transport.py | 26 ++++++++++++++------------ tests/stub_sftp.py | 6 ++++-- tests/test_auth.py | 7 ++++--- tests/test_transport.py | 13 ++++++++----- tests/test_util.py | 7 ++++--- 13 files changed, 116 insertions(+), 84 deletions(-) (limited to 'tests') diff --git a/paramiko/__init__.py b/paramiko/__init__.py index 3aca010f..197f519a 100644 --- a/paramiko/__init__.py +++ b/paramiko/__init__.py @@ -29,14 +29,18 @@ __license__ = "GNU Lesser General Public License (LGPL)" from paramiko.transport import SecurityOptions, Transport -from paramiko.client import SSHClient, MissingHostKeyPolicy, AutoAddPolicy, \ - RejectPolicy, WarningPolicy +from paramiko.client import ( + SSHClient, MissingHostKeyPolicy, AutoAddPolicy, RejectPolicy, + WarningPolicy, +) from paramiko.auth_handler import AuthHandler from paramiko.ssh_gss import GSSAuth, GSS_AUTH_AVAILABLE from paramiko.channel import Channel, ChannelFile -from paramiko.ssh_exception import SSHException, PasswordRequiredException, \ - BadAuthenticationType, ChannelException, BadHostKeyException, \ - AuthenticationException, ProxyCommandFailure +from paramiko.ssh_exception import ( + SSHException, PasswordRequiredException, BadAuthenticationType, + ChannelException, BadHostKeyException, AuthenticationException, + ProxyCommandFailure, +) from paramiko.server import ServerInterface, SubsystemHandler, InteractiveQuery from paramiko.rsakey import RSAKey from paramiko.dsskey import DSSKey @@ -57,14 +61,17 @@ from paramiko.hostkeys import HostKeys from paramiko.config import SSHConfig from paramiko.proxy import ProxyCommand -from paramiko.common import AUTH_SUCCESSFUL, AUTH_PARTIALLY_SUCCESSFUL, \ - AUTH_FAILED, OPEN_SUCCEEDED, OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED, \ - OPEN_FAILED_CONNECT_FAILED, OPEN_FAILED_UNKNOWN_CHANNEL_TYPE, \ - OPEN_FAILED_RESOURCE_SHORTAGE +from paramiko.common import ( + AUTH_SUCCESSFUL, AUTH_PARTIALLY_SUCCESSFUL, AUTH_FAILED, OPEN_SUCCEEDED, + OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED, OPEN_FAILED_CONNECT_FAILED, + OPEN_FAILED_UNKNOWN_CHANNEL_TYPE, OPEN_FAILED_RESOURCE_SHORTAGE, +) -from paramiko.sftp import SFTP_OK, SFTP_EOF, SFTP_NO_SUCH_FILE, \ - SFTP_PERMISSION_DENIED, SFTP_FAILURE, SFTP_BAD_MESSAGE, \ - SFTP_NO_CONNECTION, SFTP_CONNECTION_LOST, SFTP_OP_UNSUPPORTED +from paramiko.sftp import ( + SFTP_OK, SFTP_EOF, SFTP_NO_SUCH_FILE, SFTP_PERMISSION_DENIED, SFTP_FAILURE, + SFTP_BAD_MESSAGE, SFTP_NO_CONNECTION, SFTP_CONNECTION_LOST, + SFTP_OP_UNSUPPORTED, +) from paramiko.common import io_sleep diff --git a/paramiko/auth_handler.py b/paramiko/auth_handler.py index ace79638..83c5a575 100644 --- a/paramiko/auth_handler.py +++ b/paramiko/auth_handler.py @@ -21,25 +21,27 @@ """ import weakref -from paramiko.common import cMSG_SERVICE_REQUEST, cMSG_DISCONNECT, \ - DISCONNECT_SERVICE_NOT_AVAILABLE, \ - DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE, \ - cMSG_USERAUTH_REQUEST, cMSG_SERVICE_ACCEPT, DEBUG, AUTH_SUCCESSFUL, INFO, \ - cMSG_USERAUTH_SUCCESS, cMSG_USERAUTH_FAILURE, AUTH_PARTIALLY_SUCCESSFUL, \ - cMSG_USERAUTH_INFO_REQUEST, WARNING, AUTH_FAILED, cMSG_USERAUTH_PK_OK, \ - cMSG_USERAUTH_INFO_RESPONSE, MSG_SERVICE_REQUEST, MSG_SERVICE_ACCEPT, \ - MSG_USERAUTH_REQUEST, MSG_USERAUTH_SUCCESS, MSG_USERAUTH_FAILURE, \ - MSG_USERAUTH_BANNER, MSG_USERAUTH_INFO_REQUEST, \ - MSG_USERAUTH_INFO_RESPONSE, \ - cMSG_USERAUTH_GSSAPI_RESPONSE, cMSG_USERAUTH_GSSAPI_TOKEN, \ - cMSG_USERAUTH_GSSAPI_MIC, MSG_USERAUTH_GSSAPI_RESPONSE, \ - MSG_USERAUTH_GSSAPI_TOKEN, MSG_USERAUTH_GSSAPI_ERROR, \ - MSG_USERAUTH_GSSAPI_ERRTOK, MSG_USERAUTH_GSSAPI_MIC, MSG_NAMES +from paramiko.common import ( + cMSG_SERVICE_REQUEST, cMSG_DISCONNECT, DISCONNECT_SERVICE_NOT_AVAILABLE, + DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE, cMSG_USERAUTH_REQUEST, + cMSG_SERVICE_ACCEPT, DEBUG, AUTH_SUCCESSFUL, INFO, cMSG_USERAUTH_SUCCESS, + cMSG_USERAUTH_FAILURE, AUTH_PARTIALLY_SUCCESSFUL, + cMSG_USERAUTH_INFO_REQUEST, WARNING, AUTH_FAILED, cMSG_USERAUTH_PK_OK, + cMSG_USERAUTH_INFO_RESPONSE, MSG_SERVICE_REQUEST, MSG_SERVICE_ACCEPT, + MSG_USERAUTH_REQUEST, MSG_USERAUTH_SUCCESS, MSG_USERAUTH_FAILURE, + MSG_USERAUTH_BANNER, MSG_USERAUTH_INFO_REQUEST, MSG_USERAUTH_INFO_RESPONSE, + cMSG_USERAUTH_GSSAPI_RESPONSE, cMSG_USERAUTH_GSSAPI_TOKEN, + cMSG_USERAUTH_GSSAPI_MIC, MSG_USERAUTH_GSSAPI_RESPONSE, + MSG_USERAUTH_GSSAPI_TOKEN, MSG_USERAUTH_GSSAPI_ERROR, + MSG_USERAUTH_GSSAPI_ERRTOK, MSG_USERAUTH_GSSAPI_MIC, MSG_NAMES, +) from paramiko.message import Message from paramiko.py3compat import bytestring -from paramiko.ssh_exception import SSHException, AuthenticationException, \ - BadAuthenticationType, PartialAuthentication +from paramiko.ssh_exception import ( + SSHException, AuthenticationException, BadAuthenticationType, + PartialAuthentication, +) from paramiko.server import InteractiveQuery from paramiko.ssh_gss import GSSAuth diff --git a/paramiko/channel.py b/paramiko/channel.py index ed03a813..1bd7969d 100644 --- a/paramiko/channel.py +++ b/paramiko/channel.py @@ -28,10 +28,11 @@ import threading from functools import wraps from paramiko import util -from paramiko.common import cMSG_CHANNEL_REQUEST, cMSG_CHANNEL_WINDOW_ADJUST, \ - cMSG_CHANNEL_DATA, cMSG_CHANNEL_EXTENDED_DATA, DEBUG, ERROR, \ - cMSG_CHANNEL_SUCCESS, cMSG_CHANNEL_FAILURE, cMSG_CHANNEL_EOF, \ - cMSG_CHANNEL_CLOSE +from paramiko.common import ( + cMSG_CHANNEL_REQUEST, cMSG_CHANNEL_WINDOW_ADJUST, cMSG_CHANNEL_DATA, + cMSG_CHANNEL_EXTENDED_DATA, DEBUG, ERROR, cMSG_CHANNEL_SUCCESS, + cMSG_CHANNEL_FAILURE, cMSG_CHANNEL_EOF, cMSG_CHANNEL_CLOSE, +) from paramiko.message import Message from paramiko.py3compat import bytes_types from paramiko.ssh_exception import SSHException @@ -50,8 +51,12 @@ def open_only(func): """ @wraps(func) def _check(self, *args, **kwds): - if self.closed or self.eof_received or self.eof_sent or \ - not self.active: + if ( + self.closed or + self.eof_received or + self.eof_sent or + not self.active + ): raise SSHException('Channel is not open') return func(self, *args, **kwds) return _check diff --git a/paramiko/file.py b/paramiko/file.py index ab95c063..92bf1f14 100644 --- a/paramiko/file.py +++ b/paramiko/file.py @@ -15,8 +15,9 @@ # You should have received a copy of the GNU Lesser General Public License # along with Paramiko; if not, write to the Free Software Foundation, Inc., # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. -from paramiko.common import linefeed_byte_value, crlf, cr_byte, \ - linefeed_byte, cr_byte_value +from paramiko.common import ( + linefeed_byte_value, crlf, cr_byte, linefeed_byte, cr_byte_value, +) from paramiko.py3compat import BytesIO, PY2, u, b, bytes_types from paramiko.util import ClosingContextManager diff --git a/paramiko/packet.py b/paramiko/packet.py index cae64727..2d36adc8 100644 --- a/paramiko/packet.py +++ b/paramiko/packet.py @@ -29,8 +29,10 @@ import time from hmac import HMAC from paramiko import util -from paramiko.common import linefeed_byte, cr_byte_value, asbytes, MSG_NAMES, \ - DEBUG, xffffffff, zero_byte +from paramiko.common import ( + linefeed_byte, cr_byte_value, asbytes, MSG_NAMES, DEBUG, xffffffff, + zero_byte, +) from paramiko.py3compat import u, byte_ord from paramiko.ssh_exception import SSHException, ProxyCommandFailure from paramiko.message import Message diff --git a/paramiko/sftp_client.py b/paramiko/sftp_client.py index 719757c5..f3895caf 100644 --- a/paramiko/sftp_client.py +++ b/paramiko/sftp_client.py @@ -28,15 +28,17 @@ from paramiko import util from paramiko.channel import Channel from paramiko.message import Message from paramiko.common import INFO, DEBUG, o777 -from paramiko.py3compat import bytestring, b, u, long, string_types, \ - bytes_types -from paramiko.sftp import BaseSFTP, CMD_OPENDIR, CMD_HANDLE, SFTPError, \ - CMD_READDIR, CMD_NAME, CMD_CLOSE, SFTP_FLAG_READ, SFTP_FLAG_WRITE, \ - SFTP_FLAG_CREATE, SFTP_FLAG_TRUNC, SFTP_FLAG_APPEND, SFTP_FLAG_EXCL, \ - CMD_OPEN, CMD_REMOVE, CMD_RENAME, CMD_MKDIR, CMD_RMDIR, CMD_STAT, \ - CMD_ATTRS, CMD_LSTAT, CMD_SYMLINK, CMD_SETSTAT, CMD_READLINK, \ - CMD_REALPATH, CMD_STATUS, SFTP_OK, SFTP_EOF, SFTP_NO_SUCH_FILE, \ - SFTP_PERMISSION_DENIED +from paramiko.py3compat import ( + bytestring, b, u, long, string_types, bytes_types, +) +from paramiko.sftp import ( + BaseSFTP, CMD_OPENDIR, CMD_HANDLE, SFTPError, CMD_READDIR, CMD_NAME, + CMD_CLOSE, SFTP_FLAG_READ, SFTP_FLAG_WRITE, SFTP_FLAG_CREATE, + SFTP_FLAG_TRUNC, SFTP_FLAG_APPEND, SFTP_FLAG_EXCL, CMD_OPEN, CMD_REMOVE, + CMD_RENAME, CMD_MKDIR, CMD_RMDIR, CMD_STAT, CMD_ATTRS, CMD_LSTAT, + CMD_SYMLINK, CMD_SETSTAT, CMD_READLINK, CMD_REALPATH, CMD_STATUS, SFTP_OK, + SFTP_EOF, SFTP_NO_SUCH_FILE, SFTP_PERMISSION_DENIED, +) from paramiko.sftp_attr import SFTPAttributes from paramiko.ssh_exception import SSHException diff --git a/paramiko/sftp_file.py b/paramiko/sftp_file.py index 9ebaaf66..13e28e28 100644 --- a/paramiko/sftp_file.py +++ b/paramiko/sftp_file.py @@ -31,8 +31,10 @@ from paramiko.common import DEBUG from paramiko.file import BufferedFile from paramiko.py3compat import long -from paramiko.sftp import CMD_CLOSE, CMD_READ, CMD_DATA, SFTPError,\ - CMD_WRITE, CMD_STATUS, CMD_FSTAT, CMD_ATTRS, CMD_FSETSTAT, CMD_EXTENDED +from paramiko.sftp import ( + CMD_CLOSE, CMD_READ, CMD_DATA, SFTPError, CMD_WRITE, CMD_STATUS, CMD_FSTAT, + CMD_ATTRS, CMD_FSETSTAT, CMD_EXTENDED, +) from paramiko.sftp_attr import SFTPAttributes diff --git a/paramiko/sftp_server.py b/paramiko/sftp_server.py index 6c59b2fa..d7b46e41 100644 --- a/paramiko/sftp_server.py +++ b/paramiko/sftp_server.py @@ -26,8 +26,9 @@ import sys from hashlib import md5, sha1 from paramiko import util -from paramiko.sftp import BaseSFTP, Message, SFTP_FAILURE, \ - SFTP_PERMISSION_DENIED, SFTP_NO_SUCH_FILE +from paramiko.sftp import ( + BaseSFTP, Message, SFTP_FAILURE, SFTP_PERMISSION_DENIED, SFTP_NO_SUCH_FILE, +) from paramiko.sftp_si import SFTPServerInterface from paramiko.sftp_attr import SFTPAttributes from paramiko.common import DEBUG @@ -36,14 +37,15 @@ from paramiko.server import SubsystemHandler # known hash algorithms for the "check-file" extension -from paramiko.sftp import CMD_HANDLE, SFTP_DESC, CMD_STATUS, SFTP_EOF, \ - CMD_NAME, SFTP_BAD_MESSAGE, CMD_EXTENDED_REPLY, SFTP_FLAG_READ, \ - SFTP_FLAG_WRITE, SFTP_FLAG_APPEND, SFTP_FLAG_CREATE, SFTP_FLAG_TRUNC, \ - SFTP_FLAG_EXCL, CMD_NAMES, CMD_OPEN, CMD_CLOSE, SFTP_OK, CMD_READ, \ - CMD_DATA, CMD_WRITE, CMD_REMOVE, CMD_RENAME, CMD_MKDIR, CMD_RMDIR, \ - CMD_OPENDIR, CMD_READDIR, CMD_STAT, CMD_ATTRS, CMD_LSTAT, CMD_FSTAT, \ - CMD_SETSTAT, CMD_FSETSTAT, CMD_READLINK, CMD_SYMLINK, CMD_REALPATH, \ - CMD_EXTENDED, SFTP_OP_UNSUPPORTED +from paramiko.sftp import ( + CMD_HANDLE, SFTP_DESC, CMD_STATUS, SFTP_EOF, CMD_NAME, SFTP_BAD_MESSAGE, + CMD_EXTENDED_REPLY, SFTP_FLAG_READ, SFTP_FLAG_WRITE, SFTP_FLAG_APPEND, + SFTP_FLAG_CREATE, SFTP_FLAG_TRUNC, SFTP_FLAG_EXCL, CMD_NAMES, CMD_OPEN, + CMD_CLOSE, SFTP_OK, CMD_READ, CMD_DATA, CMD_WRITE, CMD_REMOVE, CMD_RENAME, + CMD_MKDIR, CMD_RMDIR, CMD_OPENDIR, CMD_READDIR, CMD_STAT, CMD_ATTRS, + CMD_LSTAT, CMD_FSTAT, CMD_SETSTAT, CMD_FSETSTAT, CMD_READLINK, CMD_SYMLINK, + CMD_REALPATH, CMD_EXTENDED, SFTP_OP_UNSUPPORTED, +) _hash_class = { 'sha1': sha1, diff --git a/paramiko/transport.py b/paramiko/transport.py index 1b1ebec7..0a570977 100644 --- a/paramiko/transport.py +++ b/paramiko/transport.py @@ -38,18 +38,20 @@ from paramiko import util from paramiko.auth_handler import AuthHandler from paramiko.ssh_gss import GSSAuth from paramiko.channel import Channel -from paramiko.common import xffffffff, cMSG_CHANNEL_OPEN, cMSG_IGNORE, \ - cMSG_GLOBAL_REQUEST, DEBUG, MSG_KEXINIT, MSG_IGNORE, MSG_DISCONNECT, \ - MSG_DEBUG, ERROR, WARNING, cMSG_UNIMPLEMENTED, INFO, cMSG_KEXINIT, \ - cMSG_NEWKEYS, MSG_NEWKEYS, cMSG_REQUEST_SUCCESS, cMSG_REQUEST_FAILURE, \ - CONNECTION_FAILED_CODE, OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED, \ - OPEN_SUCCEEDED, cMSG_CHANNEL_OPEN_FAILURE, cMSG_CHANNEL_OPEN_SUCCESS, \ - MSG_GLOBAL_REQUEST, MSG_REQUEST_SUCCESS, MSG_REQUEST_FAILURE, \ - MSG_CHANNEL_OPEN_SUCCESS, MSG_CHANNEL_OPEN_FAILURE, MSG_CHANNEL_OPEN, \ - MSG_CHANNEL_SUCCESS, MSG_CHANNEL_FAILURE, MSG_CHANNEL_DATA, \ - MSG_CHANNEL_EXTENDED_DATA, MSG_CHANNEL_WINDOW_ADJUST, MSG_CHANNEL_REQUEST, \ - MSG_CHANNEL_EOF, MSG_CHANNEL_CLOSE, MIN_WINDOW_SIZE, MIN_PACKET_SIZE, \ - MAX_WINDOW_SIZE, DEFAULT_WINDOW_SIZE, DEFAULT_MAX_PACKET_SIZE +from paramiko.common import ( + xffffffff, cMSG_CHANNEL_OPEN, cMSG_IGNORE, cMSG_GLOBAL_REQUEST, DEBUG, + MSG_KEXINIT, MSG_IGNORE, MSG_DISCONNECT, MSG_DEBUG, ERROR, WARNING, + cMSG_UNIMPLEMENTED, INFO, cMSG_KEXINIT, cMSG_NEWKEYS, MSG_NEWKEYS, + cMSG_REQUEST_SUCCESS, cMSG_REQUEST_FAILURE, CONNECTION_FAILED_CODE, + OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED, OPEN_SUCCEEDED, + cMSG_CHANNEL_OPEN_FAILURE, cMSG_CHANNEL_OPEN_SUCCESS, MSG_GLOBAL_REQUEST, + MSG_REQUEST_SUCCESS, MSG_REQUEST_FAILURE, MSG_CHANNEL_OPEN_SUCCESS, + MSG_CHANNEL_OPEN_FAILURE, MSG_CHANNEL_OPEN, MSG_CHANNEL_SUCCESS, + MSG_CHANNEL_FAILURE, MSG_CHANNEL_DATA, MSG_CHANNEL_EXTENDED_DATA, + MSG_CHANNEL_WINDOW_ADJUST, MSG_CHANNEL_REQUEST, MSG_CHANNEL_EOF, + MSG_CHANNEL_CLOSE, MIN_WINDOW_SIZE, MIN_PACKET_SIZE, MAX_WINDOW_SIZE, + DEFAULT_WINDOW_SIZE, DEFAULT_MAX_PACKET_SIZE, +) from paramiko.compress import ZlibCompressor, ZlibDecompressor from paramiko.dsskey import DSSKey from paramiko.kex_gex import KexGex, KexGexSHA256 diff --git a/tests/stub_sftp.py b/tests/stub_sftp.py index 5fcca386..334af561 100644 --- a/tests/stub_sftp.py +++ b/tests/stub_sftp.py @@ -22,8 +22,10 @@ A stub SFTP server for loopback SFTP testing. import os import sys -from paramiko import ServerInterface, SFTPServerInterface, SFTPServer, SFTPAttributes, \ - SFTPHandle, SFTP_OK, AUTH_SUCCESSFUL, OPEN_SUCCEEDED +from paramiko import ( + ServerInterface, SFTPServerInterface, SFTPServer, SFTPAttributes, + SFTPHandle, SFTP_OK, AUTH_SUCCESSFUL, OPEN_SUCCEEDED, +) from paramiko.common import o666 diff --git a/tests/test_auth.py b/tests/test_auth.py index 23517790..96f7611c 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -24,9 +24,10 @@ import sys import threading import unittest -from paramiko import Transport, ServerInterface, RSAKey, DSSKey, \ - BadAuthenticationType, InteractiveQuery, \ - AuthenticationException +from paramiko import ( + Transport, ServerInterface, RSAKey, DSSKey, BadAuthenticationType, + InteractiveQuery, AuthenticationException, +) from paramiko import AUTH_FAILED, AUTH_PARTIALLY_SUCCESSFUL, AUTH_SUCCESSFUL from paramiko.py3compat import u from tests.loop import LoopSocket diff --git a/tests/test_transport.py b/tests/test_transport.py index d81ad8f3..2ebdf854 100644 --- a/tests/test_transport.py +++ b/tests/test_transport.py @@ -31,13 +31,16 @@ import random from hashlib import sha1 import unittest -from paramiko import Transport, SecurityOptions, ServerInterface, RSAKey, DSSKey, \ - SSHException, ChannelException, Packetizer +from paramiko import ( + Transport, SecurityOptions, ServerInterface, RSAKey, DSSKey, SSHException, + ChannelException, Packetizer, +) from paramiko import AUTH_FAILED, AUTH_SUCCESSFUL from paramiko import OPEN_SUCCEEDED, OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED -from paramiko.common import MSG_KEXINIT, cMSG_CHANNEL_WINDOW_ADJUST, \ - MIN_PACKET_SIZE, MIN_WINDOW_SIZE, MAX_WINDOW_SIZE, \ - DEFAULT_WINDOW_SIZE, DEFAULT_MAX_PACKET_SIZE +from paramiko.common import ( + MSG_KEXINIT, cMSG_CHANNEL_WINDOW_ADJUST, MIN_PACKET_SIZE, MIN_WINDOW_SIZE, + MAX_WINDOW_SIZE, DEFAULT_WINDOW_SIZE, DEFAULT_MAX_PACKET_SIZE, +) from paramiko.py3compat import bytes from paramiko.message import Message from tests.loop import LoopSocket diff --git a/tests/test_util.py b/tests/test_util.py index a31e4507..7880e156 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -475,9 +475,10 @@ Host param3 parara safe_has_bytes = safe_string(has_bytes) expected_bytes = b("has %07%03 bytes") err = "{0!r} != {1!r}" - assert safe_vanilla == vanilla, err.format(safe_vanilla, vanilla) - assert safe_has_bytes == expected_bytes, \ - err.format(safe_has_bytes, expected_bytes) + msg = err.format(safe_vanilla, vanilla) + assert safe_vanilla == vanilla, msg + msg = err.format(safe_has_bytes, expected_bytes) + assert safe_has_bytes == expected_bytes, msg def test_proxycommand_none_issue_418(self): test_config_file = """ -- cgit v1.2.3 From 7a2c893d95651ad1f4667f7e4480da149069b7ff Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Wed, 31 May 2017 18:18:33 -0700 Subject: Even moar parentheses over backslashes --- paramiko/packet.py | 16 ++++++++++------ paramiko/primes.py | 7 +++++-- paramiko/server.py | 10 ++++++---- paramiko/sftp_file.py | 12 ++++++++---- paramiko/ssh_exception.py | 3 +-- paramiko/ssh_gss.py | 16 +++++++++++----- paramiko/transport.py | 15 +++++++++++---- paramiko/util.py | 3 +-- tests/test_gssapi.py | 8 +++++--- 9 files changed, 58 insertions(+), 32 deletions(-) (limited to 'tests') diff --git a/paramiko/packet.py b/paramiko/packet.py index 6f3cd4e2..16288a0a 100644 --- a/paramiko/packet.py +++ b/paramiko/packet.py @@ -394,9 +394,11 @@ class Packetizer (object): self.__sent_bytes += len(out) self.__sent_packets += 1 - if (self.__sent_packets >= self.REKEY_PACKETS or - self.__sent_bytes >= self.REKEY_BYTES)\ - and not self.__need_rekey: + sent_too_much = ( + self.__sent_packets >= self.REKEY_PACKETS or + self.__sent_bytes >= self.REKEY_BYTES + ) + if sent_too_much and not self.__need_rekey: # only ask once for rekeying self._log(DEBUG, 'Rekeying (hit %d packets, %d bytes sent)' % (self.__sent_packets, self.__sent_bytes)) @@ -506,9 +508,11 @@ class Packetizer (object): self.__logger.log(level, msg) def _check_keepalive(self): - if (not self.__keepalive_interval) or \ - (not self.__block_engine_out) or \ - self.__need_rekey: + if ( + not self.__keepalive_interval or + not self.__block_engine_out or + self.__need_rekey + ): # wait till we're encrypting, and not in the middle of rekeying return now = time.time() diff --git a/paramiko/primes.py b/paramiko/primes.py index 50100ad5..48a34e53 100644 --- a/paramiko/primes.py +++ b/paramiko/primes.py @@ -75,8 +75,11 @@ class ModulusPack (object): # type 2 (meets basic structural requirements) # test 4 (more than just a small-prime sieve) # tries < 100 if test & 4 (at least 100 tries of miller-rabin) - if (mod_type < 2) or (tests < 4) or \ - ((tests & 4) and (tests < 8) and (tries < 100)): + if ( + mod_type < 2 or + tests < 4 or + (tests & 4 and tests < 8 and tries < 100) + ): self.discarded.append( (modulus, 'does not meet basic requirements')) return diff --git a/paramiko/server.py b/paramiko/server.py index 89278a82..953bb33f 100644 --- a/paramiko/server.py +++ b/paramiko/server.py @@ -22,8 +22,10 @@ import threading from paramiko import util -from paramiko.common import DEBUG, ERROR, \ - OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED, AUTH_FAILED, AUTH_SUCCESSFUL +from paramiko.common import ( + DEBUG, ERROR, OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED, AUTH_FAILED, + AUTH_SUCCESSFUL, +) from paramiko.py3compat import string_types @@ -450,8 +452,8 @@ class ServerInterface (object): ``True`` if this channel is now hooked up to the requested subsystem; ``False`` if that subsystem can't or won't be provided. """ - handler_class, larg, kwarg = \ - channel.get_transport()._get_subsystem_handler(name) + transport = channel.get_transport() + handler_class, larg, kwarg = transport._get_subsystem_handler(name) if handler_class is None: return False handler = handler_class(channel, name, self, *larg, **kwarg) diff --git a/paramiko/sftp_file.py b/paramiko/sftp_file.py index 13e28e28..58653c79 100644 --- a/paramiko/sftp_file.py +++ b/paramiko/sftp_file.py @@ -194,8 +194,10 @@ class SFTPFile (BufferedFile): data[:chunk] ) self._reqs.append(sftp_async_request) - if not self.pipelined or \ - (len(self._reqs) > 100 and self.sftp.sock.recv_ready()): + if ( + not self.pipelined or + (len(self._reqs) > 100 and self.sftp.sock.recv_ready()) + ): while len(self._reqs): req = self._reqs.popleft() t, msg = self.sftp._read_response(req) @@ -476,8 +478,10 @@ class SFTPFile (BufferedFile): read_chunks = [] for offset, size in chunks: # don't fetch data that's already in the prefetch buffer - if self._data_in_prefetch_buffers(offset) or \ - self._data_in_prefetch_requests(offset, size): + if ( + self._data_in_prefetch_buffers(offset) or + self._data_in_prefetch_requests(offset, size) + ): continue # break up anything larger than the max read size diff --git a/paramiko/ssh_exception.py b/paramiko/ssh_exception.py index eb63df4e..e3584d89 100644 --- a/paramiko/ssh_exception.py +++ b/paramiko/ssh_exception.py @@ -108,8 +108,7 @@ class BadHostKeyException (SSHException): .. versionadded:: 1.6 """ def __init__(self, hostname, got_key, expected_key): - message = 'Host key for server {} does not match : ' \ - 'got {} expected {}' + message = 'Host key for server {} does not match: got {} expected {}' message = message.format( hostname, got_key.get_base64(), expected_key.get_base64()) diff --git a/paramiko/ssh_gss.py b/paramiko/ssh_gss.py index 8bb0c2e0..9c88c6fc 100644 --- a/paramiko/ssh_gss.py +++ b/paramiko/ssh_gss.py @@ -405,12 +405,16 @@ class _SSH_SSPI(_SSH_GSSAuth): _SSH_GSSAuth.__init__(self, auth_method, gss_deleg_creds) if self._gss_deleg_creds: - self._gss_flags = \ - sspicon.ISC_REQ_INTEGRITY | sspicon.ISC_REQ_MUTUAL_AUTH | \ + self._gss_flags = ( + sspicon.ISC_REQ_INTEGRITY | + sspicon.ISC_REQ_MUTUAL_AUTH | sspicon.ISC_REQ_DELEGATE + ) else: - self._gss_flags = \ - sspicon.ISC_REQ_INTEGRITY | sspicon.ISC_REQ_MUTUAL_AUTH + self._gss_flags = ( + sspicon.ISC_REQ_INTEGRITY | + sspicon.ISC_REQ_MUTUAL_AUTH + ) def ssh_init_sec_context(self, target, desired_mech=None, username=None, recv_token=None): @@ -546,8 +550,10 @@ class _SSH_SSPI(_SSH_GSSAuth): :return: ``True`` if credentials are delegated, otherwise ``False`` :rtype: Boolean """ - return self._gss_flags & sspicon.ISC_REQ_DELEGATE and \ + return ( + self._gss_flags & sspicon.ISC_REQ_DELEGATE and (self._gss_srv_ctxt_status or self._gss_flags) + ) def save_client_creds(self, client_token): """ diff --git a/paramiko/transport.py b/paramiko/transport.py index 0a570977..129562d1 100644 --- a/paramiko/transport.py +++ b/paramiko/transport.py @@ -2125,8 +2125,13 @@ class Transport (threading.Thread, ClosingContextManager): self.packetizer.set_outbound_cipher( engine, block_size, mac_engine, mac_size, mac_key, sdctr) compress_out = self._compression_info[self.local_compression][0] - if (compress_out is not None) and \ - ((self.local_compression != 'zlib@openssh.com') or self.authenticated): + if ( + compress_out is not None and + ( + self.local_compression != 'zlib@openssh.com' or + self.authenticated + ) + ): self._log(DEBUG, 'Switching on outbound compression ...') self.packetizer.set_outbound_compressor(compress_out()) if not self.packetizer.need_rekey(): @@ -2275,8 +2280,10 @@ class Transport (threading.Thread, ClosingContextManager): initial_window_size = m.get_int() max_packet_size = m.get_int() reject = False - if (kind == 'auth-agent@openssh.com') and \ - (self._forward_agent_handler is not None): + if ( + kind == 'auth-agent@openssh.com' and + self._forward_agent_handler is not None + ): self._log(DEBUG, 'Incoming forward agent connection') self.lock.acquire() try: diff --git a/paramiko/util.py b/paramiko/util.py index 82157c24..de099c0c 100644 --- a/paramiko/util.py +++ b/paramiko/util.py @@ -248,8 +248,7 @@ def log_to_file(filename, level=DEBUG): l.setLevel(level) f = open(filename, 'a') lh = logging.StreamHandler(f) - frm = '%(levelname)-.3s [%(asctime)s.%(msecs)03d] thr=%(_threadid)-3d ' \ - '%(name)s: %(message)s' + frm = '%(levelname)-.3s [%(asctime)s.%(msecs)03d] thr=%(_threadid)-3d %(name)s: %(message)s' # noqa lh.setFormatter(logging.Formatter(frm, '%Y%m%d-%H:%M:%S')) l.addHandler(lh) diff --git a/tests/test_gssapi.py b/tests/test_gssapi.py index 96c268d9..bc220108 100644 --- a/tests/test_gssapi.py +++ b/tests/test_gssapi.py @@ -104,9 +104,11 @@ class GSSAPITest(unittest.TestCase): status = gss_srv_ctxt.verify_mic(mic_msg, mic_token) self.assertEquals(0, status) else: - gss_flags = sspicon.ISC_REQ_INTEGRITY |\ - sspicon.ISC_REQ_MUTUAL_AUTH |\ - sspicon.ISC_REQ_DELEGATE + gss_flags = ( + sspicon.ISC_REQ_INTEGRITY | + sspicon.ISC_REQ_MUTUAL_AUTH | + sspicon.ISC_REQ_DELEGATE + ) # Initialize a GSS-API context. target_name = "host/" + socket.getfqdn(targ_name) gss_ctxt = sspi.ClientAuth("Kerberos", -- cgit v1.2.3 From afbcd96fcca5aa4ccb72421d9660874586cbef4f Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Thu, 1 Jun 2017 12:42:35 -0700 Subject: Remove unused value from demo/test. Honestly not sure WTF --- demos/demo_server.py | 4 +--- tests/test_ssh_gss.py | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) (limited to 'tests') diff --git a/demos/demo_server.py b/demos/demo_server.py index 4867e9ca..3a7ec854 100644 --- a/demos/demo_server.py +++ b/demos/demo_server.py @@ -96,9 +96,7 @@ class Server (paramiko.ServerInterface): return paramiko.AUTH_FAILED def enable_auth_gssapi(self): - UseGSSAPI = True - GSSAPICleanupCredentials = False - return UseGSSAPI + return True def get_allowed_auths(self, username): return 'gssapi-keyex,gssapi-with-mic,password,publickey' diff --git a/tests/test_ssh_gss.py b/tests/test_ssh_gss.py index e20d348f..967b3b81 100644 --- a/tests/test_ssh_gss.py +++ b/tests/test_ssh_gss.py @@ -43,9 +43,7 @@ class NullServer (paramiko.ServerInterface): return paramiko.AUTH_FAILED def enable_auth_gssapi(self): - UseGSSAPI = True - GSSAPICleanupCredentials = True - return UseGSSAPI + return True def check_channel_request(self, kind, chanid): return paramiko.OPEN_SUCCEEDED -- cgit v1.2.3 From 4d15d0e824d199b60f96b7ee5584d4cb5b23fc1a Mon Sep 17 00:00:00 2001 From: Jeff Forcier Date: Thu, 1 Jun 2017 13:08:10 -0700 Subject: Test & impl for truly functional HostKeys.__delitem__ --- paramiko/hostkeys.py | 9 ++++++++- tests/test_hostkeys.py | 12 ++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) (limited to 'tests') diff --git a/paramiko/hostkeys.py b/paramiko/hostkeys.py index 4be3fd42..b72abe40 100644 --- a/paramiko/hostkeys.py +++ b/paramiko/hostkeys.py @@ -246,7 +246,14 @@ class HostKeys (MutableMapping): return ret def __delitem__(self, key): - pass # Needed for instantiating HostKeys. + index = None + for i, entry in enumerate(self._entries): + if self._hostname_matches(key, entry): + index = i + break + if i is None: + raise KeyError(key) + self._entries.pop(i) def __setitem__(self, hostname, entry): # don't use this please. diff --git a/tests/test_hostkeys.py b/tests/test_hostkeys.py index 2bdcad9c..2c7ceeb9 100644 --- a/tests/test_hostkeys.py +++ b/tests/test_hostkeys.py @@ -115,3 +115,15 @@ class HostKeysTest (unittest.TestCase): self.assertEqual(b'7EC91BB336CB6D810B124B1353C32396', fp) fp = hexlify(hostdict['secure.example.com']['ssh-dss'].get_fingerprint()).upper() self.assertEqual(b'4478F0B9A23CC5182009FF755BC1D26C', fp) + + def test_delitem(self): + hostdict = paramiko.HostKeys('hostfile.temp') + target = 'happy.example.com' + entry = hostdict[target] # will KeyError if not present + del hostdict[target] + try: + entry = hostdict[target] + except KeyError: + pass # Good + else: + assert False, "Entry was not deleted from HostKeys on delitem!" -- cgit v1.2.3 From d510b1ae91978a169bb4bd2a9e5e165d2311bc6b Mon Sep 17 00:00:00 2001 From: Pierce Lopez Date: Mon, 5 Jun 2017 04:02:59 -0400 Subject: test transport security options can be set to defaults ensures all defaults key/cipher/digest etc types are supported --- tests/test_transport.py | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'tests') diff --git a/tests/test_transport.py b/tests/test_transport.py index 2ebdf854..c426cef1 100644 --- a/tests/test_transport.py +++ b/tests/test_transport.py @@ -165,6 +165,15 @@ class TransportTest(unittest.TestCase): except TypeError: pass + def test_1b_security_options_reset(self): + o = self.tc.get_security_options() + # should not throw any exceptions + o.ciphers = o.ciphers + o.digests = o.digests + o.key_types = o.key_types + o.kex = o.kex + o.compression = o.compression + def test_2_compute_key(self): self.tc.K = 123281095979686581523377256114209720774539068973101330872763622971399429481072519713536292772709507296759612401802191955568143056534122385270077606457721553469730659233569339356140085284052436697480759510519672848743794433460113118986816826624865291116513647975790797391795651716378444844877749505443714557929 self.tc.H = b'\x0C\x83\x07\xCD\xE6\x85\x6F\xF3\x0B\xA9\x36\x84\xEB\x0F\x04\xC2\x52\x0E\x9E\xD3' -- cgit v1.2.3