From 5040ed2a1b1f156c72cefe1f81b0d34fcd38f628 Mon Sep 17 00:00:00 2001 From: NoName115 Date: Wed, 23 Jan 2019 12:41:43 +0100 Subject: [PATCH 1/5] add constants & KeyUpdate msg class --- tlslite/constants.py | 8 ++++++++ tlslite/messages.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/tlslite/constants.py b/tlslite/constants.py index 2d37e145c..b19f3cd69 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -125,6 +125,7 @@ class HandshakeType(TLSEnum): client_key_exchange = 16 finished = 20 certificate_status = 22 + key_update = 24 next_protocol = 67 message_hash = 254 # TLS 1.3 @@ -409,6 +410,13 @@ class HeartbeatMessageType(TLSEnum): heartbeat_response = 2 +class KeyUpdateMessageType(TLSEnum): + """Types of keyupdate messages from RFC 8446""" + + update_not_requested = 0 + update_requested = 1 + + class AlertLevel(TLSEnum): """Enumeration of TLS Alert protocol levels""" diff --git a/tlslite/messages.py b/tlslite/messages.py index 712e84fe8..febf7be21 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -2307,3 +2307,35 @@ def _message_type(self): def __str__(self): """Return human readable representation of heartbeat message.""" return "heartbeat {0}".format(self._message_type) + + +class KeyUpdate(HandshakeMsg): + """ + Handling KeyUpdate message from RFC 8446 + + @type message_type: int + @ivar message_type: type of message (update_not_requested or + update_requested) + """ + + def __init__(self): + super(KeyUpdate, self).__init__(HandshakeType.key_update) + self.message_type = 0 + + def create(self, message_type): + """Create KeyUpdate message with selected parameter.""" + self.message_type = message_type + return self + + def parse(self, p): + """Deserialize keyupdate message from parser.""" + p.startLengthCheck(3) + self.message_type = p.get(1) + p.stopLengthCheck() + return self + + def write(self): + """Serialise keyupdate message.""" + writer = Writer() + writer.add(self.message_type, 1) + return self.postWrite(writer) From 0beed02298bb526b3dd566568dcff5bb632e2a60 Mon Sep 17 00:00:00 2001 From: NoName115 Date: Wed, 23 Jan 2019 12:42:51 +0100 Subject: [PATCH 2/5] add handling in recordlayer for KeyUpdate msg calculate new traffic keys --- tlslite/recordlayer.py | 52 +++++++++++++++++++++++++++++++++++++ tlslite/tlsrecordlayer.py | 54 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 105 insertions(+), 1 deletion(-) diff --git a/tlslite/recordlayer.py b/tlslite/recordlayer.py index 9d7cc2a98..9f124c983 100644 --- a/tlslite/recordlayer.py +++ b/tlslite/recordlayer.py @@ -1316,3 +1316,55 @@ def calcTLS1_3PendingState(self, cipherSuite, cl_traffic_secret, else: self._pendingWriteState = serverPendingState self._pendingReadState = clientPendingState + + def _calcTLS1_3KeyUpdate(self, cipherSuite, app_secret): + prf_name, prf_length = ('sha384', 48) if cipherSuite \ + in CipherSuite.sha384PrfSuites \ + else ('sha256', 32) + key_length, iv_length, cipher_func = \ + self._getCipherSettings(cipherSuite) + iv_length = 12 + + new_app_secret = HKDF_expand_label(app_secret, + b"traffic upd", b"", + prf_length, + prf_name) + new_state = ConnectionState() + new_state.macContext = None + new_state.encContext = \ + cipher_func(HKDF_expand_label(new_app_secret, + b"key", b"", + key_length, + prf_name), + None) + new_state.fixedNonce = HKDF_expand_label(new_app_secret, + b"iv", b"", + iv_length, + prf_name) + return new_app_secret, new_state + + def calcTLS1_3KeyUpdate_sender(self, cipherSuite, cl_app_secret, + sr_app_secret): + if self.client: + new_sr_app_secret, server_state = self._calcTLS1_3KeyUpdate( + cipherSuite, sr_app_secret) + self._readState = server_state + return cl_app_secret, new_sr_app_secret + else: + new_cl_app_secret, client_state = self._calcTLS1_3KeyUpdate( + cipherSuite, cl_app_secret) + self._readState = client_state + return new_cl_app_secret, sr_app_secret + + def calcTLS1_3KeyUpdate_reciever(self, cipherSuite, cl_app_secret, + sr_app_secret): + if self.client: + new_cl_app_secret, client_state = self._calcTLS1_3KeyUpdate( + cipherSuite, cl_app_secret) + self._writeState = client_state + return new_cl_app_secret, sr_app_secret + else: + new_sr_app_secret, server_state = self._calcTLS1_3KeyUpdate( + cipherSuite, sr_app_secret) + self._writeState = server_state + return cl_app_secret, new_sr_app_secret diff --git a/tlslite/tlsrecordlayer.py b/tlslite/tlsrecordlayer.py index cee6bf5f7..0d6e2da7b 100644 --- a/tlslite/tlsrecordlayer.py +++ b/tlslite/tlsrecordlayer.py @@ -300,7 +300,8 @@ def readAsync(self, max=None, min=1): if self.version > (3, 3): allowedTypes = (ContentType.application_data, ContentType.handshake) - allowedHsTypes = HandshakeType.new_session_ticket + allowedHsTypes = (HandshakeType.new_session_ticket, + HandshakeType.key_update) else: allowedTypes = ContentType.application_data allowedHsTypes = None @@ -315,6 +316,10 @@ def readAsync(self, max=None, min=1): result.time = time.time() self.tickets.append(result) continue + if isinstance(result, KeyUpdate): + for result in self._handle_keyupdate_request(result): + yield result + continue applicationData = result self._readBuffer += applicationData.write() except TLSRemoteAlert as alert: @@ -947,6 +952,8 @@ def _getMsg(self, expectedType, secondaryType=None, constructorType=None): yield EncryptedExtensions().parse(p) elif subType == HandshakeType.new_session_ticket: yield NewSessionTicket().parse(p) + elif subType == HandshakeType.key_update: + yield KeyUpdate().parse(p) else: raise AssertionError() @@ -1128,3 +1135,48 @@ def send_heartbeat_request(self, payload, padding_length): """ for _ in self.write_heartbeat(payload, padding_length): pass + + def _handle_keyupdate_request(self, request): + """Process the KeyUpdate request. + + @type request: KeyUpdate + @param request: Recieved KeyUpdate message. + """ + if request.message_type == KeyUpdateMessageType.update_not_requested or\ + request.message_type == KeyUpdateMessageType.update_requested: + self.session.cl_app_secret, self.session.sr_app_secret = self._recordLayer.\ + calcTLS1_3KeyUpdate_sender( + self.session.cipherSuite, + self.session.cl_app_secret, + self.session.sr_app_secret) + if request.message_type == KeyUpdateMessageType.update_requested: + keyupdate_request = KeyUpdate().create( + KeyUpdateMessageType.update_not_requested) + for result in self._sendMsg(keyupdate_request): + yield result + self.session.cl_app_secret, self.session.sr_app_secret = self._recordLayer.\ + calcTLS1_3KeyUpdate_reciever( + self.session.cipherSuite, + self.session.cl_app_secret, + self.session.sr_app_secret) + else: + for result in self._sendError( + AlertDescription.illegal_parameter, + "Received KeyUpdate request with unknown message_type"): + yield result + + def send_keyupdate_request(self, message_type): + """Send a KeyUpdate message. + + @type payload: int + @param payload: Type of KeyUpdate message. + + @raise socket.error: If a socket error occurs. + """ + if self.closed: + raise TLSClosedConnectionError( + "attempt to write to closed connection") + + keyupdate_request = KeyUpdate().create(message_type) + for result in self._sendMsg(keyupdate_request): + yield result From b7976505deb9e05a499e8e974aec481d18375a66 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 24 May 2019 16:42:34 +0200 Subject: [PATCH 3/5] refuse to send KeyUpdate in earlier protocol versions --- tlslite/tlsrecordlayer.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tlslite/tlsrecordlayer.py b/tlslite/tlsrecordlayer.py index 0d6e2da7b..be5e280a7 100644 --- a/tlslite/tlsrecordlayer.py +++ b/tlslite/tlsrecordlayer.py @@ -1176,6 +1176,9 @@ def send_keyupdate_request(self, message_type): if self.closed: raise TLSClosedConnectionError( "attempt to write to closed connection") + if self.version != (3, 4): + raise TLSIllegalParameterException("KeyUpdate is a TLS 1.3 specific" + " feature") keyupdate_request = KeyUpdate().create(message_type) for result in self._sendMsg(keyupdate_request): From 1283514f46f0cadc44920c81aea773beb3d0054f Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 24 May 2019 16:43:20 +0200 Subject: [PATCH 4/5] update the keys after sending the KeyUpdate --- tlslite/tlsrecordlayer.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tlslite/tlsrecordlayer.py b/tlslite/tlsrecordlayer.py index be5e280a7..a86e1d2de 100644 --- a/tlslite/tlsrecordlayer.py +++ b/tlslite/tlsrecordlayer.py @@ -1183,3 +1183,8 @@ def send_keyupdate_request(self, message_type): keyupdate_request = KeyUpdate().create(message_type) for result in self._sendMsg(keyupdate_request): yield result + self.session.cl_app_secret, self.session.sr_app_secret = \ + self._recordLayer.calcTLS1_3KeyUpdate_reciever( + self.session.cipherSuite, + self.session.cl_app_secret, + self.session.sr_app_secret) From 22f1887b0f829a4b46af9928d86e5337c6cf3e83 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 24 May 2019 18:08:45 +0200 Subject: [PATCH 5/5] FIXME make server ask for KeyUpdate after successful connection (this should be controlled either by resource requested or a command line switch) --- scripts/tls.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/scripts/tls.py b/scripts/tls.py index 130bc56f8..9bb0fe662 100755 --- a/scripts/tls.py +++ b/scripts/tls.py @@ -31,7 +31,7 @@ from tlslite.api import * from tlslite.constants import CipherSuite, HashAlgorithm, SignatureAlgorithm, \ - GroupName, SignatureScheme + GroupName, SignatureScheme, KeyUpdateMessageType from tlslite import __version__ from tlslite.utils.compat import b2a_hex, a2b_hex, time_stamp from tlslite.utils.dns_utils import is_valid_hostname @@ -561,7 +561,14 @@ def handshake(self, connection): return False else: raise - + for result in connection.send_keyupdate_request(KeyUpdateMessageType.update_requested): + if result in (0, 1): + continue + else: + print("unexpected result from send_keyupdate_request: {0}" + .format(result)) + return False + connection.ignoreAbruptClose = True printGoodConnection(connection, stop-start) printExporter(connection, expLabel, expLength)