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              self.transport.randpool.stir()
    105              x_bytes = self.transport.randpool.get_bytes(bytes)
    106              x_bytes = chr(ord(x_bytes[0]) & qmask) + x_bytes[1:]
    107              x = util.inflate_long(x_bytes, 1)
    108              if (x > 1) and (x < q):
    109                  break
    110          self.x = x
    111
    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(DEBUG, 'Picking p (%d <= %d <= %d bits)' % (minbits, preferredbits, maxbits))
    137          self.g, self.p = pack.get_modulus(minbits, preferredbits, maxbits)
    138          m = Message()
    139          m.add_byte(chr(_MSG_KEXDH_GEX_GROUP))
    140          m.add_mpint(self.p)
    141          m.add_mpint(self.g)
    142          self.transport._send_message(m)
    143          self.transport._expect_packet(_MSG_KEXDH_GEX_INIT)
    144
    145 -    def _parse_kexdh_gex_request_old(self, m):
    146          # same as above, but without min_bits or max_bits (used by older clients like putty)
    147          self.preferred_bits = m.get_int()
    148          # smoosh the user's preferred size into our own limits
    149          if self.preferred_bits > self.max_bits:
    150              self.preferred_bits = self.max_bits
    151          if self.preferred_bits < self.min_bits:
    152              self.preferred_bits = self.min_bits
    153          # generate prime
    154          pack = self.transport._get_modulus_pack()
    155          if pack is None:
    156              raise SSHException('Can\'t do server-side gex with no modulus pack')
    157          self.transport._log(DEBUG, 'Picking p (~ %d bits)' % (self.preferred_bits,))
    158          self.g, self.p = pack.get_modulus(self.min_bits, self.preferred_bits, self.max_bits)
    159          m = Message()
    160          m.add_byte(chr(_MSG_KEXDH_GEX_GROUP))
    161          m.add_mpint(self.p)
    162          m.add_mpint(self.g)
    163          self.transport._send_message(m)
    164          self.transport._expect_packet(_MSG_KEXDH_GEX_INIT)
    165          self.old_style = True
    166
    167 -    def _parse_kexdh_gex_group(self, m):
    168          self.p = m.get_mpint()
    169          self.g = m.get_mpint()
    170          # reject if p's bit length < 1024 or > 8192
    171          bitlen = util.bit_length(self.p)
    172          if (bitlen < 1024) or (bitlen > 8192):
    173              raise SSHException('Server-generated gex p (don\'t ask) is out of range (%d bits)' % bitlen)
    174          self.transport._log(DEBUG, 'Got server p (%d bits)' % bitlen)
    175          self._generate_x()
    176          # now compute e = g^x mod p
    177          self.e = pow(self.g, self.x, self.p)
    178          m = Message()
    179          m.add_byte(chr(_MSG_KEXDH_GEX_INIT))
    180          m.add_mpint(self.e)
    181          self.transport._send_message(m)
    182          self.transport._expect_packet(_MSG_KEXDH_GEX_REPLY)
    183
    184 -    def _parse_kexdh_gex_init(self, m):
    185          self.e = m.get_mpint()
    186          if (self.e < 1) or (self.e > self.p - 1):
    187              raise SSHException('Client kex "e" is out of range')
    188          self._generate_x()
    189          self.f = pow(self.g, self.x, self.p)
    190          K = pow(self.e, self.x, self.p)
    191          key = str(self.transport.get_server_key())
    192          # 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)
    193          hm = Message()
    194          hm.add(self.transport.remote_version, self.transport.local_version,
    195                 self.transport.remote_kex_init, self.transport.local_kex_init,
    196                 key)
    197          if not self.old_style:
    198              hm.add_int(self.min_bits)
    199          hm.add_int(self.preferred_bits)
    200          if not self.old_style:
    201              hm.add_int(self.max_bits)
    202          hm.add_mpint(self.p)
    203          hm.add_mpint(self.g)
    204          hm.add_mpint(self.e)
    205          hm.add_mpint(self.f)
    206          hm.add_mpint(K)
    207          H = SHA.new(str(hm)).digest()
    208          self.transport._set_K_H(K, H)
    209          # sign it
    210          sig = self.transport.get_server_key().sign_ssh_data(self.transport.randpool, H)
    211          # send reply
    212          m = Message()
    213          m.add_byte(chr(_MSG_KEXDH_GEX_REPLY))
    214          m.add_string(key)
    215          m.add_mpint(self.f)
    216          m.add_string(str(sig))
    217          self.transport._send_message(m)
    218          self.transport._activate_outbound()
    219
    220 -    def _parse_kexdh_gex_reply(self, m):
    221          host_key = m.get_string()
    222          self.f = m.get_mpint()
    223          sig = m.get_string()
    224          if (self.f < 1) or (self.f > self.p - 1):
    225              raise SSHException('Server kex "f" is out of range')
    226          K = pow(self.f, self.x, self.p)
    227          # 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)
    228          hm = Message()
    229          hm.add(self.transport.local_version, self.transport.remote_version,
    230                 self.transport.local_kex_init, self.transport.remote_kex_init,
    231                 host_key)
    232          if not self.old_style:
    233              hm.add_int(self.min_bits)
    234          hm.add_int(self.preferred_bits)
    235          if not self.old_style:
    236              hm.add_int(self.max_bits)
    237          hm.add_mpint(self.p)
    238          hm.add_mpint(self.g)
    239          hm.add_mpint(self.e)
    240          hm.add_mpint(self.f)
    241          hm.add_mpint(K)
    242          self.transport._set_K_H(K, SHA.new(str(hm)).digest())
    243          self.transport._verify_key(host_key, sig)
    244          self.transport._activate_outbound()
    245