summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--paramiko/packet.py60
-rw-r--r--paramiko/transport.py122
2 files changed, 161 insertions, 21 deletions
diff --git a/paramiko/packet.py b/paramiko/packet.py
index 1274a23c..3aeb793b 100644
--- a/paramiko/packet.py
+++ b/paramiko/packet.py
@@ -115,6 +115,12 @@ class Packetizer:
self.__etm_out = False
self.__etm_in = False
+ # aead cipher use
+ self.__aead_out = False
+ self.__aead_in = False
+ self.__iv_out = None
+ self.__iv_in = None
+
# lock around outbound writes (packet computation)
self.__write_lock = threading.RLock()
@@ -152,6 +158,8 @@ class Packetizer:
mac_key,
sdctr=False,
etm=False,
+ aead=False,
+ iv_out=None,
):
"""
Switch outbound data cipher.
@@ -166,6 +174,8 @@ class Packetizer:
self.__sent_bytes = 0
self.__sent_packets = 0
self.__etm_out = etm
+ self.__aead_out = aead
+ self.__iv_out = iv_out
# wait until the reset happens in both directions before clearing
# rekey flag
self.__init_count |= 1
@@ -181,6 +191,8 @@ class Packetizer:
mac_size,
mac_key,
etm=False,
+ aead=False,
+ iv_in=None,
):
"""
Switch inbound data cipher.
@@ -196,6 +208,8 @@ class Packetizer:
self.__received_bytes_overflow = 0
self.__received_packets_overflow = 0
self.__etm_in = etm
+ self.__aead_in = aead
+ self.__iv_in = iv_in
# wait until the reset happens in both directions before clearing
# rekey flag
self.__init_count |= 2
@@ -386,6 +400,20 @@ class Packetizer:
buf = buf[:-1]
return u(buf)
+ def _inc_iv_counter(self, iv):
+ # refer https://www.rfc-editor.org/rfc/rfc5647.html#section-7.1
+ iv_counter_b = iv[4:]
+ iv_counter = int.from_bytes(iv_counter_b, "big")
+ inc_iv_counter = iv_counter + 1
+ inc_iv_counter_b = inc_iv_counter.to_bytes(8, "big")
+ new_iv = iv[0:4] + inc_iv_counter_b
+ self._log(
+ DEBUG,
+ "old-iv_count[%s], new-iv_count[%s]"
+ % (iv_counter, inc_iv_counter),
+ )
+ return new_iv
+
def send_message(self, data):
"""
Write a block of data using the current cipher, as an SSH block.
@@ -415,12 +443,18 @@ class Packetizer:
out = packet[0:4] + self.__block_engine_out.update(
packet[4:]
)
+ elif self.__aead_out:
+ # packet length is used to associated_data
+ out = packet[0:4] + self.__block_engine_out.encrypt(
+ self.__iv_out, packet[4:], packet[0:4]
+ )
+ self.__iv_out = self._inc_iv_counter(self.__iv_out)
else:
out = self.__block_engine_out.update(packet)
else:
out = packet
- # + mac
- if self.__block_engine_out is not None:
+ # + mac, aead no need hmac
+ if self.__block_engine_out is not None and not self.__aead_out:
packed = struct.pack(">I", self.__sequence_number_out)
payload = packed + (out if self.__etm_out else packet)
out += compute_hmac(
@@ -460,7 +494,9 @@ class Packetizer:
:raises: `.SSHException` -- if the packet is mangled
:raises: `.NeedRekeyException` -- if the transport should rekey
"""
+ self._log(DEBUG, "read message from sock")
header = self.read_all(self.__block_size_in, check_rekey=True)
+ self._log(DEBUG, "raw data length[%s]" % len(header))
if self.__etm_in:
packet_size = struct.unpack(">I", header[:4])[0]
remaining = packet_size - self.__block_size_in + 4
@@ -477,14 +513,26 @@ class Packetizer:
raise SSHException("Mismatched MAC")
header = packet
- if self.__block_engine_in is not None:
+ if self.__aead_in:
+ packet_size = struct.unpack(">I", header[:4])[0]
+ aad = header[:4]
+ remaining = (
+ packet_size - self.__block_size_in + 4 + self.__mac_size_in
+ )
+ packet = header[4:] + self.read_all(remaining, check_rekey=False)
+ self._log(DEBUG, "len(aad)=%s, aad->%s" % (len(aad), aad.hex()))
+ header = self.__block_engine_in.decrypt(self.__iv_in, packet, aad)
+
+ self.__iv_in = self._inc_iv_counter(self.__iv_in)
+
+ if self.__block_engine_in is not None and not self.__aead_in:
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:
+ if self.__etm_in or self.__aead_in:
packet = header
# Otherwise, use the older non-ETM logic
else:
@@ -508,7 +556,7 @@ class Packetizer:
if self.__dump_packets:
self._log(DEBUG, util.format_binary(packet, "IN: "))
- if self.__mac_size_in > 0 and not self.__etm_in:
+ if self.__mac_size_in > 0 and not self.__etm_in and not self.__aead_in:
mac = post_packet[: self.__mac_size_in]
mac_payload = (
struct.pack(">II", self.__sequence_number_in, packet_size)
@@ -631,7 +679,7 @@ class Packetizer:
bsize = self.__block_size_out
# do not include payload length in computations for padding in EtM mode
# (payload length won't be encrypted)
- addlen = 4 if self.__etm_out else 8
+ addlen = 4 if self.__etm_out or self.__aead_out else 8
padding = 3 + bsize - ((len(payload) + addlen) % bsize)
packet = struct.pack(">IB", len(payload) + padding + 1, padding)
packet += payload
diff --git a/paramiko/transport.py b/paramiko/transport.py
index ecd8c7bc..a80ad9ef 100644
--- a/paramiko/transport.py
+++ b/paramiko/transport.py
@@ -30,7 +30,7 @@ import weakref
from hashlib import md5, sha1, sha256, sha512
from cryptography.hazmat.backends import default_backend
-from cryptography.hazmat.primitives.ciphers import algorithms, Cipher, modes
+from cryptography.hazmat.primitives.ciphers import algorithms, Cipher, modes, aead
import paramiko
from paramiko import util
@@ -179,6 +179,8 @@ class Transport(threading.Thread, ClosingContextManager):
"aes192-cbc",
"aes256-cbc",
"3des-cbc",
+ "aes128-gcm@openssh.com",
+ "aes256-gcm@openssh.com",
)
_preferred_macs = (
"hmac-sha2-256",
@@ -237,44 +239,66 @@ class Transport(threading.Thread, ClosingContextManager):
"class": algorithms.AES,
"mode": modes.CTR,
"block-size": 16,
+ "iv-size": 16,
"key-size": 16,
},
"aes192-ctr": {
"class": algorithms.AES,
"mode": modes.CTR,
"block-size": 16,
+ "iv-size": 16,
"key-size": 24,
},
"aes256-ctr": {
"class": algorithms.AES,
"mode": modes.CTR,
"block-size": 16,
+ "iv-size": 16,
"key-size": 32,
},
"aes128-cbc": {
"class": algorithms.AES,
"mode": modes.CBC,
"block-size": 16,
+ "iv-size": 16,
"key-size": 16,
},
"aes192-cbc": {
"class": algorithms.AES,
"mode": modes.CBC,
"block-size": 16,
+ "iv-size": 16,
"key-size": 24,
},
"aes256-cbc": {
"class": algorithms.AES,
"mode": modes.CBC,
"block-size": 16,
+ "iv-size": 16,
"key-size": 32,
},
"3des-cbc": {
"class": TripleDES,
"mode": modes.CBC,
"block-size": 8,
+ "iv-size": 8,
"key-size": 24,
},
+ # aead cipher
+ "aes128-gcm@openssh.com": {
+ "class": aead.AESGCM,
+ "block-size": 16,
+ "iv-size": 12,
+ "key-size": 16,
+ "is_aead": True,
+ },
+ "aes256-gcm@openssh.com": {
+ "class": aead.AESGCM,
+ "block-size": 16,
+ "iv-size": 12,
+ "key-size": 32,
+ "is_aead": True,
+ },
}
_mac_info = {
@@ -2039,6 +2063,10 @@ class Transport(threading.Thread, ClosingContextManager):
else:
return cipher.decryptor()
+ def _get_aead_cipher(self, name, key):
+ aead_cipher = self._cipher_info[name]["class"](key)
+ return aead_cipher
+
def _set_forward_agent_handler(self, handler):
if handler is None:
@@ -2707,18 +2735,32 @@ class Transport(threading.Thread, ClosingContextManager):
inbound traffic"""
block_size = self._cipher_info[self.remote_cipher]["block-size"]
if self.server_mode:
- IV_in = self._compute_key("A", block_size)
+ IV_in = self._compute_key(
+ "A", self._cipher_info[self.remote_cipher]["iv-size"]
+ )
key_in = self._compute_key(
"C", self._cipher_info[self.remote_cipher]["key-size"]
)
else:
- IV_in = self._compute_key("B", block_size)
+ IV_in = self._compute_key(
+ "B", self._cipher_info[self.remote_cipher]["iv-size"]
+ )
key_in = self._compute_key(
"D", self._cipher_info[self.remote_cipher]["key-size"]
)
- engine = self._get_cipher(
- self.remote_cipher, key_in, IV_in, self._DECRYPT
+
+ is_aead = (
+ True
+ if self._cipher_info[self.remote_cipher].get("is_aead")
+ else False
)
+
+ if is_aead:
+ engine = self._get_aead_cipher(self.remote_cipher, key_in)
+ else:
+ 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"]
@@ -2728,9 +2770,24 @@ class Transport(threading.Thread, ClosingContextManager):
mac_key = self._compute_key("E", mac_engine().digest_size)
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, etm=etm
- )
+
+ if is_aead:
+ self._log(DEBUG, "use aead-cipher, so set mac to None")
+ self.packetizer.set_inbound_cipher(
+ engine,
+ block_size,
+ None,
+ 16,
+ bytes(),
+ etm=False,
+ aead=is_aead,
+ iv_in=IV_in,
+ )
+ else:
+ self.packetizer.set_inbound_cipher(
+ 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 (
self.remote_compression != "zlib@openssh.com" or self.authenticated
@@ -2760,18 +2817,32 @@ class Transport(threading.Thread, ClosingContextManager):
self.packetizer.reset_seqno_out()
block_size = self._cipher_info[self.local_cipher]["block-size"]
if self.server_mode:
- IV_out = self._compute_key("B", block_size)
+ IV_out = self._compute_key(
+ "B", self._cipher_info[self.local_cipher]["iv-size"]
+ )
key_out = self._compute_key(
"D", self._cipher_info[self.local_cipher]["key-size"]
)
else:
- IV_out = self._compute_key("A", block_size)
+ IV_out = self._compute_key(
+ "A", self._cipher_info[self.local_cipher]["iv-size"]
+ )
key_out = self._compute_key(
"C", self._cipher_info[self.local_cipher]["key-size"]
)
- engine = self._get_cipher(
- self.local_cipher, key_out, IV_out, self._ENCRYPT
+
+ is_aead = (
+ True
+ if self._cipher_info[self.local_cipher].get("is_aead")
+ else False
)
+
+ if is_aead:
+ engine = self._get_aead_cipher(self.local_cipher, key_out)
+ else:
+ 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"]
@@ -2782,9 +2853,30 @@ class Transport(threading.Thread, ClosingContextManager):
else:
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, etm=etm
- )
+
+ if is_aead:
+ self.packetizer.set_outbound_cipher(
+ engine,
+ block_size,
+ None,
+ 16,
+ bytes(),
+ sdctr,
+ etm=False,
+ aead=is_aead,
+ iv_out=IV_out,
+ )
+ else:
+ self.packetizer.set_outbound_cipher(
+ 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 (
self.local_compression != "zlib@openssh.com" or self.authenticated