1
- # Copyright (c) 2015-2020 by Ron Frederick <[email protected] > and others.
1
+ # Copyright (c) 2015-2025 by Ron Frederick <[email protected] > and others.
2
2
#
3
3
# This program and the accompanying materials are made available under
4
4
# the terms of the Eclipse Public License v2.0 which accompanies this
35
35
from asyncssh .kex_dh import MSG_KEX_DH_GEX_REQUEST , MSG_KEX_DH_GEX_GROUP
36
36
from asyncssh .kex_dh import MSG_KEX_DH_GEX_INIT , MSG_KEX_DH_GEX_REPLY , _KexDHGex
37
37
from asyncssh .kex_dh import MSG_KEX_ECDH_INIT , MSG_KEX_ECDH_REPLY
38
- from asyncssh .kex_dh import MSG_KEXGSS_INIT , MSG_KEXGSS_COMPLETE
39
- from asyncssh .kex_dh import MSG_KEXGSS_ERROR
38
+ from asyncssh .kex_dh import MSG_KEXGSS_INIT , MSG_KEXGSS_HOSTKEY
39
+ from asyncssh .kex_dh import MSG_KEXGSS_COMPLETE , MSG_KEXGSS_ERROR
40
40
from asyncssh .kex_rsa import MSG_KEXRSA_PUBKEY , MSG_KEXRSA_SECRET
41
41
from asyncssh .kex_rsa import MSG_KEXRSA_DONE
42
42
from asyncssh .gss import GSSClient , GSSServer
51
51
class _KexConnectionStub (ConnectionStub ):
52
52
"""Connection stub class to test key exchange"""
53
53
54
- def __init__ (self , alg , gss , peer , server = False ):
54
+ def __init__ (self , alg , gss , duplicate = 0 , peer = None , server = False ):
55
55
super ().__init__ (peer , server )
56
56
57
57
self ._gss = gss
58
58
self ._key_waiter = asyncio .Future ()
59
59
60
+ self ._duplicate = duplicate
60
61
self ._kex = get_kex (self , alg )
61
62
62
63
async def start (self ):
@@ -104,6 +105,14 @@ def get_gss_context(self):
104
105
105
106
return self ._gss
106
107
108
+ def send_packet (self , pkttype , * args , ** kwargs ):
109
+ """Duplicate sending packets of a specific type"""
110
+
111
+ super ().send_packet (pkttype , * args )
112
+
113
+ if pkttype == self ._duplicate :
114
+ super ().send_packet (pkttype , * args , ** kwargs )
115
+
107
116
async def simulate_dh_init (self , e ):
108
117
"""Simulate receiving a DH init packet"""
109
118
@@ -176,21 +185,21 @@ class _KexClientStub(_KexConnectionStub):
176
185
"""Stub class for client connection"""
177
186
178
187
@classmethod
179
- def make_pair (cls , alg , gss_host = None ):
188
+ def make_pair (cls , alg , gss_host = None , duplicate = 0 ):
180
189
"""Make a client and server connection pair to test key exchange"""
181
190
182
- client_conn = cls (alg , gss_host )
191
+ client_conn = cls (alg , gss_host , duplicate )
183
192
return client_conn , client_conn .get_peer ()
184
193
185
- def __init__ (self , alg , gss_host ):
186
- server_conn = _KexServerStub (alg , gss_host , self )
194
+ def __init__ (self , alg , gss_host , duplicate ):
195
+ server_conn = _KexServerStub (alg , gss_host , duplicate , peer = self )
187
196
188
197
if gss_host :
189
198
gss = GSSClient (gss_host , None , 'delegate' in gss_host )
190
199
else :
191
200
gss = None
192
201
193
- super ().__init__ (alg , gss , server_conn )
202
+ super ().__init__ (alg , gss , duplicate , peer = server_conn )
194
203
195
204
def connection_lost (self , exc ):
196
205
"""Handle the closing of a connection"""
@@ -211,9 +220,9 @@ def validate_server_host_key(self, host_key_data):
211
220
class _KexServerStub (_KexConnectionStub ):
212
221
"""Stub class for server connection"""
213
222
214
- def __init__ (self , alg , gss_host , peer ):
223
+ def __init__ (self , alg , gss_host , duplicate , peer ):
215
224
gss = GSSServer (gss_host , None ) if gss_host else None
216
- super ().__init__ (alg , gss , peer , True )
225
+ super ().__init__ (alg , gss , duplicate , peer , True )
217
226
218
227
if gss_host and 'no_host_key' in gss_host :
219
228
self ._server_host_key = None
@@ -381,6 +390,21 @@ async def test_dh_gex_errors(self):
381
390
client_conn .close ()
382
391
server_conn .close ()
383
392
393
+ @asynctest
394
+ async def test_dh_gex_multiple_messages (self ):
395
+ """Unit test duplicate messages in DH group exchange"""
396
+
397
+ for pkttype in (MSG_KEX_DH_GEX_REQUEST , MSG_KEX_DH_GEX_GROUP ):
398
+ client_conn , server_conn = _KexClientStub .make_pair (
399
+ b'diffie-hellman-group-exchange-sha1' , duplicate = pkttype )
400
+
401
+ with self .assertRaises (asyncssh .ProtocolError ):
402
+ await client_conn .start ()
403
+ await client_conn .get_key ()
404
+
405
+ client_conn .close ()
406
+ server_conn .close ()
407
+
384
408
@unittest .skipUnless (gss_available , 'GSS not available' )
385
409
@asynctest
386
410
async def test_gss_errors (self ):
@@ -393,6 +417,15 @@ async def test_gss_errors(self):
393
417
with self .assertRaises (asyncssh .ProtocolError ):
394
418
await client_conn .process_packet (Byte (MSG_KEXGSS_INIT ))
395
419
420
+ with self .subTest ('Host key sent to server' ):
421
+ with self .assertRaises (asyncssh .ProtocolError ):
422
+ await server_conn .process_packet (Byte (MSG_KEXGSS_HOSTKEY ))
423
+
424
+ with self .subTest ('Host key sent twice to client' ):
425
+ with self .assertRaises (asyncssh .ProtocolError ):
426
+ await client_conn .process_packet (Byte (MSG_KEXGSS_HOSTKEY ))
427
+ await client_conn .process_packet (Byte (MSG_KEXGSS_HOSTKEY ))
428
+
396
429
with self .subTest ('Complete sent to server' ):
397
430
with self .assertRaises (asyncssh .ProtocolError ):
398
431
await server_conn .process_packet (Byte (MSG_KEXGSS_COMPLETE ))
0 commit comments