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

Source Code for Module paramiko.kex_gex

  1  # Copyright (C) 2003-2007  Robey Pointer <robey@lag.net> 
  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