def lookup(self, hostname): """ Find a hostkey entry for a given hostname or IP. If no entry is found, ``None`` is returned. Otherwise a dictionary of keytype to key is returned. The keytype will be either ``"ssh-rsa"`` or ``"ssh-dss"``. :param str hostname: the hostname (or IP) to lookup :return: dict of `str` -> `.PKey` keys associated with this host (or ``None``) """ class SubDict (MutableMapping): def __init__(self, hostname, entries, hostkeys): self._hostname = hostname self._entries = entries self._hostkeys = hostkeys def __iter__(self): for k in self.keys(): yield k def __len__(self): return len(self.keys()) def __delitem__(self, key): for e in list(self._entries): if e.key.get_name() == key: self._entries.remove(e) else: raise KeyError(key) def __getitem__(self, key): for e in self._entries: if e.key.get_name() == key: return e.key raise KeyError(key) def __setitem__(self, key, val): for e in self._entries: if e.key is None: continue if e.key.get_name() == key: # replace e.key = val break else: # add a new one e = HostKeyEntry([hostname], val) self._entries.append(e) self._hostkeys._entries.append(e) def keys(self): return [e.key.get_name() for e in self._entries if e.key is not None] entries = [] for e in self._entries: for h in e.hostnames: if h.startswith('|1|') and not hostname.startswith('|1|') and constant_time_bytes_eq(self.hash_host(hostname, h), h) or h == hostname: entries.append(e) if len(entries) == 0: return None return SubDict(hostname, entries, self)
def _hostname_matches(self, hostname, entry): """ Tests whether ``hostname`` string matches given SubDict ``entry``. :returns bool: """ for h in entry.hostnames: if (h == hostname or h.startswith('|1|') and not hostname.startswith('|1|') and constant_time_bytes_eq(self.hash_host(hostname, h), h)): return True return False
def lookup(self, hostname): """ Find a hostkey entry for a given hostname or IP. If no entry is found, C{None} is returned. Otherwise a dictionary of keytype to key is returned. The keytype will be either C{"ssh-rsa"} or C{"ssh-dss"}. @param hostname: the hostname (or IP) to lookup @type hostname: str @return: keys associated with this host (or C{None}) @rtype: dict(str, L{PKey}) """ class SubDict(UserDict.DictMixin): def __init__(self, hostname, entries, hostkeys): self._hostname = hostname self._entries = entries self._hostkeys = hostkeys def __getitem__(self, key): for e in self._entries: if e.key.get_name() == key: return e.key raise KeyError(key) def __setitem__(self, key, val): for e in self._entries: if e.key is None: continue if e.key.get_name() == key: # replace e.key = val break else: # add a new one e = HostKeyEntry([hostname], val) self._entries.append(e) self._hostkeys._entries.append(e) def keys(self): return [ e.key.get_name() for e in self._entries if e.key is not None ] entries = [] for e in self._entries: for h in e.hostnames: if h.startswith('|1|') and constant_time_bytes_eq( self.hash_host(hostname, h), h) or h == hostname: entries.append(e) if len(entries) == 0: return None return SubDict(hostname, entries, self)
def _hostname_matches(self, hostname, entry): """ Tests whether ``hostname`` string matches given SubDict ``entry``. :returns bool: """ for h in entry.hostnames: if ( h == hostname or h.startswith("|1|") and not hostname.startswith("|1|") and constant_time_bytes_eq(self.hash_host(hostname, h), h) ): return True return False
def lookup(self, hostname): """ Find a hostkey entry for a given hostname or IP. If no entry is found, C{None} is returned. Otherwise a dictionary of keytype to key is returned. The keytype will be either C{"ssh-rsa"} or C{"ssh-dss"}. @param hostname: the hostname (or IP) to lookup @type hostname: str @return: keys associated with this host (or C{None}) @rtype: dict(str, L{PKey}) """ class SubDict (UserDict.DictMixin): def __init__(self, hostname, entries, hostkeys): self._hostname = hostname self._entries = entries self._hostkeys = hostkeys def __getitem__(self, key): for e in self._entries: if e.key.get_name() == key: return e.key raise KeyError(key) def __setitem__(self, key, val): for e in self._entries: if e.key is None: continue if e.key.get_name() == key: # replace e.key = val break else: # add a new one e = HostKeyEntry([hostname], val) self._entries.append(e) self._hostkeys._entries.append(e) def keys(self): return [e.key.get_name() for e in self._entries if e.key is not None] entries = [] for e in self._entries: for h in e.hostnames: if h.startswith('|1|') and constant_time_bytes_eq(self.hash_host(hostname, h), h) or h == hostname: entries.append(e) if len(entries) == 0: return None return SubDict(hostname, entries, self)
def read_message(self): """ Only one thread should ever be in this function (no other locking is done). :raises SSHException: if the packet is mangled :raises NeedRekeyException: if the transport should rekey """ header = self.read_all(self.__block_size_in, check_rekey=True) if self.__block_engine_in is not 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 is not 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 not util.constant_time_bytes_eq(my_mac, mac): raise SSHException('Mismatched MAC') padding = byte_ord(packet[0]) payload = packet[1:packet_size - padding] 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) & xffffffff # check for rekey raw_packet_size = packet_size + self.__mac_size_in + 4 self.__received_bytes += raw_packet_size self.__received_packets += 1 if self.__need_rekey: # we've asked to rekey -- give them some packets to comply before # dropping the connection self.__received_bytes_overflow += raw_packet_size self.__received_packets_overflow += 1 if (self.__received_packets_overflow >= self.REKEY_PACKETS_OVERFLOW_MAX) or \ (self.__received_bytes_overflow >= self.REKEY_BYTES_OVERFLOW_MAX): 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_bytes_overflow = 0 self.__received_packets_overflow = 0 self._trigger_rekey() cmd = byte_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 read_message(self): """ Only one thread should ever be in this function (no other locking is done). :raises SSHException: if the packet is mangled :raises NeedRekeyException: if the transport should rekey """ header = self.read_all(self.__block_size_in, check_rekey=True) if self.__block_engine_in is not 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 is not 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 not util.constant_time_bytes_eq(my_mac, mac): raise SSHException('Mismatched MAC') padding = byte_ord(packet[0]) payload = packet[1:packet_size - padding] 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) & xffffffff # check for rekey raw_packet_size = packet_size + self.__mac_size_in + 4 self.__received_bytes += raw_packet_size self.__received_packets += 1 if self.__need_rekey: # we've asked to rekey -- give them some packets to comply before # dropping the connection self.__received_bytes_overflow += raw_packet_size self.__received_packets_overflow += 1 if (self.__received_packets_overflow >= self.REKEY_PACKETS_OVERFLOW_MAX) or \ (self.__received_bytes_overflow >= self.REKEY_BYTES_OVERFLOW_MAX): 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_bytes_overflow = 0 self.__received_packets_overflow = 0 self._trigger_rekey() cmd = byte_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 read_message(self): """ Only one thread should ever be in this function (no other locking is done). :raises: `.SSHException` -- if the packet is mangled :raises: `.NeedRekeyException` -- if the transport should rekey """ header = self.read_all(self.__block_size_in, check_rekey=True) if self.__etm_in: packet_size = struct.unpack(">I", header[:4])[0] remaining = packet_size - self.__block_size_in + 4 packet = header[4:] + self.read_all(remaining, check_rekey=False) mac = self.read_all(self.__mac_size_in, check_rekey=False) 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 not util.constant_time_bytes_eq(my_mac, mac): raise SSHException("Mismatched MAC") header = packet if self.__block_engine_in is not None: header = self.__block_engine_in.update(header) if self.__dump_packets: self._log(DEBUG, util.format_binary(header, "IN: ")) # When ETM is in play, we've already read the packet size & decrypted # everything, so just set the packet back to the header we obtained. if self.__etm_in: packet = header # Otherwise, use the older non-ETM logic else: 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 is not None: packet = self.__block_engine_in.update(packet) packet = leftover + packet if self.__dump_packets: self._log(DEBUG, util.format_binary(packet, "IN: ")) if self.__mac_size_in > 0 and not self.__etm_in: 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 not util.constant_time_bytes_eq(my_mac, mac): raise SSHException("Mismatched MAC") padding = byte_ord(packet[0]) payload = packet[1 : packet_size - padding] if self.__dump_packets: self._log( DEBUG, "Got payload ({} bytes, {} padding)".format( 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) & xffffffff # check for rekey raw_packet_size = packet_size + self.__mac_size_in + 4 self.__received_bytes += raw_packet_size self.__received_packets += 1 if self.__need_rekey: # we've asked to rekey -- give them some packets to comply before # dropping the connection self.__received_bytes_overflow += raw_packet_size self.__received_packets_overflow += 1 if ( self.__received_packets_overflow >= self.REKEY_PACKETS_OVERFLOW_MAX ) or ( self.__received_bytes_overflow >= self.REKEY_BYTES_OVERFLOW_MAX ): 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 err = "Rekeying (hit {} packets, {} bytes received)" self._log( DEBUG, err.format(self.__received_packets, self.__received_bytes), ) self.__received_bytes_overflow = 0 self.__received_packets_overflow = 0 self._trigger_rekey() cmd = byte_ord(payload[0]) if cmd in MSG_NAMES: cmd_name = MSG_NAMES[cmd] else: cmd_name = "${:x}".format(cmd) if self.__dump_packets: self._log( DEBUG, "Read packet <{}>, length {}".format(cmd_name, len(payload)), ) return cmd, msg