kex_gex.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  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. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  18. """
  19. Variant on `KexGroup1 <paramiko.kex_group1.KexGroup1>` where the prime "p" and
  20. generator "g" are provided by the server. A bit more work is required on the
  21. client side, and a **lot** more on the server side.
  22. """
  23. import os
  24. from hashlib import sha1, sha256
  25. from paramiko import util
  26. from paramiko.common import DEBUG
  27. from paramiko.message import Message
  28. from paramiko.py3compat import byte_chr, byte_ord, byte_mask
  29. from paramiko.ssh_exception import SSHException
  30. (
  31. _MSG_KEXDH_GEX_REQUEST_OLD,
  32. _MSG_KEXDH_GEX_GROUP,
  33. _MSG_KEXDH_GEX_INIT,
  34. _MSG_KEXDH_GEX_REPLY,
  35. _MSG_KEXDH_GEX_REQUEST,
  36. ) = range(30, 35)
  37. (
  38. c_MSG_KEXDH_GEX_REQUEST_OLD,
  39. c_MSG_KEXDH_GEX_GROUP,
  40. c_MSG_KEXDH_GEX_INIT,
  41. c_MSG_KEXDH_GEX_REPLY,
  42. c_MSG_KEXDH_GEX_REQUEST,
  43. ) = [byte_chr(c) for c in range(30, 35)]
  44. class KexGex(object):
  45. name = "diffie-hellman-group-exchange-sha1"
  46. min_bits = 1024
  47. max_bits = 8192
  48. preferred_bits = 2048
  49. hash_algo = sha1
  50. def __init__(self, transport):
  51. self.transport = transport
  52. self.p = None
  53. self.q = None
  54. self.g = None
  55. self.x = None
  56. self.e = None
  57. self.f = None
  58. self.old_style = False
  59. def start_kex(self, _test_old_style=False):
  60. if self.transport.server_mode:
  61. self.transport._expect_packet(
  62. _MSG_KEXDH_GEX_REQUEST, _MSG_KEXDH_GEX_REQUEST_OLD
  63. )
  64. return
  65. # request a bit range: we accept (min_bits) to (max_bits), but prefer
  66. # (preferred_bits). according to the spec, we shouldn't pull the
  67. # minimum up above 1024.
  68. m = Message()
  69. if _test_old_style:
  70. # only used for unit tests: we shouldn't ever send this
  71. m.add_byte(c_MSG_KEXDH_GEX_REQUEST_OLD)
  72. m.add_int(self.preferred_bits)
  73. self.old_style = True
  74. else:
  75. m.add_byte(c_MSG_KEXDH_GEX_REQUEST)
  76. m.add_int(self.min_bits)
  77. m.add_int(self.preferred_bits)
  78. m.add_int(self.max_bits)
  79. self.transport._send_message(m)
  80. self.transport._expect_packet(_MSG_KEXDH_GEX_GROUP)
  81. def parse_next(self, ptype, m):
  82. if ptype == _MSG_KEXDH_GEX_REQUEST:
  83. return self._parse_kexdh_gex_request(m)
  84. elif ptype == _MSG_KEXDH_GEX_GROUP:
  85. return self._parse_kexdh_gex_group(m)
  86. elif ptype == _MSG_KEXDH_GEX_INIT:
  87. return self._parse_kexdh_gex_init(m)
  88. elif ptype == _MSG_KEXDH_GEX_REPLY:
  89. return self._parse_kexdh_gex_reply(m)
  90. elif ptype == _MSG_KEXDH_GEX_REQUEST_OLD:
  91. return self._parse_kexdh_gex_request_old(m)
  92. msg = "KexGex {} asked to handle packet type {:d}"
  93. raise SSHException(msg.format(self.name, ptype))
  94. # ...internals...
  95. def _generate_x(self):
  96. # generate an "x" (1 < x < (p-1)/2).
  97. q = (self.p - 1) // 2
  98. qnorm = util.deflate_long(q, 0)
  99. qhbyte = byte_ord(qnorm[0])
  100. byte_count = len(qnorm)
  101. qmask = 0xff
  102. while not (qhbyte & 0x80):
  103. qhbyte <<= 1
  104. qmask >>= 1
  105. while True:
  106. x_bytes = os.urandom(byte_count)
  107. x_bytes = byte_mask(x_bytes[0], qmask) + x_bytes[1:]
  108. x = util.inflate_long(x_bytes, 1)
  109. if (x > 1) and (x < q):
  110. break
  111. self.x = x
  112. def _parse_kexdh_gex_request(self, m):
  113. minbits = m.get_int()
  114. preferredbits = m.get_int()
  115. maxbits = m.get_int()
  116. # smoosh the user's preferred size into our own limits
  117. if preferredbits > self.max_bits:
  118. preferredbits = self.max_bits
  119. if preferredbits < self.min_bits:
  120. preferredbits = self.min_bits
  121. # fix min/max if they're inconsistent. technically, we could just pout
  122. # and hang up, but there's no harm in giving them the benefit of the
  123. # doubt and just picking a bitsize for them.
  124. if minbits > preferredbits:
  125. minbits = preferredbits
  126. if maxbits < preferredbits:
  127. maxbits = preferredbits
  128. # now save a copy
  129. self.min_bits = minbits
  130. self.preferred_bits = preferredbits
  131. self.max_bits = maxbits
  132. # generate prime
  133. pack = self.transport._get_modulus_pack()
  134. if pack is None:
  135. raise SSHException("Can't do server-side gex with no modulus pack")
  136. self.transport._log(
  137. DEBUG,
  138. "Picking p ({} <= {} <= {} bits)".format(
  139. minbits, preferredbits, maxbits
  140. ),
  141. )
  142. self.g, self.p = pack.get_modulus(minbits, preferredbits, maxbits)
  143. m = Message()
  144. m.add_byte(c_MSG_KEXDH_GEX_GROUP)
  145. m.add_mpint(self.p)
  146. m.add_mpint(self.g)
  147. self.transport._send_message(m)
  148. self.transport._expect_packet(_MSG_KEXDH_GEX_INIT)
  149. def _parse_kexdh_gex_request_old(self, m):
  150. # same as above, but without min_bits or max_bits (used by older
  151. # clients like putty)
  152. self.preferred_bits = m.get_int()
  153. # smoosh the user's preferred size into our own limits
  154. if self.preferred_bits > self.max_bits:
  155. self.preferred_bits = self.max_bits
  156. if self.preferred_bits < self.min_bits:
  157. self.preferred_bits = self.min_bits
  158. # generate prime
  159. pack = self.transport._get_modulus_pack()
  160. if pack is None:
  161. raise SSHException("Can't do server-side gex with no modulus pack")
  162. self.transport._log(
  163. DEBUG, "Picking p (~ {} bits)".format(self.preferred_bits)
  164. )
  165. self.g, self.p = pack.get_modulus(
  166. self.min_bits, self.preferred_bits, self.max_bits
  167. )
  168. m = Message()
  169. m.add_byte(c_MSG_KEXDH_GEX_GROUP)
  170. m.add_mpint(self.p)
  171. m.add_mpint(self.g)
  172. self.transport._send_message(m)
  173. self.transport._expect_packet(_MSG_KEXDH_GEX_INIT)
  174. self.old_style = True
  175. def _parse_kexdh_gex_group(self, m):
  176. self.p = m.get_mpint()
  177. self.g = m.get_mpint()
  178. # reject if p's bit length < 1024 or > 8192
  179. bitlen = util.bit_length(self.p)
  180. if (bitlen < 1024) or (bitlen > 8192):
  181. raise SSHException(
  182. "Server-generated gex p (don't ask) is out of range "
  183. "({} bits)".format(bitlen)
  184. )
  185. self.transport._log(DEBUG, "Got server p ({} bits)".format(bitlen))
  186. self._generate_x()
  187. # now compute e = g^x mod p
  188. self.e = pow(self.g, self.x, self.p)
  189. m = Message()
  190. m.add_byte(c_MSG_KEXDH_GEX_INIT)
  191. m.add_mpint(self.e)
  192. self.transport._send_message(m)
  193. self.transport._expect_packet(_MSG_KEXDH_GEX_REPLY)
  194. def _parse_kexdh_gex_init(self, m):
  195. self.e = m.get_mpint()
  196. if (self.e < 1) or (self.e > self.p - 1):
  197. raise SSHException('Client kex "e" is out of range')
  198. self._generate_x()
  199. self.f = pow(self.g, self.x, self.p)
  200. K = pow(self.e, self.x, self.p)
  201. key = self.transport.get_server_key().asbytes()
  202. # okay, build up the hash H of
  203. # (V_C || V_S || I_C || I_S || K_S || min || n || max || p || g || e || f || K) # noqa
  204. hm = Message()
  205. hm.add(
  206. self.transport.remote_version,
  207. self.transport.local_version,
  208. self.transport.remote_kex_init,
  209. self.transport.local_kex_init,
  210. key,
  211. )
  212. if not self.old_style:
  213. hm.add_int(self.min_bits)
  214. hm.add_int(self.preferred_bits)
  215. if not self.old_style:
  216. hm.add_int(self.max_bits)
  217. hm.add_mpint(self.p)
  218. hm.add_mpint(self.g)
  219. hm.add_mpint(self.e)
  220. hm.add_mpint(self.f)
  221. hm.add_mpint(K)
  222. H = self.hash_algo(hm.asbytes()).digest()
  223. self.transport._set_K_H(K, H)
  224. # sign it
  225. sig = self.transport.get_server_key().sign_ssh_data(
  226. H, self.transport.host_key_type
  227. )
  228. # send reply
  229. m = Message()
  230. m.add_byte(c_MSG_KEXDH_GEX_REPLY)
  231. m.add_string(key)
  232. m.add_mpint(self.f)
  233. m.add_string(sig)
  234. self.transport._send_message(m)
  235. self.transport._activate_outbound()
  236. def _parse_kexdh_gex_reply(self, m):
  237. host_key = m.get_string()
  238. self.f = m.get_mpint()
  239. sig = m.get_string()
  240. if (self.f < 1) or (self.f > self.p - 1):
  241. raise SSHException('Server kex "f" is out of range')
  242. K = pow(self.f, self.x, self.p)
  243. # okay, build up the hash H of
  244. # (V_C || V_S || I_C || I_S || K_S || min || n || max || p || g || e || f || K) # noqa
  245. hm = Message()
  246. hm.add(
  247. self.transport.local_version,
  248. self.transport.remote_version,
  249. self.transport.local_kex_init,
  250. self.transport.remote_kex_init,
  251. host_key,
  252. )
  253. if not self.old_style:
  254. hm.add_int(self.min_bits)
  255. hm.add_int(self.preferred_bits)
  256. if not self.old_style:
  257. hm.add_int(self.max_bits)
  258. hm.add_mpint(self.p)
  259. hm.add_mpint(self.g)
  260. hm.add_mpint(self.e)
  261. hm.add_mpint(self.f)
  262. hm.add_mpint(K)
  263. self.transport._set_K_H(K, self.hash_algo(hm.asbytes()).digest())
  264. self.transport._verify_key(host_key, sig)
  265. self.transport._activate_outbound()
  266. class KexGexSHA256(KexGex):
  267. name = "diffie-hellman-group-exchange-sha256"
  268. hash_algo = sha256