def __init__(self, msg=None, data=None, filename=None, password=None, vals=None, file_obj=None): self.p = None self.q = None self.g = None self.y = None self.x = None if file_obj is not None: self._from_private_key(file_obj, password) return if filename is not None: self._from_private_key_file(filename, password) return if (msg is None) and (data is not None): msg = Message(data) if vals is not None: self.p, self.q, self.g, self.y = vals else: if msg is None: raise SSHException('Key object may not be empty') if msg.get_string() != 'ssh-dss': raise SSHException('Invalid key') self.p = msg.get_mpint() self.q = msg.get_mpint() self.g = msg.get_mpint() self.y = msg.get_mpint() self.size = util.bit_length(self.p)
def _disconnect_no_more_auth(self): m = Message() m.add_byte(chr(MSG_DISCONNECT)) m.add_int(DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE) m.add_string('No more auth methods available') m.add_string('en') self.transport._send_message(m) self.transport.close()
def _disconnect_service_not_available(self): m = Message() m.add_byte(chr(MSG_DISCONNECT)) m.add_int(DISCONNECT_SERVICE_NOT_AVAILABLE) m.add_string('Service not available') m.add_string('en') self.transport._send_message(m) self.transport.close()
def sign_ssh_data(self, randpool, data): msg = Message() msg.add_byte(chr(SSH2_AGENTC_SIGN_REQUEST)) msg.add_string(self.blob) msg.add_string(data) msg.add_int(0) ptype, result = self.agent._send_message(msg) if ptype != SSH2_AGENT_SIGN_RESPONSE: raise SSHException('key cannot be used for signing') return result.get_string()
def __str__(self): m = Message() m.add_string('ssh-dss') m.add_mpint(self.p) m.add_mpint(self.q) m.add_mpint(self.g) m.add_mpint(self.y) return str(m)
def _parse_kexdh_reply(self, m): # client mode host_key = m.get_string() self.f = m.get_mpint() if (self.f < 1) or (self.f > P - 1): raise SSHException('Server kex "f" is out of range') sig = m.get_string() K = pow(self.f, self.x, P) # okay, build up the hash H of (V_C || V_S || I_C || I_S || K_S || e || f || K) hm = Message() hm.add(self.transport.local_version, self.transport.remote_version, self.transport.local_kex_init, self.transport.remote_kex_init) hm.add_string(host_key) hm.add_mpint(self.e) hm.add_mpint(self.f) hm.add_mpint(K) self.transport._set_K_H(K, SHA.new(str(hm)).digest()) self.transport._verify_key(host_key, sig) self.transport._activate_outbound()
def _parse_kexdh_gex_request(self, m): minbits = m.get_int() preferredbits = m.get_int() maxbits = m.get_int() # smoosh the user's preferred size into our own limits if preferredbits > self.max_bits: preferredbits = self.max_bits if preferredbits < self.min_bits: preferredbits = self.min_bits # fix min/max if they're inconsistent. technically, we could just pout # and hang up, but there's no harm in giving them the benefit of the # doubt and just picking a bitsize for them. if minbits > preferredbits: minbits = preferredbits if maxbits < preferredbits: maxbits = preferredbits # now save a copy self.min_bits = minbits self.preferred_bits = preferredbits self.max_bits = maxbits # generate prime pack = self.transport._get_modulus_pack() if pack is None: raise SSHException( 'Can\'t do server-side gex with no modulus pack') self.transport._log( DEBUG, 'Picking p (%d <= %d <= %d bits)' % (minbits, preferredbits, maxbits)) self.g, self.p = pack.get_modulus(minbits, preferredbits, maxbits) m = Message() m.add_byte(chr(_MSG_KEXDH_GEX_GROUP)) m.add_mpint(self.p) m.add_mpint(self.g) self.transport._send_message(m) self.transport._expect_packet(_MSG_KEXDH_GEX_INIT)
def sign_ssh_data(self, rpool, data): digest = SHA.new(data).digest() rsa = RSA.construct((long(self.n), long(self.e), long(self.d))) sig = util.deflate_long(rsa.sign(self._pkcs1imify(digest), '')[0], 0) m = Message() m.add_string('ssh-rsa') m.add_string(sig) return m
def _parse_service_request(self, m): service = m.get_string() if self.transport.server_mode and (service == 'ssh-userauth'): # accepted m = Message() m.add_byte(chr(MSG_SERVICE_ACCEPT)) m.add_string(service) self.transport._send_message(m) return # dunno this one self._disconnect_service_not_available()
def _parse_userauth_info_request(self, m): if self.auth_method != 'keyboard-interactive': raise SSHException('Illegal info request from server') title = m.get_string() instructions = m.get_string() m.get_string() # lang prompts = m.get_int() prompt_list = [] for i in range(prompts): prompt_list.append((m.get_string(), m.get_boolean())) response_list = self.interactive_handler(title, instructions, prompt_list) m = Message() m.add_byte(chr(MSG_USERAUTH_INFO_RESPONSE)) m.add_int(len(response_list)) for r in response_list: m.add_string(r) self.transport._send_message(m)
def _send_server_version(self): # winscp will freak out if the server sends version info before the # client finishes sending INIT. t, data = self._read_packet() if t != CMD_INIT: raise SFTPError('Incompatible sftp protocol') version = struct.unpack('>I', data[:4])[0] # advertise that we support "check-file" extension_pairs = [ 'check-file', 'md5,sha1' ] msg = Message() msg.add_int(_VERSION) msg.add(*extension_pairs) self._send_packet(CMD_VERSION, str(msg)) return version
def start_kex(self): self._generate_x() if self.transport.server_mode: # compute f = g^x mod p, but don't send it yet self.f = pow(G, self.x, P) self.transport._expect_packet(_MSG_KEXDH_INIT) return # compute e = g^x mod p (where g=2), and send it self.e = pow(G, self.x, P) m = Message() m.add_byte(chr(_MSG_KEXDH_INIT)) m.add_mpint(self.e) self.transport._send_message(m) self.transport._expect_packet(_MSG_KEXDH_REPLY)
def _parse_kexdh_gex_group(self, m): self.p = m.get_mpint() self.g = m.get_mpint() # reject if p's bit length < 1024 or > 8192 bitlen = util.bit_length(self.p) if (bitlen < 1024) or (bitlen > 8192): raise SSHException( 'Server-generated gex p (don\'t ask) is out of range (%d bits)' % bitlen) self.transport._log(DEBUG, 'Got server p (%d bits)' % bitlen) self._generate_x() # now compute e = g^x mod p self.e = pow(self.g, self.x, self.p) m = Message() m.add_byte(chr(_MSG_KEXDH_GEX_INIT)) m.add_mpint(self.e) self.transport._send_message(m) self.transport._expect_packet(_MSG_KEXDH_GEX_REPLY)
def sign_ssh_data(self, rpool, data): digest = SHA.new(data).digest() dss = DSA.construct((long(self.y), long(self.g), long(self.p), long(self.q), long(self.x))) # generate a suitable k qsize = len(util.deflate_long(self.q, 0)) while True: k = util.inflate_long(rpool.get_bytes(qsize), 1) if (k > 2) and (k < self.q): break r, s = dss.sign(util.inflate_long(digest, 1), k) m = Message() m.add_string('ssh-dss') # apparently, in rare cases, r or s may be shorter than 20 bytes! rstr = util.deflate_long(r, 0) sstr = util.deflate_long(s, 0) if len(rstr) < 20: rstr = '\x00' * (20 - len(rstr)) + rstr if len(sstr) < 20: sstr = '\x00' * (20 - len(sstr)) + sstr m.add_string(rstr + sstr) return m
def _parse_kexdh_gex_request_old(self, m): # same as above, but without min_bits or max_bits (used by older clients like putty) self.preferred_bits = m.get_int() # smoosh the user's preferred size into our own limits if self.preferred_bits > self.max_bits: self.preferred_bits = self.max_bits if self.preferred_bits < self.min_bits: self.preferred_bits = self.min_bits # generate prime pack = self.transport._get_modulus_pack() if pack is None: raise SSHException( 'Can\'t do server-side gex with no modulus pack') self.transport._log(DEBUG, 'Picking p (~ %d bits)' % (self.preferred_bits, )) self.g, self.p = pack.get_modulus(self.min_bits, self.preferred_bits, self.max_bits) m = Message() m.add_byte(chr(_MSG_KEXDH_GEX_GROUP)) m.add_mpint(self.p) m.add_mpint(self.g) self.transport._send_message(m) self.transport._expect_packet(_MSG_KEXDH_GEX_INIT) self.old_style = True
def read_message(self): """ Only one thread should ever be in this function (no other locking is done). @raise SSHException: if the packet is mangled @raise NeedRekeyException: if the transport should rekey """ header = self.read_all(self.__block_size_in, check_rekey=True) if self.__block_engine_in != None: header = self.__block_engine_in.decrypt(header) if self.__dump_packets: self._log(DEBUG, util.format_binary(header, 'IN: ')) packet_size = struct.unpack('>I', header[:4])[0] # leftover contains decrypted bytes from the first block (after the length field) leftover = header[4:] if (packet_size - len(leftover)) % self.__block_size_in != 0: raise SSHException('Invalid packet blocking') buf = self.read_all(packet_size + self.__mac_size_in - len(leftover)) packet = buf[:packet_size - len(leftover)] post_packet = buf[packet_size - len(leftover):] if self.__block_engine_in != None: packet = self.__block_engine_in.decrypt(packet) if self.__dump_packets: self._log(DEBUG, util.format_binary(packet, 'IN: ')) packet = leftover + packet if self.__mac_size_in > 0: mac = post_packet[:self.__mac_size_in] mac_payload = struct.pack('>II', self.__sequence_number_in, packet_size) + packet my_mac = compute_hmac(self.__mac_key_in, mac_payload, self.__mac_engine_in)[:self.__mac_size_in] if my_mac != mac: raise SSHException('Mismatched MAC') padding = ord(packet[0]) payload = packet[1:packet_size - padding] randpool.add_event() if self.__dump_packets: self._log( DEBUG, 'Got payload (%d bytes, %d padding)' % (packet_size, padding)) if self.__compress_engine_in is not None: payload = self.__compress_engine_in(payload) msg = Message(payload[1:]) msg.seqno = self.__sequence_number_in self.__sequence_number_in = (self.__sequence_number_in + 1) & 0xffffffffL # check for rekey self.__received_bytes += packet_size + self.__mac_size_in + 4 self.__received_packets += 1 if self.__need_rekey: # we've asked to rekey -- give them 20 packets to comply before # dropping the connection self.__received_packets_overflow += 1 if self.__received_packets_overflow >= 20: raise SSHException( 'Remote transport is ignoring rekey requests') elif (self.__received_packets >= self.REKEY_PACKETS) or \ (self.__received_bytes >= self.REKEY_BYTES): # only ask once for rekeying self._log( DEBUG, 'Rekeying (hit %d packets, %d bytes received)' % (self.__received_packets, self.__received_bytes)) self.__received_packets_overflow = 0 self._trigger_rekey() cmd = ord(payload[0]) if cmd in MSG_NAMES: cmd_name = MSG_NAMES[cmd] else: cmd_name = '$%x' % cmd if self.__dump_packets: self._log(DEBUG, 'Read packet <%s>, length %d' % (cmd_name, len(payload))) return cmd, msg
def __init__(self, agent, blob): self.agent = agent self.blob = blob self.name = Message(blob).get_string()
def _parse_userauth_request(self, m): if not self.transport.server_mode: # er, uh... what? m = Message() m.add_byte(chr(MSG_USERAUTH_FAILURE)) m.add_string('none') m.add_boolean(0) self.transport._send_message(m) return if self.authenticated: # ignore return username = m.get_string() service = m.get_string() method = m.get_string() self.transport._log( DEBUG, 'Auth request (type=%s) service=%s, username=%s' % (method, service, username)) if service != 'ssh-connection': self._disconnect_service_not_available() return if (self.auth_username is not None) and (self.auth_username != username): self.transport._log( WARNING, 'Auth rejected because the client attempted to change username in mid-flight' ) self._disconnect_no_more_auth() return self.auth_username = username if method == 'none': result = self.transport.server_object.check_auth_none(username) elif method == 'password': changereq = m.get_boolean() password = m.get_string() try: password = password.decode('UTF-8') except UnicodeError: # some clients/servers expect non-utf-8 passwords! # in this case, just return the raw byte string. pass if changereq: # always treated as failure, since we don't support changing passwords, but collect # the list of valid auth types from the callback anyway self.transport._log( DEBUG, 'Auth request to change passwords (rejected)') newpassword = m.get_string() try: newpassword = newpassword.decode('UTF-8', 'replace') except UnicodeError: pass result = AUTH_FAILED else: result = self.transport.server_object.check_auth_password( username, password) elif method == 'publickey': sig_attached = m.get_boolean() keytype = m.get_string() keyblob = m.get_string() try: key = self.transport._key_info[keytype](Message(keyblob)) except SSHException, e: self.transport._log(INFO, 'Auth rejected: public key: %s' % str(e)) key = None except:
def _parse_kexdh_init(self, m): # server mode self.e = m.get_mpint() if (self.e < 1) or (self.e > P - 1): raise SSHException('Client kex "e" is out of range') K = pow(self.e, self.x, P) key = str(self.transport.get_server_key()) # okay, build up the hash H of (V_C || V_S || I_C || I_S || K_S || e || f || K) hm = Message() hm.add(self.transport.remote_version, self.transport.local_version, self.transport.remote_kex_init, self.transport.local_kex_init) hm.add_string(key) hm.add_mpint(self.e) hm.add_mpint(self.f) hm.add_mpint(K) H = SHA.new(str(hm)).digest() self.transport._set_K_H(K, H) # sign it sig = self.transport.get_server_key().sign_ssh_data( self.transport.randpool, H) # send reply m = Message() m.add_byte(chr(_MSG_KEXDH_REPLY)) m.add_string(key) m.add_mpint(self.f) m.add_string(str(sig)) self.transport._send_message(m) self.transport._activate_outbound()
def _send_auth_result(self, username, method, result): # okay, send result m = Message() if result == AUTH_SUCCESSFUL: self.transport._log(INFO, 'Auth granted (%s).' % method) m.add_byte(chr(MSG_USERAUTH_SUCCESS)) self.authenticated = True else: self.transport._log(INFO, 'Auth rejected (%s).' % method) m.add_byte(chr(MSG_USERAUTH_FAILURE)) m.add_string( self.transport.server_object.get_allowed_auths(username)) if result == AUTH_PARTIALLY_SUCCESSFUL: m.add_boolean(1) else: m.add_boolean(0) self.auth_fail_count += 1 self.transport._send_message(m) if self.auth_fail_count >= 10: self._disconnect_no_more_auth() if result == AUTH_SUCCESSFUL: self.transport._auth_trigger()
def _interactive_query(self, q): # make interactive query instead of response m = Message() m.add_byte(chr(MSG_USERAUTH_INFO_REQUEST)) m.add_string(q.name) m.add_string(q.instructions) m.add_string('') m.add_int(len(q.prompts)) for p in q.prompts: m.add_string(p[0]) m.add_boolean(p[1]) self.transport._send_message(m)
def _parse_service_accept(self, m): service = m.get_string() if service == 'ssh-userauth': self.transport._log(DEBUG, 'userauth is OK') m = Message() m.add_byte(chr(MSG_USERAUTH_REQUEST)) m.add_string(self.username) m.add_string('ssh-connection') m.add_string(self.auth_method) if self.auth_method == 'password': m.add_boolean(False) password = self.password if isinstance(password, unicode): password = password.encode('UTF-8') m.add_string(password) elif self.auth_method == 'publickey': m.add_boolean(True) m.add_string(self.private_key.get_name()) m.add_string(str(self.private_key)) blob = self._get_session_blob(self.private_key, 'ssh-connection', self.username) sig = self.private_key.sign_ssh_data(self.transport.randpool, blob) m.add_string(str(sig)) elif self.auth_method == 'keyboard-interactive': m.add_string('') m.add_string(self.submethods) elif self.auth_method == 'none': pass else: raise SSHException('Unknown auth method "%s"' % self.auth_method) self.transport._send_message(m) else: self.transport._log(DEBUG, 'Service request "%s" accepted (?)' % service)
def __str__(self): m = Message() m.add_string('ssh-rsa') m.add_mpint(self.e) m.add_mpint(self.n) return str(m)
def start_kex(self, _test_old_style=False): if self.transport.server_mode: self.transport._expect_packet(_MSG_KEXDH_GEX_REQUEST, _MSG_KEXDH_GEX_REQUEST_OLD) return # request a bit range: we accept (min_bits) to (max_bits), but prefer # (preferred_bits). according to the spec, we shouldn't pull the # minimum up above 1024. m = Message() if _test_old_style: # only used for unit tests: we shouldn't ever send this m.add_byte(chr(_MSG_KEXDH_GEX_REQUEST_OLD)) m.add_int(self.preferred_bits) self.old_style = True else: m.add_byte(chr(_MSG_KEXDH_GEX_REQUEST)) m.add_int(self.min_bits) m.add_int(self.preferred_bits) m.add_int(self.max_bits) self.transport._send_message(m) self.transport._expect_packet(_MSG_KEXDH_GEX_GROUP)
except: self.transport._log( INFO, 'Auth rejected: unsupported or mangled public key') key = None if key is None: self._disconnect_no_more_auth() return # first check if this key is okay... if not, we can skip the verify result = self.transport.server_object.check_auth_publickey( username, key) if result != AUTH_FAILED: # key is okay, verify it if not sig_attached: # client wants to know if this key is acceptable, before it # signs anything... send special "ok" message m = Message() m.add_byte(chr(MSG_USERAUTH_PK_OK)) m.add_string(keytype) m.add_string(keyblob) self.transport._send_message(m) return sig = Message(m.get_string()) blob = self._get_session_blob(key, service, username) if not key.verify_ssh_sig(blob, sig): self.transport._log(INFO, 'Auth rejected: invalid signature') result = AUTH_FAILED elif method == 'keyboard-interactive': lang = m.get_string() submethods = m.get_string() result = self.transport.server_object.check_auth_interactive(
def _request_auth(self): m = Message() m.add_byte(chr(MSG_SERVICE_REQUEST)) m.add_string('ssh-userauth') self.transport._send_message(m)
def _send_message(self, msg): msg = str(msg) self.conn.send(struct.pack('>I', len(msg)) + msg) l = self._read_all(4) msg = Message(self._read_all(struct.unpack('>I', l)[0])) return ord(msg.get_byte()), msg
def _parse_kexdh_gex_init(self, m): self.e = m.get_mpint() if (self.e < 1) or (self.e > self.p - 1): raise SSHException('Client kex "e" is out of range') self._generate_x() self.f = pow(self.g, self.x, self.p) K = pow(self.e, self.x, self.p) key = str(self.transport.get_server_key()) # 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) hm = Message() hm.add(self.transport.remote_version, self.transport.local_version, self.transport.remote_kex_init, self.transport.local_kex_init, key) if not self.old_style: hm.add_int(self.min_bits) hm.add_int(self.preferred_bits) if not self.old_style: hm.add_int(self.max_bits) hm.add_mpint(self.p) hm.add_mpint(self.g) hm.add_mpint(self.e) hm.add_mpint(self.f) hm.add_mpint(K) H = SHA.new(str(hm)).digest() self.transport._set_K_H(K, H) # sign it sig = self.transport.get_server_key().sign_ssh_data( self.transport.randpool, H) # send reply m = Message() m.add_byte(chr(_MSG_KEXDH_GEX_REPLY)) m.add_string(key) m.add_mpint(self.f) m.add_string(str(sig)) self.transport._send_message(m) self.transport._activate_outbound()
def _get_session_blob(self, key, service, username): m = Message() m.add_string(self.transport.session_id) m.add_byte(chr(MSG_USERAUTH_REQUEST)) m.add_string(username) m.add_string(service) m.add_string('publickey') m.add_boolean(1) m.add_string(key.get_name()) m.add_string(str(key)) return str(m)
def _parse_kexdh_gex_reply(self, m): host_key = m.get_string() self.f = m.get_mpint() sig = m.get_string() if (self.f < 1) or (self.f > self.p - 1): raise SSHException('Server kex "f" is out of range') K = pow(self.f, self.x, self.p) # 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) hm = Message() hm.add(self.transport.local_version, self.transport.remote_version, self.transport.local_kex_init, self.transport.remote_kex_init, host_key) if not self.old_style: hm.add_int(self.min_bits) hm.add_int(self.preferred_bits) if not self.old_style: hm.add_int(self.max_bits) hm.add_mpint(self.p) hm.add_mpint(self.g) hm.add_mpint(self.e) hm.add_mpint(self.f) hm.add_mpint(K) self.transport._set_K_H(K, SHA.new(str(hm)).digest()) self.transport._verify_key(host_key, sig) self.transport._activate_outbound()