kex_group1.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. # Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com>
  2. #
  3. # This file is part of paramiko.
  4. #
  5. # Paramiko is free software; you can redistribute it and/or modify it under the
  6. # terms of the GNU Lesser General Public License as published by the Free
  7. # Software Foundation; either version 2.1 of the License, or (at your option)
  8. # any later version.
  9. #
  10. # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY
  11. # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
  13. # details.
  14. #
  15. # You should have received a copy of the GNU Lesser General Public License
  16. # along with Paramiko; if not, write to the Free Software Foundation, Inc.,
  17. # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
  18. """
  19. Standard SSH key exchange ("kex" if you wanna sound cool). Diffie-Hellman of
  20. 1024 bit key halves, using a known "p" prime and "g" generator.
  21. """
  22. import os
  23. from hashlib import sha1
  24. from paramiko import util
  25. from paramiko.common import max_byte, zero_byte
  26. from paramiko.message import Message
  27. from paramiko.py3compat import byte_chr, long, byte_mask
  28. from paramiko.ssh_exception import SSHException
  29. _MSG_KEXDH_INIT, _MSG_KEXDH_REPLY = range(30, 32)
  30. c_MSG_KEXDH_INIT, c_MSG_KEXDH_REPLY = [byte_chr(c) for c in range(30, 32)]
  31. b7fffffffffffffff = byte_chr(0x7f) + max_byte * 7
  32. b0000000000000000 = zero_byte * 8
  33. class KexGroup1(object):
  34. # draft-ietf-secsh-transport-09.txt, page 17
  35. P = 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF # noqa
  36. G = 2
  37. name = 'diffie-hellman-group1-sha1'
  38. hash_algo = sha1
  39. def __init__(self, transport):
  40. self.transport = transport
  41. self.x = long(0)
  42. self.e = long(0)
  43. self.f = long(0)
  44. def start_kex(self):
  45. self._generate_x()
  46. if self.transport.server_mode:
  47. # compute f = g^x mod p, but don't send it yet
  48. self.f = pow(self.G, self.x, self.P)
  49. self.transport._expect_packet(_MSG_KEXDH_INIT)
  50. return
  51. # compute e = g^x mod p (where g=2), and send it
  52. self.e = pow(self.G, self.x, self.P)
  53. m = Message()
  54. m.add_byte(c_MSG_KEXDH_INIT)
  55. m.add_mpint(self.e)
  56. self.transport._send_message(m)
  57. self.transport._expect_packet(_MSG_KEXDH_REPLY)
  58. def parse_next(self, ptype, m):
  59. if self.transport.server_mode and (ptype == _MSG_KEXDH_INIT):
  60. return self._parse_kexdh_init(m)
  61. elif not self.transport.server_mode and (ptype == _MSG_KEXDH_REPLY):
  62. return self._parse_kexdh_reply(m)
  63. raise SSHException('KexGroup1 asked to handle packet type %d' % ptype)
  64. # ...internals...
  65. def _generate_x(self):
  66. # generate an "x" (1 < x < q), where q is (p-1)/2.
  67. # p is a 128-byte (1024-bit) number, where the first 64 bits are 1.
  68. # therefore q can be approximated as a 2^1023. we drop the subset of
  69. # potential x where the first 63 bits are 1, because some of those
  70. # will be larger than q (but this is a tiny tiny subset of
  71. # potential x).
  72. while 1:
  73. x_bytes = os.urandom(128)
  74. x_bytes = byte_mask(x_bytes[0], 0x7f) + x_bytes[1:]
  75. if (x_bytes[:8] != b7fffffffffffffff and
  76. x_bytes[:8] != b0000000000000000):
  77. break
  78. self.x = util.inflate_long(x_bytes)
  79. def _parse_kexdh_reply(self, m):
  80. # client mode
  81. host_key = m.get_string()
  82. self.f = m.get_mpint()
  83. if (self.f < 1) or (self.f > self.P - 1):
  84. raise SSHException('Server kex "f" is out of range')
  85. sig = m.get_binary()
  86. K = pow(self.f, self.x, self.P)
  87. # okay, build up the hash H of
  88. # (V_C || V_S || I_C || I_S || K_S || e || f || K)
  89. hm = Message()
  90. hm.add(self.transport.local_version, self.transport.remote_version,
  91. self.transport.local_kex_init, self.transport.remote_kex_init)
  92. hm.add_string(host_key)
  93. hm.add_mpint(self.e)
  94. hm.add_mpint(self.f)
  95. hm.add_mpint(K)
  96. self.transport._set_K_H(K, sha1(hm.asbytes()).digest())
  97. self.transport._verify_key(host_key, sig)
  98. self.transport._activate_outbound()
  99. def _parse_kexdh_init(self, m):
  100. # server mode
  101. self.e = m.get_mpint()
  102. if (self.e < 1) or (self.e > self.P - 1):
  103. raise SSHException('Client kex "e" is out of range')
  104. K = pow(self.e, self.x, self.P)
  105. key = self.transport.get_server_key().asbytes()
  106. # okay, build up the hash H of
  107. # (V_C || V_S || I_C || I_S || K_S || e || f || K)
  108. hm = Message()
  109. hm.add(self.transport.remote_version, self.transport.local_version,
  110. self.transport.remote_kex_init, self.transport.local_kex_init)
  111. hm.add_string(key)
  112. hm.add_mpint(self.e)
  113. hm.add_mpint(self.f)
  114. hm.add_mpint(K)
  115. H = sha1(hm.asbytes()).digest()
  116. self.transport._set_K_H(K, H)
  117. # sign it
  118. sig = self.transport.get_server_key().sign_ssh_data(H)
  119. # send reply
  120. m = Message()
  121. m.add_byte(c_MSG_KEXDH_REPLY)
  122. m.add_string(key)
  123. m.add_mpint(self.f)
  124. m.add_string(sig)
  125. self.transport._send_message(m)
  126. self.transport._activate_outbound()