Skip to content

Commit 1ad38b9

Browse files
committed
send CCS only when client asks, don't allow CCS post handshake
1 parent 25c3fec commit 1ad38b9

File tree

3 files changed

+124
-7
lines changed

3 files changed

+124
-7
lines changed

tlslite/tlsconnection.py

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -734,7 +734,9 @@ def _clientSendClientHello(self, settings, session, srpUsername,
734734
create(bytearray(b'')))
735735
self._client_keypair = certParams
736736

737+
# fake session_id for middlebox compatibility mode
737738
session_id = getRandomBytes(32)
739+
738740
extensions.append(SupportedVersionsExtension().
739741
create(settings.versions))
740742

@@ -964,6 +966,13 @@ def _clientGetServerHello(self, settings, session, clientHello):
964966
"update to Client Hello"):
965967
yield result
966968

969+
if clientHello.session_id != hello_retry.session_id:
970+
for result in self._sendError(
971+
AlertDescription.illegal_parameter,
972+
"Received HRR session_id does not match the one in "
973+
"ClientHello"):
974+
yield result
975+
967976
ext = clientHello.getExtension(ExtensionType.pre_shared_key)
968977
if ext:
969978
# move the extension to end (in case extension like cookie was
@@ -979,8 +988,12 @@ def _clientGetServerHello(self, settings, session, clientHello):
979988
if session else None)
980989

981990
# resend the client hello with performed changes
982-
ccs = ChangeCipherSpec().create()
983-
for result in self._sendMsgs([ccs, clientHello]):
991+
msgs = []
992+
if clientHello.session_id:
993+
ccs = ChangeCipherSpec().create()
994+
msgs.append(ccs)
995+
msgs.append(clientHello)
996+
for result in self._sendMsgs(msgs):
984997
yield result
985998
self._ccs_sent = True
986999

@@ -1023,6 +1036,13 @@ def _clientGetServerHello(self, settings, session, clientHello):
10231036
"Too new version: {0} (max: {1})"
10241037
.format(real_version, settings.maxVersion)):
10251038
yield result
1039+
if real_version > (3, 3) and \
1040+
serverHello.session_id != clientHello.session_id:
1041+
for result in self._sendError(
1042+
AlertDescription.illegal_parameter,
1043+
"Received ServerHello session_id does not match the one "
1044+
"in ClientHello"):
1045+
yield result
10261046
cipherSuites = CipherSuite.filterForVersion(clientHello.cipher_suites,
10271047
minVersion=real_version,
10281048
maxVersion=real_version)
@@ -1460,7 +1480,7 @@ def _clientTLS13Handshake(self, settings, session, clientHello,
14601480
cl_finished = Finished(self.version, prf_size)
14611481
cl_finished.create(cl_verify_data)
14621482

1463-
if not self._ccs_sent:
1483+
if not self._ccs_sent and clientHello.session_id:
14641484
ccs = ChangeCipherSpec().create()
14651485
msgs = [ccs, cl_finished]
14661486
else:
@@ -1469,6 +1489,8 @@ def _clientTLS13Handshake(self, settings, session, clientHello,
14691489
for result in self._sendMsgs(msgs):
14701490
yield result
14711491

1492+
# CCS messages are not allowed in post handshake authentication
1493+
self._middlebox_compat_mode = False
14721494

14731495
# fully switch to application data
14741496
self._changeWriteState()
@@ -2637,7 +2659,7 @@ def _serverTLS13Handshake(self, settings, clientHello, cipherSuite,
26372659

26382660
msgs = []
26392661
msgs.append(serverHello)
2640-
if not self._ccs_sent:
2662+
if not self._ccs_sent and clientHello.session_id:
26412663
ccs = ChangeCipherSpec().create()
26422664
msgs.append(ccs)
26432665
for result in self._sendMsgs(msgs):
@@ -2888,6 +2910,9 @@ def _serverTLS13Handshake(self, settings, clientHello, cipherSuite,
28882910
"Finished value is not valid"):
28892911
yield result
28902912

2913+
# disallow CCS messages after handshake
2914+
self._middlebox_compat_mode = False
2915+
28912916
resumption_master_secret = derive_secret(secret,
28922917
bytearray(b'res master'),
28932918
self._handshake_hash,
@@ -3647,8 +3672,11 @@ def _serverGetClientHello(self, settings, private_key, cert_chain,
36473672
hrr.create((3, 3), TLS_1_3_HRR, clientHello.session_id,
36483673
cipherSuite, extensions=hrr_ext)
36493674

3650-
ccs = ChangeCipherSpec().create()
3651-
for result in self._sendMsgs([hrr, ccs]):
3675+
msgs = [hrr]
3676+
if clientHello.session_id:
3677+
ccs = ChangeCipherSpec().create()
3678+
msgs.append(ccs)
3679+
for result in self._sendMsgs(msgs):
36523680
yield result
36533681
self._ccs_sent = True
36543682

tlslite/tlsrecordlayer.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,11 @@ def __init__(self, sock):
202202
# doesn't provide a certificate
203203
self.client_cert_required = False
204204

205+
# boolean to control if ChangeCipherSpec in TLS1.3 is allowed or not
206+
# we start with True, as peers MUST always processe the messages
207+
# before the handshake is done, only then we disable it (for PHA)
208+
self._middlebox_compat_mode = True
209+
205210
@property
206211
def _send_record_limit(self):
207212
"""Maximum size of payload that can be sent."""
@@ -1006,6 +1011,7 @@ def _getMsg(self, expectedType, secondaryType=None, constructorType=None):
10061011
# continue
10071012
if self.version > (3, 3) and \
10081013
ContentType.handshake in expectedType and \
1014+
self._middlebox_compat_mode and \
10091015
recordHeader.type == ContentType.change_cipher_spec:
10101016
ccs = ChangeCipherSpec().parse(p)
10111017
if ccs.type != 1:

unit_tests/test_tlslite_tlsconnection.py

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,17 @@
1414

1515
from tlslite.recordlayer import RecordLayer
1616
from tlslite.messages import ServerHello, ClientHello, Alert, RecordHeader3
17-
from tlslite.constants import CipherSuite, AlertDescription, ContentType
17+
from tlslite.constants import CipherSuite, AlertDescription, ContentType, \
18+
GroupName, TLS_1_3_HRR
1819
from tlslite.tlsconnection import TLSConnection
1920
from tlslite.errors import TLSLocalAlert, TLSRemoteAlert
2021
from tlslite.x509 import X509
2122
from tlslite.x509certchain import X509CertChain
2223
from tlslite.utils.keyfactory import parsePEMKey
2324
from tlslite.handshakesettings import HandshakeSettings
2425
from tlslite.session import Session
26+
from tlslite.extensions import SrvSupportedVersionsExtension, \
27+
ServerKeyShareExtension, KeyShareEntry, HRRKeyShareExtension
2528

2629
from unit_tests.mocksock import MockSocket
2730

@@ -205,6 +208,86 @@ def test_server_with_client_not_using_required_EMS(self):
205208
self.assertEqual(err.exception.description,
206209
AlertDescription.insufficient_security)
207210

211+
def test_client_with_server_responing_with_wrong_session_id_in_TLS1_3(self):
212+
# socket to generate the faux response
213+
gen_sock = MockSocket(bytearray(0))
214+
215+
gen_record_layer = RecordLayer(gen_sock)
216+
gen_record_layer.version = (3, 3)
217+
218+
srv_ext = []
219+
srv_ext.append(SrvSupportedVersionsExtension().create((3, 4)))
220+
srv_ext.append(ServerKeyShareExtension().create(
221+
KeyShareEntry().create(
222+
GroupName.secp256r1, bytearray(b'\x03' + b'\x01' * 32))))
223+
224+
server_hello = ServerHello().create(
225+
version=(3, 3),
226+
random=bytearray(32),
227+
session_id=bytearray(b"test"),
228+
cipher_suite=CipherSuite.TLS_AES_128_GCM_SHA256,
229+
certificate_type=None,
230+
tackExt=None,
231+
next_protos_advertised=None,
232+
extensions=srv_ext)
233+
234+
for res in gen_record_layer.sendRecord(server_hello):
235+
if res in (0, 1):
236+
self.assertTrue(False, "Blocking socket")
237+
else:
238+
break
239+
240+
# test proper
241+
sock = MockSocket(gen_sock.sent[0])
242+
243+
conn = TLSConnection(sock)
244+
245+
with self.assertRaises(TLSLocalAlert) as err:
246+
conn.handshakeClientCert()
247+
248+
self.assertEqual(err.exception.description,
249+
AlertDescription.illegal_parameter)
250+
251+
def test_client_with_server_responing_with_wrong_session_id_in_TLS1_3_HRR(self):
252+
# socket to generate the faux response
253+
gen_sock = MockSocket(bytearray(0))
254+
255+
gen_record_layer = RecordLayer(gen_sock)
256+
gen_record_layer.version = (3, 3)
257+
258+
srv_ext = []
259+
srv_ext.append(SrvSupportedVersionsExtension().create((3, 4)))
260+
srv_ext.append(HRRKeyShareExtension().create(
261+
GroupName.secp521r1))
262+
263+
server_hello = ServerHello().create(
264+
version=(3, 3),
265+
random=TLS_1_3_HRR,
266+
session_id=bytearray(b"test"),
267+
cipher_suite=CipherSuite.TLS_AES_128_GCM_SHA256,
268+
certificate_type=None,
269+
tackExt=None,
270+
next_protos_advertised=None,
271+
extensions=srv_ext)
272+
273+
for res in gen_record_layer.sendRecord(server_hello):
274+
if res in (0, 1):
275+
self.assertTrue(False, "Blocking socket")
276+
else:
277+
break
278+
279+
# test proper
280+
sock = MockSocket(gen_sock.sent[0])
281+
282+
conn = TLSConnection(sock)
283+
284+
with self.assertRaises(TLSLocalAlert) as err:
285+
conn.handshakeClientCert()
286+
287+
self.assertEqual(err.exception.description,
288+
AlertDescription.illegal_parameter)
289+
290+
208291
def prepare_mock_socket_with_handshake_failure(self):
209292
alertObj = Alert().create(AlertDescription.handshake_failure)
210293
alert = alertObj.write()

0 commit comments

Comments
 (0)