diff options
-rw-r--r-- | paramiko/packet.py | 77 | ||||
-rw-r--r-- | paramiko/transport.py | 10 |
2 files changed, 68 insertions, 19 deletions
diff --git a/paramiko/packet.py b/paramiko/packet.py index d324fc35..76e10dbd 100644 --- a/paramiko/packet.py +++ b/paramiko/packet.py @@ -110,6 +110,8 @@ class Packetizer(object): self.__compress_engine_in = None self.__sequence_number_out = 0 self.__sequence_number_in = 0 + self.__etm_out = False + self.__etm_in = False # lock around outbound writes (packet computation) self.__write_lock = threading.RLock() @@ -141,9 +143,11 @@ class Packetizer(object): mac_size, mac_key, sdctr=False, + etm=False, ): """ Switch outbound data cipher. + :param etm: Set encrypt-then-mac from OpenSSH """ self.__block_engine_out = block_engine self.__sdctr_out = sdctr @@ -153,6 +157,7 @@ class Packetizer(object): self.__mac_key_out = mac_key self.__sent_bytes = 0 self.__sent_packets = 0 + self.__etm_out = etm # wait until the reset happens in both directions before clearing # rekey flag self.__init_count |= 1 @@ -161,10 +166,11 @@ class Packetizer(object): self.__need_rekey = False def set_inbound_cipher( - self, block_engine, block_size, mac_engine, mac_size, mac_key + self, block_engine, block_size, mac_engine, mac_size, mac_key, etm=False ): """ Switch inbound data cipher. + :param etm: Set encrypt-then-mac from OpenSSH """ self.__block_engine_in = block_engine self.__block_size_in = block_size @@ -175,6 +181,7 @@ class Packetizer(object): self.__received_packets = 0 self.__received_bytes_overflow = 0 self.__received_packets_overflow = 0 + self.__etm_in = etm # wait until the reset happens in both directions before clearing # rekey flag self.__init_count |= 2 @@ -395,14 +402,23 @@ class Packetizer(object): ) self._log(DEBUG, util.format_binary(packet, "OUT: ")) if self.__block_engine_out is not None: - out = self.__block_engine_out.update(packet) + if self.__etm_out: + ## packet length is not encrypted in EtM + out = packet[0:4] + self.__block_engine_out.update(packet[4:]) + else: + out = self.__block_engine_out.update(packet) else: out = packet # + mac if self.__block_engine_out is not None: - payload = ( - struct.pack(">I", self.__sequence_number_out) + packet - ) + if self.__etm_out: + payload = ( + struct.pack(">I", self.__sequence_number_out) + out + ) + else: + payload = ( + struct.pack(">I", self.__sequence_number_out) + packet + ) out += compute_hmac( self.__mac_key_out, payload, self.__mac_engine_out )[: self.__mac_size_out] @@ -438,26 +454,52 @@ class Packetizer(object): :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] + packet = header[4:] + self.read_all(packet_size-self.__block_size_in+4, 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: ")) - packet_size = struct.unpack(">I", header[:4])[0] + + #already computed + packet_size = packet_size if self.__etm_in else 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) + + #no leftovers + if not self.__etm_in: + 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 + + else: + #already decrypted everything above + packet = header + if self.__dump_packets: self._log(DEBUG, util.format_binary(packet, "IN: ")) - packet = leftover + packet - if self.__mac_size_in > 0: + 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) @@ -578,7 +620,8 @@ class Packetizer(object): def _build_packet(self, payload): # pad up at least 4 bytes, to nearest block-size (usually 8) bsize = self.__block_size_out - padding = 3 + bsize - ((len(payload) + 8) % bsize) + # do not include payload length in computations for padding in EtM mode (payload lenght won't be encrypted) + padding = 3 + bsize - ((len(payload) + (4 if self.__etm_out else 8)) % bsize) packet = struct.pack(">IB", len(payload) + padding + 1, padding) packet += payload if self.__sdctr_out or self.__block_engine_out is None: diff --git a/paramiko/transport.py b/paramiko/transport.py index 5687778e..d7ab0351 100644 --- a/paramiko/transport.py +++ b/paramiko/transport.py @@ -152,6 +152,8 @@ class Transport(threading.Thread, ClosingContextManager): "3des-cbc", ) _preferred_macs = ( + "hmac-sha2-256-etm@openssh.com", + "hmac-sha2-512-etm@openssh.com", "hmac-sha2-256", "hmac-sha2-512", "hmac-sha1", @@ -240,7 +242,9 @@ class Transport(threading.Thread, ClosingContextManager): "hmac-sha1": {"class": sha1, "size": 20}, "hmac-sha1-96": {"class": sha1, "size": 12}, "hmac-sha2-256": {"class": sha256, "size": 32}, + "hmac-sha2-256-etm@openssh.com": {"class": sha256, "size": 32}, "hmac-sha2-512": {"class": sha512, "size": 64}, + "hmac-sha2-512-etm@openssh.com": {"class": sha512, "size": 64}, "hmac-md5": {"class": md5, "size": 16}, "hmac-md5-96": {"class": md5, "size": 12}, } @@ -2434,6 +2438,7 @@ class Transport(threading.Thread, ClosingContextManager): engine = self._get_cipher( self.remote_cipher, key_in, IV_in, self._DECRYPT ) + etm = "etm@openssh.com" in self.remote_mac mac_size = self._mac_info[self.remote_mac]["size"] mac_engine = self._mac_info[self.remote_mac]["class"] # initial mac keys are done in the hash's natural size (not the @@ -2443,7 +2448,7 @@ class Transport(threading.Thread, ClosingContextManager): else: mac_key = self._compute_key("F", mac_engine().digest_size) self.packetizer.set_inbound_cipher( - engine, block_size, mac_engine, mac_size, mac_key + engine, block_size, mac_engine, mac_size, mac_key, etm=etm ) compress_in = self._compression_info[self.remote_compression][1] if compress_in is not None and ( @@ -2472,6 +2477,7 @@ class Transport(threading.Thread, ClosingContextManager): engine = self._get_cipher( self.local_cipher, key_out, IV_out, self._ENCRYPT ) + etm = "etm@openssh.com" in self.local_mac mac_size = self._mac_info[self.local_mac]["size"] mac_engine = self._mac_info[self.local_mac]["class"] # initial mac keys are done in the hash's natural size (not the @@ -2482,7 +2488,7 @@ class Transport(threading.Thread, ClosingContextManager): mac_key = self._compute_key("E", mac_engine().digest_size) sdctr = self.local_cipher.endswith("-ctr") self.packetizer.set_outbound_cipher( - engine, block_size, mac_engine, mac_size, mac_key, sdctr + engine, block_size, mac_engine, mac_size, mac_key, sdctr, etm=etm ) compress_out = self._compression_info[self.local_compression][0] if compress_out is not None and ( |