paramiko.kex_gex

Package paramiko :: Module kex_gex |
[frames] | no frames]

# Source Code for Module paramiko.kex_gex

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