summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--paramiko/packet.py77
-rw-r--r--paramiko/transport.py10
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 (