From 787b0b461da1bb8b9b20930c367051cd31697abf Mon Sep 17 00:00:00 2001 From: Robey Pointer Date: Sun, 23 Jul 2006 16:56:49 -0700 Subject: [PATCH] [project @ robey@lag.net-20060723235649-5f757e44908ffb31] attempt to implement support for kex-gex 'old' packet type, which is apparently used by putty (this would only affect paramiko in server mode) --- paramiko/kex_gex.py | 51 ++++++++++++++++++++++++------ tests/test_kex.py | 76 ++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 110 insertions(+), 17 deletions(-) diff --git a/paramiko/kex_gex.py b/paramiko/kex_gex.py index e945c1f..9f8848c 100644 --- a/paramiko/kex_gex.py +++ b/paramiko/kex_gex.py @@ -31,7 +31,8 @@ from paramiko.message import Message from paramiko.ssh_exception import SSHException -_MSG_KEXDH_GEX_GROUP, _MSG_KEXDH_GEX_INIT, _MSG_KEXDH_GEX_REPLY, _MSG_KEXDH_GEX_REQUEST = range(31, 35) +_MSG_KEXDH_GEX_REQUEST_OLD, _MSG_KEXDH_GEX_GROUP, _MSG_KEXDH_GEX_INIT, \ + _MSG_KEXDH_GEX_REPLY, _MSG_KEXDH_GEX_REQUEST = range(30, 35) class KexGex (object): @@ -49,19 +50,25 @@ class KexGex (object): self.x = None self.e = None self.f = None + self.old_style = False - def start_kex(self): + def start_kex(self, _test_old_style=False): if self.transport.server_mode: - self.transport._expect_packet(_MSG_KEXDH_GEX_REQUEST) + self.transport._expect_packet(_MSG_KEXDH_GEX_REQUEST, _MSG_KEXDH_GEX_REQUEST_OLD) return # request a bit range: we accept (min_bits) to (max_bits), but prefer # (preferred_bits). according to the spec, we shouldn't pull the # minimum up above 1024. m = Message() - m.add_byte(chr(_MSG_KEXDH_GEX_REQUEST)) - m.add_int(self.min_bits) - m.add_int(self.preferred_bits) - m.add_int(self.max_bits) + if _test_old_style: + # only used for unit tests: we shouldn't ever send this + m.add_byte(chr(_MSG_KEXDH_GEX_REQUEST_OLD)) + m.add_int(self.preferred_bits) + else: + m.add_byte(chr(_MSG_KEXDH_GEX_REQUEST)) + m.add_int(self.min_bits) + m.add_int(self.preferred_bits) + m.add_int(self.max_bits) self.transport._send_message(m) self.transport._expect_packet(_MSG_KEXDH_GEX_GROUP) @@ -74,6 +81,8 @@ class KexGex (object): return self._parse_kexdh_gex_init(m) elif ptype == _MSG_KEXDH_GEX_REPLY: return self._parse_kexdh_gex_reply(m) + elif ptype == _MSG_KEXDH_GEX_REQUEST_OLD: + return self._parse_kexdh_gex_request_old(m) raise SSHException('KexGex asked to handle packet type %d' % ptype) @@ -132,6 +141,28 @@ class KexGex (object): self.transport._send_message(m) self.transport._expect_packet(_MSG_KEXDH_GEX_INIT) + def _parse_kexdh_gex_request_old(self, m): + # same as above, but without min_bits or max_bits (used by older clients like putty) + self.preferred_bits = m.get_int() + # smoosh the user's preferred size into our own limits + if self.preferred_bits > self.max_bits: + self.preferred_bits = self.max_bits + if self.preferred_bits < self.min_bits: + self.preferred_bits = self.min_bits + # generate prime + pack = self.transport._get_modulus_pack() + if pack is None: + raise SSHException('Can\'t do server-side gex with no modulus pack') + self.transport._log(DEBUG, 'Picking p (~ %d bits)' % (self.preferred_bits,)) + self.g, self.p = pack.get_modulus(self.min_bits, self.preferred_bits, self.max_bits) + m = Message() + m.add_byte(chr(_MSG_KEXDH_GEX_GROUP)) + m.add_mpint(self.p) + m.add_mpint(self.g) + self.transport._send_message(m) + self.transport._expect_packet(_MSG_KEXDH_GEX_INIT) + self.old_style = True + def _parse_kexdh_gex_group(self, m): self.p = m.get_mpint() self.g = m.get_mpint() @@ -162,9 +193,11 @@ class KexGex (object): hm.add(self.transport.remote_version, self.transport.local_version, self.transport.remote_kex_init, self.transport.local_kex_init, key) - hm.add_int(self.min_bits) + if not self.old_style: + hm.add_int(self.min_bits) hm.add_int(self.preferred_bits) - hm.add_int(self.max_bits) + if not self.old_style: + hm.add_int(self.max_bits) hm.add_mpint(self.p) hm.add_mpint(self.g) hm.add_mpint(self.e) diff --git a/tests/test_kex.py b/tests/test_kex.py index 2680853..3ab95b0 100644 --- a/tests/test_kex.py +++ b/tests/test_kex.py @@ -35,18 +35,21 @@ class FakeRandpool (object): def get_bytes(self, n): return chr(0xcc) * n + class FakeKey (object): def __str__(self): return 'fake-key' def sign_ssh_data(self, randpool, H): return 'fake-sig' + class FakeModulusPack (object): P = 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFFL G = 2 def get_modulus(self, min, ask, max): return self.G, self.P + class FakeTransport (object): randpool = FakeRandpool() local_version = 'SSH-2.0-paramiko_1.0' @@ -56,7 +59,7 @@ class FakeTransport (object): def _send_message(self, m): self._message = m - def _expect_packet(self, t): + def _expect_packet(self, *t): self._expect = t def _set_K_H(self, K, H): self._K = K @@ -90,7 +93,7 @@ class KexTest (unittest.TestCase): kex.start_kex() x = '1E000000807E2DDB1743F3487D6545F04F1C8476092FB912B013626AB5BCEB764257D88BBA64243B9F348DF7B41B8C814A995E00299913503456983FFB9178D3CD79EB6D55522418A8ABF65375872E55938AB99A84A0B5FC8A1ECC66A7C3766E7E0F80B7CE2C9225FC2DD683F4764244B72963BBB383F529DCF0C5D17740B8A2ADBE9208D4' self.assertEquals(x, paramiko.util.hexify(str(transport._message))) - self.assertEquals(paramiko.kex_group1._MSG_KEXDH_REPLY, transport._expect) + self.assertEquals((paramiko.kex_group1._MSG_KEXDH_REPLY,), transport._expect) # fake "reply" msg = Message() @@ -110,7 +113,7 @@ class KexTest (unittest.TestCase): transport.server_mode = True kex = KexGroup1(transport) kex.start_kex() - self.assertEquals(paramiko.kex_group1._MSG_KEXDH_INIT, transport._expect) + self.assertEquals((paramiko.kex_group1._MSG_KEXDH_INIT,), transport._expect) msg = Message() msg.add_mpint(69) @@ -130,7 +133,7 @@ class KexTest (unittest.TestCase): kex.start_kex() x = '22000004000000080000002000' self.assertEquals(x, paramiko.util.hexify(str(transport._message))) - self.assertEquals(paramiko.kex_gex._MSG_KEXDH_GEX_GROUP, transport._expect) + self.assertEquals((paramiko.kex_gex._MSG_KEXDH_GEX_GROUP,), transport._expect) msg = Message() msg.add_mpint(FakeModulusPack.P) @@ -139,7 +142,7 @@ class KexTest (unittest.TestCase): kex.parse_next(paramiko.kex_gex._MSG_KEXDH_GEX_GROUP, msg) x = '20000000807E2DDB1743F3487D6545F04F1C8476092FB912B013626AB5BCEB764257D88BBA64243B9F348DF7B41B8C814A995E00299913503456983FFB9178D3CD79EB6D55522418A8ABF65375872E55938AB99A84A0B5FC8A1ECC66A7C3766E7E0F80B7CE2C9225FC2DD683F4764244B72963BBB383F529DCF0C5D17740B8A2ADBE9208D4' self.assertEquals(x, paramiko.util.hexify(str(transport._message))) - self.assertEquals(paramiko.kex_gex._MSG_KEXDH_GEX_REPLY, transport._expect) + self.assertEquals((paramiko.kex_gex._MSG_KEXDH_GEX_REPLY,), transport._expect) msg = Message() msg.add_string('fake-host-key') @@ -153,12 +156,42 @@ class KexTest (unittest.TestCase): self.assertEquals(('fake-host-key', 'fake-sig'), transport._verify) self.assert_(transport._activated) - def test_4_gex_server(self): + def test_4_gex_old_client(self): + transport = FakeTransport() + transport.server_mode = False + kex = KexGex(transport) + kex.start_kex(_test_old_style=True) + x = '1E00000800' + self.assertEquals(x, paramiko.util.hexify(str(transport._message))) + self.assertEquals((paramiko.kex_gex._MSG_KEXDH_GEX_GROUP,), transport._expect) + + msg = Message() + msg.add_mpint(FakeModulusPack.P) + msg.add_mpint(FakeModulusPack.G) + msg.rewind() + kex.parse_next(paramiko.kex_gex._MSG_KEXDH_GEX_GROUP, msg) + x = '20000000807E2DDB1743F3487D6545F04F1C8476092FB912B013626AB5BCEB764257D88BBA64243B9F348DF7B41B8C814A995E00299913503456983FFB9178D3CD79EB6D55522418A8ABF65375872E55938AB99A84A0B5FC8A1ECC66A7C3766E7E0F80B7CE2C9225FC2DD683F4764244B72963BBB383F529DCF0C5D17740B8A2ADBE9208D4' + self.assertEquals(x, paramiko.util.hexify(str(transport._message))) + self.assertEquals((paramiko.kex_gex._MSG_KEXDH_GEX_REPLY,), transport._expect) + + msg = Message() + msg.add_string('fake-host-key') + msg.add_mpint(69) + msg.add_string('fake-sig') + msg.rewind() + kex.parse_next(paramiko.kex_gex._MSG_KEXDH_GEX_REPLY, msg) + H = 'A265563F2FA87F1A89BF007EE90D58BE2E4A4BD0' + self.assertEquals(self.K, transport._K) + self.assertEquals(H, paramiko.util.hexify(transport._H)) + self.assertEquals(('fake-host-key', 'fake-sig'), transport._verify) + self.assert_(transport._activated) + + def test_5_gex_server(self): transport = FakeTransport() transport.server_mode = True kex = KexGex(transport) kex.start_kex() - self.assertEquals(paramiko.kex_gex._MSG_KEXDH_GEX_REQUEST, transport._expect) + self.assertEquals((paramiko.kex_gex._MSG_KEXDH_GEX_REQUEST, paramiko.kex_gex._MSG_KEXDH_GEX_REQUEST_OLD), transport._expect) msg = Message() msg.add_int(1024) @@ -168,7 +201,7 @@ class KexTest (unittest.TestCase): kex.parse_next(paramiko.kex_gex._MSG_KEXDH_GEX_REQUEST, msg) x = '1F0000008100FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF0000000102' self.assertEquals(x, paramiko.util.hexify(str(transport._message))) - self.assertEquals(paramiko.kex_gex._MSG_KEXDH_GEX_INIT, transport._expect) + self.assertEquals((paramiko.kex_gex._MSG_KEXDH_GEX_INIT,), transport._expect) msg = Message() msg.add_mpint(12345) @@ -181,3 +214,30 @@ class KexTest (unittest.TestCase): self.assertEquals(H, paramiko.util.hexify(transport._H)) self.assertEquals(x, paramiko.util.hexify(str(transport._message))) self.assert_(transport._activated) + + def test_6_gex_server_with_old_client(self): + transport = FakeTransport() + transport.server_mode = True + kex = KexGex(transport) + kex.start_kex() + self.assertEquals((paramiko.kex_gex._MSG_KEXDH_GEX_REQUEST, paramiko.kex_gex._MSG_KEXDH_GEX_REQUEST_OLD), transport._expect) + + msg = Message() + msg.add_int(2048) + msg.rewind() + kex.parse_next(paramiko.kex_gex._MSG_KEXDH_GEX_REQUEST_OLD, msg) + x = '1F0000008100FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF0000000102' + self.assertEquals(x, paramiko.util.hexify(str(transport._message))) + self.assertEquals((paramiko.kex_gex._MSG_KEXDH_GEX_INIT,), transport._expect) + + msg = Message() + msg.add_mpint(12345) + msg.rewind() + kex.parse_next(paramiko.kex_gex._MSG_KEXDH_GEX_INIT, msg) + K = 67592995013596137876033460028393339951879041140378510871612128162185209509220726296697886624612526735888348020498716482757677848959420073720160491114319163078862905400020959196386947926388406687288901564192071077389283980347784184487280885335302632305026248574716290537036069329724382811853044654824945750581L + H = 'B41A06B2E59043CEFC1AE16EC31F1E2D12EC455B' + x = '210000000866616B652D6B6579000000807E2DDB1743F3487D6545F04F1C8476092FB912B013626AB5BCEB764257D88BBA64243B9F348DF7B41B8C814A995E00299913503456983FFB9178D3CD79EB6D55522418A8ABF65375872E55938AB99A84A0B5FC8A1ECC66A7C3766E7E0F80B7CE2C9225FC2DD683F4764244B72963BBB383F529DCF0C5D17740B8A2ADBE9208D40000000866616B652D736967' + self.assertEquals(K, transport._K) + self.assertEquals(H, paramiko.util.hexify(transport._H)) + self.assertEquals(x, paramiko.util.hexify(str(transport._message))) + self.assert_(transport._activated)