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 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  # 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA. 
 18   
 19  """ 
 20  Variant on `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  import os 
 26  from hashlib import sha1 
 27   
 28  from paramiko import util 
 29  from paramiko.common import DEBUG 
 30  from paramiko.message import Message 
 31  from paramiko.py3compat import byte_chr, byte_ord, byte_mask 
 32  from paramiko.ssh_exception import SSHException 
 33   
 34   
 35  _MSG_KEXDH_GEX_REQUEST_OLD, _MSG_KEXDH_GEX_GROUP, _MSG_KEXDH_GEX_INIT, \ 
 36      _MSG_KEXDH_GEX_REPLY, _MSG_KEXDH_GEX_REQUEST = range(30, 35) 
 37  c_MSG_KEXDH_GEX_REQUEST_OLD, c_MSG_KEXDH_GEX_GROUP, c_MSG_KEXDH_GEX_INIT, \ 
 38      c_MSG_KEXDH_GEX_REPLY, c_MSG_KEXDH_GEX_REQUEST = [byte_chr(c) for c in range(30, 35)] 
 39   
 40   
41 -class KexGex (object):
42 43 name = 'diffie-hellman-group-exchange-sha1' 44 min_bits = 1024 45 max_bits = 8192 46 preferred_bits = 2048 47
48 - def __init__(self, transport):
49 self.transport = transport 50 self.p = None 51 self.q = None 52 self.g = None 53 self.x = None 54 self.e = None 55 self.f = None 56 self.old_style = False
57
58 - def start_kex(self, _test_old_style=False):
59 if self.transport.server_mode: 60 self.transport._expect_packet(_MSG_KEXDH_GEX_REQUEST, _MSG_KEXDH_GEX_REQUEST_OLD) 61 return 62 # request a bit range: we accept (min_bits) to (max_bits), but prefer 63 # (preferred_bits). according to the spec, we shouldn't pull the 64 # minimum up above 1024. 65 m = Message() 66 if _test_old_style: 67 # only used for unit tests: we shouldn't ever send this 68 m.add_byte(c_MSG_KEXDH_GEX_REQUEST_OLD) 69 m.add_int(self.preferred_bits) 70 self.old_style = True 71 else: 72 m.add_byte(c_MSG_KEXDH_GEX_REQUEST) 73 m.add_int(self.min_bits) 74 m.add_int(self.preferred_bits) 75 m.add_int(self.max_bits) 76 self.transport._send_message(m) 77 self.transport._expect_packet(_MSG_KEXDH_GEX_GROUP)
78
79 - def parse_next(self, ptype, m):
80 if ptype == _MSG_KEXDH_GEX_REQUEST: 81 return self._parse_kexdh_gex_request(m) 82 elif ptype == _MSG_KEXDH_GEX_GROUP: 83 return self._parse_kexdh_gex_group(m) 84 elif ptype == _MSG_KEXDH_GEX_INIT: 85 return self._parse_kexdh_gex_init(m) 86 elif ptype == _MSG_KEXDH_GEX_REPLY: 87 return self._parse_kexdh_gex_reply(m) 88 elif ptype == _MSG_KEXDH_GEX_REQUEST_OLD: 89 return self._parse_kexdh_gex_request_old(m) 90 raise SSHException('KexGex asked to handle packet type %d' % ptype)
91 92 ### internals... 93
94 - def _generate_x(self):
95 # generate an "x" (1 < x < (p-1)/2). 96 q = (self.p - 1) // 2 97 qnorm = util.deflate_long(q, 0) 98 qhbyte = byte_ord(qnorm[0]) 99 byte_count = len(qnorm) 100 qmask = 0xff 101 while not (qhbyte & 0x80): 102 qhbyte <<= 1 103 qmask >>= 1 104 while True: 105 x_bytes = os.urandom(byte_count) 106 x_bytes = byte_mask(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(c_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(c_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(c_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 = self.transport.get_server_key().asbytes() 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 = sha1(hm.asbytes()).digest() 208 self.transport._set_K_H(K, H) 209 # sign it 210 sig = self.transport.get_server_key().sign_ssh_data(H) 211 # send reply 212 m = Message() 213 m.add_byte(c_MSG_KEXDH_GEX_REPLY) 214 m.add_string(key) 215 m.add_mpint(self.f) 216 m.add_string(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, sha1(hm.asbytes()).digest()) 243 self.transport._verify_key(host_key, sig) 244 self.transport._activate_outbound()
245