Skip to content

Commit fa46de7

Browse files
committed
Reset sequence numbers on rekey
1 parent 75e311d commit fa46de7

File tree

3 files changed

+49
-4
lines changed

3 files changed

+49
-4
lines changed

paramiko/packet.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,12 @@ def __init__(self, socket):
130130
def closed(self):
131131
return self.__closed
132132

133+
def reset_seqno_out(self):
134+
self.__sequence_number_out = 0
135+
136+
def reset_seqno_in(self):
137+
self.__sequence_number_in = 0
138+
133139
def set_log(self, log):
134140
"""
135141
Set the Python log object to use for logging.

paramiko/transport.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2499,9 +2499,13 @@ def _parse_kex_init(self, m):
24992499

25002500
# CVE mitigation: expect zeroed-out seqno anytime we are performing kex
25012501
# init phase, if strict mode was negotiated.
2502-
if self.agreed_on_strict_kex and m.seqno != 0:
2502+
if (
2503+
self.agreed_on_strict_kex
2504+
and not self.initial_kex_done
2505+
and m.seqno != 0
2506+
):
25032507
raise MessageOrderError(
2504-
f"Got nonzero seqno ({m.seqno}) during strict KEXINIT!"
2508+
"In strict-kex mode, but KEXINIT was not the first packet!"
25052509
)
25062510

25072511
# as a server, we pick the first item in the client's list that we
@@ -2703,13 +2707,27 @@ def _activate_inbound(self):
27032707
):
27042708
self._log(DEBUG, "Switching on inbound compression ...")
27052709
self.packetizer.set_inbound_compressor(compress_in())
2710+
# Reset inbound sequence number if strict mode.
2711+
if self.agreed_on_strict_kex:
2712+
self._log(
2713+
DEBUG,
2714+
f"Resetting inbound seqno after NEWKEYS due to strict mode",
2715+
)
2716+
self.packetizer.reset_seqno_in()
27062717

27072718
def _activate_outbound(self):
27082719
"""switch on newly negotiated encryption parameters for
27092720
outbound traffic"""
27102721
m = Message()
27112722
m.add_byte(cMSG_NEWKEYS)
27122723
self._send_message(m)
2724+
# Reset outbound sequence number if strict mode.
2725+
if self.agreed_on_strict_kex:
2726+
self._log(
2727+
DEBUG,
2728+
f"Resetting outbound sequence number after NEWKEYS due to strict mode",
2729+
)
2730+
self.packetizer.reset_seqno_out()
27132731
block_size = self._cipher_info[self.local_cipher]["block-size"]
27142732
if self.server_mode:
27152733
IV_out = self._compute_key("B", block_size)

tests/test_transport.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1345,5 +1345,26 @@ def test_MessageOrderError_raised_when_kexinit_not_seq_0_and_strict(self):
13451345
):
13461346
pass # kexinit happens at connect...
13471347

1348-
def test_sequence_numbers_reset_on_newkeys(self):
1349-
skip()
1348+
def test_sequence_numbers_reset_on_newkeys_when_strict(self):
1349+
with server(defer=True) as (tc, ts):
1350+
# When in strict mode, these should all be zero or close to it
1351+
# (post-kexinit, pre-auth).
1352+
# Server->client will be 1 (EXT_INFO got sent after NEWKEYS)
1353+
assert tc.packetizer._Packetizer__sequence_number_in == 1
1354+
assert ts.packetizer._Packetizer__sequence_number_out == 1
1355+
# Client->server will be 0
1356+
assert tc.packetizer._Packetizer__sequence_number_out == 0
1357+
assert ts.packetizer._Packetizer__sequence_number_in == 0
1358+
1359+
def test_sequence_numbers_not_reset_on_newkeys_when_not_strict(self):
1360+
with server(defer=True, client_init=dict(strict_kex=False)) as (
1361+
tc,
1362+
ts,
1363+
):
1364+
# When not in strict mode, these will all be ~3-4 or so
1365+
# (post-kexinit, pre-auth). Not encoding exact values as it will
1366+
# change anytime we mess with the test harness...
1367+
assert tc.packetizer._Packetizer__sequence_number_in != 0
1368+
assert tc.packetizer._Packetizer__sequence_number_out != 0
1369+
assert ts.packetizer._Packetizer__sequence_number_in != 0
1370+
assert ts.packetizer._Packetizer__sequence_number_out != 0

0 commit comments

Comments
 (0)