Skip to content

Commit c19aa18

Browse files
authored
Fix do not accept unencrypted client-auth messages (#68)
1 parent 4b2a340 commit c19aa18

File tree

2 files changed

+101
-1
lines changed

2 files changed

+101
-1
lines changed

saltyrtc/server/message.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ def unpack(cls, client, data):
220220

221221
# Decrypt if directed at us and keys have been exchanged
222222
# or just return a raw message to be sent to another client
223+
expect_type = None
223224
if destination_type == AddressType.server:
224225
data = data[NONCE_LENGTH:]
225226
if not client.authenticated and client.type is None:
@@ -231,16 +232,20 @@ def unpack(cls, client, data):
231232
cls._decrypt_payload(client, nonce, data))
232233
except MessageError:
233234
pass
235+
else:
236+
expect_type = MessageType.client_auth
234237

235238
# Try client-hello (unencrypted)
236239
if payload is None:
237240
try:
238241
payload = cls._unpack_payload(data)
239242
except MessageError:
240243
payload = None
244+
else:
245+
expect_type = MessageType.client_hello
241246

242247
# Still no payload?
243-
if payload is None:
248+
if expect_type is None or payload is None:
244249
message = 'Expected either client-hello or client-auth, got neither'
245250
raise MessageError(message)
246251
else:
@@ -266,6 +271,10 @@ def unpack(cls, client, data):
266271
except ValueError as exc:
267272
raise MessageError('Unknown message type: {}'.format(type_)) from exc
268273

274+
# Ensure type isn't violated
275+
if expect_type is not None and type_ != expect_type:
276+
raise MessageError('Expected type {}, got {}'.format(expect_type, type_))
277+
269278
# Check and convert payload on appropriate message class
270279
try:
271280
message_class = cls._get_message_classes()[type_]

tests/test_protocol.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,33 @@ def test_subprotocol_downgrade_2(
354354
assert not client.ws_client.open
355355
assert client.ws_client.close_code == CloseCode.protocol_error
356356

357+
@pytest.mark.asyncio
358+
def test_initiator_handshake_unencrypted(
359+
self, cookie_factory, pack_nonce, server, client_factory
360+
):
361+
"""
362+
Check that we cannot do a complete handshake for an initiator
363+
when 'client-auth' is not encrypted.
364+
"""
365+
client = yield from client_factory()
366+
367+
# server-hello, already checked in another test
368+
message, _, sck, s, d, start_scsn = yield from client.recv()
369+
370+
# client-auth
371+
cck, ccsn = cookie_factory(), 2**32 - 1
372+
yield from client.send(pack_nonce(cck, 0x00, 0x00, ccsn), {
373+
'type': 'client-auth',
374+
'your_cookie': sck,
375+
'subprotocols': pytest.saltyrtc.subprotocols,
376+
})
377+
ccsn += 1
378+
379+
# Expect protocol error
380+
yield from server.wait_connections_closed()
381+
assert not client.ws_client.open
382+
assert client.ws_client.close_code == CloseCode.protocol_error
383+
357384
@pytest.mark.asyncio
358385
def test_initiator_handshake(
359386
self, cookie_factory, initiator_key, pack_nonce, server, client_factory,
@@ -447,6 +474,39 @@ def test_responder_handshake(
447474
yield from client.close()
448475
yield from server.wait_connections_closed()
449476

477+
@pytest.mark.asyncio
478+
def test_responder_handshake_unencrypted(
479+
self, cookie_factory, responder_key, pack_nonce, client_factory, server
480+
):
481+
"""
482+
Check that we can do a complete handshake for a responder.
483+
"""
484+
client = yield from client_factory()
485+
486+
# server-hello, already checked in another test
487+
message, _, sck, s, d, start_scsn = yield from client.recv()
488+
489+
# client-hello
490+
cck, ccsn = cookie_factory(), 2**32 - 1
491+
yield from client.send(pack_nonce(cck, 0x00, 0x00, ccsn), {
492+
'type': 'client-hello',
493+
'key': responder_key.pk,
494+
})
495+
ccsn += 1
496+
497+
# client-auth
498+
yield from client.send(pack_nonce(cck, 0x00, 0x00, ccsn), {
499+
'type': 'client-auth',
500+
'your_cookie': sck,
501+
'subprotocols': pytest.saltyrtc.subprotocols,
502+
})
503+
ccsn += 1
504+
505+
# Expect protocol error
506+
yield from server.wait_connections_closed()
507+
assert not client.ws_client.open
508+
assert client.ws_client.close_code == CloseCode.protocol_error
509+
450510
@pytest.mark.asyncio
451511
def test_client_factory_handshake(
452512
self, server, client_factory, initiator_key, responder_key
@@ -633,6 +693,37 @@ def test_invalid_destination_after_handshake(
633693
assert not responder.ws_client.open
634694
assert responder.ws_client.close_code == CloseCode.protocol_error
635695

696+
@pytest.mark.asyncio
697+
def test_unencrypted_packet_after_initiator_handshake(
698+
self, pack_nonce, server, client_factory
699+
):
700+
"""
701+
Check that the server closes with Protocol Error when an
702+
unencrypted packet is being sent by an initiator.
703+
"""
704+
# Initiator handshake
705+
initiator, i = yield from client_factory(initiator_handshake=True)
706+
assert len(i['responders']) == 0
707+
708+
# Drop non-existing responder (encrypted)
709+
yield from initiator.send(pack_nonce(i['cck'], 0x01, 0x00, i['ccsn']), {
710+
'type': 'drop-responder',
711+
'id': 0x02,
712+
})
713+
i['ccsn'] += 1
714+
715+
# Drop non-existing responder (unencrypted)
716+
yield from initiator.send(pack_nonce(i['cck'], 0x01, 0x00, i['ccsn']), {
717+
'type': 'drop-responder',
718+
'id': 0x02,
719+
}, box=None)
720+
i['ccsn'] += 1
721+
722+
# Expect protocol error
723+
yield from server.wait_connections_closed()
724+
assert not initiator.ws_client.open
725+
assert initiator.ws_client.close_code == CloseCode.protocol_error
726+
636727
@pytest.mark.asyncio
637728
def test_new_initiator(self, server, client_factory):
638729
"""

0 commit comments

Comments
 (0)