kex_group1.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  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. 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. msg = "KexGroup1 asked to handle packet type {:d}"
  64. raise SSHException(msg.format(ptype))
  65. # ...internals...
  66. def _generate_x(self):
  67. # generate an "x" (1 < x < q), where q is (p-1)/2.
  68. # p is a 128-byte (1024-bit) number, where the first 64 bits are 1.
  69. # therefore q can be approximated as a 2^1023. we drop the subset of
  70. # potential x where the first 63 bits are 1, because some of those
  71. # will be larger than q (but this is a tiny tiny subset of
  72. # potential x).
  73. while 1:
  74. x_bytes = os.urandom(128)
  75. x_bytes = byte_mask(x_bytes[0], 0x7f) + x_bytes[1:]
  76. if (
  77. x_bytes[:8] != b7fffffffffffffff
  78. and x_bytes[:8] != b0000000000000000
  79. ):
  80. break
  81. self.x = util.inflate_long(x_bytes)
  82. def _parse_kexdh_reply(self, m):
  83. # client mode
  84. host_key = m.get_string()
  85. self.f = m.get_mpint()
  86. if (self.f < 1) or (self.f > self.P - 1):
  87. raise SSHException('Server kex "f" is out of range')
  88. sig = m.get_binary()
  89. K = pow(self.f, self.x, self.P)
  90. # okay, build up the hash H of
  91. # (V_C || V_S || I_C || I_S || K_S || e || f || K)
  92. hm = Message()
  93. hm.add(
  94. self.transport.local_version,
  95. self.transport.remote_version,
  96. self.transport.local_kex_init,
  97. self.transport.remote_kex_init,
  98. )
  99. hm.add_string(host_key)
  100. hm.add_mpint(self.e)
  101. hm.add_mpint(self.f)
  102. hm.add_mpint(K)
  103. self.transport._set_K_H(K, self.hash_algo(hm.asbytes()).digest())
  104. self.transport._verify_key(host_key, sig)
  105. self.transport._activate_outbound()
  106. def _parse_kexdh_init(self, m):
  107. # server mode
  108. self.e = m.get_mpint()
  109. if (self.e < 1) or (self.e > self.P - 1):
  110. raise SSHException('Client kex "e" is out of range')
  111. K = pow(self.e, self.x, self.P)
  112. key = self.transport.get_server_key().asbytes()
  113. # okay, build up the hash H of
  114. # (V_C || V_S || I_C || I_S || K_S || e || f || K)
  115. hm = Message()
  116. hm.add(
  117. self.transport.remote_version,
  118. self.transport.local_version,
  119. self.transport.remote_kex_init,
  120. self.transport.local_kex_init,
  121. )
  122. hm.add_string(key)
  123. hm.add_mpint(self.e)
  124. hm.add_mpint(self.f)
  125. hm.add_mpint(K)
  126. H = self.hash_algo(hm.asbytes()).digest()
  127. self.transport._set_K_H(K, H)
  128. # sign it
  129. sig = self.transport.get_server_key().sign_ssh_data(
  130. H, self.transport.host_key_type
  131. )
  132. # send reply
  133. m = Message()
  134. m.add_byte(c_MSG_KEXDH_REPLY)
  135. m.add_string(key)
  136. m.add_mpint(self.f)
  137. m.add_string(sig)
  138. self.transport._send_message(m)
  139. self.transport._activate_outbound()